diff options
Diffstat (limited to '')
472 files changed, 34704 insertions, 13468 deletions
diff --git a/.gitignore b/.gitignore index 1a58409d8..977ae6a12 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ config.sub config_detected.h config_detected.mk configure +configure.lineno depcomp depmode install-sh @@ -32,9 +33,11 @@ ltmain.sh missing mkinstalldirs mpd +mpd.exe stamp-h1 tags *~ +.#* .stgit* doc/protocol.html doc/protocol @@ -43,10 +46,16 @@ doc/developer doc/sticker doc/api test/software_volume +test/run_convert test/run_decoder test/read_tags +test/run_filter test/run_encoder test/run_output test/read_conf test/run_input test/read_mixer +test/dump_playlist +test/run_normalize +test/tmp +test/run_inotify @@ -19,9 +19,6 @@ Eric Wollesen <encoded@xmtp.net> Thomas Jansen <mithi@mithi.net> multithreading tweaks, miscellaneous -Rasmus Steinke <rasi1979@googlemail.com> - documentation - Romain Bignon <romain@peerfuse.org> playlist manipulation @@ -13,7 +13,7 @@ Dependencies gcc - http://gcc.gnu.org/ Any other C99 compliant compiler should also work. -glib - http://www.gtk.org/ +GLib 2.12 - http://www.gtk.org/ General-purpose utility library. @@ -56,6 +56,12 @@ libshout - http://www.icecast.org/ For streaming to an Icecast or Shoutcast server. You also need an encoder: either libvorbisenc (ogg), or liblame (mp3). +OpenAL - http://kcat.strangesoft.net/openal.html +Open Audio Library + +libffado - http://www.ffado.org/ +For FireWire audio devices. + Optional Input Dependencies --------------------------- @@ -69,6 +75,9 @@ MAD - http://www.underbit.com/products/mad/ For MP3 support. You will need libmad, and optionally libid3tag if you want ID3 tag support. +libmpg123 - http://www.mpg123.de/ +Alternative for MP3 support. + Ogg Vorbis - http://www.xiph.org/ogg/vorbis/ For Ogg Vorbis support. You will need libogg and libvorbis. @@ -104,6 +113,12 @@ 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. + +libwavpack - http://www.wavpack.com/ +For WavPack playback. + Optional Miscellaneous Dependencies ----------------------------------- diff --git a/Makefile.am b/Makefile.am index 083c72a5f..92a2a9f55 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,12 +1,15 @@ ACLOCAL_AMFLAGS = -I m4 -AUTOMAKE_OPTIONS = foreign 1.9 dist-bzip2 +AUTOMAKE_OPTIONS = foreign 1.10 dist-bzip2 subdir-objects AM_CPPFLAGS = -I$(srcdir)/src $(GLIB_CFLAGS) +AM_CPPFLAGS += -DSYSTEM_CONFIG_FILE_LOCATION='"$(sysconfdir)/mpd.conf"' + bin_PROGRAMS = src/mpd src_mpd_CFLAGS = $(AM_CFLAGS) $(MPD_CFLAGS) src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \ + $(LIBWRAP_CFLAGS) \ $(SQLITE_CFLAGS) \ $(ARCHIVE_CFLAGS) \ $(INPUT_CFLAGS) \ @@ -16,6 +19,7 @@ src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \ $(FILTER_CFLAGS) \ $(OUTPUT_CFLAGS) src_mpd_LDADD = $(MPD_LIBS) \ + $(LIBWRAP_LDFLAGS) \ $(SQLITE_LIBS) \ $(ARCHIVE_LIBS) \ $(INPUT_LIBS) \ @@ -27,10 +31,12 @@ src_mpd_LDADD = $(MPD_LIBS) \ $(GLIB_LIBS) mpd_headers = \ + src/check.h \ src/notify.h \ src/ack.h \ src/audio.h \ src/audio_format.h \ + src/audio_check.h \ src/audio_parser.h \ src/output_internal.h \ src/output_api.h \ @@ -42,7 +48,15 @@ mpd_headers = \ src/output_state.h \ src/output_print.h \ src/output_command.h \ - src/buffer2array.h \ + src/filter_internal.h \ + src/filter_config.h \ + src/filter_plugin.h \ + src/filter_registry.h \ + src/filter/autoconvert_filter_plugin.h \ + src/filter/chain_filter_plugin.h \ + src/filter/convert_filter_plugin.h \ + src/filter/replay_gain_filter_plugin.h \ + src/filter/volume_filter_plugin.h \ src/command.h \ src/idle.h \ src/cmdline.h \ @@ -64,23 +78,40 @@ mpd_headers = \ src/encoder_plugin.h \ src/encoder_list.h \ src/encoder_api.h \ + src/exclude.h \ + src/fd_util.h \ src/fifo_buffer.h \ + src/glib_compat.h \ src/update.h \ + src/update_internal.h \ + src/inotify_source.h \ + src/inotify_queue.h \ + src/inotify_update.h \ src/dirvec.h \ src/gcc.h \ src/decoder_list.h \ + src/decoder_print.h \ + src/decoder/flac_compat.h \ + src/decoder/flac_metadata.h \ + src/decoder/flac_pcm.h \ src/decoder/_flac_common.h \ src/decoder/_ogg_common.h \ + src/input_init.h \ src/input_plugin.h \ + src/input_registry.h \ src/input_stream.h \ src/input/file_input_plugin.h \ + src/input/ffmpeg_input_plugin.h \ src/input/curl_input_plugin.h \ src/input/rewind_input_plugin.h \ - src/input/lastfm_input_plugin.h \ src/input/mms_input_plugin.h \ + src/text_file.h \ + src/text_input_stream.h \ src/icy_server.h \ src/icy_metadata.h \ src/client.h \ + src/client_internal.h \ + src/server_socket.h \ src/listen.h \ src/log.h \ src/ls.h \ @@ -91,27 +122,35 @@ 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/mixer/pulse_mixer_plugin.h \ src/daemon.h \ - src/normalize.h \ - src/compress.h \ + src/AudioCompress/config.h \ + src/AudioCompress/compress.h \ src/buffer.h \ src/pipe.h \ src/chunk.h \ src/path.h \ src/mapper.h \ + src/open.h \ src/output/httpd_client.h \ src/output/httpd_internal.h \ + src/output/pulse_output_plugin.h \ + src/output/winmm_output_plugin.h \ src/page.h \ src/pcm_buffer.h \ src/pcm_utils.h \ src/pcm_convert.h \ src/pcm_volume.h \ src/pcm_mix.h \ + src/pcm_byteswap.h \ src/pcm_channels.h \ src/pcm_format.h \ src/pcm_resample.h \ src/pcm_resample_internal.h \ src/pcm_dither.h \ + src/pcm_pack.h \ src/pcm_prng.h \ src/permission.h \ src/player_thread.h \ @@ -121,13 +160,32 @@ mpd_headers = \ src/playlist_print.h \ src/playlist_save.h \ src/playlist_state.h \ + src/playlist_plugin.h \ + src/playlist_list.h \ + src/playlist_mapper.h \ + src/playlist_any.h \ + src/playlist_song.h \ + src/playlist_queue.h \ + src/playlist_vector.h \ + src/playlist_database.h \ + src/playlist/extm3u_playlist_plugin.h \ + src/playlist/m3u_playlist_plugin.h \ + src/playlist/pls_playlist_plugin.h \ + src/playlist/xspf_playlist_plugin.h \ + src/playlist/asx_playlist_plugin.h \ + src/playlist/rss_playlist_plugin.h \ + src/playlist/lastfm_playlist_plugin.h \ + src/playlist/cue_playlist_plugin.h \ + src/playlist/flac_playlist_plugin.h \ src/poison.h \ src/riff.h \ src/aiff.h \ src/queue.h \ src/queue_print.h \ src/queue_save.h \ - src/replay_gain.h \ + src/refcount.h \ + src/replay_gain_config.h \ + src/replay_gain_info.h \ src/sig_handlers.h \ src/song.h \ src/song_print.h \ @@ -145,8 +203,10 @@ mpd_headers = \ src/tag_table.h \ src/tag_ape.h \ src/tag_id3.h \ + src/tag_rva2.h \ src/tag_print.h \ src/tag_save.h \ + src/tokenizer.h \ src/strset.h \ src/uri.h \ src/utils.h \ @@ -158,22 +218,30 @@ mpd_headers = \ src/archive_api.h \ src/archive_internal.h \ src/archive_list.h \ + src/archive_plugin.h \ + src/archive/bz2_archive_plugin.h \ + src/archive/iso9660_archive_plugin.h \ + src/archive/zzip_archive_plugin.h \ src/input/archive_input_plugin.h \ - src/cue/cue_tag.h + src/cue/cue_tag.h\ + src/mpd_error.h src_mpd_SOURCES = \ $(mpd_headers) \ $(ARCHIVE_SRC) \ $(INPUT_SRC) \ + $(PLAYLIST_SRC) \ $(TAG_SRC) \ $(DECODER_SRC) \ $(ENCODER_SRC) \ $(OUTPUT_API_SRC) $(OUTPUT_SRC) \ $(MIXER_API_SRC) $(MIXER_SRC) \ + $(FILTER_SRC) \ src/notify.c \ src/audio.c \ + src/audio_check.c \ + src/audio_format.c \ src/audio_parser.c \ - src/buffer2array.c \ src/command.c \ src/idle.c \ src/cmdline.c \ @@ -184,22 +252,41 @@ src_mpd_SOURCES = \ src/decoder_control.c \ src/decoder_api.c \ src/decoder_internal.c \ + src/decoder_print.c \ src/directory.c \ src/directory_save.c \ src/directory_print.c \ src/database.c \ src/dirvec.c \ + src/exclude.c \ + src/fd_util.c \ src/fifo_buffer.c \ + src/filter_config.c \ + src/filter_plugin.c \ + src/filter_registry.c \ src/update.c \ + src/update_queue.c \ + src/update_walk.c \ + src/update_remove.c \ src/client.c \ + src/client_event.c \ + src/client_expire.c \ + src/client_global.c \ + src/client_idle.c \ + src/client_list.c \ + src/client_new.c \ + src/client_process.c \ + src/client_read.c \ + src/client_write.c \ + src/server_socket.c \ src/listen.c \ src/log.c \ src/ls.c \ src/main.c \ + src/main_win32.c \ src/event_pipe.c \ src/daemon.c \ - src/normalize.c \ - src/compress.c \ + src/AudioCompress/compress.c \ src/buffer.c \ src/pipe.c \ src/chunk.c \ @@ -209,7 +296,9 @@ src_mpd_SOURCES = \ src/pcm_convert.c \ src/pcm_volume.c \ src/pcm_mix.c \ + src/pcm_byteswap.c \ src/pcm_channels.c \ + src/pcm_pack.c \ src/pcm_format.c \ src/pcm_resample.c \ src/pcm_resample_fallback.c \ @@ -223,13 +312,21 @@ src_mpd_SOURCES = \ src/playlist_edit.c \ src/playlist_print.c \ src/playlist_save.c \ + src/playlist_mapper.c \ + src/playlist_any.c \ + src/playlist_song.c \ src/playlist_state.c \ + src/playlist_queue.c \ + src/playlist_vector.c \ + src/playlist_database.c \ src/queue.c \ src/queue_print.c \ src/queue_save.c \ - src/replay_gain.c \ + src/replay_gain_config.c \ + src/replay_gain_info.c \ src/sig_handlers.c \ src/song.c \ + src/song_update.c \ src/song_print.c \ src/song_save.c \ src/songvec.c \ @@ -240,6 +337,9 @@ src_mpd_SOURCES = \ src/tag_pool.c \ src/tag_print.c \ src/tag_save.c \ + src/tokenizer.c \ + src/text_file.c \ + src/text_input_stream.c \ src/strset.c \ src/uri.c \ src/utils.c \ @@ -248,6 +348,13 @@ src_mpd_SOURCES = \ src/stored_playlist.c \ src/timer.c +if ENABLE_INOTIFY +src_mpd_SOURCES += \ + src/inotify_source.c \ + src/inotify_queue.c \ + src/inotify_update.c +endif + if ENABLE_SQLITE src_mpd_SOURCES += \ src/sticker.c \ @@ -277,21 +384,22 @@ ARCHIVE_LIBS = \ ARCHIVE_SRC = if HAVE_BZ2 -ARCHIVE_SRC += src/archive/bz2_plugin.c +ARCHIVE_SRC += src/archive/bz2_archive_plugin.c endif -if HAVE_ZIP -ARCHIVE_SRC += src/archive/zip_plugin.c +if HAVE_ZZIP +ARCHIVE_SRC += src/archive/zzip_archive_plugin.c endif -if HAVE_ISO -ARCHIVE_SRC += src/archive/iso_plugin.c +if HAVE_ISO9660 +ARCHIVE_SRC += src/archive/iso9660_archive_plugin.c endif if ENABLE_ARCHIVE ARCHIVE_SRC += \ src/archive_api.c \ src/archive_list.c \ + src/archive_plugin.c \ src/input/archive_input_plugin.c endif @@ -308,6 +416,7 @@ TAG_SRC = \ if HAVE_ID3TAG TAG_SRC += src/tag_id3.c \ + src/tag_rva2.c \ src/riff.c src/aiff.c endif @@ -316,51 +425,64 @@ endif DECODER_CFLAGS = \ $(VORBIS_CFLAGS) $(TREMOR_CFLAGS) \ $(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \ + $(SNDFILE_CFLAGS) \ $(AUDIOFILE_CFLAGS) \ $(LIBMIKMOD_CFLAGS) \ $(MODPLUG_CFLAGS) \ + $(GME_CFLAGS) \ $(SIDPLAY_CFLAGS) \ $(FLUIDSYNTH_CFLAGS) \ $(WILDMIDI_CFLAGS) \ + $(WAVPACK_CFLAGS) \ $(MAD_CFLAGS) \ + $(MPG123_CFLAGS) \ $(FFMPEG_CFLAGS) \ $(CUE_CFLAGS) DECODER_LIBS = \ $(VORBIS_LIBS) $(TREMOR_LIBS) \ $(FLAC_LIBS) \ + $(SNDFILE_LIBS) \ $(AUDIOFILE_LIBS) $(LIBMIKMOD_LIBS) \ $(MODPLUG_LIBS) \ + $(GME_LIBS) \ $(SIDPLAY_LIBS) \ $(FLUIDSYNTH_LIBS) \ $(WILDMIDI_LIBS) \ + $(WAVPACK_LIBS) \ $(MAD_LIBS) \ + $(MPG123_LIBS) \ $(MP4FF_LIBS) \ $(FFMPEG_LIBS) \ $(CUE_LIBS) DECODER_SRC = \ src/decoder_buffer.c \ + src/decoder_plugin.c \ src/decoder_list.c if HAVE_MAD -DECODER_SRC += src/decoder/mad_plugin.c +DECODER_SRC += src/decoder/mad_decoder_plugin.c +endif + +if HAVE_MPG123 +DECODER_SRC += src/decoder/mpg123_decoder_plugin.c endif if HAVE_MPCDEC -DECODER_SRC += src/decoder/mpcdec_plugin.c +DECODER_SRC += src/decoder/mpcdec_decoder_plugin.c endif if HAVE_WAVPACK -DECODER_SRC += src/decoder/wavpack_plugin.c +DECODER_SRC += src/decoder/wavpack_decoder_plugin.c endif if HAVE_FAAD -DECODER_SRC += src/decoder/faad_plugin.c +DECODER_SRC += src/decoder/faad_decoder_plugin.c endif if HAVE_MP4 -DECODER_SRC += src/decoder/mp4ff_plugin.c +DECODER_SRC += src/decoder/mp4ff_decoder_plugin.c endif if HAVE_OGG_COMMON @@ -368,63 +490,83 @@ DECODER_SRC += src/decoder/_ogg_common.c endif if HAVE_FLAC_COMMON -DECODER_SRC += src/decoder/_flac_common.c +DECODER_SRC += \ + src/decoder/flac_metadata.c \ + src/decoder/flac_pcm.c \ + src/decoder/_flac_common.c endif if ENABLE_VORBIS_DECODER -DECODER_SRC += src/decoder/vorbis_plugin.c +DECODER_SRC += src/decoder/vorbis_decoder_plugin.c endif if HAVE_FLAC -DECODER_SRC += src/decoder/flac_plugin.c +DECODER_SRC += src/decoder/flac_decoder_plugin.c endif if HAVE_OGGFLAC -DECODER_SRC += src/decoder/oggflac_plugin.c +DECODER_SRC += src/decoder/oggflac_decoder_plugin.c endif if HAVE_AUDIOFILE -DECODER_SRC += src/decoder/audiofile_plugin.c +DECODER_SRC += src/decoder/audiofile_decoder_plugin.c endif if ENABLE_MIKMOD_DECODER -DECODER_SRC += src/decoder/mikmod_plugin.c +DECODER_SRC += src/decoder/mikmod_decoder_plugin.c endif if HAVE_MODPLUG -DECODER_SRC += src/decoder/modplug_plugin.c +DECODER_SRC += src/decoder/modplug_decoder_plugin.c endif if ENABLE_SIDPLAY -DECODER_SRC += src/decoder/sidplay_plugin.cxx +DECODER_SRC += src/decoder/sidplay_decoder_plugin.cxx endif if ENABLE_FLUIDSYNTH -DECODER_SRC += src/decoder/fluidsynth_plugin.c +DECODER_SRC += src/decoder/fluidsynth_decoder_plugin.c endif if ENABLE_WILDMIDI -DECODER_SRC += src/decoder/wildmidi_plugin.c +DECODER_SRC += src/decoder/wildmidi_decoder_plugin.c endif if HAVE_FFMPEG -DECODER_SRC += src/decoder/ffmpeg_plugin.c +DECODER_SRC += src/decoder/ffmpeg_decoder_plugin.c +endif + +if ENABLE_SNDFILE +DECODER_SRC += src/decoder/sndfile_decoder_plugin.c +endif + +if HAVE_GME +DECODER_SRC += src/decoder/gme_decoder_plugin.c endif # encoder plugins ENCODER_CFLAGS = \ $(LAME_CFLAGS) \ + $(TWOLAME_CFLAGS) \ + $(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \ $(VORBISENC_CFLAGS) ENCODER_LIBS = \ $(LAME_LIBS) \ + $(TWOLAME_LIBS) \ + $(FLAC_LIBS) \ $(VORBISENC_LIBS) ENCODER_SRC = if ENABLE_ENCODER ENCODER_SRC += src/encoder_list.c +ENCODER_SRC += src/encoder/null_encoder.c + +if ENABLE_WAVE_ENCODER +ENCODER_SRC += src/encoder/wave_encoder.c +endif if ENABLE_VORBIS_ENCODER ENCODER_SRC += src/encoder/vorbis_encoder.c @@ -433,6 +575,14 @@ 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 + +if ENABLE_FLAC_ENCODER +ENCODER_SRC += src/encoder/flac_encoder.c +endif endif @@ -458,24 +608,28 @@ endif INPUT_CFLAGS = \ $(CURL_CFLAGS) \ + $(FFMPEG_CFLAGS) \ $(MMS_CFLAGS) INPUT_LIBS = \ $(CURL_LIBS) \ + $(FFMPEG_LIBS) \ $(MMS_LIBS) INPUT_SRC = \ + src/input_init.c \ + src/input_registry.c \ src/input_stream.c \ + src/input/rewind_input_plugin.c \ src/input/file_input_plugin.c -if HAVE_CURL +if ENABLE_CURL INPUT_SRC += src/input/curl_input_plugin.c \ - src/input/rewind_input_plugin.c \ src/icy_metadata.c endif -if ENABLE_LASTFM -INPUT_SRC += src/input/lastfm_input_plugin.c +if HAVE_FFMPEG +INPUT_SRC += src/input/ffmpeg_input_plugin.c endif if ENABLE_MMS @@ -486,14 +640,19 @@ endif OUTPUT_CFLAGS = \ $(AO_CFLAGS) \ $(ALSA_CFLAGS) \ + $(FFADO_CFLAGS) \ $(JACK_CFLAGS) \ + $(OPENAL_CFLAGS) \ $(PULSE_CFLAGS) \ $(SHOUT_CFLAGS) OUTPUT_LIBS = \ + $(LIBWRAP_LDFLAGS) \ $(AO_LIBS) \ $(ALSA_LIBS) \ + $(FFADO_LIBS) \ $(JACK_LIBS) \ + $(OPENAL_LIBS) \ $(PULSE_LIBS) \ $(SHOUT_LIBS) @@ -512,14 +671,20 @@ 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 -MIXER_SRC += src/mixer/alsa_mixer.c +MIXER_SRC += src/mixer/alsa_mixer_plugin.c +endif + +if ENABLE_FFADO_OUTPUT +OUTPUT_SRC += src/output/ffado_output_plugin.c endif if HAVE_AO @@ -527,7 +692,7 @@ OUTPUT_SRC += src/output/ao_plugin.c endif if HAVE_FIFO -OUTPUT_SRC += src/output/fifo_plugin.c +OUTPUT_SRC += src/output/fifo_output_plugin.c endif if ENABLE_PIPE_OUTPUT @@ -535,7 +700,7 @@ OUTPUT_SRC += src/output/pipe_output_plugin.c endif if HAVE_JACK -OUTPUT_SRC += src/output/jack_plugin.c +OUTPUT_SRC += src/output/jack_output_plugin.c endif if HAVE_MVP @@ -544,7 +709,11 @@ endif if HAVE_OSS OUTPUT_SRC += src/output/oss_plugin.c -MIXER_SRC += src/mixer/oss_mixer.c +MIXER_SRC += src/mixer/oss_mixer_plugin.c +endif + +if HAVE_OPENAL +OUTPUT_SRC += src/output/openal_plugin.c endif if HAVE_OSX @@ -552,14 +721,18 @@ OUTPUT_SRC += src/output/osx_plugin.c endif if HAVE_PULSE -OUTPUT_SRC += src/output/pulse_plugin.c -MIXER_SRC += src/mixer/pulse_mixer.c +OUTPUT_SRC += src/output/pulse_output_plugin.c +MIXER_SRC += src/mixer/pulse_mixer_plugin.c endif if HAVE_SHOUT OUTPUT_SRC += src/output/shout_plugin.c endif +if ENABLE_RECORDER_OUTPUT +OUTPUT_SRC += src/output/recorder_output_plugin.c +endif + if ENABLE_HTTPD_OUTPUT OUTPUT_SRC += \ src/icy_server.c \ @@ -571,6 +744,52 @@ if ENABLE_SOLARIS_OUTPUT OUTPUT_SRC += src/output/solaris_output_plugin.c endif +if ENABLE_WINMM_OUTPUT +OUTPUT_SRC += src/output/winmm_output_plugin.c +MIXER_SRC += src/mixer/winmm_mixer_plugin.c +endif + + +# +# Playlist plugins +# + +PLAYLIST_SRC = \ + src/playlist/extm3u_playlist_plugin.c \ + src/playlist/m3u_playlist_plugin.c \ + src/playlist/pls_playlist_plugin.c \ + src/playlist/xspf_playlist_plugin.c \ + src/playlist/asx_playlist_plugin.c \ + src/playlist/rss_playlist_plugin.c \ + src/playlist_list.c + +if ENABLE_LASTFM +PLAYLIST_SRC += src/playlist/lastfm_playlist_plugin.c +endif + +if HAVE_CUE +PLAYLIST_SRC += src/playlist/cue_playlist_plugin.c +endif + +if HAVE_FLAC +PLAYLIST_SRC += src/playlist/flac_playlist_plugin.c +endif + + +# +# Filter plugins +# + +FILTER_SRC = \ + src/filter/null_filter_plugin.c \ + src/filter/chain_filter_plugin.c \ + src/filter/autoconvert_filter_plugin.c \ + src/filter/convert_filter_plugin.c \ + src/filter/route_filter_plugin.c \ + src/filter/normalize_filter_plugin.c \ + src/filter/replay_gain_filter_plugin.c \ + src/filter/volume_filter_plugin.c + # # Sparse code analysis @@ -598,21 +817,31 @@ sparse-check: if ENABLE_TEST +TESTS = + noinst_PROGRAMS = \ test/read_conf \ test/run_input \ + test/dump_playlist \ test/run_decoder \ test/read_tags \ + test/run_filter \ test/run_output \ - test/read_mixer \ + test/run_convert \ + test/run_normalize \ test/software_volume +if HAVE_ALSA +# this debug program is still ALSA specific +noinst_PROGRAMS += test/read_mixer +endif + test_read_conf_CPPFLAGS = $(AM_CPPFLAGS) \ $(GLIB_CFLAGS) test_read_conf_LDADD = $(MPD_LIBS) \ $(GLIB_LIBS) test_read_conf_SOURCES = test/read_conf.c \ - src/conf.c src/buffer2array.c src/utils.c + src/conf.c src/tokenizer.c src/utils.c test_run_input_CPPFLAGS = $(AM_CPPFLAGS) \ $(ARCHIVE_CFLAGS) \ @@ -622,11 +851,43 @@ test_run_input_LDADD = $(MPD_LIBS) \ $(INPUT_LIBS) \ $(GLIB_LIBS) test_run_input_SOURCES = test/run_input.c \ - src/conf.c src/buffer2array.c src/utils.c \ + src/conf.c src/tokenizer.c src/utils.c \ src/tag.c src/tag_pool.c src/tag_save.c \ + src/fd_util.c \ $(ARCHIVE_SRC) \ $(INPUT_SRC) +test_dump_playlist_CPPFLAGS = $(AM_CPPFLAGS) \ + $(CUE_CFLAGS) \ + $(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \ + $(ARCHIVE_CFLAGS) \ + $(INPUT_CFLAGS) +test_dump_playlist_LDADD = $(MPD_LIBS) \ + $(CUE_LIBS) \ + $(FLAC_LIBS) \ + $(ARCHIVE_LIBS) \ + $(INPUT_LIBS) \ + $(GLIB_LIBS) +test_dump_playlist_SOURCES = test/dump_playlist.c \ + src/conf.c src/tokenizer.c src/utils.c \ + src/uri.c \ + src/song.c src/tag.c src/tag_pool.c src/tag_save.c \ + src/text_input_stream.c src/fifo_buffer.c \ + src/fd_util.c \ + $(ARCHIVE_SRC) \ + $(INPUT_SRC) \ + $(PLAYLIST_SRC) + +if HAVE_CUE +test_dump_playlist_SOURCES += src/cue/cue_tag.c +endif + +if HAVE_FLAC +test_dump_playlist_SOURCES += \ + src/replay_gain_info.c \ + src/decoder/flac_metadata.c +endif + test_run_decoder_CPPFLAGS = $(AM_CPPFLAGS) \ $(TAG_CFLAGS) \ $(ARCHIVE_CFLAGS) \ @@ -637,10 +898,13 @@ test_run_decoder_LDADD = $(MPD_LIBS) \ $(INPUT_LIBS) $(DECODER_LIBS) \ $(GLIB_LIBS) test_run_decoder_SOURCES = test/run_decoder.c \ - src/conf.c src/buffer2array.c src/utils.c src/log.c \ + src/conf.c src/tokenizer.c src/utils.c src/log.c \ src/tag.c src/tag_pool.c \ - src/replay_gain.c \ + src/replay_gain_info.c \ src/uri.c \ + src/fd_util.c \ + src/audio_check.c \ + src/audio_format.c \ src/timer.c \ $(ARCHIVE_SRC) \ $(INPUT_SRC) \ @@ -657,22 +921,50 @@ test_read_tags_LDADD = $(MPD_LIBS) \ $(INPUT_LIBS) $(DECODER_LIBS) \ $(GLIB_LIBS) test_read_tags_SOURCES = test/read_tags.c \ - src/conf.c src/buffer2array.c src/utils.c src/log.c \ + src/conf.c src/tokenizer.c src/utils.c src/log.c \ src/tag.c src/tag_pool.c \ - src/replay_gain.c \ + src/replay_gain_info.c \ src/uri.c \ + src/fd_util.c \ + src/audio_check.c \ src/timer.c \ $(ARCHIVE_SRC) \ $(INPUT_SRC) \ $(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/tokenizer.c src/utils.c \ + src/pcm_volume.c src/pcm_convert.c src/pcm_byteswap.c \ + src/pcm_format.c src/pcm_channels.c src/pcm_dither.c \ + src/pcm_pack.c \ + src/pcm_resample.c src/pcm_resample_fallback.c \ + src/audio_check.c \ + src/audio_format.c \ + src/audio_parser.c \ + src/replay_gain_config.c \ + src/replay_gain_info.c \ + src/AudioCompress/compress.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 \ - src/conf.c src/buffer2array.c \ + src/conf.c src/tokenizer.c \ src/utils.c \ src/tag.c src/tag_pool.c \ + src/audio_check.c \ + src/audio_format.c \ src/audio_parser.c \ $(ENCODER_SRC) test_run_encoder_LDADD = $(MPD_LIBS) \ @@ -681,11 +973,42 @@ test_run_encoder_LDADD = $(MPD_LIBS) \ endif test_software_volume_SOURCES = test/software_volume.c \ + src/audio_check.c \ src/audio_parser.c \ src/pcm_volume.c test_software_volume_LDADD = \ $(GLIB_LIBS) +test_run_normalize_SOURCES = test/run_normalize.c \ + src/audio_check.c \ + src/audio_parser.c \ + src/AudioCompress/compress.c +test_run_normalize_LDADD = \ + $(GLIB_LIBS) + +test_run_convert_SOURCES = test/run_convert.c \ + src/fifo_buffer.c \ + src/audio_format.c \ + src/audio_check.c \ + src/audio_parser.c \ + src/pcm_channels.c \ + src/pcm_format.c \ + src/pcm_pack.c \ + src/pcm_dither.c \ + src/pcm_byteswap.c \ + src/pcm_resample.c \ + src/pcm_resample_fallback.c \ + src/pcm_convert.c +test_run_convert_CPPFLAGS = $(AM_CPPFLAGS) $(SAMPLERATE_CFLAGS) +test_run_convert_LDADD = \ + $(SAMPLERATE_LIBS) \ + $(GLIB_LIBS) + +if HAVE_LIBSAMPLERATE +test_run_convert_SOURCES += src/pcm_resample_libsamplerate.c +endif + +test_run_output_CFLAGS = $(AM_CFLAGS) $(MPD_CFLAGS) test_run_output_CPPFLAGS = $(AM_CPPFLAGS) \ $(ENCODER_CFLAGS) \ $(OUTPUT_CFLAGS) @@ -694,7 +1017,9 @@ test_run_output_LDADD = $(MPD_LIBS) \ $(OUTPUT_LIBS) \ $(GLIB_LIBS) test_run_output_SOURCES = test/run_output.c \ - src/conf.c src/buffer2array.c src/utils.c src/log.c \ + src/conf.c src/tokenizer.c src/utils.c src/log.c \ + src/audio_check.c \ + src/audio_format.c \ src/audio_parser.c \ src/timer.c \ src/tag.c src/tag_pool.c \ @@ -705,7 +1030,21 @@ 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_config.c \ + src/filter/autoconvert_filter_plugin.c \ + src/filter/convert_filter_plugin.c \ + src/filter/replay_gain_filter_plugin.c \ + src/filter/normalize_filter_plugin.c \ + src/filter/volume_filter_plugin.c \ + src/pcm_volume.c \ + src/AudioCompress/compress.c \ + src/replay_gain_info.c \ + src/replay_gain_config.c \ + src/fd_util.c \ + src/server_socket.c \ $(OUTPUT_SRC) test_read_mixer_CPPFLAGS = $(AM_CPPFLAGS) \ @@ -714,10 +1053,34 @@ test_read_mixer_LDADD = $(MPD_LIBS) \ $(OUTPUT_LIBS) \ $(GLIB_LIBS) test_read_mixer_SOURCES = test/read_mixer.c \ - src/conf.c src/buffer2array.c src/utils.c src/log.c \ + src/conf.c src/tokenizer.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 \ + src/fd_util.c \ $(MIXER_SRC) +if ENABLE_BZIP2_TEST +TESTS += test/test_archive_bzip2.sh +endif + +if ENABLE_ZZIP_TEST +TESTS += test/test_archive_zzip.sh +endif + +if ENABLE_ISO9660_TEST +TESTS += test/test_archive_iso9660.sh +endif + +if ENABLE_INOTIFY +noinst_PROGRAMS += test/run_inotify +test_run_inotify_SOURCES = test/run_inotify.c \ + src/fd_util.c \ + src/fifo_buffer.c \ + src/inotify_source.c +test_run_inotify_LDADD = $(GLIB_LIBS) +endif + endif @@ -728,7 +1091,7 @@ endif man_MANS = doc/mpd.1 doc/mpd.conf.5 doc_DATA = AUTHORS COPYING NEWS README UPGRADING doc/mpdconf.example -DOCBOOK_FILES = doc/protocol.xml doc/user.xml doc/developer.xml doc/sticker.xml +DOCBOOK_FILES = doc/protocol.xml doc/user.xml doc/developer.xml if ENABLE_DOCUMENTATION protocoldir = $(docdir)/protocol @@ -755,7 +1118,8 @@ endif doc/api/html/index.html: doc/doxygen.conf @mkdir -p $(@D) - $(DOXYGEN) $< + [ "$(srcdir)" = "." ] || sed '/INPUT *=/ s/\([^ ]\+\/\)/$(subst /,\/,$(srcdir))\/\1/g' $(srcdir)/doc/doxygen.conf >doc/doxygen.conf + $(DOXYGEN) doc/doxygen.conf all-local: $(DOCBOOK_HTML) doc/api/html/index.html @@ -765,10 +1129,12 @@ clean-local: install-data-local: doc/api/html/index.html $(mkinstalldirs) $(DESTDIR)$(docdir)/api/html - $(INSTALL_DATA) -c -m 644 doc/api/html/*.html doc/api/html/*.css \ - doc/api/html/*.png doc/api/html/*.gif \ + $(INSTALL_DATA) -c -m 644 doc/api/html/*.* \ $(DESTDIR)$(docdir)/api/html +uninstall-local: + rm -f $(DESTDIR)$(docdir)/api/html/*.* + upload: $(DOCBOOK_HTML) doc/api/html/index.html rsync -vpruz --delete doc/ cirrus@www.musicpd.org:/var/www/musicpd.org/www/doc/ \ --chmod=Dug+rwx,Do+rx,Fug+rw,Fo+r \ @@ -1,3 +1,114 @@ +ver 0.16 (20??/??/??) +* protocol: + - send song modification time to client + - added "update" idle event + - removed the deprecated "volume" command + - added the "findadd" command + - range support for "delete" + - "previous" really plays the previous song + - "addid" with negative position is deprecated + - "load" supports remote playlists (extm3u, pls, asx, xspf, lastfm://) + - allow changing replay gain mode on-the-fly + - omitting the range end is possible + - "update" checks if the path is malformed +* archive: + - iso: renamed plugin to "iso9660" + - zip: renamed plugin to "zzip" +* input: + - lastfm: obsolete plugin removed + - ffmpeg: new input plugin using libavformat's "avio" library +* tags: + - added tags "ArtistSort", "AlbumArtistSort" + - id3: revised "performer" tag support + - ape: MusicBrainz tags +* decoders: + - don't try a plugin twice (MIME type & suffix) + - don't fall back to "mad" unless no plugin matches + - ffmpeg: support multiple tags + - ffmpeg: convert metadata to generic format + - ffmpeg: implement the libavutil log callback + - sndfile: new decoder plugin based on libsndfile + - flac: moved CUE sheet support to a playlist plugin + - flac: support streams without STREAMINFO block + - mikmod: sample rate is configurable + - mpg123: new decoder plugin based on libmpg123 + - sidplay: support sub-tunes + - sidplay: implemented songlength database + - sidplay: support seeking + - sidplay: play monaural SID tunes in mono + - sidplay: play mus, str, prg, x00 files + - wavpack: activate 32 bit support + - wavpack: allow more than 2 channels + - mp4ff: rename plugin "mp4" to "mp4ff" +* encoders: + - twolame: new encoder plugin based on libtwolame + - flac: new encoder plugin based on libFLAC + - wave: new encoder plugin for PCM WAV format +* output: + - recorder: new output plugin for recording radio streams + - alsa: don't recover on CANCEL + - alsa: fill period buffer with silence before draining + - openal: new output plugin + - pulse: announce "media.role=music" + - pulse: renamed context to "Music Player Daemon" + - pulse: connect to server on MPD startup, implement pause + - jack: require libjack 0.100 + - jack: don't disconnect during pause + - jack: connect to server on MPD startup + - jack: added options "client_name", "server_name" + - jack: clear ring buffers before activating + - jack: renamed option "ports" to "destination_ports" + - jack: support more than two audio channels + - httpd: bind port when output is enabled + - httpd: added name/genre/website configuration + - httpd: implement "pause" + - httpd: bind_to_address support (including IPv6) + - oss: 24 bit support via OSS4 + - win32: new output plugin for Windows Wave + - shout, httpd: more responsive to control commands + - wildcards allowed in audio_format configuration + - consistently lock audio output objects +* player: + - drain audio outputs at the end of the playlist +* 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" +* update: + - automatically update the database with Linux inotify + - support .mpdignore files in the music directory + - sort songs by album name first, then disc/track number + - rescan after metadata_to_use change +* normalize: upgraded to AudioCompress 2.0 + - automatically convert to 16 bit samples +* replay gain: + - reimplemented as a filter plugin + - fall back to track gain if album gain is unavailable + - optionally use hardware mixer to apply replay gain + - added mode "auto" +* log unused/unknown block parameters +* removed the deprecated "error_file" option +* save state when stopped +* renamed option "--stdout" to "--stderr" +* removed options --create-db and --no-create-db +* state_file: save only if something has changed +* database: eliminated maximum line length +* log: redirect stdout/stderr to /dev/null if syslog is used +* set the close-on-exec flag on all file descriptors +* pcm_volume, pcm_mix: implemented 32 bit support +* support packed 24 bit samples +* CUE sheet support +* support for MixRamp tags +* obey $(sysconfdir) for default mpd.conf location +* build with large file support by default +* added test suite ("make check") +* require GLib 2.12 +* added libwrap support +* make single mode 'sticky' + + ver 0.15.15 (2010/11/08) * input: - rewind: fix assertion failure diff --git a/autogen.sh b/autogen.sh index 51fe243d7..28a89c674 100755 --- a/autogen.sh +++ b/autogen.sh @@ -9,20 +9,20 @@ srcdir="`dirname $0`" test -z "$srcdir" && srcdir=. cd "$srcdir" DIE= -AM_VERSIONGREP="sed -e s/.*[^0-9\.]\([0-9]\.[0-9]\).*/\1/" +AM_VERSIONGREP="sed -e s/.*[^0-9\.]\([0-9]\.[0-9][0-9]*\).*/\1/" AC_VERSIONGREP="sed -e s/.*[^0-9\.]\([0-9]\.[0-9][0-9]\).*/\1/" VERSIONMKINT="sed -e s/[^0-9]//" if test -n "$AM_FORCE_VERSION" then AM_VERSIONS="$AM_FORCE_VERSION" else - AM_VERSIONS='1.9 1.10' + AM_VERSIONS='1.11 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 da0ec2d7f..94221506d 100644 --- a/configure.ac +++ b/configure.ac @@ -1,17 +1,16 @@ AC_PREREQ(2.60) -AC_INIT(mpd, 0.15.15, 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 subdir-objects]) 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 -dnl programs -dnl - +dnl --------------------------------------------------------------------------- +dnl Programs +dnl --------------------------------------------------------------------------- AC_PROG_CC_C99 AC_PROG_CXX @@ -34,11 +33,9 @@ AC_PROG_INSTALL AC_PROG_MAKE_SET PKG_PROG_PKG_CONFIG - -dnl -dnl declare variables -dnl - +dnl --------------------------------------------------------------------------- +dnl Declare Variables +dnl --------------------------------------------------------------------------- AC_SUBST(AM_CFLAGS,"") AC_SUBST(MPD_LIBS) @@ -46,11 +43,9 @@ AC_SUBST(MPD_CFLAGS) MPD_LIBS="" MPD_CFLAGS="" - -dnl -dnl OS specific defaults -dnl - +dnl --------------------------------------------------------------------------- +dnl OS Specific Defaults +dnl --------------------------------------------------------------------------- AC_CANONICAL_HOST case "$host_os" in @@ -95,12 +90,10 @@ if test -z "$prefix" || test "x$prefix" = xNONE; then done fi - -dnl -dnl libc features -dnl - -AC_CHECK_FUNCS(syslog) +dnl --------------------------------------------------------------------------- +dnl Header/Library Checks +dnl --------------------------------------------------------------------------- +AC_CHECK_FUNCS(daemon fork syslog) if test $ac_cv_func_syslog = no; then # syslog is not in the default libraries. See if it's in some other. for lib in bsd socket inet; do @@ -113,34 +106,312 @@ fi AC_CHECK_LIB(socket,socket,MPD_LIBS="$MPD_LIBS -lsocket",) AC_CHECK_LIB(nsl,gethostbyname,MPD_LIBS="$MPD_LIBS -lnsl",) +AC_CHECK_FUNCS(pipe2 accept4) + AC_CHECK_LIB(m,exp,MPD_LIBS="$MPD_LIBS -lm",) AC_CHECK_HEADERS(locale.h) AC_CHECK_HEADERS(valgrind/memcheck.h) +dnl --------------------------------------------------------------------------- +dnl Allow tools to be specifically built +dnl --------------------------------------------------------------------------- +AC_ARG_ENABLE(alsa, + AS_HELP_STRING([--enable-alsa], [enable ALSA support]),, + [enable_alsa=auto]) + +AC_ARG_ENABLE(ao, + AS_HELP_STRING([--enable-ao], + [enable support for libao]),, + enable_ao=auto) + +AC_ARG_ENABLE(audiofile, + AS_HELP_STRING([--enable-audiofile], + [enable audiofile support (WAV and others)]),, + enable_audiofile=auto) -dnl -dnl mandatory libraries -dnl +AC_ARG_ENABLE(bzip2, + AS_HELP_STRING([--enable-bzip2], + [enable bzip2 archive support (default: disabled)]),, + enable_bzip2=no) -PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.6 gthread-2.0],, - [AC_MSG_ERROR([glib-2.6 is required])]) +AC_ARG_ENABLE(cue, + AS_HELP_STRING([--enable-cue], + [enable support for libcue support]),, + enable_cue=auto) +AC_ARG_ENABLE(curl, + AS_HELP_STRING([--enable-curl], + [enable support for libcurl HTTP streaming (default: auto)]),, + [enable_curl=auto]) -dnl -dnl protocol options -dnl +AC_ARG_ENABLE(debug, + AS_HELP_STRING([--enable-debug], + [enable debugging (default: disabled)]),, + enable_debug=no) -AC_ARG_ENABLE(tcp, - AS_HELP_STRING([--disable-tcp], - [disable support for clients connecting via TCP (default: enable)]),, - [enable_tcp=yes]) +AC_ARG_ENABLE(documentation, + AS_HELP_STRING([--enable-documentation], + [build documentation (default: disable)]),, + [enable_documentation=no]) + +AC_ARG_ENABLE(ffado, + AS_HELP_STRING([--enable-ffado], [enable libffado (FireWire) support]),, + [enable_ffado=auto]) + +AC_ARG_ENABLE(ffmpeg, + AS_HELP_STRING([--enable-ffmpeg], + [enable FFMPEG support]),, + enable_ffmpeg=auto) + +AC_ARG_ENABLE(fifo, + AS_HELP_STRING([--disable-fifo], + [disable support for writing audio to a FIFO (default: enable)]),, + enable_fifo=yes) + +AC_ARG_ENABLE(flac, + AS_HELP_STRING([--disable-flac], + [disable flac support (default: enable)]),, + enable_flac=yes) + +AC_ARG_ENABLE(fluidsynth, + AS_HELP_STRING([--enable-fluidsynth], + [enable MIDI support via fluidsynth (default: disable)]),, + enable_fluidsynth=no) + +AC_ARG_ENABLE(gme, + AS_HELP_STRING([--enable-gme], + [enable Blargg's game music emulator plugin]),, + enable_gme=auto) + +AC_ARG_ENABLE(gprof, + AS_HELP_STRING([--enable-gprof], + [enable profiling via gprof (default: disabled)]),, + enable_gprof=no) + +AC_ARG_ENABLE(httpd-output, + AS_HELP_STRING([--enable-httpd-output], + [enables the HTTP server output]),, + [enable_httpd_output=auto]) + +AC_ARG_ENABLE(id3, + AS_HELP_STRING([--disable-id3], + [disable id3 support (default: enable)]),, + enable_id3=yes) + +AC_ARG_ENABLE(inotify, + AS_HELP_STRING([--disable-inotify], + [disable support Inotify automatic database update (default: enabled) ]),, + [enable_inotify=yes]) AC_ARG_ENABLE(ipv6, AS_HELP_STRING([--disable-ipv6], [disable IPv6 support (default: enable)]),, [enable_ipv6=yes]) +AC_ARG_ENABLE(iso9660, + AS_HELP_STRING([--enable-iso9660], + [enable iso9660 archive support (default: disabled)]),, + enable_iso9660=no) + +AC_ARG_ENABLE(jack, + AS_HELP_STRING([--enable-jack], + [enable jack support]),, + enable_jack=auto) + +AC_SYS_LARGEFILE + +AC_ARG_ENABLE(lastfm, + AS_HELP_STRING([--enable-lastfm], + [enable support for last.fm radio (default: disable)]),, + [enable_lastfm=no]) + +AC_ARG_ENABLE(lame-encoder, + AS_HELP_STRING([--enable-lame-encoder], + [enable the LAME mp3 encoder]),, + enable_lame_encoder=auto) + +AC_ARG_ENABLE([libwrap], + AS_HELP_STRING([--enable-libwrap], [use libwrap]),, + [enable_libwrap=auto]) + +AC_ARG_ENABLE(lsr, + AS_HELP_STRING([--enable-lsr], + [enable libsamplerate support]),, + enable_lsr=auto) + +AC_ARG_ENABLE(mad, + AS_HELP_STRING([--enable-mad], + [enable libmad mp3 decoder plugin]),, + enable_mad=auto) + +AC_ARG_ENABLE(mikmod, + AS_HELP_STRING([--enable-mikmod], + [enable the mikmod decoder (default: disable)]),, + enable_mikmod=no) + +AC_ARG_ENABLE(mms, + AS_HELP_STRING([--enable-mms], + [enable the MMS protocol with libmms]),, + [enable_mms=auto]) + +AC_ARG_ENABLE(modplug, + AS_HELP_STRING([--enable-modplug], + [enable modplug decoder plugin]),, + enable_modplug=auto) + +AC_ARG_ENABLE(mpc, + AS_HELP_STRING([--disable-mpc], + [disable musepack (MPC) support (default: enable)]),, + enable_mpc=yes) + +AC_ARG_ENABLE(mpg123, + AS_HELP_STRING([--enable-mpg123], + [enable libmpg123 decoder plugin]),, + enable_mpg123=auto) + +AC_ARG_ENABLE(mvp, + AS_HELP_STRING([--enable-mvp], + [enable support for Hauppauge Media MVP (default: disable)]),, + enable_mvp=no) + +AC_ARG_ENABLE(oggflac, + AS_HELP_STRING([--disable-oggflac], + [disable OggFLAC support (default: enable)]),, + enable_oggflac=yes) + +AC_ARG_ENABLE(openal, + AS_HELP_STRING([--enable-openal], + [enable OpenAL support (default: disable)]),, + enable_openal=no) + +AC_ARG_ENABLE(oss, + AS_HELP_STRING([--disable-oss], + [disable OSS support (default: enable)]),, + enable_oss=yes) + +AC_ARG_ENABLE(pipe-output, + AS_HELP_STRING([--enable-pipe-output], + [enable support for writing audio to a pipe (default: disable)]),, + enable_pipe_output=no) + +AC_ARG_ENABLE(pulse, + AS_HELP_STRING([--enable-pulse], + [enable support for the PulseAudio sound server]),, + enable_pulse=auto) + +AC_ARG_ENABLE(recorder-output, + AS_HELP_STRING([--enable-recorder-output], + [enables the recorder file output plugin (default: disable)]),, + [enable_recorder_output=auto]) + +AC_ARG_ENABLE(sidplay, + AS_HELP_STRING([--enable-sidplay], + [enable C64 SID support via libsidplay2]),, + enable_sidplay=auto) + + +AC_ARG_ENABLE(shout, + AS_HELP_STRING([--enable-shout], + [enables the shoutcast streaming output]),, + [enable_shout=auto]) + +AC_ARG_ENABLE(sndfile, + AS_HELP_STRING([--enable-sndfile], + [enable sndfile support]),, + enable_sndfile=auto) + +AC_ARG_ENABLE(sqlite, + AS_HELP_STRING([--enable-sqlite], + [enable support for the SQLite database]),, + [enable_sqlite=auto]) + +AC_ARG_ENABLE(tcp, + AS_HELP_STRING([--disable-tcp], + [disable support for clients connecting via TCP (default: enable)]),, + [enable_tcp=yes]) + +AC_ARG_ENABLE(test, + AS_HELP_STRING([--enable-test], + [build the test programs (default: disabled)]),, + enable_test=no) + +AC_ARG_WITH(tremor, + AS_HELP_STRING([--with-tremor=PFX], + [use Tremor (vorbisidec) integer Ogg Vorbis decoder (with optional prefix)]),, + with_tremor=no) + +AC_ARG_ENABLE(twolame-encoder, + AS_HELP_STRING([--enable-twolame-encoder], + [enable the TwoLAME mp2 encoder]),, + enable_twolame_encoder=auto) + +AC_ARG_ENABLE(un, + AS_HELP_STRING([--disable-un], + [disable support for clients connecting via unix domain sockets (default: enable)]),, + [enable_un=yes]) + +AC_ARG_ENABLE(vorbis, + AS_HELP_STRING([--disable-vorbis], + [disable Ogg Vorbis support (default: enable)]),, + enable_vorbis=yes) + +AC_ARG_ENABLE(vorbis-encoder, + AS_HELP_STRING([--enable-vorbis-encoder], + [enable the Ogg Vorbis encoder]),, + [enable_vorbis_encoder=auto]) + +AC_ARG_ENABLE(wave-encoder, + AS_HELP_STRING([--enable-wave-encoder], + [enable the PCM wave encoder]),, + enable_wave_encoder=yes) + +AC_ARG_ENABLE(wavpack, + AS_HELP_STRING([--enable-wavpack], + [enable WavPack support]),, + enable_wavpack=auto) + +AC_ARG_ENABLE(werror, + AS_HELP_STRING([--enable-werror], + [treat warnings as errors (default: disabled)]),, + enable_werror=no) + +AC_ARG_ENABLE(wildmidi, + AS_HELP_STRING([--enable-wildmidi], + [enable MIDI support via wildmidi (default: disable)]),, + enable_wildmidi=no) + +AC_ARG_WITH(zeroconf, + AS_HELP_STRING([--with-zeroconf=@<:@auto|avahi|bonjour|no@:>@], + [enable zeroconf backend (default=auto)]),, + with_zeroconf="auto") + +AC_ARG_ENABLE(zzip, + AS_HELP_STRING([--enable-zzip], + [enable zip archive support (default: disabled)]),, + enable_zzip=no) + + +AC_ARG_WITH(tremor-libraries, + AS_HELP_STRING([--with-tremor-libraries=DIR], + [directory where Tremor library is installed (optional)]),, + tremor_libraries="") + +AC_ARG_WITH(tremor-includes, + AS_HELP_STRING([--with-tremor-includes=DIR], + [directory where Tremor header files are installed (optional)]),, + tremor_includes="") + +dnl --------------------------------------------------------------------------- +dnl Mandatory Libraries +dnl --------------------------------------------------------------------------- +PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.12 gthread-2.0],, + [AC_MSG_ERROR([GLib 2.12 is required])]) + +dnl --------------------------------------------------------------------------- +dnl Protocol Options +dnl --------------------------------------------------------------------------- + if test x$enable_tcp = xno; then # if we don't support TCP, we don't need IPv6 either enable_ipv6=no @@ -169,10 +440,11 @@ if test x$enable_tcp = xyes; then AC_DEFINE(HAVE_TCP, 1, [Define if TCP socket support is enabled]) fi -AC_ARG_ENABLE(un, - AS_HELP_STRING([--disable-un], - [disable support for clients connecting via unix domain sockets (default: enable)]),, - [enable_un=yes]) +case "$host_os" in +mingw* | windows* | cygwin*) + enable_un=no + ;; +esac if test x$enable_un = xyes; then AC_DEFINE(HAVE_UN, 1, [Define if unix domain socket support is enabled]) @@ -180,16 +452,53 @@ if test x$enable_un = xyes; then AC_CHECK_FUNCS(getpeereid) fi +dnl --------------------------- Post Protocol Tests --------------------------- +if + test x$enable_tcp = xno && + test x$enable_un = xno; then + AC_MSG_ERROR([No client interfaces configured!]) +fi -dnl ## -dnl misc libraries -dnl ## +dnl --------------------------------------------------------------------------- +dnl LIBC Features +dnl --------------------------------------------------------------------------- +if test x$enable_largefile != xno; then + AC_DEFINE([ENABLE_LARGEFILE], 1, [Define if large file support is enabled]) +fi -AC_ARG_ENABLE(cue, - AS_HELP_STRING([--enable-cue], - [enable support for libcue support]),, - enable_cue=auto) +dnl --------------------------------------------------------------------------- +dnl Miscellaneous Libraries +dnl --------------------------------------------------------------------------- + +dnl --------------------------------- inotify --------------------------------- +AC_CHECK_FUNCS(inotify_init inotify_init1) + +if test x$ac_cv_func_inotify_init = xno; then + enable_inotify=no +fi +if test x$enable_inotify = xyes; then + AC_DEFINE([ENABLE_INOTIFY], 1, [Define to enable inotify support]) +fi +AM_CONDITIONAL(ENABLE_INOTIFY, test x$enable_inotify = xyes) + +dnl --------------------------------- libwrap --------------------------------- +if test x$enable_libwrap != xno; then + AC_CHECK_LIBWRAP(found_libwrap=yes, found_libwrap=no) + MPD_AUTO_RESULT(libwrap, libwrap, [libwrap not found]) +fi + +if test x$enable_libwrap = xyes; then + AC_SUBST(LIBWRAP_CFLAGS) + AC_SUBST(LIBWRAP_LDFLAGS) + AC_DEFINE(HAVE_LIBWRAP, 1, [define to enable libwrap library]) +fi + +dnl --------------------------------------------------------------------------- +dnl Metadata Plugins +dnl --------------------------------------------------------------------------- + +dnl ---------------------------------- libcue --------------------------------- MPD_AUTO_PKG(cue, CUE, [libcue], [libcue parsing library], [libcue not found]) if test x$enable_cue = xyes; then @@ -199,13 +508,25 @@ fi AM_CONDITIONAL(HAVE_CUE, test x$enable_cue = xyes) -dnl ## -dnl Avahi / Zeroconf -dnl ## -AC_ARG_WITH(zeroconf, - AS_HELP_STRING([--with-zeroconf=@<:@auto|avahi|bonjour|no@:>@], - [enable zeroconf backend (default=auto)]),, - with_zeroconf="auto") +dnl -------------------------------- libid3tag -------------------------------- +if test x$enable_id3 = xyes; then + PKG_CHECK_MODULES([ID3TAG], [id3tag],, + AC_CHECK_LIB(id3tag, id3_file_open, + [ID3TAG_LIBS="-lid3tag -lz" ID3TAG_CFLAGS=""], + enable_id3=no)) +fi + +if test x$enable_id3 = xyes; then + AC_DEFINE(HAVE_ID3TAG, 1, [Define to use id3tag]) +fi + +AM_CONDITIONAL(HAVE_ID3TAG, test x$enable_id3 = xyes) + +dnl --------------------------------------------------------------------------- +dnl Autodiscovery +dnl --------------------------------------------------------------------------- + +dnl --------------------------------- zeroconf -------------------------------- case $with_zeroconf in no|avahi|bonjour) @@ -255,10 +576,11 @@ AM_CONDITIONAL(HAVE_ZEROCONF, test x$with_zeroconf != xno) AM_CONDITIONAL(HAVE_AVAHI, test x$with_zeroconf = xavahi) AM_CONDITIONAL(HAVE_BONJOUR, test x$with_zeroconf = xbonjour) -AC_ARG_ENABLE(sqlite, - AS_HELP_STRING([--enable-sqlite], - [enable support for the SQLite database]),, - [enable_sqlite=auto]) +dnl --------------------------------------------------------------------------- +dnl Sticker Database +dnl --------------------------------------------------------------------------- + +dnl ---------------------------------- sqlite --------------------------------- MPD_AUTO_PKG(sqlite, SQLITE, [sqlite3], [SQLite database support], [sqlite not found]) @@ -268,28 +590,40 @@ fi AM_CONDITIONAL(ENABLE_SQLITE, test x$enable_sqlite = xyes) +dnl --------------------------------------------------------------------------- +dnl Converter Plugins +dnl --------------------------------------------------------------------------- -dnl -dnl input plugins -dnl +dnl ------------------------------ libsamplerate ------------------------------ +MPD_AUTO_PKG(lsr, SAMPLERATE, [samplerate >= 0.0.15], + [libsamplerate resampling], [libsamplerate not found]) +if test x$enable_lsr = xyes; then + AC_DEFINE([HAVE_LIBSAMPLERATE], 1, + [Define to enable libsamplerate]) +fi -AC_ARG_ENABLE(curl, - AS_HELP_STRING([--enable-curl], - [enable support for libcurl HTTP streaming (default: auto)]),, - [enable_curl=auto]) +if test x$enable_lsr = xyes; then + PKG_CHECK_MODULES([SAMPLERATE_013], + [samplerate >= 0.1.3],, + [AC_DEFINE([HAVE_LIBSAMPLERATE_NOINT], 1, + [libsamplerate doesn't provide src_int_to_float_array() (<0.1.3)])]) +fi + +AM_CONDITIONAL(HAVE_LIBSAMPLERATE, test x$enable_lsr = xyes) + +dnl --------------------------------------------------------------------------- +dnl Input Plugins +dnl --------------------------------------------------------------------------- +dnl ----------------------------------- CURL ---------------------------------- MPD_AUTO_PKG(curl, CURL, [libcurl], [libcurl HTTP streaming], [libcurl not found]) if test x$enable_curl = xyes; then - AC_DEFINE(HAVE_CURL, 1, [Define when libcurl is used for HTTP streaming]) + AC_DEFINE(ENABLE_CURL, 1, [Define when libcurl is used for HTTP streaming]) fi -AM_CONDITIONAL(HAVE_CURL, test x$enable_curl = xyes) - -AC_ARG_ENABLE(lastfm, - AS_HELP_STRING([--enable-lastfm], - [enable support for last.fm radio (default: disable)]),, - [enable_lastfm=no]) +AM_CONDITIONAL(ENABLE_CURL, test x$enable_curl = xyes) +dnl --------------------------------- Last.FM --------------------------------- if test x$enable_lastfm = xyes; then if test x$enable_curl != xyes; then AC_MSG_ERROR([Cannot enable last.fm radio without curl]) @@ -299,11 +633,12 @@ if test x$enable_lastfm = xyes; then fi AM_CONDITIONAL(ENABLE_LASTFM, test x$enable_lastfm = xyes) -AC_ARG_ENABLE(mms, - AS_HELP_STRING([--enable-mms], - [enable the MMS protocol with libmms]),, - [enable_mms=auto]) +dnl ---------------------------------- libogg --------------------------------- +if test x$with_tremor == xno || test -z $with_tremor; then + PKG_CHECK_MODULES(OGG, [ogg], enable_ogg=yes, enable_ogg=no) +fi +dnl ---------------------------------- libmms --------------------------------- MPD_AUTO_PKG(mms, MMS, [libmms >= 0.4], [libmms mms:// protocol support], [libmms not found]) if test x$enable_mms = xyes; then @@ -312,17 +647,26 @@ if test x$enable_mms = xyes; then fi AM_CONDITIONAL(ENABLE_MMS, test x$enable_mms = xyes) +dnl --------------------------------------------------------------------------- +dnl Archive Plugins +dnl --------------------------------------------------------------------------- -dnl -dnl archive plugins -dnl +dnl --------------------------------- iso9660 --------------------------------- +MPD_AUTO_PKG(iso9660, ISO9660, [libiso9660], + [libiso9660 archive library], [libiso9660 not found]) -dnl bzip2 -AC_ARG_ENABLE(bzip2, - AS_HELP_STRING([--enable-bzip2], - [enable bzip2 archive support (default: disabled)]),, - enable_bzip2=no) +AM_CONDITIONAL(HAVE_ISO9660, test x$enable_iso9660 = xyes) +if test x$enable_iso9660 = xyes; then + AC_DEFINE(HAVE_ISO9660, 1, [Define to have ISO9660 archive support]) + + AC_PATH_PROG(MKISOFS, mkisofs, no) +else + MKISOFS="no" +fi + +AM_CONDITIONAL(ENABLE_ISO9660_TEST, test x$MKISOFS != xno) +dnl ---------------------------------- libbz2 --------------------------------- if test x$enable_bzip2 = xyes; then AC_CHECK_LIB(bz2, BZ2_bzDecompressInit, [MPD_LIBS="$MPD_LIBS -lbz2"], @@ -332,40 +676,33 @@ fi AM_CONDITIONAL(HAVE_BZ2, test x$enable_bzip2 = xyes) if test x$enable_bzip2 = xyes; then AC_DEFINE(HAVE_BZ2, 1, [Define to have bz2 archive support]) + + AC_PATH_PROG(BZIP2, bzip2, no) +else + BZIP2="no" fi -dnl zip -AC_ARG_ENABLE(zip, - AS_HELP_STRING([--enable-zip], - [enable zip archive support (default: disabled)]),, - enable_zip=no) +AM_CONDITIONAL(ENABLE_BZIP2_TEST, test x$BZIP2 != xno) -MPD_AUTO_PKG(zip, ZZIP, [zziplib >= 0.13], +dnl --------------------------------- libzzip --------------------------------- +MPD_AUTO_PKG(zzip, ZZIP, [zziplib >= 0.13], [libzzip archive library], [libzzip not found]) -AM_CONDITIONAL(HAVE_ZIP, test x$enable_zip = xyes) -if test x$enable_zip = xyes; then - AC_DEFINE(HAVE_ZIP, 1, [Define to have zip archive support]) -fi - -dnl iso9660 -AC_ARG_ENABLE(iso9660, - AS_HELP_STRING([--enable-iso9660], - [enable iso9660 archive support (default: disabled)]),, - enable_iso9660=no) +AM_CONDITIONAL(HAVE_ZZIP, test x$enable_zzip = xyes) +if test x$enable_zzip = xyes; then + AC_DEFINE(HAVE_ZZIP, 1, [Define to have zip archive support]) -MPD_AUTO_PKG(iso9660, ISO9660, [libiso9660], - [libiso9660 archive library], [libiso9660 not found]) - -AM_CONDITIONAL(HAVE_ISO, test x$enable_iso9660 = xyes) -if test x$enable_iso9660 = xyes; then - AC_DEFINE(HAVE_ISO, 1, [Define to have iso archive support]) + AC_PATH_PROG(ZIP, zip, no) +else + ZIP="no" fi -dnl archive API +AM_CONDITIONAL(ENABLE_ZZIP_TEST, test x$ZIP != xno) + +dnl ------------------------------- Archive API ------------------------------- if test x$enable_bzip2 = xyes || - test x$enable_zip = xyes || + test x$enable_zzip = xyes || test x$enable_iso9660 = xyes; then enable_archive=yes AC_DEFINE(ENABLE_ARCHIVE, 1, [The archive API is available]) @@ -375,47 +712,95 @@ fi AM_CONDITIONAL(ENABLE_ARCHIVE, test x$enable_archive = xyes) +dnl --------------------------------------------------------------------------- +dnl Decoder Plugins +dnl --------------------------------------------------------------------------- -dnl -dnl metadata -dnl +dnl -------------------------------- audiofile -------------------------------- +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(id3, - AS_HELP_STRING([--disable-id3], - [disable id3 support (default: enable)]),, - enable_id3=yes) +dnl ----------------------------------- FAAD ---------------------------------- +AM_PATH_FAAD() +AM_CONDITIONAL(HAVE_FAAD, test x$enable_aac = xyes) +AM_CONDITIONAL(HAVE_MP4, test x$enable_mp4 = xyes) -dnl -dnl decoder plugins -dnl +dnl ---------------------------------- ffmpeg --------------------------------- +MPD_AUTO_PKG(ffmpeg, FFMPEG, [libavformat >= 52 libavcodec >= 51 libavutil >= 49], + [ffmpeg decoder library], [libavformat+libavcodec+libavutil not found]) +if test x$enable_ffmpeg = xyes; then + # prior to ffmpeg svn12865, you had to specify include files + # without path prefix + old_CPPCFLAGS=$CPPFLAGS + CPPFLAGS="$CPPFLAGS $FFMPEG_CFLAGS" + AC_CHECK_HEADER(libavcodec/avcodec.h,, + AC_DEFINE(OLD_FFMPEG_INCLUDES, 1, + [Define if avcodec.h instead of libavcodec/avcodec.h should be included])) + CPPCFLAGS=$old_CPPFLAGS +fi +if test x$enable_ffmpeg = xyes; then + AC_DEFINE(HAVE_FFMPEG, 1, [Define for FFMPEG support]) +fi -AC_ARG_ENABLE(audiofile, - AS_HELP_STRING([--disable-audiofile], - [disable audiofile support, disables wave support (default: enable)]),, - enable_audiofile=yes) +AM_CONDITIONAL(HAVE_FFMPEG, test x$enable_ffmpeg = xyes) -AC_ARG_ENABLE(ffmpeg, - AS_HELP_STRING([--enable-ffmpeg], - [enable FFMPEG support]),, - enable_ffmpeg=auto) +dnl ----------------------------------- FLAC ---------------------------------- +if test x$enable_flac = xyes; then + PKG_CHECK_MODULES(FLAC, [flac >= 1.1], + AC_DEFINE(HAVE_FLAC, 1, [Define for FLAC support]), + enable_flac=no) -AC_ARG_ENABLE(flac, - AS_HELP_STRING([--disable-flac], - [disable flac support (default: enable)]),, - enable_flac=yes) + oldcflags="$CFLAGS" + oldlibs="$LIBS" + CFLAGS="$CFLAGS $FLAC_CFLAGS" + LIBS="$LIBS $FLAC_LIBS" + if test x$enable_flac = xyes && test x$enable_oggflac = xyes; then + AC_CHECK_DECL(FLAC_API_SUPPORTS_OGG_FLAC, + [enable_oggflac=flac], [], + [#include <FLAC/export.h>]) + fi + CFLAGS="$oldcflags" + LIBS="$oldlibs" -dnl ### -dnl MAD mp3 decoder -dnl ### + if test x$enable_oggflac = xflac; then + if test x$enable_ogg = xyes; then + FLAC_LIBS="${FLAC_LIBS} -logg" + else + enable_oggflac=yes + AC_MSG_WARN("FLAC has the ogg API built in, but couldn't find ogg. Disabling oggflac.") + fi + fi +fi -AC_ARG_ENABLE(mad, - AS_HELP_STRING([--enable-mad], - [enable libmad mp3 decoder plugin]),, - enable_mad=auto) +AM_CONDITIONAL(HAVE_FLAC, test x$enable_flac = xyes) + +enable_flac_encoder=$enable_flac + +dnl -------------------------------- FluidSynth ------------------------------- +if test x$enable_fluidsynth = xyes; then + PKG_CHECK_MODULES(FLUIDSYNTH, [fluidsynth], + AC_DEFINE(ENABLE_FLUIDSYNTH, 1, [Define for fluidsynth support]), + enable_fluidsynth=no) +fi +AM_CONDITIONAL(ENABLE_FLUIDSYNTH, test x$enable_fluidsynth = xyes) + +dnl ---------------------------------- libgme --------------------------------- +MPD_AUTO_PKG(gme, GME, [libgme], + [gme decoder plugin], [libgme not found]) +AM_CONDITIONAL(HAVE_GME, test x$enable_gme = xyes) +if test x$enable_gme = xyes; then + AC_DEFINE(HAVE_GME, 1, [Define for gme support]) +fi + +dnl ---------------------------------- libmad --------------------------------- MPD_AUTO_PKG(mad, MAD, [mad], [libmad MP3 decoder plugin], [libmad not found]) if test x$enable_mad = xyes; then @@ -423,11 +808,22 @@ if test x$enable_mad = xyes; then fi AM_CONDITIONAL(HAVE_MAD, test x$enable_mad = xyes) -AC_ARG_ENABLE(mikmod, - AS_HELP_STRING([--enable-mikmod], - [enable the mikmod decoder (default: disable)]),, - enable_mikmod=no) +enable_shout2="$enable_shout" +MPD_AUTO_PKG(shout, SHOUT, [shout], + [shout output plugin], [libshout not found]) +if test x$enable_shout = xyes && test x$enable_shout2 = xauto; then + enable_shout=auto +fi +dnl -------------------------------- libmpg123 -------------------------------- +MPD_AUTO_PKG(mpg123, MPG123, [libmpg123], + [libmpg123 decoder plugin], [libmpg123 not found]) +if test x$enable_mpg123 = xyes; then + AC_DEFINE(HAVE_MPG123, 1, [Define to use libmpg123]) +fi +AM_CONDITIONAL(HAVE_MPG123, test x$enable_mpg123 = xyes) + +dnl -------------------------------- libmikmod -------------------------------- if test x$enable_mikmod = xyes; then AC_PATH_PROG(LIBMIKMOD_CONFIG, libmikmod-config) if test x$LIBMIKMOD_CONFIG != x ; then @@ -441,322 +837,38 @@ fi AM_CONDITIONAL(ENABLE_MIKMOD_DECODER, test x$enable_mikmod = xyes) -AC_ARG_ENABLE(modplug, - AS_HELP_STRING([--enable-modplug], - [enable modplug decoder plugin]),, - enable_modplug=auto) - +dnl -------------------------------- libmodplug ------------------------------- found_modplug=$HAVE_CXX MPD_AUTO_PRE(modplug, [modplug decoder plugin], [No C++ compiler found]) MPD_AUTO_PKG(modplug, MODPLUG, [libmodplug], [modplug decoder plugin], [libmodplug not found]) -AM_CONDITIONAL(HAVE_MODPLUG, test x$enable_modplug = xyes) + if test x$enable_modplug = xyes; then AC_DEFINE(HAVE_MODPLUG, 1, [Define for modplug support]) fi +AM_CONDITIONAL(HAVE_MODPLUG, test x$enable_modplug = xyes) -AC_ARG_ENABLE(mpc, - AS_HELP_STRING([--disable-mpc], - [disable musepack (MPC) support (default: enable)]),, - enable_mpc=yes) - -AC_ARG_ENABLE(oggflac, - AS_HELP_STRING([--disable-oggflac], - [disable OggFLAC support (default: enable)]),, - enable_oggflac=yes) - -AC_ARG_ENABLE(vorbis, - AS_HELP_STRING([--disable-vorbis], - [disable Ogg Vorbis support (default: enable)]),, - enable_vorbis=yes) - -dnl ### -dnl Ogg Tremor -dnl ### -AC_ARG_WITH(tremor, - AS_HELP_STRING([--with-tremor=PFX], - [use Tremor (vorbisidec) integer Ogg Vorbis decoder (with optional prefix)]),, - with_tremor=no) - -if test x$with_tremor = xyes || test x$with_tremor = xno; then - use_tremor="$with_tremor" -else - tremor_prefix="$with_tremor" - use_tremor=yes -fi - -AC_ARG_WITH(tremor-libraries, - AS_HELP_STRING([--with-tremor-libraries=DIR], - [directory where Tremor library is installed (optional)]),, - tremor_libraries="") - -AC_ARG_WITH(tremor-includes, - AS_HELP_STRING([--with-tremor-includes=DIR], - [directory where Tremor header files are installed (optional)]),, - tremor_includes="") - -AC_ARG_ENABLE(sidplay, - AS_HELP_STRING([--enable-sidplay], - [enable C64 SID support via libsidplay2]),, - enable_sidplay=auto) - -found_sidplay=$HAVE_CXX -MPD_AUTO_PRE(sidplay, [sidplay decoder plugin], [No C++ compiler found]) - -if test x$enable_sidplay != xno; then - # we're not using pkg-config here - # because libsidplay2's .pc file requires libtool - AC_HAVE_LIBRARY(sidplay2, [found_sidplay=yes], [found_sidplay=no]) - MPD_AUTO_PRE(sidplay, [sidplay decoder plugin], - [libsidplay2 not found]) -fi - -if test x$enable_sidplay != xno; then - # can't use AC_HAVE_LIBRARY here, because the dash in the - # library name triggers an autoconf bug - AC_CHECK_LIB(resid-builder, main, - [found_sidplay=yes], [found_sidplay=no]) - MPD_AUTO_RESULT(sidplay, [sidplay decoder plugin], - [libresid-builder not found]) -fi - -if test x$enable_sidplay = xyes; then - AC_SUBST(SIDPLAY_LIBS,"-lsidplay2 -lresid-builder") - AC_SUBST(SIDPLAY_CFLAGS,) - - AC_DEFINE(ENABLE_SIDPLAY, 1, [Define for libsidplay2 support]) -fi - -AM_CONDITIONAL(ENABLE_SIDPLAY, test x$enable_sidplay = xyes) - -AC_ARG_ENABLE(fluidsynth, - AS_HELP_STRING([--enable-fluidsynth], - [enable MIDI support via fluidsynth (default: disable)]),, - enable_fluidsynth=no) - -AC_ARG_ENABLE(wildmidi, - AS_HELP_STRING([--enable-wildmidi], - [enable MIDI support via wildmidi (default: disable)]),, - enable_wildmidi=no) - -AC_ARG_ENABLE(wavpack, - AS_HELP_STRING([--disable-wavpack], - [disable WavPack support (default: enable)]),, - enable_wavpack=yes) - - -dnl -dnl converters -dnl - -AC_ARG_ENABLE(lsr, - AS_HELP_STRING([--enable-lsr], - [enable libsamplerate support]),, - enable_lsr=auto) - -MPD_AUTO_PKG(lsr, SAMPLERATE, [samplerate >= 0.0.15], - [libsamplerate resampling], [libsamplerate not found]) -if test x$enable_lsr = xyes; then - AC_DEFINE([HAVE_LIBSAMPLERATE], 1, - [Define to enable libsamplerate]) -fi - -if test x$enable_lsr = xyes; then - PKG_CHECK_MODULES([SAMPLERATE_013], - [samplerate >= 0.1.3],, - [AC_DEFINE([HAVE_LIBSAMPLERATE_NOINT], 1, - [libsamplerate doesn't provide src_int_to_float_array() (<0.1.3)])]) -fi - -AM_CONDITIONAL(HAVE_LIBSAMPLERATE, test x$enable_lsr = xyes) - - -dnl -dnl encoder plugins -dnl - -AC_ARG_ENABLE(vorbis-encoder, - AS_HELP_STRING([--enable-vorbis-encoder], - [enable the Ogg Vorbis encoder]),, - [enable_vorbis_encoder=auto]) - -AC_ARG_ENABLE(lame-encoder, - AS_HELP_STRING([--enable-lame-encoder], - [enable the LAME mp3 encoder]),, - enable_lame_encoder=auto) - - -dnl -dnl audio output plugins -dnl - -AC_ARG_ENABLE(alsa, - AS_HELP_STRING([--enable-alsa], [enable ALSA support]),, - enable_alsa=auto) - -AC_ARG_ENABLE(ao, - AS_HELP_STRING([--enable-ao], - [enable support for libao]),, - enable_ao=auto) - -MPD_AUTO_PKG(ao, AO, [ao], - [libao output plugin], [libao not found]) -if test x$enable_ao = xyes; then - AC_DEFINE(HAVE_AO, 1, [Define to play with ao]) -fi - -AM_CONDITIONAL(HAVE_AO, test x$enable_ao = xyes) - -AC_ARG_ENABLE(fifo, - AS_HELP_STRING([--disable-fifo], - [disable support for writing audio to a FIFO (default: enable)]),, - enable_fifo=yes) - -AC_ARG_ENABLE(pipe-output, - AS_HELP_STRING([--enable-pipe-output], - [enable support for writing audio to a pipe (default: disable)]),, - enable_pipe_output=no) - -if test x$enable_pipe_output = xyes; then - AC_DEFINE([ENABLE_PIPE_OUTPUT], 1, - [Define to enable support for writing audio to a pipe]) -fi -AM_CONDITIONAL(ENABLE_PIPE_OUTPUT, test x$enable_pipe_output = xyes) - -AC_ARG_ENABLE(jack, - AS_HELP_STRING([--enable-jack], - [enable jack support]),, - enable_jack=auto) - -MPD_AUTO_PKG(jack, JACK, [jack >= 0.4], - [JACK output plugin], [libjack not found]) -if test x$enable_jack = xyes; then - AC_DEFINE([HAVE_JACK], 1, [Define to enable JACK support]) -fi - -if test x$enable_jack = xyes; then - # check whether jack_set_info_function() is available - old_LIBS=$LIBS - LIBS="$LIBS $JACK_LIBS" - - AC_CHECK_FUNCS(jack_set_info_function) - - LIBS=$old_LIBS -fi - -AM_CONDITIONAL(HAVE_JACK, test x$enable_jack = xyes) - -AC_ARG_ENABLE(mvp, - AS_HELP_STRING([--enable-mvp], - [enable support for Hauppauge Media MVP (default: disable)]),, - enable_mvp=no) - -AC_ARG_ENABLE(oss, - AS_HELP_STRING([--disable-oss], - [disable OSS support (default: enable)]),, - enable_oss=yes) - -AC_ARG_ENABLE(pulse, - AS_HELP_STRING([--enable-pulse], - [enable support for the PulseAudio sound server]),, - enable_pulse=auto) - -MPD_AUTO_PKG(pulse, PULSE, [libpulse-simple], - [PulseAudio output plugin], [libpulse not found]) -if test x$enable_pulse = xyes; then - AC_DEFINE([HAVE_PULSE], 1, - [Define to enable PulseAudio support]) -fi - -AM_CONDITIONAL(HAVE_PULSE, test x$enable_pulse = xyes) - -AC_ARG_ENABLE(httpd-output, - AS_HELP_STRING([--enable-httpd-output], - [enables the HTTP server output]),, - [enable_httpd_output=auto]) - -enable_osx=no -case "$host_os" in - darwin*) - AC_DEFINE(HAVE_OSX, 1, [Define for compiling OS X support]) - MPD_LIBS="$MPD_LIBS -framework AudioUnit -framework CoreServices" - enable_osx=yes ;; -esac - -AM_CONDITIONAL(HAVE_OSX, test x$enable_osx = xyes) - -AC_ARG_ENABLE(shout, - AS_HELP_STRING([--enable-shout], - [enables the shoutcast streaming output]),, - [enable_shout=auto]) - -enable_shout2="$enable_shout" -MPD_AUTO_PKG(shout, SHOUT, [shout], - [shout output plugin], [libshout not found]) -if test x$enable_shout = xyes && test x$enable_shout2 = xauto; then - enable_shout=auto -fi - -case "$host_os" in - solaris*) - AC_DEFINE(ENABLE_SOLARIS_OUTPUT, 1, [Define to enable Solaris /dev/audio support]) - enable_solaris_output=yes - ;; - - *) - enable_solaris_output=no - ;; -esac - -AM_CONDITIONAL(ENABLE_SOLARIS_OUTPUT, test x$enable_solaris_output = xyes) - -if test x$enable_oss = xyes; then - AC_CHECK_HEADER(sys/soundcard.h, - [enable_oss=yes;AC_DEFINE(HAVE_OSS,1,[Define to enable OSS])], - [AC_MSG_WARN(Soundcard headers not found -- disabling OSS support); - enable_oss=no]) -fi - -AM_CONDITIONAL(HAVE_OSS, test x$enable_oss = xyes) - -if test x$enable_fifo = xyes; then - AC_CHECK_FUNC([mkfifo], - [enable_fifo=yes;AC_DEFINE([HAVE_FIFO], 1, - [Define to enable support for writing audio to a FIFO])], - [enable_fifo=no;AC_MSG_WARN([mkfifo not found -- disabling support for writing audio to a FIFO])]) -fi - -AM_CONDITIONAL(HAVE_FIFO, test x$enable_fifo = xyes) - -if test x$enable_mvp = xyes; then - AC_DEFINE(HAVE_MVP,1,[Define to enable Hauppauge Media MVP support]) +dnl --------------------------- sndfile/modplug test -------------------------- +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 -AM_CONDITIONAL(HAVE_MVP, test x$enable_mvp = xyes) - -MPD_AUTO_PKG(alsa, ALSA, [alsa >= 0.9.0], - [ALSA output plugin], [libasound not found]) +dnl -------------------------------- libsndfile ------------------------------- +dnl See above test, which may disable this. +MPD_AUTO_PKG(sndfile, SNDFILE, [sndfile], + [libsndfile decoder plugin], [libsndfile not found]) -if test x$enable_alsa = xyes; then - AC_DEFINE(HAVE_ALSA, 1, [Define to enable ALSA support]) +if test x$enable_sndfile = xyes; then + AC_DEFINE(ENABLE_SNDFILE, 1, [Define to enable the sndfile decoder plugin]) fi +AM_CONDITIONAL(ENABLE_SNDFILE, test x$enable_sndfile = xyes) -AM_CONDITIONAL(HAVE_ALSA, test x$enable_alsa = xyes) - -if test x$enable_id3 = xyes; then - PKG_CHECK_MODULES([ID3TAG], [id3tag],, - AC_CHECK_LIB(id3tag, id3_file_open, - [ID3TAG_LIBS="-lid3tag -lz" ID3TAG_CFLAGS=""], - enable_id3=no)) -fi - -if test x$enable_id3 = xyes; then - AC_DEFINE(HAVE_ID3TAG, 1, [Define to use id3tag]) -fi - -AM_CONDITIONAL(HAVE_ID3TAG, test x$enable_id3 = xyes) - +dnl --------------------------------- musepack -------------------------------- if test x$enable_mpc = xyes; then if test "x$mpcdec_libraries" != "x" ; then MPCDEC_LIBS="-L$mpcdec_libraries" @@ -806,25 +918,15 @@ 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) +dnl -------------------------------- Ogg Tremor ------------------------------- +if test x$with_tremor = xyes || test x$with_tremor = xno; then + enable_tremor="$with_tremor" +else + tremor_prefix="$with_tremor" + enable_tremor=yes fi -AM_CONDITIONAL(HAVE_WAVPACK, test x$enable_wavpack = xyes) - -AM_PATH_FAAD() - -AM_CONDITIONAL(HAVE_FAAD, test x$enable_aac = xyes) - -AM_CONDITIONAL(HAVE_MP4, test x$enable_mp4 = xyes) - -if test x$use_tremor = xyes; then +if test x$enable_tremor = xyes; then if test "x$tremor_libraries" != "x" ; then TREMOR_LIBS="-L$tremor_libraries" elif test "x$tremor_prefix" != "x" ; then @@ -844,104 +946,94 @@ if test x$use_tremor = xyes; then AC_MSG_WARN([vorbisidec lib needed for ogg support with tremor -- disabling ogg support])) CFLAGS="$ac_save_CFLAGS" LIBS="$ac_save_LIBS" - if test x$enable_vorbis = xyes; then - AC_DEFINE(ENABLE_VORBIS_DECODER, 1, [Define for Ogg Vorbis support]) - fi -elif test x$enable_vorbis = xyes; then - PKG_CHECK_MODULES(VORBIS, [ogg vorbis vorbisfile], - AC_DEFINE(ENABLE_VORBIS_DECODER, 1, [Define for Ogg Vorbis support]), - enable_vorbis=no) -fi -AM_CONDITIONAL(ENABLE_VORBIS_DECODER, test x$enable_vorbis = xyes) - -if test x$use_tremor = xyes; then AC_DEFINE(HAVE_TREMOR,1, [Define to use tremor (libvorbisidec) for ogg support]) - if test x$enable_oggflac = xyes; then - AC_MSG_WARN([disabling OggFLAC support because it is incompatible with tremor]) - enable_oggflac=no - fi fi AC_SUBST(TREMOR_CFLAGS) AC_SUBST(TREMOR_LIBS) -if test x$enable_flac = xyes; then - PKG_CHECK_MODULES(FLAC, [flac >= 1.1], - AC_DEFINE(HAVE_FLAC, 1, [Define for FLAC support]), - enable_flac=no) +dnl --------------------------------- OggFLAC --------------------------------- +dnl OggFLAC must go after Ogg Tremor - oldcflags="$CFLAGS" - oldlibs="$LIBS" - CFLAGS="$CFLAGS $FLAC_CFLAGS" - LIBS="$LIBS $FLAC_LIBS" - if test x$enable_flac = xyes && test x$enable_oggflac = xyes; then - AC_CHECK_DECL(FLAC_API_SUPPORTS_OGG_FLAC, - [enable_oggflac=flac], [], - [#include <FLAC/export.h>]) - fi - CFLAGS="$oldcflags" - LIBS="$oldlibs" +if test x$enable_tremor = xyes && test x$enable_oggflac = xyes; then + AC_MSG_WARN([disabling OggFLAC support because it is incompatible with tremor]) + enable_oggflac=no fi -AM_CONDITIONAL(HAVE_FLAC, test x$enable_flac = xyes) - if test x$enable_oggflac = xyes; then - oldmpdcflags="$MPD_CFLAGS" - oldmpdlibs="$MPD_LIBS" - AM_PATH_LIBOGGFLAC(MPD_LIBS="$MPD_LIBS $LIBOGGFLAC_LIBS" - MPD_CFLAGS="$MPD_CFLAGS $LIBOGGFLAC_CFLAGS", - enable_oggflac=no) + AC_CHECK_HEADER([OggFLAC/stream_decoder.h],, enable_oggflac=no) fi if test x$enable_oggflac = xyes; then AC_DEFINE(HAVE_OGGFLAC,1,[Define for OggFLAC support]) + MPD_LIBS="$MPD_LIBS -lOggFLAC -lFLAC -lm" fi AM_CONDITIONAL(HAVE_OGGFLAC, test x$enable_oggflac = xyes) -AM_CONDITIONAL(HAVE_FLAC_COMMON, - test x$enable_flac = xyes || test x$enable_oggflac = xyes) -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) +dnl -------------------------------- Ogg Vorbis ------------------------------- +if test x$enable_vorbis = xyes; then + if test x$enable_tremor = xyes; then + AC_MSG_WARN(["OggTremor detected, could not enable Vorbis."]) + enable_vorbis=no + elif test x$enable_ogg = xyes; then + PKG_CHECK_MODULES(VORBIS, [vorbis vorbisfile], + AC_DEFINE(ENABLE_VORBIS_DECODER, 1, [Define for Ogg Vorbis support]), + enable_vorbis=no) + else + AC_MSG_WARN(["Ogg not detected, could not enable Vorbis."]) + enable_vorbis=no + fi fi -AM_CONDITIONAL(HAVE_AUDIOFILE, test x$enable_audiofile = xyes) +AM_CONDITIONAL(ENABLE_VORBIS_DECODER, test x$enable_vorbis = xyes) -MPD_AUTO_PKG(ffmpeg, FFMPEG, [libavformat >= 52 libavcodec >= 51 libavutil >= 49], - [ffmpeg decoder library], [libavformat+libavcodec+libavutil not found]) +dnl --------------------------------- sidplay --------------------------------- +found_sidplay=$HAVE_CXX +MPD_AUTO_PRE(sidplay, [sidplay decoder plugin], [No C++ compiler found]) -if test x$enable_ffmpeg = xyes; then - # prior to ffmpeg svn12865, you had to specify include files - # without path prefix - old_CPPCFLAGS=$CPPFLAGS - CPPFLAGS="$CPPFLAGS $FFMPEG_CFLAGS" - AC_CHECK_HEADER(libavcodec/avcodec.h,, - AC_DEFINE(OLD_FFMPEG_INCLUDES, 1, - [Define if avcodec.h instead of libavcodec/avcodec.h should be included])) - CPPCFLAGS=$old_CPPFLAGS +if test x$enable_sidplay != xno; then + # we're not using pkg-config here + # because libsidplay2's .pc file requires libtool + AC_HAVE_LIBRARY(sidplay2, [found_sidplay=yes], [found_sidplay=no]) + MPD_AUTO_PRE(sidplay, [sidplay decoder plugin], + [libsidplay2 not found]) fi -if test x$enable_ffmpeg = xyes; then - AC_DEFINE(HAVE_FFMPEG, 1, [Define for FFMPEG support]) +if test x$enable_sidplay != xno; then + # can't use AC_HAVE_LIBRARY here, because the dash in the + # library name triggers an autoconf bug + AC_CHECK_LIB(resid-builder, main, + [found_sidplay=yes], [found_sidplay=no]) + + if test x$found_sidplay = xyes; then + AC_HAVE_LIBRARY(sidutils,, [found_sidplay=no]) + fi + + MPD_AUTO_RESULT(sidplay, [sidplay decoder plugin], + [libresid-builder or libsidutils not found]) fi -AM_CONDITIONAL(HAVE_FFMPEG, test x$enable_ffmpeg = xyes) +if test x$enable_sidplay = xyes; then + AC_SUBST(SIDPLAY_LIBS,"-lsidplay2 -lresid-builder -lsidutils") + AC_SUBST(SIDPLAY_CFLAGS,) -if test x$enable_fluidsynth = xyes; then - PKG_CHECK_MODULES(FLUIDSYNTH, [fluidsynth], - AC_DEFINE(ENABLE_FLUIDSYNTH, 1, [Define for fluidsynth support]), - enable_fluidsynth=no) + AC_DEFINE(ENABLE_SIDPLAY, 1, [Define for libsidplay2 support]) fi -AM_CONDITIONAL(ENABLE_FLUIDSYNTH, test x$enable_fluidsynth = xyes) +AM_CONDITIONAL(ENABLE_SIDPLAY, test x$enable_sidplay = xyes) + +dnl --------------------------------- wavpack --------------------------------- +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 --------------------------------- WildMidi -------------------------------- if test x$enable_wildmidi = xyes; then oldcflags=$CFLAGS oldlibs=$LIBS @@ -966,15 +1058,49 @@ fi AM_CONDITIONAL(ENABLE_WILDMIDI, test x$enable_wildmidi = xyes) +dnl ------------------------ Post Decoder Plugins Tests ----------------------- + +if + test x$enable_aac = xno && + test x$enable_audiofile = xno && + test x$enable_ffmpeg = xno && + test x$enable_flac = xno && + test x$enable_fluidsynth = xno && + test x$enable_mad = xno && + test x$enable_mikmod = xno; then + test x$enable_modplug = xno && + test x$enable_mp4 = xno && + test x$enable_mpc = xno && + test x$enable_mpg123 = xno && + test x$enable_oggflac = xno && + test x$enable_sidplay = xno && + test x$enable_tremor = xno && + test x$enable_vorbis = xno && + test x$enable_wavpack = xno && + test x$enable_wildmidi = xno && + + AC_MSG_ERROR([No input plugins supported!]) +fi + +AM_CONDITIONAL(HAVE_OGG_COMMON, + test x$enable_vorbis = xyes || test x$enable_oggflac = xyes || test x$enable_flac = xyes) + +AM_CONDITIONAL(HAVE_FLAC_COMMON, + test x$enable_flac = xyes || test x$enable_oggflac = xyes) -dnl -dnl Encoder API and shout/httpd output plugin -dnl +dnl --------------------------------------------------------------------------- +dnl Encoders for Streaming Audio Output Plugins +dnl --------------------------------------------------------------------------- -if test x$enable_shout = xyes || test x$enable_httpd_output = xyes; then +dnl ------------------------------- Encoder API ------------------------------- +if test x$enable_shout = xyes || \ + test x$enable_recorder_output = xyes || \ + test x$enable_httpd_output = xyes; then # at least one output using encoders is explicitly enabled need_encoder=yes -elif test x$enable_shout = xauto || test x$enable_httpd_output = xauto; then +elif test x$enable_shout = xauto || \ + test x$enable_recorder_output = xauto || \ + test x$enable_httpd_output = xauto; then need_encoder=auto else # all outputs using encoders are disabled @@ -983,22 +1109,75 @@ else # don't bother to check for encoder plugins enable_vorbis_encoder=no enable_lame_encoder=no + enable_twolame_encoder=no + enable_wave_encoder=no + enable_flac_encoder=no +fi + +dnl ------------------------------- FLAC Encoder ------------------------------ +if test x$enable_flac_encoder = xyes; then + AC_DEFINE(ENABLE_FLAC_ENCODER, 1, + [Define to enable the FLAC encoder plugin]) fi +AM_CONDITIONAL(ENABLE_FLAC_ENCODER, test x$enable_flac_encoder = xyes) +dnl ---------------------------- Ogg Vorbis Encoder --------------------------- MPD_AUTO_PKG(vorbis_encoder, VORBISENC, [vorbisenc], [Ogg Vorbis encoder], [libvorbisenc not found]) +if test x$enable_vorbis_encoder = xyes; then + AC_DEFINE(ENABLE_VORBIS_ENCODER, 1, + [Define to enable the vorbis encoder plugin]) +fi +AM_CONDITIONAL(ENABLE_VORBIS_ENCODER, test x$enable_vorbis_encoder = xyes) + +dnl ------------------------------- LAME Encoder ------------------------------ if test x$enable_lame_encoder != xno; then - AM_PATH_LAME([found_lame_encoder=yes], [found_lame_encoder=no]) - MPD_AUTO_RESULT(lame_encoder, [LAME encoder plugin], - [LAME not found]) + AC_CHECK_HEADERS(lame/lame.h,, + [AC_CHECK_HEADERS(lame.h,, using_lame=no)]) + AC_CHECK_LIB(mp3lame, lame_init,, using_lame=no) + if test x$using_lame != xno; then + AC_DEFINE(HAVE_LAME, 1, [Define to 1 if you have lame 3.98 or greater.]) + LAME_LIBS="-lmp3lame -lm" + enable_lame_encoder=yes + fi + + if test "$enable_lame_encoder" = "yes" -a "$using_lame" = "no"; then + AC_MSG_ERROR([LAME libraries and development support files not found.]) + fi fi -AC_SUBST(LAME_CFLAGS) AC_SUBST(LAME_LIBS) +if test x$enable_lame_encoder = xyes; then + AC_DEFINE(ENABLE_LAME_ENCODER, 1, + [Define to enable the lame encoder plugin]) +fi +AM_CONDITIONAL(ENABLE_LAME_ENCODER, test x$enable_lame_encoder = xyes) + +dnl ----------------------------- TwoLAME Encoder ----------------------------- +MPD_AUTO_PKG(twolame_encoder, TWOLAME, [twolame], + [TwoLAME encoder], [libtwolame not found]) + +if test x$enable_twolame_encoder = xyes; then + AC_DEFINE(ENABLE_TWOLAME_ENCODER, 1, + [Define to enable the TwoLAME encoder plugin]) +fi +AM_CONDITIONAL(ENABLE_TWOLAME_ENCODER, test x$enable_twolame_encoder = xyes) + +dnl ------------------------------- WAVE Encoder ------------------------------ +AM_CONDITIONAL(ENABLE_WAVE_ENCODER, test x$enable_wave_encoder = xyes) +if test x$enable_wave_encoder = xyes; then + AC_DEFINE(ENABLE_WAVE_ENCODER, 1, + [Define to enable the PCM wave encoder plugin]) +fi + +dnl --------------------------- encoder plugins test -------------------------- 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 || + test x$enable_flac_encoder != xno || + test x$enable_wave_encoder != xno; then # at least one encoder plugin is enabled enable_encoder=yes else @@ -1010,18 +1189,48 @@ else fi fi +if test x$enable_encoder = xyes; then + AC_DEFINE(ENABLE_ENCODER, 1, + [Define to enable the encoder plugins]) +fi +AM_CONDITIONAL(ENABLE_ENCODER, test x$enable_encoder = xyes) -if test x$enable_shout = xauto; then - # handle shout auto-detection: disable if no encoder is - # available - if test x$enable_encoder = xyes; then - enable_shout=yes - else - AC_MSG_WARN([No encoder plugin -- disabling the shout output plugin]) - enable_shout=no - fi +dnl --------------------------------------------------------------------------- +dnl Audio Output Plugins +dnl --------------------------------------------------------------------------- + +dnl ----------------------------------- ALSA ---------------------------------- +MPD_AUTO_PKG(alsa, ALSA, [alsa >= 0.9.0], + [ALSA output plugin], [libasound not found]) + +if test x$enable_alsa = xyes; then + AC_DEFINE(HAVE_ALSA, 1, [Define to enable ALSA support]) +fi + +AM_CONDITIONAL(HAVE_ALSA, test x$enable_alsa = xyes) + +dnl ----------------------------------- FFADO --------------------------------- + +MPD_AUTO_PKG(ffado, FFADO, [libffado], + [libffado output plugin], [libffado not found]) + +if test x$enable_ffado = xyes; then + AC_DEFINE(ENABLE_FFADO_OUTPUT, 1, [Define to enable the libffado output plugin]) fi +AM_CONDITIONAL(ENABLE_FFADO_OUTPUT, test x$enable_ffado = xyes) + +dnl ----------------------------------- FIFO ---------------------------------- +if test x$enable_fifo = xyes; then + AC_CHECK_FUNC([mkfifo], + [enable_fifo=yes;AC_DEFINE([HAVE_FIFO], 1, + [Define to enable support for writing audio to a FIFO])], + [enable_fifo=no;AC_MSG_WARN([mkfifo not found -- disabling support for writing audio to a FIFO])]) +fi + +AM_CONDITIONAL(HAVE_FIFO, test x$enable_fifo = xyes) + +dnl ------------------------------- HTTPD Output ------------------------------ if test x$enable_httpd_output = xauto; then # handle HTTPD auto-detection: disable if no encoder is # available @@ -1033,468 +1242,356 @@ if test x$enable_httpd_output = xauto; then fi fi -AM_CONDITIONAL(HAVE_SHOUT, test x$enable_shout = xyes) -if test x$enable_shout = xyes; then - AC_DEFINE(HAVE_SHOUT, 1, [Define to enable the shoutcast output]) -fi - -AM_CONDITIONAL(ENABLE_HTTPD_OUTPUT, test x$enable_httpd_output = xyes) if test x$enable_httpd_output = xyes; then AC_DEFINE(ENABLE_HTTPD_OUTPUT, 1, [Define to enable the HTTP server output]) fi +AM_CONDITIONAL(ENABLE_HTTPD_OUTPUT, test x$enable_httpd_output = xyes) -AM_CONDITIONAL(ENABLE_ENCODER, test x$enable_encoder = xyes) - -AM_CONDITIONAL(ENABLE_VORBIS_ENCODER, test x$enable_vorbis_encoder = xyes) -if test x$enable_vorbis_encoder = xyes; then - AC_DEFINE(ENABLE_VORBIS_ENCODER, 1, - [Define to enable the vorbis encoder plugin]) -fi - -AM_CONDITIONAL(ENABLE_LAME_ENCODER, test x$enable_lame_encoder = xyes) -if test x$enable_lame_encoder = xyes; then - AC_DEFINE(ENABLE_LAME_ENCODER, 1, - [Define to enable the lame encoder plugin]) +dnl ----------------------------------- JACK ---------------------------------- +MPD_AUTO_PKG(jack, JACK, [jack >= 0.100], + [JACK output plugin], [libjack not found]) +if test x$enable_jack = xyes; then + AC_DEFINE([HAVE_JACK], 1, [Define to enable JACK support]) fi +if test x$enable_jack = xyes; then + # check whether jack_set_info_function() is available + old_LIBS=$LIBS + LIBS="$LIBS $JACK_LIBS" -dnl -dnl Documentation -dnl - -AC_ARG_ENABLE(documentation, - AS_HELP_STRING([--enable-documentation], - [build documentation (default: disable)]),, - [enable_documentation=no]) - -if test x$enable_documentation = xyes; then - AC_PATH_PROG(XMLTO, xmlto) - AC_SUBST(XMLTO) - AM_CONDITIONAL(HAVE_XMLTO, test x$XMLTO != x) - - AC_PATH_PROG(DOXYGEN, doxygen) - if test x$DOXYGEN = x; then - AC_MSG_ERROR([doxygen not found]) - fi + AC_CHECK_FUNCS(jack_set_info_function) - AC_SUBST(DOXYGEN) -else - AM_CONDITIONAL(HAVE_XMLTO, false) + LIBS=$old_LIBS fi -AM_CONDITIONAL(ENABLE_DOCUMENTATION, test x$enable_documentation = xyes) - - -dnl -dnl build options -dnl - -AC_ARG_ENABLE(werror, - AS_HELP_STRING([--enable-werror], - [treat warnings as errors (default: disabled)]),, - enable_werror=no) +AM_CONDITIONAL(HAVE_JACK, test x$enable_jack = xyes) -if test "x$enable_werror" = xyes; then - AM_CFLAGS="$AM_CFLAGS -Werror -pedantic-errors" +dnl ---------------------------------- libao ---------------------------------- +MPD_AUTO_PKG(ao, AO, [ao], + [libao output plugin], [libao not found]) +if test x$enable_ao = xyes; then + AC_DEFINE(HAVE_AO, 1, [Define to play with ao]) fi -AC_ARG_ENABLE(debug, - AS_HELP_STRING([--enable-debug], - [enable debugging (default: disabled)]),, - enable_debug=no) - -#if test "x$enable_debug" = xno; then - # don't set NDEBUG for now, until MPD is stable - #AM_CFLAGS="$AM_CFLAGS -DNDEBUG" -#fi - -AC_ARG_ENABLE(gprof, - AS_HELP_STRING([--enable-gprof], - [enable profiling via gprof (default: disabled)]),, - enable_gprof=no) +AM_CONDITIONAL(HAVE_AO, test x$enable_ao = xyes) -if test "x$enable_gprof" = xyes; then - MPD_CFLAGS="$MPD_CFLAGS -pg" - MPD_LIBS="$MPD_LIBS -pg" +dnl ----------------------------------- MVP ----------------------------------- +if test x$enable_mvp = xyes; then + AC_DEFINE(HAVE_MVP,1,[Define to enable Hauppauge Media MVP support]) fi -AC_ARG_ENABLE(test, - AS_HELP_STRING([--enable-test], - [build the test programs (default: disabled)]),, - enable_test=no) - -AM_CONDITIONAL(ENABLE_TEST, test "x$enable_test" = xyes) - +AM_CONDITIONAL(HAVE_MVP, test x$enable_mvp = xyes) -dnl -dnl CFLAGS -dnl +dnl ---------------------------------- OpenAL --------------------------------- +AC_SUBST(OPENAL_CFLAGS,"") +AC_SUBST(OPENAL_LIBS,"") -if test x$GCC = xyes -then - MPD_CHECK_FLAG([-Wall]) - MPD_CHECK_FLAG([-Wextra]) - MPD_CHECK_FLAG([-Wno-deprecated-declarations]) - MPD_CHECK_FLAG([-Wmissing-prototypes]) - MPD_CHECK_FLAG([-Wshadow]) - MPD_CHECK_FLAG([-Wpointer-arith]) - MPD_CHECK_FLAG([-Wstrict-prototypes]) - MPD_CHECK_FLAG([-Wcast-qual]) - MPD_CHECK_FLAG([-Wwrite-strings]) - MPD_CHECK_FLAG([-pedantic]) +if test x$enable_openal = xyes; then + if test x$enable_osx = xyes; then + AC_CHECK_HEADERS([OpenAL/al.h OpenAL/alc.h], [], [enable_openal=no]) + if test x$enable_openal = xyes; then + OPENAL_LIBS="-framework OpenAL" + AC_DEFINE(HAVE_OPENAL, 1, [Define for OpenAL support]) + else + AC_MSG_WARN(OpenAL headers not found -- disabling OpenAL support) + fi + else + PKG_CHECK_MODULES([OPENAL], [openal], + AC_DEFINE(HAVE_OPENAL, 1, [Define for OpenAL support]), + enable_openal=no) + fi fi -dnl -dnl pretty-print result -dnl +AM_CONDITIONAL(HAVE_OPENAL, test x$enable_openal = xyes) -echo "" -echo "########### MPD CONFIGURATION ############" -echo "" - -echo " Client Support:" -if test x$enable_ipv6 = xyes; then - echo " IPv6 support ..................enabled" -else - echo " IPv6 support ..................disabled" +dnl ---------------------------- Open Sound System ---------------------------- +if test x$enable_oss = xyes; then + AC_CHECK_HEADER(sys/soundcard.h, + [enable_oss=yes;AC_DEFINE(HAVE_OSS,1,[Define to enable OSS])], + [AC_MSG_WARN(Soundcard headers not found -- disabling OSS support); + enable_oss=no]) fi -if test x$enable_tcp = xyes; then - echo " TCP support ...................enabled" -else - echo " TCP support ...................disabled" -fi +AM_CONDITIONAL(HAVE_OSS, test x$enable_oss = xyes) -if test x$enable_un = xyes; then - echo " Unix domain socket support ....enabled" -else - echo " Unix domain socket support ....disabled" -fi +dnl ----------------------------------- OSX ----------------------------------- +enable_osx=no +case "$host_os" in + darwin*) + AC_DEFINE(HAVE_OSX, 1, [Define for compiling OS X support]) + MPD_LIBS="$MPD_LIBS -framework AudioUnit -framework CoreServices" + enable_osx=yes ;; +esac -echo "" +AM_CONDITIONAL(HAVE_OSX, test x$enable_osx = xyes) -if - test x$enable_tcp = xno && - test x$enable_un = xno; then - AC_MSG_ERROR([No client interfaces configured!]) +dnl ------------------------------- Pipe Output ------------------------------- +if test x$enable_pipe_output = xyes; then + AC_DEFINE([ENABLE_PIPE_OUTPUT], 1, + [Define to enable support for writing audio to a pipe]) fi +AM_CONDITIONAL(ENABLE_PIPE_OUTPUT, test x$enable_pipe_output = xyes) -echo " Playback Support:" -if test x$enable_alsa = xyes; then - echo " ALSA support ..................enabled" -else - echo " ALSA support ..................disabled" +dnl -------------------------------- PulseAudio ------------------------------- +MPD_AUTO_PKG(pulse, PULSE, [libpulse], + [PulseAudio output plugin], [libpulse not found]) +if test x$enable_pulse = xyes; then + AC_DEFINE([HAVE_PULSE], 1, + [Define to enable PulseAudio support]) fi -if test x$enable_fifo = xyes; then - echo " FIFO support ..................enabled" -else - echo " FIFO support ..................disabled" -fi +AM_CONDITIONAL(HAVE_PULSE, test x$enable_pulse = xyes) -if test x$enable_httpd_output = xyes; then - echo " HTTP daemon support ...........enabled" -else - echo " HTTP daemon support ...........disabled" +dnl --------------------------------- Recorder -------------------------------- +if test x$enable_recorder_output = xauto; then + # handle recorder auto-detection: disable if no encoder is + # available + if test x$enable_encoder = xyes; then + enable_recorder_output=yes + else + AC_MSG_WARN([No encoder plugin -- disabling the recorder output plugin]) + enable_recorder_output=no + fi fi -if test x$enable_jack = xyes; then - echo " JACK support ..................enabled" -else - echo " JACK support ..................disabled" +if test x$enable_recorder_output = xyes; then + AC_DEFINE(ENABLE_RECORDER_OUTPUT, 1, [Define to enable the recorder output]) fi +AM_CONDITIONAL(ENABLE_RECORDER_OUTPUT, test x$enable_recorder_output = xyes) -if test x$enable_ao = xyes; then - echo " libao support .................enabled" -else - echo " libao support .................disabled" +dnl -------------------------------- SHOUTcast -------------------------------- +if test x$enable_shout = xauto; then + # handle shout auto-detection: disable if no encoder is + # available + if test x$enable_encoder = xyes; then + enable_shout=yes + else + AC_MSG_WARN([No encoder plugin -- disabling the shout output plugin]) + enable_shout=no + fi fi -if test x$enable_oss = xyes; then - echo " OSS support ...................enabled" -else - echo " OSS support ...................disabled" +if test x$enable_shout = xyes; then + AC_DEFINE(HAVE_SHOUT, 1, [Define to enable the shoutcast output]) fi +AM_CONDITIONAL(HAVE_SHOUT, test x$enable_shout = xyes) -if test x$enable_osx = xyes; then - echo " OS X support ..................enabled" -else - echo " OS X support ..................disabled" -fi +dnl --------------------------------- Solaris --------------------------------- +case "$host_os" in + solaris*) + AC_DEFINE(ENABLE_SOLARIS_OUTPUT, 1, [Define to enable Solaris /dev/audio support]) + enable_solaris_output=yes + ;; -if test x$enable_pipe_output = xyes; then - echo " Pipeline output support .......enabled" -else - echo " Pipeline output support .......disabled" -fi + *) + enable_solaris_output=no + ;; +esac -if test x$enable_pulse = xyes; then - echo " PulseAudio support ............enabled" -else - echo " PulseAudio support ............disabled" -fi +AM_CONDITIONAL(ENABLE_SOLARIS_OUTPUT, test x$enable_solaris_output = xyes) -if test x$enable_mvp = xyes; then - echo " Media MVP support .............enabled" -else - echo " Media MVP support .............disabled" -fi +dnl --------------------------------- WinMM --------------------------------- -if test x$enable_shout = xyes; then - echo " SHOUTcast support .............enabled" -else - echo " SHOUTcast support .............disabled" -fi +case "$host_os" in + mingw32* | windows*) + AC_DEFINE(ENABLE_WINMM_OUTPUT, 1, [Define to enable WinMM support]) + enable_winmm_output=yes + MPD_LIBS="$MPD_LIBS -lwinmm" + ;; -if test x$enable_solaris_output = xyes; then - echo " Solaris /dev/audio support ....enabled" -else - echo " Solaris /dev/audio support ....disabled" -fi + *) + enable_winmm_output=no + ;; +esac -echo "" +AM_CONDITIONAL(ENABLE_WINMM_OUTPUT, test x$enable_winmm_output = xyes) +dnl --------------------- Post Audio Output Plugins Tests --------------------- if + test x$enable_alsa = xno && test x$enable_ao = xno && - test x$enable_oss = xno && - test x$enable_shout = xno && + test x$enable_ffado = xno && + test x$enable_fifo = xno && test x$enable_httpd_output = xno && - test x$enable_solaris_output = xno && - test x$enable_alsa = xno && - test x$enable_osx = xno && - test x$enable_pulse = xno && test x$enable_jack = xno && - test x$enable_fifo = xno && - test x$enable_pipe_output = xno && test x$enable_mvp = xno; then - AC_MSG_ERROR([No Audio Output types configured!]) -fi - -if - test x$enable_shout = xyes || - test x$enable_httpd_output = xyes; then - echo " Streaming Encoder Support:" - if test x$enable_lame_encoder = xyes; then - echo " LAME mp3 encoder ..............enabled" - else - echo " LAME mp3 encoder ..............disabled" - fi - - if test x$enable_vorbis_encoder = xyes; then - echo " Ogg Vorbis encoder ............enabled" - else - echo " Ogg Vorbis encoder ............disabled" - fi - echo "" -fi - -echo " File Format Support:" - -if test x$enable_aac = xyes; then - echo " AAC support ...................enabled" -else - echo " AAC support ...................disabled" -fi - -if test x$enable_sidplay = xyes; then - echo " C64 SID support ...............enabled" -else - echo " C64 SID support ...............disabled" -fi - -if test x$enable_ffmpeg = xyes; then - echo " FFMPEG support ................enabled" -else - echo " FFMPEG support ................disabled" -fi - -if test x$enable_flac = xyes; then - echo " FLAC support ..................enabled" -else - echo " FLAC support ..................disabled" -fi - -if test x$enable_fluidsynth = xyes; then - echo " fluidsynth MIDI support .......enabled" -else - echo " fluidsynth MIDI support .......disabled" -fi - -if test x$enable_mikmod = xyes; then - echo " MikMod support ................enabled" -else - echo " MikMod support ................disabled" -fi - -if test x$enable_modplug = xyes; then - echo " MODPLUG support ...............enabled" -else - echo " MODPLUG support ...............disabled" -fi - -if test x$enable_mad = xyes; then - echo " MAD mp3 decoder support .......enabled" -else - echo " MAD mp3 decoder support .......disabled" -fi - -if test x$enable_mp4 = xyes; then - echo " MP4 support ...................enabled" -else - echo " MP4 support ...................disabled" -fi + test x$enable_openal = xno && + test x$enable_oss = xno && + test x$enable_osx = xno && + test x$enable_pipe_output = xno && + test x$enable_pulse = xno && + test x$enable_recorder_output = xno && + test x$enable_shout = xno && + test x$enable_solaris_output = xno && + test x$enable_winmm_output = xno && -if test x$enable_mpc = xyes; then - echo " Musepack (MPC) support ........enabled" -else - echo " Musepack (MPC) support ........disabled" + AC_MSG_ERROR([No Audio Output types configured!]) fi -case $enable_oggflac in -yes) - echo " OggFLAC support ...............enabled" - ;; -flac) - echo " OggFLAC support ...............enabled(FLAC 1.1.3)" - ;; -*) - echo " OggFLAC support ...............disabled" - ;; -esac +dnl --------------------------------------------------------------------------- +dnl Documentation +dnl --------------------------------------------------------------------------- +if test x$enable_documentation = xyes; then + AC_PATH_PROG(XMLTO, xmlto) + AC_SUBST(XMLTO) + AM_CONDITIONAL(HAVE_XMLTO, test x$XMLTO != x) -if test x$enable_vorbis = xyes; then - echo " Ogg Vorbis support ............enabled" - if test x$use_tremor = xyes; then - echo " using tremor.................yes" - else - echo " using tremor.................no" + AC_PATH_PROG(DOXYGEN, doxygen) + if test x$DOXYGEN = x; then + AC_MSG_ERROR([doxygen not found]) fi -else - echo " Ogg Vorbis support ............disabled" -fi - -if test x$enable_audiofile = xyes; then - echo " Wave file support .............enabled" -else - echo " Wave file support .............disabled" -fi - -if test x$enable_wavpack = xyes; then - echo " WavPack support ...............enabled" -else - echo " WavPack support ...............disabled" -fi -if test x$enable_wildmidi = xyes; then - echo " wildmidi MIDI support .........enabled" + AC_SUBST(DOXYGEN) else - echo " wildmidi MIDI support .........disabled" + AM_CONDITIONAL(HAVE_XMLTO, false) fi +AM_CONDITIONAL(ENABLE_DOCUMENTATION, test x$enable_documentation = xyes) +dnl --------------------------------------------------------------------------- +dnl test suite +dnl --------------------------------------------------------------------------- +AM_CONDITIONAL(ENABLE_TEST, test "x$enable_test" = xyes) -if - test x$enable_mad = xno && - test x$enable_vorbis = xno && - test x$enable_flac = xno && - test x$enable_oggflac = xno && - test x$enable_audiofile = xno && - test x$enable_aac = xno && - test x$enable_mpc = xno && - test x$enable_wavpack = xno && - test x$enable_ffmpeg = xno && - test x$enable_modplug = xno && - test x$enable_sidplay = xno && - test x$enable_fluidsynth = xno && - test x$enable_wildmidi = xno && - test x$enable_mp4 = xno && - test x$enable_mikmod = xno; then - AC_MSG_ERROR([No input plugins supported!]) -fi - -echo "" -echo " Archive support:" +dnl --------------------------------------------------------------------------- +dnl CFLAGS +dnl --------------------------------------------------------------------------- -if test x$enable_bzip2 = xyes; then - echo " BZ2 archives support ..........enabled" -else - echo " BZ2 archives support ..........disabled" -fi +dnl ---------------------------------- debug ---------------------------------- +#if test "x$enable_debug" = xno; then + # don't set NDEBUG for now, until MPD is stable + #AM_CFLAGS="$AM_CFLAGS -DNDEBUG" +#fi -if test x$enable_iso9660 = xyes; then - echo " ISO 9660 archives support .....enabled" -else - echo " ISO 9660 archives support .....disabled" +dnl ----------------------------------- GCC ----------------------------------- +if test x$GCC = xyes +then + MPD_CHECK_FLAG([-Wall]) + MPD_CHECK_FLAG([-Wextra]) + MPD_CHECK_FLAG([-Wno-deprecated-declarations]) + MPD_CHECK_FLAG([-Wmissing-prototypes]) + MPD_CHECK_FLAG([-Wshadow]) + MPD_CHECK_FLAG([-Wpointer-arith]) + MPD_CHECK_FLAG([-Wstrict-prototypes]) + MPD_CHECK_FLAG([-Wcast-qual]) + MPD_CHECK_FLAG([-Wwrite-strings]) + MPD_CHECK_FLAG([-pedantic]) fi -if test x$enable_zip = xyes; then - echo " ZIP archives support ..........enabled" -else - echo " ZIP archives support ..........disabled" +dnl ------------------------------ gprof profiler ----------------------------- +if test "x$enable_gprof" = xyes; then + MPD_CFLAGS="$MPD_CFLAGS -pg" + MPD_LIBS="$MPD_LIBS -pg" fi -echo "" -echo " Streaming support:" - -if test x$enable_lastfm = xyes; then - echo " last.fm radio support .........enabled" -else - echo " last.fm radio support .........disabled" +dnl ---------------------------- warnings as errors --------------------------- +if test "x$enable_werror" = xyes; then + AM_CFLAGS="$AM_CFLAGS -Werror -pedantic-errors" fi -if test x$enable_curl = xyes; then - echo " libcurl support (streaming) ...enabled" -else - echo " libcurl support (streaming) ...disabled" -fi +dnl --------------------------------------------------------------------------- +dnl Pretty-Print Results +dnl --------------------------------------------------------------------------- +echo '' +echo '########### MPD CONFIGURATION ############' -if test x$enable_mms = xyes; then - echo " libmms support ................enabled" -else - echo " libmms support ................disabled" -fi +echo -ne '\nArchive support:\n\t' +results(bzip2,[bzip2]) +results(iso9660,[ISO9660]) +results(zzip,[ZIP]) -echo "" -echo " Other features:" +if test x$with_zeroconf != xno; then + echo -ne '\nAutodiscovery support:\n\t' + results(avahi, [Avahi]) + results(bonjour, [Bonjour]) +fi + +echo -ne '\nClient support:\n\t' +results(ipv6, "IPv6") +results(tcp, "TCP") +results(un,[UNIX Domain Sockets]) + +echo -ne '\nFile format support:\n\t' +results(aac, [AAC]) +results(sidplay, [C64 SID]) +results(ffmpeg, [FFMPEG]) +results(flac, [FLAC]) +results(fluidsynth, [FluidSynth]) +results(gme, [GME]) +results(sndfile, [libsndfile]) +echo -ne '\n\t' +results(mikmod, [MikMod]) +results(modplug, [MODPLUG]) +results(mad, [MAD]) +results(mpg123, [MPG123]) +results(mp4, [MP4]) +results(mpc, [Musepack]) +results(oggflac, [OggFLAC], flac) +echo -ne '\n\t' +results(tremor, [OggTremor]) +results(vorbis, [OggVorbis]) +results(audiofile, [WAVE]) +results(wavpack, [WavPack]) +results(wildmidi, [WildMidi]) + +echo -en '\nOther features:\n\t' +results(lsr, [libsamplerate]) +results(inotify, [inotify]) +results(sqlite, [SQLite]) + +echo -en '\nMetadata support:\n\t' +results(cue,[cue]) +results(id3,[ID3]) + +echo -en '\nPlayback support:\n\t' +results(alsa,ALSA) +results(ffado,FFADO) +results(fifo,FIFO) +results(recorder_output,[File Recorder]) +results(httpd_output,[HTTP Daemon]) +results(jack,[JACK]) +results(ao,[libao]) +results(oss,[OSS]) +echo -ne '\n\t' +results(openal,[OpenAL]) +results(osx, [OS X]) +results(pipe_output, [Pipeline]) +results(pulse, [PulseAudio]) +results(mvp, [Media MVP]) +results(shout, [SHOUTcast]) +echo -ne '\n\t' +results(solaris, [Solaris]) +results(winmm_output, [WinMM]) -if test x$enable_id3 = xyes; then - echo " ID3 tag support ...............enabled" -else - echo " ID3 tag support ...............disabled" +if + test x$enable_shout = xyes || + test x$enable_recorder = xyes || + test x$enable_httpd_output = xyes; then + echo -en '\nStreaming encoder support:\n\t' + results(flac_encoder, [FLAC]) + results(lame_encoder, [LAME]) + results(vorbis_encoder, [Ogg Vorbis]) + results(twolame_encoder, [TwoLAME]) + results(wave_encoder, [WAVE]) fi -if test x$enable_lsr = xyes; then - echo " libsamplerate support .........enabled" -else - echo " libsamplerate support .........disabled" -fi +echo -en '\nStreaming support:\n\t' +results(curl,[CURL]) +results(lastfm,[Last.FM]) +results(mms,[MMS]) -if test x$with_zeroconf != xno; then - echo " Zeroconf support ..............$with_zeroconf" -else - echo " Zeroconf support ..............disabled" -fi +echo -ne '\n\n##########################################\n\n' -if test x$enable_cue = xyes; then - echo " libcue support ................enabled" -else - echo " libcue support ................disabled" +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 "" -echo "##########################################" -echo "" - -echo "Generating needed files for compilation" -echo "" - -dnl -dnl generate files -dnl +echo -ne 'Generating files needed for compilation\n' +dnl --------------------------------------------------------------------------- +dnl Generate files +dnl --------------------------------------------------------------------------- AC_OUTPUT(Makefile) -echo "" - -echo "You are now ready to compile MPD" -echo "Type \"make\" to compile MPD" +echo 'MPD is ready for compilation, type "make" to begin.' diff --git a/doc/doxygen.conf b/doc/doxygen.conf index 625e070ae..ddece77e8 100644 --- a/doc/doxygen.conf +++ b/doc/doxygen.conf @@ -534,7 +534,7 @@ WARN_LOGFILE = # directories like "/usr/src/myproject". Separate the files or directories # with spaces. -INPUT = src +INPUT = src/ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is @@ -25,17 +25,11 @@ Output a brief help message. Kill the currently running mpd session. The pid_file parameter must be specified in the config file for this to work. .TP -.BI --create-db -Force (re)creation of database. -.TP -.BI --no-create-db -Do not create database, even if it doesn't exist. -.TP .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..4cddd7ba9 100644 --- a/doc/mpd.conf.5 +++ b/doc/mpd.conf.5 @@ -129,6 +129,8 @@ audio that is sent to each audio output. Note that audio outputs may specify their own audio format which will be used for actual output to the audio device. An example is "44100:16:2" for 44100Hz, 16 bits, and 2 channels. The default is to use the audio format of the input file. +Any of the three attributes may be an asterisk to specify that this +attribute should not be enforced .TP .B samplerate_converter <integer or prefix> This specifies the libsamplerate converter to use. The supplied value should @@ -168,31 +170,14 @@ only choice) if MPD was compiled without libsamplerate. For an up-to-date list of available converters, please see the libsamplerate documentation (available online at <\fBhttp://www.mega-nerd.com/SRC/\fP>). .TP -.B mixer_type <alsa, oss, software, hardware or disabled> -This specifies which mixer to use. The default is hardware and depends on -what audio output support mpd was built with. Options alsa and oss are -legacy and should not be used in new configs, but when set mixer_device -and mixer_control will apply. -.TP -.B mixer_device <mixer dev> -This specifies which mixer to use. The default for oss is -"/dev/mixer"; the default for alsa is "default". This global option is -deprecated and should not be used. Look at the mixer_device option of -corresponding output device instead. -.TP -.B mixer_control <mixer ctrl> -This specifies which mixer control to use (sometimes referred to as -the "device"). Examples of mixer controls are PCM, Line1, Master, -etc. An example for OSS is "Pcm", and an example for alsa is -"PCM". This global option is deprecated and should not be used. Look -at the mixer_control option of corresponding output device instead. -.TP -.B replaygain <album or track> +.B replaygain <off or album or track or auto> If specified, mpd will adjust the volume of songs played using ReplayGain tags (see <\fBhttp://www.replaygain.org/\fP>). Setting this to "album" will adjust volume using the album's ReplayGain tags, while setting it to "track" will -adjust it using the track ReplayGain tags. Currently only FLAC, Ogg Vorbis, -Musepack, and MP3 (through ID3v2 ReplayGain tags, not APEv2) are supported. +adjust it using the track ReplayGain tags. "auto" uses the track ReplayGain +tags if random play is activated otherwise the album ReplayGain tags. Currently +only FLAC, Ogg Vorbis, Musepack, and MP3 (through ID3v2 ReplayGain tags, not +APEv2) are supported. .TP .B replaygain_preamp <-15 to 15> This is the gain (in dB) applied to songs with ReplayGain tags. @@ -265,6 +250,15 @@ tags may be specified as a comma separated list. An example value is "artist,album,title,track". The special value "none" may be used alone to disable all metadata. The default is to use all known tag types except for comments. +.TP +.B auto_update <yes or no> +This specifies the wheter to support automatic update of music database when +files are changed in music_directory. The default is to disable autoupdate +of database. +.TP +.B auto_update_depth <N> +Limit the depth of the directories being watched, 0 means only watch +the music directory itself. There is no limit by default. .SH REQUIRED AUDIO OUTPUT PARAMETERS .TP .B type <type> @@ -280,11 +274,25 @@ This specifies the sample rate, bits per sample, and number of channels of audio that is sent to the audio output device. See documentation for the \fBaudio_output_format\fP parameter for more details. The default is to use whatever audio format is passed to the audio output. +Any of the three attributes may be an asterisk to specify that this +attribute should not be enforced +.TP +.B replay_gain_handler <software, mixer or none> +Specifies how replay gain is applied. The default is "software", +which uses an internal software volume control. "mixer" uses the +configured (hardware) mixer control. "none" disables replay gain on +this audio output. .SH OPTIONAL ALSA OUTPUT PARAMETERS .TP .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". @@ -352,13 +360,12 @@ after another until it successfully establishes a connection. .TP .B sink <sink> The sink to output to. The default is to let PulseAudio choose a sink. -.SH REQUIRED JACK OUTPUT PARAMETERS +.SH OPTIONAL JACK OUTPUT PARAMETERS .TP -.B name <name> +.B client_name <name> The client name to use when connecting to JACK. The output ports <name>:left and <name>:right will also be created for the left and right channels, respectively. -.SH OPTIONAL JACK OUTPUT PARAMETERS .TP .B ports <left_port,right_port> This specifies the left and right ports to connect to for the left and right diff --git a/doc/mpdconf.example b/doc/mpdconf.example index 11b14bd93..b14337c76 100644 --- a/doc/mpdconf.example +++ b/doc/mpdconf.example @@ -49,6 +49,11 @@ # #state_file "~/.mpd/state" # +# The location of the sticker database. This is a database which +# manages dynamic information attached to songs. +# +#sticker_file "~/.mpd/sticker.sql" +# ############################################################################### @@ -61,6 +66,13 @@ # #user "nobody" # +# This setting specifies the group that MPD will run as. If not specified +# primary group of user specified with "user" setting will be used (if set). +# This is useful if MPD needs to be a member of group such as "audio" to +# have permission to use sound card. +# +#group "nogroup" +# # This setting sets the address for the daemon to listen on. Careful attention # should be paid if this is assigned to anything other then the default, any. # This setting can deny access to control of the daemon. @@ -102,6 +114,16 @@ # #metadata_to_use "artist,album,title,track,name,genre,date,composer,performer,disc" # +# This setting enables automatic update of MPD's database when files in +# music_directory are changed. +# +#auto_update "yes" +# +# Limit the depth of the directories being watched, 0 means only watch +# the music directory itself. There is no limit by default. +# +#auto_update_depth "3" +# ############################################################################### @@ -179,6 +201,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 +214,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 +238,19 @@ input { ## genre "jazz" # optional ## public "no" # optional ## timeout "2" # optional +## mixer_type "software" # optional +#} +# +# An example of a recorder output: +# +#audio_output { +# type "recorder" +# name "My recorder" +# encoder "vorbis" # optional, vorbis or lame +# path "/var/lib/mpd/recorder/mpd.ogg" +## quality "5.0" # do not define if bitrate is defined +# bitrate "128" # do not define if quality is defined +# format "44100:16:1" #} # # An example of a httpd output (built-in HTTP streaming server): @@ -223,9 +260,11 @@ input { # name "My HTTP Stream" # encoder "vorbis" # optional, vorbis or lame # port "8000" +# bind_to_address "0.0.0.0" # optional, IPv4 or IPv6 ## quality "5.0" # do not define if bitrate is defined # bitrate "128" # do not define if quality is defined # format "44100:16:1" +# max_clients "0" # optional 0=no limit #} # # An example of a pulseaudio output (streaming to a remote pulseaudio server) @@ -255,6 +294,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,38 +313,11 @@ 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 -# the argument "album" or "track". See <http://www.replaygain.org> for more -# details. This setting is disabled by default. +# the argument "off", "album" or "track". See <http://www.replaygain.org> +# for more details. This setting is off by default. # #replaygain "album" # @@ -357,8 +370,7 @@ input { # Character Encoding ########################################################## # # If file or directory names do not display correctly for your locale then you -# may need to modify this setting. After modification of this setting mpd -# --create-db must be run to change the database. +# may need to modify this setting. # #filesystem_charset "UTF-8" # @@ -367,3 +379,29 @@ input { #id3v1_encoding "ISO-8859-1" # ############################################################################### + + +# SIDPlay decoder ############################################################# +# +# songlength_database: +# Location of your songlengths file, as distributed with the HVSC. +# The sidplay plugin checks this for matching MD5 fingerprints. +# See http://www.c64.org/HVSC/DOCUMENTS/Songlengths.faq +# +# default_songlength: +# This is the default playing time in seconds for songs not in the +# songlength database, or in case you're not using a database. +# A value of 0 means play indefinitely. +# +# filter: +# Turns the SID filter emulation on or off. +# +#decoder { +# plugin "sidplay" +# songlength_database "/media/C64Music/DOCUMENTS/Songlengths.txt" +# default_songlength "120" +# filter "true" +#} +# +############################################################################### + diff --git a/doc/protocol.xml b/doc/protocol.xml index 2bc7d55ae..3eb5aa932 100644 --- a/doc/protocol.xml +++ b/doc/protocol.xml @@ -67,6 +67,20 @@ successful command executed in the command list. </para> </section> + + <section> + <title>Ranges</title> + + <para> + Some commands (e.g. <link + linkend="command_delete"><command>delete</command></link>) + allow specifying a range in the form + <parameter>START:END</parameter> (the <varname>END</varname> + item is not included in the range, similar to ranges in the + Python programming language). If <varname>END</varname> is + omitted, then the maximum possible value is assumed. + </para> + </section> </chapter> <chapter> @@ -123,7 +137,7 @@ </term> <listitem> <para> - <footnote id="since_0_14"><simpara>Since MPD 0.14</simpara></footnote> + <footnote id="since_0_14"><simpara>Introduced with MPD 0.14</simpara></footnote> Waits until there is a noteworthy change in one or more of MPD's subsystems. As soon as there is one, it lists all changed systems in a line in the format @@ -139,6 +153,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 @@ -172,7 +195,7 @@ <para> <returnvalue>options</returnvalue>: options like <option>repeat</option>, <option>random</option>, - <option>crossfade</option> + <option>crossfade</option>, replay gain </para> </listitem> </itemizedlist> @@ -191,9 +214,6 @@ MPD will only send notifications when something changed in one of the specified subsytems. </para> - <simpara> - Since <application>MPD</application> 0.14 - </simpara> </listitem> </varlistentry> <varlistentry id="command_status"> @@ -221,17 +241,23 @@ </para> </listitem> <listitem> + <para> + <varname>random</varname>: + <returnvalue>0 or 1</returnvalue> + </para> + </listitem> + <listitem> <para> <varname>single</varname>: + <footnote id="since_0_15"><simpara>Introduced with MPD 0.15</simpara></footnote> <returnvalue>0 or 1</returnvalue> - <footnote id="since_0_15"><simpara>Since MPD 0.15</simpara></footnote> </para> </listitem> <listitem> <para> <varname>consume</varname>: - <returnvalue>0 or 1</returnvalue> <footnoteref linkend="since_0_15"/> + <returnvalue>0 or 1</returnvalue> </para> </listitem> <listitem> @@ -297,6 +323,16 @@ </listitem> <listitem> <para> + <varname>elapsed</varname>: + <footnote id="since_0_16"><simpara>Introduced with 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> @@ -310,6 +346,18 @@ </listitem> <listitem> <para> + <varname>mixrampdb</varname>: + <returnvalue>mixramp threshold in dB</returnvalue> + </para> + </listitem> + <listitem> + <para> + <varname>mixrampdelay</varname>: + <returnvalue>mixrampdelay in seconds</returnvalue> + </para> + </listitem> + <listitem> + <para> <varname>audio</varname>: <returnvalue>sampleRate:bits:channels</returnvalue> </para> @@ -412,6 +460,32 @@ </para> </listitem> </varlistentry> + <varlistentry id="command_mixrampdb"> + <term> + <cmdsynopsis> + <command>mixrampdb</command> + <arg choice="req"><replaceable>deciBels</replaceable></arg> + </cmdsynopsis> + </term> + <listitem> + <para> + Sets the threshold at which songs will be overlapped. Like crossfading but doesn't fade the track volume, just overlaps. The songs need to have MixRamp tags added by an external tool. 0dB is the normalized maximum volume so use negative values, I prefer -17dB. In the absence of mixramp tags crossfading will be used. See http://sourceforge.net/projects/mixramp + </para> + </listitem> + </varlistentry> + <varlistentry id="command_mixrampdelay"> + <term> + <cmdsynopsis> + <command>mixrampdelay</command> + <arg choice="req"><replaceable>SECONDS</replaceable></arg> + </cmdsynopsis> + </term> + <listitem> + <para> + Additional time subtracted from the overlap calculated by mixrampdb. A value of "nan" disables MixRamp overlapping and falls back to crossfading. + </para> + </listitem> + </varlistentry> <varlistentry id="command_random"> <term> <cmdsynopsis> @@ -471,23 +545,43 @@ </para> </listitem> </varlistentry> - <varlistentry id="command_volume"> + <varlistentry id="command_replay_gain_mode"> <term> <cmdsynopsis> - <command>volume</command> - <arg choice="req"><replaceable>CHANGE</replaceable></arg> + <command>replay_gain_mode</command> + <arg choice="req"><replaceable>MODE</replaceable></arg> </cmdsynopsis> </term> <listitem> <para> - Changes volume by amount <varname>CHANGE</varname>. + Sets the replay gain mode. One of + <parameter>off</parameter>, + <parameter>track</parameter>, + <parameter>album</parameter>. + </para> + <para> + Changing the mode during playback may take several + seconds, because the new settings does not affect the + buffered data. + </para> + <para> + This command triggers the + <returnvalue>options</returnvalue> idle event. + </para> + </listitem> + </varlistentry> + <varlistentry id="command_replay_gain_status"> + <term> + <cmdsynopsis> + <command>replay_gain_status</command> + </cmdsynopsis> + </term> + <listitem> + <para> + Prints replay gain options. Currently, only the + variable <varname>replay_gain_mode</varname> is + returned. </para> - <note> - <para> - <command>volume</command> is deprecated, use - <command>setvol</command> instead. - </para> - </note> </listitem> </varlistentry> </variablelist> @@ -648,10 +742,7 @@ </para> <para> <varname>URI</varname> is always a single file or - URL. <varname>POSITION</varname> is optional, a - negative number means it is relative to the currently - playing song in the playlist (if there is one). - For example: + URL. For example: </para> <screen> addid "foo.mp3" @@ -676,7 +767,10 @@ OK <term> <cmdsynopsis> <command>delete</command> - <arg choice="req"><replaceable>SONGPOS</replaceable></arg> + <group> + <arg choice="req"><replaceable>POS</replaceable></arg> + <arg choice="req"><replaceable>START:END</replaceable></arg> + </group> </cmdsynopsis> </term> <listitem> @@ -910,6 +1004,20 @@ OK <section> <title>Stored playlists</title> + <para> + Playlists are stored inside the configured playlist directory. + They are addressed with their file name (without the directory + and without the <filename>.m3u</filename> suffix). + </para> + + <para> + Some of the commands described in this section can be used to + run playlist plugins instead of the hard-coded simple + <filename>m3u</filename> parser. They can access playlists in + the music directory (relative path including the suffix) or + remote playlists (absolute URI with a supported scheme). + </para> + <variablelist> <varlistentry id="command_listplaylist"> <term> @@ -920,8 +1028,8 @@ OK </term> <listitem> <para> - Lists the files in the playlist - <filename>NAME.m3u</filename>. + Lists the songs in the playlist. Playlist plugins are + supported. </para> </listitem> </varlistentry> @@ -934,7 +1042,8 @@ OK </term> <listitem> <para> - Lists songs in the playlist <filename>NAME.m3u</filename>. + Lists the songs with metadata in the playlist. Playlist + plugins are supported. </para> </listitem> </varlistentry> @@ -966,8 +1075,8 @@ OK </term> <listitem> <para> - Loads the playlist <filename>NAME.m3u</filename> from - the playlist directory. + Loads the playlist into the current queue. Playlist + plugins are supported. </para> </listitem> </varlistentry> @@ -1105,16 +1214,35 @@ OK <command>find</command> <arg choice="req"><replaceable>TYPE</replaceable></arg> <arg choice="req"><replaceable>WHAT</replaceable></arg> + <arg choice="opt"><replaceable>...</replaceable></arg> + </cmdsynopsis> + </term> + <listitem> + <para> + Finds songs in the db that are exactly + <varname>WHAT</varname>. <varname>TYPE</varname> can + be any tag supported by MPD, or one of the two special + parameters — <parameter>file</parameter> to search by + full path (relative to database root), and + <parameter>any</parameter> to match against all + available tags. <varname>WHAT</varname> is what to find. + </para> + </listitem> + </varlistentry> + <varlistentry id="command_findadd"> + <term> + <cmdsynopsis> + <command>findadd</command> + <arg choice="req"><replaceable>TYPE</replaceable></arg> + <arg choice="req"><replaceable>WHAT</replaceable></arg> + <arg choice="opt"><replaceable>...</replaceable></arg> </cmdsynopsis> </term> <listitem> <para> Finds songs in the db that are exactly - <varname>WHAT</varname>. <varname>TYPE</varname> should - be <parameter>album</parameter>, - <parameter>artist</parameter>, or - <parameter>title</parameter>. <varname>WHAT</varname> - is what to find. + <varname>WHAT</varname> and adds them to current playlist. + Parameters have the same meaning as for <command>find</command>. </para> </listitem> </varlistentry> @@ -1129,7 +1257,8 @@ OK <listitem> <para> Lists all tags of the specified type. - <varname>TYPE</varname> should be album or artist. + <varname>TYPE</varname> can be any tag supported by MPD or + <parameter>file</parameter>. </para> <para> <varname>ARTIST</varname> is an optional parameter when @@ -1192,17 +1321,15 @@ OK <command>search</command> <arg choice="req"><replaceable>TYPE</replaceable></arg> <arg choice="req"><replaceable>WHAT</replaceable></arg> + <arg choice="opt"><replaceable>...</replaceable></arg> </cmdsynopsis> </term> <listitem> <para> Searches for any song that contains - <varname>WHAT</varname>. <varname>TYPE</varname> can be - <parameter>title</parameter>, - <parameter>artist</parameter>, - <parameter>album</parameter> or - <parameter>filename</parameter>. Search is not case - sensitive. + <varname>WHAT</varname>. Parameters have the same meaning + as for <command>find</command>, except that search is not + case sensitive. </para> </listitem> </varlistentry> @@ -1231,6 +1358,20 @@ OK </para> </listitem> </varlistentry> + <varlistentry id="command_rescan"> + <term> + <cmdsynopsis> + <command>rescan</command> + <arg choice="opt"><replaceable>URI</replaceable></arg> + </cmdsynopsis> + </term> + <listitem> + <para> + Same as <command>update</command>, but also rescans + unmodified files. + </para> + </listitem> + </varlistentry> </variablelist> </section> @@ -1508,6 +1649,25 @@ OK </para> </listitem> </varlistentry> + <varlistentry id="command_decoders"> + <term> + <cmdsynopsis> + <command>decoders</command> + </cmdsynopsis> + </term> + <listitem> + <para> + Print a list of decoder plugins, followed by their + supported suffixes and MIME types. Example response: + </para> + <programlisting>plugin: mad +suffix: mp3 +suffix: mp2 +mime_type: audio/mpeg +plugin: mpcdec +suffix: mpc</programlisting> + </listitem> + </varlistentry> </variablelist> </section> </chapter> diff --git a/doc/sticker.xml b/doc/sticker.xml deleted file mode 100644 index 0e27ba8cf..000000000 --- a/doc/sticker.xml +++ /dev/null @@ -1,91 +0,0 @@ -<?xml version='1.0' encoding="utf-8"?> -<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" - "docbook/dtd/xml/4.2/docbookx.dtd"> -<book> - <title>The Music Player Daemon Sticker Database</title> - - <chapter> - <title>Introduction to MPD's Sticker Database</title> - <para> - This document shell give a short guideline for recommended tags - for use in MPD's Sticker Database. - MPD's Sticker Database is a subsystem that enables users to add - custom tags. MPD does not alter the media files. - </para> - </chapter> - - <chapter> - <title>Guideline for recommended tags</title> - <para> - Since there is no standard for tags in media files, this - document is trying to give you some help deciding what tags to - use. The selection of these tags tries to cover the most - widely used tags. This way the tags might still work in other - players, if you sync the database with your original media - files. - Keep in mind that we stick with lower case tags with underscores - instead of spaces. If there will be a Sync tool in future - its easy to change this on the fly, if needed. - </para> - - <variablelist> - <varlistentry> - <term><varname>rating</varname></term> - <listitem> - <para> - Will store a rating value from 1 (worst) to 5 (best) for a - given song. - </para> - </listitem> - </varlistentry> - - <varlistentry> - <term><varname>album_rating</varname></term> - <listitem> - <para> - Will store a rating value from 1 (worst) to 5 (best) for a - given album. - </para> - </listitem> - </varlistentry> - - <varlistentry> - <term><varname>style</varname></term> - <listitem> - <para> - This tag is used to keep the Genre tag clean, by now - having 1000's of genres. Instead you define a Main Genre - for each file and can make a more specific - description. This should be one Keyword like "Post Punk" - or "Progressive Death Metal" An Alternative name for this - tag is "Subgenre", time will tell which one gets more - support. - </para> - </listitem> - </varlistentry> - - <varlistentry> - <term><varname>lyrics</varname></term> - <listitem> - <para> - This one is self explaining. This gives the option to - store lyrics of a song where they belong to: mapped to the - song - </para> - </listitem> - </varlistentry> - - <varlistentry> - <term><varname>similar_artists</varname> (Comma seperated list of artists)</term> - <listitem> - <para> - This tag enables a last.fm alike aproach which will still - work when being offline Keep in mind, that this tag is - absolutely non-standard! I am not aware of any other - player that uses a comparable tag. - </para> - </listitem> - </varlistentry> - </variablelist> - </chapter> -</book> diff --git a/doc/user.xml b/doc/user.xml index 6c3f5edeb..17bbdf91f 100644 --- a/doc/user.xml +++ b/doc/user.xml @@ -300,10 +300,30 @@ cd mpd-version</programlisting> <varname>format</varname> </entry> <entry> - Always open the audio output with the specified audio - format (samplerate:bits:channels), regardless of the - format of the input file. This is optional for most - plugins. + <para> + Always open the audio output with the specified audio + format (samplerate:bits:channels), regardless of the + format of the input file. This is optional for most + plugins. + </para> + <para> + Any of the three attributes may be an asterisk to + specify that this attribute should not be enforced, + example: <parameter>48000:16:*</parameter>. + <parameter>*:*:*</parameter> is equal to not having + a <varname>format</varname> specification. + </para> + <para> + The following values are valid for + <varname>bits</varname>: <varname>8</varname> + (signed 8 bit integer samples), + <varname>16</varname>, <varname>24</varname> (signed + 24 bit integer samples padded to 32 bit), + <varname>24_3</varname> (signed 24 bit integer + samples, no padding, 3 bytes per sample), + <varname>32</varname> (signed 32 bit integer + samples). + </para> </entry> </row> <row> @@ -319,13 +339,159 @@ cd mpd-version</programlisting> </row> <row> <entry> - <varname>mixer_enabled</varname> + <varname>always_on</varname> + <parameter>yes|no</parameter> + </entry> + <entry> + If set to "yes", then MPD attempts to keep this audio + output always open. This may be useful for streaming + servers, when you don't want to disconnect all + listeners even when playback is accidently stopped. + </entry> + </row> + <row> + <entry> + <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> + <row> + <entry> + <varname>replay_gain_handler</varname> + <parameter>software|mixer|none</parameter> + </entry> + <entry> + Specifies how replay gain is applied. The default is + "software", which uses an internal software volume + control. "mixer" uses the configured (hardware) mixer + control. "none" disables replay gain on this audio + output. + </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> + The name of the filter. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> + + <section> + <title>Configuring playlist plugins</title> + + <para> + Playlist plugins are used to load remote playlists. This is + not related to MPD's playlist directory. + </para> + + <para> + To configure a filter, add a + <varname>playlist_plugin</varname> block to + <filename>mpd.conf</filename>: + </para> + + <programlisting>playlist_plugin { + name "m3u" + enabled "true" +} + </programlisting> + + <para> + The following table lists the + <varname>playlist_plugin</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>name</varname> + </entry> + <entry> + The name of the plugin. + </entry> + </row> + <row> + <entry> + <varname>enabled</varname> <parameter>yes|no</parameter> </entry> <entry> - Specifies whether the hardware mixer of this audio - output should be used. By default, all hardware - mixers are enabled if available. + Allows you to disable a input plugin without + recompiling. By default, all plugins are enabled. </entry> </row> </tbody> @@ -335,6 +501,68 @@ cd mpd-version</programlisting> </chapter> <chapter> + <title>Using MPD</title> + + <section> + <title>The client</title> + + <para> + After you have installed, configured and started MPD, you + choose a client to control the playback. + </para> + + <para> + The most basic client is <filename>mpc</filename>, which + provides a command line interface. It is useful in shell + scripts. Many people bind specific <filename>mpc</filename> + commands to hotkeys. + </para> + + <para> + The <ulink url="http://mpd.wikia.com/wiki/Clients">MPD + Wiki</ulink> contains an extensive list of clients to choose + from. + </para> + </section> + + <section> + <title>The music directory and the database</title> + + <para> + The "music directory" is where you store your music files. + MPD stores all relevant meta information about all songs in + its "database". Whenever you add, modify or remove songs in + the music directory, you have to update the database, for + example with <filename>mpc</filename>: + </para> + + <programlisting>mpc update</programlisting> + + <para> + Depending on the size of your music collection and the speed + of the storage, this can take a while. + </para> + + <para> + To exclude a file from the update, create a file called + <filename>.mpdignore</filename> in its parent directory. Each + line of that file may contain a list of shell wildcards. + </para> + </section> + + <section> + <title>The queue</title> + + <para> + The queue (sometimes called "current playlist") is a list of + songs to be played by MPD. To play a song, add it to the + queue and start playback. Most clients offer an interface to + edit the queue. + </para> + </section> + </chapter> + + <chapter> <title>Plugin reference</title> <section> @@ -387,20 +615,45 @@ cd mpd-version</programlisting> </section> <section> - <title><varname>lastfm</varname></title> + <title><varname>mms</varname></title> <para> - Plays last.fm radio. This plugin is experimental, and will - be superseded by a better solution in MPD 0.16. + Plays streams with the MMS protocol. </para> </section> + </section> + + <section> + <title>Decoder plugins</title> <section> - <title><varname>mms</varname></title> + <title><varname>mikmod</varname></title> <para> - Plays streams with the MMS protocol. + Module player based on MikMod. </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>sample_rate</varname> + </entry> + <entry> + Sets the sample rate generated by + <filename>libmikmod</filename>. Default is 44100. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> </section> </section> @@ -536,12 +789,124 @@ cd mpd-version</programlisting> </section> <section> + <title><varname>ffado</varname></title> + + <para> + The <varname>ffado</varname> plugin connects to FireWire + audio devices via <filename>libffado</filename>. + </para> + + <para> + Warning: this plugin was not tested successfully. I just + couldn't keep libffado2 from crashing. Use at your own + risk. + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>device</varname> + <parameter>NAME</parameter> + </entry> + <entry> + Sets the device which should be used, e.g. "hw:0". + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> + + <section> <title><varname>jack</varname></title> <para> The <varname>jack</varname> plugin connects to a JACK server. </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>client_name</varname> + <parameter>NAME</parameter> + </entry> + <entry> + The name of the JACK client. Defaults to "Music + Player Daemon". + </entry> + </row> + <row> + <entry> + <varname>server_name</varname> + <parameter>NAME</parameter> + </entry> + <entry> + Optional name of the JACK server. + </entry> + </row> + <row> + <entry> + <varname>autostart</varname> + <parameter>yes|no</parameter> + </entry> + <entry> + If set to <parameter>yes</parameter>, then + <filename>libjack</filename> will automatically + launch the JACK daemon. Disabled by default. + </entry> + </row> + <row> + <entry> + <varname>source_ports</varname> + <parameter>A,B</parameter> + </entry> + <entry> + The names of the JACK source ports to be created. + By default, the ports "left" and "right" are + created. To use more ports, you have to tweak this + option. + </entry> + </row> + <row> + <entry> + <varname>destination_ports</varname> + <parameter>A,B</parameter> + </entry> + <entry> + The names of the JACK destination ports to connect to. + </entry> + </row> + <row> + <entry> + <varname>ringbuffer_size</varname> + <parameter>NBYTES</parameter> + </entry> + <entry> + Sets the size of the ring buffer for each channel. + Do not configure this value unless you know what + you're doing. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> </section> <section> @@ -586,8 +951,17 @@ cd mpd-version</programlisting> <parameter>P</parameter> </entry> <entry> - Binds the HTTP server to the specified port (on all - interfaces). + Binds the HTTP server to the specified port. + </entry> + </row> + <row> + <entry> + <varname>bind_to_address</varname> + <parameter>ADDR</parameter> + </entry> + <entry> + Binds the HTTP server to the specified address (IPv4 or + IPv6). Multiple addresses in parallel are not supported. </entry> </row> <row> @@ -620,6 +994,16 @@ cd mpd-version</programlisting> second. </entry> </row> + <row> + <entry> + <varname>max_clients</varname> + <parameter>MC</parameter> + </entry> + <entry> + Sets a limit, number of concurrent clients. When set + to 0 no limit will apply. + </entry> + </row> </tbody> </tgroup> </informaltable> @@ -694,6 +1078,39 @@ cd mpd-version</programlisting> </section> <section> + <title><varname>openal</varname></title> + + <para> + The "OpenAL" plugin uses <filename>libopenal</filename>. + It is supported on many platforms. + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>device</varname> + <parameter>NAME</parameter> + </entry> + <entry> + Sets the device which should be used. This can be + any valid OpenAL device name. If not specified, then + <filename>libopenal</filename> will choose a default device. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> + + <section> <title><varname>osx</varname></title> <para> @@ -776,6 +1193,73 @@ cd mpd-version</programlisting> </section> <section> + <title><varname>recorder</varname></title> + + <para> + The <varname>recorder</varname> plugin writes the audio + played by MPD to a file. This may be useful for recording + radio streams. + </para> + + <para> + You must configure either <varname>quality</varname> or + <varname>bitrate</varname>. + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>path</varname> + <parameter>P</parameter> + </entry> + <entry> + Write to this file. + </entry> + </row> + <row> + <entry> + <varname>encoder</varname> + <parameter>NAME</parameter> + </entry> + <entry> + Chooses an encoder plugin, + e.g. <parameter>vorbis</parameter>. + </entry> + </row> + <row> + <entry> + <varname>quality</varname> + <parameter>Q</parameter> + </entry> + <entry> + Configures the encoder quality (for VBR) in the + range -1 .. 10. + </entry> + </row> + <row> + <entry> + <varname>bitrate</varname> + <parameter>BR</parameter> + </entry> + <entry> + Sets a constant encoder bit rate, in kilobit per + second. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> + + <section> <title><varname>shout</varname></title> <para> @@ -939,5 +1423,81 @@ cd mpd-version</programlisting> </informaltable> </section> </section> + + <section> + <title>Playlist plugins</title> + + <section> + <title><varname>lastfm</varname></title> + + <para> + Plays last.fm radio. + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>user</varname> + <parameter>USERNAME</parameter> + </entry> + <entry> + The last.fm user name. + </entry> + </row> + <row> + <entry> + <varname>password</varname> + <parameter>PWD</parameter> + </entry> + <entry> + The last.fm password. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> + + <section> + <title><varname>m3u</varname></title> + + <para> + Reads <filename>.m3u</filename> playlist files. + </para> + </section> + + <section> + <title><varname>extm3u</varname></title> + + <para> + Reads extended <filename>.m3u</filename> playlist files. + </para> + </section> + + <section> + <title><varname>pls</varname></title> + + <para> + Reads <filename>.pls</filename> playlist files. + </para> + </section> + + <section> + <title><varname>xspf</varname></title> + + <para> + Reads <ulink url="http://www.xspf.org/">XSPF</ulink> + playlist files. + </para> + </section> + </section> </chapter> </book> diff --git a/m4/lame.m4 b/m4/lame.m4 deleted file mode 100644 index 0723f7dba..000000000 --- a/m4/lame.m4 +++ /dev/null @@ -1,111 +0,0 @@ -dnl borrowed from oddsock.org -dnl AM_PATH_LAME([ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) -dnl Test for liblame, and define LAME_CFLAGS and LAME_LIBS -dnl -AC_DEFUN([AM_PATH_LAME], -[dnl -dnl Get the cflags and libraries -dnl -AC_ARG_WITH(lame, - AS_HELP_STRING([--with-lame=PFX], - [prefix where liblame is installed (optional)]),, - lame_prefix="") -AC_ARG_WITH(lame-libraries, - AS_HELP_STRING([--with-lame-libraries=DIR], - [directory where liblame library is installed (optional)]),, - lame_libraries="") -AC_ARG_WITH(lame-includes, - AS_HELP_STRING([--with-lame-includes=DIR], - [directory where liblame header files are installed (optional)]),, - lame_includes="") - -if test "x$lame_prefix" != "xno" ; then - - if test "x$lame_libraries" != "x" ; then - LAME_LIBS="-L$lame_libraries" - elif test "x$lame_prefix" != "x" ; then - LAME_LIBS="-L$lame_prefix/lib" - elif test "x$prefix" != "xNONE" ; then - LAME_LIBS="-L$prefix/lib" - fi - - LAME_LIBS="$LAME_LIBS -lmp3lame -lm" - - if test "x$lame_includes" != "x" ; then - LAME_CFLAGS="-I$lame_includes" - elif test "x$lame_prefix" != "x" ; then - LAME_CFLAGS="-I$lame_prefix/include" - elif test "x$prefix" != "xNONE"; then - LAME_CFLAGS="-I$prefix/include" - fi - - AC_MSG_CHECKING(for liblame) - no_lame="" - - - ac_save_CFLAGS="$CFLAGS" - ac_save_LIBS="$LIBS" - CFLAGS="$CFLAGS $LAME_CFLAGS" - LIBS="$LIBS $LAME_LIBS" -dnl -dnl Now check if the installed liblame is sufficiently new. -dnl - rm -f conf.lametest - AC_TRY_RUN([ -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <lame/lame.h> - -int main () -{ - system("touch conf.lametest"); - return 0; -} - -],, no_lame=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"]) - CFLAGS="$ac_save_CFLAGS" - LIBS="$ac_save_LIBS" - fi - - if test "x$no_lame" = "x" ; then - AC_MSG_RESULT(yes) - ifelse([$1], , :, [$1]) - else - AC_MSG_RESULT(no) - if test -f conf.lametest ; then - : - else - echo "*** Could not run liblame test program, checking why..." - CFLAGS="$CFLAGS $LAME_CFLAGS" - LIBS="$LIBS $LAME_LIBS" - AC_TRY_LINK([ -#include <stdio.h> -#include <lame/lame.h> -], [ return 0; ], - [ echo "*** The test program compiled, but did not run. This usually means" - echo "*** that the run-time linker is not finding liblame or finding the wrong" - echo "*** version of liblame. If it is not finding liblame, you'll need to set your" - echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point" - echo "*** to the installed location Also, make sure you have run ldconfig if that" - echo "*** is required on your system" - echo "***" - echo "*** If you have an old version installed, it is best to remove it, although" - echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"], - [ echo "*** The test program failed to compile or link. See the file config.log for the" - echo "*** exact error that occured. This usually means liblame was incorrectly installed" - echo "*** or that you have moved liblame since it was installed." ]) - CFLAGS="$ac_save_CFLAGS" - LIBS="$ac_save_LIBS" - fi - LAME_CFLAGS="" - LAME_LIBS="" - ifelse([$2], , :, [$2]) - fi - AC_DEFINE(HAVE_LAME, 1, [Define if you have liblame.]) - use_lame="1" - AC_SUBST(LAME_CFLAGS) - AC_SUBST(LAME_LIBS) - rm -f conf.lametest -]) - diff --git a/m4/libOggFLAC.m4 b/m4/libOggFLAC.m4 deleted file mode 100644 index 8167c4d68..000000000 --- a/m4/libOggFLAC.m4 +++ /dev/null @@ -1,116 +0,0 @@ -# Configure paths for libOggFLAC -# "Inspired" by ogg.m4 - -dnl AM_PATH_LIBOGGFLAC([ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) -dnl Test for libOggFLAC, and define LIBOGGFLAC_CFLAGS and LIBOGGFLAC_LIBS -dnl -AC_DEFUN([AM_PATH_LIBOGGFLAC], -[dnl -dnl Get the cflags and libraries -dnl -AC_ARG_WITH(libOggFLAC, - AS_HELP_STRING([--with-libOggFLAC=PFX], - [prefix where libOggFLAC is installed (optional)]),, - libOggFLAC_prefix="") -AC_ARG_WITH(libOggFLAC-libraries, - AS_HELP_STRING([--with-libOggFLAC-libraries=DIR], - [directory where libOggFLAC library is installed (optional)]),, - libOggFLAC_libraries="") -AC_ARG_WITH(libOggFLAC-includes, - AS_HELP_STRING([--with-libOggFLAC-includes=DIR], - [directory where libOggFLAC header files are installed (optional)]),, - libOggFLAC_includes="") -AC_ARG_ENABLE(libOggFLACtest, - AS_HELP_STRING([--disable-libOggFLACtest], - [do not try to compile and run a test libOggFLAC program]),, - enable_libOggFLACtest=yes) - - if test "x$libOggFLAC_libraries" != "x" ; then - LIBOGGFLAC_LIBS="-L$libOggFLAC_libraries" - elif test "x$libOggFLAC_prefix" != "x" ; then - LIBOGGFLAC_LIBS="-L$libOggFLAC_prefix/lib" - elif test "x$prefix" != "xNONE" ; then - LIBOGGFLAC_LIBS="-L$libdir" - fi - - LIBOGGFLAC_LIBS="$LIBOGGFLAC_LIBS -lOggFLAC -lFLAC -lm" - - if test "x$libOggFLAC_includes" != "x" ; then - LIBOGGFLAC_CFLAGS="-I$libOggFLAC_includes" - elif test "x$libOggFLAC_prefix" != "x" ; then - LIBOGGFLAC_CFLAGS="-I$libOggFLAC_prefix/include" - elif test "x$prefix" != "xNONE"; then - LIBOGGFLAC_CFLAGS="-I$prefix/include" - fi - - AC_MSG_CHECKING(for libOggFLAC) - no_libOggFLAC="" - - - if test "x$enable_libOggFLACtest" = "xyes" ; then - ac_save_CFLAGS="$CFLAGS" - ac_save_CXXFLAGS="$CXXFLAGS" - ac_save_LIBS="$LIBS" - CFLAGS="$CFLAGS $LIBOGGFLAC_CFLAGS" - CXXFLAGS="$CXXFLAGS $LIBOGGFLAC_CFLAGS" - LIBS="$LIBS $LIBOGGFLAC_LIBS" -dnl -dnl Now check if the installed libOggFLAC is sufficiently new. -dnl - rm -f conf.libOggFLACtest - AC_TRY_RUN([ -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <OggFLAC/stream_decoder.h> - -int main () -{ - system("touch conf.libOggFLACtest"); - return 0; -} - -],, no_libOggFLAC=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"]) - CFLAGS="$ac_save_CFLAGS" - LIBS="$ac_save_LIBS" - fi - - if test "x$no_libOggFLAC" = "x" ; then - AC_MSG_RESULT(yes) - ifelse([$1], , :, [$1]) - else - AC_MSG_RESULT(no) - if test -f conf.libOggFLACtest ; then - : - else - echo "*** Could not run libOggFLAC test program, checking why..." - CFLAGS="$CFLAGS $LIBOGGFLAC_CFLAGS" - LIBS="$LIBS $LIBOGGFLAC_LIBS" - AC_TRY_LINK([ -#include <stdio.h> -#include <OggFLAC/stream_decoder.h> -], [ return 0; ], - [ echo "*** The test program compiled, but did not run. This usually means" - echo "*** that the run-time linker is not finding libOggFLAC or finding the wrong" - echo "*** version of libOggFLAC. If it is not finding libOggFLAC, you'll need to set your" - echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point" - echo "*** to the installed location Also, make sure you have run ldconfig if that" - echo "*** is required on your system" - echo "***" - echo "*** If you have an old version installed, it is best to remove it, although" - echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"], - [ echo "*** The test program failed to compile or link. See the file config.log for the" - echo "*** exact error that occured. This usually means libOggFLAC was incorrectly installed" - echo "*** or that you have moved libOggFLAC since it was installed. In the latter case, you" - echo "*** may want to edit the libOggFLAC-config script: $LIBOGGFLAC_CONFIG" ]) - CFLAGS="$ac_save_CFLAGS" - LIBS="$ac_save_LIBS" - fi - LIBOGGFLAC_CFLAGS="" - LIBOGGFLAC_LIBS="" - ifelse([$2], , :, [$2]) - fi - AC_SUBST(LIBOGGFLAC_CFLAGS) - AC_SUBST(LIBOGGFLAC_LIBS) - rm -f conf.libOggFLACtest -]) diff --git a/m4/libwrap.m4 b/m4/libwrap.m4 new file mode 100644 index 000000000..5ad3df19b --- /dev/null +++ b/m4/libwrap.m4 @@ -0,0 +1,14 @@ +dnl +dnl Usage: +dnl AC_CHECK_LIBWRAP([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +dnl + +AC_DEFUN([AC_CHECK_LIBWRAP],[ + AC_CHECK_HEADERS([tcpd.h], + AC_CHECK_LIB([wrap], [request_init], + [LIBWRAP_CFLAGS="" + LIBWRAP_LDFLAGS="-lwrap" + $1], + $2), + $2) +]) diff --git a/m4/pretty_print.m4 b/m4/pretty_print.m4 new file mode 100644 index 000000000..a22357a8d --- /dev/null +++ b/m4/pretty_print.m4 @@ -0,0 +1,19 @@ +AC_DEFUN([results], [ + dnl This is a hack to allow "with" names, otherwise "enable". + num=`expr match $1 'with'` + if test "$num" != "0"; then + var="`echo '$'$1`" + else + var="`echo '$'enable_$1`" + fi + + echo -n '(' + if eval "test x$var = xyes"; then + echo -n '+' + elif test -n "$3" && eval "test x$var = x$3"; then + echo -n '+' + else + echo -n '-' + fi + echo -n "$2) " +]) diff --git a/scripts/check_config_h.rb b/scripts/check_config_h.rb new file mode 100755 index 000000000..2619984e4 --- /dev/null +++ b/scripts/check_config_h.rb @@ -0,0 +1,47 @@ +#!/usr/bin/env ruby +# +# This script verifies that every source includes config.h first. +# This is very important for consistent Large File Support. +# + +def check_file(file) + first = true + file.each_line do |line| + if line =~ /^\#include\s+(\S+)/ then + if $1 == '"config.h"' + unless first + puts "#{file.path}: config.h included too late" + end + else + if first + puts "#{file.path}: config.h missing" + end + end + first = false + end + end +end + +def check_path(path) + File.open(path) do |file| + check_file(file) + end +end + +if ARGV.empty? + Dir["src/*.c"].each do |path| + check_path(path) + end + + Dir["src/*/*.c"].each do |path| + check_path(path) + end + + Dir["test/*.c"].each do |path| + check_path(path) + end +else + ARGV.each do |path| + check_path(path) + end +end diff --git a/scripts/test.sh b/scripts/test.sh index 170e247fa..739a8a6e7 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -18,6 +18,7 @@ test -x configure || NOCONFIGURE=1 ./autogen.sh ./configure --prefix=$PREFIX/full \ --disable-dependency-tracking --enable-debug --enable-werror \ --enable-un \ + --enable-modplug \ --enable-ao --enable-mikmod --enable-mvp $MAKE install $MAKE distclean @@ -47,6 +48,7 @@ $MAKE install $MAKE distclean # shout: ogg without mp3 +# sndfile instead of modplug ./configure --prefix=$PREFIX/shout_ogg \ --disable-dependency-tracking --disable-debug --enable-werror \ --disable-tcp \ @@ -56,6 +58,7 @@ $MAKE distclean --enable-shout-ogg --disable-shout-mp3 --disable-lame-encoder \ --disable-ffmpeg --disable-wavpack --disable-mpc --disable-aac \ --disable-flac --enable-vorbis --disable-oggflac --disable-audiofile \ + --disable-modplug --enable-sndfile \ --with-zeroconf=no $MAKE install $MAKE distclean diff --git a/src/AudioCompress/compress.c b/src/AudioCompress/compress.c new file mode 100644 index 000000000..d5c08372c --- /dev/null +++ b/src/AudioCompress/compress.c @@ -0,0 +1,185 @@ +/* compress.c + * Compressor logic + * + * (c)2007 busybee (http://beesbuzz.biz/ + * Licensed under the terms of the LGPL. See the file COPYING for details. + */ + +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "config.h" +#include "compress.h" + +struct Compressor { + //! The compressor's preferences + struct CompressorConfig prefs; + + //! History of the peak values + int *peaks; + + //! History of the gain values + int *gain; + + //! History of clip amounts + int *clipped; + + unsigned int pos; + unsigned int bufsz; +}; + +struct Compressor *Compressor_new(unsigned int history) +{ + struct Compressor *obj = malloc(sizeof(struct Compressor)); + + obj->prefs.target = TARGET; + obj->prefs.maxgain = GAINMAX; + obj->prefs.smooth = GAINSMOOTH; + + obj->peaks = obj->gain = obj->clipped = NULL; + obj->bufsz = 0; + obj->pos = 0; + + Compressor_setHistory(obj, history); + + return obj; +} + +void Compressor_delete(struct Compressor *obj) +{ + if (obj->peaks) + free(obj->peaks); + if (obj->gain) + free(obj->gain); + if (obj->clipped) + free(obj->clipped); + free(obj); +} + +static int *resizeArray(int *data, int newsz, int oldsz) +{ + data = realloc(data, newsz*sizeof(int)); + if (newsz > oldsz) + memset(data + oldsz, 0, sizeof(int)*(newsz - oldsz)); + return data; +} + +void Compressor_setHistory(struct Compressor *obj, unsigned int history) +{ + if (!history) + history = BUCKETS; + + obj->peaks = resizeArray(obj->peaks, history, obj->bufsz); + obj->gain = resizeArray(obj->gain, history, obj->bufsz); + obj->clipped = resizeArray(obj->clipped, history, obj->bufsz); + obj->bufsz = history; +} + +struct CompressorConfig *Compressor_getConfig(struct Compressor *obj) +{ + return &obj->prefs; +} + +void Compressor_Process_int16(struct Compressor *obj, int16_t *audio, + unsigned int count) +{ + struct CompressorConfig *prefs = Compressor_getConfig(obj); + int16_t *ap; + unsigned int i; + int *peaks = obj->peaks; + int curGain = obj->gain[obj->pos]; + int newGain; + int peakVal = 1; + int peakPos = 0; + int slot = (obj->pos + 1) % obj->bufsz; + int *clipped = obj->clipped + slot; + unsigned int ramp = count; + int delta; + + ap = audio; + for (i = 0; i < count; i++) + { + int val = *ap++; + if (val < 0) + val = -val; + if (val > peakVal) + { + peakVal = val; + peakPos = i; + } + } + peaks[slot] = peakVal; + + + for (i = 0; i < obj->bufsz; i++) + { + if (peaks[i] > peakVal) + { + peakVal = peaks[i]; + peakPos = 0; + } + } + + //! Determine target gain + newGain = (1 << 10)*prefs->target/peakVal; + + //! Adjust the gain with inertia from the previous gain value + newGain = (curGain*((1 << prefs->smooth) - 1) + newGain) + >> prefs->smooth; + + //! Make sure it's no more than the maximum gain value + if (newGain > (prefs->maxgain << 10)) + newGain = prefs->maxgain << 10; + + //! Make sure it's no less than 1:1 + if (newGain < (1 << 10)) + newGain = 1 << 10; + + //! Make sure the adjusted gain won't cause clipping + if ((peakVal*newGain >> 10) > 32767) + { + newGain = (32767 << 10)/peakVal; + //! Truncate the ramp time + ramp = peakPos; + } + + //! Record the new gain + obj->gain[slot] = newGain; + + if (!ramp) + ramp = 1; + if (!curGain) + curGain = 1 << 10; + delta = (newGain - curGain) / (int)ramp; + + ap = audio; + *clipped = 0; + for (i = 0; i < count; i++) + { + int sample; + + //! Amplify the sample + sample = *ap*curGain >> 10; + if (sample < -32768) + { + *clipped += -32768 - sample; + sample = -32768; + } else if (sample > 32767) + { + *clipped += sample - 32767; + sample = 32767; + } + *ap++ = sample; + + //! Adjust the gain + if (i < ramp) + curGain += delta; + else + curGain = newGain; + } + + obj->pos = slot; +} + diff --git a/src/AudioCompress/compress.h b/src/AudioCompress/compress.h new file mode 100644 index 000000000..073d4af9a --- /dev/null +++ b/src/AudioCompress/compress.h @@ -0,0 +1,40 @@ +/*! compress.h + * interface to audio compression + * + * (c)2007 busybee (http://beesbuzz.biz/) + * Licensed under the terms of the LGPL. See the file COPYING for details. + */ + +#ifndef COMPRESS_H +#define COMPRESS_H + +#include <stdint.h> + +//! Configuration values for the compressor object +struct CompressorConfig { + int target; + int maxgain; + int smooth; +}; + +struct Compressor; + +//! Create a new compressor (use history value of 0 for default) +struct Compressor *Compressor_new(unsigned int history); + +//! Delete a compressor +void Compressor_delete(struct Compressor *); + +//! Set the history length +void Compressor_setHistory(struct Compressor *, unsigned int history); + +//! Get the configuration for a compressor +struct CompressorConfig *Compressor_getConfig(struct Compressor *); + +//! Process 16-bit signed data +void Compressor_Process_int16(struct Compressor *, int16_t *data, unsigned int count); + +//! TODO: Compressor_Process_int32, Compressor_Process_float, others as needed + +//! TODO: functions for getting at the peak/gain/clip history buffers (for monitoring) +#endif diff --git a/src/AudioCompress/config.h b/src/AudioCompress/config.h new file mode 100644 index 000000000..25615ee68 --- /dev/null +++ b/src/AudioCompress/config.h @@ -0,0 +1,19 @@ +/* config.h +** Default values for the configuration, and also a few random debug things +*/ + +#ifndef AC_CONFIG_H +#define AC_CONFIG_H + +/*** Version information ***/ +#define ACVERSION "2.0" + +/*** Default configuration stuff ***/ +#define TARGET 16384 /*!< Target level (on a scale of 0-32767) */ + +#define GAINMAX 32 /*!< The maximum amount to amplify by */ +#define GAINSMOOTH 8 /*!< How much inertia ramping has*/ +#define BUCKETS 400 /*!< How long of a history to use by default */ + +#endif + @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/aiff.c b/src/aiff.c index d4bec628b..e2ca0dfe4 100644 --- a/src/aiff.c +++ b/src/aiff.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" /* must be first for large file support */ #include "aiff.h" #include <glib.h> diff --git a/src/aiff.h b/src/aiff.h index 3388a52fe..52c0a73ec 100644 --- a/src/aiff.h +++ b/src/aiff.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/archive/bz2_archive_plugin.c b/src/archive/bz2_archive_plugin.c new file mode 100644 index 000000000..2414eb519 --- /dev/null +++ b/src/archive/bz2_archive_plugin.c @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2003-2010 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. + */ + +/** + * single bz2 archive handling (requires libbz2) + */ + +#include "config.h" +#include "archive/bz2_archive_plugin.h" +#include "archive_api.h" +#include "input_plugin.h" +#include "refcount.h" + +#include <stdint.h> +#include <stddef.h> +#include <string.h> +#include <glib.h> +#include <bzlib.h> + +#ifdef HAVE_OLDER_BZIP2 +#define BZ2_bzDecompressInit bzDecompressInit +#define BZ2_bzDecompress bzDecompress +#endif + +struct bz2_archive_file { + struct archive_file base; + + struct refcount ref; + + char *name; + bool reset; + struct input_stream *istream; +}; + +struct bz2_input_stream { + struct input_stream base; + + struct bz2_archive_file *archive; + + bool eof; + + bz_stream bzstream; + + char buffer[5000]; +}; + +static const struct input_plugin bz2_inputplugin; + +static inline GQuark +bz2_quark(void) +{ + return g_quark_from_static_string("bz2"); +} + +/* single archive handling allocation helpers */ + +static bool +bz2_alloc(struct bz2_input_stream *data, GError **error_r) +{ + int ret; + + data->bzstream.bzalloc = NULL; + data->bzstream.bzfree = NULL; + data->bzstream.opaque = NULL; + + data->bzstream.next_in = (void *) data->buffer; + data->bzstream.avail_in = 0; + + ret = BZ2_bzDecompressInit(&data->bzstream, 0, 0); + if (ret != BZ_OK) { + g_free(data); + + g_set_error(error_r, bz2_quark(), ret, + "BZ2_bzDecompressInit() has failed"); + return false; + } + + return true; +} + +static void +bz2_destroy(struct bz2_input_stream *data) +{ + BZ2_bzDecompressEnd(&data->bzstream); +} + +/* archive open && listing routine */ + +static struct archive_file * +bz2_open(const char *pathname, GError **error_r) +{ + struct bz2_archive_file *context; + int len; + + context = g_malloc(sizeof(*context)); + archive_file_init(&context->base, &bz2_archive_plugin); + refcount_init(&context->ref); + + //open archive + context->istream = input_stream_open(pathname, error_r); + if (context->istream == NULL) { + g_free(context); + return NULL; + } + + context->name = g_path_get_basename(pathname); + + //remove suffix + len = strlen(context->name); + if (len > 4) { + context->name[len - 4] = 0; //remove .bz2 suffix + } + + return &context->base; +} + +static void +bz2_scan_reset(struct archive_file *file) +{ + struct bz2_archive_file *context = (struct bz2_archive_file *) file; + context->reset = true; +} + +static char * +bz2_scan_next(struct archive_file *file) +{ + struct bz2_archive_file *context = (struct bz2_archive_file *) file; + char *name = NULL; + + if (context->reset) { + name = context->name; + context->reset = false; + } + + return name; +} + +static void +bz2_close(struct archive_file *file) +{ + struct bz2_archive_file *context = (struct bz2_archive_file *) file; + + if (!refcount_dec(&context->ref)) + return; + + g_free(context->name); + + input_stream_close(context->istream); + g_free(context); +} + +/* single archive handling */ + +static struct input_stream * +bz2_open_stream(struct archive_file *file, const char *path, GError **error_r) +{ + struct bz2_archive_file *context = (struct bz2_archive_file *) file; + struct bz2_input_stream *bis = g_new(struct bz2_input_stream, 1); + + input_stream_init(&bis->base, &bz2_inputplugin, path); + + bis->archive = context; + + bis->base.ready = true; + bis->base.seekable = false; + + if (!bz2_alloc(bis, error_r)) { + input_stream_deinit(&bis->base); + g_free(bis); + return NULL; + } + + bis->eof = false; + + refcount_inc(&context->ref); + + return &bis->base; +} + +static void +bz2_is_close(struct input_stream *is) +{ + struct bz2_input_stream *bis = (struct bz2_input_stream *)is; + + bz2_destroy(bis); + + bz2_close(&bis->archive->base); + + input_stream_deinit(&bis->base); + g_free(bis); +} + +static bool +bz2_fillbuffer(struct bz2_input_stream *bis, GError **error_r) +{ + size_t count; + bz_stream *bzstream; + + bzstream = &bis->bzstream; + + if (bzstream->avail_in > 0) + return true; + + count = input_stream_read(bis->archive->istream, + bis->buffer, sizeof(bis->buffer), + error_r); + if (count == 0) + return false; + + bzstream->next_in = bis->buffer; + bzstream->avail_in = count; + return true; +} + +static size_t +bz2_is_read(struct input_stream *is, void *ptr, size_t length, + GError **error_r) +{ + struct bz2_input_stream *bis = (struct bz2_input_stream *)is; + bz_stream *bzstream; + int bz_result; + size_t nbytes = 0; + + if (bis->eof) + return 0; + + bzstream = &bis->bzstream; + bzstream->next_out = ptr; + bzstream->avail_out = length; + + do { + if (!bz2_fillbuffer(bis, error_r)) + return 0; + + bz_result = BZ2_bzDecompress(bzstream); + + if (bz_result == BZ_STREAM_END) { + bis->eof = true; + break; + } + + if (bz_result != BZ_OK) { + g_set_error(error_r, bz2_quark(), bz_result, + "BZ2_bzDecompress() has failed"); + return 0; + } + } while (bzstream->avail_out == length); + + nbytes = length - bzstream->avail_out; + is->offset += nbytes; + + return nbytes; +} + +static bool +bz2_is_eof(struct input_stream *is) +{ + struct bz2_input_stream *bis = (struct bz2_input_stream *)is; + + return bis->eof; +} + +/* exported structures */ + +static const char *const bz2_extensions[] = { + "bz2", + NULL +}; + +static const struct input_plugin bz2_inputplugin = { + .close = bz2_is_close, + .read = bz2_is_read, + .eof = bz2_is_eof, +}; + +const struct archive_plugin bz2_archive_plugin = { + .name = "bz2", + .open = bz2_open, + .scan_reset = bz2_scan_reset, + .scan_next = bz2_scan_next, + .open_stream = bz2_open_stream, + .close = bz2_close, + .suffixes = bz2_extensions +}; + diff --git a/src/input/lastfm_input_plugin.h b/src/archive/bz2_archive_plugin.h index d0eaf5a55..199049008 100644 --- a/src/input/lastfm_input_plugin.h +++ b/src/archive/bz2_archive_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,9 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef LASTFM_INPUT_PLUGIN_H -#define LASTFM_INPUT_PLUGIN_H +#ifndef MPD_ARCHIVE_BZ2_H +#define MPD_ARCHIVE_BZ2_H -extern const struct input_plugin lastfm_input_plugin; +extern const struct archive_plugin bz2_archive_plugin; #endif diff --git a/src/archive/bz2_plugin.c b/src/archive/bz2_plugin.c deleted file mode 100644 index 0ef042e90..000000000 --- a/src/archive/bz2_plugin.c +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (C) 2003-2009 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** - * single bz2 archive handling (requires libbz2) - */ - -#include "archive_api.h" -#include "input_plugin.h" -#include "config.h" - -#include <stdint.h> -#include <stddef.h> -#include <string.h> -#include <glib.h> -#include <bzlib.h> - -#ifdef HAVE_OLDER_BZIP2 -#define BZ2_bzDecompressInit bzDecompressInit -#define BZ2_bzDecompress bzDecompress -#endif - -#define BZ_BUFSIZE 5000 - -typedef struct { - char *name; - bool reset; - struct input_stream istream; - int last_bz_result; - int last_parent_result; - bz_stream bzstream; - char *buffer; -} bz2_context; - - -static const struct input_plugin bz2_inputplugin; - -/* single archive handling allocation helpers */ - -static bool -bz2_alloc(bz2_context *data) -{ - data->bzstream.bzalloc = NULL; - data->bzstream.bzfree = NULL; - data->bzstream.opaque = NULL; - - data->buffer = g_malloc(BZ_BUFSIZE); - data->bzstream.next_in = (void *) data->buffer; - data->bzstream.avail_in = 0; - - if (BZ2_bzDecompressInit(&data->bzstream, 0, 0) != BZ_OK) { - g_free(data->buffer); - g_free(data); - return false; - } - - data->last_bz_result = BZ_OK; - data->last_parent_result = 0; - return true; -} - -static void -bz2_destroy(bz2_context *data) -{ - BZ2_bzDecompressEnd(&data->bzstream); - g_free(data->buffer); -} - -/* archive open && listing routine */ - -static struct archive_file * -bz2_open(char * pathname) -{ - bz2_context *context; - char *name; - int len; - - context = g_malloc(sizeof(bz2_context)); - if (!context) { - return NULL; - } - //open archive - if (!input_stream_open(&context->istream, pathname)) { - g_warning("failed to open an bzip2 archive %s\n",pathname); - g_free(context); - return NULL; - } - //capture filename - name = strrchr(pathname, '/'); - if (name == NULL) { - g_warning("failed to get bzip2 name from %s\n",pathname); - g_free(context); - return NULL; - } - context->name = g_strdup(name+1); - //remove suffix - len = strlen(context->name); - if (len > 4) { - context->name[len-4] = 0; //remove .bz2 suffix - } - return (struct archive_file *) context; -} - -static void -bz2_scan_reset(struct archive_file *file) -{ - bz2_context *context = (bz2_context *) file; - context->reset = true; -} - -static char * -bz2_scan_next(struct archive_file *file) -{ - bz2_context *context = (bz2_context *) file; - char *name = NULL; - if (context->reset) { - name = context->name; - context->reset = false; - } - return name; -} - -static void -bz2_close(struct archive_file *file) -{ - bz2_context *context = (bz2_context *) file; - - g_free(context->name); - - input_stream_close(&context->istream); - g_free(context); -} - -/* single archive handling */ - -static bool -bz2_open_stream(struct archive_file *file, struct input_stream *is, - G_GNUC_UNUSED const char *path) -{ - bz2_context *context = (bz2_context *) file; - //setup file ops - is->plugin = &bz2_inputplugin; - //insert back reference - is->data = context; - is->seekable = false; - - if (!bz2_alloc(context)) { - g_warning("alloc bz2 failed\n"); - return false; - } - return true; -} - -static void -bz2_is_close(struct input_stream *is) -{ - bz2_context *context = (bz2_context *) is->data; - bz2_destroy(context); - is->data = NULL; - - bz2_close((struct archive_file *)context); -} - -static int -bz2_fillbuffer(bz2_context *context, - size_t numBytes) -{ - size_t count; - bz_stream *bzstream; - - bzstream = &context->bzstream; - - if (bzstream->avail_in > 0) - return 0; - - count = input_stream_read(&context->istream, - context->buffer, BZ_BUFSIZE); - - if (count == 0) { - if (bzstream->avail_out == numBytes) - return -1; - if (!input_stream_eof(&context->istream)) - context->last_parent_result = 1; - } else { - bzstream->next_in = context->buffer; - bzstream->avail_in = count; - } - - return 0; -} - -static size_t -bz2_is_read(struct input_stream *is, void *ptr, size_t size) -{ - bz2_context *context = (bz2_context *) is->data; - bz_stream *bzstream; - int bz_result; - size_t numBytes = size; - size_t bytesRead = 0; - - if (context->last_bz_result != BZ_OK) - return 0; - if (context->last_parent_result != 0) - return 0; - - bzstream = &context->bzstream; - bzstream->next_out = ptr; - bzstream->avail_out = numBytes; - - while (bzstream->avail_out != 0) { - if (bz2_fillbuffer(context, numBytes) != 0) - break; - - bz_result = BZ2_bzDecompress(bzstream); - - if (context->last_bz_result != BZ_OK - && bzstream->avail_out == numBytes) { - context->last_bz_result = bz_result; - break; - } - - if (bz_result == BZ_STREAM_END) { - context->last_bz_result = bz_result; - break; - } - } - - bytesRead = numBytes - bzstream->avail_out; - is->offset += bytesRead; - - return bytesRead; -} - -static bool -bz2_is_eof(struct input_stream *is) -{ - bz2_context *context = (bz2_context *) is->data; - - if (context->last_bz_result == BZ_STREAM_END) { - return true; - } - - return false; -} - -/* exported structures */ - -static const char *const bz2_extensions[] = { - "bz2", - NULL -}; - -static const struct input_plugin bz2_inputplugin = { - .close = bz2_is_close, - .read = bz2_is_read, - .eof = bz2_is_eof, -}; - -const struct archive_plugin bz2_plugin = { - .name = "bz2", - .open = bz2_open, - .scan_reset = bz2_scan_reset, - .scan_next = bz2_scan_next, - .open_stream = bz2_open_stream, - .close = bz2_close, - .suffixes = bz2_extensions -}; - diff --git a/src/archive/iso9660_archive_plugin.c b/src/archive/iso9660_archive_plugin.c new file mode 100644 index 000000000..142fa10e0 --- /dev/null +++ b/src/archive/iso9660_archive_plugin.c @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2003-2010 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. + */ + +/** + * iso archive handling (requires cdio, and iso9660) + */ + +#include "config.h" +#include "archive/iso9660_archive_plugin.h" +#include "archive_api.h" +#include "input_plugin.h" +#include "refcount.h" + +#include <cdio/cdio.h> +#include <cdio/iso9660.h> + +#include <glib.h> +#include <string.h> + +#define CEILING(x, y) ((x+(y-1))/y) + +struct iso9660_archive_file { + struct archive_file base; + + struct refcount ref; + + iso9660_t *iso; + GSList *list; + GSList *iter; +}; + +static const struct input_plugin iso9660_input_plugin; + +static inline GQuark +iso9660_quark(void) +{ + return g_quark_from_static_string("iso9660"); +} + +/* archive open && listing routine */ + +static void +listdir_recur(const char *psz_path, struct iso9660_archive_file *context) +{ + iso9660_t *iso = context->iso; + CdioList_t *entlist; + CdioListNode_t *entnode; + iso9660_stat_t *statbuf; + char pathname[4096]; + + entlist = iso9660_ifs_readdir (iso, psz_path); + if (!entlist) { + return; + } + /* Iterate over the list of nodes that iso9660_ifs_readdir gives */ + _CDIO_LIST_FOREACH (entnode, entlist) { + statbuf = (iso9660_stat_t *) _cdio_list_node_data (entnode); + + strcpy(pathname, psz_path); + strcat(pathname, statbuf->filename); + + if (_STAT_DIR == statbuf->type ) { + if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) { + strcat(pathname, "/"); + listdir_recur(pathname, context); + } + } else { + //remove leading / + context->list = g_slist_prepend( context->list, + g_strdup(pathname + 1)); + } + } + _cdio_list_free (entlist, true); +} + +static struct archive_file * +iso9660_archive_open(const char *pathname, GError **error_r) +{ + struct iso9660_archive_file *context = + g_new(struct iso9660_archive_file, 1); + + archive_file_init(&context->base, &iso9660_archive_plugin); + refcount_init(&context->ref); + + context->list = NULL; + + /* open archive */ + context->iso = iso9660_open (pathname); + if (context->iso == NULL) { + g_set_error(error_r, iso9660_quark(), 0, + "Failed to open ISO9660 file %s", pathname); + return NULL; + } + + listdir_recur("/", context); + + return &context->base; +} + +static void +iso9660_archive_scan_reset(struct archive_file *file) +{ + struct iso9660_archive_file *context = + (struct iso9660_archive_file *)file; + + //reset iterator + context->iter = context->list; +} + +static char * +iso9660_archive_scan_next(struct archive_file *file) +{ + struct iso9660_archive_file *context = + (struct iso9660_archive_file *)file; + + char *data = NULL; + if (context->iter != NULL) { + ///fetch data and goto next + data = context->iter->data; + context->iter = g_slist_next(context->iter); + } + return data; +} + +static void +iso9660_archive_close(struct archive_file *file) +{ + struct iso9660_archive_file *context = + (struct iso9660_archive_file *)file; + GSList *tmp; + + if (!refcount_dec(&context->ref)) + return; + + if (context->list) { + //free list + for (tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp)) + g_free(tmp->data); + g_slist_free(context->list); + } + //close archive + iso9660_close(context->iso); + + g_free(context); +} + +/* single archive handling */ + +struct iso9660_input_stream { + struct input_stream base; + + struct iso9660_archive_file *archive; + + iso9660_stat_t *statbuf; + size_t max_blocks; +}; + +static struct input_stream * +iso9660_archive_open_stream(struct archive_file *file, + const char *pathname, GError **error_r) +{ + struct iso9660_archive_file *context = + (struct iso9660_archive_file *)file; + struct iso9660_input_stream *iis; + + iis = g_new(struct iso9660_input_stream, 1); + input_stream_init(&iis->base, &iso9660_input_plugin, pathname); + + iis->archive = context; + iis->statbuf = iso9660_ifs_stat_translate(context->iso, pathname); + if (iis->statbuf == NULL) { + g_free(iis); + g_set_error(error_r, iso9660_quark(), 0, + "not found in the ISO file: %s", pathname); + return NULL; + } + + iis->base.ready = true; + //we are not seekable + iis->base.seekable = false; + + iis->base.size = iis->statbuf->size; + + iis->max_blocks = CEILING(iis->statbuf->size, ISO_BLOCKSIZE); + + refcount_inc(&context->ref); + + return &iis->base; +} + +static void +iso9660_input_close(struct input_stream *is) +{ + struct iso9660_input_stream *iis = (struct iso9660_input_stream *)is; + + g_free(iis->statbuf); + + iso9660_archive_close(&iis->archive->base); + + input_stream_deinit(&iis->base); + g_free(iis); +} + + +static size_t +iso9660_input_read(struct input_stream *is, void *ptr, size_t size, GError **error_r) +{ + struct iso9660_input_stream *iis = (struct iso9660_input_stream *)is; + int toread, readed = 0; + int no_blocks, cur_block; + size_t left_bytes = iis->statbuf->size - is->offset; + + size = (size * ISO_BLOCKSIZE) / ISO_BLOCKSIZE; + + if (left_bytes < size) { + toread = left_bytes; + no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE); + } else { + toread = size; + no_blocks = toread / ISO_BLOCKSIZE; + } + if (no_blocks > 0) { + + cur_block = is->offset / ISO_BLOCKSIZE; + + readed = iso9660_iso_seek_read (iis->archive->iso, ptr, + iis->statbuf->lsn + cur_block, no_blocks); + + if (readed != no_blocks * ISO_BLOCKSIZE) { + g_set_error(error_r, iso9660_quark(), 0, + "error reading ISO file at lsn %lu", + (long unsigned int) cur_block); + return 0; + } + if (left_bytes < size) { + readed = left_bytes; + } + + is->offset += readed; + } + return readed; +} + +static bool +iso9660_input_eof(struct input_stream *is) +{ + return is->offset == is->size; +} + +/* exported structures */ + +static const char *const iso9660_archive_extensions[] = { + "iso", + NULL +}; + +static const struct input_plugin iso9660_input_plugin = { + .close = iso9660_input_close, + .read = iso9660_input_read, + .eof = iso9660_input_eof, +}; + +const struct archive_plugin iso9660_archive_plugin = { + .name = "iso", + .open = iso9660_archive_open, + .scan_reset = iso9660_archive_scan_reset, + .scan_next = iso9660_archive_scan_next, + .open_stream = iso9660_archive_open_stream, + .close = iso9660_archive_close, + .suffixes = iso9660_archive_extensions +}; diff --git a/src/archive/iso9660_archive_plugin.h b/src/archive/iso9660_archive_plugin.h new file mode 100644 index 000000000..2a3864cee --- /dev/null +++ b/src/archive/iso9660_archive_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ARCHIVE_ISO9660_H +#define MPD_ARCHIVE_ISO9660_H + +extern const struct archive_plugin iso9660_archive_plugin; + +#endif diff --git a/src/archive/iso_plugin.c b/src/archive/iso_plugin.c deleted file mode 100644 index 9063af0fc..000000000 --- a/src/archive/iso_plugin.c +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (C) 2003-2009 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** - * iso archive handling (requires cdio, and iso9660) - */ - -#include "archive_api.h" -#include "input_plugin.h" - -#include <cdio/cdio.h> -#include <cdio/iso9660.h> - -#include <glib.h> -#include <string.h> - -#define CEILING(x, y) ((x+(y-1))/y) - -typedef struct { - iso9660_t *iso; - iso9660_stat_t *statbuf; - size_t cur_ofs; - size_t max_blocks; - GSList *list; - GSList *iter; -} iso_context; - -static const struct input_plugin iso_inputplugin; - -/* archive open && listing routine */ - -static void -listdir_recur(const char *psz_path, iso_context *context) -{ - iso9660_t *iso = context->iso; - CdioList_t *entlist; - CdioListNode_t *entnode; - iso9660_stat_t *statbuf; - char pathname[4096]; - - entlist = iso9660_ifs_readdir (iso, psz_path); - if (!entlist) { - return; - } - /* Iterate over the list of nodes that iso9660_ifs_readdir gives */ - _CDIO_LIST_FOREACH (entnode, entlist) { - statbuf = (iso9660_stat_t *) _cdio_list_node_data (entnode); - - strcpy(pathname, psz_path); - strcat(pathname, statbuf->filename); - - if (_STAT_DIR == statbuf->type ) { - if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) { - strcat(pathname, "/"); - listdir_recur(pathname, context); - } - } else { - //remove leading / - context->list = g_slist_prepend( context->list, - g_strdup(pathname + 1)); - } - } - _cdio_list_free (entlist, true); -} - -static struct archive_file * -iso_open(char * pathname) -{ - iso_context *context = g_malloc(sizeof(iso_context)); - - context->list = NULL; - - /* open archive */ - context->iso = iso9660_open (pathname); - if (context->iso == NULL) { - g_warning("iso %s open failed\n", pathname); - return NULL; - } - - listdir_recur("/", context); - - return (struct archive_file *)context; -} - -static void -iso_scan_reset(struct archive_file *file) -{ - iso_context *context = (iso_context *) file; - //reset iterator - context->iter = context->list; -} - -static char * -iso_scan_next(struct archive_file *file) -{ - iso_context *context = (iso_context *) file; - char *data = NULL; - if (context->iter != NULL) { - ///fetch data and goto next - data = context->iter->data; - context->iter = g_slist_next(context->iter); - } - return data; -} - -static void -iso_close(struct archive_file *file) -{ - iso_context *context = (iso_context *) file; - GSList *tmp; - if (context->list) { - //free list - for (tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp)) - g_free(tmp->data); - g_slist_free(context->list); - } - //close archive - iso9660_close(context->iso); - - g_free(context); -} - -/* single archive handling */ - -static bool -iso_open_stream(struct archive_file *file, struct input_stream *is, - const char *pathname) -{ - iso_context *context = (iso_context *) file; - //setup file ops - is->plugin = &iso_inputplugin; - //insert back reference - is->data = context; - //we are not seekable - is->seekable = false; - - context->statbuf = iso9660_ifs_stat_translate (context->iso, pathname); - - if (context->statbuf == NULL) { - g_warning("file %s not found in iso\n", pathname); - return false; - } - context->cur_ofs = 0; - context->max_blocks = CEILING(context->statbuf->size, ISO_BLOCKSIZE); - return true; -} - -static void -iso_is_close(struct input_stream *is) -{ - iso_context *context = (iso_context *) is->data; - g_free(context->statbuf); - - iso_close((struct archive_file *)context); -} - - -static size_t -iso_is_read(struct input_stream *is, void *ptr, size_t size) -{ - iso_context *context = (iso_context *) is->data; - int toread, readed = 0; - int no_blocks, cur_block; - size_t left_bytes = context->statbuf->size - context->cur_ofs; - - size = (size * ISO_BLOCKSIZE) / ISO_BLOCKSIZE; - - if (left_bytes < size) { - toread = left_bytes; - no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE); - } else { - toread = size; - no_blocks = toread / ISO_BLOCKSIZE; - } - if (no_blocks > 0) { - - cur_block = context->cur_ofs / ISO_BLOCKSIZE; - - readed = iso9660_iso_seek_read (context->iso, ptr, - context->statbuf->lsn + cur_block, no_blocks); - - if (readed != no_blocks * ISO_BLOCKSIZE) { - g_warning("error reading ISO file at lsn %lu\n", - (long unsigned int) cur_block ); - return -1; - } - if (left_bytes < size) { - readed = left_bytes; - } - context->cur_ofs += readed; - } - return readed; -} - -static bool -iso_is_eof(struct input_stream *is) -{ - iso_context *context = (iso_context *) is->data; - return (context->cur_ofs == context->statbuf->size); -} - -/* exported structures */ - -static const char *const iso_extensions[] = { - "iso", - NULL -}; - -static const struct input_plugin iso_inputplugin = { - .close = iso_is_close, - .read = iso_is_read, - .eof = iso_is_eof, -}; - -const struct archive_plugin iso_plugin = { - .name = "iso", - .open = iso_open, - .scan_reset = iso_scan_reset, - .scan_next = iso_scan_next, - .open_stream = iso_open_stream, - .close = iso_close, - .suffixes = iso_extensions -}; diff --git a/src/archive/zip_plugin.c b/src/archive/zip_plugin.c deleted file mode 100644 index 243d46418..000000000 --- a/src/archive/zip_plugin.c +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (C) 2003-2009 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** - * zip archive handling (requires zziplib) - */ - -#include "archive_api.h" -#include "archive_api.h" -#include "input_plugin.h" - -#include <zzip/zzip.h> -#include <glib.h> -#include <string.h> - -typedef struct { - ZZIP_DIR *dir; - ZZIP_FILE *file; - size_t length; - GSList *list; - GSList *iter; -} zip_context; - -static const struct input_plugin zip_inputplugin; - -/* archive open && listing routine */ - -static struct archive_file * -zip_open(char * pathname) -{ - zip_context *context = g_malloc(sizeof(zip_context)); - ZZIP_DIRENT dirent; - - // open archive - context->list = NULL; - context->dir = zzip_dir_open(pathname, NULL); - if (context->dir == NULL) { - g_warning("zipfile %s open failed\n", pathname); - return NULL; - } - - while (zzip_dir_read(context->dir, &dirent)) { - //add only files - if (dirent.st_size > 0) { - context->list = g_slist_prepend(context->list, - g_strdup(dirent.d_name)); - } - } - - return (struct archive_file *)context; -} - -static void -zip_scan_reset(struct archive_file *file) -{ - zip_context *context = (zip_context *) file; - //reset iterator - context->iter = context->list; -} - -static char * -zip_scan_next(struct archive_file *file) -{ - zip_context *context = (zip_context *) file; - char *data = NULL; - if (context->iter != NULL) { - ///fetch data and goto next - data = context->iter->data; - context->iter = g_slist_next(context->iter); - } - return data; -} - -static void -zip_close(struct archive_file *file) -{ - zip_context *context = (zip_context *) file; - if (context->list) { - //free list - for (GSList *tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp)) - g_free(tmp->data); - g_slist_free(context->list); - } - //close archive - zzip_dir_close (context->dir); - - g_free(context); -} - -/* single archive handling */ - -static bool -zip_open_stream(struct archive_file *file, struct input_stream *is, - const char *pathname) -{ - zip_context *context = (zip_context *) file; - ZZIP_STAT z_stat; - - //setup file ops - is->plugin = &zip_inputplugin; - //insert back reference - is->data = context; - //we are seekable (but its not recommendent to do so) - is->seekable = true; - - context->file = zzip_file_open(context->dir, pathname, 0); - if (!context->file) { - g_warning("file %s not found in the zipfile\n", pathname); - return false; - } - zzip_file_stat(context->file, &z_stat); - context->length = z_stat.st_size; - return true; -} - -static void -zip_is_close(struct input_stream *is) -{ - zip_context *context = (zip_context *) is->data; - zzip_file_close (context->file); - - zip_close((struct archive_file *)context); -} - -static size_t -zip_is_read(struct input_stream *is, void *ptr, size_t size) -{ - zip_context *context = (zip_context *) is->data; - int ret; - ret = zzip_file_read(context->file, ptr, size); - if (ret < 0) { - g_warning("error %d reading zipfile\n", ret); - return 0; - } - return ret; -} - -static bool -zip_is_eof(struct input_stream *is) -{ - zip_context *context = (zip_context *) is->data; - return ((size_t) zzip_tell(context->file) == context->length); -} - -static bool -zip_is_seek(G_GNUC_UNUSED struct input_stream *is, - G_GNUC_UNUSED off_t offset, G_GNUC_UNUSED int whence) -{ - zip_context *context = (zip_context *) is->data; - zzip_off_t ofs = zzip_seek(context->file, offset, whence); - if (ofs != -1) { - is->offset = ofs; - return true; - } - return false; -} - -/* exported structures */ - -static const char *const zip_extensions[] = { - "zip", - NULL -}; - -static const struct input_plugin zip_inputplugin = { - .close = zip_is_close, - .read = zip_is_read, - .eof = zip_is_eof, - .seek = zip_is_seek, -}; - -const struct archive_plugin zip_plugin = { - .name = "zip", - .open = zip_open, - .scan_reset = zip_scan_reset, - .scan_next = zip_scan_next, - .open_stream = zip_open_stream, - .close = zip_close, - .suffixes = zip_extensions -}; diff --git a/src/archive/zzip_archive_plugin.c b/src/archive/zzip_archive_plugin.c new file mode 100644 index 000000000..3c2b80318 --- /dev/null +++ b/src/archive/zzip_archive_plugin.c @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2003-2010 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. + */ + +/** + * zip archive handling (requires zziplib) + */ + +#include "config.h" +#include "archive/zzip_archive_plugin.h" +#include "archive_api.h" +#include "archive_api.h" +#include "input_plugin.h" +#include "refcount.h" + +#include <zzip/zzip.h> +#include <glib.h> +#include <string.h> + +struct zzip_archive { + struct archive_file base; + + struct refcount ref; + + ZZIP_DIR *dir; + GSList *list; + GSList *iter; +}; + +static const struct input_plugin zzip_input_plugin; + +static inline GQuark +zzip_quark(void) +{ + return g_quark_from_static_string("zzip"); +} + +/* archive open && listing routine */ + +static struct archive_file * +zzip_archive_open(const char *pathname, GError **error_r) +{ + struct zzip_archive *context = g_malloc(sizeof(*context)); + ZZIP_DIRENT dirent; + + archive_file_init(&context->base, &zzip_archive_plugin); + refcount_init(&context->ref); + + // open archive + context->list = NULL; + context->dir = zzip_dir_open(pathname, NULL); + if (context->dir == NULL) { + g_set_error(error_r, zzip_quark(), 0, + "Failed to open ZIP file %s", pathname); + return NULL; + } + + while (zzip_dir_read(context->dir, &dirent)) { + //add only files + if (dirent.st_size > 0) { + context->list = g_slist_prepend(context->list, + g_strdup(dirent.d_name)); + } + } + + return &context->base; +} + +static void +zzip_archive_scan_reset(struct archive_file *file) +{ + struct zzip_archive *context = (struct zzip_archive *) file; + //reset iterator + context->iter = context->list; +} + +static char * +zzip_archive_scan_next(struct archive_file *file) +{ + struct zzip_archive *context = (struct zzip_archive *) file; + char *data = NULL; + if (context->iter != NULL) { + ///fetch data and goto next + data = context->iter->data; + context->iter = g_slist_next(context->iter); + } + return data; +} + +static void +zzip_archive_close(struct archive_file *file) +{ + struct zzip_archive *context = (struct zzip_archive *) file; + + if (!refcount_dec(&context->ref)) + return; + + if (context->list) { + //free list + for (GSList *tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp)) + g_free(tmp->data); + g_slist_free(context->list); + } + //close archive + zzip_dir_close (context->dir); + + g_free(context); +} + +/* single archive handling */ + +struct zzip_input_stream { + struct input_stream base; + + struct zzip_archive *archive; + + ZZIP_FILE *file; +}; + +static struct input_stream * +zzip_archive_open_stream(struct archive_file *file, + const char *pathname, GError **error_r) +{ + struct zzip_archive *context = (struct zzip_archive *) file; + struct zzip_input_stream *zis; + ZZIP_STAT z_stat; + + zis = g_new(struct zzip_input_stream, 1); + input_stream_init(&zis->base, &zzip_input_plugin, pathname); + + zis->archive = context; + zis->file = zzip_file_open(context->dir, pathname, 0); + if (zis->file == NULL) { + g_free(zis); + g_set_error(error_r, zzip_quark(), 0, + "not found in the ZIP file: %s", pathname); + return NULL; + } + + zis->base.ready = true; + //we are seekable (but its not recommendent to do so) + zis->base.seekable = true; + + zzip_file_stat(zis->file, &z_stat); + zis->base.size = z_stat.st_size; + + refcount_inc(&context->ref); + + return &zis->base; +} + +static void +zzip_input_close(struct input_stream *is) +{ + struct zzip_input_stream *zis = (struct zzip_input_stream *)is; + + zzip_file_close(zis->file); + zzip_archive_close(&zis->archive->base); + input_stream_deinit(&zis->base); + g_free(zis); +} + +static size_t +zzip_input_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) +{ + struct zzip_input_stream *zis = (struct zzip_input_stream *)is; + int ret; + + ret = zzip_file_read(zis->file, ptr, size); + if (ret < 0) { + g_set_error(error_r, zzip_quark(), ret, + "zzip_file_read() has failed"); + return 0; + } + + is->offset = zzip_tell(zis->file); + + return ret; +} + +static bool +zzip_input_eof(struct input_stream *is) +{ + struct zzip_input_stream *zis = (struct zzip_input_stream *)is; + + return (goffset)zzip_tell(zis->file) == is->size; +} + +static bool +zzip_input_seek(struct input_stream *is, + goffset offset, int whence, GError **error_r) +{ + struct zzip_input_stream *zis = (struct zzip_input_stream *)is; + zzip_off_t ofs = zzip_seek(zis->file, offset, whence); + if (ofs != -1) { + g_set_error(error_r, zzip_quark(), ofs, + "zzip_seek() has failed"); + is->offset = ofs; + return true; + } + return false; +} + +/* exported structures */ + +static const char *const zzip_archive_extensions[] = { + "zip", + NULL +}; + +static const struct input_plugin zzip_input_plugin = { + .close = zzip_input_close, + .read = zzip_input_read, + .eof = zzip_input_eof, + .seek = zzip_input_seek, +}; + +const struct archive_plugin zzip_archive_plugin = { + .name = "zzip", + .open = zzip_archive_open, + .scan_reset = zzip_archive_scan_reset, + .scan_next = zzip_archive_scan_next, + .open_stream = zzip_archive_open_stream, + .close = zzip_archive_close, + .suffixes = zzip_archive_extensions +}; diff --git a/src/archive/zzip_archive_plugin.h b/src/archive/zzip_archive_plugin.h new file mode 100644 index 000000000..6d5037eef --- /dev/null +++ b/src/archive/zzip_archive_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ARCHIVE_ZZIP_H +#define MPD_ARCHIVE_ZZIP_H + +extern const struct archive_plugin zzip_archive_plugin; + +#endif diff --git a/src/archive_api.c b/src/archive_api.c index 153afa361..b15810f1b 100644 --- a/src/archive_api.c +++ b/src/archive_api.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" /* must be first for large file support */ +#include "archive_api.h" + #include <stdio.h> #include <string.h> @@ -26,8 +29,6 @@ #include <errno.h> #include <glib.h> -#include "archive_api.h" - /** * * archive_lookup is used to determine if part of pathname refers to an regular diff --git a/src/archive_api.h b/src/archive_api.h index 2efcc1e6a..f08960c72 100644 --- a/src/archive_api.h +++ b/src/archive_api.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,72 +27,11 @@ */ #include "archive_internal.h" +#include "archive_plugin.h" #include "input_stream.h" #include <stdbool.h> -struct archive_file; - -struct archive_plugin { - const char *name; - - /** - * optional, set this to NULL if the archive plugin doesn't - * have/need one this must false if there is an error and - * true otherwise - */ - bool (*init)(void); - - /** - * optional, set this to NULL if the archive plugin doesn't - * have/need one - */ - void (*finish)(void); - - /** - * tryes to open archive file and associates handle with archive - * returns pointer to handle used is all operations with this archive - * or NULL when opening fails - */ - struct archive_file *(*open)(char * pathname); - - /** - * reset routine will move current read index in archive to default - * position and then the filenames from archives can be read - * via scan_next routine - */ - void (*scan_reset)(struct archive_file *); - - /** - * the read method will return corresponding files from archive - * (as pathnames) and move read index to next file. When there is no - * next file it return NULL. - */ - char *(*scan_next)(struct archive_file *); - - /** - * Opens an input_stream of a file within the archive. - * - * If this function succeeds, then the #input_stream "owns" - * the archive file and will automatically close it. - * - * @param path the path within the archive - */ - bool (*open_stream)(struct archive_file *, struct input_stream *is, - const char *path); - - /** - * closes archive file. - */ - void (*close)(struct archive_file *); - - /** - * suffixes handled by this plugin. - * last element in these arrays must always be a NULL - */ - const char *const*suffixes; -}; - bool archive_lookup(char *pathname, char **archive, char **inpath, char **suffix); #endif diff --git a/src/archive_internal.h b/src/archive_internal.h index 130d25d65..03439e826 100644 --- a/src/archive_internal.h +++ b/src/archive_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,7 +21,14 @@ #define MPD_ARCHIVE_INTERNAL_H struct archive_file { - int placeholder; + const struct archive_plugin *plugin; }; +static inline void +archive_file_init(struct archive_file *archive_file, + const struct archive_plugin *plugin) +{ + archive_file->plugin = plugin; +} + #endif diff --git a/src/archive_list.c b/src/archive_list.c index 8228fc961..2656726b5 100644 --- a/src/archive_list.c +++ b/src/archive_list.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,50 +17,44 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "archive_list.h" -#include "archive_api.h" +#include "archive_plugin.h" #include "utils.h" -#include "config.h" +#include "archive/bz2_archive_plugin.h" +#include "archive/iso9660_archive_plugin.h" +#include "archive/zzip_archive_plugin.h" #include <string.h> #include <glib.h> -extern const struct archive_plugin bz2_plugin; -extern const struct archive_plugin zip_plugin; -extern const struct archive_plugin iso_plugin; - static const struct archive_plugin *const archive_plugins[] = { #ifdef HAVE_BZ2 - &bz2_plugin, + &bz2_archive_plugin, #endif -#ifdef HAVE_ZIP - &zip_plugin, +#ifdef HAVE_ZZIP + &zzip_archive_plugin, #endif -#ifdef HAVE_ISO - &iso_plugin, +#ifdef HAVE_ISO9660 + &iso9660_archive_plugin, #endif NULL }; -enum { - num_archive_plugins = G_N_ELEMENTS(archive_plugins)-1, -}; - /** which plugins have been initialized successfully? */ -static bool archive_plugins_enabled[num_archive_plugins+1]; +static bool archive_plugins_enabled[G_N_ELEMENTS(archive_plugins) - 1]; const struct archive_plugin * archive_plugin_from_suffix(const char *suffix) { - unsigned i; - if (suffix == NULL) return NULL; - for (i=0; i < num_archive_plugins; ++i) { + for (unsigned i = 0; archive_plugins[i] != NULL; ++i) { const struct archive_plugin *plugin = archive_plugins[i]; if (archive_plugins_enabled[i] && - stringFoundInStringArray(plugin->suffixes, suffix)) { + plugin->suffixes != NULL && + string_array_contains(plugin->suffixes, suffix)) { ++i; return plugin; } @@ -71,7 +65,7 @@ archive_plugin_from_suffix(const char *suffix) const struct archive_plugin * archive_plugin_from_name(const char *name) { - for (unsigned i = 0; i < num_archive_plugins; ++i) { + for (unsigned i = 0; archive_plugins[i] != NULL; ++i) { const struct archive_plugin *plugin = archive_plugins[i]; if (archive_plugins_enabled[i] && strcmp(plugin->name, name) == 0) @@ -84,7 +78,7 @@ void archive_plugin_print_all_suffixes(FILE * fp) { const char *const*suffixes; - for (unsigned i = 0; i < num_archive_plugins; ++i) { + for (unsigned i = 0; archive_plugins[i] != NULL; ++i) { const struct archive_plugin *plugin = archive_plugins[i]; if (!archive_plugins_enabled[i]) continue; @@ -101,7 +95,7 @@ void archive_plugin_print_all_suffixes(FILE * fp) void archive_plugin_init_all(void) { - for (unsigned i = 0; i < num_archive_plugins; ++i) { + for (unsigned i = 0; archive_plugins[i] != NULL; ++i) { const struct archive_plugin *plugin = archive_plugins[i]; if (plugin->init == NULL || archive_plugins[i]->init()) archive_plugins_enabled[i] = true; @@ -110,7 +104,7 @@ void archive_plugin_init_all(void) void archive_plugin_deinit_all(void) { - for (unsigned i = 0; i < num_archive_plugins; ++i) { + for (unsigned i = 0; archive_plugins[i] != NULL; ++i) { const struct archive_plugin *plugin = archive_plugins[i]; if (archive_plugins_enabled[i] && plugin->finish != NULL) archive_plugins[i]->finish(); diff --git a/src/archive_list.h b/src/archive_list.h index 55278fbc4..b65245ce9 100644 --- a/src/archive_list.h +++ b/src/archive_list.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,8 +20,6 @@ #ifndef MPD_ARCHIVE_LIST_H #define MPD_ARCHIVE_LIST_H -#include "archive_api.h" - #include <stdio.h> struct archive_plugin; diff --git a/src/archive_plugin.c b/src/archive_plugin.c new file mode 100644 index 000000000..60da4d283 --- /dev/null +++ b/src/archive_plugin.c @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2003-2010 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 "archive_plugin.h" +#include "archive_internal.h" + +#include <assert.h> + +struct archive_file * +archive_file_open(const struct archive_plugin *plugin, const char *path, + GError **error_r) +{ + struct archive_file *file; + + assert(plugin != NULL); + assert(plugin->open != NULL); + assert(path != NULL); + assert(error_r == NULL || *error_r == NULL); + + file = plugin->open(path, error_r); + + if (file != NULL) { + assert(file->plugin != NULL); + assert(file->plugin->close != NULL); + assert(file->plugin->scan_reset != NULL); + assert(file->plugin->scan_next != NULL); + assert(file->plugin->open_stream != NULL); + assert(error_r == NULL || *error_r == NULL); + } else { + assert(error_r == NULL || *error_r != NULL); + } + + return file; +} + +void +archive_file_close(struct archive_file *file) +{ + assert(file != NULL); + assert(file->plugin != NULL); + assert(file->plugin->close != NULL); + + file->plugin->close(file); +} + +void +archive_file_scan_reset(struct archive_file *file) +{ + assert(file != NULL); + assert(file->plugin != NULL); + assert(file->plugin->scan_reset != NULL); + assert(file->plugin->scan_next != NULL); + + file->plugin->scan_reset(file); +} + +char * +archive_file_scan_next(struct archive_file *file) +{ + assert(file != NULL); + assert(file->plugin != NULL); + assert(file->plugin->scan_next != NULL); + + return file->plugin->scan_next(file); +} + +struct input_stream * +archive_file_open_stream(struct archive_file *file, + const char *path, GError **error_r) +{ + assert(file != NULL); + assert(file->plugin != NULL); + assert(file->plugin->open_stream != NULL); + + return file->plugin->open_stream(file, path, error_r); +} diff --git a/src/archive_plugin.h b/src/archive_plugin.h new file mode 100644 index 000000000..b08c93389 --- /dev/null +++ b/src/archive_plugin.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ARCHIVE_PLUGIN_H +#define MPD_ARCHIVE_PLUGIN_H + +#include <glib.h> + +#include <stdbool.h> + +struct input_stream; +struct archive_file; + +struct archive_plugin { + const char *name; + + /** + * optional, set this to NULL if the archive plugin doesn't + * have/need one this must false if there is an error and + * true otherwise + */ + bool (*init)(void); + + /** + * optional, set this to NULL if the archive plugin doesn't + * have/need one + */ + void (*finish)(void); + + /** + * tryes to open archive file and associates handle with archive + * returns pointer to handle used is all operations with this archive + * or NULL when opening fails + */ + struct archive_file *(*open)(const char *path_fs, GError **error_r); + + /** + * reset routine will move current read index in archive to default + * position and then the filenames from archives can be read + * via scan_next routine + */ + void (*scan_reset)(struct archive_file *); + + /** + * the read method will return corresponding files from archive + * (as pathnames) and move read index to next file. When there is no + * next file it return NULL. + */ + char *(*scan_next)(struct archive_file *); + + /** + * Opens an input_stream of a file within the archive. + * + * @param path the path within the archive + * @param error_r location to store the error occuring, or + * NULL to ignore errors + */ + struct input_stream *(*open_stream)(struct archive_file *af, + const char *path, + GError **error_r); + + /** + * closes archive file. + */ + void (*close)(struct archive_file *); + + /** + * suffixes handled by this plugin. + * last element in these arrays must always be a NULL + */ + const char *const*suffixes; +}; + +struct archive_file * +archive_file_open(const struct archive_plugin *plugin, const char *path, + GError **error_r); + +void +archive_file_close(struct archive_file *file); + +void +archive_file_scan_reset(struct archive_file *file); + +char * +archive_file_scan_next(struct archive_file *file); + +struct input_stream * +archive_file_open_stream(struct archive_file *file, + const char *path, GError **error_r); + +#endif diff --git a/src/audio.c b/src/audio.c index d48558e46..f9894cf3c 100644 --- a/src/audio.c +++ b/src/audio.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "audio.h" #include "audio_format.h" #include "audio_parser.h" @@ -24,6 +25,7 @@ #include "output_plugin.h" #include "output_all.h" #include "conf.h" +#include "mpd_error.h" #include <glib.h> @@ -35,9 +37,8 @@ static struct audio_format configured_audio_format; void getOutputAudioFormat(const struct audio_format *inAudioFormat, struct audio_format *outAudioFormat) { - *outAudioFormat = audio_format_defined(&configured_audio_format) - ? configured_audio_format - : *inAudioFormat; + *outAudioFormat = *inAudioFormat; + audio_format_mask_apply(outAudioFormat, &configured_audio_format); } void initAudioConfig(void) @@ -46,17 +47,13 @@ void initAudioConfig(void) GError *error = NULL; bool ret; - if (NULL == param || NULL == param->value) + if (param == NULL) return; ret = audio_format_parse(&configured_audio_format, param->value, - &error); + true, &error); if (!ret) - g_error("error parsing \"%s\" at line %i: %s", - CONF_AUDIO_OUTPUT_FORMAT, param->line, error->message); -} - -void finishAudioConfig(void) -{ - audio_format_clear(&configured_audio_format); + MPD_ERROR("error parsing \"%s\" at line %i: %s", + CONF_AUDIO_OUTPUT_FORMAT, param->line, + error->message); } diff --git a/src/audio.h b/src/audio.h index 4d80ee0e6..cb3ab7bbe 100644 --- a/src/audio.h +++ b/src/audio.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -30,6 +30,4 @@ void getOutputAudioFormat(const struct audio_format *inFormat, /* make sure initPlayerData is called before this function!! */ void initAudioConfig(void); -void finishAudioConfig(void); - #endif diff --git a/src/audio_check.c b/src/audio_check.c new file mode 100644 index 000000000..61d2c5833 --- /dev/null +++ b/src/audio_check.c @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "audio_check.h" +#include "audio_format.h" + +#include <assert.h> + +bool +audio_check_sample_rate(unsigned long sample_rate, GError **error_r) +{ + if (!audio_valid_sample_rate(sample_rate)) { + g_set_error(error_r, audio_format_quark(), 0, + "Invalid sample rate: %lu", sample_rate); + return false; + } + + return true; +} + +bool +audio_check_sample_format(enum sample_format sample_format, GError **error_r) +{ + if (!audio_valid_sample_format(sample_format)) { + g_set_error(error_r, audio_format_quark(), 0, + "Invalid sample format: %u", sample_format); + return false; + } + + return true; +} + +bool +audio_check_channel_count(unsigned channels, GError **error_r) +{ + if (!audio_valid_channel_count(channels)) { + g_set_error(error_r, audio_format_quark(), 0, + "Invalid channel count: %u", channels); + return false; + } + + return true; +} + +bool +audio_format_init_checked(struct audio_format *af, unsigned long sample_rate, + enum sample_format sample_format, unsigned channels, + GError **error_r) +{ + if (audio_check_sample_rate(sample_rate, error_r) && + audio_check_sample_format(sample_format, error_r) && + audio_check_channel_count(channels, error_r)) { + audio_format_init(af, sample_rate, sample_format, channels); + assert(audio_format_valid(af)); + return true; + } else + return false; +} diff --git a/src/audio_check.h b/src/audio_check.h new file mode 100644 index 000000000..cc08c9ba1 --- /dev/null +++ b/src/audio_check.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_AUDIO_CHECK_H +#define MPD_AUDIO_CHECK_H + +#include "audio_format.h" + +#include <glib.h> +#include <stdbool.h> + +/** + * The GLib quark used for errors reported by this library. + */ +static inline GQuark +audio_format_quark(void) +{ + return g_quark_from_static_string("audio_format"); +} + +bool +audio_check_sample_rate(unsigned long sample_rate, GError **error_r); + +bool +audio_check_sample_format(unsigned sample_format, GError **error_r); + +bool +audio_check_channel_count(unsigned sample_format, GError **error_r); + +/** + * Wrapper for audio_format_init(), which checks all attributes. + */ +bool +audio_format_init_checked(struct audio_format *af, unsigned long sample_rate, + enum sample_format sample_format, unsigned channels, + GError **error_r); + +#endif diff --git a/src/audio_format.c b/src/audio_format.c new file mode 100644 index 000000000..13403fbc1 --- /dev/null +++ b/src/audio_format.c @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "audio_format.h" + +#include <assert.h> +#include <stdio.h> + +#if G_BYTE_ORDER == G_BIG_ENDIAN +#define REVERSE_ENDIAN_SUFFIX "_le" +#else +#define REVERSE_ENDIAN_SUFFIX "_be" +#endif + +const char * +sample_format_to_string(enum sample_format format) +{ + switch (format) { + case SAMPLE_FORMAT_UNDEFINED: + return "?"; + + case SAMPLE_FORMAT_S8: + return "8"; + + case SAMPLE_FORMAT_S16: + return "16"; + + case SAMPLE_FORMAT_S24: + return "24_3"; + + case SAMPLE_FORMAT_S24_P32: + return "24"; + + case SAMPLE_FORMAT_S32: + return "32"; + } + + /* unreachable */ + assert(false); + return "?"; +} + +const char * +audio_format_to_string(const struct audio_format *af, + struct audio_format_string *s) +{ + assert(af != NULL); + assert(s != NULL); + + snprintf(s->buffer, sizeof(s->buffer), "%u:%s%s:%u", + af->sample_rate, sample_format_to_string(af->format), + af->reverse_endian ? REVERSE_ENDIAN_SUFFIX : "", + af->channels); + + return s->buffer; +} diff --git a/src/audio_format.h b/src/audio_format.h index 64087d070..dd32731c3 100644 --- a/src/audio_format.h +++ b/src/audio_format.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,25 +23,123 @@ #include <stdint.h> #include <stdbool.h> +enum sample_format { + SAMPLE_FORMAT_UNDEFINED = 0, + + SAMPLE_FORMAT_S8, + SAMPLE_FORMAT_S16, + + /** + * Signed 24 bit integer samples, without padding. + */ + SAMPLE_FORMAT_S24, + + /** + * Signed 24 bit integer samples, packed in 32 bit integers + * (the most significant byte is filled with the sign bit). + */ + SAMPLE_FORMAT_S24_P32, + + SAMPLE_FORMAT_S32, +}; + +/** + * This structure describes the format of a raw PCM stream. + */ struct audio_format { + /** + * The sample rate in Hz. A better name for this attribute is + * "frame rate", because technically, you have two samples per + * frame in stereo sound. + */ uint32_t sample_rate; - uint8_t bits; + + /** + * The format samples are stored in. See the #sample_format + * enum for valid values. + */ + uint8_t format; + + /** + * The number of channels. Only mono (1) and stereo (2) are + * fully supported currently. + */ uint8_t channels; + + /** + * If zero, then samples are stored in host byte order. If + * nonzero, then samples are stored in the reverse host byte + * order. + */ + uint8_t reverse_endian; +}; + +/** + * Buffer for audio_format_string(). + */ +struct audio_format_string { + char buffer[24]; }; +/** + * Clears the #audio_format object, i.e. sets all attributes to an + * undefined (invalid) value. + */ static inline void audio_format_clear(struct audio_format *af) { af->sample_rate = 0; - af->bits = 0; + af->format = SAMPLE_FORMAT_UNDEFINED; af->channels = 0; + af->reverse_endian = 0; } +/** + * Initializes an #audio_format object, i.e. sets all + * attributes to valid values. + */ +static inline void audio_format_init(struct audio_format *af, + uint32_t sample_rate, + enum sample_format format, uint8_t channels) +{ + af->sample_rate = sample_rate; + af->format = (uint8_t)format; + af->channels = channels; + af->reverse_endian = 0; +} + +/** + * Checks whether the specified #audio_format object has a defined + * value. + */ static inline bool audio_format_defined(const struct audio_format *af) { return af->sample_rate != 0; } /** + * Checks whether the specified #audio_format object is full, i.e. all + * attributes are defined. This is more complete than + * audio_format_defined(), but slower. + */ +static inline bool +audio_format_fully_defined(const struct audio_format *af) +{ + return af->sample_rate != 0 && af->format != SAMPLE_FORMAT_UNDEFINED && + af->channels != 0; +} + +/** + * Checks whether the specified #audio_format object has at least one + * defined value. + */ +static inline bool +audio_format_mask_defined(const struct audio_format *af) +{ + return af->sample_rate != 0 || af->format != SAMPLE_FORMAT_UNDEFINED || + af->channels != 0; +} + +/** * Checks whether the sample rate is valid. * * @param sample_rate the sample rate in Hz @@ -58,9 +156,21 @@ audio_valid_sample_rate(unsigned sample_rate) * @param bits the number of significant bits per sample */ static inline bool -audio_valid_sample_format(unsigned bits) +audio_valid_sample_format(enum sample_format format) { - return bits == 16 || bits == 24 || bits == 32 || bits == 8; + switch (format) { + case SAMPLE_FORMAT_S8: + case SAMPLE_FORMAT_S16: + case SAMPLE_FORMAT_S24: + case SAMPLE_FORMAT_S24_P32: + case SAMPLE_FORMAT_S32: + return true; + + case SAMPLE_FORMAT_UNDEFINED: + break; + } + + return false; } /** @@ -79,16 +189,44 @@ audio_valid_channel_count(unsigned channels) static inline bool audio_format_valid(const struct audio_format *af) { return audio_valid_sample_rate(af->sample_rate) && - audio_valid_sample_format(af->bits) && + audio_valid_sample_format((enum sample_format)af->format) && audio_valid_channel_count(af->channels); } +/** + * Returns false if the format mask is not valid for playback with + * MPD. This function performs some basic validity checks. + */ +static inline bool audio_format_mask_valid(const struct audio_format *af) +{ + return (af->sample_rate == 0 || + audio_valid_sample_rate(af->sample_rate)) && + (af->format == SAMPLE_FORMAT_UNDEFINED || + audio_valid_sample_format((enum sample_format)af->format)) && + (af->channels == 0 || audio_valid_channel_count(af->channels)); +} + static inline bool audio_format_equals(const struct audio_format *a, const struct audio_format *b) { return a->sample_rate == b->sample_rate && - a->bits == b->bits && - a->channels == b->channels; + a->format == b->format && + a->channels == b->channels && + a->reverse_endian == b->reverse_endian; +} + +static inline void +audio_format_mask_apply(struct audio_format *af, + const struct audio_format *mask) +{ + if (mask->sample_rate != 0) + af->sample_rate = mask->sample_rate; + + if (mask->format != SAMPLE_FORMAT_UNDEFINED) + af->format = mask->format; + + if (mask->channels != 0) + af->channels = mask->channels; } /** @@ -96,28 +234,65 @@ static inline bool audio_format_equals(const struct audio_format *a, */ static inline unsigned audio_format_sample_size(const struct audio_format *af) { - if (af->bits <= 8) + switch (af->format) { + case SAMPLE_FORMAT_S8: return 1; - else if (af->bits <= 16) + + case SAMPLE_FORMAT_S16: return 2; - else + + case SAMPLE_FORMAT_S24: + return 3; + + case SAMPLE_FORMAT_S24_P32: + case SAMPLE_FORMAT_S32: return 4; + + case SAMPLE_FORMAT_UNDEFINED: + break; + } + + return 0; } +/** + * Returns the size of each full frame in bytes. + */ static inline unsigned audio_format_frame_size(const struct audio_format *af) { return audio_format_sample_size(af) * af->channels; } +/** + * Returns the floating point factor which converts a time span to a + * storage size in bytes. + */ static inline double audio_format_time_to_size(const struct audio_format *af) { return af->sample_rate * audio_format_frame_size(af); } -static inline double audioFormatSizeToTime(const struct audio_format *af) -{ - return 1.0 / audio_format_time_to_size(af); -} +/** + * Renders a #sample_format enum into a string, e.g. for printing it + * in a log file. + * + * @param format a #sample_format enum value + * @return the string + */ +const char * +sample_format_to_string(enum sample_format format); + +/** + * Renders the #audio_format object into a string, e.g. for printing + * it in a log file. + * + * @param af the #audio_format object + * @param s a buffer to print into + * @return the string, or NULL if the #audio_format object is invalid + */ +const char * +audio_format_to_string(const struct audio_format *af, + struct audio_format_string *s); #endif diff --git a/src/audio_parser.c b/src/audio_parser.c index 906b0f819..039ffa1ab 100644 --- a/src/audio_parser.c +++ b/src/audio_parser.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,9 +22,13 @@ * */ +#include "config.h" #include "audio_parser.h" #include "audio_format.h" +#include "audio_check.h" +#include <assert.h> +#include <string.h> #include <stdlib.h> /** @@ -36,64 +40,158 @@ audio_parser_quark(void) return g_quark_from_static_string("audio_parser"); } -bool -audio_format_parse(struct audio_format *dest, const char *src, GError **error) +static bool +parse_sample_rate(const char *src, bool mask, uint32_t *sample_rate_r, + const char **endptr_r, GError **error_r) { - char *endptr; unsigned long value; + char *endptr; - audio_format_clear(dest); - - /* parse sample rate */ + if (mask && *src == '*') { + *sample_rate_r = 0; + *endptr_r = src + 1; + return true; + } value = strtoul(src, &endptr, 10); if (endptr == src) { - g_set_error(error, audio_parser_quark(), 0, - "Sample rate missing"); + g_set_error(error_r, audio_parser_quark(), 0, + "Failed to parse the sample rate"); return false; - } else if (*endptr != ':') { - g_set_error(error, audio_parser_quark(), 0, - "Sample format missing"); + } else if (!audio_check_sample_rate(value, error_r)) + return false; + + *sample_rate_r = value; + *endptr_r = endptr; + return true; +} + +static bool +parse_sample_format(const char *src, bool mask, + enum sample_format *sample_format_r, + const char **endptr_r, GError **error_r) +{ + unsigned long value; + char *endptr; + enum sample_format sample_format; + + if (mask && *src == '*') { + *sample_format_r = SAMPLE_FORMAT_UNDEFINED; + *endptr_r = src + 1; + return true; + } + + value = strtoul(src, &endptr, 10); + if (endptr == src) { + g_set_error(error_r, audio_parser_quark(), 0, + "Failed to parse the sample format"); return false; - } else if (!audio_valid_sample_rate(value)) { - g_set_error(error, audio_parser_quark(), 0, - "Invalid sample rate: %lu", value); + } + + switch (value) { + case 8: + sample_format = SAMPLE_FORMAT_S8; + break; + + case 16: + sample_format = SAMPLE_FORMAT_S16; + break; + + case 24: + if (memcmp(endptr, "_3", 2) == 0) { + sample_format = SAMPLE_FORMAT_S24; + endptr += 2; + } else + sample_format = SAMPLE_FORMAT_S24_P32; + break; + + case 32: + sample_format = SAMPLE_FORMAT_S32; + break; + + default: + g_set_error(error_r, audio_parser_quark(), 0, + "Invalid sample format: %lu", value); return false; } - dest->sample_rate = value; + assert(audio_valid_sample_format(sample_format)); - /* parse sample format */ + *sample_format_r = sample_format; + *endptr_r = endptr; + return true; +} + +static bool +parse_channel_count(const char *src, bool mask, uint8_t *channels_r, + const char **endptr_r, GError **error_r) +{ + unsigned long value; + char *endptr; + + if (mask && *src == '*') { + *channels_r = 0; + *endptr_r = src + 1; + return true; + } - src = endptr + 1; value = strtoul(src, &endptr, 10); if (endptr == src) { - g_set_error(error, audio_parser_quark(), 0, - "Sample format missing"); + g_set_error(error_r, audio_parser_quark(), 0, + "Failed to parse the channel count"); return false; - } else if (*endptr != ':') { - g_set_error(error, audio_parser_quark(), 0, - "Channel count missing"); + } else if (!audio_check_channel_count(value, error_r)) return false; - } else if (!audio_valid_sample_format(value)) { - g_set_error(error, audio_parser_quark(), 0, - "Invalid sample format: %lu", value); + + *channels_r = value; + *endptr_r = endptr; + return true; +} + +bool +audio_format_parse(struct audio_format *dest, const char *src, + bool mask, GError **error_r) +{ + uint32_t rate; + enum sample_format sample_format; + uint8_t channels; + + audio_format_clear(dest); + + /* parse sample rate */ + + if (!parse_sample_rate(src, mask, &rate, &src, error_r)) + return false; + + if (*src++ != ':') { + g_set_error(error_r, audio_parser_quark(), 0, + "Sample format missing"); return false; } - dest->bits = value; + /* parse sample format */ + + if (!parse_sample_format(src, mask, &sample_format, &src, error_r)) + return false; + + if (*src++ != ':') { + g_set_error(error_r, audio_parser_quark(), 0, + "Channel count missing"); + return false; + } /* parse channel count */ - src = endptr + 1; - value = strtoul(src, &endptr, 10); - if (*endptr != 0 || !audio_valid_channel_count(value)) { - g_set_error(error, audio_parser_quark(), 0, - "Invalid channel count: %s", src); + if (!parse_channel_count(src, mask, &channels, &src, error_r)) + return false; + + if (*src != 0) { + g_set_error(error_r, audio_parser_quark(), 0, + "Extra data after channel count: %s", src); return false; } - dest->channels = value; + audio_format_init(dest, rate, sample_format, channels); return true; } diff --git a/src/audio_parser.h b/src/audio_parser.h index 30a927456..214ec5eb1 100644 --- a/src/audio_parser.h +++ b/src/audio_parser.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -37,11 +37,13 @@ struct audio_format; * * @param dest the destination #audio_format struct * @param src the input string - * @param error location to store the error occuring, or NULL to + * @param mask if true, then "*" is allowed for any number of items + * @param error_r location to store the error occuring, or NULL to * ignore errors * @return true on success */ bool -audio_format_parse(struct audio_format *dest, const char *src, GError **error); +audio_format_parse(struct audio_format *dest, const char *src, + bool mask, GError **error_r); #endif diff --git a/src/buffer.c b/src/buffer.c index 24715a744..bee871700 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "buffer.h" #include "chunk.h" #include "poison.h" @@ -117,6 +118,9 @@ music_buffer_return(struct music_buffer *buffer, struct music_chunk *chunk) assert(buffer != NULL); assert(chunk != NULL); + if (chunk->other != NULL) + music_buffer_return(buffer, chunk->other); + g_mutex_lock(buffer->mutex); music_chunk_free(chunk); diff --git a/src/buffer.h b/src/buffer.h index 441e0ea4c..75e5bc6e6 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/buffer2array.c b/src/buffer2array.c deleted file mode 100644 index b6029d754..000000000 --- a/src/buffer2array.c +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2003-2009 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "buffer2array.h" - -#include <glib.h> - -#include <string.h> - -int buffer2array(char *buffer, char *array[], const int max) -{ - int i = 0; - char *c = buffer; - - while (*c != '\0' && i < max) { - if (*c == '\"') { - array[i++] = ++c; - while (*c != '\0') { - if (*c == '\"') { - *(c++) = '\0'; - break; - } - else if (*(c++) == '\\' && *c != '\0') { - memmove(c - 1, c, strlen(c) + 1); - } - } - } else { - c = g_strchug(c); - if (*c == '\0') - return i; - - array[i++] = c++; - - while (!g_ascii_isspace(*c) && *c != '\0') - ++c; - } - if (*c == '\0') - return i; - *(c++) = '\0'; - - c = g_strchug(c); - } - return i; -} - -#ifdef UNIT_TEST - -#include <stdio.h> -#include <string.h> -#include <assert.h> - -int main() -{ - char *a[4] = { NULL }; - char *b; - int max; - - b = strdup("lsinfo \"/some/dir/name \\\"test\\\"\""); - assert(b); - max = buffer2array(b, a, 4); - assert( !strcmp("lsinfo", a[0]) ); - assert( !strcmp("/some/dir/name \"test\"", a[1]) ); - assert( !a[2] ); - - b = strdup("lsinfo \"/some/dir/name \\\"test\\\" something else\""); - assert(b); - max = buffer2array(b, a, 4); - assert( !strcmp("lsinfo", a[0]) ); - assert( !strcmp("/some/dir/name \"test\" something else", a[1]) ); - assert( !a[2] ); - - b = strdup("lsinfo \"/some/dir\\\\name\""); - assert(b); - max = buffer2array(b, a, 4); - assert( !strcmp("lsinfo", a[0]) ); - assert( !strcmp("/some/dir\\name", a[1]) ); - assert( !a[2] ); - - b = strdup("lsinfo \"/some/dir name\""); - assert(b); - max = buffer2array(b, a, 4); - assert( !strcmp("lsinfo", a[0]) ); - assert( !strcmp("/some/dir name", a[1]) ); - assert( !a[2] ); - - b = strdup("lsinfo \"\\\"/some/dir\\\"\""); - assert(b); - max = buffer2array(b, a, 4); - assert( !strcmp("lsinfo", a[0]) ); - assert( !strcmp("\"/some/dir\"", a[1]) ); - assert( !a[2] ); - - b = strdup("lsinfo \"\\\"/some/dir\\\" x\""); - assert(b); - max = buffer2array(b, a, 4); - assert( !strcmp("lsinfo", a[0]) ); - assert( !strcmp("\"/some/dir\" x", a[1]) ); - assert( !a[2] ); - - b = strdup("lsinfo \"single quote\\'d from php magicquotes\""); - assert(b); - max = buffer2array(b, a, 4); - assert( !strcmp("lsinfo", a[0]) ); - assert( !strcmp("single quote\'d from php magicquotes", a[1]) ); - assert( !a[2] ); - - b = strdup("lsinfo \"double quote\\\"d from php magicquotes\""); - assert(b); - max = buffer2array(b, a, 4); - assert( !strcmp("lsinfo", a[0]) ); - assert( !strcmp("double quote\"d from php magicquotes", a[1]) ); - assert( !a[2] ); - - return 0; -} - -#endif diff --git a/src/check.h b/src/check.h new file mode 100644 index 000000000..56061621f --- /dev/null +++ b/src/check.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CHECK_H +#define MPD_CHECK_H + +/* + * All sources must include config.h on the first line to ensure that + * Large File Support is configured properly. This header checks + * whether this has happened. + * + * Usage: include this header before you use any of the above types. + * It will stop the compiler if something went wrong. + * + * This is Linux/glibc specific, and only enabled in the debug build, + * so bugs in this headers don't affect users with production builds. + * + */ + +#ifndef PACKAGE_VERSION +#error config.h missing +#endif + +#if defined(__linux__) && !defined(NDEBUG) && defined(ENABLE_LARGEFILE) && \ + defined(_FEATURES_H) && defined(__i386__) && \ + !defined(__USE_FILE_OFFSET64) +/* on i386, check if LFS is enabled */ +#error config.h was included too late +#endif + +#endif diff --git a/src/chunk.c b/src/chunk.c index 3ac190633..79597506d 100644 --- a/src/chunk.c +++ b/src/chunk.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "chunk.h" #include "audio_format.h" #include "tag.h" @@ -26,8 +27,10 @@ void music_chunk_init(struct music_chunk *chunk) { + chunk->other = NULL; chunk->length = 0; chunk->tag = NULL; + chunk->replay_gain_serial = 0; } void diff --git a/src/chunk.h b/src/chunk.h index 51e75d906..02e7b3650 100644 --- a/src/chunk.h +++ b/src/chunk.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,8 @@ #ifndef MPD_CHUNK_H #define MPD_CHUNK_H +#include "replay_gain_info.h" + #ifndef NDEBUG #include "audio_format.h" #endif @@ -42,6 +44,18 @@ struct music_chunk { /** the next chunk in a linked list */ struct music_chunk *next; + /** + * An optional chunk which should be mixed into this chunk. + * This is used for cross-fading. + */ + struct music_chunk *other; + + /** + * The current mix ratio for cross-fading: 1.0 means play 100% + * of this chunk, 0.0 means play 100% of the "other" chunk. + */ + float mix_ratio; + /** number of bytes stored in this chunk */ uint16_t length; @@ -59,6 +73,19 @@ struct music_chunk { */ struct tag *tag; + /** + * Replay gain information associated with this chunk. + * Only valid if the serial is not 0. + */ + struct replay_gain_info replay_gain_info; + + /** + * A serial number for checking if replay gain info has + * changed since the last chunk. The magic value 0 indicates + * that there is no replay gain info available. + */ + unsigned replay_gain_serial; + /** the data (probably PCM) */ char data[CHUNK_SIZE]; diff --git a/src/client.c b/src/client.c index 6a256998f..9668c9249 100644 --- a/src/client.c +++ b/src/client.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,110 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "client.h" -#include "fifo_buffer.h" -#include "command.h" -#include "conf.h" -#include "listen.h" -#include "socket_util.h" -#include "permission.h" -#include "event_pipe.h" -#include "idle.h" -#include "main.h" #include "config.h" - -#include <glib.h> -#include <assert.h> -#include <unistd.h> -#include <string.h> -#include <stdlib.h> -#include <stdio.h> -#include <errno.h> - -#ifdef WIN32 -#include <ws2tcpip.h> -#include <winsock.h> -#else -#include <sys/socket.h> -#include <netinet/in.h> -#include <arpa/inet.h> -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "client" -#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO - -static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n"; - -#define CLIENT_LIST_MODE_BEGIN "command_list_begin" -#define CLIENT_LIST_OK_MODE_BEGIN "command_list_ok_begin" -#define CLIENT_LIST_MODE_END "command_list_end" -#define CLIENT_TIMEOUT_DEFAULT (60) -#define CLIENT_MAX_CONNECTIONS_DEFAULT (10) -#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024) -#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024) - -/* set this to zero to indicate we have no possible clients */ -static unsigned int client_max_connections; /*CLIENT_MAX_CONNECTIONS_DEFAULT; */ -static int client_timeout; -static size_t client_max_command_list_size; -static size_t client_max_output_buffer_size; - -struct deferred_buffer { - size_t size; - char data[sizeof(long)]; -}; - -struct client { - GIOChannel *channel; - guint source_id; - - /** the buffer for reading lines from the #channel */ - struct fifo_buffer *input; - - unsigned permission; - - /** the uid of the client process, or -1 if unknown */ - int uid; - - /** - * How long since the last activity from this client? - */ - GTimer *last_activity; - - GSList *cmd_list; /* for when in list mode */ - int cmd_list_OK; /* print OK after each command execution */ - size_t cmd_list_size; /* mem cmd_list consumes */ - GQueue *deferred_send; /* for output if client is slow */ - size_t deferred_bytes; /* mem deferred_send consumes */ - unsigned int num; /* client number */ - - char send_buf[4096]; - size_t send_buf_used; /* bytes used this instance */ - - /** is this client waiting for an "idle" response? */ - bool idle_waiting; - - /** idle flags pending on this client, to be sent as soon as - the client enters "idle" */ - unsigned idle_flags; - - /** idle flags that the client wants to receive */ - unsigned idle_subscriptions; -}; - -static GList *clients; -static unsigned num_clients; -static guint expire_source_id; - -static void client_write_deferred(struct client *client); - -static void client_write_output(struct client *client); - -static void client_manager_expire(void); - -static gboolean -client_in_event(GIOChannel *source, GIOCondition condition, gpointer data); +#include "client_internal.h" bool client_is_expired(const struct client *client) { @@ -141,782 +39,3 @@ void client_set_permission(struct client *client, unsigned permission) { client->permission = permission; } - -/** - * An idle event which calls client_manager_expire(). - */ -static gboolean -client_manager_expire_event(G_GNUC_UNUSED gpointer data) -{ - expire_source_id = 0; - client_manager_expire(); - return false; -} - -static inline void client_set_expired(struct client *client) -{ - if (expire_source_id == 0 && !client_is_expired(client)) - /* delayed deletion */ - expire_source_id = g_idle_add(client_manager_expire_event, - NULL); - - if (client->source_id != 0) { - g_source_remove(client->source_id); - client->source_id = 0; - } - - if (client->channel != NULL) { - g_io_channel_unref(client->channel); - client->channel = NULL; - } -} - -static void client_init(struct client *client, int fd) -{ - static unsigned int next_client_num; - - assert(fd >= 0); - - client->cmd_list_size = 0; - client->cmd_list_OK = -1; - -#ifndef G_OS_WIN32 - client->channel = g_io_channel_unix_new(fd); -#else - client->channel = g_io_channel_win32_new_socket(fd); -#endif - /* GLib is responsible for closing the file descriptor */ - g_io_channel_set_close_on_unref(client->channel, true); - /* NULL encoding means the stream is binary safe; the MPD - protocol is UTF-8 only, but we are doing this call anyway - to prevent GLib from messing around with the stream */ - g_io_channel_set_encoding(client->channel, NULL, NULL); - /* we prefer to do buffering */ - g_io_channel_set_buffered(client->channel, false); - - client->source_id = g_io_add_watch(client->channel, - G_IO_IN|G_IO_ERR|G_IO_HUP, - client_in_event, client); - - client->input = fifo_buffer_new(4096); - - client->cmd_list = NULL; - client->deferred_send = g_queue_new(); - client->deferred_bytes = 0; - client->num = next_client_num++; - client->send_buf_used = 0; - - client->permission = getDefaultPermissions(); - - (void)write(fd, GREETING, sizeof(GREETING) - 1); -} - -static void free_cmd_list(GSList *list) -{ - for (GSList *tmp = list; tmp != NULL; tmp = g_slist_next(tmp)) - g_free(tmp->data); - - g_slist_free(list); -} - -static void new_cmd_list_ptr(struct client *client, char *s) -{ - client->cmd_list = g_slist_prepend(client->cmd_list, g_strdup(s)); -} - -static void -deferred_buffer_free(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct deferred_buffer *buffer = data; - g_free(buffer); -} - -static void client_close(struct client *client) -{ - assert(num_clients > 0); - assert(clients != NULL); - - clients = g_list_remove(clients, client); - --num_clients; - - client_set_expired(client); - - g_timer_destroy(client->last_activity); - - if (client->cmd_list) { - free_cmd_list(client->cmd_list); - client->cmd_list = NULL; - } - - g_queue_foreach(client->deferred_send, deferred_buffer_free, NULL); - g_queue_free(client->deferred_send); - - fifo_buffer_free(client->input); - - g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE, - "[%u] closed", client->num); - g_free(client); -} - -void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid) -{ - struct client *client; - char *remote; - - if (num_clients >= client_max_connections) { - g_warning("Max Connections Reached!"); - close(fd); - return; - } - - client = g_new0(struct client, 1); - clients = g_list_prepend(clients, client); - ++num_clients; - - client_init(client, fd); - client->uid = uid; - - client->last_activity = g_timer_new(); - - remote = sockaddr_to_string(sa, sa_length, NULL); - g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE, - "[%u] opened from %s", client->num, remote); - g_free(remote); -} - -static int client_process_line(struct client *client, char *line) -{ - int ret = 1; - - if (strcmp(line, "noidle") == 0) { - if (client->idle_waiting) { - /* send empty idle response and leave idle mode */ - client->idle_waiting = false; - command_success(client); - client_write_output(client); - } - - /* do nothing if the client wasn't idling: the client - has already received the full idle response from - client_idle_notify(), which he can now evaluate */ - - return 0; - } else if (client->idle_waiting) { - /* during idle mode, clients must not send anything - except "noidle" */ - g_warning("[%u] command \"%s\" during idle", - client->num, line); - return COMMAND_RETURN_CLOSE; - } - - if (client->cmd_list_OK >= 0) { - if (strcmp(line, CLIENT_LIST_MODE_END) == 0) { - g_debug("[%u] process command list", - client->num); - - /* for scalability reasons, we have prepended - each new command; now we have to reverse it - to restore the correct order */ - client->cmd_list = g_slist_reverse(client->cmd_list); - - ret = command_process_list(client, - client->cmd_list_OK, - client->cmd_list); - g_debug("[%u] process command " - "list returned %i", client->num, ret); - - if (ret == COMMAND_RETURN_CLOSE || - client_is_expired(client)) - return COMMAND_RETURN_CLOSE; - - if (ret == 0) - command_success(client); - - client_write_output(client); - free_cmd_list(client->cmd_list); - client->cmd_list = NULL; - client->cmd_list_OK = -1; - } else { - size_t len = strlen(line) + 1; - client->cmd_list_size += len; - if (client->cmd_list_size > - client_max_command_list_size) { - g_warning("[%u] command list size (%lu) " - "is larger than the max (%lu)", - client->num, - (unsigned long)client->cmd_list_size, - (unsigned long)client_max_command_list_size); - return COMMAND_RETURN_CLOSE; - } else - new_cmd_list_ptr(client, line); - } - } else { - if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) { - client->cmd_list_OK = 0; - ret = 1; - } else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) { - client->cmd_list_OK = 1; - ret = 1; - } else { - g_debug("[%u] process command \"%s\"", - client->num, line); - ret = command_process(client, line); - g_debug("[%u] command returned %i", - client->num, ret); - - if (ret == COMMAND_RETURN_CLOSE || - client_is_expired(client)) - return COMMAND_RETURN_CLOSE; - - if (ret == 0) - command_success(client); - - client_write_output(client); - } - } - - return ret; -} - -static char * -client_read_line(struct client *client) -{ - const char *p, *newline; - size_t length; - char *line; - - p = fifo_buffer_read(client->input, &length); - if (p == NULL) - return NULL; - - newline = memchr(p, '\n', length); - if (newline == NULL) - return NULL; - - line = g_strndup(p, newline - p); - fifo_buffer_consume(client->input, newline - p + 1); - - return g_strchomp(line); -} - -static int client_input_received(struct client *client, size_t bytesRead) -{ - char *line; - int ret; - - fifo_buffer_append(client->input, bytesRead); - - /* process all lines */ - - while ((line = client_read_line(client)) != NULL) { - ret = client_process_line(client, line); - g_free(line); - - if (ret == COMMAND_RETURN_KILL || - ret == COMMAND_RETURN_CLOSE) - return ret; - if (client_is_expired(client)) - return COMMAND_RETURN_CLOSE; - } - - return 0; -} - -static int client_read(struct client *client) -{ - char *p; - size_t max_length; - GError *error = NULL; - GIOStatus status; - gsize bytes_read; - - assert(client != NULL); - assert(client->channel != NULL); - - p = fifo_buffer_write(client->input, &max_length); - if (p == NULL) { - g_warning("[%u] buffer overflow", client->num); - return COMMAND_RETURN_CLOSE; - } - - status = g_io_channel_read_chars(client->channel, p, max_length, - &bytes_read, &error); - switch (status) { - case G_IO_STATUS_NORMAL: - return client_input_received(client, bytes_read); - - case G_IO_STATUS_AGAIN: - /* try again later, after select() */ - return 0; - - case G_IO_STATUS_EOF: - /* peer disconnected */ - return COMMAND_RETURN_CLOSE; - - case G_IO_STATUS_ERROR: - /* I/O error */ - g_warning("failed to read from client %d: %s", - client->num, error->message); - g_error_free(error); - return COMMAND_RETURN_CLOSE; - } - - /* unreachable */ - return COMMAND_RETURN_CLOSE; -} - -static gboolean -client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, - gpointer data); - -static gboolean -client_in_event(G_GNUC_UNUSED GIOChannel *source, - GIOCondition condition, - gpointer data) -{ - struct client *client = data; - int ret; - - assert(!client_is_expired(client)); - - if (condition != G_IO_IN) { - client_set_expired(client); - return false; - } - - g_timer_start(client->last_activity); - - ret = client_read(client); - switch (ret) { - case COMMAND_RETURN_KILL: - client_close(client); - g_main_loop_quit(main_loop); - return false; - - case COMMAND_RETURN_CLOSE: - client_close(client); - return false; - } - - if (client_is_expired(client)) { - client_close(client); - return false; - } - - if (!g_queue_is_empty(client->deferred_send)) { - /* deferred buffers exist: schedule write */ - client->source_id = g_io_add_watch(client->channel, - G_IO_OUT|G_IO_ERR|G_IO_HUP, - client_out_event, client); - return false; - } - - /* read more */ - return true; -} - -static gboolean -client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, - gpointer data) -{ - struct client *client = data; - - assert(!client_is_expired(client)); - - if (condition != G_IO_OUT) { - client_set_expired(client); - return false; - } - - client_write_deferred(client); - - if (client_is_expired(client)) { - client_close(client); - return false; - } - - g_timer_start(client->last_activity); - - if (g_queue_is_empty(client->deferred_send)) { - /* done sending deferred buffers exist: schedule - read */ - client->source_id = g_io_add_watch(client->channel, - G_IO_IN|G_IO_ERR|G_IO_HUP, - client_in_event, client); - return false; - } - - /* write more */ - return true; -} - -void client_manager_init(void) -{ - client_timeout = config_get_positive(CONF_CONN_TIMEOUT, - CLIENT_TIMEOUT_DEFAULT); - client_max_connections = - config_get_positive(CONF_MAX_CONN, - CLIENT_MAX_CONNECTIONS_DEFAULT); - client_max_command_list_size = - config_get_positive(CONF_MAX_COMMAND_LIST_SIZE, - CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024) - * 1024; - - client_max_output_buffer_size = - config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE, - CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024) - * 1024; -} - -static void client_close_all(void) -{ - while (clients != NULL) { - struct client *client = clients->data; - - client_close(client); - } - - assert(num_clients == 0); -} - -void client_manager_deinit(void) -{ - client_close_all(); - - client_max_connections = 0; - - if (expire_source_id != 0) - g_source_remove(expire_source_id); -} - -static void -client_check_expired_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct client *client = data; - - if (client_is_expired(client)) { - g_debug("[%u] expired", client->num); - client_close(client); - } else if (!client->idle_waiting && /* idle clients - never expire */ - (int)g_timer_elapsed(client->last_activity, NULL) > - client_timeout) { - g_debug("[%u] timeout", client->num); - client_close(client); - } -} - -static void -client_manager_expire(void) -{ - g_list_foreach(clients, client_check_expired_callback, NULL); -} - -static size_t -client_write_deferred_buffer(struct client *client, - const struct deferred_buffer *buffer) -{ - GError *error = NULL; - GIOStatus status; - gsize bytes_written; - - assert(client != NULL); - assert(client->channel != NULL); - assert(buffer != NULL); - - status = g_io_channel_write_chars - (client->channel, buffer->data, buffer->size, - &bytes_written, &error); - switch (status) { - case G_IO_STATUS_NORMAL: - return bytes_written; - - case G_IO_STATUS_AGAIN: - return 0; - - case G_IO_STATUS_EOF: - /* client has disconnected */ - - client_set_expired(client); - return 0; - - case G_IO_STATUS_ERROR: - /* I/O error */ - - client_set_expired(client); - g_warning("failed to flush buffer for %i: %s", - client->num, error->message); - g_error_free(error); - return 0; - } - - /* unreachable */ - return 0; -} - -static void client_write_deferred(struct client *client) -{ - size_t ret; - - while (!g_queue_is_empty(client->deferred_send)) { - struct deferred_buffer *buf = - g_queue_peek_head(client->deferred_send); - - assert(buf->size > 0); - assert(buf->size <= client->deferred_bytes); - - ret = client_write_deferred_buffer(client, buf); - if (ret == 0) - break; - - if (ret < buf->size) { - assert(client->deferred_bytes >= (size_t)ret); - client->deferred_bytes -= ret; - buf->size -= ret; - memmove(buf->data, buf->data + ret, buf->size); - break; - } else { - size_t decr = sizeof(*buf) - - sizeof(buf->data) + buf->size; - - assert(client->deferred_bytes >= decr); - client->deferred_bytes -= decr; - g_free(buf); - g_queue_pop_head(client->deferred_send); - } - - g_timer_start(client->last_activity); - } - - if (g_queue_is_empty(client->deferred_send)) { - g_debug("[%u] buffer empty %lu", client->num, - (unsigned long)client->deferred_bytes); - assert(client->deferred_bytes == 0); - } -} - -static void client_defer_output(struct client *client, - const void *data, size_t length) -{ - size_t alloc; - struct deferred_buffer *buf; - - assert(length > 0); - - alloc = sizeof(*buf) - sizeof(buf->data) + length; - client->deferred_bytes += alloc; - if (client->deferred_bytes > client_max_output_buffer_size) { - g_warning("[%u] output buffer size (%lu) is " - "larger than the max (%lu)", - client->num, - (unsigned long)client->deferred_bytes, - (unsigned long)client_max_output_buffer_size); - /* cause client to close */ - client_set_expired(client); - return; - } - - buf = g_malloc(alloc); - buf->size = length; - memcpy(buf->data, data, length); - - g_queue_push_tail(client->deferred_send, buf); -} - -static void client_write_direct(struct client *client, - const char *data, size_t length) -{ - GError *error = NULL; - GIOStatus status; - gsize bytes_written; - - assert(client != NULL); - assert(client->channel != NULL); - assert(data != NULL); - assert(length > 0); - assert(g_queue_is_empty(client->deferred_send)); - - status = g_io_channel_write_chars(client->channel, data, length, - &bytes_written, &error); - switch (status) { - case G_IO_STATUS_NORMAL: - case G_IO_STATUS_AGAIN: - break; - - case G_IO_STATUS_EOF: - /* client has disconnected */ - - client_set_expired(client); - return; - - case G_IO_STATUS_ERROR: - /* I/O error */ - - client_set_expired(client); - g_warning("failed to write to %i: %s", - client->num, error->message); - g_error_free(error); - return; - } - - if (bytes_written < length) - client_defer_output(client, data + bytes_written, - length - bytes_written); - - if (!g_queue_is_empty(client->deferred_send)) - g_debug("[%u] buffer created", client->num); -} - -static void client_write_output(struct client *client) -{ - if (client_is_expired(client) || !client->send_buf_used) - return; - - if (!g_queue_is_empty(client->deferred_send)) { - client_defer_output(client, client->send_buf, - client->send_buf_used); - - if (client_is_expired(client)) - return; - - /* try to flush the deferred buffers now; the current - server command may take too long to finish, and - meanwhile try to feed output to the client, - otherwise it will time out. One reason why - deferring is slow might be that currently each - client_write() allocates a new deferred buffer. - This should be optimized after MPD 0.14. */ - client_write_deferred(client); - } else - client_write_direct(client, client->send_buf, - client->send_buf_used); - - client->send_buf_used = 0; -} - -/** - * Write a block of data to the client. - */ -static void client_write(struct client *client, const char *buffer, size_t buflen) -{ - /* if the client is going to be closed, do nothing */ - if (client_is_expired(client)) - return; - - while (buflen > 0 && !client_is_expired(client)) { - size_t copylen; - - assert(client->send_buf_used < sizeof(client->send_buf)); - - copylen = sizeof(client->send_buf) - client->send_buf_used; - if (copylen > buflen) - copylen = buflen; - - memcpy(client->send_buf + client->send_buf_used, buffer, - copylen); - buflen -= copylen; - client->send_buf_used += copylen; - buffer += copylen; - if (client->send_buf_used >= sizeof(client->send_buf)) - client_write_output(client); - } -} - -void client_puts(struct client *client, const char *s) -{ - client_write(client, s, strlen(s)); -} - -void client_vprintf(struct client *client, const char *fmt, va_list args) -{ - va_list tmp; - int length; - char *buffer; - - va_copy(tmp, args); - length = vsnprintf(NULL, 0, fmt, tmp); - va_end(tmp); - - if (length <= 0) - /* wtf.. */ - return; - - buffer = g_malloc(length + 1); - vsnprintf(buffer, length + 1, fmt, args); - client_write(client, buffer, length); - g_free(buffer); -} - -G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...) -{ - va_list args; - - va_start(args, fmt); - client_vprintf(client, fmt, args); - va_end(args); -} - -/** - * Send "idle" response to this client. - */ -static void -client_idle_notify(struct client *client) -{ - unsigned flags, i; - const char *const* idle_names; - - assert(client->idle_waiting); - assert(client->idle_flags != 0); - - flags = client->idle_flags; - client->idle_flags = 0; - client->idle_waiting = false; - - idle_names = idle_get_names(); - for (i = 0; idle_names[i]; ++i) { - if (flags & (1 << i) & client->idle_subscriptions) - client_printf(client, "changed: %s\n", - idle_names[i]); - } - - client_puts(client, "OK\n"); - g_timer_start(client->last_activity); -} - -static void -client_idle_callback(gpointer data, gpointer user_data) -{ - struct client *client = data; - unsigned flags = GPOINTER_TO_UINT(user_data); - - if (client_is_expired(client)) - return; - - client->idle_flags |= flags; - if (client->idle_waiting - && (client->idle_flags & client->idle_subscriptions)) { - client_idle_notify(client); - client_write_output(client); - } -} - -void client_manager_idle_add(unsigned flags) -{ - assert(flags != 0); - - g_list_foreach(clients, client_idle_callback, GUINT_TO_POINTER(flags)); -} - -bool client_idle_wait(struct client *client, unsigned flags) -{ - assert(!client->idle_waiting); - - client->idle_waiting = true; - client->idle_subscriptions = flags; - - if (client->idle_flags & client->idle_subscriptions) { - client_idle_notify(client); - return true; - } else - return false; -} diff --git a/src/client.h b/src/client.h index 824497aba..d46747b4f 100644 --- a/src/client.h +++ b/src/client.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/client_event.c b/src/client_event.c new file mode 100644 index 000000000..93f5a9df7 --- /dev/null +++ b/src/client_event.c @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "client_internal.h" +#include "main.h" + +#include <assert.h> + +static gboolean +client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, + gpointer data) +{ + struct client *client = data; + + assert(!client_is_expired(client)); + + if (condition != G_IO_OUT) { + client_set_expired(client); + return false; + } + + client_write_deferred(client); + + if (client_is_expired(client)) { + client_close(client); + return false; + } + + g_timer_start(client->last_activity); + + if (g_queue_is_empty(client->deferred_send)) { + /* done sending deferred buffers exist: schedule + read */ + client->source_id = g_io_add_watch(client->channel, + G_IO_IN|G_IO_ERR|G_IO_HUP, + client_in_event, client); + return false; + } + + /* write more */ + return true; +} + +gboolean +client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, + gpointer data) +{ + struct client *client = data; + enum command_return ret; + + assert(!client_is_expired(client)); + + if (condition != G_IO_IN) { + client_set_expired(client); + return false; + } + + g_timer_start(client->last_activity); + + ret = client_read(client); + switch (ret) { + case COMMAND_RETURN_OK: + case COMMAND_RETURN_ERROR: + break; + + case COMMAND_RETURN_KILL: + client_close(client); + g_main_loop_quit(main_loop); + return false; + + case COMMAND_RETURN_CLOSE: + client_close(client); + return false; + } + + if (client_is_expired(client)) { + client_close(client); + return false; + } + + if (!g_queue_is_empty(client->deferred_send)) { + /* deferred buffers exist: schedule write */ + client->source_id = g_io_add_watch(client->channel, + G_IO_OUT|G_IO_ERR|G_IO_HUP, + client_out_event, client); + return false; + } + + /* read more */ + return true; +} diff --git a/src/client_expire.c b/src/client_expire.c new file mode 100644 index 000000000..a5b0be047 --- /dev/null +++ b/src/client_expire.c @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "client_internal.h" + +static guint expire_source_id; + +void +client_set_expired(struct client *client) +{ + if (!client_is_expired(client)) + client_schedule_expire(); + + if (client->source_id != 0) { + g_source_remove(client->source_id); + client->source_id = 0; + } + + if (client->channel != NULL) { + g_io_channel_unref(client->channel); + client->channel = NULL; + } +} + +static void +client_check_expired_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) +{ + struct client *client = data; + + if (client_is_expired(client)) { + g_debug("[%u] expired", client->num); + client_close(client); + } else if (!client->idle_waiting && /* idle clients + never expire */ + (int)g_timer_elapsed(client->last_activity, NULL) > + client_timeout) { + g_debug("[%u] timeout", client->num); + client_close(client); + } +} + +static void +client_manager_expire(void) +{ + client_list_foreach(client_check_expired_callback, NULL); +} + +/** + * An idle event which calls client_manager_expire(). + */ +static gboolean +client_manager_expire_event(G_GNUC_UNUSED gpointer data) +{ + expire_source_id = 0; + client_manager_expire(); + return false; +} + +void +client_schedule_expire(void) +{ + if (expire_source_id == 0) + /* delayed deletion */ + expire_source_id = g_idle_add(client_manager_expire_event, + NULL); +} + +void +client_deinit_expire(void) +{ + if (expire_source_id != 0) + g_source_remove(expire_source_id); +} diff --git a/src/client_global.c b/src/client_global.c new file mode 100644 index 000000000..fc5adedba --- /dev/null +++ b/src/client_global.c @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "client_internal.h" +#include "conf.h" + +#include <assert.h> + +#define CLIENT_TIMEOUT_DEFAULT (60) +#define CLIENT_MAX_CONNECTIONS_DEFAULT (10) +#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024) +#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024) + +/* set this to zero to indicate we have no possible clients */ +unsigned int client_max_connections; +int client_timeout; +size_t client_max_command_list_size; +size_t client_max_output_buffer_size; + +void client_manager_init(void) +{ + client_timeout = config_get_positive(CONF_CONN_TIMEOUT, + CLIENT_TIMEOUT_DEFAULT); + client_max_connections = + config_get_positive(CONF_MAX_CONN, + CLIENT_MAX_CONNECTIONS_DEFAULT); + client_max_command_list_size = + config_get_positive(CONF_MAX_COMMAND_LIST_SIZE, + CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024) + * 1024; + + client_max_output_buffer_size = + config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE, + CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024) + * 1024; +} + +static void client_close_all(void) +{ + while (!client_list_is_empty()) { + struct client *client = client_list_get_first(); + + client_close(client); + } + + assert(client_list_is_empty()); +} + +void client_manager_deinit(void) +{ + client_close_all(); + + client_max_connections = 0; + + client_deinit_expire(); +} diff --git a/src/client_idle.c b/src/client_idle.c new file mode 100644 index 000000000..10be4d430 --- /dev/null +++ b/src/client_idle.c @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "client_internal.h" +#include "idle.h" + +#include <assert.h> + +/** + * Send "idle" response to this client. + */ +static void +client_idle_notify(struct client *client) +{ + unsigned flags, i; + const char *const* idle_names; + + assert(client->idle_waiting); + assert(client->idle_flags != 0); + + flags = client->idle_flags; + client->idle_flags = 0; + client->idle_waiting = false; + + idle_names = idle_get_names(); + for (i = 0; idle_names[i]; ++i) { + if (flags & (1 << i) & client->idle_subscriptions) + client_printf(client, "changed: %s\n", + idle_names[i]); + } + + client_puts(client, "OK\n"); + g_timer_start(client->last_activity); +} + +static void +client_idle_callback(gpointer data, gpointer user_data) +{ + struct client *client = data; + unsigned flags = GPOINTER_TO_UINT(user_data); + + if (client_is_expired(client)) + return; + + client->idle_flags |= flags; + if (client->idle_waiting + && (client->idle_flags & client->idle_subscriptions)) { + client_idle_notify(client); + client_write_output(client); + } +} + +void client_manager_idle_add(unsigned flags) +{ + assert(flags != 0); + + client_list_foreach(client_idle_callback, GUINT_TO_POINTER(flags)); +} + +bool client_idle_wait(struct client *client, unsigned flags) +{ + assert(!client->idle_waiting); + + client->idle_waiting = true; + client->idle_subscriptions = flags; + + if (client->idle_flags & client->idle_subscriptions) { + client_idle_notify(client); + return true; + } else + return false; +} diff --git a/src/client_internal.h b/src/client_internal.h new file mode 100644 index 000000000..2b1b92433 --- /dev/null +++ b/src/client_internal.h @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CLIENT_INTERNAL_H +#define MPD_CLIENT_INTERNAL_H + +#include "client.h" +#include "command.h" + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "client" + +struct deferred_buffer { + size_t size; + char data[sizeof(long)]; +}; + +struct client { + GIOChannel *channel; + guint source_id; + + /** the buffer for reading lines from the #channel */ + struct fifo_buffer *input; + + unsigned permission; + + /** the uid of the client process, or -1 if unknown */ + int uid; + + /** + * How long since the last activity from this client? + */ + GTimer *last_activity; + + GSList *cmd_list; /* for when in list mode */ + int cmd_list_OK; /* print OK after each command execution */ + size_t cmd_list_size; /* mem cmd_list consumes */ + GQueue *deferred_send; /* for output if client is slow */ + size_t deferred_bytes; /* mem deferred_send consumes */ + unsigned int num; /* client number */ + + char send_buf[16384]; + size_t send_buf_used; /* bytes used this instance */ + + /** is this client waiting for an "idle" response? */ + bool idle_waiting; + + /** idle flags pending on this client, to be sent as soon as + the client enters "idle" */ + unsigned idle_flags; + + /** idle flags that the client wants to receive */ + unsigned idle_subscriptions; +}; + +extern unsigned int client_max_connections; +extern int client_timeout; +extern size_t client_max_command_list_size; +extern size_t client_max_output_buffer_size; + +bool +client_list_is_empty(void); + +bool +client_list_is_full(void); + +struct client * +client_list_get_first(void); + +void +client_list_add(struct client *client); + +void +client_list_foreach(GFunc func, gpointer user_data); + +void +client_list_remove(struct client *client); + +void +client_close(struct client *client); + +static inline void +new_cmd_list_ptr(struct client *client, const char *s) +{ + client->cmd_list = g_slist_prepend(client->cmd_list, g_strdup(s)); +} + +static inline void +free_cmd_list(GSList *list) +{ + for (GSList *tmp = list; tmp != NULL; tmp = g_slist_next(tmp)) + g_free(tmp->data); + + g_slist_free(list); +} + +void +client_set_expired(struct client *client); + +/** + * Schedule an "expired" check for all clients: permanently delete + * clients which have been set "expired" with client_set_expired(). + */ +void +client_schedule_expire(void); + +/** + * Removes a scheduled "expired" check. + */ +void +client_deinit_expire(void); + +enum command_return +client_read(struct client *client); + +enum command_return +client_process_line(struct client *client, char *line); + +void +client_write_deferred(struct client *client); + +void +client_write_output(struct client *client); + +gboolean +client_in_event(GIOChannel *source, GIOCondition condition, + gpointer data); + +#endif diff --git a/src/client_list.c b/src/client_list.c new file mode 100644 index 000000000..5332ed65f --- /dev/null +++ b/src/client_list.c @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "client_internal.h" + +#include <assert.h> + +static GList *clients; +static unsigned num_clients; + +bool +client_list_is_empty(void) +{ + return num_clients == 0; +} + +bool +client_list_is_full(void) +{ + return num_clients >= client_max_connections; +} + +struct client * +client_list_get_first(void) +{ + assert(clients != NULL); + + return clients->data; +} + +void +client_list_add(struct client *client) +{ + clients = g_list_prepend(clients, client); + ++num_clients; +} + +void +client_list_foreach(GFunc func, gpointer user_data) +{ + g_list_foreach(clients, func, user_data); +} + +void +client_list_remove(struct client *client) +{ + assert(num_clients > 0); + assert(clients != NULL); + + clients = g_list_remove(clients, client); + --num_clients; +} diff --git a/src/client_new.c b/src/client_new.c new file mode 100644 index 000000000..781a36524 --- /dev/null +++ b/src/client_new.c @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "client_internal.h" +#include "fifo_buffer.h" +#include "socket_util.h" +#include "permission.h" + +#include <assert.h> +#include <sys/types.h> +#ifdef WIN32 +#include <winsock2.h> +#else +#include <sys/socket.h> +#endif +#include <unistd.h> + +#ifdef HAVE_LIBWRAP +#include <tcpd.h> +#endif + + +#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO + +static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n"; + +void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid) +{ + static unsigned int next_client_num; + struct client *client; + char *remote; + + assert(fd >= 0); + +#ifdef HAVE_LIBWRAP + if (sa->sa_family != AF_UNIX) { + char *hostaddr = sockaddr_to_string(sa, sa_length, NULL); + const char *progname = g_get_prgname(); + + struct request_info req; + request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0); + + fromhost(&req); + + if (!hosts_access(&req)) { + /* tcp wrappers says no */ + g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE, + "libwrap refused connection (libwrap=%s) from %s", + progname, hostaddr); + + g_free(hostaddr); + close(fd); + return; + } + + g_free(hostaddr); + } +#endif /* HAVE_WRAP */ + + if (client_list_is_full()) { + g_warning("Max Connections Reached!"); + close(fd); + return; + } + + client = g_new0(struct client, 1); + +#ifndef G_OS_WIN32 + client->channel = g_io_channel_unix_new(fd); +#else + client->channel = g_io_channel_win32_new_socket(fd); +#endif + /* GLib is responsible for closing the file descriptor */ + g_io_channel_set_close_on_unref(client->channel, true); + /* NULL encoding means the stream is binary safe; the MPD + protocol is UTF-8 only, but we are doing this call anyway + to prevent GLib from messing around with the stream */ + g_io_channel_set_encoding(client->channel, NULL, NULL); + /* we prefer to do buffering */ + g_io_channel_set_buffered(client->channel, false); + + client->source_id = g_io_add_watch(client->channel, + G_IO_IN|G_IO_ERR|G_IO_HUP, + client_in_event, client); + + client->input = fifo_buffer_new(4096); + + client->permission = getDefaultPermissions(); + client->uid = uid; + + client->last_activity = g_timer_new(); + + client->cmd_list = NULL; + client->cmd_list_OK = -1; + client->cmd_list_size = 0; + + client->deferred_send = g_queue_new(); + client->deferred_bytes = 0; + client->num = next_client_num++; + + client->send_buf_used = 0; + + (void)send(fd, GREETING, sizeof(GREETING) - 1, 0); + + client_list_add(client); + + remote = sockaddr_to_string(sa, sa_length, NULL); + g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE, + "[%u] opened from %s", client->num, remote); + g_free(remote); +} + +static void +deferred_buffer_free(gpointer data, G_GNUC_UNUSED gpointer user_data) +{ + struct deferred_buffer *buffer = data; + g_free(buffer); +} + +void +client_close(struct client *client) +{ + client_list_remove(client); + + client_set_expired(client); + + g_timer_destroy(client->last_activity); + + if (client->cmd_list) { + free_cmd_list(client->cmd_list); + client->cmd_list = NULL; + } + + g_queue_foreach(client->deferred_send, deferred_buffer_free, NULL); + g_queue_free(client->deferred_send); + + fifo_buffer_free(client->input); + + g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE, + "[%u] closed", client->num); + g_free(client); +} diff --git a/src/client_process.c b/src/client_process.c new file mode 100644 index 000000000..aeb75bb57 --- /dev/null +++ b/src/client_process.c @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "client_internal.h" + +#include <string.h> + +#define CLIENT_LIST_MODE_BEGIN "command_list_begin" +#define CLIENT_LIST_OK_MODE_BEGIN "command_list_ok_begin" +#define CLIENT_LIST_MODE_END "command_list_end" + +static enum command_return +client_process_command_list(struct client *client, bool list_ok, GSList *list) +{ + enum command_return ret = COMMAND_RETURN_OK; + unsigned num = 0; + + for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) { + char *cmd = cur->data; + + g_debug("command_process_list: process command \"%s\"", + cmd); + ret = command_process(client, num++, cmd); + g_debug("command_process_list: command returned %i", ret); + if (ret != COMMAND_RETURN_OK || client_is_expired(client)) + break; + else if (list_ok) + client_puts(client, "list_OK\n"); + } + + return ret; +} + +enum command_return +client_process_line(struct client *client, char *line) +{ + enum command_return ret; + + if (strcmp(line, "noidle") == 0) { + if (client->idle_waiting) { + /* send empty idle response and leave idle mode */ + client->idle_waiting = false; + command_success(client); + client_write_output(client); + } + + /* do nothing if the client wasn't idling: the client + has already received the full idle response from + client_idle_notify(), which he can now evaluate */ + + return COMMAND_RETURN_OK; + } else if (client->idle_waiting) { + /* during idle mode, clients must not send anything + except "noidle" */ + g_warning("[%u] command \"%s\" during idle", + client->num, line); + return COMMAND_RETURN_CLOSE; + } + + if (client->cmd_list_OK >= 0) { + if (strcmp(line, CLIENT_LIST_MODE_END) == 0) { + g_debug("[%u] process command list", + client->num); + + /* for scalability reasons, we have prepended + each new command; now we have to reverse it + to restore the correct order */ + client->cmd_list = g_slist_reverse(client->cmd_list); + + ret = client_process_command_list(client, + client->cmd_list_OK, + client->cmd_list); + g_debug("[%u] process command " + "list returned %i", client->num, ret); + + if (ret == COMMAND_RETURN_CLOSE || + client_is_expired(client)) + return COMMAND_RETURN_CLOSE; + + if (ret == COMMAND_RETURN_OK) + command_success(client); + + client_write_output(client); + free_cmd_list(client->cmd_list); + client->cmd_list = NULL; + client->cmd_list_OK = -1; + } else { + size_t len = strlen(line) + 1; + client->cmd_list_size += len; + if (client->cmd_list_size > + client_max_command_list_size) { + g_warning("[%u] command list size (%lu) " + "is larger than the max (%lu)", + client->num, + (unsigned long)client->cmd_list_size, + (unsigned long)client_max_command_list_size); + return COMMAND_RETURN_CLOSE; + } + + new_cmd_list_ptr(client, line); + ret = COMMAND_RETURN_OK; + } + } else { + if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) { + client->cmd_list_OK = 0; + ret = COMMAND_RETURN_OK; + } else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) { + client->cmd_list_OK = 1; + ret = COMMAND_RETURN_OK; + } else { + g_debug("[%u] process command \"%s\"", + client->num, line); + ret = command_process(client, 0, line); + g_debug("[%u] command returned %i", + client->num, ret); + + if (ret == COMMAND_RETURN_CLOSE || + client_is_expired(client)) + return COMMAND_RETURN_CLOSE; + + if (ret == COMMAND_RETURN_OK) + command_success(client); + + client_write_output(client); + } + } + + return ret; +} diff --git a/src/client_read.c b/src/client_read.c new file mode 100644 index 000000000..7a6bd3d5e --- /dev/null +++ b/src/client_read.c @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "client_internal.h" +#include "fifo_buffer.h" + +#include <assert.h> +#include <string.h> + +static char * +client_read_line(struct client *client) +{ + const char *p, *newline; + size_t length; + char *line; + + p = fifo_buffer_read(client->input, &length); + if (p == NULL) + return NULL; + + newline = memchr(p, '\n', length); + if (newline == NULL) + return NULL; + + line = g_strndup(p, newline - p); + fifo_buffer_consume(client->input, newline - p + 1); + + return g_strchomp(line); +} + +static enum command_return +client_input_received(struct client *client, size_t bytesRead) +{ + char *line; + + fifo_buffer_append(client->input, bytesRead); + + /* process all lines */ + + while ((line = client_read_line(client)) != NULL) { + enum command_return ret = client_process_line(client, line); + g_free(line); + + if (ret == COMMAND_RETURN_KILL || + ret == COMMAND_RETURN_CLOSE) + return ret; + if (client_is_expired(client)) + return COMMAND_RETURN_CLOSE; + } + + return COMMAND_RETURN_OK; +} + +enum command_return +client_read(struct client *client) +{ + char *p; + size_t max_length; + GError *error = NULL; + GIOStatus status; + gsize bytes_read; + + assert(client != NULL); + assert(client->channel != NULL); + + p = fifo_buffer_write(client->input, &max_length); + if (p == NULL) { + g_warning("[%u] buffer overflow", client->num); + return COMMAND_RETURN_CLOSE; + } + + status = g_io_channel_read_chars(client->channel, p, max_length, + &bytes_read, &error); + switch (status) { + case G_IO_STATUS_NORMAL: + return client_input_received(client, bytes_read); + + case G_IO_STATUS_AGAIN: + /* try again later, after select() */ + return COMMAND_RETURN_OK; + + case G_IO_STATUS_EOF: + /* peer disconnected */ + return COMMAND_RETURN_CLOSE; + + case G_IO_STATUS_ERROR: + /* I/O error */ + g_warning("failed to read from client %d: %s", + client->num, error->message); + g_error_free(error); + return COMMAND_RETURN_CLOSE; + } + + /* unreachable */ + return COMMAND_RETURN_CLOSE; +} diff --git a/src/client_write.c b/src/client_write.c new file mode 100644 index 000000000..543cdbb6c --- /dev/null +++ b/src/client_write.c @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "client_internal.h" + +#include <assert.h> +#include <string.h> +#include <stdio.h> + +static size_t +client_write_deferred_buffer(struct client *client, + const struct deferred_buffer *buffer) +{ + GError *error = NULL; + GIOStatus status; + gsize bytes_written; + + assert(client != NULL); + assert(client->channel != NULL); + assert(buffer != NULL); + + status = g_io_channel_write_chars + (client->channel, buffer->data, buffer->size, + &bytes_written, &error); + switch (status) { + case G_IO_STATUS_NORMAL: + return bytes_written; + + case G_IO_STATUS_AGAIN: + return 0; + + case G_IO_STATUS_EOF: + /* client has disconnected */ + + client_set_expired(client); + return 0; + + case G_IO_STATUS_ERROR: + /* I/O error */ + + client_set_expired(client); + g_warning("failed to flush buffer for %i: %s", + client->num, error->message); + g_error_free(error); + return 0; + } + + /* unreachable */ + return 0; +} + +void +client_write_deferred(struct client *client) +{ + size_t ret; + + while (!g_queue_is_empty(client->deferred_send)) { + struct deferred_buffer *buf = + g_queue_peek_head(client->deferred_send); + + assert(buf->size > 0); + assert(buf->size <= client->deferred_bytes); + + ret = client_write_deferred_buffer(client, buf); + if (ret == 0) + break; + + if (ret < buf->size) { + assert(client->deferred_bytes >= (size_t)ret); + client->deferred_bytes -= ret; + buf->size -= ret; + memmove(buf->data, buf->data + ret, buf->size); + break; + } else { + size_t decr = sizeof(*buf) - + sizeof(buf->data) + buf->size; + + assert(client->deferred_bytes >= decr); + client->deferred_bytes -= decr; + g_free(buf); + g_queue_pop_head(client->deferred_send); + } + + g_timer_start(client->last_activity); + } + + if (g_queue_is_empty(client->deferred_send)) { + g_debug("[%u] buffer empty %lu", client->num, + (unsigned long)client->deferred_bytes); + assert(client->deferred_bytes == 0); + } +} + +static void client_defer_output(struct client *client, + const void *data, size_t length) +{ + size_t alloc; + struct deferred_buffer *buf; + + assert(length > 0); + + alloc = sizeof(*buf) - sizeof(buf->data) + length; + client->deferred_bytes += alloc; + if (client->deferred_bytes > client_max_output_buffer_size) { + g_warning("[%u] output buffer size (%lu) is " + "larger than the max (%lu)", + client->num, + (unsigned long)client->deferred_bytes, + (unsigned long)client_max_output_buffer_size); + /* cause client to close */ + client_set_expired(client); + return; + } + + buf = g_malloc(alloc); + buf->size = length; + memcpy(buf->data, data, length); + + g_queue_push_tail(client->deferred_send, buf); +} + +static void client_write_direct(struct client *client, + const char *data, size_t length) +{ + GError *error = NULL; + GIOStatus status; + gsize bytes_written; + + assert(client != NULL); + assert(client->channel != NULL); + assert(data != NULL); + assert(length > 0); + assert(g_queue_is_empty(client->deferred_send)); + + status = g_io_channel_write_chars(client->channel, data, length, + &bytes_written, &error); + switch (status) { + case G_IO_STATUS_NORMAL: + case G_IO_STATUS_AGAIN: + break; + + case G_IO_STATUS_EOF: + /* client has disconnected */ + + client_set_expired(client); + return; + + case G_IO_STATUS_ERROR: + /* I/O error */ + + client_set_expired(client); + g_warning("failed to write to %i: %s", + client->num, error->message); + g_error_free(error); + return; + } + + if (bytes_written < length) + client_defer_output(client, data + bytes_written, + length - bytes_written); + + if (!g_queue_is_empty(client->deferred_send)) + g_debug("[%u] buffer created", client->num); +} + +void +client_write_output(struct client *client) +{ + if (client_is_expired(client) || !client->send_buf_used) + return; + + if (!g_queue_is_empty(client->deferred_send)) { + client_defer_output(client, client->send_buf, + client->send_buf_used); + + if (client_is_expired(client)) + return; + + /* try to flush the deferred buffers now; the current + server command may take too long to finish, and + meanwhile try to feed output to the client, + otherwise it will time out. One reason why + deferring is slow might be that currently each + client_write() allocates a new deferred buffer. + This should be optimized after MPD 0.14. */ + client_write_deferred(client); + } else + client_write_direct(client, client->send_buf, + client->send_buf_used); + + client->send_buf_used = 0; +} + +/** + * Write a block of data to the client. + */ +static void client_write(struct client *client, const char *buffer, size_t buflen) +{ + /* if the client is going to be closed, do nothing */ + if (client_is_expired(client)) + return; + + while (buflen > 0 && !client_is_expired(client)) { + size_t copylen; + + assert(client->send_buf_used < sizeof(client->send_buf)); + + copylen = sizeof(client->send_buf) - client->send_buf_used; + if (copylen > buflen) + copylen = buflen; + + memcpy(client->send_buf + client->send_buf_used, buffer, + copylen); + buflen -= copylen; + client->send_buf_used += copylen; + buffer += copylen; + if (client->send_buf_used >= sizeof(client->send_buf)) + client_write_output(client); + } +} + +void client_puts(struct client *client, const char *s) +{ + client_write(client, s, strlen(s)); +} + +void client_vprintf(struct client *client, const char *fmt, va_list args) +{ +#ifndef G_OS_WIN32 + va_list tmp; + int length; + char *buffer; + + va_copy(tmp, args); + length = vsnprintf(NULL, 0, fmt, tmp); + va_end(tmp); + + if (length <= 0) + /* wtf.. */ + return; + + buffer = g_malloc(length + 1); + vsnprintf(buffer, length + 1, fmt, args); + client_write(client, buffer, length); + g_free(buffer); +#else + /* On mingw32, snprintf() expects a 64 bit integer instead of + a "long int" for "%li". This is not consistent with our + expectation, so we're using plain sprintf() here, hoping + the static buffer is large enough. Sorry for this hack, + but WIN32 development is so painful, I'm not in the mood to + do it properly now. */ + + static char buffer[4096]; + vsprintf(buffer, fmt, args); + client_write(client, buffer, strlen(buffer)); +#endif +} + +G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + client_vprintf(client, fmt, args); + va_end(args); +} diff --git a/src/cmdline.c b/src/cmdline.c index e0274ef36..2c1db890b 100644 --- a/src/cmdline.c +++ b/src/cmdline.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,14 +17,20 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "cmdline.h" #include "path.h" #include "log.h" #include "conf.h" #include "decoder_list.h" -#include "config.h" +#include "decoder_plugin.h" #include "output_list.h" #include "ls.h" +#include "mpd_error.h" + +#ifdef ENABLE_ENCODER +#include "encoder_list.h" +#endif #ifdef ENABLE_ARCHIVE #include "archive_list.h" @@ -35,9 +41,37 @@ #include <stdio.h> #include <stdlib.h> -#define SYSTEM_CONFIG_FILE_LOCATION "/etc/mpd.conf" +#ifdef G_OS_WIN32 +#define CONFIG_FILE_LOCATION "\\mpd\\mpd.conf" +#else /* G_OS_WIN32 */ #define USER_CONFIG_FILE_LOCATION1 ".mpdconf" #define USER_CONFIG_FILE_LOCATION2 ".mpd/mpd.conf" +#endif + +static GQuark +cmdline_quark(void) +{ + return g_quark_from_static_string("cmdline"); +} + +static void +print_all_decoders(FILE *fp) +{ + for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) { + const struct decoder_plugin *plugin = decoder_plugins[i]; + const char *const*suffixes; + + fprintf(fp, "[%s]", plugin->name); + + for (suffixes = plugin->suffixes; + suffixes != NULL && *suffixes != NULL; + ++suffixes) { + fprintf(fp, " %s", *suffixes); + } + + fprintf(fp, "\n"); + } +} G_GNUC_NORETURN static void version(void) @@ -45,19 +79,25 @@ static void version(void) puts(PACKAGE " (MPD: Music Player Daemon) " VERSION " \n" "\n" "Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n" - "Copyright (C) 2008 Max Kellermann <max@duempel.org>\n" + "Copyright (C) 2008-2010 Max Kellermann <max@duempel.org>\n" "This is free software; see the source for copying conditions. There is NO\n" "warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" "\n" "Supported decoders:\n"); - decoder_plugin_init_all(); - decoder_plugin_print_all_decoders(stdout); + print_all_decoders(stdout); puts("\n" "Supported outputs:\n"); audio_output_plugin_print_all_types(stdout); +#ifdef ENABLE_ENCODER + puts("\n" + "Supported encoders:\n"); + encoder_plugin_print_all_types(stdout); +#endif + + #ifdef ENABLE_ARCHIVE puts("\n" "Supported archives:\n"); @@ -72,31 +112,29 @@ static void version(void) exit(EXIT_SUCCESS); } -#if GLIB_CHECK_VERSION(2,12,0) static const char *summary = "Music Player Daemon - a daemon for playing music."; -#endif -void parseOptions(int argc, char **argv, Options *options) +bool +parse_cmdline(int argc, char **argv, struct options *options, + GError **error_r) { GError *error = NULL; GOptionContext *context; bool ret; static gboolean option_version, - option_create_db, option_no_create_db, option_no_daemon, + option_no_daemon, option_no_config; const GOptionEntry entries[] = { - { "create-db", 0, 0, G_OPTION_ARG_NONE, &option_create_db, - "force (re)creation of database", NULL }, { "kill", 0, 0, G_OPTION_ARG_NONE, &options->kill, "kill the currently running mpd session", NULL }, { "no-config", 0, 0, G_OPTION_ARG_NONE, &option_no_config, "don't read from config", NULL }, - { "no-create-db", 0, 0, G_OPTION_ARG_NONE, &option_no_create_db, - "don't create database, even if it doesn't exist", NULL }, { "no-daemon", 0, 0, G_OPTION_ARG_NONE, &option_no_daemon, "don't detach from console", NULL }, - { "stdout", 0, 0, G_OPTION_ARG_NONE, &options->stdOutput, + { "stdout", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr, + NULL, NULL }, + { "stderr", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr, "print messages to stderr", NULL }, { "verbose", 'v', 0, G_OPTION_ARG_NONE, &options->verbose, "verbose logging", NULL }, @@ -107,24 +145,19 @@ void parseOptions(int argc, char **argv, Options *options) options->kill = false; options->daemon = true; - options->stdOutput = false; + options->log_stderr = false; options->verbose = false; - options->createDB = 0; context = g_option_context_new("[path/to/mpd.conf]"); g_option_context_add_main_entries(context, entries, NULL); -#if GLIB_CHECK_VERSION(2,12,0) g_option_context_set_summary(context, summary); -#endif ret = g_option_context_parse(context, &argc, &argv, &error); g_option_context_free(context); - if (!ret) { - g_error("option parsing failed: %s\n", error->message); - exit(1); - } + if (!ret) + MPD_ERROR("option parsing failed: %s\n", error->message); if (option_version) version(); @@ -133,39 +166,71 @@ void parseOptions(int argc, char **argv, Options *options) parser can use it already */ log_early_init(options->verbose); - if (option_create_db && option_no_create_db) - g_error("Cannot use both --create-db and --no-create-db\n"); - - if (option_no_create_db) - options->createDB = -1; - else if (option_create_db) - options->createDB = 1; - options->daemon = !option_no_daemon; if (option_no_config) { g_debug("Ignoring config, using daemon defaults\n"); + return true; } else if (argc <= 1) { /* default configuration file path */ char *path1; - char *path2; +#ifdef G_OS_WIN32 + path1 = g_build_filename(g_get_user_config_dir(), + CONFIG_FILE_LOCATION, NULL); + if (g_file_test(path1, G_FILE_TEST_IS_REGULAR)) + ret = config_read_file(path1, error_r); + else { + int i = 0; + char *system_path = NULL; + const char * const *system_config_dirs; + + system_config_dirs = g_get_system_config_dirs(); + + while(system_config_dirs[i] != NULL) { + system_path = g_build_filename(system_config_dirs[i], + CONFIG_FILE_LOCATION, + NULL); + if(g_file_test(system_path, + G_FILE_TEST_IS_REGULAR)) { + ret = config_read_file(system_path,error_r); + g_free(system_path); + g_free(&system_config_dirs); + break; + } + ++i;; + } + g_free(system_path); + g_free(&system_config_dirs); + } +#else /* G_OS_WIN32 */ + char *path2; path1 = g_build_filename(g_get_home_dir(), USER_CONFIG_FILE_LOCATION1, NULL); path2 = g_build_filename(g_get_home_dir(), USER_CONFIG_FILE_LOCATION2, NULL); if (g_file_test(path1, G_FILE_TEST_IS_REGULAR)) - config_read_file(path1); + ret = config_read_file(path1, error_r); else if (g_file_test(path2, G_FILE_TEST_IS_REGULAR)) - config_read_file(path2); + ret = config_read_file(path2, error_r); else if (g_file_test(SYSTEM_CONFIG_FILE_LOCATION, G_FILE_TEST_IS_REGULAR)) - config_read_file(SYSTEM_CONFIG_FILE_LOCATION); + ret = config_read_file(SYSTEM_CONFIG_FILE_LOCATION, + error_r); +#endif + g_free(path1); +#ifndef G_OS_WIN32 g_free(path2); +#endif + + return ret; } else if (argc == 2) { /* specified configuration file */ - config_read_file(argv[1]); - } else - g_error("too many arguments"); + return config_read_file(argv[1], error_r); + } else { + g_set_error(error_r, cmdline_quark(), 0, + "too many arguments"); + return false; + } } diff --git a/src/cmdline.h b/src/cmdline.h index 673701055..b7af63c5a 100644 --- a/src/cmdline.h +++ b/src/cmdline.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,14 +22,17 @@ #include <glib.h> -typedef struct _Options { +#include <stdbool.h> + +struct options { gboolean kill; gboolean daemon; - gboolean stdOutput; + gboolean log_stderr; gboolean verbose; - int createDB; -} Options; +}; -void parseOptions(int argc, char **argv, Options *options); +bool +parse_cmdline(int argc, char **argv, struct options *options, + GError **error_r); #endif diff --git a/src/command.c b/src/command.c index d30b63594..df0146c16 100644 --- a/src/command.c +++ b/src/command.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,14 +17,17 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "command.h" #include "player_control.h" #include "playlist.h" #include "playlist_print.h" #include "playlist_save.h" +#include "playlist_queue.h" #include "queue_print.h" #include "ls.h" #include "uri.h" +#include "decoder_print.h" #include "directory.h" #include "directory_print.h" #include "database.h" @@ -32,7 +35,7 @@ #include "volume.h" #include "stats.h" #include "permission.h" -#include "buffer2array.h" +#include "tokenizer.h" #include "stored_playlist.h" #include "ack.h" #include "output_command.h" @@ -43,8 +46,8 @@ #include "client.h" #include "tag_print.h" #include "path.h" +#include "replay_gain_config.h" #include "idle.h" -#include "config.h" #ifdef ENABLE_SQLITE #include "sticker.h" @@ -58,7 +61,6 @@ #include <stdlib.h> #include <errno.h> -#define COMMAND_STATUS_VOLUME "volume" #define COMMAND_STATUS_STATE "state" #define COMMAND_STATUS_REPEAT "repeat" #define COMMAND_STATUS_SINGLE "single" @@ -74,6 +76,8 @@ #define COMMAND_STATUS_BITRATE "bitrate" #define COMMAND_STATUS_ERROR "error" #define COMMAND_STATUS_CROSSFADE "xfade" +#define COMMAND_STATUS_MIXRAMPDB "mixrampdb" +#define COMMAND_STATUS_MIXRAMPDELAY "mixrampdelay" #define COMMAND_STATUS_AUDIO "audio" #define COMMAND_STATUS_UPDATING_DB "updating_db" @@ -166,8 +170,8 @@ check_int(struct client *client, int *value_r, return false; } -#if LONG_MAX > INT_MAX - if (value < INT_MIN || value > INT_MAX) { +#if G_MAXLONG > G_MAXINT + if (value < G_MININT || value > G_MAXINT) { command_error(client, ACK_ERROR_ARG, "Number too large: %s", s); return false; @@ -198,7 +202,7 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2, /* compatibility with older MPD versions: specifying "-1" makes MPD display the whole list */ *value_r1 = 0; - *value_r2 = UINT_MAX; + *value_r2 = G_MAXUINT; return true; } @@ -208,8 +212,8 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2, return false; } -#if LONG_MAX > UINT_MAX - if (value > UINT_MAX) { +#if G_MAXLONG > G_MAXUINT + if (value > G_MAXUINT) { command_error(client, ACK_ERROR_ARG, "Number too large: %s", s); return false; @@ -220,7 +224,7 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2, if (*test == ':') { value = strtol(++test, &test2, 10); - if (*test2 != '\0' || test == test2) { + if (*test2 != '\0') { va_list args; va_start(args, fmt); command_error_v(client, ACK_ERROR_ARG, fmt, args); @@ -228,14 +232,17 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2, return false; } + if (test == test2) + value = G_MAXUINT; + if (value < 0) { command_error(client, ACK_ERROR_ARG, "Number is negative: %s", s); return false; } -#if LONG_MAX > UINT_MAX - if (value > UINT_MAX) { +#if G_MAXLONG > G_MAXUINT + if (value > G_MAXUINT) { command_error(client, ACK_ERROR_ARG, "Number too large: %s", s); return false; @@ -262,7 +269,7 @@ check_unsigned(struct client *client, unsigned *value_r, const char *s) return false; } - if (value > UINT_MAX) { + if (value > G_MAXUINT) { command_error(client, ACK_ERROR_ARG, "Number too large: %s", s); return false; @@ -289,6 +296,23 @@ check_bool(struct client *client, bool *value_r, const char *s) return true; } +static bool +check_float(struct client *client, float *value_r, const char *s) +{ + float value; + char *endptr; + + value = strtof(s, &endptr); + if (*endptr != 0 && endptr == s) { + command_error(client, ACK_ERROR_ARG, + "Float expected: %s", s); + return false; + } + + *value_r = value; + return true; +} + static enum command_return print_playlist_result(struct client *client, enum playlist_result result) @@ -363,10 +387,12 @@ print_spl_list(struct client *client, GPtrArray *list) client_printf(client, "playlist: %s\n", playlist->name); t = playlist->mtime; - strftime(timestamp, sizeof(timestamp), "%FT%TZ", -#ifdef WIN32 + strftime(timestamp, sizeof(timestamp), +#ifdef G_OS_WIN32 + "%Y-%m-%dT%H:%M:%SZ", gmtime(&t) #else + "%FT%TZ", gmtime_r(&t, &tm) #endif ); @@ -385,6 +411,14 @@ handle_urlhandlers(struct client *client, } static enum command_return +handle_decoders(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + decoder_list_print(client); + return COMMAND_RETURN_OK; +} + +static enum command_return handle_tagtypes(struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { @@ -400,7 +434,7 @@ handle_play(struct client *client, int argc, char *argv[]) if (argc == 2 && !check_int(client, &song, argv[1], need_positive)) return COMMAND_RETURN_ERROR; - result = playPlaylist(&g_playlist, song); + result = playlist_play(&g_playlist, song); return print_playlist_result(client, result); } @@ -413,7 +447,7 @@ handle_playid(struct client *client, int argc, char *argv[]) if (argc == 2 && !check_int(client, &id, argv[1], need_positive)) return COMMAND_RETURN_ERROR; - result = playPlaylistById(&g_playlist, id); + result = playlist_play_id(&g_playlist, id); return print_playlist_result(client, result); } @@ -421,7 +455,7 @@ static enum command_return handle_stop(G_GNUC_UNUSED struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - stopPlaylist(&g_playlist); + playlist_stop(&g_playlist); return COMMAND_RETURN_OK; } @@ -441,11 +475,11 @@ handle_pause(struct client *client, bool pause_flag; if (!check_bool(client, &pause_flag, argv[1])) return COMMAND_RETURN_ERROR; - playerSetPause(pause_flag); - return COMMAND_RETURN_OK; - } - playerPause(); + pc_set_pause(pause_flag); + } else + pc_pause(); + return COMMAND_RETURN_OK; } @@ -454,10 +488,14 @@ handle_status(struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { const char *state = NULL; + struct player_status player_status; int updateJobId; + char *error; int song; - switch (getPlayerState()) { + pc_get_status(&player_status); + + switch (player_status.state) { case PLAYER_STATE_STOP: state = "stop"; break; @@ -470,7 +508,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" @@ -478,34 +516,43 @@ handle_status(struct client *client, COMMAND_STATUS_PLAYLIST ": %li\n" COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n" COMMAND_STATUS_CROSSFADE ": %i\n" + COMMAND_STATUS_MIXRAMPDB ": %f\n" + COMMAND_STATUS_MIXRAMPDELAY ": %f\n" COMMAND_STATUS_STATE ": %s\n", volume_level_get(), - getPlaylistRepeatStatus(&g_playlist), - getPlaylistRandomStatus(&g_playlist), - getPlaylistSingleStatus(&g_playlist), - getPlaylistConsumeStatus(&g_playlist), - getPlaylistVersion(&g_playlist), - getPlaylistLength(&g_playlist), - (int)(getPlayerCrossFade() + 0.5), + playlist_get_repeat(&g_playlist), + playlist_get_random(&g_playlist), + playlist_get_single(&g_playlist), + playlist_get_consume(&g_playlist), + playlist_get_version(&g_playlist), + playlist_get_length(&g_playlist), + (int)(pc_get_cross_fade() + 0.5), + pc_get_mixramp_db(), + pc_get_mixramp_delay(), state); - song = getPlaylistCurrentSong(&g_playlist); + song = playlist_get_current_song(&g_playlist); if (song >= 0) { client_printf(client, COMMAND_STATUS_SONG ": %i\n" COMMAND_STATUS_SONGID ": %u\n", - song, getPlaylistSongId(&g_playlist, song)); + song, playlist_get_song_id(&g_playlist, song)); } - if (getPlayerState() != PLAYER_STATE_STOP) { - const struct audio_format *af = player_get_audio_format(); + if (player_status.state != PLAYER_STATE_STOP) { + struct audio_format_string af_string; + client_printf(client, COMMAND_STATUS_TIME ": %i:%i\n" - COMMAND_STATUS_BITRATE ": %li\n" - COMMAND_STATUS_AUDIO ": %u:%u:%u\n", - getPlayerElapsedTime(), getPlayerTotalTime(), - getPlayerBitRate(), - af->sample_rate, af->bits, af->channels); + "elapsed: %1.3f\n" + COMMAND_STATUS_BITRATE ": %u\n" + COMMAND_STATUS_AUDIO ": %s\n", + (int)(player_status.elapsed_time + 0.5), + (int)(player_status.total_time + 0.5), + player_status.elapsed_time, + player_status.bit_rate, + audio_format_to_string(&player_status.audio_format, + &af_string)); } if ((updateJobId = isUpdatingDB())) { @@ -514,18 +561,20 @@ handle_status(struct client *client, updateJobId); } - if (getPlayerError() != PLAYER_ERROR_NOERROR) { + error = pc_get_error_message(); + if (error != NULL) { client_printf(client, COMMAND_STATUS_ERROR ": %s\n", - getPlayerErrorStr()); + error); + g_free(error); } - song = getPlaylistNextSong(&g_playlist); + song = playlist_get_next_song(&g_playlist); if (song >= 0) { client_printf(client, COMMAND_STATUS_NEXTSONG ": %i\n" COMMAND_STATUS_NEXTSONGID ": %u\n", - song, getPlaylistSongId(&g_playlist, song)); + song, playlist_get_song_id(&g_playlist, song)); } return COMMAND_RETURN_OK; @@ -569,7 +618,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 +654,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 +664,11 @@ handle_addid(struct client *client, int argc, char *argv[]) int to; if (!check_int(client, &to, argv[2], check_integer, argv[2])) return COMMAND_RETURN_ERROR; - result = moveSongInPlaylistById(&g_playlist, added_id, to); + result = playlist_move_id(&g_playlist, added_id, to); if (result != PLAYLIST_RESULT_SUCCESS) { enum command_return ret = print_playlist_result(client, result); - deleteFromPlaylistById(&g_playlist, added_id); + playlist_delete_id(&g_playlist, added_id); return ret; } } @@ -631,13 +680,13 @@ handle_addid(struct client *client, int argc, char *argv[]) static enum command_return handle_delete(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - int song; + unsigned start, end; enum playlist_result result; - if (!check_int(client, &song, argv[1], need_positive)) + if (!check_range(client, &start, &end, argv[1], need_range)) return COMMAND_RETURN_ERROR; - result = deleteFromPlaylist(&g_playlist, song); + result = playlist_delete_range(&g_playlist, start, end); return print_playlist_result(client, result); } @@ -650,7 +699,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 +720,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 +728,7 @@ static enum command_return handle_clear(G_GNUC_UNUSED struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - clearPlaylist(&g_playlist); + playlist_clear(&g_playlist); return COMMAND_RETURN_OK; } @@ -698,6 +747,10 @@ handle_load(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { enum playlist_result result; + result = playlist_open_into_queue(argv[1], &g_playlist); + if (result != PLAYLIST_RESULT_NO_SUCH_LIST) + return result; + result = playlist_load_spl(&g_playlist, argv[1]); return print_playlist_result(client, result); } @@ -705,6 +758,9 @@ handle_load(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) static enum command_return handle_listplaylist(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { + if (playlist_file_print(client, argv[1], false)) + return COMMAND_RETURN_OK; + bool ret; ret = spl_print(client, argv[1], false); @@ -720,6 +776,9 @@ static enum command_return handle_listplaylistinfo(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { + if (playlist_file_print(client, argv[1], true)) + return COMMAND_RETURN_OK; + bool ret; ret = spl_print(client, argv[1], true); @@ -808,7 +867,7 @@ handle_plchangesposid(struct client *client, G_GNUC_UNUSED int argc, char *argv[ static enum command_return handle_playlistinfo(struct client *client, int argc, char *argv[]) { - unsigned start = 0, end = UINT_MAX; + unsigned start = 0, end = G_MAXUINT; bool ret; if (argc == 2 && !check_range(client, &start, &end, @@ -837,7 +896,7 @@ handle_playlistid(struct client *client, int argc, char *argv[]) return print_playlist_result(client, PLAYLIST_RESULT_NO_SUCH_SONG); } else { - playlist_print_info(client, &g_playlist, 0, UINT_MAX); + playlist_print_info(client, &g_playlist, 0, G_MAXUINT); } return COMMAND_RETURN_OK; @@ -869,6 +928,30 @@ handle_find(struct client *client, int argc, char *argv[]) } static enum command_return +handle_findadd(struct client *client, int argc, char *argv[]) +{ + int ret; + struct locate_item_list *list = + locate_item_list_parse(argv + 1, argc - 1); + if (list == NULL || list->length == 0) { + if (list != NULL) + locate_item_list_free(list); + + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + ret = findAddIn(client, NULL, list); + if (ret == -1) + command_error(client, ACK_ERROR_NO_EXIST, + "directory or file not found"); + + locate_item_list_free(list); + + return ret; +} + +static enum command_return handle_search(struct client *client, int argc, char *argv[]) { int ret; @@ -993,14 +1076,52 @@ handle_playlistmove(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) static enum command_return handle_update(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - char *path = NULL; + const char *path = NULL; unsigned ret; assert(argc <= 2); - if (argc == 2) - path = g_strdup(argv[1]); + if (argc == 2) { + path = argv[1]; + + if (*path == 0 || strcmp(path, "/") == 0) + /* backwards compatibility with MPD 0.15 */ + path = NULL; + else if (!uri_safe_local(path)) { + command_error(client, ACK_ERROR_ARG, + "Malformed path"); + return COMMAND_RETURN_ERROR; + } + } + + ret = update_enqueue(path, false); + if (ret > 0) { + client_printf(client, "updating_db: %i\n", ret); + return COMMAND_RETURN_OK; + } else { + command_error(client, ACK_ERROR_UPDATE_ALREADY, + "already updating"); + return COMMAND_RETURN_ERROR; + } +} + +static enum command_return +handle_rescan(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + const char *path = NULL; + unsigned ret; - ret = directory_update_init(path); + assert(argc <= 2); + if (argc == 2) { + path = argv[1]; + + if (!uri_safe_local(path)) { + command_error(client, ACK_ERROR_ARG, + "Malformed path"); + return COMMAND_RETURN_ERROR; + } + } + + ret = update_enqueue(path, true); if (ret > 0) { client_printf(client, "updating_db: %i\n", ret); return COMMAND_RETURN_OK; @@ -1020,7 +1141,7 @@ handle_next(G_GNUC_UNUSED struct client *client, int single = g_playlist.queue.single; g_playlist.queue.single = false; - nextSongInPlaylist(&g_playlist); + playlist_next(&g_playlist); g_playlist.queue.single = single; return COMMAND_RETURN_OK; @@ -1030,7 +1151,7 @@ static enum command_return handle_previous(G_GNUC_UNUSED struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - previousSongInPlaylist(&g_playlist); + playlist_previous(&g_playlist); return COMMAND_RETURN_OK; } @@ -1052,25 +1173,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 +1181,12 @@ handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (!check_int(client, &level, argv[1], need_integer)) return COMMAND_RETURN_ERROR; - success = volume_level_change(level, 0); + if (level < 0 || level > 100) { + command_error(client, ACK_ERROR_ARG, "Invalid volume value"); + return COMMAND_RETURN_ERROR; + } + + success = volume_level_change(level); if (!success) { command_error(client, ACK_ERROR_SYSTEM, "problems setting volume"); @@ -1103,7 +1210,7 @@ handle_repeat(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - setPlaylistRepeatStatus(&g_playlist, status); + playlist_set_repeat(&g_playlist, status); return COMMAND_RETURN_OK; } @@ -1121,7 +1228,7 @@ handle_single(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - setPlaylistSingleStatus(&g_playlist, status); + playlist_set_single(&g_playlist, status); return COMMAND_RETURN_OK; } @@ -1139,7 +1246,7 @@ handle_consume(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - setPlaylistConsumeStatus(&g_playlist, status); + playlist_set_consume(&g_playlist, status); return COMMAND_RETURN_OK; } @@ -1157,7 +1264,7 @@ handle_random(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - setPlaylistRandomStatus(&g_playlist, status); + playlist_set_random(&g_playlist, status); return COMMAND_RETURN_OK; } @@ -1172,7 +1279,7 @@ static enum command_return handle_clearerror(G_GNUC_UNUSED struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - clearPlayerError(); + pc_clear_error(); return COMMAND_RETURN_OK; } @@ -1196,17 +1303,17 @@ handle_list(struct client *client, int argc, char *argv[]) /* for compatibility with < 0.12.0 */ if (argc == 3) { - if (tagType != TAG_ITEM_ALBUM) { + if (tagType != TAG_ALBUM) { command_error(client, ACK_ERROR_ARG, "should be \"%s\" for 3 arguments", - tag_item_names[TAG_ITEM_ALBUM]); + tag_item_names[TAG_ALBUM]); return COMMAND_RETURN_ERROR; } locate_item_list_parse(argv + 1, argc - 1); conditionals = locate_item_list_new(1); - conditionals->items[0].tag = TAG_ITEM_ARTIST; + conditionals->items[0].tag = TAG_ARTIST; conditionals->items[0].needle = g_strdup(argv[2]); } else { conditionals = @@ -1241,7 +1348,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 +1362,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 +1376,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 +1390,7 @@ handle_swapid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; if (!check_int(client, &id2, argv[2], check_integer, argv[2])) return COMMAND_RETURN_ERROR; - result = swapSongsInPlaylistById(&g_playlist, id1, id2); + result = playlist_swap_songs_id(&g_playlist, id1, id2); return print_playlist_result(client, result); } @@ -1298,7 +1405,7 @@ handle_seek(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (!check_int(client, &seek_time, argv[2], check_integer, argv[2])) return COMMAND_RETURN_ERROR; - result = seekSongInPlaylist(&g_playlist, song, seek_time); + result = playlist_seek_song(&g_playlist, song, seek_time); return print_playlist_result(client, result); } @@ -1313,7 +1420,7 @@ handle_seekid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (!check_int(client, &seek_time, argv[2], check_integer, argv[2])) return COMMAND_RETURN_ERROR; - result = seekSongInPlaylistById(&g_playlist, id, seek_time); + result = playlist_seek_song_id(&g_playlist, id, seek_time); return print_playlist_result(client, result); } @@ -1363,7 +1470,31 @@ handle_crossfade(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (!check_unsigned(client, &xfade_time, argv[1])) return COMMAND_RETURN_ERROR; - setPlayerCrossFade(xfade_time); + pc_set_cross_fade(xfade_time); + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_mixrampdb(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + float db; + + if (!check_float(client, &db, argv[1])) + return COMMAND_RETURN_ERROR; + pc_set_mixramp_db(db); + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_mixrampdelay(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + float delay_secs; + + if (!check_float(client, &delay_secs, argv[1])) + return COMMAND_RETURN_ERROR; + pc_set_mixramp_delay(delay_secs); return COMMAND_RETURN_OK; } @@ -1477,6 +1608,28 @@ handle_listplaylists(struct client *client, } static enum command_return +handle_replay_gain_mode(struct client *client, + G_GNUC_UNUSED int argc, char *argv[]) +{ + if (!replay_gain_set_mode_string(argv[1])) { + command_error(client, ACK_ERROR_ARG, + "Unrecognized replay gain mode"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_replay_gain_status(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + client_printf(client, "replay_gain_mode: %s\n", + replay_gain_get_mode_string()); + return COMMAND_RETURN_OK; +} + +static enum command_return handle_idle(struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { @@ -1519,13 +1672,14 @@ sticker_song_find_print_cb(struct song *song, const char *value, { struct sticker_song_find_data *data = user_data; - song_print_url(data->client, song); + song_print_uri(data->client, song); sticker_print_value(data->client, data->name, value); } static enum command_return handle_sticker_song(struct client *client, int argc, char *argv[]) { + /* get song song_id key */ if (argc == 5 && strcmp(argv[1], "get") == 0) { struct song *song; char *value; @@ -1548,6 +1702,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[]) g_free(value); return COMMAND_RETURN_OK; + /* list song song_id */ } else if (argc == 4 && strcmp(argv[1], "list") == 0) { struct song *song; struct sticker *sticker; @@ -1570,6 +1725,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[]) sticker_free(sticker); return COMMAND_RETURN_OK; + /* set song song_id id key */ } else if (argc == 6 && strcmp(argv[1], "set") == 0) { struct song *song; bool ret; @@ -1589,6 +1745,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[]) } return COMMAND_RETURN_OK; + /* delete song song_id [key] */ } else if ((argc == 4 || argc == 5) && strcmp(argv[1], "delete") == 0) { struct song *song; @@ -1611,6 +1768,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[]) } return COMMAND_RETURN_OK; + /* find song dir key */ } else if (argc == 5 && strcmp(argv[1], "find") == 0) { /* "sticker find song a/directory name" */ struct directory *directory; @@ -1679,11 +1837,13 @@ static const struct command commands[] = { { "count", PERMISSION_READ, 2, -1, handle_count }, { "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade }, { "currentsong", PERMISSION_READ, 0, 0, handle_currentsong }, + { "decoders", PERMISSION_READ, 0, 0, handle_decoders }, { "delete", PERMISSION_CONTROL, 1, 1, handle_delete }, { "deleteid", PERMISSION_CONTROL, 1, 1, handle_deleteid }, { "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput }, { "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput }, { "find", PERMISSION_READ, 2, -1, handle_find }, + { "findadd", PERMISSION_READ, 2, -1, handle_findadd}, { "idle", PERMISSION_READ, 0, -1, handle_idle }, { "kill", PERMISSION_ADMIN, -1, -1, handle_kill }, { "list", PERMISSION_READ, 1, -1, handle_list }, @@ -1694,6 +1854,8 @@ static const struct command commands[] = { { "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists }, { "load", PERMISSION_ADD, 1, 1, handle_load }, { "lsinfo", PERMISSION_READ, 0, 1, handle_lsinfo }, + { "mixrampdb", PERMISSION_CONTROL, 1, 1, handle_mixrampdb }, + { "mixrampdelay", PERMISSION_CONTROL, 1, 1, handle_mixrampdelay }, { "move", PERMISSION_CONTROL, 2, 2, handle_move }, { "moveid", PERMISSION_CONTROL, 2, 2, handle_moveid }, { "next", PERMISSION_CONTROL, 0, 0, handle_next }, @@ -1719,6 +1881,11 @@ static const struct command commands[] = { { "random", PERMISSION_CONTROL, 1, 1, handle_random }, { "rename", PERMISSION_CONTROL, 2, 2, handle_rename }, { "repeat", PERMISSION_CONTROL, 1, 1, handle_repeat }, + { "replay_gain_mode", PERMISSION_CONTROL, 1, 1, + handle_replay_gain_mode }, + { "replay_gain_status", PERMISSION_READ, 0, 0, + handle_replay_gain_status }, + { "rescan", PERMISSION_ADMIN, 0, 1, handle_rescan }, { "rm", PERMISSION_CONTROL, 1, 1, handle_rm }, { "save", PERMISSION_CONTROL, 1, 1, handle_save }, { "search", PERMISSION_READ, 2, -1, handle_search }, @@ -1738,7 +1905,6 @@ static const struct command commands[] = { { "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes }, { "update", PERMISSION_ADMIN, 0, 1, handle_update }, { "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers }, - { "volume", PERMISSION_CONTROL, 1, 1, handle_volume }, }; static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]); @@ -1892,48 +2058,71 @@ command_checked_lookup(struct client *client, unsigned permission, } enum command_return -command_process(struct client *client, char *commandString) +command_process(struct client *client, unsigned num, char *line) { + GError *error = NULL; int argc; char *argv[COMMAND_ARGV_MAX] = { NULL }; const struct command *cmd; enum command_return ret = COMMAND_RETURN_ERROR; - if (!(argc = buffer2array(commandString, argv, COMMAND_ARGV_MAX))) - return COMMAND_RETURN_OK; + command_list_num = num; - cmd = command_checked_lookup(client, client_get_permission(client), - argc, argv); - if (cmd) - ret = cmd->handler(client, argc, argv); + /* get the command name (first word on the line) */ - current_command = NULL; + argv[0] = tokenizer_next_word(&line, &error); + if (argv[0] == NULL) { + current_command = ""; + if (*line == 0) + command_error(client, ACK_ERROR_UNKNOWN, + "No command given"); + else { + command_error(client, ACK_ERROR_UNKNOWN, + "%s", error->message); + g_error_free(error); + } + current_command = NULL; - return ret; -} + return COMMAND_RETURN_ERROR; + } -enum command_return -command_process_list(struct client *client, - bool list_ok, GSList *list) -{ - enum command_return ret = COMMAND_RETURN_OK; + argc = 1; - command_list_num = 0; + /* now parse the arguments (quoted or unquoted) */ + + while (argc < (int)G_N_ELEMENTS(argv) && + (argv[argc] = + tokenizer_next_param(&line, &error)) != NULL) + ++argc; + + /* some error checks; we have to set current_command because + command_error() expects it to be set */ - for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) { - char *cmd = cur->data; + current_command = argv[0]; - g_debug("command_process_list: process command \"%s\"", - cmd); - ret = command_process(client, cmd); - g_debug("command_process_list: command returned %i", ret); - if (ret != COMMAND_RETURN_OK || client_is_expired(client)) - break; - else if (list_ok) - client_puts(client, "list_OK\n"); - command_list_num++; + if (argc >= (int)G_N_ELEMENTS(argv)) { + command_error(client, ACK_ERROR_ARG, "Too many arguments"); + current_command = NULL; + return COMMAND_RETURN_ERROR; + } + + if (*line != 0) { + command_error(client, ACK_ERROR_ARG, + "%s", error->message); + current_command = NULL; + g_error_free(error); + return COMMAND_RETURN_ERROR; } + /* look up and invoke the command handler */ + + cmd = command_checked_lookup(client, client_get_permission(client), + argc, argv); + if (cmd) + ret = cmd->handler(client, argc, argv); + + current_command = NULL; command_list_num = 0; + return ret; } diff --git a/src/command.h b/src/command.h index a7c408ed7..39389385d 100644 --- a/src/command.h +++ b/src/command.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -39,11 +39,7 @@ void command_init(void); void command_finish(void); enum command_return -command_process_list(struct client *client, - bool list_ok, GSList *list); - -enum command_return -command_process(struct client *client, char *commandString); +command_process(struct client *client, unsigned num, char *line); void command_success(struct client *client); diff --git a/src/compress.c b/src/compress.c deleted file mode 100644 index 3a0b4beb0..000000000 --- a/src/compress.c +++ /dev/null @@ -1,410 +0,0 @@ -/* - * Copyright (C) 2003-2009 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Imported from AudioCompress by J. Shagam <fluffy@beesbuzz.biz> - */ - -#include "compress.h" - -#include <glib.h> - -#include <stdint.h> -#include <string.h> - -#ifdef USE_X -#include <X11/Xlib.h> -#include <X11/Xutil.h> - -static Display *display; -static Window window; -static Visual *visual; -static int screen; -static GC blackGC, whiteGC, blueGC, yellowGC, dkyellowGC, redGC; -#endif - -static int *peaks; -static int gainCurrent, gainTarget; - -static struct { - int show_mon; - int anticlip; - int target; - int gainmax; - int gainsmooth; - unsigned buckets; -} prefs; - -#ifdef USE_X -static int mon_init; -#endif - -void CompressCfg(int show_mon, int anticlip, int target, int gainmax, - int gainsmooth, unsigned buckets) -{ - static unsigned lastsize; - - prefs.show_mon = show_mon; - prefs.anticlip = anticlip; - prefs.target = target; - prefs.gainmax = gainmax; - prefs.gainsmooth = gainsmooth; - prefs.buckets = buckets; - - /* Allocate the peak structure */ - peaks = g_realloc(peaks, sizeof(int)*prefs.buckets); - - if (prefs.buckets > lastsize) - memset(peaks + lastsize, 0, sizeof(int)*(prefs.buckets - - lastsize)); - lastsize = prefs.buckets; - -#ifdef USE_X - /* Configure the monitor window if needed */ - if (show_mon && !mon_init) - { - display = XOpenDisplay(getenv("DISPLAY")); - - /* We really shouldn't try to init X if there's no X */ - if (!display) - { - fprintf(stderr, - "X not detected; disabling monitor window\n"); - show_mon = prefs.show_mon = 0; - } - } - - if (show_mon && !mon_init) - { - XGCValues gcv; - XColor col; - - gainCurrent = gainTarget = (1 << GAINSHIFT); - - - - screen = DefaultScreen(display); - visual = DefaultVisual(display, screen); - window = XCreateSimpleWindow(display, - RootWindow(display, screen), - 0, 0, prefs.buckets, 128 + 8, 0, - BlackPixel(display, screen), - WhitePixel(display, screen)); - XStoreName(display, window, "AudioCompress monitor"); - - gcv.foreground = BlackPixel(display, screen); - blackGC = XCreateGC(display, window, GCForeground, &gcv); - gcv.foreground = WhitePixel(display, screen); - whiteGC = XCreateGC(display, window, GCForeground, &gcv); - col.red = 0; - col.green = 0; - col.blue = 65535; - XAllocColor(display, DefaultColormap(display, screen), &col); - gcv.foreground = col.pixel; - blueGC = XCreateGC(display, window, GCForeground, &gcv); - col.red = 65535; - col.green = 65535; - col.blue = 0; - XAllocColor(display, DefaultColormap(display, screen), &col); - gcv.foreground = col.pixel; - yellowGC = XCreateGC(display, window, GCForeground, &gcv); - col.red = 32767; - col.green = 32767; - col.blue = 0; - XAllocColor(display, DefaultColormap(display, screen), &col); - gcv.foreground = col.pixel; - dkyellowGC = XCreateGC(display, window, GCForeground, &gcv); - col.red = 65535; - col.green = 0; - col.blue = 0; - XAllocColor(display, DefaultColormap(display, screen), &col); - gcv.foreground = col.pixel; - redGC = XCreateGC(display, window, GCForeground, &gcv); - mon_init = 1; - } - - if (mon_init) - { - if (show_mon) - XMapWindow(display, window); - else - XUnmapWindow(display, window); - XResizeWindow(display, window, prefs.buckets, 128 + 8); - XFlush(display); - } -#endif -} - -void CompressFree(void) -{ -#ifdef USE_X - if (mon_init) - { - XFreeGC(display, blackGC); - XFreeGC(display, whiteGC); - XFreeGC(display, blueGC); - XFreeGC(display, yellowGC); - XFreeGC(display, dkyellowGC); - XFreeGC(display, redGC); - XDestroyWindow(display, window); - XCloseDisplay(display); - } -#endif - - g_free(peaks); -} - -void CompressDo(void *data, unsigned int length) -{ - int16_t *audio = (int16_t *)data, *ap; - int peak; - unsigned int i, pos; - int gr, gf, gn; - static int pn = -1; -#ifdef STATS - static int clip; -#endif - static int clipped; - - if (!peaks) - return; - - if (pn == -1) - { - for (i = 0; i < prefs.buckets; i++) - peaks[i] = 0; - } - pn = (pn + 1)%prefs.buckets; - -#ifdef DEBUG - fprintf(stderr, "modifyNative16(0x%08x, %d)\n",(unsigned int)data, - length); -#endif - - /* Determine peak's value and position */ - peak = 1; - pos = 0; - -#ifdef DEBUG - fprintf(stderr, "finding peak(b=%d)\n", pn); -#endif - - ap = audio; - for (i = 0; i < length/2; i++) - { - int val = *ap; - if (val > peak) - { - peak = val; - pos = i; - } else if (-val > peak) - { - peak = -val; - pos = i; - } - ap++; - } - peaks[pn] = peak; - - /* Only draw if needed, of course */ -#ifdef USE_X - if (prefs.show_mon) - { - /* current amplitude */ - XDrawLine(display, window, whiteGC, - pn, 0, - pn, - 127 - - (peaks[pn]*gainCurrent >> (GAINSHIFT + 8))); - - /* amplification */ - XDrawLine(display, window, yellowGC, - pn, - 127 - (peaks[pn]*gainCurrent - >> (GAINSHIFT + 8)), - pn, 127); - - /* peak */ - XDrawLine(display, window, blackGC, - pn, 127 - (peaks[pn] >> 8), pn, 127); - - /* clip indicator */ - if (clipped) - XDrawLine(display, window, redGC, - (pn + prefs.buckets - 1)%prefs.buckets, - 126 - clipped/(length*512), - (pn + prefs.buckets - 1)%prefs.buckets, - 127); - clipped = 0; - - /* target line */ - /* XDrawPoint(display, window, redGC, */ - /* pn, 127 - TARGET/256); */ - /* amplification edge */ - XDrawLine(display, window, dkyellowGC, - pn, - 127 - (peaks[pn]*gainCurrent - >> (GAINSHIFT + 8)), - pn - 1, - 127 - - (peaks[(pn + prefs.buckets - - 1)%prefs.buckets]*gainCurrent - >> (GAINSHIFT + 8))); - } -#endif - - for (i = 0; i < prefs.buckets; i++) - { - if (peaks[i] > peak) - { - peak = peaks[i]; - pos = 0; - } - } - - /* Determine target gain */ - gn = (1 << GAINSHIFT)*prefs.target/peak; - - if (gn <(1 << GAINSHIFT)) - gn = 1 << GAINSHIFT; - - gainTarget = (gainTarget *((1 << prefs.gainsmooth) - 1) + gn) - >> prefs.gainsmooth; - - /* Give it an extra insignifigant nudge to counteract possible - ** rounding error - */ - - if (gn < gainTarget) - gainTarget--; - else if (gn > gainTarget) - gainTarget++; - - if (gainTarget > prefs.gainmax << GAINSHIFT) - gainTarget = prefs.gainmax << GAINSHIFT; - - -#ifdef USE_X - if (prefs.show_mon) - { - int x; - - /* peak*gain */ - XDrawPoint(display, window, redGC, - pn, - 127 - (peak*gainCurrent - >> (GAINSHIFT + 8))); - - /* gain indicator */ - XFillRectangle(display, window, whiteGC, 0, 128, - prefs.buckets, 8); - x = (gainTarget - (1 << GAINSHIFT))*prefs.buckets - / ((prefs.gainmax - 1) << GAINSHIFT); - XDrawLine(display, window, redGC, x, - 128, x, 128 + 8); - - x = (gn - (1 << GAINSHIFT))*prefs.buckets - / ((prefs.gainmax - 1) << GAINSHIFT); - - XDrawLine(display, window, blackGC, - x, 132 - 1, - x, 132 + 1); - - /* blue peak line */ - XDrawLine(display, window, blueGC, - 0, 127 - (peak >> 8), prefs.buckets, - 127 - (peak >> 8)); - XFlush(display); - XDrawLine(display, window, whiteGC, - 0, 127 - (peak >> 8), prefs.buckets, - 127 - (peak >> 8)); - } -#endif - - /* See if a peak is going to clip */ - gn = (1 << GAINSHIFT)*32768/peak; - - if (gn < gainTarget) - { - gainTarget = gn; - - if (prefs.anticlip) - pos = 0; - - } else - { - /* We're ramping up, so draw it out over the whole frame */ - pos = length; - } - - /* Determine gain rate necessary to make target */ - if (!pos) - pos = 1; - - gr = ((gainTarget - gainCurrent) << 16)/(int)pos; - - /* Do the shiznit */ - gf = gainCurrent << 16; - -#ifdef STATS - fprintf(stderr, "\rgain = %2.2f%+.2e ", - gainCurrent*1.0/(1 << GAINSHIFT), - (gainTarget - gainCurrent)*1.0/(1 << GAINSHIFT)); -#endif - - ap = audio; - for (i = 0; i < length/2; i++) - { - int sample; - - /* Interpolate the gain */ - gainCurrent = gf >> 16; - if (i < pos) - gf += gr; - else if (i == pos) - gf = gainTarget << 16; - - /* Amplify */ - sample = (*ap)*gainCurrent >> GAINSHIFT; - if (sample < -32768) - { -#ifdef STATS - clip++; -#endif - clipped += -32768 - sample; - sample = -32768; - } else if (sample > 32767) - { -#ifdef STATS - clip++; -#endif - clipped += sample - 32767; - sample = 32767; - } - *ap++ = sample; - } -#ifdef STATS - fprintf(stderr, "clip %d b%-3d ", clip, pn); -#endif - -#ifdef DEBUG - fprintf(stderr, "\ndone\n"); -#endif -} - diff --git a/src/compress.h b/src/compress.h deleted file mode 100644 index 3e3afb565..000000000 --- a/src/compress.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2003-2009 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Imported from AudioCompress by J. Shagam <fluffy@beesbuzz.biz> - */ - -#ifndef MPD_COMPRESS_H -#define MPD_COMPRESS_H - -/* These are copied from the AudioCompress config.h, mainly because CompressDo - * needs GAINSHIFT defined. The rest are here so they can be used as defaults - * to pass to CompressCfg(). -- jat */ -#define ANTICLIP 0 /* Strict clipping protection */ -#define TARGET 25000 /* Target level */ -#define GAINMAX 32 /* The maximum amount to amplify by */ -#define GAINSHIFT 10 /* How fine-grained the gain is */ -#define GAINSMOOTH 8 /* How much inertia ramping has*/ -#define BUCKETS 400 /* How long of a history to store */ - -void CompressCfg(int monitor, - int anticlip, - int target, - int maxgain, - int smooth, - unsigned buckets); - -void CompressDo(void *data, unsigned int numSamples); - -void CompressFree(void); - -#endif diff --git a/src/conf.c b/src/conf.c index cce7dbf27..705942085 100644 --- a/src/conf.c +++ b/src/conf.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "conf.h" #include "utils.h" -#include "buffer2array.h" +#include "tokenizer.h" #include "path.h" +#include "glib_compat.h" +#include "mpd_error.h" #include <glib.h> @@ -36,37 +39,84 @@ #define MAX_STRING_SIZE MPD_PATH_MAX+80 #define CONF_COMMENT '#' -#define CONF_BLOCK_BEGIN "{" -#define CONF_BLOCK_END "}" - -#define CONF_REPEATABLE_MASK 0x01 -#define CONF_BLOCK_MASK 0x02 -#define CONF_LINE_TOKEN_MAX 3 struct config_entry { - const char *name; - unsigned char mask; + const char *const name; + const bool repeatable; + const bool block; GSList *params; }; -static GSList *config_entries; +static struct config_entry config_entries[] = { + { .name = CONF_MUSIC_DIR, false, false }, + { .name = CONF_PLAYLIST_DIR, false, false }, + { .name = CONF_FOLLOW_INSIDE_SYMLINKS, false, false }, + { .name = CONF_FOLLOW_OUTSIDE_SYMLINKS, false, false }, + { .name = CONF_DB_FILE, false, false }, + { .name = CONF_STICKER_FILE, false, false }, + { .name = CONF_LOG_FILE, false, false }, + { .name = CONF_PID_FILE, false, false }, + { .name = CONF_STATE_FILE, false, false }, + { .name = CONF_USER, false, false }, + { .name = CONF_GROUP, false, false }, + { .name = CONF_BIND_TO_ADDRESS, true, false }, + { .name = CONF_PORT, false, false }, + { .name = CONF_LOG_LEVEL, false, false }, + { .name = CONF_ZEROCONF_NAME, false, false }, + { .name = CONF_ZEROCONF_ENABLED, false, false }, + { .name = CONF_PASSWORD, true, false }, + { .name = CONF_DEFAULT_PERMS, false, false }, + { .name = CONF_AUDIO_OUTPUT, true, true }, + { .name = CONF_AUDIO_OUTPUT_FORMAT, false, false }, + { .name = CONF_MIXER_TYPE, false, false }, + { .name = CONF_REPLAYGAIN, false, false }, + { .name = CONF_REPLAYGAIN_PREAMP, false, false }, + { .name = CONF_REPLAYGAIN_MISSING_PREAMP, false, false }, + { .name = CONF_REPLAYGAIN_LIMIT, false, false }, + { .name = CONF_VOLUME_NORMALIZATION, false, false }, + { .name = CONF_SAMPLERATE_CONVERTER, false, false }, + { .name = CONF_AUDIO_BUFFER_SIZE, false, false }, + { .name = CONF_BUFFER_BEFORE_PLAY, false, false }, + { .name = CONF_HTTP_PROXY_HOST, false, false }, + { .name = CONF_HTTP_PROXY_PORT, false, false }, + { .name = CONF_HTTP_PROXY_USER, false, false }, + { .name = CONF_HTTP_PROXY_PASSWORD, false, false }, + { .name = CONF_CONN_TIMEOUT, false, false }, + { .name = CONF_MAX_CONN, false, false }, + { .name = CONF_MAX_PLAYLIST_LENGTH, false, false }, + { .name = CONF_MAX_COMMAND_LIST_SIZE, false, false }, + { .name = CONF_MAX_OUTPUT_BUFFER_SIZE, false, false }, + { .name = CONF_FS_CHARSET, false, false }, + { .name = CONF_ID3V1_ENCODING, false, false }, + { .name = CONF_METADATA_TO_USE, false, false }, + { .name = CONF_SAVE_ABSOLUTE_PATHS, false, false }, + { .name = CONF_DECODER, true, true }, + { .name = CONF_INPUT, true, true }, + { .name = CONF_GAPLESS_MP3_PLAYBACK, false, false }, + { .name = CONF_PLAYLIST_PLUGIN, true, true }, + { .name = CONF_AUTO_UPDATE, false, false }, + { .name = CONF_AUTO_UPDATE_DEPTH, false, false }, + { .name = "filter", true, true }, +}; -static int get_bool(const char *value) +static bool +get_bool(const char *value, bool *value_r) { - const char **x; static const char *t[] = { "yes", "true", "1", NULL }; static const char *f[] = { "no", "false", "0", NULL }; - for (x = t; *x; x++) { - if (!g_ascii_strcasecmp(*x, value)) - return 1; + if (string_array_contains(t, value)) { + *value_r = true; + return true; } - for (x = f; *x; x++) { - if (!g_ascii_strcasecmp(*x, value)) - return 0; + + if (string_array_contains(f, value)) { + *value_r = false; + return true; } - return CONF_BOOL_INVALID; + + return false; } struct config_param * @@ -83,15 +133,14 @@ config_new_param(const char *value, int line) ret->num_block_params = 0; ret->block_params = NULL; + ret->used = false; return ret; } static void -config_param_free(gpointer data, G_GNUC_UNUSED gpointer user_data) +config_param_free(struct config_param *param) { - struct config_param *param = data; - g_free(param->value); for (unsigned i = 0; i < param->num_block_params; i++) { @@ -105,42 +154,19 @@ config_param_free(gpointer data, G_GNUC_UNUSED gpointer user_data) g_free(param); } -static struct config_entry * -newConfigEntry(const char *name, int repeatable, int block) -{ - struct config_entry *ret = g_new(struct config_entry, 1); - - ret->name = name; - ret->mask = 0; - ret->params = NULL; - - if (repeatable) - ret->mask |= CONF_REPEATABLE_MASK; - if (block) - ret->mask |= CONF_BLOCK_MASK; - - return ret; -} - static void -config_entry_free(gpointer data, G_GNUC_UNUSED gpointer user_data) +config_param_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) { - struct config_entry *entry = data; - - g_slist_foreach(entry->params, config_param_free, NULL); - g_slist_free(entry->params); + struct config_param *param = data; - g_free(entry); + config_param_free(param); } static struct config_entry * config_entry_get(const char *name) { - GSList *list; - - for (list = config_entries; list != NULL; - list = g_slist_next(list)) { - struct config_entry *entry = list->data; + for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) { + struct config_entry *entry = &config_entries[i]; if (strcmp(entry->name, name) == 0) return entry; } @@ -148,82 +174,65 @@ config_entry_get(const char *name) return NULL; } -static void registerConfigParam(const char *name, int repeatable, int block) +void config_global_finish(void) { - struct config_entry *entry; + for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) { + struct config_entry *entry = &config_entries[i]; - entry = config_entry_get(name); - if (entry != NULL) - g_error("config parameter \"%s\" already registered\n", name); + g_slist_foreach(entry->params, + config_param_free_callback, NULL); + g_slist_free(entry->params); + } +} - entry = newConfigEntry(name, repeatable, block); - config_entries = g_slist_prepend(config_entries, entry); +void config_global_init(void) +{ } -void config_global_finish(void) +static void +config_param_check(gpointer data, G_GNUC_UNUSED gpointer user_data) { - g_slist_foreach(config_entries, config_entry_free, NULL); - g_slist_free(config_entries); + struct config_param *param = data; + + if (!param->used) + /* this whole config_param was not queried at all - + the feature might be disabled at compile time? + Silently ignore it here. */ + return; + + for (unsigned i = 0; i < param->num_block_params; i++) { + struct block_param *bp = ¶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 +bool config_add_block_param(struct config_param * param, const char *name, - const char *value, int line) + const char *value, int line, GError **error_r) { struct block_param *bp; + bp = config_get_block_param(param, name); + if (bp != NULL) { + g_set_error(error_r, config_quark(), 0, + "\"%s\" first defined on line %i, and " + "redefined on line %i\n", name, + bp->line, line); + return false; + } + param->num_block_params++; param->block_params = g_realloc(param->block_params, @@ -235,67 +244,97 @@ config_add_block_param(struct config_param * param, const char *name, bp->name = g_strdup(name); bp->value = g_strdup(value); bp->line = line; + bp->used = false; + + return true; } static struct config_param * -config_read_block(FILE *fp, int *count, char *string) +config_read_block(FILE *fp, int *count, char *string, GError **error_r) { struct config_param *ret = config_new_param(NULL, *count); - - int i; - int numberOfArgs; - int argsMinusComment; - - while (fgets(string, MAX_STRING_SIZE, fp)) { - char *array[CONF_LINE_TOKEN_MAX] = { NULL }; + GError *error = NULL; + bool success; + + while (true) { + char *line; + const char *name, *value; + + line = fgets(string, MAX_STRING_SIZE, fp); + if (line == NULL) { + config_param_free(ret); + g_set_error(error_r, config_quark(), 0, + "Expected '}' before end-of-file"); + return NULL; + } (*count)++; + line = g_strchug(line); + if (*line == 0 || *line == CONF_COMMENT) + continue; - numberOfArgs = buffer2array(string, array, CONF_LINE_TOKEN_MAX); + if (*line == '}') { + /* end of this block; return from the function + (and from this "while" loop) */ + + line = g_strchug(line + 1); + if (*line != 0 && *line != CONF_COMMENT) { + config_param_free(ret); + g_set_error(error_r, config_quark(), 0, + "line %i: Unknown tokens after '}'", + *count); + return false; + } - for (i = 0; i < numberOfArgs; i++) { - if (array[i][0] == CONF_COMMENT) - break; + return ret; } - argsMinusComment = i; + /* parse name and value */ - if (0 == argsMinusComment) { - continue; + name = tokenizer_next_word(&line, &error); + if (name == NULL) { + assert(*line != 0); + config_param_free(ret); + g_propagate_prefixed_error(error_r, error, + "line %i: ", *count); + return NULL; } - if (1 == argsMinusComment && - 0 == strcmp(array[0], CONF_BLOCK_END)) { - break; + value = tokenizer_next_string(&line, &error); + if (value == NULL) { + config_param_free(ret); + if (*line == 0) + g_set_error(error_r, config_quark(), 0, + "line %i: Value missing", *count); + else + g_propagate_prefixed_error(error_r, error, + "line %i: ", + *count); + return NULL; } - if (2 != argsMinusComment) { - g_error("improperly formatted config file at line %i:" - " %s\n", *count, string); + if (*line != 0 && *line != CONF_COMMENT) { + config_param_free(ret); + g_set_error(error_r, config_quark(), 0, + "line %i: Unknown tokens after value", + *count); + return NULL; } - if (0 == strcmp(array[0], CONF_BLOCK_BEGIN) || - 0 == strcmp(array[1], CONF_BLOCK_BEGIN) || - 0 == strcmp(array[0], CONF_BLOCK_END) || - 0 == strcmp(array[1], CONF_BLOCK_END)) { - g_error("improperly formatted config file at line %i: %s " - "in block beginning at line %i\n", - *count, string, ret->line); + success = config_add_block_param(ret, name, value, *count, + error_r); + if (!success) { + config_param_free(ret); + return false; } - - config_add_block_param(ret, array[0], array[1], *count); } - - return ret; } -void config_read_file(const char *file) +bool +config_read_file(const char *file, GError **error_r) { FILE *fp; char string[MAX_STRING_SIZE + 1]; - int i; - int numberOfArgs; - int argsMinusComment; int count = 0; struct config_entry *entry; struct config_param *param; @@ -303,67 +342,110 @@ void config_read_file(const char *file) g_debug("loading file %s", file); if (!(fp = fopen(file, "r"))) { - g_error("problems opening file %s for reading: %s\n", - file, strerror(errno)); + g_set_error(error_r, config_quark(), errno, + "Failed to open %s: %s", + file, strerror(errno)); + return false; } while (fgets(string, MAX_STRING_SIZE, fp)) { - char *array[CONF_LINE_TOKEN_MAX] = { NULL }; + char *line; + const char *name, *value; + GError *error = NULL; count++; - numberOfArgs = buffer2array(string, array, CONF_LINE_TOKEN_MAX); + line = g_strchug(string); + if (*line == 0 || *line == CONF_COMMENT) + continue; + + /* the first token in each line is the name, followed + by either the value or '{' */ - for (i = 0; i < numberOfArgs; i++) { - if (array[i][0] == CONF_COMMENT) - break; + name = tokenizer_next_word(&line, &error); + if (name == NULL) { + assert(*line != 0); + g_propagate_prefixed_error(error_r, error, + "line %i: ", count); + return false; } - argsMinusComment = i; + /* get the definition of that option, and check the + "repeatable" flag */ - if (0 == argsMinusComment) { - continue; + entry = config_entry_get(name); + if (entry == NULL) { + g_set_error(error_r, config_quark(), 0, + "unrecognized parameter in config file at " + "line %i: %s\n", count, name); + return false; } - if (2 != argsMinusComment) { - g_error("improperly formatted config file at line %i:" - " %s\n", count, string); + if (entry->params != NULL && !entry->repeatable) { + param = entry->params->data; + g_set_error(error_r, config_quark(), 0, + "config parameter \"%s\" is first defined " + "on line %i and redefined on line %i\n", + name, param->line, count); + return false; } - entry = config_entry_get(array[0]); - if (entry == NULL) - g_error("unrecognized parameter in config file at " - "line %i: %s\n", count, string); + /* now parse the block or the value */ - if (!(entry->mask & CONF_REPEATABLE_MASK) && - entry->params != NULL) { - param = entry->params->data; - g_error("config parameter \"%s\" is first defined on " - "line %i and redefined on line %i\n", - array[0], param->line, count); - } + if (entry->block) { + /* it's a block, call config_read_block() */ + + if (*line != '{') { + g_set_error(error_r, config_quark(), 0, + "line %i: '{' expected", count); + return false; + } + + line = g_strchug(line + 1); + if (*line != 0 && *line != CONF_COMMENT) { + g_set_error(error_r, config_quark(), 0, + "line %i: Unknown tokens after '{'", + count); + return false; + } + + param = config_read_block(fp, &count, string, error_r); + if (param == NULL) + return false; + } else { + /* a string value */ + + value = tokenizer_next_string(&line, &error); + if (value == NULL) { + if (*line == 0) + g_set_error(error_r, config_quark(), 0, + "line %i: Value missing", + count); + else { + g_set_error(error_r, config_quark(), 0, + "line %i: %s", count, + error->message); + g_error_free(error); + } + + return false; + } - if (entry->mask & CONF_BLOCK_MASK) { - if (0 != strcmp(array[1], CONF_BLOCK_BEGIN)) { - g_error("improperly formatted config file at " - "line %i: %s\n", count, string); + if (*line != 0 && *line != CONF_COMMENT) { + g_set_error(error_r, config_quark(), 0, + "line %i: Unknown tokens after value", + count); + return false; } - param = config_read_block(fp, &count, string); - } else - param = config_new_param(array[1], count); + + param = config_new_param(value, count); + } entry->params = g_slist_append(entry->params, param); } fclose(fp); -} - -void -config_add_param(const char *name, struct config_param *param) -{ - struct config_entry *entry = config_entry_get(name); - assert(entry != NULL); - entry->params = g_slist_append(entry->params, param); + return true; } struct config_param * @@ -391,7 +473,7 @@ config_get_next_param(const char *name, const struct config_param * last) return NULL; param = node->data; - + param->used = true; return param; } @@ -417,14 +499,32 @@ config_get_path(const char *name) path = parsePath(param->value); if (path == NULL) - g_error("error parsing \"%s\" at line %i\n", - name, param->line); + MPD_ERROR("error parsing \"%s\" at line %i\n", + name, param->line); g_free(param->value); return param->value = path; } unsigned +config_get_unsigned(const char *name, unsigned default_value) +{ + const struct config_param *param = config_get_param(name); + long value; + char *endptr; + + if (param == NULL) + return default_value; + + value = strtol(param->value, &endptr, 0); + if (*endptr != 0 || value < 0) + MPD_ERROR("Not a valid non-negative number in line %i", + param->line); + + return (unsigned)value; +} + +unsigned config_get_positive(const char *name, unsigned default_value) { const struct config_param *param = config_get_param(name); @@ -436,10 +536,10 @@ config_get_positive(const char *name, unsigned default_value) value = strtol(param->value, &endptr, 0); if (*endptr != 0) - g_error("Not a valid number in line %i", param->line); + MPD_ERROR("Not a valid number in line %i", param->line); if (value <= 0) - g_error("Not a positive number in line %i", param->line); + MPD_ERROR("Not a positive number in line %i", param->line); return (unsigned)value; } @@ -447,43 +547,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) - 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; + success = get_bool(param->value, &value); + if (!success) + MPD_ERROR("%s is not a boolean value (yes, true, 1) or " + "(no, false, 0) on line %i\n", + name, param->line); - return !!value; + return value; } const char * @@ -511,10 +603,10 @@ config_get_block_unsigned(const struct config_param *param, const char *name, value = strtol(bp->value, &endptr, 0); if (*endptr != 0) - g_error("Not a valid number in line %i", bp->line); + MPD_ERROR("Not a valid number in line %i", bp->line); if (value < 0) - g_error("Not a positive number in line %i", bp->line); + MPD_ERROR("Not a positive number in line %i", bp->line); return (unsigned)value; } @@ -524,19 +616,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) - 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; + success = get_bool(bp->value, &value); + if (!success) + MPD_ERROR("%s is not a boolean value (yes, true, 1) or " + "(no, false, 0) on line %i\n", + name, bp->line); - return !!value; + return value; } diff --git a/src/conf.h b/src/conf.h index c5e49960e..8a0678312 100644 --- a/src/conf.h +++ b/src/conf.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,26 +28,27 @@ #define CONF_FOLLOW_INSIDE_SYMLINKS "follow_inside_symlinks" #define CONF_FOLLOW_OUTSIDE_SYMLINKS "follow_outside_symlinks" #define CONF_DB_FILE "db_file" -#define CONF_STICKER_FILE "sticker_file" +#define CONF_STICKER_FILE "sticker_file" #define CONF_LOG_FILE "log_file" -#define CONF_ERROR_FILE "error_file" #define CONF_PID_FILE "pid_file" #define CONF_STATE_FILE "state_file" #define CONF_USER "user" +#define CONF_GROUP "group" #define CONF_BIND_TO_ADDRESS "bind_to_address" #define CONF_PORT "port" #define CONF_LOG_LEVEL "log_level" #define CONF_ZEROCONF_NAME "zeroconf_name" -#define CONF_ZEROCONF_ENABLED "zeroconf_enabled" +#define CONF_ZEROCONF_ENABLED "zeroconf_enabled" #define CONF_PASSWORD "password" #define CONF_DEFAULT_PERMS "default_permissions" #define CONF_AUDIO_OUTPUT "audio_output" +#define CONF_AUDIO_FILTER "filter" #define CONF_AUDIO_OUTPUT_FORMAT "audio_output_format" #define CONF_MIXER_TYPE "mixer_type" -#define CONF_MIXER_DEVICE "mixer_device" -#define CONF_MIXER_CONTROL "mixer_control" #define CONF_REPLAYGAIN "replaygain" #define CONF_REPLAYGAIN_PREAMP "replaygain_preamp" +#define CONF_REPLAYGAIN_MISSING_PREAMP "replaygain_missing_preamp" +#define CONF_REPLAYGAIN_LIMIT "replaygain_limit" #define CONF_VOLUME_NORMALIZATION "volume_normalization" #define CONF_SAMPLERATE_CONVERTER "samplerate_converter" #define CONF_AUDIO_BUFFER_SIZE "audio_buffer_size" @@ -65,20 +66,28 @@ #define CONF_ID3V1_ENCODING "id3v1_encoding" #define CONF_METADATA_TO_USE "metadata_to_use" #define CONF_SAVE_ABSOLUTE_PATHS "save_absolute_paths_in_playlists" -#define CONF_DECODER "decoder" -#define CONF_INPUT "input" -#define CONF_GAPLESS_MP3_PLAYBACK "gapless_mp3_playback" - -#define CONF_BOOL_UNSET -1 -#define CONF_BOOL_INVALID -2 +#define CONF_DECODER "decoder" +#define CONF_INPUT "input" +#define CONF_GAPLESS_MP3_PLAYBACK "gapless_mp3_playback" +#define CONF_PLAYLIST_PLUGIN "playlist_plugin" +#define CONF_AUTO_UPDATE "auto_update" +#define CONF_AUTO_UPDATE_DEPTH "auto_update_depth" #define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16) #define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false +#define MAX_FILTER_CHAIN_LENGTH 255 + struct block_param { char *name; char *value; int line; + + /** + * This flag is false when nobody has queried the value of + * this option yet. + */ + bool used; }; struct config_param { @@ -87,31 +96,57 @@ struct config_param { struct block_param *block_params; unsigned num_block_params; + + /** + * This flag is false when nobody has queried the value of + * this option yet. + */ + bool used; }; +/** + * A GQuark for GError instances, resulting from malformed + * configuration. + */ +static inline GQuark +config_quark(void) +{ + return g_quark_from_static_string("config"); +} + void config_global_init(void); void config_global_finish(void); -void config_read_file(const char *file); - /** - * Adds a new configuration parameter. The name must be registered - * with registerConfigParam(). + * Call this function after all configuration has been evaluated. It + * checks for unused parameters, and logs warnings. */ -void -config_add_param(const char *name, struct config_param *param); +void config_global_check(void); + +bool +config_read_file(const char *file, GError **error_r); /* don't free the returned value set _last_ to NULL to get first entry */ +G_GNUC_PURE struct config_param * config_get_next_param(const char *name, const struct config_param *last); +G_GNUC_PURE static inline struct config_param * config_get_param(const char *name) { return config_get_next_param(name, NULL); } +/* Note on G_GNUC_PURE: Some of the functions declared pure are not + really pure in strict sense. They have side effect such that they + validate parameter's value and signal an error if it's invalid. + However, if the argument was already validated or we don't care + about the argument at all, this may be ignored so in the end, we + should be fine with calling those functions pure. */ + +G_GNUC_PURE const char * config_get_string(const char *name, const char *default_value); @@ -120,17 +155,31 @@ config_get_string(const char *name, const char *default_value); * absolute path. If there is a tilde prefix, it is expanded. Aborts * MPD if the path is not a valid absolute path. */ +/* We lie here really. This function is not pure as it has side + effects -- it parse the value and creates new string freeing + previous one. However, because this works the very same way each + time (ie. from the outside it appears as if function had no side + effects) we should be in the clear declaring it pure. */ +G_GNUC_PURE const char * config_get_path(const char *name); +G_GNUC_PURE +unsigned +config_get_unsigned(const char *name, unsigned default_value); + +G_GNUC_PURE unsigned config_get_positive(const char *name, unsigned default_value); +G_GNUC_PURE struct block_param * config_get_block_param(const struct config_param *param, const char *name); +G_GNUC_PURE bool config_get_bool(const char *name, bool default_value); +G_GNUC_PURE const char * config_get_block_string(const struct config_param *param, const char *name, const char *default_value); @@ -142,10 +191,12 @@ config_dup_block_string(const struct config_param *param, const char *name, return g_strdup(config_get_block_string(param, name, default_value)); } +G_GNUC_PURE unsigned config_get_block_unsigned(const struct config_param *param, const char *name, unsigned default_value); +G_GNUC_PURE bool config_get_block_bool(const struct config_param *param, const char *name, bool default_value); @@ -153,8 +204,8 @@ config_get_block_bool(const struct config_param *param, const char *name, struct config_param * config_new_param(const char *value, int line); -void -config_add_block_param(struct config_param *param, const char *name, - const char *value, int line); +bool +config_add_block_param(struct config_param * param, const char *name, + const char *value, int line, GError **error_r); #endif diff --git a/src/crossfade.c b/src/crossfade.c index 01552bf65..cdfd82879 100644 --- a/src/crossfade.c +++ b/src/crossfade.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "crossfade.h" #include "pcm_mix.h" #include "chunk.h" @@ -25,73 +26,112 @@ #include <assert.h> #include <string.h> +#include <stdlib.h> +#include <glib.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "crossfade" + +#ifdef G_OS_WIN32 +static char * +strtok_r(char *str, const char *delim, G_GNUC_UNUSED char **saveptr) +{ + return strtok(str, delim); +} +#endif + +static float mixramp_interpolate(char *ramp_list, float required_db) +{ + float db, secs, last_db = nan(""), last_secs = 0; + char *ramp_str, *save_str = NULL; + + /* ramp_list is a string of pairs of dBs and seconds that describe the + * volume profile. Delimiters are semi-colons between pairs and spaces + * between the dB and seconds of a pair. + * The dB values must be monotonically increasing for this to work. */ + + while (1) { + /* Parse the dB tokens out of the input string. */ + ramp_str = strtok_r(ramp_list, " ", &save_str); + + /* Tell strtok to continue next time round. */ + ramp_list = NULL; + + /* Parse the dB value. */ + if (NULL == ramp_str) { + return nan(""); + } + db = (float)atof(ramp_str); + + /* Parse the time. */ + ramp_str = strtok_r(NULL, ";", &save_str); + if (NULL == ramp_str) { + return nan(""); + } + secs = (float)atof(ramp_str); + + /* Check for exact match. */ + if (db == required_db) { + return secs; + } + + /* Save if too quiet. */ + if (db < required_db) { + last_db = db; + last_secs = secs; + continue; + } + + /* If required db < any stored value, use the least. */ + if (isnan(last_db)) { + return secs; + } + + /* Finally, interpolate linearly. */ + secs = last_secs + (required_db - last_db) * (secs - last_secs) / (db - last_db); + return secs; + } +} unsigned cross_fade_calc(float duration, float total_time, + float mixramp_db, float mixramp_delay, + float replay_gain_db, float replay_gain_prev_db, + char *mixramp_start, char *mixramp_prev_end, const struct audio_format *af, const struct audio_format *old_format, unsigned max_chunks) { - unsigned int chunks; + unsigned int chunks = 0; + float chunks_f; + float mixramp_overlap; - if (duration <= 0 || duration >= total_time || + if (duration < 0 || duration >= total_time || /* we can't crossfade when the audio formats are different */ !audio_format_equals(af, old_format)) return 0; - assert(duration > 0); - assert(af->bits > 0); - assert(af->channels > 0); - assert(af->sample_rate > 0); - - chunks = audio_format_time_to_size(af) / CHUNK_SIZE; - chunks = (chunks * duration + 0.5); + assert(duration >= 0); + assert(audio_format_valid(af)); + + chunks_f = (float)audio_format_time_to_size(af) / (float)CHUNK_SIZE; + + if (isnan(mixramp_delay) || !(mixramp_start) || !(mixramp_prev_end)) { + chunks = (chunks_f * duration + 0.5); + } else { + /* Calculate mixramp overlap. */ + mixramp_overlap = mixramp_interpolate(mixramp_start, mixramp_db - replay_gain_db) + + mixramp_interpolate(mixramp_prev_end, mixramp_db - replay_gain_prev_db); + if (!isnan(mixramp_overlap) && (mixramp_delay <= mixramp_overlap)) { + chunks = (chunks_f * (mixramp_overlap - mixramp_delay)); + g_debug("will overlap %d chunks, %fs", chunks, + mixramp_overlap - mixramp_delay); + } + } - if (chunks > max_chunks) + if (chunks > max_chunks) { chunks = max_chunks; + g_warning("audio_buffer_size too small for computed MixRamp overlap"); + } return chunks; } - -void cross_fade_apply(struct music_chunk *a, const struct music_chunk *b, - const struct audio_format *format, - unsigned int current_chunk, unsigned int num_chunks) -{ - size_t size; - - assert(a != NULL); - assert(b != NULL); - assert(a->length == 0 || b->length == 0 || - audio_format_equals(&a->audio_format, &b->audio_format)); - assert(current_chunk <= num_chunks); - - if (a->tag == NULL && b->tag != NULL) - /* merge the tag into the destination chunk */ - a->tag = tag_dup(b->tag); - - size = b->length > a->length - ? a->length - : b->length; - - pcm_mix(a->data, - b->data, - size, - format, - ((float)current_chunk) / num_chunks); - - if (b->length > a->length) { - /* the second buffer is larger than the first one: - there is unmixed rest at the end. Copy it over. - The output buffer API guarantees that there is - enough room in a->data. */ - -#ifndef NDEBUG - if (a->length == 0) - a->audio_format = b->audio_format; -#endif - - memcpy(a->data + a->length, - b->data + a->length, - b->length - a->length); - a->length = b->length; - } -} diff --git a/src/crossfade.h b/src/crossfade.h index 1a09ede5b..096a62020 100644 --- a/src/crossfade.h +++ b/src/crossfade.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,6 +28,12 @@ struct music_chunk; * * @param duration the requested crossfade duration * @param total_time total_time the duration of the new song + * @param mixramp_db the current mixramp_db setting + * @param mixramp_delay the current mixramp_delay setting + * @param replay_gain_db the ReplayGain adjustment used for this song + * @param replay_gain_prev_db the ReplayGain adjustment used on the last song + * @param mixramp_start the next songs mixramp_start tag + * @param mixramp_prev_end the last songs mixramp_end setting * @param af the audio format of the new song * @param old_format the audio format of the current song * @param max_chunks the maximum number of chunks @@ -35,22 +41,11 @@ struct music_chunk; * should be disabled for this song change */ unsigned cross_fade_calc(float duration, float total_time, + float mixramp_db, float mixramp_delay, + float replay_gain_db, float replay_gain_prev_db, + char *mixramp_start, char *mixramp_prev_end, const struct audio_format *af, const struct audio_format *old_format, unsigned max_chunks); -/** - * Applies cross fading to two chunks, i.e. mixes these chunks. - * Internally, this calls pcm_mix(). - * - * @param a the chunk in the current song (and the destination chunk) - * @param b the according chunk in the new song - * @param format the audio format of both chunks (must be the same) - * @param current_chunk the relative index of the current chunk - * @param num_chunks the number of chunks used for cross fading - */ -void cross_fade_apply(struct music_chunk *a, const struct music_chunk *b, - const struct audio_format *format, - unsigned int current_chunk, unsigned int num_chunks); - #endif diff --git a/src/cue/cue_tag.c b/src/cue/cue_tag.c index 6251b03e2..ba1172559 100644 --- a/src/cue/cue_tag.c +++ b/src/cue/cue_tag.c @@ -1,76 +1,78 @@ +#include "config.h" #include "cue_tag.h" +#include "tag.h" -static struct tag* -cue_tag_cd(struct Cdtext* cdtext, struct Rem* rem) +#include <libcue/libcue.h> +#include <assert.h> + +static struct tag * +cue_tag_cd(struct Cdtext *cdtext, struct Rem *rem) { - char* tmp = NULL; - struct tag* tag = NULL; + struct tag *tag; + char *tmp; - //if (cdtext == NULL) - //return NULL; + assert(cdtext != NULL); tag = tag_new(); tag_begin_add(tag); - { /* TAG_ITEM_ALBUM_ARTIST */ + /* TAG_ALBUM_ARTIST */ if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp); + tag_add_item(tag, TAG_ALBUM_ARTIST, tmp); else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp); + tag_add_item(tag, TAG_ALBUM_ARTIST, tmp); else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp); + tag_add_item(tag, TAG_ALBUM_ARTIST, tmp); else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp); - /* TAG_ITEM_ALBUM_ARTIST */ } + tag_add_item(tag, TAG_ALBUM_ARTIST, tmp); - { /* TAG_ITEM_ARTIST */ + /* TAG_ARTIST */ if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ARTIST, tmp); + tag_add_item(tag, TAG_ARTIST, tmp); else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ARTIST, tmp); + tag_add_item(tag, TAG_ARTIST, tmp); else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ARTIST, tmp); + tag_add_item(tag, TAG_ARTIST, tmp); else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ARTIST, tmp); - /* TAG_ITEM_ARTIST */ } + tag_add_item(tag, TAG_ARTIST, tmp); - /* TAG_ITEM_PERFORMER */ + /* TAG_PERFORMER */ if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_PERFORMER, tmp); + tag_add_item(tag, TAG_PERFORMER, tmp); - /* TAG_ITEM_COMPOSER */ + /* TAG_COMPOSER */ if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_COMPOSER, tmp); + tag_add_item(tag, TAG_COMPOSER, tmp); - /* TAG_ITEM_ALBUM */ + /* TAG_ALBUM */ if ((tmp = cdtext_get(PTI_TITLE, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ALBUM, tmp); + tag_add_item(tag, TAG_ALBUM, tmp); - /* TAG_ITEM_GENRE */ + /* TAG_GENRE */ if ((tmp = cdtext_get(PTI_GENRE, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_GENRE, tmp); + tag_add_item(tag, TAG_GENRE, tmp); - /* TAG_ITEM_DATE */ + /* TAG_DATE */ if ((tmp = rem_get(REM_DATE, rem)) != NULL) - tag_add_item(tag, TAG_ITEM_DATE, tmp); + tag_add_item(tag, TAG_DATE, tmp); - /* TAG_ITEM_COMMENT */ + /* TAG_COMMENT */ if ((tmp = cdtext_get(PTI_MESSAGE, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_COMMENT, tmp); + tag_add_item(tag, TAG_COMMENT, tmp); - /* TAG_ITEM_DISC */ + /* TAG_DISC */ if ((tmp = cdtext_get(PTI_DISC_ID, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_DISC, tmp); + tag_add_item(tag, TAG_DISC, tmp); /* stream name, usually empty - * tag_add_item(tag, TAG_ITEM_NAME,); + * tag_add_item(tag, TAG_NAME,); */ /* REM MUSICBRAINZ entry? @@ -82,175 +84,152 @@ cue_tag_cd(struct Cdtext* cdtext, struct Rem* rem) tag_end_add(tag); - if (tag != NULL) - { - if (tag_is_empty(tag)) - { - tag_free(tag); - return NULL; - } - else - return tag; - } - else + if (tag_is_empty(tag)) { + tag_free(tag); return NULL; + } + + return tag; } -static struct tag* -cue_tag_track(struct Cdtext* cdtext, struct Rem* rem) +static struct tag * +cue_tag_track(struct Cdtext *cdtext, struct Rem *rem) { - char* tmp = NULL; - struct tag* tag = NULL; + struct tag *tag; + char *tmp; - //if (cdtext == NULL) - //return NULL; + assert(cdtext != NULL); tag = tag_new(); tag_begin_add(tag); - { /* TAG_ITEM_ARTIST */ + /* TAG_ARTIST */ if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ARTIST, tmp); + tag_add_item(tag, TAG_ARTIST, tmp); else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ARTIST, tmp); + tag_add_item(tag, TAG_ARTIST, tmp); else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ARTIST, tmp); + tag_add_item(tag, TAG_ARTIST, tmp); else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ARTIST, tmp); - /* TAG_ITEM_ARTIST */ } + tag_add_item(tag, TAG_ARTIST, tmp); - /* TAG_ITEM_TITLE */ + /* TAG_TITLE */ if ((tmp = cdtext_get(PTI_TITLE, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_TITLE, tmp); + tag_add_item(tag, TAG_TITLE, tmp); - /* TAG_ITEM_GENRE */ + /* TAG_GENRE */ if ((tmp = cdtext_get(PTI_GENRE, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_GENRE, tmp); + tag_add_item(tag, TAG_GENRE, tmp); - /* TAG_ITEM_DATE */ + /* TAG_DATE */ if ((tmp = rem_get(REM_DATE, rem)) != NULL) - tag_add_item(tag, TAG_ITEM_DATE, tmp); + tag_add_item(tag, TAG_DATE, tmp); - /* TAG_ITEM_COMPOSER */ + /* TAG_COMPOSER */ if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_COMPOSER, tmp); + tag_add_item(tag, TAG_COMPOSER, tmp); - /* TAG_ITEM_PERFORMER */ + /* TAG_PERFORMER */ if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_PERFORMER, tmp); + tag_add_item(tag, TAG_PERFORMER, tmp); - /* TAG_ITEM_COMMENT */ + /* TAG_COMMENT */ if ((tmp = cdtext_get(PTI_MESSAGE, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_COMMENT, tmp); + tag_add_item(tag, TAG_COMMENT, tmp); - /* TAG_ITEM_DISC */ + /* TAG_DISC */ if ((tmp = cdtext_get(PTI_DISC_ID, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_DISC, tmp); + tag_add_item(tag, TAG_DISC, tmp); tag_end_add(tag); - if (tag != NULL) - { - if (tag_is_empty(tag)) - { - tag_free(tag); - return NULL; - } - else - return tag; - } - else + if (tag_is_empty(tag)) { + tag_free(tag); return NULL; + } + + return tag; } -struct tag* -cue_tag_file( FILE* fp, - const unsigned int tnum) +struct tag * +cue_tag(struct Cd *cd, unsigned tnum) { - struct tag* cd_tag = NULL; - struct tag* track_tag = NULL; - struct Cd* cd = NULL; + struct tag *cd_tag, *track_tag, *tag; + struct Track *track; - if (tnum > 256) - return NULL; + assert(cd != NULL); - if (fp == NULL) + track = cd_get_track(cd, tnum); + if (track == NULL) return NULL; - else - cd = cue_parse_file(fp); - if (cd == NULL) - return NULL; - else - { - /* tag from CDtext info */ - cd_tag = cue_tag_cd( cd_get_cdtext(cd), - cd_get_rem(cd)); + /* tag from CDtext info */ + cd_tag = cue_tag_cd(cd_get_cdtext(cd), cd_get_rem(cd)); - /* tag from TRACKtext info */ - track_tag = cue_tag_track( track_get_cdtext( cd_get_track(cd, tnum)), - track_get_rem( cd_get_track(cd, tnum))); + /* tag from TRACKtext info */ + track_tag = cue_tag_track(track_get_cdtext(track), + track_get_rem(track)); - cd_delete(cd); - } + tag = tag_merge_replace(cd_tag, track_tag); + if (tag == NULL) + return NULL; - return tag_merge_replace(cd_tag, track_tag); + tag->time = track_get_length(track) + - track_get_index(track, 1) + + track_get_zero_pre(track); + track = cd_get_track(cd, tnum + 1); + if (track != NULL) + tag->time += track_get_index(track, 1) + - track_get_zero_pre(track); + /* libcue returns the track duration in frames, and there are + 75 frames per second; this formula rounds down */ + tag->time = tag->time / 75; + + return tag; } -struct tag* -cue_tag_string( char* str, - const unsigned int tnum) +struct tag * +cue_tag_file(FILE *fp, unsigned tnum) { - struct tag* cd_tag = NULL; - struct tag* track_tag = NULL; - struct tag* merge_tag = NULL; - struct Cd* cd = NULL; + struct Cd *cd; + struct tag *tag; - if (tnum > 256) - return NULL; + assert(fp != NULL); - if (str == NULL) + if (tnum > 256) return NULL; - else - cd = cue_parse_string(str); + cd = cue_parse_file(fp); if (cd == NULL) return NULL; - else - { - /* tag from CDtext info */ - cd_tag = cue_tag_cd( cd_get_cdtext(cd), - cd_get_rem(cd)); - /* tag from TRACKtext info */ - track_tag = cue_tag_track( track_get_cdtext( cd_get_track(cd, tnum)), - track_get_rem( cd_get_track(cd, tnum))); + tag = cue_tag(cd, tnum); + cd_delete(cd); - cd_delete(cd); - } + return tag; +} - if ((cd_tag != NULL) && (track_tag != NULL)) - { - merge_tag = tag_merge(cd_tag, track_tag); - tag_free(cd_tag); - tag_free(track_tag); - return merge_tag; - } +struct tag * +cue_tag_string(const char *str, unsigned tnum) +{ + struct Cd *cd; + struct tag *tag; - else if (cd_tag != NULL) - { - return cd_tag; - } + assert(str != NULL); - else if (track_tag != NULL) - { - return track_tag; - } + if (tnum > 256) + return NULL; - else + cd = cue_parse_string(str); + if (cd == NULL) return NULL; + + tag = cue_tag(cd, tnum); + cd_delete(cd); + + return tag; } diff --git a/src/cue/cue_tag.h b/src/cue/cue_tag.h index adc4c466e..1ddaa59c8 100644 --- a/src/cue/cue_tag.h +++ b/src/cue/cue_tag.h @@ -1,20 +1,23 @@ #ifndef MPD_CUE_TAG_H #define MPD_CUE_TAG_H -#include "config.h" +#include "check.h" #ifdef HAVE_CUE /* libcue */ -#include <libcue/libcue.h> -#include "../tag.h" +#include <stdio.h> -struct tag* -cue_tag_file( FILE*, - const unsigned int); +struct tag; +struct Cd; -struct tag* -cue_tag_string( char*, - const unsigned int); +struct tag * +cue_tag(struct Cd *cd, unsigned tnum); + +struct tag * +cue_tag_file(FILE *file, unsigned tnum); + +struct tag * +cue_tag_string(const char *str, unsigned tnum); #endif /* libcue */ #endif diff --git a/src/daemon.c b/src/daemon.c index 33b2953a9..852541375 100644 --- a/src/daemon.c +++ b/src/daemon.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "daemon.h" #include <glib.h> @@ -45,78 +46,72 @@ static char *user_name; /** the Unix user id which MPD runs as */ -static uid_t user_uid; +static uid_t user_uid = (uid_t)-1; /** the Unix group id which MPD runs as */ -static gid_t user_gid; +static gid_t user_gid = (pid_t)-1; /** the absolute path of the pidfile */ static char *pidfile; -#endif +/* whether "group" conf. option was given */ +static bool had_group = false; + void daemonize_kill(void) { -#ifndef WIN32 FILE *fp; int pid, ret; if (pidfile == NULL) - g_error("no pid_file specified in the config file"); + MPD_ERROR("no pid_file specified in the config file"); fp = fopen(pidfile, "r"); if (fp == NULL) - g_error("unable to open pid file \"%s\": %s", - pidfile, g_strerror(errno)); + MPD_ERROR("unable to open pid file \"%s\": %s", + pidfile, g_strerror(errno)); if (fscanf(fp, "%i", &pid) != 1) { - g_error("unable to read the pid from file \"%s\"", - pidfile); + MPD_ERROR("unable to read the pid from file \"%s\"", + pidfile); } fclose(fp); ret = kill(pid, SIGTERM); if (ret < 0) - g_error("unable to kill proccess %i: %s", - pid, g_strerror(errno)); + MPD_ERROR("unable to kill proccess %i: %s", + pid, g_strerror(errno)); exit(EXIT_SUCCESS); -#else - g_error("--kill is not available on WIN32"); -#endif } void daemonize_close_stdin(void) { - int fd = open("/dev/null", O_RDONLY); - - if (fd < 0) - close(STDIN_FILENO); - else if (fd != STDIN_FILENO) { - dup2(fd, STDIN_FILENO); - close(fd); - } + close(STDIN_FILENO); + open("/dev/null", O_RDONLY); } void daemonize_set_user(void) { -#ifndef WIN32 if (user_name == NULL) return; - /* get uid */ - if (setgid(user_gid) == -1) { - g_error("cannot setgid for user \"%s\": %s", - user_name, g_strerror(errno)); + /* set gid */ + if (user_gid != (gid_t)-1 && user_gid != getgid()) { + if (setgid(user_gid) == -1) { + MPD_ERROR("cannot setgid to %d: %s", + (int)user_gid, g_strerror(errno)); + } } + #ifdef _BSD_SOURCE /* init suplementary groups * (must be done before we change our uid) */ - if (initgroups(user_name, user_gid) == -1) { + if (!had_group && initgroups(user_name, user_gid) == -1) { g_warning("cannot init supplementary groups " "of user \"%s\": %s", user_name, g_strerror(errno)); @@ -124,50 +119,58 @@ daemonize_set_user(void) #endif /* set uid */ - if (setuid(user_uid) == -1) { - g_error("cannot change to uid of user \"%s\": %s", - user_name, g_strerror(errno)); + if (user_uid != (uid_t)-1 && user_uid != getuid() && + setuid(user_uid) == -1) { + MPD_ERROR("cannot change to uid of user \"%s\": %s", + user_name, g_strerror(errno)); } -#endif } -#ifndef G_OS_WIN32 static void daemonize_detach(void) { - pid_t pid; - /* flush all file handles before duplicating the buffers */ fflush(NULL); - /* detach from parent process */ +#ifdef HAVE_DAEMON + + if (daemon(0, 1)) + MPD_ERROR("daemon() failed: %s", g_strerror(errno)); - pid = fork(); - if (pid < 0) - g_error("fork() failed: %s", g_strerror(errno)); +#elif defined(HAVE_FORK) - if (pid > 0) + /* detach from parent process */ + + switch (fork()) { + case -1: + MPD_ERROR("fork() failed: %s", g_strerror(errno)); + case 0: + break; + default: /* exit the parent process */ _exit(EXIT_SUCCESS); + } /* release the current working directory */ if (chdir("/") < 0) - g_error("problems changing to root directory"); + MPD_ERROR("problems changing to root directory"); /* detach from the current session */ setsid(); +#else + MPD_ERROR("no support for daemonizing"); +#endif + g_debug("daemonized!"); } -#endif void daemonize(bool detach) { -#ifndef WIN32 FILE *fp = NULL; if (pidfile != NULL) { @@ -176,8 +179,8 @@ daemonize(bool detach) g_debug("opening pid file"); fp = fopen(pidfile, "w+"); if (!fp) { - g_error("could not create pid file \"%s\": %s", - pidfile, g_strerror(errno)); + MPD_ERROR("could not create pid file \"%s\": %s", + pidfile, g_strerror(errno)); } } @@ -189,47 +192,45 @@ daemonize(bool detach) fprintf(fp, "%lu\n", (unsigned long)getpid()); fclose(fp); } -#else - /* no daemonization on WIN32 */ - (void)detach; -#endif } void -daemonize_init(const char *user, const char *_pidfile) +daemonize_init(const char *user, const char *group, const char *_pidfile) { -#ifndef WIN32 - if (user != NULL && strcmp(user, g_get_user_name()) != 0) { - struct passwd *pwd; - - user_name = g_strdup(user); - - pwd = getpwnam(user_name); - if (pwd == NULL) - g_error("no such user \"%s\"", user_name); + if (user) { + struct passwd *pwd = getpwnam(user); + if (!pwd) + MPD_ERROR("no such user \"%s\"", user); user_uid = pwd->pw_uid; user_gid = pwd->pw_gid; + user_name = g_strdup(user); + /* this is needed by libs such as arts */ g_setenv("HOME", pwd->pw_dir, true); } + if (group) { + struct group *grp = grp = getgrnam(group); + if (!grp) + MPD_ERROR("no such group \"%s\"", group); + user_gid = grp->gr_gid; + had_group = true; + } + + pidfile = g_strdup(_pidfile); -#else - (void)user; - (void)_pidfile; -#endif } void daemonize_finish(void) { -#ifndef WIN32 if (pidfile != NULL) unlink(pidfile); g_free(user_name); g_free(pidfile); -#endif } + +#endif diff --git a/src/daemon.h b/src/daemon.h index 5b3f9a7dc..71039543c 100644 --- a/src/daemon.h +++ b/src/daemon.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,34 +20,72 @@ #ifndef DAEMON_H #define DAEMON_H +#include "mpd_error.h" + #include <stdbool.h> +#ifndef WIN32 void -daemonize_init(const char *user, const char *pidfile); +daemonize_init(const char *user, const char *group, const char *pidfile); +#else +static inline void +daemonize_init(const char *user, const char *group, const char *pidfile) +{ (void)user; (void)group; (void)pidfile; } +#endif +#ifndef WIN32 void daemonize_finish(void); +#else +static inline void +daemonize_finish(void) +{ /* nop */ } +#endif /** * Kill the MPD which is currently running, pid determined from the * pid file. */ +#ifndef WIN32 void daemonize_kill(void); +#else +#include <glib.h> +static inline void +daemonize_kill(void) +{ MPD_ERROR("--kill is not available on WIN32"); } +#endif /** * Close stdin (fd 0) and re-open it as /dev/null. */ +#ifndef WIN32 void daemonize_close_stdin(void); +#else +static inline void +daemonize_close_stdin(void) {} +#endif /** * Change to the configured Unix user. */ +#ifndef WIN32 void daemonize_set_user(void); +#else +static inline void +daemonize_set_user(void) +{ /* nop */ } +#endif +#ifndef WIN32 void daemonize(bool detach); +#else +static inline void +daemonize(bool detach) +{ (void)detach; } +#endif #endif diff --git a/src/database.c b/src/database.c index 5a06dda98..f255d6ca5 100644 --- a/src/database.c +++ b/src/database.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,16 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "database.h" #include "directory.h" #include "directory_save.h" #include "song.h" #include "path.h" #include "stats.h" -#include "config.h" +#include "text_file.h" +#include "tag.h" +#include "tag_internal.h" #include <glib.h> @@ -40,8 +43,14 @@ #define DIRECTORY_INFO_BEGIN "info_begin" #define DIRECTORY_INFO_END "info_end" +#define DB_FORMAT_PREFIX "format: " #define DIRECTORY_MPD_VERSION "mpd_version: " #define DIRECTORY_FS_CHARSET "fs_charset: " +#define DB_TAG_PREFIX "tag: " + +enum { + DB_FORMAT = 1, +}; static char *database_path; @@ -230,20 +239,27 @@ db_save(void) return false; } - /* block signals when writing the db so we don't get a corrupted db */ fprintf(fp, "%s\n", DIRECTORY_INFO_BEGIN); + fprintf(fp, DB_FORMAT_PREFIX "%u\n", DB_FORMAT); fprintf(fp, "%s%s\n", DIRECTORY_MPD_VERSION, VERSION); fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET, path_get_fs_charset()); + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + if (!ignore_tag_items[i]) + fprintf(fp, DB_TAG_PREFIX "%s\n", tag_item_names[i]); + fprintf(fp, "%s\n", DIRECTORY_INFO_END); - if (directory_save(fp, music_root) < 0) { + directory_save(fp, music_root); + + if (ferror(fp)) { g_warning("Failed to write to database file: %s", strerror(errno)); - while (fclose(fp) && errno == EINTR); + fclose(fp); return false; } - while (fclose(fp) && errno == EINTR); + fclose(fp); if (stat(database_path, &st) == 0) database_mtime = st.st_mtime; @@ -256,64 +272,64 @@ db_load(GError **error) { FILE *fp = NULL; struct stat st; - char buffer[100]; + GString *buffer = g_string_sized_new(1024); + char *line; + int format = 0; bool found_charset = false, found_version = false; bool success; + bool tags[TAG_NUM_OF_ITEM_TYPES]; assert(database_path != NULL); assert(music_root != NULL); - if (!music_root) - music_root = directory_new("", NULL); - while (!(fp = fopen(database_path, "r")) && errno == EINTR) ; + fp = fopen(database_path, "r"); if (fp == NULL) { g_set_error(error, db_quark(), errno, "Failed to open database file \"%s\": %s", database_path, strerror(errno)); + g_string_free(buffer, true); return false; } /* get initial info */ - if (!fgets(buffer, sizeof(buffer), fp)) { - fclose(fp); - g_set_error(error, db_quark(), 0, "Unexpected end of file"); - return false; - } - - g_strchomp(buffer); - - if (0 != strcmp(DIRECTORY_INFO_BEGIN, buffer)) { + line = read_text_line(fp, buffer); + if (line == NULL || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) { fclose(fp); g_set_error(error, db_quark(), 0, "Database corrupted"); + g_string_free(buffer, true); return false; } - while (fgets(buffer, sizeof(buffer), fp) && - !g_str_has_prefix(buffer, DIRECTORY_INFO_END)) { - g_strchomp(buffer); + memset(tags, false, sizeof(tags)); - if (g_str_has_prefix(buffer, DIRECTORY_MPD_VERSION)) { + while ((line = read_text_line(fp, buffer)) != NULL && + strcmp(line, DIRECTORY_INFO_END) != 0) { + if (g_str_has_prefix(line, DB_FORMAT_PREFIX)) { + format = atoi(line + sizeof(DB_FORMAT_PREFIX) - 1); + } else if (g_str_has_prefix(line, DIRECTORY_MPD_VERSION)) { if (found_version) { fclose(fp); g_set_error(error, db_quark(), 0, "Duplicate version line"); + g_string_free(buffer, true); return false; } found_version = true; - } else if (g_str_has_prefix(buffer, DIRECTORY_FS_CHARSET)) { + } else if (g_str_has_prefix(line, DIRECTORY_FS_CHARSET)) { const char *new_charset, *old_charset; if (found_charset) { fclose(fp); g_set_error(error, db_quark(), 0, "Duplicate charset line"); + g_string_free(buffer, true); return false; } found_charset = true; - new_charset = &(buffer[strlen(DIRECTORY_FS_CHARSET)]); + new_charset = line + sizeof(DIRECTORY_FS_CHARSET) - 1; old_charset = path_get_fs_charset(); if (old_charset != NULL && strcmp(new_charset, old_charset)) { @@ -323,20 +339,51 @@ db_load(GError **error) "\"%s\" instead of \"%s\"; " "discarding database file", new_charset, old_charset); + g_string_free(buffer, true); return false; } + } else if (g_str_has_prefix(line, DB_TAG_PREFIX)) { + const char *name = line + sizeof(DB_TAG_PREFIX) - 1; + enum tag_type tag = tag_name_parse(name); + if (tag == TAG_NUM_OF_ITEM_TYPES) { + g_set_error(error, db_quark(), 0, + "Unrecognized tag '%s', " + "discarding database file", + name); + return false; + } + + tags[tag] = true; } else { fclose(fp); g_set_error(error, db_quark(), 0, - "Malformed line: %s", buffer); + "Malformed line: %s", line); + g_string_free(buffer, true); + return false; + } + } + + if (format != DB_FORMAT) { + g_set_error(error, db_quark(), 0, + "Database format mismatch, " + "discarding database file"); + return false; + } + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { + if (!ignore_tag_items[i] && !tags[i]) { + g_set_error(error, db_quark(), 0, + "Tag list mismatch, " + "discarding database file"); return false; } } g_debug("reading DB"); - success = directory_load(fp, music_root, error); - while (fclose(fp) && errno == EINTR) ; + success = directory_load(fp, music_root, buffer, error); + g_string_free(buffer, true); + fclose(fp); if (!success) return false; diff --git a/src/database.h b/src/database.h index f4420928d..67149b20b 100644 --- a/src/database.h +++ b/src/database.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/dbUtils.c b/src/dbUtils.c index 2e2552698..f950d42cc 100644 --- a/src/dbUtils.c +++ b/src/dbUtils.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "dbUtils.h" #include "locate.h" #include "directory.h" @@ -59,7 +60,7 @@ static int printSongInDirectory(struct song *song, G_GNUC_UNUSED void *data) { struct client *client = data; - song_print_url(client, song); + song_print_uri(client, song); return 0; } @@ -74,7 +75,7 @@ searchInDirectory(struct song *song, void *_data) struct search_data *data = _data; if (locate_song_search(song, data->criteria)) - return song_print_info(data->client, song); + song_print_info(data->client, song); return 0; } @@ -104,7 +105,7 @@ findInDirectory(struct song *song, void *_data) struct search_data *data = _data; if (locate_song_match(song, data->criteria)) - return song_print_info(data->client, song); + song_print_info(data->client, song); return 0; } @@ -134,8 +135,7 @@ searchStatsInDirectory(struct song *song, void *data) if (locate_song_match(song, stats->criteria)) { stats->numberOfSongs++; - if (song->tag->time > 0) - stats->playTime += song->tag->time; + stats->playTime += song_get_duration(song); } return 0; @@ -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 { @@ -200,6 +200,28 @@ int addAllInToStoredPlaylist(const char *name, const char *utf8file) } static int +findAddInDirectory(struct song *song, void *_data) +{ + struct search_data *data = _data; + + if (locate_song_match(song, data->criteria)) + return directoryAddSongToPlaylist(song, data); + + return 0; +} + +int findAddIn(struct client *client, const char *name, + const struct locate_item_list *criteria) +{ + struct search_data data; + + data.client = client; + data.criteria = criteria; + + return db_walk(name, findAddInDirectory, NULL, &data); +} + +static int directoryPrintSongInfo(struct song *song, void *data) { struct client *client = data; @@ -237,7 +259,7 @@ visitTag(struct client *client, struct strset *set, bool found = false; if (tagType == LOCATE_TAG_FILE_TYPE) { - song_print_url(client, song); + song_print_uri(client, song); return; } diff --git a/src/dbUtils.h b/src/dbUtils.h index 1382c243e..bba253154 100644 --- a/src/dbUtils.h +++ b/src/dbUtils.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -40,6 +40,10 @@ findSongsIn(struct client *client, const char *name, const struct locate_item_list *criteria); int +findAddIn(struct client *client, const char *name, + const struct locate_item_list *criteria); + +int searchStatsForSongsIn(struct client *client, const char *name, const struct locate_item_list *criteria); diff --git a/src/decoder/_flac_common.c b/src/decoder/_flac_common.c index 7c8fe9875..8dd22a253 100644 --- a/src/decoder/_flac_common.c +++ b/src/decoder/_flac_common.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,7 +21,11 @@ * Common data structures and functions used by FLAC and OggFLAC */ +#include "config.h" #include "_flac_common.h" +#include "flac_metadata.h" +#include "flac_pcm.h" +#include "audio_check.h" #include <glib.h> @@ -31,186 +35,104 @@ void flac_data_init(struct flac_data *data, struct decoder * decoder, struct input_stream *input_stream) { - data->time = 0; + pcm_buffer_init(&data->buffer); + + data->unsupported = false; + data->initialized = false; + data->total_frames = 0; + data->first_frame = 0; + data->next_frame = 0; + data->position = 0; - data->bit_rate = 0; data->decoder = decoder; data->input_stream = input_stream; - data->replay_gain_info = NULL; data->tag = NULL; } -static void -flac_find_float_comment(const FLAC__StreamMetadata *block, - const char *cmnt, float *fl, bool *found_r) +void +flac_data_deinit(struct flac_data *data) { - int offset; - size_t pos; - int len; - unsigned char tmp, *p; - - offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, - cmnt); - if (offset < 0) - return; - - pos = strlen(cmnt) + 1; /* 1 is for '=' */ - len = block->data.vorbis_comment.comments[offset].length - pos; - if (len <= 0) - return; - - p = &block->data.vorbis_comment.comments[offset].entry[pos]; - tmp = p[len]; - p[len] = '\0'; - *fl = (float)atof((char *)p); - p[len] = tmp; - - *found_r = true; -} + pcm_buffer_deinit(&data->buffer); -static void -flac_parse_replay_gain(const FLAC__StreamMetadata *block, - struct flac_data *data) -{ - bool found = false; - - if (data->replay_gain_info) - replay_gain_info_free(data->replay_gain_info); - - data->replay_gain_info = replay_gain_info_new(); - - flac_find_float_comment(block, "replaygain_album_gain", - &data->replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain, - &found); - flac_find_float_comment(block, "replaygain_album_peak", - &data->replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak, - &found); - flac_find_float_comment(block, "replaygain_track_gain", - &data->replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain, - &found); - flac_find_float_comment(block, "replaygain_track_peak", - &data->replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak, - &found); - - if (!found) { - replay_gain_info_free(data->replay_gain_info); - data->replay_gain_info = NULL; - } + if (data->tag != NULL) + tag_free(data->tag); } -/** - * Checks if the specified name matches the entry's name, and if yes, - * returns the comment value (not null-temrinated). - */ -static const char * -flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, - const char *name, const char *char_tnum, size_t *length_r) +static enum sample_format +flac_sample_format(unsigned bits_per_sample) { - size_t name_length = strlen(name); - size_t char_tnum_length = 0; - const char *comment = (const char*)entry->entry; - - if (entry->length <= name_length || - g_ascii_strncasecmp(comment, name, name_length) != 0) - return NULL; - - if (char_tnum != NULL) { - char_tnum_length = strlen(char_tnum); - if (entry->length > name_length + char_tnum_length + 2 && - comment[name_length] == '[' && - g_ascii_strncasecmp(comment + name_length + 1, - char_tnum, char_tnum_length) == 0 && - comment[name_length + char_tnum_length + 1] == ']') - name_length = name_length + char_tnum_length + 2; - else if (entry->length > name_length + char_tnum_length && - g_ascii_strncasecmp(comment + name_length, - char_tnum, char_tnum_length) == 0) - name_length = name_length + char_tnum_length; - } + switch (bits_per_sample) { + case 8: + return SAMPLE_FORMAT_S8; - if (comment[name_length] == '=') { - *length_r = entry->length - name_length - 1; - return comment + name_length + 1; - } + case 16: + return SAMPLE_FORMAT_S16; - return NULL; -} + case 24: + return SAMPLE_FORMAT_S24_P32; -/** - * Check if the comment's name equals the passed name, and if so, copy - * the comment value into the tag. - */ -static bool -flac_copy_comment(struct tag *tag, - const FLAC__StreamMetadata_VorbisComment_Entry *entry, - const char *name, enum tag_type tag_type, - const char *char_tnum) -{ - const char *value; - size_t value_length; + case 32: + return SAMPLE_FORMAT_S32; - value = flac_comment_value(entry, name, char_tnum, &value_length); - if (value != NULL) { - tag_add_item_n(tag, tag_type, value, value_length); - return true; + default: + return SAMPLE_FORMAT_UNDEFINED; } - - return false; } -/* tracknumber is used in VCs, MPD uses "track" ..., all the other - * tag names match */ -static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber"; -static const char *VORBIS_COMMENT_DISC_KEY = "discnumber"; - static void -flac_parse_comment(struct tag *tag, const char *char_tnum, - const FLAC__StreamMetadata_VorbisComment_Entry *entry) +flac_got_stream_info(struct flac_data *data, + const FLAC__StreamMetadata_StreamInfo *stream_info) { - assert(tag != NULL); - - if (flac_copy_comment(tag, entry, VORBIS_COMMENT_TRACK_KEY, - TAG_ITEM_TRACK, char_tnum) || - flac_copy_comment(tag, entry, VORBIS_COMMENT_DISC_KEY, - TAG_ITEM_DISC, char_tnum) || - flac_copy_comment(tag, entry, "album artist", - TAG_ITEM_ALBUM_ARTIST, char_tnum)) + if (data->initialized || data->unsupported) return; - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - if (flac_copy_comment(tag, entry, - tag_item_names[i], i, char_tnum)) - return; -} + GError *error = NULL; + if (!audio_format_init_checked(&data->audio_format, + stream_info->sample_rate, + flac_sample_format(stream_info->bits_per_sample), + stream_info->channels, &error)) { + g_warning("%s", error->message); + g_error_free(error); + data->unsupported = true; + return; + } -void -flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum, - const FLAC__StreamMetadata *block) -{ - FLAC__StreamMetadata_VorbisComment_Entry *comments = - block->data.vorbis_comment.comments; + data->frame_size = audio_format_frame_size(&data->audio_format); - for (unsigned i = block->data.vorbis_comment.num_comments; i > 0; --i) - flac_parse_comment(tag, char_tnum, comments++); + if (data->total_frames == 0) + data->total_frames = stream_info->total_samples; + + data->initialized = true; } void flac_metadata_common_cb(const FLAC__StreamMetadata * block, struct flac_data *data) { - const FLAC__StreamMetadata_StreamInfo *si = &(block->data.stream_info); + if (data->unsupported) + return; + + struct replay_gain_info rgi; + char *mixramp_start; + char *mixramp_end; + float replay_gain_db = 0; switch (block->type) { case FLAC__METADATA_TYPE_STREAMINFO: - data->audio_format.bits = (int8_t)si->bits_per_sample; - data->audio_format.sample_rate = si->sample_rate; - data->audio_format.channels = (int8_t)si->channels; - data->total_time = ((float)si->total_samples) / (si->sample_rate); + flac_got_stream_info(data, &block->data.stream_info); break; + case FLAC__METADATA_TYPE_VORBIS_COMMENT: - flac_parse_replay_gain(block, data); + if (flac_parse_replay_gain(&rgi, block)) + replay_gain_db = decoder_replay_gain(data->decoder, &rgi); + if (flac_parse_mixramp(&mixramp_start, &mixramp_end, block)) { + g_debug("setting mixramp_tags"); + decoder_mixramp(data->decoder, replay_gain_db, + mixramp_start, mixramp_end); + } if (data->tag != NULL) - flac_vorbis_comments_to_tag(data->tag, NULL, block); + flac_vorbis_comments_to_tag(data->tag, NULL, + &block->data.vorbis_comment); default: break; @@ -239,187 +161,82 @@ void flac_error_common_cb(const char *plugin, } } -static void flac_convert_stereo16(int16_t *dest, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - for (; position < end; ++position) { - *dest++ = buf[0][position]; - *dest++ = buf[1][position]; - } -} - -static void -flac_convert_16(int16_t *dest, - unsigned int num_channels, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - unsigned int c_chan; - - for (; position < end; ++position) - for (c_chan = 0; c_chan < num_channels; c_chan++) - *dest++ = buf[c_chan][position]; -} - /** - * Note: this function also handles 24 bit files! + * This function attempts to call decoder_initialized() in case there + * was no STREAMINFO block. This is allowed for nonseekable streams, + * where the server sends us only a part of the file, without + * providing the STREAMINFO block from the beginning of the file + * (e.g. when seeking with SqueezeBox Server). */ -static void -flac_convert_32(int32_t *dest, - unsigned int num_channels, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - unsigned int c_chan; - - for (; position < end; ++position) - for (c_chan = 0; c_chan < num_channels; c_chan++) - *dest++ = buf[c_chan][position]; -} - -static void -flac_convert_8(int8_t *dest, - unsigned int num_channels, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - unsigned int c_chan; +static bool +flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header) +{ + if (data->unsupported) + return false; + + GError *error = NULL; + if (!audio_format_init_checked(&data->audio_format, + header->sample_rate, + flac_sample_format(header->bits_per_sample), + header->channels, &error)) { + g_warning("%s", error->message); + g_error_free(error); + data->unsupported = true; + return false; + } - for (; position < end; ++position) - for (c_chan = 0; c_chan < num_channels; c_chan++) - *dest++ = buf[c_chan][position]; -} + data->frame_size = audio_format_frame_size(&data->audio_format); -static void flac_convert(unsigned char *dest, - unsigned int num_channels, - unsigned int bytes_per_sample, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - switch (bytes_per_sample) { - case 2: - if (num_channels == 2) - flac_convert_stereo16((int16_t*)dest, buf, - position, end); - else - flac_convert_16((int16_t*)dest, num_channels, buf, - position, end); - break; + decoder_initialized(data->decoder, &data->audio_format, + data->input_stream->seekable, + (float)data->total_frames / + (float)data->audio_format.sample_rate); - case 4: - flac_convert_32((int32_t*)dest, num_channels, buf, - position, end); - break; + data->initialized = true; - case 1: - flac_convert_8((int8_t*)dest, num_channels, buf, - position, end); - break; - } + return true; } FLAC__StreamDecoderWriteStatus flac_common_write(struct flac_data *data, const FLAC__Frame * frame, - const FLAC__int32 *const buf[]) + const FLAC__int32 *const buf[], + FLAC__uint64 nbytes) { - unsigned int c_samp; - const unsigned int num_channels = frame->header.channels; - const unsigned int bytes_per_sample = - audio_format_sample_size(&data->audio_format); - const unsigned int bytes_per_channel = - bytes_per_sample * frame->header.channels; - const unsigned int max_samples = FLAC_CHUNK_SIZE / bytes_per_channel; - unsigned int num_samples; enum decoder_command cmd; + void *buffer; + unsigned bit_rate; - if (bytes_per_sample != 1 && bytes_per_sample != 2 && - bytes_per_sample != 4) - /* exotic unsupported bit rate */ + if (!data->initialized && !flac_got_first_frame(data, &frame->header)) return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - for (c_samp = 0; c_samp < frame->header.blocksize; - c_samp += num_samples) { - num_samples = frame->header.blocksize - c_samp; - if (num_samples > max_samples) - num_samples = max_samples; - - flac_convert(data->chunk, - num_channels, bytes_per_sample, buf, - c_samp, c_samp + num_samples); - - cmd = decoder_data(data->decoder, data->input_stream, - data->chunk, - num_samples * bytes_per_channel, - data->time, data->bit_rate, - data->replay_gain_info); - switch (cmd) { - case DECODE_COMMAND_NONE: - case DECODE_COMMAND_START: - break; - - case DECODE_COMMAND_STOP: - return - FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - - case DECODE_COMMAND_SEEK: - return - FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; - } - } - - return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; -} - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + size_t buffer_size = frame->header.blocksize * data->frame_size; + buffer = pcm_buffer_get(&data->buffer, buffer_size); -char* -flac_cue_track( const char* pathname, - const unsigned int tnum) -{ - FLAC__bool success; - FLAC__StreamMetadata* cs; - - success = FLAC__metadata_get_cuesheet(pathname, &cs); - if (!success) - return NULL; - - assert(cs != NULL); - - if (cs->data.cue_sheet.num_tracks <= 1) - { - FLAC__metadata_object_delete(cs); - return NULL; - } + flac_convert(buffer, frame->header.channels, + data->audio_format.format, buf, + 0, frame->header.blocksize); - if (tnum > 0 && tnum < cs->data.cue_sheet.num_tracks) - { - char* track = g_strdup_printf("track_%03u.flac", tnum); + if (nbytes > 0) + bit_rate = nbytes * 8 * frame->header.sample_rate / + (1000 * frame->header.blocksize); + else + bit_rate = 0; + + cmd = decoder_data(data->decoder, data->input_stream, + buffer, buffer_size, + bit_rate); + data->next_frame += frame->header.blocksize; + switch (cmd) { + case DECODE_COMMAND_NONE: + case DECODE_COMMAND_START: + break; - FLAC__metadata_object_delete(cs); + case DECODE_COMMAND_STOP: + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - return track; - } - else - { - FLAC__metadata_object_delete(cs); - return NULL; + case DECODE_COMMAND_SEEK: + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; } -} -unsigned int -flac_vtrack_tnum(const char* fname) -{ - /* find last occurrence of '_' in fname - * which is hopefully something like track_xxx.flac - * another/better way would be to use tag struct - */ - char* ptr = strrchr(fname, '_'); - if (ptr == NULL) - return 0; - - // copy ascii tracknumber to int - return (unsigned int)strtol(++ptr, NULL, 10); + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; } - -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ diff --git a/src/decoder/_flac_common.h b/src/decoder/_flac_common.h index 68de7e969..5c59ee123 100644 --- a/src/decoder/_flac_common.h +++ b/src/decoder/_flac_common.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,136 +24,62 @@ #ifndef MPD_FLAC_COMMON_H #define MPD_FLAC_COMMON_H -#include "../decoder_api.h" -#include "config.h" +#include "decoder_api.h" +#include "pcm_buffer.h" #include <glib.h> +#include <FLAC/stream_decoder.h> +#include <FLAC/metadata.h> + #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "flac" -#include <FLAC/export.h> -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 -# include <FLAC/seekable_stream_decoder.h> -# define flac_decoder FLAC__SeekableStreamDecoder -# define flac_new() FLAC__seekable_stream_decoder_new() - -# define flac_ogg_init(a,b,c,d,e,f,g,h,i,j) (0) - -# define flac_get_decode_position(x,y) \ - FLAC__seekable_stream_decoder_get_decode_position(x,y) -# define flac_get_state(x) FLAC__seekable_stream_decoder_get_state(x) -# define flac_process_single(x) FLAC__seekable_stream_decoder_process_single(x) -# define flac_process_metadata(x) \ - FLAC__seekable_stream_decoder_process_until_end_of_metadata(x) -# define flac_seek_absolute(x,y) \ - FLAC__seekable_stream_decoder_seek_absolute(x,y) -# define flac_finish(x) FLAC__seekable_stream_decoder_finish(x) -# define flac_delete(x) FLAC__seekable_stream_decoder_delete(x) - -# define flac_decoder_eof FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM - -typedef unsigned flac_read_status_size_t; -# define flac_read_status FLAC__SeekableStreamDecoderReadStatus -# define flac_read_status_continue \ - FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK -# define flac_read_status_eof FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK -# define flac_read_status_abort \ - FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR - -# define flac_seek_status FLAC__SeekableStreamDecoderSeekStatus -# define flac_seek_status_ok FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK -# define flac_seek_status_error FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR - -# define flac_tell_status FLAC__SeekableStreamDecoderTellStatus -# define flac_tell_status_ok FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK -# define flac_tell_status_error \ - FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR -# define flac_tell_status_unsupported \ - FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR - -# define flac_length_status FLAC__SeekableStreamDecoderLengthStatus -# define flac_length_status_ok FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK -# define flac_length_status_error \ - FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR -# define flac_length_status_unsupported \ - FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR - -# ifdef HAVE_OGGFLAC -# include <OggFLAC/seekable_stream_decoder.h> -# endif -#else /* FLAC_API_VERSION_CURRENT > 7 */ +struct flac_data { + struct pcm_buffer buffer; + + /** + * The size of one frame in the output buffer. + */ + unsigned frame_size; + + /** + * Has decoder_initialized() been called yet? + */ + bool initialized; + + /** + * Does the FLAC file contain an unsupported audio format? + */ + bool unsupported; + + /** + * The validated audio format of the FLAC file. This + * attribute is defined if "initialized" is true. + */ + struct audio_format audio_format; -/* - * OggFLAC support is handled by our flac_plugin already, and - * thus we *can* always have it if libFLAC was compiled with it - */ -# include "_ogg_common.h" - -# include <FLAC/stream_decoder.h> -# define flac_decoder FLAC__StreamDecoder -# define flac_new() FLAC__stream_decoder_new() - -# define flac_init(a,b,c,d,e,f,g,h,i,j) \ - (FLAC__stream_decoder_init_stream(a,b,c,d,e,f,g,h,i,j) \ - == FLAC__STREAM_DECODER_INIT_STATUS_OK) -# define flac_ogg_init(a,b,c,d,e,f,g,h,i,j) \ - (FLAC__stream_decoder_init_ogg_stream(a,b,c,d,e,f,g,h,i,j) \ - == FLAC__STREAM_DECODER_INIT_STATUS_OK) - -# define flac_get_decode_position(x,y) \ - FLAC__stream_decoder_get_decode_position(x,y) -# define flac_get_state(x) FLAC__stream_decoder_get_state(x) -# define flac_process_single(x) FLAC__stream_decoder_process_single(x) -# define flac_process_metadata(x) \ - FLAC__stream_decoder_process_until_end_of_metadata(x) -# define flac_seek_absolute(x,y) FLAC__stream_decoder_seek_absolute(x,y) -# define flac_finish(x) FLAC__stream_decoder_finish(x) -# define flac_delete(x) FLAC__stream_decoder_delete(x) - -# define flac_decoder_eof FLAC__STREAM_DECODER_END_OF_STREAM - -typedef size_t flac_read_status_size_t; -# define flac_read_status FLAC__StreamDecoderReadStatus -# define flac_read_status_continue \ - FLAC__STREAM_DECODER_READ_STATUS_CONTINUE -# define flac_read_status_eof FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM -# define flac_read_status_abort FLAC__STREAM_DECODER_READ_STATUS_ABORT - -# define flac_seek_status FLAC__StreamDecoderSeekStatus -# define flac_seek_status_ok FLAC__STREAM_DECODER_SEEK_STATUS_OK -# define flac_seek_status_error FLAC__STREAM_DECODER_SEEK_STATUS_ERROR -# define flac_seek_status_unsupported \ - FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED - -# define flac_tell_status FLAC__StreamDecoderTellStatus -# define flac_tell_status_ok FLAC__STREAM_DECODER_TELL_STATUS_OK -# define flac_tell_status_error FLAC__STREAM_DECODER_TELL_STATUS_ERROR -# define flac_tell_status_unsupported \ - FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED - -# define flac_length_status FLAC__StreamDecoderLengthStatus -# define flac_length_status_ok FLAC__STREAM_DECODER_LENGTH_STATUS_OK -# define flac_length_status_error FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR -# define flac_length_status_unsupported \ - FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED - -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ + /** + * The total number of frames in this song. The decoder + * plugin may initialize this attribute to override the value + * provided by libFLAC (e.g. for sub songs from a CUE sheet). + */ + FLAC__uint64 total_frames; -#include <FLAC/metadata.h> + /** + * The number of the first frame in this song. This is only + * non-zero if playing sub songs from a CUE sheet. + */ + FLAC__uint64 first_frame; -#define FLAC_CHUNK_SIZE 4080 + /** + * The number of the next frame which is going to be decoded. + */ + FLAC__uint64 next_frame; -struct flac_data { - unsigned char chunk[FLAC_CHUNK_SIZE]; - float time; - unsigned int bit_rate; - struct audio_format audio_format; - float total_time; FLAC__uint64 position; struct decoder *decoder; struct input_stream *input_stream; - struct replay_gain_info *replay_gain_info; struct tag *tag; }; @@ -162,6 +88,9 @@ void flac_data_init(struct flac_data *data, struct decoder * decoder, struct input_stream *input_stream); +void +flac_data_deinit(struct flac_data *data); + void flac_metadata_common_cb(const FLAC__StreamMetadata * block, struct flac_data *data); @@ -169,23 +98,9 @@ void flac_error_common_cb(const char *plugin, FLAC__StreamDecoderErrorStatus status, struct flac_data *data); -void -flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum, - const FLAC__StreamMetadata *block); - FLAC__StreamDecoderWriteStatus flac_common_write(struct flac_data *data, const FLAC__Frame * frame, - const FLAC__int32 *const buf[]); - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - -char* -flac_cue_track( const char* pathname, - const unsigned int tnum); - -unsigned int -flac_vtrack_tnum( const char*); - -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ + const FLAC__int32 *const buf[], + FLAC__uint64 nbytes); #endif /* _FLAC_COMMON_H */ diff --git a/src/decoder/_ogg_common.c b/src/decoder/_ogg_common.c index 6c6553422..bd0650ac4 100644 --- a/src/decoder/_ogg_common.c +++ b/src/decoder/_ogg_common.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,8 +21,8 @@ * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) */ +#include "config.h" #include "_ogg_common.h" -#include "../utils.h" ogg_stream_type ogg_stream_type_detect(struct input_stream *inStream) { diff --git a/src/decoder/_ogg_common.h b/src/decoder/_ogg_common.h index e650c366d..f8446c69c 100644 --- a/src/decoder/_ogg_common.h +++ b/src/decoder/_ogg_common.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,7 +24,7 @@ #ifndef MPD_OGG_COMMON_H #define MPD_OGG_COMMON_H -#include "../decoder_api.h" +#include "decoder_api.h" typedef enum _ogg_stream_type { VORBIS, FLAC } ogg_stream_type; diff --git a/src/decoder/audiofile_plugin.c b/src/decoder/audiofile_decoder_plugin.c index f66d90dc1..3026f3cc7 100644 --- a/src/decoder/audiofile_plugin.c +++ b/src/decoder/audiofile_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,7 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../decoder_api.h" +#include "config.h" +#include "decoder_api.h" +#include "audio_check.h" #include <audiofile.h> #include <af_vfs.h> @@ -45,10 +47,20 @@ static int audiofile_get_duration(const char *file) } static ssize_t -audiofile_file_read(AFvirtualfile *vfile, void *data, size_t nbytes) +audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length) { struct input_stream *is = (struct input_stream *) vfile->closure; - return input_stream_read(is, data, nbytes); + GError *error = NULL; + size_t nbytes; + + nbytes = input_stream_read(is, data, length, &error); + if (nbytes == 0 && error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + return -1; + } + + return nbytes; } static long @@ -78,7 +90,7 @@ audiofile_file_seek(AFvirtualfile *vfile, long offset, int is_relative) { struct input_stream *is = (struct input_stream *) vfile->closure; int whence = (is_relative ? SEEK_CUR : SEEK_SET); - if (input_stream_seek(is, offset, whence)) { + if (input_stream_seek(is, offset, whence, NULL)) { return is->offset; } else { return -1; @@ -99,17 +111,56 @@ setup_virtual_fops(struct input_stream *stream) return vf; } +static enum sample_format +audiofile_bits_to_sample_format(int bits) +{ + switch (bits) { + case 8: + return SAMPLE_FORMAT_S8; + + case 16: + return SAMPLE_FORMAT_S16; + + case 24: + return SAMPLE_FORMAT_S24_P32; + + case 32: + return SAMPLE_FORMAT_S32; + } + + return SAMPLE_FORMAT_UNDEFINED; +} + +static enum sample_format +audiofile_setup_sample_format(AFfilehandle af_fp) +{ + int fs, bits; + + afGetSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits); + if (!audio_valid_sample_format(audiofile_bits_to_sample_format(bits))) { + g_debug("input file has %d bit samples, converting to 16", + bits); + bits = 16; + } + + afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, + AF_SAMPFMT_TWOSCOMP, bits); + afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits); + + return audiofile_bits_to_sample_format(bits); +} + static void audiofile_stream_decode(struct decoder *decoder, struct input_stream *is) { + GError *error = NULL; AFvirtualfile *vf; int fs, frame_count; AFfilehandle af_fp; - int bits; struct audio_format audio_format; float total_time; uint16_t bit_rate; - int ret, current = 0; + int ret; char chunk[CHUNK_SIZE]; enum decoder_command cmd; @@ -126,26 +177,13 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is) return; } - afGetSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits); - if (!audio_valid_sample_format(bits)) { - g_debug("input file has %d bit samples, converting to 16", - bits); - bits = 16; - } - - afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, - AF_SAMPFMT_TWOSCOMP, bits); - afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits); - audio_format.bits = (uint8_t)bits; - audio_format.sample_rate = - (unsigned int)afGetRate(af_fp, AF_DEFAULT_TRACK); - audio_format.channels = - (uint8_t)afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK); - - if (!audio_format_valid(&audio_format)) { - g_warning("Invalid audio format: %u:%u:%u\n", - audio_format.sample_rate, audio_format.bits, - audio_format.channels); + if (!audio_format_init_checked(&audio_format, + afGetRate(af_fp, AF_DEFAULT_TRACK), + audiofile_setup_sample_format(af_fp), + afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK), + &error)) { + g_warning("%s", error->message); + g_error_free(error); afCloseFile(af_fp); return; } @@ -166,17 +204,14 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is) if (ret <= 0) break; - current += ret; cmd = decoder_data(decoder, NULL, chunk, ret * fs, - (float)current / - (float)audio_format.sample_rate, - bit_rate, NULL); + bit_rate); if (cmd == DECODE_COMMAND_SEEK) { - current = decoder_seek_where(decoder) * + AFframecount frame = decoder_seek_where(decoder) * audio_format.sample_rate; - afSeekFrame(af_fp, AF_DEFAULT_TRACK, current); + afSeekFrame(af_fp, AF_DEFAULT_TRACK, frame); decoder_command_finished(decoder); cmd = DECODE_COMMAND_NONE; diff --git a/src/decoder/faad_plugin.c b/src/decoder/faad_decoder_plugin.c index 7b2806a4c..8f932ad58 100644 --- a/src/decoder/faad_plugin.c +++ b/src/decoder/faad_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,9 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../decoder_api.h" -#include "decoder_buffer.h" #include "config.h" +#include "decoder_api.h" +#include "decoder_buffer.h" +#include "audio_check.h" #define AAC_MAX_CHANNELS 6 @@ -37,6 +38,15 @@ static const unsigned adts_sample_rates[] = }; /** + * The GLib quark used for errors reported by this plugin. + */ +static inline GQuark +faad_decoder_quark(void) +{ + return g_quark_from_static_string("faad"); +} + +/** * Check whether the buffer head is an AAC frame, and return the frame * length. Returns 0 if it is not a frame. */ @@ -195,7 +205,7 @@ faad_song_duration(struct decoder_buffer *buffer, struct input_stream *is) /* obtain the duration from the ADTS header */ float song_length = adts_song_duration(buffer); - input_stream_seek(is, tagsize, SEEK_SET); + input_stream_seek(is, tagsize, SEEK_SET, NULL); data = decoder_buffer_read(buffer, &length); if (data != NULL) @@ -232,7 +242,7 @@ faad_song_duration(struct decoder_buffer *buffer, struct input_stream *is) */ static bool faad_decoder_init(faacDecHandle decoder, struct decoder_buffer *buffer, - struct audio_format *audio_format) + struct audio_format *audio_format, GError **error_r) { union { /* deconst hack for libfaad */ @@ -247,32 +257,33 @@ faad_decoder_init(faacDecHandle decoder, struct decoder_buffer *buffer, /* neaacdec.h declares all arguments as "unsigned long", but internally expects uint32_t pointers. To avoid gcc warnings, use this workaround. */ - unsigned long *sample_rate_r = (unsigned long *)(void *)&sample_rate; + unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate; #else - uint32_t *sample_rate_r = &sample_rate; + uint32_t *sample_rate_p = &sample_rate; #endif u.in = decoder_buffer_read(buffer, &length); - if (u.in == NULL) + if (u.in == NULL) { + g_set_error(error_r, faad_decoder_quark(), 0, + "Empty file"); return false; + } nbytes = faacDecInit(decoder, u.out, #ifdef HAVE_FAAD_BUFLEN_FUNCS length, #endif - sample_rate_r, &channels); - if (nbytes < 0) + sample_rate_p, &channels); + if (nbytes < 0) { + g_set_error(error_r, faad_decoder_quark(), 0, + "Not an AAC stream"); return false; + } decoder_buffer_consume(buffer, nbytes); - *audio_format = (struct audio_format){ - .bits = 16, - .channels = channels, - .sample_rate = sample_rate, - }; - - return true; + return audio_format_init_checked(audio_format, sample_rate, + SAMPLE_FORMAT_S16, channels, error_r); } /** @@ -311,20 +322,16 @@ faad_decoder_decode(faacDecHandle decoder, struct decoder_buffer *buffer, * file is invalid. */ static float -faad_get_file_time_float(const char *file) +faad_get_file_time_float(struct input_stream *is) { struct decoder_buffer *buffer; float length; faacDecHandle decoder; faacDecConfigurationPtr config; - struct input_stream is; - - if (!input_stream_open(&is, file)) - return -1; - buffer = decoder_buffer_new(NULL, &is, + buffer = decoder_buffer_new(NULL, is, FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); - length = faad_song_duration(buffer, &is); + length = faad_song_duration(buffer, is); if (length < 0) { bool ret; @@ -338,15 +345,14 @@ faad_get_file_time_float(const char *file) decoder_buffer_fill(buffer); - ret = faad_decoder_init(decoder, buffer, &audio_format); - if (ret && audio_format_valid(&audio_format)) + ret = faad_decoder_init(decoder, buffer, &audio_format, NULL); + if (ret) length = 0; faacDecClose(decoder); } decoder_buffer_free(buffer); - input_stream_close(&is); return length; } @@ -357,12 +363,12 @@ faad_get_file_time_float(const char *file) * file is invalid. */ static int -faad_get_file_time(const char *file) +faad_get_file_time(struct input_stream *is) { int file_time = -1; float length; - if ((length = faad_get_file_time_float(file)) >= 0) + if ((length = faad_get_file_time_float(is)) >= 0) file_time = length + 0.5; return file_time; @@ -371,7 +377,7 @@ faad_get_file_time(const char *file) static void faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is) { - float file_time; + GError *error = NULL; float total_time = 0; faacDecHandle decoder; struct audio_format audio_format; @@ -408,15 +414,10 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is) /* initialize it */ - ret = faad_decoder_init(decoder, buffer, &audio_format); + ret = faad_decoder_init(decoder, buffer, &audio_format, &error); if (!ret) { - g_warning("Error not a AAC stream.\n"); - faacDecClose(decoder); - return; - } - - if (!audio_format_valid(&audio_format)) { - g_warning("invalid audio format\n"); + g_warning("%s", error->message); + g_error_free(error); faacDecClose(decoder); return; } @@ -427,8 +428,6 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is) /* the decoder loop */ - file_time = 0.0; - do { size_t frame_size; const void *decoded; @@ -474,16 +473,13 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is) bit_rate = frame_info.bytesconsumed * 8.0 * frame_info.channels * audio_format.sample_rate / frame_info.samples / 1000 + 0.5; - file_time += - (float)(frame_info.samples) / frame_info.channels / - audio_format.sample_rate; } /* send PCM samples to MPD */ cmd = decoder_data(mpd_decoder, is, decoded, - (size_t)frame_info.samples * 2, file_time, - bit_rate, NULL); + (size_t)frame_info.samples * 2, + bit_rate); } while (cmd != DECODE_COMMAND_STOP); /* cleanup */ @@ -492,15 +488,13 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is) } static struct tag * -faad_tag_dup(const char *file) +faad_stream_tag(struct input_stream *is) { - int file_time = faad_get_file_time(file); + int file_time = faad_get_file_time(is); struct tag *tag; - if (file_time < 0) { - g_debug("Failed to get total song time from: %s", file); + if (file_time < 0) return NULL; - } tag = tag_new(); tag->time = file_time; @@ -515,7 +509,7 @@ static const char *const faad_mime_types[] = { const struct decoder_plugin faad_decoder_plugin = { .name = "faad", .stream_decode = faad_stream_decode, - .tag_dup = faad_tag_dup, + .stream_tag = faad_stream_tag, .suffixes = faad_suffixes, .mime_types = faad_mime_types, }; diff --git a/src/decoder/ffmpeg_plugin.c b/src/decoder/ffmpeg_decoder_plugin.c index 10894b633..f9d4eb8a9 100644 --- a/src/decoder/ffmpeg_plugin.c +++ b/src/decoder/ffmpeg_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../decoder_api.h" #include "config.h" +#include "decoder_api.h" +#include "audio_check.h" #include <glib.h> @@ -39,19 +40,46 @@ #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavformat/avio.h> +#include <libavutil/log.h> #endif #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "ffmpeg" -struct ffmpeg_context { - int audio_stream; - AVFormatContext *format_context; - AVCodecContext *codec_context; - struct decoder *decoder; - struct input_stream *input; - struct tag *tag; -}; +#ifndef OLD_FFMPEG_INCLUDES + +static GLogLevelFlags +level_ffmpeg_to_glib(int level) +{ + if (level <= AV_LOG_FATAL) + return G_LOG_LEVEL_CRITICAL; + + if (level <= AV_LOG_ERROR) + return G_LOG_LEVEL_WARNING; + + if (level <= AV_LOG_INFO) + return G_LOG_LEVEL_MESSAGE; + + return G_LOG_LEVEL_DEBUG; +} + +static void +mpd_ffmpeg_log_callback(G_GNUC_UNUSED void *ptr, int level, + const char *fmt, va_list vl) +{ + const AVClass * cls = NULL; + + if (ptr != NULL) + cls = *(const AVClass *const*)ptr; + + if (cls != NULL) { + char *domain = g_strconcat(G_LOG_DOMAIN, "/", cls->item_name(ptr), NULL); + g_logv(domain, level_ffmpeg_to_glib(level), fmt, vl); + g_free(domain); + } +} + +#endif /* !OLD_FFMPEG_INCLUDES */ struct mpd_ffmpeg_stream { struct decoder *decoder; @@ -79,7 +107,7 @@ mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence) if (whence == AVSEEK_SIZE) return stream->input->size; - ret = input_stream_seek(stream->input, pos, whence); + ret = input_stream_seek(stream->input, pos, whence, NULL); if (!ret) return -1; @@ -115,6 +143,10 @@ mpd_ffmpeg_stream_close(struct mpd_ffmpeg_stream *stream) static bool ffmpeg_init(G_GNUC_UNUSED const struct config_param *param) { +#ifndef OLD_FFMPEG_INCLUDES + av_log_set_callback(mpd_ffmpeg_log_callback); +#endif + av_register_all(); return true; } @@ -130,9 +162,95 @@ ffmpeg_find_audio_stream(const AVFormatContext *format_context) return -1; } +/** + * On some platforms, libavcodec wants the output buffer aligned to 16 + * bytes (because it uses SSE/Altivec internally). This function + * returns the aligned version of the specified buffer, and corrects + * the buffer size. + */ +static void * +align16(void *p, size_t *length_p) +{ + unsigned add = 16 - (size_t)p % 16; + + *length_p -= add; + return (char *)p + add; +} + +static enum decoder_command +ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is, + const AVPacket *packet, + AVCodecContext *codec_context, + const AVRational *time_base) +{ + enum decoder_command cmd = DECODE_COMMAND_NONE; + uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16]; + int16_t *aligned_buffer; + size_t buffer_size; + int len, audio_size; + uint8_t *packet_data; + int packet_size; + + if (packet->pts != (int64_t)AV_NOPTS_VALUE) + decoder_timestamp(decoder, + av_rescale_q(packet->pts, *time_base, + (AVRational){1, 1})); + + packet_data = packet->data; + packet_size = packet->size; + + buffer_size = sizeof(audio_buf); + aligned_buffer = align16(audio_buf, &buffer_size); + + while ((packet_size > 0) && (cmd == DECODE_COMMAND_NONE)) { + audio_size = buffer_size; + len = avcodec_decode_audio2(codec_context, + aligned_buffer, &audio_size, + packet_data, packet_size); + + if (len < 0) { + /* if error, we skip the frame */ + g_message("decoding failed\n"); + break; + } + + packet_data += len; + packet_size -= len; + + if (audio_size <= 0) + continue; + + cmd = decoder_data(decoder, is, + aligned_buffer, audio_size, + codec_context->bit_rate / 1000); + } + return cmd; +} + +static enum sample_format +ffmpeg_sample_format(G_GNUC_UNUSED const AVCodecContext *codec_context) +{ +#if LIBAVCODEC_VERSION_INT >= ((51<<16)+(41<<8)+0) + switch (codec_context->sample_fmt) { + case SAMPLE_FMT_S16: + return SAMPLE_FORMAT_S16; + + case SAMPLE_FMT_S32: + return SAMPLE_FORMAT_S32; + + default: + g_warning("Unsupported libavcodec SampleFormat value: %d", + codec_context->sample_fmt); + return SAMPLE_FORMAT_UNDEFINED; + } +#else + /* XXX fixme 16-bit for older ffmpeg (13 Aug 2007) */ + return SAMPLE_FORMAT_S16; +#endif +} + static AVInputFormat * -ffmpeg_probe(struct decoder *decoder, struct input_stream *is, - const char *uri) +ffmpeg_probe(struct decoder *decoder, struct input_stream *is) { enum { BUFFER_SIZE = 16384, @@ -141,7 +259,7 @@ ffmpeg_probe(struct decoder *decoder, struct input_stream *is, unsigned char *buffer = g_malloc(BUFFER_SIZE); size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE); - if (nbytes <= PADDING || !input_stream_seek(is, 0, SEEK_SET)) { + if (nbytes <= PADDING || !input_stream_seek(is, 0, SEEK_SET, NULL)) { g_free(buffer); return NULL; } @@ -155,7 +273,7 @@ ffmpeg_probe(struct decoder *decoder, struct input_stream *is, AVProbeData avpd = { .buf = buffer, .buf_size = nbytes, - .filename = uri, + .filename = is->uri, }; AVInputFormat *format = av_probe_input_format(&avpd, true); @@ -164,15 +282,12 @@ ffmpeg_probe(struct decoder *decoder, struct input_stream *is, return format; } -static bool -ffmpeg_helper(const char *uri, - struct decoder *decoder, struct input_stream *input, - bool (*callback)(struct ffmpeg_context *ctx), - struct ffmpeg_context *ctx) +static void +ffmpeg_decode(struct decoder *decoder, struct input_stream *input) { - AVInputFormat *input_format = ffmpeg_probe(decoder, input, uri); + AVInputFormat *input_format = ffmpeg_probe(decoder, input); if (input_format == NULL) - return false; + return; g_debug("detected input format '%s' (%s)", input_format->name, input_format->long_name); @@ -181,28 +296,27 @@ ffmpeg_helper(const char *uri, mpd_ffmpeg_stream_open(decoder, input); if (stream == NULL) { g_warning("Failed to open stream"); - return false; + return; } AVFormatContext *format_context; AVCodecContext *codec_context; AVCodec *codec; int audio_stream; - bool ret; //ffmpeg works with ours "fileops" helper - if (av_open_input_stream(&format_context, stream->io, uri, + if (av_open_input_stream(&format_context, stream->io, input->uri, input_format, NULL) != 0) { g_warning("Open failed\n"); mpd_ffmpeg_stream_close(stream); - return false; + return; } if (av_find_stream_info(format_context)<0) { g_warning("Couldn't find stream info\n"); av_close_input_stream(format_context); mpd_ffmpeg_stream_close(stream); - return false; + return; } audio_stream = ffmpeg_find_audio_stream(format_context); @@ -210,7 +324,7 @@ ffmpeg_helper(const char *uri, g_warning("No audio stream inside\n"); av_close_input_stream(format_context); mpd_ffmpeg_stream_close(stream); - return false; + return; } codec_context = format_context->streams[audio_stream]->codec; @@ -223,145 +337,48 @@ ffmpeg_helper(const char *uri, g_warning("Unsupported audio codec\n"); av_close_input_stream(format_context); mpd_ffmpeg_stream_close(stream); - return false; + return; } if (avcodec_open(codec_context, codec)<0) { g_warning("Could not open codec\n"); av_close_input_stream(format_context); mpd_ffmpeg_stream_close(stream); - return false; - } - - if (callback) { - ctx->audio_stream = audio_stream; - ctx->format_context = format_context; - ctx->codec_context = codec_context; - - ret = callback(ctx); - } else - ret = true; - - avcodec_close(codec_context); - av_close_input_stream(format_context); - mpd_ffmpeg_stream_close(stream); - - return ret; -} - -/** - * On some platforms, libavcodec wants the output buffer aligned to 16 - * bytes (because it uses SSE/Altivec internally). This function - * returns the aligned version of the specified buffer, and corrects - * the buffer size. - */ -static void * -align16(void *p, size_t *length_p) -{ - unsigned add = 16 - (size_t)p % 16; - - *length_p -= add; - return (char *)p + add; -} - -static enum decoder_command -ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is, - const AVPacket *packet, - AVCodecContext *codec_context, - const AVRational *time_base) -{ - enum decoder_command cmd = DECODE_COMMAND_NONE; - int position; - uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16]; - int16_t *aligned_buffer; - size_t buffer_size; - int len, audio_size; - uint8_t *packet_data; - int packet_size; - - packet_data = packet->data; - packet_size = packet->size; - - buffer_size = sizeof(audio_buf); - aligned_buffer = align16(audio_buf, &buffer_size); - - while ((packet_size > 0) && (cmd == DECODE_COMMAND_NONE)) { - audio_size = buffer_size; - len = avcodec_decode_audio2(codec_context, - aligned_buffer, &audio_size, - packet_data, packet_size); - - if (len < 0) { - /* if error, we skip the frame */ - g_message("decoding failed\n"); - break; - } - - packet_data += len; - packet_size -= len; - - if (audio_size <= 0) - continue; - - position = packet->pts != (int64_t)AV_NOPTS_VALUE - ? av_rescale_q(packet->pts, *time_base, - (AVRational){1, 1}) - : 0; - - cmd = decoder_data(decoder, is, - aligned_buffer, audio_size, - position, - codec_context->bit_rate / 1000, NULL); + return; } - return cmd; -} -static bool -ffmpeg_decode_internal(struct ffmpeg_context *ctx) -{ - struct decoder *decoder = ctx->decoder; - AVCodecContext *codec_context = ctx->codec_context; - AVFormatContext *format_context = ctx->format_context; - AVPacket packet; + GError *error = NULL; struct audio_format audio_format; - enum decoder_command cmd; - int total_time; - - total_time = 0; - -#if LIBAVCODEC_VERSION_INT >= ((51<<16)+(41<<8)+0) - audio_format.bits = (uint8_t) av_get_bits_per_sample_format(codec_context->sample_fmt); -#else - /* XXX fixme 16-bit for older ffmpeg (13 Aug 2007) */ - audio_format.bits = (uint8_t) 16; -#endif - audio_format.sample_rate = (unsigned int)codec_context->sample_rate; - audio_format.channels = codec_context->channels; - - if (!audio_format_valid(&audio_format)) { - g_warning("Invalid audio format: %u:%u:%u\n", - audio_format.sample_rate, audio_format.bits, - audio_format.channels); - return false; + if (!audio_format_init_checked(&audio_format, + codec_context->sample_rate, + ffmpeg_sample_format(codec_context), + codec_context->channels, &error)) { + g_warning("%s", error->message); + g_error_free(error); + avcodec_close(codec_context); + av_close_input_stream(format_context); + mpd_ffmpeg_stream_close(stream); + return; } - //there is some problem with this on some demux (mp3 at least) - if (format_context->duration != (int64_t)AV_NOPTS_VALUE) { - total_time = format_context->duration / AV_TIME_BASE; - } + int total_time = format_context->duration != (int64_t)AV_NOPTS_VALUE + ? format_context->duration / AV_TIME_BASE + : 0; decoder_initialized(decoder, &audio_format, - ctx->input->seekable, total_time); + input->seekable, total_time); + enum decoder_command cmd; do { + AVPacket packet; if (av_read_frame(format_context, &packet) < 0) /* end of file */ break; - if (packet.stream_index == ctx->audio_stream) - cmd = ffmpeg_send_packet(decoder, ctx->input, + if (packet.stream_index == audio_stream) + cmd = ffmpeg_send_packet(decoder, input, &packet, codec_context, - &format_context->streams[ctx->audio_stream]->time_base); + &format_context->streams[audio_stream]->time_base); else cmd = decoder_get_command(decoder); @@ -378,115 +395,121 @@ ffmpeg_decode_internal(struct ffmpeg_context *ctx) } } while (cmd != DECODE_COMMAND_STOP); - return true; + avcodec_close(codec_context); + av_close_input_stream(format_context); + mpd_ffmpeg_stream_close(stream); } -static void -ffmpeg_decode(struct decoder *decoder, struct input_stream *input) -{ - struct ffmpeg_context ctx; - - ctx.input = input; - ctx.decoder = decoder; +#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0) +typedef struct ffmpeg_tag_map { + enum tag_type type; + const char *name; +} ffmpeg_tag_map; - char *uri = decoder_get_uri(decoder); - ffmpeg_helper(uri, decoder, input, - ffmpeg_decode_internal, &ctx); - g_free(uri); -} +static const ffmpeg_tag_map ffmpeg_tag_maps[] = { + { TAG_TITLE, "title" }, +#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(50<<8)) + { TAG_ARTIST, "artist" }, + { TAG_DATE, "date" }, +#else + { TAG_ARTIST, "author" }, + { TAG_DATE, "year" }, +#endif + { TAG_ALBUM, "album" }, + { TAG_COMMENT, "comment" }, + { TAG_GENRE, "genre" }, + { TAG_TRACK, "track" }, + { TAG_ARTIST_SORT, "author-sort" }, + { TAG_ALBUM_ARTIST, "album_artist" }, + { TAG_ALBUM_ARTIST_SORT, "album_artist-sort" }, + { TAG_COMPOSER, "composer" }, + { TAG_PERFORMER, "performer" }, + { TAG_DISC, "disc" }, +}; -#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0) static bool ffmpeg_copy_metadata(struct tag *tag, AVMetadata *m, - enum tag_type type, const char *name) + const ffmpeg_tag_map tag_map) { - AVMetadataTag *mt = av_metadata_get(m, name, NULL, 0); - if (mt != NULL) - tag_add_item(tag, type, mt->value); + AVMetadataTag *mt = NULL; + + while ((mt = av_metadata_get(m, tag_map.name, mt, 0)) != NULL) + tag_add_item(tag, tag_map.type, mt->value); return mt != NULL; } + #endif -static bool ffmpeg_tag_internal(struct ffmpeg_context *ctx) +//no tag reading in ffmpeg, check if playable +static struct tag * +ffmpeg_stream_tag(struct input_stream *is) { - struct tag *tag = (struct tag *) ctx->tag; - AVFormatContext *f = ctx->format_context; + AVInputFormat *input_format = ffmpeg_probe(NULL, is); + if (input_format == NULL) + return NULL; - tag->time = 0; - if (f->duration != (int64_t)AV_NOPTS_VALUE) - tag->time = f->duration / AV_TIME_BASE; + struct mpd_ffmpeg_stream *stream = mpd_ffmpeg_stream_open(NULL, is); + if (stream == NULL) + return NULL; + + AVFormatContext *f; + if (av_open_input_stream(&f, stream->io, is->uri, + input_format, NULL) != 0) { + mpd_ffmpeg_stream_close(stream); + return NULL; + } + + if (av_find_stream_info(f) < 0) { + av_close_input_stream(f); + mpd_ffmpeg_stream_close(stream); + return NULL; + } + + struct tag *tag = tag_new(); + + tag->time = f->duration != (int64_t)AV_NOPTS_VALUE + ? f->duration / AV_TIME_BASE + : 0; #if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0) av_metadata_conv(f, NULL, f->iformat->metadata_conv); - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TITLE, "title"); -#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(50<<8)) - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ARTIST, "artist"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DATE, "date"); -#else - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ARTIST, "author"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DATE, "year"); -#endif - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ALBUM, "album"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_COMMENT, "comment"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_GENRE, "genre"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TRACK, "track"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ALBUM_ARTIST, "album_artist"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_COMPOSER, "composer"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_PERFORMER, "performer"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DISC, "disc"); + for (unsigned i = 0; i < sizeof(ffmpeg_tag_maps)/sizeof(ffmpeg_tag_map); i++) { + int idx = ffmpeg_find_audio_stream(f); + ffmpeg_copy_metadata(tag, f->metadata, ffmpeg_tag_maps[i]); + if (idx >= 0) + ffmpeg_copy_metadata(tag, f->streams[idx]->metadata, ffmpeg_tag_maps[i]); + } #else if (f->author[0]) - tag_add_item(tag, TAG_ITEM_ARTIST, f->author); + tag_add_item(tag, TAG_ARTIST, f->author); if (f->title[0]) - tag_add_item(tag, TAG_ITEM_TITLE, f->title); + tag_add_item(tag, TAG_TITLE, f->title); if (f->album[0]) - tag_add_item(tag, TAG_ITEM_ALBUM, f->album); + tag_add_item(tag, TAG_ALBUM, f->album); if (f->track > 0) { char buffer[16]; snprintf(buffer, sizeof(buffer), "%d", f->track); - tag_add_item(tag, TAG_ITEM_TRACK, buffer); + tag_add_item(tag, TAG_TRACK, buffer); } if (f->comment[0]) - tag_add_item(tag, TAG_ITEM_COMMENT, f->comment); + tag_add_item(tag, TAG_COMMENT, f->comment); if (f->genre[0]) - tag_add_item(tag, TAG_ITEM_GENRE, f->genre); + tag_add_item(tag, TAG_GENRE, f->genre); if (f->year > 0) { char buffer[16]; snprintf(buffer, sizeof(buffer), "%d", f->year); - tag_add_item(tag, TAG_ITEM_DATE, buffer); + tag_add_item(tag, TAG_DATE, buffer); } #endif - return true; -} - -//no tag reading in ffmpeg, check if playable -static struct tag *ffmpeg_tag(const char *file) -{ - struct input_stream input; - struct ffmpeg_context ctx; - bool ret; - - if (!input_stream_open(&input, file)) { - g_warning("failed to open %s\n", file); - return NULL; - } - - ctx.decoder = NULL; - ctx.tag = tag_new(); - - ret = ffmpeg_helper(file, NULL, &input, ffmpeg_tag_internal, &ctx); - if (!ret) { - tag_free(ctx.tag); - ctx.tag = NULL; - } - input_stream_close(&input); + av_close_input_stream(f); + mpd_ffmpeg_stream_close(stream); - return ctx.tag; + return tag; } /** @@ -592,6 +615,12 @@ static const char *const ffmpeg_mime_types[] = { "video/x-vid", "video/x-wmv", "video/x-xvid", + + /* special value for the "ffmpeg" input plugin: all streams by + the "ffmpeg" input plugin shall be decoded by this + plugin */ + "audio/x-mpd-ffmpeg", + NULL }; @@ -599,7 +628,7 @@ const struct decoder_plugin ffmpeg_decoder_plugin = { .name = "ffmpeg", .init = ffmpeg_init, .stream_decode = ffmpeg_decode, - .tag_dup = ffmpeg_tag, + .stream_tag = ffmpeg_stream_tag, .suffixes = ffmpeg_suffixes, .mime_types = ffmpeg_mime_types }; diff --git a/src/decoder/flac_compat.h b/src/decoder/flac_compat.h new file mode 100644 index 000000000..d597690a0 --- /dev/null +++ b/src/decoder/flac_compat.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Common data structures and functions used by FLAC and OggFLAC + */ + +#ifndef MPD_FLAC_COMPAT_H +#define MPD_FLAC_COMPAT_H + +#include <FLAC/export.h> +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 +# include <FLAC/seekable_stream_decoder.h> + +/* starting with libFLAC 1.1.3, the SeekableStreamDecoder has been + merged into the StreamDecoder. The following macros try to emulate + the new API for libFLAC 1.1.2 by mapping MPD's StreamDecoder calls + to the old SeekableStreamDecoder API. */ + +#define FLAC__StreamDecoder FLAC__SeekableStreamDecoder +#define FLAC__stream_decoder_new FLAC__seekable_stream_decoder_new +#define FLAC__stream_decoder_get_decode_position FLAC__seekable_stream_decoder_get_decode_position +#define FLAC__stream_decoder_get_state FLAC__seekable_stream_decoder_get_state +#define FLAC__stream_decoder_process_single FLAC__seekable_stream_decoder_process_single +#define FLAC__stream_decoder_process_until_end_of_metadata FLAC__seekable_stream_decoder_process_until_end_of_metadata +#define FLAC__stream_decoder_seek_absolute FLAC__seekable_stream_decoder_seek_absolute +#define FLAC__stream_decoder_finish FLAC__seekable_stream_decoder_finish +#define FLAC__stream_decoder_delete FLAC__seekable_stream_decoder_delete + +#define FLAC__STREAM_DECODER_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM + +typedef unsigned flac_read_status_size_t; + +#define FLAC__StreamDecoderReadStatus FLAC__SeekableStreamDecoderReadStatus +#define FLAC__STREAM_DECODER_READ_STATUS_CONTINUE FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK +#define FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK +#define FLAC__STREAM_DECODER_READ_STATUS_ABORT FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR + +#define FLAC__StreamDecoderSeekStatus FLAC__SeekableStreamDecoderSeekStatus +#define FLAC__STREAM_DECODER_SEEK_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK +#define FLAC__STREAM_DECODER_SEEK_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR +#define FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR + +#define FLAC__StreamDecoderTellStatus FLAC__SeekableStreamDecoderTellStatus +#define FLAC__STREAM_DECODER_TELL_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK +#define FLAC__STREAM_DECODER_TELL_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR +#define FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR + +#define FLAC__StreamDecoderLengthStatus FLAC__SeekableStreamDecoderLengthStatus +#define FLAC__STREAM_DECODER_LENGTH_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK +#define FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR +#define FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR + +typedef enum { + FLAC__STREAM_DECODER_INIT_STATUS_OK, + FLAC__STREAM_DECODER_INIT_STATUS_ERROR, +} FLAC__StreamDecoderInitStatus; + +static inline FLAC__StreamDecoderInitStatus +FLAC__stream_decoder_init_stream(FLAC__SeekableStreamDecoder *decoder, + FLAC__SeekableStreamDecoderReadCallback read_cb, + FLAC__SeekableStreamDecoderSeekCallback seek_cb, + FLAC__SeekableStreamDecoderTellCallback tell_cb, + FLAC__SeekableStreamDecoderLengthCallback length_cb, + FLAC__SeekableStreamDecoderEofCallback eof_cb, + FLAC__SeekableStreamDecoderWriteCallback write_cb, + FLAC__SeekableStreamDecoderMetadataCallback metadata_cb, + FLAC__SeekableStreamDecoderErrorCallback error_cb, + void *data) +{ + return FLAC__seekable_stream_decoder_set_read_callback(decoder, read_cb) && + FLAC__seekable_stream_decoder_set_seek_callback(decoder, seek_cb) && + FLAC__seekable_stream_decoder_set_tell_callback(decoder, tell_cb) && + FLAC__seekable_stream_decoder_set_length_callback(decoder, length_cb) && + FLAC__seekable_stream_decoder_set_eof_callback(decoder, eof_cb) && + FLAC__seekable_stream_decoder_set_write_callback(decoder, write_cb) && + FLAC__seekable_stream_decoder_set_metadata_callback(decoder, metadata_cb) && + FLAC__seekable_stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT) && + FLAC__seekable_stream_decoder_set_error_callback(decoder, error_cb) && + FLAC__seekable_stream_decoder_set_client_data(decoder, data) && + FLAC__seekable_stream_decoder_init(decoder) == FLAC__SEEKABLE_STREAM_DECODER_OK + ? FLAC__STREAM_DECODER_INIT_STATUS_OK + : FLAC__STREAM_DECODER_INIT_STATUS_ERROR; +} + +#else /* FLAC_API_VERSION_CURRENT > 7 */ + +# include <FLAC/stream_decoder.h> + +# define flac_init(a,b,c,d,e,f,g,h,i,j) \ + (FLAC__stream_decoder_init_stream(a,b,c,d,e,f,g,h,i,j) \ + == FLAC__STREAM_DECODER_INIT_STATUS_OK) + +typedef size_t flac_read_status_size_t; + +#endif /* FLAC_API_VERSION_CURRENT >= 7 */ + +#endif /* _FLAC_COMMON_H */ diff --git a/src/decoder/flac_decoder_plugin.c b/src/decoder/flac_decoder_plugin.c new file mode 100644 index 000000000..e89e2ea11 --- /dev/null +++ b/src/decoder/flac_decoder_plugin.c @@ -0,0 +1,497 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "_flac_common.h" +#include "flac_compat.h" +#include "flac_metadata.h" + +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 +#include "_ogg_common.h" +#endif + +#include <glib.h> + +#include <assert.h> +#include <unistd.h> + +#include <sys/stat.h> +#include <sys/types.h> + +/* this code was based on flac123, from flac-tools */ + +static FLAC__StreamDecoderReadStatus +flac_read_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, + FLAC__byte buf[], flac_read_status_size_t *bytes, + void *fdata) +{ + struct flac_data *data = fdata; + size_t r; + + r = decoder_read(data->decoder, data->input_stream, + (void *)buf, *bytes); + *bytes = r; + + if (r == 0) { + if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE || + input_stream_eof(data->input_stream)) + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + else + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; + } + + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; +} + +static FLAC__StreamDecoderSeekStatus +flac_seek_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, + FLAC__uint64 offset, void *fdata) +{ + struct flac_data *data = (struct flac_data *) fdata; + + if (!data->input_stream->seekable) + return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; + + if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL)) + return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; + + return FLAC__STREAM_DECODER_SEEK_STATUS_OK; +} + +static FLAC__StreamDecoderTellStatus +flac_tell_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, + FLAC__uint64 * offset, void *fdata) +{ + struct flac_data *data = (struct flac_data *) fdata; + + if (!data->input_stream->seekable) + return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; + + *offset = (long)(data->input_stream->offset); + + return FLAC__STREAM_DECODER_TELL_STATUS_OK; +} + +static FLAC__StreamDecoderLengthStatus +flac_length_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, + FLAC__uint64 * length, void *fdata) +{ + struct flac_data *data = (struct flac_data *) fdata; + + if (data->input_stream->size < 0) + return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; + + *length = (size_t) (data->input_stream->size); + + return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; +} + +static FLAC__bool +flac_eof_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, void *fdata) +{ + struct flac_data *data = (struct flac_data *) fdata; + + return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE && + decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) || + input_stream_eof(data->input_stream); +} + +static void +flac_error_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, + FLAC__StreamDecoderErrorStatus status, void *fdata) +{ + flac_error_common_cb("flac", status, (struct flac_data *) fdata); +} + +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 +static void flacPrintErroredState(FLAC__SeekableStreamDecoderState state) +{ + const char *str = ""; /* "" to silence compiler warning */ + switch (state) { + case FLAC__SEEKABLE_STREAM_DECODER_OK: + case FLAC__SEEKABLE_STREAM_DECODER_SEEKING: + case FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM: + return; + case FLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR: + str = "allocation error"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_READ_ERROR: + str = "read error"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR: + str = "seek error"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR: + str = "seekable stream error"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED: + str = "decoder already initialized"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK: + str = "invalid callback"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED: + str = "decoder uninitialized"; + } + + g_warning("%s\n", str); +} +#else /* FLAC_API_VERSION_CURRENT >= 7 */ +static void flacPrintErroredState(FLAC__StreamDecoderState state) +{ + const char *str = ""; /* "" to silence compiler warning */ + switch (state) { + case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA: + case FLAC__STREAM_DECODER_READ_METADATA: + case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC: + case FLAC__STREAM_DECODER_READ_FRAME: + case FLAC__STREAM_DECODER_END_OF_STREAM: + return; + case FLAC__STREAM_DECODER_OGG_ERROR: + str = "error in the Ogg layer"; + break; + case FLAC__STREAM_DECODER_SEEK_ERROR: + str = "seek error"; + break; + case FLAC__STREAM_DECODER_ABORTED: + str = "decoder aborted by read"; + break; + case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR: + str = "allocation error"; + break; + case FLAC__STREAM_DECODER_UNINITIALIZED: + str = "decoder uninitialized"; + } + + g_warning("%s\n", str); +} +#endif /* FLAC_API_VERSION_CURRENT >= 7 */ + +static void flacMetadata(G_GNUC_UNUSED const FLAC__StreamDecoder * dec, + const FLAC__StreamMetadata * block, void *vdata) +{ + flac_metadata_common_cb(block, (struct flac_data *) vdata); +} + +static FLAC__StreamDecoderWriteStatus +flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame, + const FLAC__int32 *const buf[], void *vdata) +{ + struct flac_data *data = (struct flac_data *) vdata; + FLAC__uint64 nbytes = 0; + + if (FLAC__stream_decoder_get_decode_position(dec, &nbytes)) { + if (data->position > 0 && nbytes > data->position) { + nbytes -= data->position; + data->position += nbytes; + } else { + data->position = nbytes; + nbytes = 0; + } + } else + nbytes = 0; + + return flac_common_write(data, frame, buf, nbytes); +} + +static struct tag * +flac_tag_dup(const char *file) +{ + return flac_tag_load(file, NULL); +} + +/** + * Some glue code around FLAC__stream_decoder_new(). + */ +static FLAC__StreamDecoder * +flac_decoder_new(void) +{ + FLAC__StreamDecoder *sd = FLAC__stream_decoder_new(); + if (sd == NULL) { + g_warning("FLAC__stream_decoder_new() failed"); + return NULL; + } + +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT)) + g_debug("FLAC__stream_decoder_set_metadata_respond() has failed"); +#endif + + return sd; +} + +static bool +flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd, + FLAC__uint64 duration) +{ + data->total_frames = duration; + + if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) { + g_warning("problem reading metadata"); + return false; + } + + if (data->initialized) { + /* done */ + decoder_initialized(data->decoder, &data->audio_format, + data->input_stream->seekable, + (float)data->total_frames / + (float)data->audio_format.sample_rate); + return true; + } + + if (data->input_stream->seekable) + /* allow the workaround below only for nonseekable + streams*/ + return false; + + /* no stream_info packet found; try to initialize the decoder + from the first frame header */ + FLAC__stream_decoder_process_single(sd); + return data->initialized; +} + +static void +flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec, + FLAC__uint64 t_start, FLAC__uint64 t_end) +{ + struct decoder *decoder = data->decoder; + enum decoder_command cmd; + + data->first_frame = t_start; + + while (true) { + if (data->tag != NULL && !tag_is_empty(data->tag)) { + cmd = decoder_tag(data->decoder, data->input_stream, + data->tag); + tag_free(data->tag); + data->tag = tag_new(); + } else + cmd = decoder_get_command(decoder); + + if (cmd == DECODE_COMMAND_SEEK) { + FLAC__uint64 seek_sample = t_start + + decoder_seek_where(decoder) * + data->audio_format.sample_rate; + if (seek_sample >= t_start && + (t_end == 0 || seek_sample <= t_end) && + FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) { + data->next_frame = seek_sample; + data->position = 0; + decoder_command_finished(decoder); + } else + decoder_seek_error(decoder); + } else if (cmd == DECODE_COMMAND_STOP || + FLAC__stream_decoder_get_state(flac_dec) == FLAC__STREAM_DECODER_END_OF_STREAM) + break; + + if (t_end != 0 && data->next_frame >= t_end) + /* end of this sub track */ + break; + + if (!FLAC__stream_decoder_process_single(flac_dec)) { + cmd = decoder_get_command(decoder); + if (cmd != DECODE_COMMAND_SEEK) + break; + } + } + + if (cmd != DECODE_COMMAND_STOP) { + flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec)); + FLAC__stream_decoder_finish(flac_dec); + } +} + +static void +flac_decode_internal(struct decoder * decoder, + struct input_stream *input_stream, + bool is_ogg) +{ + FLAC__StreamDecoder *flac_dec; + struct flac_data data; + const char *err = NULL; + + flac_dec = flac_decoder_new(); + if (flac_dec == NULL) + return; + + flac_data_init(&data, decoder, input_stream); + data.tag = tag_new(); + + if (is_ogg) { +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + FLAC__StreamDecoderInitStatus status = + FLAC__stream_decoder_init_ogg_stream(flac_dec, + flac_read_cb, + flac_seek_cb, + flac_tell_cb, + flac_length_cb, + flac_eof_cb, + flac_write_cb, + flacMetadata, + flac_error_cb, + (void *)&data); + if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { + err = "doing Ogg init()"; + goto fail; + } +#else + goto fail; +#endif + } else { + FLAC__StreamDecoderInitStatus status = + FLAC__stream_decoder_init_stream(flac_dec, + flac_read_cb, + flac_seek_cb, + flac_tell_cb, + flac_length_cb, + flac_eof_cb, + flac_write_cb, + flacMetadata, + flac_error_cb, + (void *)&data); + if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { + err = "doing init()"; + goto fail; + } + } + + if (!flac_decoder_initialize(&data, flac_dec, 0)) { + flac_data_deinit(&data); + FLAC__stream_decoder_delete(flac_dec); + return; + } + + flac_decoder_loop(&data, flac_dec, 0, 0); + +fail: + flac_data_deinit(&data); + FLAC__stream_decoder_delete(flac_dec); + + if (err) + g_warning("%s\n", err); +} + +static void +flac_decode(struct decoder * decoder, struct input_stream *input_stream) +{ + flac_decode_internal(decoder, input_stream, false); +} + +#ifndef HAVE_OGGFLAC + +static bool +oggflac_init(G_GNUC_UNUSED const struct config_param *param) +{ +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + return !!FLAC_API_SUPPORTS_OGG_FLAC; +#else + /* disable oggflac when libflac is too old */ + return false; +#endif +} + +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + +static struct tag * +oggflac_tag_dup(const char *file) +{ + struct tag *ret = NULL; + FLAC__Metadata_Iterator *it; + FLAC__StreamMetadata *block; + FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new(); + + if (!(FLAC__metadata_chain_read_ogg(chain, file))) + goto out; + it = FLAC__metadata_iterator_new(); + FLAC__metadata_iterator_init(it, chain); + + ret = tag_new(); + do { + if (!(block = FLAC__metadata_iterator_get_block(it))) + break; + + flac_tag_apply_metadata(ret, NULL, block); + } while (FLAC__metadata_iterator_next(it)); + FLAC__metadata_iterator_delete(it); + + if (!tag_is_defined(ret)) { + tag_free(ret); + ret = NULL; + } + +out: + FLAC__metadata_chain_delete(chain); + return ret; +} + +static void +oggflac_decode(struct decoder *decoder, struct input_stream *input_stream) +{ + if (ogg_stream_type_detect(input_stream) != FLAC) + return; + + /* rewind the stream, because ogg_stream_type_detect() has + moved it */ + input_stream_seek(input_stream, 0, SEEK_SET, NULL); + + flac_decode_internal(decoder, input_stream, true); +} + +static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL }; +static const char *const oggflac_mime_types[] = { + "application/ogg", + "application/x-ogg", + "audio/ogg", + "audio/x-flac+ogg", + "audio/x-ogg", + NULL +}; + +#endif /* FLAC_API_VERSION_CURRENT >= 7 */ + +const struct decoder_plugin oggflac_decoder_plugin = { + .name = "oggflac", + .init = oggflac_init, +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + .stream_decode = oggflac_decode, + .tag_dup = oggflac_tag_dup, + .suffixes = oggflac_suffixes, + .mime_types = oggflac_mime_types +#endif +}; + +#endif /* HAVE_OGGFLAC */ + +static const char *const flac_suffixes[] = { "flac", NULL }; +static const char *const flac_mime_types[] = { + "application/flac", + "application/x-flac", + "audio/flac", + "audio/x-flac", + NULL +}; + +const struct decoder_plugin flac_decoder_plugin = { + .name = "flac", + .stream_decode = flac_decode, + .tag_dup = flac_tag_dup, + .suffixes = flac_suffixes, + .mime_types = flac_mime_types, +}; diff --git a/src/decoder/flac_metadata.c b/src/decoder/flac_metadata.c new file mode 100644 index 000000000..f2f2f954d --- /dev/null +++ b/src/decoder/flac_metadata.c @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "flac_metadata.h" +#include "replay_gain_info.h" +#include "tag.h" + +#include <glib.h> + +#include <assert.h> +#include <stdbool.h> +#include <stdlib.h> + +static bool +flac_find_float_comment(const FLAC__StreamMetadata *block, + const char *cmnt, float *fl) +{ + int offset; + size_t pos; + int len; + unsigned char tmp, *p; + + offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, + cmnt); + if (offset < 0) + return false; + + pos = strlen(cmnt) + 1; /* 1 is for '=' */ + len = block->data.vorbis_comment.comments[offset].length - pos; + if (len <= 0) + return false; + + p = &block->data.vorbis_comment.comments[offset].entry[pos]; + tmp = p[len]; + p[len] = '\0'; + *fl = (float)atof((char *)p); + p[len] = tmp; + + return true; +} + +bool +flac_parse_replay_gain(struct replay_gain_info *rgi, + const FLAC__StreamMetadata *block) +{ + bool found = false; + + replay_gain_info_init(rgi); + + if (flac_find_float_comment(block, "replaygain_album_gain", + &rgi->tuples[REPLAY_GAIN_ALBUM].gain)) + found = true; + if (flac_find_float_comment(block, "replaygain_album_peak", + &rgi->tuples[REPLAY_GAIN_ALBUM].peak)) + found = true; + if (flac_find_float_comment(block, "replaygain_track_gain", + &rgi->tuples[REPLAY_GAIN_TRACK].gain)) + found = true; + if (flac_find_float_comment(block, "replaygain_track_peak", + &rgi->tuples[REPLAY_GAIN_TRACK].peak)) + found = true; + + return found; +} + +static bool +flac_find_string_comment(const FLAC__StreamMetadata *block, + const char *cmnt, char **str) +{ + int offset; + size_t pos; + int len; + const unsigned char *p; + + *str = NULL; + offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, + cmnt); + if (offset < 0) + return false; + + pos = strlen(cmnt) + 1; /* 1 is for '=' */ + len = block->data.vorbis_comment.comments[offset].length - pos; + if (len <= 0) + return false; + + p = &block->data.vorbis_comment.comments[offset].entry[pos]; + *str = g_strndup((const char *)p, len); + + return true; +} + +bool +flac_parse_mixramp(char **mixramp_start, char **mixramp_end, + const FLAC__StreamMetadata *block) +{ + bool found = false; + + if (flac_find_string_comment(block, "mixramp_start", mixramp_start)) + found = true; + if (flac_find_string_comment(block, "mixramp_end", mixramp_end)) + found = true; + + return found; +} + +/** + * Checks if the specified name matches the entry's name, and if yes, + * returns the comment value (not null-temrinated). + */ +static const char * +flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, + const char *name, const char *char_tnum, size_t *length_r) +{ + size_t name_length = strlen(name); + size_t char_tnum_length = 0; + const char *comment = (const char*)entry->entry; + + if (entry->length <= name_length || + g_ascii_strncasecmp(comment, name, name_length) != 0) + return NULL; + + if (char_tnum != NULL) { + char_tnum_length = strlen(char_tnum); + if (entry->length > name_length + char_tnum_length + 2 && + comment[name_length] == '[' && + g_ascii_strncasecmp(comment + name_length + 1, + char_tnum, char_tnum_length) == 0 && + comment[name_length + char_tnum_length + 1] == ']') + name_length = name_length + char_tnum_length + 2; + else if (entry->length > name_length + char_tnum_length && + g_ascii_strncasecmp(comment + name_length, + char_tnum, char_tnum_length) == 0) + name_length = name_length + char_tnum_length; + } + + if (comment[name_length] == '=') { + *length_r = entry->length - name_length - 1; + return comment + name_length + 1; + } + + return NULL; +} + +/** + * Check if the comment's name equals the passed name, and if so, copy + * the comment value into the tag. + */ +static bool +flac_copy_comment(struct tag *tag, + const FLAC__StreamMetadata_VorbisComment_Entry *entry, + const char *name, enum tag_type tag_type, + const char *char_tnum) +{ + const char *value; + size_t value_length; + + value = flac_comment_value(entry, name, char_tnum, &value_length); + if (value != NULL) { + tag_add_item_n(tag, tag_type, value, value_length); + return true; + } + + return false; +} + +/* tracknumber is used in VCs, MPD uses "track" ..., all the other + * tag names match */ +static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber"; +static const char *VORBIS_COMMENT_DISC_KEY = "discnumber"; + +static void +flac_parse_comment(struct tag *tag, const char *char_tnum, + const FLAC__StreamMetadata_VorbisComment_Entry *entry) +{ + assert(tag != NULL); + + if (flac_copy_comment(tag, entry, VORBIS_COMMENT_TRACK_KEY, + TAG_TRACK, char_tnum) || + flac_copy_comment(tag, entry, VORBIS_COMMENT_DISC_KEY, + TAG_DISC, char_tnum) || + flac_copy_comment(tag, entry, "album artist", + TAG_ALBUM_ARTIST, char_tnum)) + return; + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + if (flac_copy_comment(tag, entry, + tag_item_names[i], i, char_tnum)) + return; +} + +void +flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum, + const FLAC__StreamMetadata_VorbisComment *comment) +{ + for (unsigned i = 0; i < comment->num_comments; ++i) + flac_parse_comment(tag, char_tnum, &comment->comments[i]); +} + +void +flac_tag_apply_metadata(struct tag *tag, const char *track, + const FLAC__StreamMetadata *block) +{ + switch (block->type) { + case FLAC__METADATA_TYPE_VORBIS_COMMENT: + flac_vorbis_comments_to_tag(tag, track, + &block->data.vorbis_comment); + break; + + case FLAC__METADATA_TYPE_STREAMINFO: + tag->time = flac_duration(&block->data.stream_info); + break; + + default: + break; + } +} + +struct tag * +flac_tag_load(const char *file, const char *char_tnum) +{ + struct tag *tag; + FLAC__Metadata_SimpleIterator *it; + FLAC__StreamMetadata *block = NULL; + + it = FLAC__metadata_simple_iterator_new(); + if (!FLAC__metadata_simple_iterator_init(it, file, 1, 0)) { + const char *err; + FLAC_API FLAC__Metadata_SimpleIteratorStatus s; + + s = FLAC__metadata_simple_iterator_status(it); + + switch (s) { /* slightly more human-friendly messages: */ + case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ILLEGAL_INPUT: + err = "illegal input"; + break; + case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ERROR_OPENING_FILE: + err = "error opening file"; + break; + case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_NOT_A_FLAC_FILE: + err = "not a FLAC file"; + break; + default: + err = FLAC__Metadata_SimpleIteratorStatusString[s]; + } + g_debug("Reading '%s' metadata gave the following error: %s\n", + file, err); + FLAC__metadata_simple_iterator_delete(it); + return NULL; + } + + tag = tag_new(); + do { + block = FLAC__metadata_simple_iterator_get_block(it); + if (!block) + break; + + flac_tag_apply_metadata(tag, char_tnum, block); + FLAC__metadata_object_delete(block); + } while (FLAC__metadata_simple_iterator_next(it)); + + FLAC__metadata_simple_iterator_delete(it); + + if (!tag_is_defined(tag)) { + tag_free(tag); + tag = NULL; + } + + return tag; +} diff --git a/src/decoder/flac_metadata.h b/src/decoder/flac_metadata.h new file mode 100644 index 000000000..06e691d1d --- /dev/null +++ b/src/decoder/flac_metadata.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FLAC_METADATA_H +#define MPD_FLAC_METADATA_H + +#include <stdbool.h> +#include <FLAC/metadata.h> + +struct tag; +struct replay_gain_info; + +static inline unsigned +flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info) +{ + return (stream_info->total_samples + stream_info->sample_rate - 1) / + stream_info->sample_rate; +} + +bool +flac_parse_replay_gain(struct replay_gain_info *rgi, + const FLAC__StreamMetadata *block); + +bool +flac_parse_mixramp(char **mixramp_start, char **mixramp_end, + const FLAC__StreamMetadata *block); + +void +flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum, + const FLAC__StreamMetadata_VorbisComment *comment); + +void +flac_tag_apply_metadata(struct tag *tag, const char *track, + const FLAC__StreamMetadata *block); + +struct tag * +flac_tag_load(const char *file, const char *char_tnum); + +#endif diff --git a/src/decoder/flac_pcm.c b/src/decoder/flac_pcm.c new file mode 100644 index 000000000..bf6e2612c --- /dev/null +++ b/src/decoder/flac_pcm.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "flac_pcm.h" + +#include <assert.h> + +static void flac_convert_stereo16(int16_t *dest, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + for (; position < end; ++position) { + *dest++ = buf[0][position]; + *dest++ = buf[1][position]; + } +} + +static void +flac_convert_16(int16_t *dest, + unsigned int num_channels, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + unsigned int c_chan; + + for (; position < end; ++position) + for (c_chan = 0; c_chan < num_channels; c_chan++) + *dest++ = buf[c_chan][position]; +} + +/** + * Note: this function also handles 24 bit files! + */ +static void +flac_convert_32(int32_t *dest, + unsigned int num_channels, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + unsigned int c_chan; + + for (; position < end; ++position) + for (c_chan = 0; c_chan < num_channels; c_chan++) + *dest++ = buf[c_chan][position]; +} + +static void +flac_convert_8(int8_t *dest, + unsigned int num_channels, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + unsigned int c_chan; + + for (; position < end; ++position) + for (c_chan = 0; c_chan < num_channels; c_chan++) + *dest++ = buf[c_chan][position]; +} + +void +flac_convert(void *dest, + unsigned int num_channels, enum sample_format sample_format, + const FLAC__int32 *const buf[], + unsigned int position, unsigned int end) +{ + switch (sample_format) { + case SAMPLE_FORMAT_S16: + if (num_channels == 2) + flac_convert_stereo16((int16_t*)dest, buf, + position, end); + else + flac_convert_16((int16_t*)dest, num_channels, buf, + position, end); + break; + + case SAMPLE_FORMAT_S24_P32: + case SAMPLE_FORMAT_S32: + flac_convert_32((int32_t*)dest, num_channels, buf, + position, end); + break; + + case SAMPLE_FORMAT_S8: + flac_convert_8((int8_t*)dest, num_channels, buf, + position, end); + break; + + case SAMPLE_FORMAT_S24: + case SAMPLE_FORMAT_UNDEFINED: + /* unreachable */ + assert(false); + } +} diff --git a/src/normalize.c b/src/decoder/flac_pcm.h index 63c0d15cb..bccfc645c 100644 --- a/src/normalize.c +++ b/src/decoder/flac_pcm.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,33 +17,17 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "normalize.h" -#include "compress.h" -#include "conf.h" -#include "audio_format.h" - -#define DEFAULT_VOLUME_NORMALIZATION 0 - -int normalizationEnabled; +#ifndef MPD_FLAC_PCM_H +#define MPD_FLAC_PCM_H -void initNormalization(void) -{ - normalizationEnabled = config_get_bool(CONF_VOLUME_NORMALIZATION, - DEFAULT_VOLUME_NORMALIZATION); - - if (normalizationEnabled) - CompressCfg(0, ANTICLIP, TARGET, GAINMAX, GAINSMOOTH, BUCKETS); -} +#include "audio_format.h" -void finishNormalization(void) -{ - if (normalizationEnabled) CompressFree(); -} +#include <FLAC/ordinals.h> -void normalizeData(char *buffer, int bufferSize, - const struct audio_format *format) -{ - if ((format->bits != 16) || (format->channels != 2)) return; +void +flac_convert(void *dest, + unsigned int num_channels, enum sample_format sample_format, + const FLAC__int32 *const buf[], + unsigned int position, unsigned int end); - CompressDo(buffer, bufferSize); -} +#endif diff --git a/src/decoder/flac_plugin.c b/src/decoder/flac_plugin.c deleted file mode 100644 index 1e568f70d..000000000 --- a/src/decoder/flac_plugin.c +++ /dev/null @@ -1,918 +0,0 @@ -/* - * Copyright (C) 2003-2009 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "_flac_common.h" - -#include <glib.h> - -#include <assert.h> -#include <unistd.h> - -#include <sys/stat.h> -#include <sys/types.h> - -#ifdef HAVE_CUE /* libcue */ -#include "../cue/cue_tag.h" -#endif - -/* this code was based on flac123, from flac-tools */ - -static flac_read_status -flac_read_cb(G_GNUC_UNUSED const flac_decoder *fd, - FLAC__byte buf[], flac_read_status_size_t *bytes, - void *fdata) -{ - struct flac_data *data = fdata; - size_t r; - - r = decoder_read(data->decoder, data->input_stream, - (void *)buf, *bytes); - *bytes = r; - - if (r == 0) { - if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE || - input_stream_eof(data->input_stream)) - return flac_read_status_eof; - else - return flac_read_status_abort; - } - - return flac_read_status_continue; -} - -static flac_seek_status -flac_seek_cb(G_GNUC_UNUSED const flac_decoder *fd, - FLAC__uint64 offset, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - if (!input_stream_seek(data->input_stream, offset, SEEK_SET)) - return flac_seek_status_error; - - return flac_seek_status_ok; -} - -static flac_tell_status -flac_tell_cb(G_GNUC_UNUSED const flac_decoder *fd, - FLAC__uint64 * offset, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - *offset = (long)(data->input_stream->offset); - - return flac_tell_status_ok; -} - -static flac_length_status -flac_length_cb(G_GNUC_UNUSED const flac_decoder *fd, - FLAC__uint64 * length, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - if (data->input_stream->size < 0) - return flac_length_status_unsupported; - - *length = (size_t) (data->input_stream->size); - - return flac_length_status_ok; -} - -static FLAC__bool -flac_eof_cb(G_GNUC_UNUSED const flac_decoder *fd, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE && - decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) || - input_stream_eof(data->input_stream); -} - -static void -flac_error_cb(G_GNUC_UNUSED const flac_decoder *fd, - FLAC__StreamDecoderErrorStatus status, void *fdata) -{ - flac_error_common_cb("flac", status, (struct flac_data *) fdata); -} - -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 -static void flacPrintErroredState(FLAC__SeekableStreamDecoderState state) -{ - const char *str = ""; /* "" to silence compiler warning */ - switch (state) { - case FLAC__SEEKABLE_STREAM_DECODER_OK: - case FLAC__SEEKABLE_STREAM_DECODER_SEEKING: - case FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM: - return; - case FLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR: - str = "allocation error"; - break; - case FLAC__SEEKABLE_STREAM_DECODER_READ_ERROR: - str = "read error"; - break; - case FLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR: - str = "seek error"; - break; - case FLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR: - str = "seekable stream error"; - break; - case FLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED: - str = "decoder already initialized"; - break; - case FLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK: - str = "invalid callback"; - break; - case FLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED: - str = "decoder uninitialized"; - } - - g_warning("%s\n", str); -} - -static bool -flac_init(FLAC__SeekableStreamDecoder *dec, - FLAC__SeekableStreamDecoderReadCallback read_cb, - FLAC__SeekableStreamDecoderSeekCallback seek_cb, - FLAC__SeekableStreamDecoderTellCallback tell_cb, - FLAC__SeekableStreamDecoderLengthCallback length_cb, - FLAC__SeekableStreamDecoderEofCallback eof_cb, - FLAC__SeekableStreamDecoderWriteCallback write_cb, - FLAC__SeekableStreamDecoderMetadataCallback metadata_cb, - FLAC__SeekableStreamDecoderErrorCallback error_cb, - void *data) -{ - return FLAC__seekable_stream_decoder_set_read_callback(dec, read_cb) && - FLAC__seekable_stream_decoder_set_seek_callback(dec, seek_cb) && - FLAC__seekable_stream_decoder_set_tell_callback(dec, tell_cb) && - FLAC__seekable_stream_decoder_set_length_callback(dec, length_cb) && - FLAC__seekable_stream_decoder_set_eof_callback(dec, eof_cb) && - FLAC__seekable_stream_decoder_set_write_callback(dec, write_cb) && - FLAC__seekable_stream_decoder_set_metadata_callback(dec, metadata_cb) && - FLAC__seekable_stream_decoder_set_metadata_respond(dec, FLAC__METADATA_TYPE_VORBIS_COMMENT) && - FLAC__seekable_stream_decoder_set_error_callback(dec, error_cb) && - FLAC__seekable_stream_decoder_set_client_data(dec, data) && - FLAC__seekable_stream_decoder_init(dec) == FLAC__SEEKABLE_STREAM_DECODER_OK; -} -#else /* FLAC_API_VERSION_CURRENT >= 7 */ -static void flacPrintErroredState(FLAC__StreamDecoderState state) -{ - const char *str = ""; /* "" to silence compiler warning */ - switch (state) { - case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA: - case FLAC__STREAM_DECODER_READ_METADATA: - case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC: - case FLAC__STREAM_DECODER_READ_FRAME: - case FLAC__STREAM_DECODER_END_OF_STREAM: - return; - case FLAC__STREAM_DECODER_OGG_ERROR: - str = "error in the Ogg layer"; - break; - case FLAC__STREAM_DECODER_SEEK_ERROR: - str = "seek error"; - break; - case FLAC__STREAM_DECODER_ABORTED: - str = "decoder aborted by read"; - break; - case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR: - str = "allocation error"; - break; - case FLAC__STREAM_DECODER_UNINITIALIZED: - str = "decoder uninitialized"; - } - - g_warning("%s\n", str); -} -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - -static void flacMetadata(G_GNUC_UNUSED const flac_decoder * dec, - const FLAC__StreamMetadata * block, void *vdata) -{ - flac_metadata_common_cb(block, (struct flac_data *) vdata); -} - -static FLAC__StreamDecoderWriteStatus -flac_write_cb(const flac_decoder *dec, const FLAC__Frame *frame, - const FLAC__int32 *const buf[], void *vdata) -{ - FLAC__uint32 samples = frame->header.blocksize; - struct flac_data *data = (struct flac_data *) vdata; - float timeChange; - FLAC__uint64 newPosition = 0; - - timeChange = ((float)samples) / frame->header.sample_rate; - data->time += timeChange; - - flac_get_decode_position(dec, &newPosition); - if (data->position && newPosition >= data->position) { - assert(timeChange >= 0); - - data->bit_rate = - ((newPosition - data->position) * 8.0 / timeChange) - / 1000 + 0.5; - } - data->position = newPosition; - - return flac_common_write(data, frame, buf); -} - -static struct tag * -flac_tag_load(const char *file, const char *char_tnum) -{ - struct tag *tag; - FLAC__Metadata_SimpleIterator *it; - FLAC__StreamMetadata *block = NULL; - - it = FLAC__metadata_simple_iterator_new(); - if (!FLAC__metadata_simple_iterator_init(it, file, 1, 0)) { - const char *err; - FLAC_API FLAC__Metadata_SimpleIteratorStatus s; - - s = FLAC__metadata_simple_iterator_status(it); - - switch (s) { /* slightly more human-friendly messages: */ - case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ILLEGAL_INPUT: - err = "illegal input"; - break; - case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ERROR_OPENING_FILE: - err = "error opening file"; - break; - case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_NOT_A_FLAC_FILE: - err = "not a FLAC file"; - break; - default: - err = FLAC__Metadata_SimpleIteratorStatusString[s]; - } - g_debug("Reading '%s' metadata gave the following error: %s\n", - file, err); - FLAC__metadata_simple_iterator_delete(it); - return NULL; - } - - tag = tag_new(); - do { - block = FLAC__metadata_simple_iterator_get_block(it); - if (!block) - break; - if (block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) { - flac_vorbis_comments_to_tag(tag, char_tnum, block); - } else if (block->type == FLAC__METADATA_TYPE_STREAMINFO) { - tag->time = ((float)block->data.stream_info.total_samples) / - block->data.stream_info.sample_rate + 0.5; - } - FLAC__metadata_object_delete(block); - } while (FLAC__metadata_simple_iterator_next(it)); - - FLAC__metadata_simple_iterator_delete(it); - - if (!tag_is_defined(tag)) { - tag_free(tag); - tag = NULL; - } - - return tag; -} - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - -static struct tag * -flac_cue_tag_load(const char *file) -{ - struct tag* tag = NULL; - char* char_tnum = NULL; - char* ptr = NULL; - unsigned int tnum = 0; - unsigned int sample_rate = 0; - FLAC__uint64 track_time = 0; -#ifdef HAVE_CUE /* libcue */ - FLAC__StreamMetadata* vc; -#endif /* libcue */ - FLAC__StreamMetadata* si = FLAC__metadata_object_new(FLAC__METADATA_TYPE_STREAMINFO); - FLAC__StreamMetadata* cs; - - tnum = flac_vtrack_tnum(file); - char_tnum = g_strdup_printf("%u", tnum); - - ptr = strrchr(file, '/'); - *ptr = '\0'; - -#ifdef HAVE_CUE /* libcue */ - if (FLAC__metadata_get_tags(file, &vc)) - { - for (unsigned i = 0; i < vc->data.vorbis_comment.num_comments; - i++) - { - if ((ptr = (char*)vc->data.vorbis_comment.comments[i].entry) != NULL) - { - if (g_ascii_strncasecmp(ptr, "cuesheet", 8) == 0) - { - while (*(++ptr) != '='); - tag = cue_tag_string( ++ptr, - tnum); - } - } - } - - FLAC__metadata_object_delete(vc); - } -#endif /* libcue */ - - if (tag == NULL) - tag = flac_tag_load(file, char_tnum); - - if (char_tnum != NULL) - { - tag_add_item( tag, - TAG_ITEM_TRACK, - char_tnum); - g_free(char_tnum); - } - - if (FLAC__metadata_get_streaminfo(file, si)) - { - sample_rate = si->data.stream_info.sample_rate; - FLAC__metadata_object_delete(si); - } - - if (FLAC__metadata_get_cuesheet(file, &cs)) - { - if (cs->data.cue_sheet.tracks != NULL - && (tnum <= cs->data.cue_sheet.num_tracks - 1)) - { - track_time = cs->data.cue_sheet.tracks[tnum].offset - - cs->data.cue_sheet.tracks[tnum - 1].offset; - } - FLAC__metadata_object_delete(cs); - } - - if (sample_rate != 0) - { - tag->time = (unsigned int)(track_time/sample_rate); - } - - return tag; -} - -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - -static struct tag * -flac_tag_dup(const char *file) -{ -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - struct stat st; - - if (stat(file, &st) < 0) - return flac_cue_tag_load(file); - else -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - return flac_tag_load(file, NULL); -} - -static void -flac_decode_internal(struct decoder * decoder, - struct input_stream *input_stream, - bool is_ogg) -{ - flac_decoder *flac_dec; - struct flac_data data; - enum decoder_command cmd; - const char *err = NULL; - - if (!(flac_dec = flac_new())) - return; - flac_data_init(&data, decoder, input_stream); - data.tag = tag_new(); - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - if(!FLAC__stream_decoder_set_metadata_respond(flac_dec, FLAC__METADATA_TYPE_VORBIS_COMMENT)) - { - g_debug("Failed to set metadata respond\n"); - } -#endif - - if (is_ogg) { - if (!flac_ogg_init(flac_dec, flac_read_cb, - flac_seek_cb, flac_tell_cb, - flac_length_cb, flac_eof_cb, - flac_write_cb, flacMetadata, - flac_error_cb, (void *)&data)) { - err = "doing Ogg init()"; - goto fail; - } - } else { - if (!flac_init(flac_dec, flac_read_cb, - flac_seek_cb, flac_tell_cb, - flac_length_cb, flac_eof_cb, - flac_write_cb, flacMetadata, - flac_error_cb, (void *)&data)) { - err = "doing init()"; - goto fail; - } - } - - if (!flac_process_metadata(flac_dec)) { - err = "problem reading metadata"; - goto fail; - } - - if (!audio_format_valid(&data.audio_format)) { - g_warning("Invalid audio format: %u:%u:%u\n", - data.audio_format.sample_rate, - data.audio_format.bits, - data.audio_format.channels); - goto fail; - } - - decoder_initialized(decoder, &data.audio_format, - input_stream->seekable, data.total_time); - - while (true) { - if (!tag_is_empty(data.tag)) { - cmd = decoder_tag(decoder, input_stream, data.tag); - tag_free(data.tag); - data.tag = tag_new(); - } else - cmd = decoder_get_command(decoder); - - if (cmd == DECODE_COMMAND_SEEK) { - FLAC__uint64 seek_sample = decoder_seek_where(decoder) * - data.audio_format.sample_rate + 0.5; - if (flac_seek_absolute(flac_dec, seek_sample)) { - data.time = ((float)seek_sample) / - data.audio_format.sample_rate; - data.position = 0; - decoder_command_finished(decoder); - } else - decoder_seek_error(decoder); - } else if (cmd == DECODE_COMMAND_STOP || - flac_get_state(flac_dec) == flac_decoder_eof) - break; - - if (!flac_process_single(flac_dec)) { - cmd = decoder_get_command(decoder); - if (cmd != DECODE_COMMAND_SEEK) - break; - } - } - if (cmd != DECODE_COMMAND_STOP) { - flacPrintErroredState(flac_get_state(flac_dec)); - flac_finish(flac_dec); - } - -fail: - if (data.replay_gain_info) - replay_gain_info_free(data.replay_gain_info); - - tag_free(data.tag); - - if (flac_dec) - flac_delete(flac_dec); - - if (err) - g_warning("%s\n", err); -} - -static void -flac_decode(struct decoder * decoder, struct input_stream *input_stream) -{ - flac_decode_internal(decoder, input_stream, false); -} - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - -/** - * @brief Decode a flac file with embedded cue sheets - * @param const char* fname filename on fs - */ -static void -flac_container_decode(struct decoder* decoder, - const char* fname, - bool is_ogg) -{ - unsigned int tnum = 0; - FLAC__uint64 t_start = 0; - FLAC__uint64 t_end = 0; - FLAC__uint64 track_time = 0; - FLAC__StreamMetadata* cs = NULL; - - flac_decoder *flac_dec; - struct flac_data data; - const char *err = NULL; - - char* pathname = g_strdup(fname); - char* slash = strrchr(pathname, '/'); - *slash = '\0'; - - tnum = flac_vtrack_tnum(fname); - - cs = FLAC__metadata_object_new(FLAC__METADATA_TYPE_CUESHEET); - - FLAC__metadata_get_cuesheet(pathname, &cs); - - if (cs != NULL) - { - if (cs->data.cue_sheet.tracks != NULL - && (tnum <= cs->data.cue_sheet.num_tracks - 1)) - { - t_start = cs->data.cue_sheet.tracks[tnum - 1].offset; - t_end = cs->data.cue_sheet.tracks[tnum].offset; - track_time = cs->data.cue_sheet.tracks[tnum].offset - - cs->data.cue_sheet.tracks[tnum - 1].offset; - } - - FLAC__metadata_object_delete(cs); - } - else - { - g_free(pathname); - return; - } - - if (!(flac_dec = flac_new())) - { - g_free(pathname); - return; - } - - flac_data_init(&data, decoder, NULL); - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - if(!FLAC__stream_decoder_set_metadata_respond(flac_dec, FLAC__METADATA_TYPE_VORBIS_COMMENT)) - { - g_debug("Failed to set metadata respond\n"); - } -#endif - - - if (is_ogg) - { - if (FLAC__stream_decoder_init_ogg_file( flac_dec, - pathname, - flac_write_cb, - flacMetadata, - flac_error_cb, - (void*) &data ) - != FLAC__STREAM_DECODER_INIT_STATUS_OK ) - { - err = "doing Ogg init()"; - goto fail; - } - } - else - { - if (FLAC__stream_decoder_init_file( flac_dec, - pathname, - flac_write_cb, - flacMetadata, - flac_error_cb, - (void*) &data ) - != FLAC__STREAM_DECODER_INIT_STATUS_OK ) - { - err = "doing init()"; - goto fail; - } - } - - if (!flac_process_metadata(flac_dec)) - { - err = "problem reading metadata"; - goto fail; - } - - if (!audio_format_valid(&data.audio_format)) - { - g_warning("Invalid audio format: %u:%u:%u\n", - data.audio_format.sample_rate, - data.audio_format.bits, - data.audio_format.channels); - goto fail; - } - - // set track time (order is important: after stream init) - data.total_time = ((float)track_time / (float)data.audio_format.sample_rate); - data.position = 0; - - decoder_initialized(decoder, &data.audio_format, - true, data.total_time); - - // seek to song start (order is important: after decoder init) - flac_seek_absolute(flac_dec, (FLAC__uint64)t_start); - - while (true) - { - if (!flac_process_single(flac_dec)) - break; - - // we only need to break at the end of track if we are in "cue mode" - if (data.time >= data.total_time) - { - flacPrintErroredState(flac_get_state(flac_dec)); - flac_finish(flac_dec); - } - - if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) - { - FLAC__uint64 seek_sample = t_start + - (decoder_seek_where(decoder) * data.audio_format.sample_rate); - - if (seek_sample >= t_start && seek_sample <= t_end && - flac_seek_absolute(flac_dec, (FLAC__uint64)seek_sample)) { - data.time = (float)(seek_sample - t_start) / - data.audio_format.sample_rate; - data.position = 0; - - decoder_command_finished(decoder); - } else - decoder_seek_error(decoder); - } - else if (flac_get_state(flac_dec) == flac_decoder_eof) - break; - } - - if (decoder_get_command(decoder) != DECODE_COMMAND_STOP) - { - flacPrintErroredState(flac_get_state(flac_dec)); - flac_finish(flac_dec); - } - -fail: - if (pathname) - g_free(pathname); - - if (data.replay_gain_info) - replay_gain_info_free(data.replay_gain_info); - - if (flac_dec) - flac_delete(flac_dec); - - if (err) - g_warning("%s\n", err); -} - -/** - * @brief Open a flac file for decoding - * @param const char* fname filename on fs - */ -static void -flac_filedecode_internal(struct decoder* decoder, - const char* fname, - bool is_ogg) -{ - flac_decoder *flac_dec; - struct flac_data data; - const char *err = NULL; - unsigned int flac_err_state = 0; - - if (!(flac_dec = flac_new())) - return; - - flac_data_init(&data, decoder, NULL); - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - if(!FLAC__stream_decoder_set_metadata_respond(flac_dec, FLAC__METADATA_TYPE_VORBIS_COMMENT)) - { - g_debug("Failed to set metadata respond\n"); - } -#endif - - - if (is_ogg) - { - if ( (flac_err_state = FLAC__stream_decoder_init_ogg_file( flac_dec, - fname, - flac_write_cb, - flacMetadata, - flac_error_cb, - (void*) &data )) - == FLAC__STREAM_DECODER_INIT_STATUS_ERROR_OPENING_FILE) - { - flac_container_decode(decoder, fname, is_ogg); - } - else if (flac_err_state != FLAC__STREAM_DECODER_INIT_STATUS_OK) - { - err = "doing Ogg init()"; - goto fail; - } - } - else - { - if ( (flac_err_state = FLAC__stream_decoder_init_file( flac_dec, - fname, - flac_write_cb, - flacMetadata, - flac_error_cb, - (void*) &data )) - == FLAC__STREAM_DECODER_INIT_STATUS_ERROR_OPENING_FILE) - { - flac_container_decode(decoder, fname, is_ogg); - } - else if (flac_err_state != FLAC__STREAM_DECODER_INIT_STATUS_OK) - { - err = "doing init()"; - goto fail; - } - } - - if (!flac_process_metadata(flac_dec)) - { - err = "problem reading metadata"; - goto fail; - } - - if (!audio_format_valid(&data.audio_format)) - { - g_warning("Invalid audio format: %u:%u:%u\n", - data.audio_format.sample_rate, - data.audio_format.bits, - data.audio_format.channels); - goto fail; - } - - decoder_initialized(decoder, &data.audio_format, - true, data.total_time); - - while (true) - { - if (!flac_process_single(flac_dec)) - break; - - if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) - { - FLAC__uint64 seek_sample = decoder_seek_where(decoder) * - data.audio_format.sample_rate + 0.5; - if (flac_seek_absolute(flac_dec, seek_sample)) - { - data.time = ((float)seek_sample) / - data.audio_format.sample_rate; - data.position = 0; - decoder_command_finished(decoder); - } - else - decoder_seek_error(decoder); - - } - else if (flac_get_state(flac_dec) == flac_decoder_eof) - break; - } - - if (decoder_get_command(decoder) != DECODE_COMMAND_STOP) - { - flacPrintErroredState(flac_get_state(flac_dec)); - flac_finish(flac_dec); - } - -fail: - if (data.replay_gain_info) - replay_gain_info_free(data.replay_gain_info); - - if (flac_dec) - flac_delete(flac_dec); - - if (err) - g_warning("%s\n", err); -} - -/** - * @brief wrapper function for - * flac_filedecode_internal method - * for decoding without ogg - */ -static void -flac_filedecode(struct decoder *decoder, const char *fname) -{ - struct stat st; - - if (stat(fname, &st) < 0) { - flac_container_decode(decoder, fname, false); - } else - flac_filedecode_internal(decoder, fname, false); -} - -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - -#ifndef HAVE_OGGFLAC - -static bool -oggflac_init(G_GNUC_UNUSED const struct config_param *param) -{ -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - return !!FLAC_API_SUPPORTS_OGG_FLAC; -#else - /* disable oggflac when libflac is too old */ - return false; -#endif -} - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - -static struct tag * -oggflac_tag_dup(const char *file) -{ - struct tag *ret = NULL; - FLAC__Metadata_Iterator *it; - FLAC__StreamMetadata *block; - FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new(); - - if (!(FLAC__metadata_chain_read_ogg(chain, file))) - goto out; - it = FLAC__metadata_iterator_new(); - FLAC__metadata_iterator_init(it, chain); - - ret = tag_new(); - do { - if (!(block = FLAC__metadata_iterator_get_block(it))) - break; - if (block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) { - flac_vorbis_comments_to_tag(ret, NULL, block); - } else if (block->type == FLAC__METADATA_TYPE_STREAMINFO) { - ret->time = ((float)block->data.stream_info. - total_samples) / - block->data.stream_info.sample_rate + 0.5; - } - } while (FLAC__metadata_iterator_next(it)); - FLAC__metadata_iterator_delete(it); - - if (!tag_is_defined(ret)) { - tag_free(ret); - ret = NULL; - } - -out: - FLAC__metadata_chain_delete(chain); - return ret; -} - -static void -oggflac_decode(struct decoder *decoder, struct input_stream *input_stream) -{ - if (ogg_stream_type_detect(input_stream) != FLAC) - return; - - /* rewind the stream, because ogg_stream_type_detect() has - moved it */ - input_stream_seek(input_stream, 0, SEEK_SET); - - flac_decode_internal(decoder, input_stream, true); -} - -static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL }; -static const char *const oggflac_mime_types[] = { - "application/ogg", - "application/x-ogg", - "audio/ogg", - "audio/x-flac+ogg", - "audio/x-ogg", - NULL -}; - -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - -const struct decoder_plugin oggflac_decoder_plugin = { - .name = "oggflac", - .init = oggflac_init, -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - .stream_decode = oggflac_decode, - .tag_dup = oggflac_tag_dup, - .suffixes = oggflac_suffixes, - .mime_types = oggflac_mime_types -#endif -}; - -#endif /* HAVE_OGGFLAC */ - -static const char *const flac_suffixes[] = { "flac", NULL }; -static const char *const flac_mime_types[] = { - "application/flac", - "application/x-flac", - "audio/flac", - "audio/x-flac", - NULL -}; - -const struct decoder_plugin flac_decoder_plugin = { - .name = "flac", - .stream_decode = flac_decode, -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - .file_decode = flac_filedecode, -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - .tag_dup = flac_tag_dup, - .suffixes = flac_suffixes, - .mime_types = flac_mime_types, -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - .container_scan = flac_cue_track, -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ -}; diff --git a/src/decoder/fluidsynth_plugin.c b/src/decoder/fluidsynth_decoder_plugin.c index 99c874c09..b9a2d0d99 100644 --- a/src/decoder/fluidsynth_plugin.c +++ b/src/decoder/fluidsynth_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,9 +26,10 @@ * */ -#include "../decoder_api.h" -#include "../timer.h" -#include "../conf.h" +#include "config.h" +#include "decoder_api.h" +#include "timer.h" +#include "conf.h" #include <glib.h> @@ -87,7 +88,7 @@ fluidsynth_file_decode(struct decoder *decoder, const char *path_fs) { static const struct audio_format audio_format = { .sample_rate = 48000, - .bits = 16, + .format = SAMPLE_FORMAT_S16, .channels = 2, }; char setting_sample_rate[] = "synth.sample-rate"; @@ -203,7 +204,7 @@ fluidsynth_file_decode(struct decoder *decoder, const char *path_fs) break; cmd = decoder_data(decoder, NULL, buffer, sizeof(buffer), - 0, 0, NULL); + 0); } while (cmd == DECODE_COMMAND_NONE); /* clean up */ diff --git a/src/decoder/gme_decoder_plugin.c b/src/decoder/gme_decoder_plugin.c new file mode 100644 index 000000000..4a5220a3f --- /dev/null +++ b/src/decoder/gme_decoder_plugin.c @@ -0,0 +1,244 @@ +#include "config.h" +#include "../decoder_api.h" +#include "audio_check.h" +#include "uri.h" + +#include <glib.h> +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include <gme/gme.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "gme" + +#define SUBTUNE_PREFIX "tune_" + +enum { + GME_SAMPLE_RATE = 44100, + GME_CHANNELS = 2, + GME_BUFFER_FRAMES = 2048, + GME_BUFFER_SAMPLES = GME_BUFFER_FRAMES * GME_CHANNELS, +}; + +/** + * returns the file path stripped of any /tune_xxx.* subtune + * suffix + */ +static char * +get_container_name(const char *path_fs) +{ + const char *subtune_suffix = uri_get_suffix(path_fs); + char *path_container = g_strdup(path_fs); + char *pat = g_strconcat("*/" SUBTUNE_PREFIX "???.", subtune_suffix, NULL); + GPatternSpec *path_with_subtune = g_pattern_spec_new(pat); + g_free(pat); + if (!g_pattern_match(path_with_subtune, + strlen(path_container), path_container, NULL)) { + g_pattern_spec_free(path_with_subtune); + return path_container; + } + + char *ptr = g_strrstr(path_container, "/" SUBTUNE_PREFIX); + if (ptr != NULL) + *ptr='\0'; + + g_pattern_spec_free(path_with_subtune); + return path_container; +} + +/** + * returns tune number from file.nsf/tune_xxx.* style path or 0 if no subtune + * is appended. + */ +static int +get_song_num(const char *path_fs) +{ + const char *subtune_suffix = uri_get_suffix(path_fs); + char *pat = g_strconcat("*/" SUBTUNE_PREFIX "???.", subtune_suffix, NULL); + GPatternSpec *path_with_subtune = g_pattern_spec_new(pat); + g_free(pat); + + if (g_pattern_match(path_with_subtune, + strlen(path_fs), path_fs, NULL)) { + char *sub = g_strrstr(path_fs, "/" SUBTUNE_PREFIX); + g_pattern_spec_free(path_with_subtune); + if(!sub) + return 0; + + sub += strlen("/" SUBTUNE_PREFIX); + int song_num = strtol(sub, NULL, 10); + + return song_num - 1; + } else { + g_pattern_spec_free(path_with_subtune); + return 0; + } +} + +static char * +gme_container_scan(const char *path_fs, const unsigned int tnum) +{ + Music_Emu *emu; + const char* gme_err; + unsigned int num_songs; + + gme_err = gme_open_file(path_fs, &emu, GME_SAMPLE_RATE); + if (gme_err != NULL) { + g_warning("%s", gme_err); + return NULL; + } + + num_songs = gme_track_count(emu); + /* if it only contains a single tune, don't treat as container */ + if (num_songs < 2) + return NULL; + + const char *subtune_suffix = uri_get_suffix(path_fs); + if (tnum <= num_songs){ + char *subtune = g_strdup_printf( + SUBTUNE_PREFIX "%03u.%s", tnum, subtune_suffix); + return subtune; + } else + return NULL; +} + +static void +gme_file_decode(struct decoder *decoder, const char *path_fs) +{ + float song_len; + Music_Emu *emu; + gme_info_t *ti; + struct audio_format audio_format; + enum decoder_command cmd; + short buf[GME_BUFFER_SAMPLES]; + const char* gme_err; + char *path_container = get_container_name(path_fs); + int song_num = get_song_num(path_fs); + + gme_err = gme_open_file(path_container, &emu, GME_SAMPLE_RATE); + g_free(path_container); + if (gme_err != NULL) { + g_warning("%s", gme_err); + return; + } + + if((gme_err = gme_track_info(emu, &ti, song_num)) != NULL){ + g_warning("%s", gme_err); + gme_delete(emu); + return; + } + + if(ti->length > 0) + song_len = ti->length / 1000.0; + else song_len = -1; + + /* initialize the MPD decoder */ + + GError *error = NULL; + if (!audio_format_init_checked(&audio_format, GME_SAMPLE_RATE, + SAMPLE_FORMAT_S16, GME_CHANNELS, + &error)) { + g_warning("%s", error->message); + g_error_free(error); + gme_free_info(ti); + gme_delete(emu); + return; + } + + decoder_initialized(decoder, &audio_format, true, song_len); + + if((gme_err = gme_start_track(emu, song_num)) != NULL) + g_warning("%s", gme_err); + + /* play */ + do { + gme_err = gme_play(emu, GME_BUFFER_SAMPLES, buf); + if (gme_err != NULL) { + g_warning("%s", gme_err); + return; + } + cmd = decoder_data(decoder, NULL, buf, sizeof(buf), 0); + + if(cmd == DECODE_COMMAND_SEEK) { + float where = decoder_seek_where(decoder); + if((gme_err = gme_seek(emu, (int)where*1000)) != NULL) + g_warning("%s", gme_err); + decoder_command_finished(decoder); + } + + if(gme_track_ended(emu)) + break; + } while(cmd != DECODE_COMMAND_STOP); + + gme_free_info(ti); + gme_delete(emu); +} + +static struct tag * +gme_tag_dup(const char *path_fs) +{ + Music_Emu *emu; + gme_info_t *ti; + const char* gme_err; + char *path_container=get_container_name(path_fs); + int song_num; + song_num=get_song_num(path_fs); + + gme_err = gme_open_file(path_container, &emu, GME_SAMPLE_RATE); + g_free(path_container); + if (gme_err != NULL) { + g_warning("%s", gme_err); + return NULL; + } + if((gme_err = gme_track_info(emu, &ti, song_num)) != NULL){ + g_warning("%s", gme_err); + gme_delete(emu); + return NULL; + } + + struct tag *tag = tag_new(); + if(ti != NULL){ + if(ti->length > 0) + tag->time = ti->length / 1000; + if(ti->song != NULL){ + if(gme_track_count(emu) > 1){ + /* start numbering subtunes from 1 */ + char *tag_title=g_strdup_printf("%s (%d/%d)", + ti->song, song_num+1, gme_track_count(emu)); + tag_add_item(tag, TAG_TITLE, tag_title); + g_free(tag_title); + }else + tag_add_item(tag, TAG_TITLE, ti->song); + } + if(ti->author != NULL) + tag_add_item(tag, TAG_ARTIST, ti->author); + if(ti->game != NULL) + tag_add_item(tag, TAG_ALBUM, ti->game); + if(ti->comment != NULL) + tag_add_item(tag, TAG_COMMENT, ti->comment); + if(ti->copyright != NULL) + tag_add_item(tag, TAG_DATE, ti->copyright); + } + + gme_free_info(ti); + gme_delete(emu); + return tag; +} + +static const char *const gme_suffixes[] = { + "ay", "gbs", "gym", "hes", "kss", "nsf", + "nsfe", "sap", "spc", "vgm", "vgz", + NULL +}; + +extern const struct decoder_plugin gme_decoder_plugin; +const struct decoder_plugin gme_decoder_plugin = { + .name = "gme", + .file_decode = gme_file_decode, + .tag_dup = gme_tag_dup, + .suffixes = gme_suffixes, + .container_scan = gme_container_scan, +}; diff --git a/src/decoder/mad_plugin.c b/src/decoder/mad_decoder_plugin.c index 9b3259485..a11d1b020 100644 --- a/src/decoder/mad_plugin.c +++ b/src/decoder/mad_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../decoder_api.h" -#include "../conf.h" #include "config.h" +#include "decoder_api.h" +#include "conf.h" #include "tag_id3.h" +#include "tag_rva2.h" +#include "audio_check.h" #include <assert.h> #include <unistd.h> @@ -125,6 +127,7 @@ struct mp3_data { unsigned int drop_end_frames; unsigned int drop_start_samples; unsigned int drop_end_samples; + bool found_replay_gain; bool found_xing; bool found_first_frame; bool decoded_first_frame; @@ -148,6 +151,7 @@ mp3_data_init(struct mp3_data *data, struct decoder *decoder, data->drop_end_frames = 0; data->drop_start_samples = 0; data->drop_end_samples = 0; + data->found_replay_gain = false; data->found_xing = false; data->found_first_frame = false; data->decoded_first_frame = false; @@ -164,7 +168,7 @@ mp3_data_init(struct mp3_data *data, struct decoder *decoder, static bool mp3_seek(struct mp3_data *data, long offset) { - if (!input_stream_seek(data->input_stream, offset, SEEK_SET)) + if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL)) return false; mad_stream_buffer(&data->stream, data->input_buffer, 0); @@ -208,105 +212,17 @@ mp3_fill_buffer(struct mp3_data *data) } #ifdef HAVE_ID3TAG -/* Parse mp3 RVA2 frame. Shamelessly stolen from madplay. */ static bool -parse_rva2(struct id3_tag *tag, struct replay_gain_info *replay_gain_info) -{ - struct id3_frame const * frame; - - id3_latin1_t const *id; - id3_byte_t const *data; - id3_length_t length; - - enum { - CHANNEL_OTHER = 0x00, - CHANNEL_MASTER_VOLUME = 0x01, - CHANNEL_FRONT_RIGHT = 0x02, - CHANNEL_FRONT_LEFT = 0x03, - CHANNEL_BACK_RIGHT = 0x04, - CHANNEL_BACK_LEFT = 0x05, - CHANNEL_FRONT_CENTRE = 0x06, - CHANNEL_BACK_CENTRE = 0x07, - CHANNEL_SUBWOOFER = 0x08 - }; - - /* relative volume adjustment information */ - - frame = id3_tag_findframe(tag, "RVA2", 0); - if (frame == NULL) - return false; - - id = id3_field_getlatin1(id3_frame_field(frame, 0)); - data = id3_field_getbinarydata(id3_frame_field(frame, 1), - &length); - - if (id == NULL || data == NULL) - return false; - - /* - * "The 'identification' string is used to identify the - * situation and/or device where this adjustment should apply. - * The following is then repeated for every channel - * - * Type of channel $xx - * Volume adjustment $xx xx - * Bits representing peak $xx - * Peak volume $xx (xx ...)" - */ - - while (length >= 4) { - unsigned int peak_bytes; - - peak_bytes = (data[3] + 7) / 8; - if (4 + peak_bytes > length) - break; - - if (data[0] == CHANNEL_MASTER_VOLUME) { - signed int voladj_fixed; - double voladj_float; - - /* - * "The volume adjustment is encoded as a fixed - * point decibel value, 16 bit signed integer - * representing (adjustment*512), giving +/- 64 - * dB with a precision of 0.001953125 dB." - */ - - voladj_fixed = (data[1] << 8) | (data[2] << 0); - voladj_fixed |= -(voladj_fixed & 0x8000); - - voladj_float = (double) voladj_fixed / 512; - - replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = voladj_float; - replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = voladj_float; - - g_debug("parseRVA2: Relative Volume " - "%+.1f dB adjustment (%s)\n", - voladj_float, id); - - return true; - } - - data += 4 + peak_bytes; - length -= 4 + peak_bytes; - } - - return false; -} -#endif - -#ifdef HAVE_ID3TAG -static struct replay_gain_info * -parse_id3_replay_gain_info(struct id3_tag *tag) +parse_id3_replay_gain_info(struct replay_gain_info *replay_gain_info, + struct id3_tag *tag) { int i; char *key; char *value; struct id3_frame *frame; bool found = false; - struct replay_gain_info *replay_gain_info; - replay_gain_info = replay_gain_info_new(); + replay_gain_info_init(replay_gain_info); for (i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) { if (frame->nfields < 3) @@ -337,21 +253,55 @@ parse_id3_replay_gain_info(struct id3_tag *tag) free(value); } - if (!found) { + return found || /* fall back on RVA2 if no replaygain tags found */ - found = parse_rva2(tag, replay_gain_info); + tag_rva2_parse(tag, replay_gain_info); +} +#endif + +#ifdef HAVE_ID3TAG +static bool +parse_id3_mixramp(char **mixramp_start, char **mixramp_end, + struct id3_tag *tag) +{ + int i; + char *key; + char *value; + struct id3_frame *frame; + bool found = false; + + *mixramp_start = NULL; + *mixramp_end = NULL; + + for (i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) { + if (frame->nfields < 3) + continue; + + key = (char *) + id3_ucs4_latin1duplicate(id3_field_getstring + (&frame->fields[1])); + value = (char *) + id3_ucs4_latin1duplicate(id3_field_getstring + (&frame->fields[2])); + + if (g_ascii_strcasecmp(key, "mixramp_start") == 0) { + *mixramp_start = g_strdup(value); + found = true; + } else if (g_ascii_strcasecmp(key, "mixramp_end") == 0) { + *mixramp_end = g_strdup(value); + found = true; + } + + free(key); + free(value); } - if (found) - return replay_gain_info; - replay_gain_info_free(replay_gain_info); - return NULL; + return found; } #endif static void mp3_parse_id3(struct mp3_data *data, size_t tagsize, - struct tag **mpd_tag, - struct replay_gain_info **replay_gain_info_r) + struct tag **mpd_tag) { #ifdef HAVE_ID3TAG struct id3_tag *id3_tag = NULL; @@ -404,13 +354,20 @@ static void mp3_parse_id3(struct mp3_data *data, size_t tagsize, } } - if (replay_gain_info_r) { - struct replay_gain_info *tmp_rgi = - parse_id3_replay_gain_info(id3_tag); - if (tmp_rgi != NULL) { - if (*replay_gain_info_r) - replay_gain_info_free(*replay_gain_info_r); - *replay_gain_info_r = tmp_rgi; + if (data->decoder != NULL) { + struct replay_gain_info rgi; + char *mixramp_start; + char *mixramp_end; + float replay_gain_db = 0; + + if (parse_id3_replay_gain_info(&rgi, id3_tag)) { + replay_gain_db = decoder_replay_gain(data->decoder, &rgi); + data->found_replay_gain = true; + } + if (parse_id3_mixramp(&mixramp_start, &mixramp_end, id3_tag)) { + g_debug("setting mixramp_tags"); + decoder_mixramp(data->decoder, replay_gain_db, + mixramp_start, mixramp_end); } } @@ -419,7 +376,6 @@ static void mp3_parse_id3(struct mp3_data *data, size_t tagsize, g_free(allocated); #else /* !HAVE_ID3TAG */ (void)mpd_tag; - (void)replay_gain_info_r; /* This code is enabled when libid3tag is disabled. Instead of parsing the ID3 frame, it just skips it. */ @@ -467,8 +423,7 @@ id3_tag_query(const void *p0, size_t length) #endif /* !HAVE_ID3TAG */ static enum mp3_action -decode_next_frame_header(struct mp3_data *data, G_GNUC_UNUSED struct tag **tag, - G_GNUC_UNUSED struct replay_gain_info **replay_gain_info_r) +decode_next_frame_header(struct mp3_data *data, G_GNUC_UNUSED struct tag **tag) { enum mad_layer layer; @@ -490,7 +445,7 @@ decode_next_frame_header(struct mp3_data *data, G_GNUC_UNUSED struct tag **tag, if (tagsize > 0) { if (tag && !(*tag)) { mp3_parse_id3(data, (size_t)tagsize, - tag, replay_gain_info_r); + tag); } else { mad_stream_skip(&(data->stream), tagsize); @@ -798,10 +753,10 @@ mp3_frame_duration(const struct mad_frame *frame) MAD_UNITS_MILLISECONDS) / 1000.0; } -static off_t +static goffset mp3_this_frame_offset(const struct mp3_data *data) { - off_t offset = data->input_stream->offset; + goffset offset = data->input_stream->offset; if (data->stream.this_frame != NULL) offset -= data->stream.bufend - data->stream.this_frame; @@ -811,7 +766,7 @@ mp3_this_frame_offset(const struct mp3_data *data) return offset; } -static off_t +static goffset mp3_rest_including_this_frame(const struct mp3_data *data) { return data->input_stream->size - mp3_this_frame_offset(data); @@ -823,7 +778,7 @@ mp3_rest_including_this_frame(const struct mp3_data *data) static void mp3_filesize_to_song_length(struct mp3_data *data) { - off_t rest = mp3_rest_including_this_frame(data); + goffset rest = mp3_rest_including_this_frame(data); if (rest > 0) { float frame_duration = mp3_frame_duration(&data->frame); @@ -838,8 +793,7 @@ mp3_filesize_to_song_length(struct mp3_data *data) } static bool -mp3_decode_first_frame(struct mp3_data *data, struct tag **tag, - struct replay_gain_info **replay_gain_info_r) +mp3_decode_first_frame(struct mp3_data *data, struct tag **tag) { struct xing xing; struct lame lame; @@ -853,8 +807,7 @@ mp3_decode_first_frame(struct mp3_data *data, struct tag **tag, while (true) { do { - ret = decode_next_frame_header(data, tag, - replay_gain_info_r); + ret = decode_next_frame_header(data, tag); } while (ret == DECODE_CONT); if (ret == DECODE_BREAK) return false; @@ -897,14 +850,17 @@ mp3_decode_first_frame(struct mp3_data *data, struct tag **tag, /* Album gain isn't currently used. See comment in * parse_lame() for details. -- jat */ - if (replay_gain_info_r && !*replay_gain_info_r && + if (data->decoder != NULL && + !data->found_replay_gain && lame.track_gain) { - *replay_gain_info_r = replay_gain_info_new(); - (*replay_gain_info_r)->tuples[REPLAY_GAIN_TRACK].gain = lame.track_gain; - (*replay_gain_info_r)->tuples[REPLAY_GAIN_TRACK].peak = lame.peak; + struct replay_gain_info rgi; + replay_gain_info_init(&rgi); + rgi.tuples[REPLAY_GAIN_TRACK].gain = lame.track_gain; + rgi.tuples[REPLAY_GAIN_TRACK].peak = lame.peak; + decoder_replay_gain(data->decoder, &rgi); } } - } + } if (!data->max_frames) return false; @@ -932,33 +888,29 @@ static void mp3_data_finish(struct mp3_data *data) } /* this is primarily used for getting total time for tags */ -static int mp3_total_file_time(const char *file) +static int +mad_decoder_total_file_time(struct input_stream *is) { - struct input_stream input_stream; struct mp3_data data; int ret; - if (!input_stream_open(&input_stream, file)) - return -1; - mp3_data_init(&data, NULL, &input_stream); - if (!mp3_decode_first_frame(&data, NULL, NULL)) + mp3_data_init(&data, NULL, is); + if (!mp3_decode_first_frame(&data, NULL)) ret = -1; else ret = data.total_time + 0.5; mp3_data_finish(&data); - input_stream_close(&input_stream); return ret; } static bool mp3_open(struct input_stream *is, struct mp3_data *data, - struct decoder *decoder, struct tag **tag, - struct replay_gain_info **replay_gain_info_r) + struct decoder *decoder, struct tag **tag) { mp3_data_init(data, decoder, is); *tag = NULL; - if (!mp3_decode_first_frame(data, tag, replay_gain_info_r)) { + if (!mp3_decode_first_frame(data, tag)) { mp3_data_finish(data); if (tag && *tag) tag_free(*tag); @@ -1017,8 +969,7 @@ mp3_update_timer_next_frame(struct mp3_data *data) * Sends the synthesized current frame via decoder_data(). */ static enum decoder_command -mp3_send_pcm(struct mp3_data *data, unsigned i, unsigned pcm_length, - struct replay_gain_info *replay_gain_info) +mp3_send_pcm(struct mp3_data *data, unsigned i, unsigned pcm_length) { unsigned max_samples; @@ -1043,9 +994,7 @@ mp3_send_pcm(struct mp3_data *data, unsigned i, unsigned pcm_length, cmd = decoder_data(data->decoder, data->input_stream, data->output_buffer, sizeof(data->output_buffer[0]) * num_samples, - data->elapsed_time, - data->bit_rate / 1000, - replay_gain_info); + data->bit_rate / 1000); if (cmd != DECODE_COMMAND_NONE) return cmd; } @@ -1057,8 +1006,7 @@ mp3_send_pcm(struct mp3_data *data, unsigned i, unsigned pcm_length, * Synthesize the current frame and send it via decoder_data(). */ static enum decoder_command -mp3_synth_and_send(struct mp3_data *data, - struct replay_gain_info *replay_gain_info) +mp3_synth_and_send(struct mp3_data *data) { unsigned i, pcm_length; enum decoder_command cmd; @@ -1099,7 +1047,7 @@ mp3_synth_and_send(struct mp3_data *data, pcm_length -= data->drop_end_samples; } - cmd = mp3_send_pcm(data, i, pcm_length, replay_gain_info); + cmd = mp3_send_pcm(data, i, pcm_length); if (cmd != DECODE_COMMAND_NONE) return cmd; @@ -1113,7 +1061,7 @@ mp3_synth_and_send(struct mp3_data *data, } static bool -mp3_read(struct mp3_data *data, struct replay_gain_info **replay_gain_info_r) +mp3_read(struct mp3_data *data) { struct decoder *decoder = data->decoder; enum mp3_action ret; @@ -1130,9 +1078,7 @@ mp3_read(struct mp3_data *data, struct replay_gain_info **replay_gain_info_r) data->mute_frame = MUTEFRAME_NONE; break; case MUTEFRAME_NONE: - cmd = mp3_synth_and_send(data, - replay_gain_info_r != NULL - ? *replay_gain_info_r : NULL); + cmd = mp3_synth_and_send(data); if (cmd == DECODE_COMMAND_SEEK) { unsigned long j; @@ -1161,8 +1107,7 @@ mp3_read(struct mp3_data *data, struct replay_gain_info **replay_gain_info_r) do { struct tag *tag = NULL; - ret = decode_next_frame_header(data, &tag, - replay_gain_info_r); + ret = decode_next_frame_header(data, &tag); if (tag != NULL) { decoder_tag(decoder, data->input_stream, tag); @@ -1189,29 +1134,34 @@ mp3_read(struct mp3_data *data, struct replay_gain_info **replay_gain_info_r) return ret != DECODE_BREAK; } -static void mp3_audio_format(struct mp3_data *data, struct audio_format *af) -{ - af->bits = 24; - af->sample_rate = (data->frame).header.samplerate; - af->channels = MAD_NCHANNELS(&(data->frame).header); -} - static void mp3_decode(struct decoder *decoder, struct input_stream *input_stream) { struct mp3_data data; + GError *error = NULL; struct tag *tag = NULL; - struct replay_gain_info *replay_gain_info = NULL; struct audio_format audio_format; - if (!mp3_open(input_stream, &data, decoder, &tag, &replay_gain_info)) { + if (!mp3_open(input_stream, &data, decoder, &tag)) { if (decoder_get_command(decoder) == DECODE_COMMAND_NONE) g_warning ("Input does not appear to be a mp3 bit stream.\n"); return; } - mp3_audio_format(&data, &audio_format); + if (!audio_format_init_checked(&audio_format, + data.frame.header.samplerate, + SAMPLE_FORMAT_S24_P32, + MAD_NCHANNELS(&data.frame.header), + &error)) { + g_warning("%s", error->message); + g_error_free(error); + + if (tag != NULL) + tag_free(tag); + mp3_data_finish(&data); + return; + } decoder_initialized(decoder, &audio_format, data.input_stream->seekable, data.total_time); @@ -1221,24 +1171,20 @@ mp3_decode(struct decoder *decoder, struct input_stream *input_stream) tag_free(tag); } - while (mp3_read(&data, &replay_gain_info)) ; - - if (replay_gain_info) - replay_gain_info_free(replay_gain_info); + while (mp3_read(&data)) ; mp3_data_finish(&data); } -static struct tag *mp3_tag_dup(const char *file) +static struct tag * +mad_decoder_stream_tag(struct input_stream *is) { struct tag *tag; int total_time; - total_time = mp3_total_file_time(file); - if (total_time < 0) { - g_debug("Failed to get total song time from: %s", file); + total_time = mad_decoder_total_file_time(is); + if (total_time < 0) return NULL; - } tag = tag_new(); tag->time = total_time; @@ -1252,7 +1198,7 @@ const struct decoder_plugin mad_decoder_plugin = { .name = "mad", .init = mp3_plugin_init, .stream_decode = mp3_decode, - .tag_dup = mp3_tag_dup, + .stream_tag = mad_decoder_stream_tag, .suffixes = mp3_suffixes, .mime_types = mp3_mime_types }; diff --git a/src/decoder/mikmod_plugin.c b/src/decoder/mikmod_decoder_plugin.c index f60dcbc61..91478e86f 100644 --- a/src/decoder/mikmod_plugin.c +++ b/src/decoder/mikmod_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../decoder_api.h" +#include "config.h" +#include "decoder_api.h" +#include "mpd_error.h" #include <glib.h> #include <mikmod.h> +#include <assert.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "mikmod" @@ -29,30 +32,34 @@ #define MIKMOD_FRAME_SIZE 4096 -static BOOL mod_mpd_Init(void) +static BOOL +mikmod_mpd_init(void) { return VC_Init(); } -static void mod_mpd_Exit(void) +static void +mikmod_mpd_exit(void) { VC_Exit(); } -static void mod_mpd_Update(void) +static void +mikmod_mpd_update(void) { } -static BOOL mod_mpd_IsThere(void) +static BOOL +mikmod_mpd_is_present(void) { - return 1; + return true; } -static char drv_name[] = "MPD"; -static char drv_version[] = "MPD Output Driver v0.1"; +static char drv_name[] = PACKAGE_NAME; +static char drv_version[] = VERSION; #if (LIBMIKMOD_VERSION > 0x030106) -static char drv_alias[] = "mpd"; +static char drv_alias[] = PACKAGE; #endif static MDRIVER drv_mpd = { @@ -68,18 +75,18 @@ static MDRIVER drv_mpd = { #endif NULL, /* CommandLine */ #endif - mod_mpd_IsThere, + mikmod_mpd_is_present, VC_SampleLoad, VC_SampleUnload, VC_SampleSpace, VC_SampleLength, - mod_mpd_Init, - mod_mpd_Exit, + mikmod_mpd_init, + mikmod_mpd_exit, NULL, VC_SetNumVoices, VC_PlayStart, VC_PlayStop, - mod_mpd_Update, + mikmod_mpd_update, NULL, VC_VoiceSetVolume, VC_VoiceGetVolume, @@ -94,11 +101,19 @@ static MDRIVER drv_mpd = { VC_VoiceRealVolume }; +static unsigned mikmod_sample_rate; + static bool -mod_initMikMod(G_GNUC_UNUSED const struct config_param *param) +mikmod_decoder_init(const struct config_param *param) { static char params[] = ""; + mikmod_sample_rate = config_get_block_unsigned(param, "sample_rate", + 44100); + if (!audio_valid_sample_rate(mikmod_sample_rate)) + MPD_ERROR("Invalid sample rate in line %d: %u", + param->line, mikmod_sample_rate); + md_device = 0; md_reverb = 0; @@ -106,7 +121,7 @@ mod_initMikMod(G_GNUC_UNUSED const struct config_param *param) MikMod_RegisterAllLoaders(); md_pansep = 64; - md_mixfreq = 44100; + md_mixfreq = mikmod_sample_rate; md_mode = (DMODE_SOFT_MUSIC | DMODE_INTERP | DMODE_STEREO | DMODE_16BITS); @@ -119,117 +134,80 @@ mod_initMikMod(G_GNUC_UNUSED const struct config_param *param) return true; } -static void mod_finishMikMod(void) +static void +mikmod_decoder_finish(void) { MikMod_Exit(); } -typedef struct _mod_Data { - MODULE *moduleHandle; - SBYTE audio_buffer[MIKMOD_FRAME_SIZE]; -} mod_Data; - -static mod_Data *mod_open(const char *path) -{ - char *path2; - MODULE *moduleHandle; - mod_Data *data; - - path2 = g_strdup(path); - moduleHandle = Player_Load(path2, 128, 0); - g_free(path2); - - if (moduleHandle == NULL) - return NULL; - - /* Prevent module from looping forever */ - moduleHandle->loop = 0; - - data = g_new(mod_Data, 1); - data->moduleHandle = moduleHandle; - - Player_Start(data->moduleHandle); - - return data; -} - -static void mod_close(mod_Data * data) -{ - Player_Stop(); - Player_Free(data->moduleHandle); - g_free(data); -} - static void -mod_decode(struct decoder *decoder, const char *path) +mikmod_decoder_file_decode(struct decoder *decoder, const char *path_fs) { - mod_Data *data; + char *path2; + MODULE *handle; struct audio_format audio_format; - float total_time = 0.0; int ret; - float secPerByte; + SBYTE buffer[MIKMOD_FRAME_SIZE]; enum decoder_command cmd = DECODE_COMMAND_NONE; - if (!(data = mod_open(path))) { - g_warning("failed to open mod: %s\n", path); + path2 = g_strdup(path_fs); + handle = Player_Load(path2, 128, 0); + g_free(path2); + + if (handle == NULL) { + g_warning("failed to open mod: %s", path_fs); return; } - audio_format.bits = 16; - audio_format.sample_rate = 44100; - audio_format.channels = 2; + /* Prevent module from looping forever */ + handle->loop = 0; - secPerByte = - 1.0 / ((audio_format.bits * audio_format.channels / 8.0) * - (float)audio_format.sample_rate); + audio_format_init(&audio_format, mikmod_sample_rate, SAMPLE_FORMAT_S16, 2); + assert(audio_format_valid(&audio_format)); decoder_initialized(decoder, &audio_format, false, 0); + Player_Start(handle); while (cmd == DECODE_COMMAND_NONE && Player_Active()) { - ret = VC_WriteBytes(data->audio_buffer, MIKMOD_FRAME_SIZE); - total_time += ret * secPerByte; - cmd = decoder_data(decoder, NULL, - data->audio_buffer, ret, - total_time, 0, NULL); + ret = VC_WriteBytes(buffer, sizeof(buffer)); + cmd = decoder_data(decoder, NULL, buffer, ret, 0); } - mod_close(data); + Player_Stop(); + Player_Free(handle); } -static struct tag *modTagDup(const char *file) +static struct tag * +mikmod_decoder_tag_dup(const char *path_fs) { - char *path2; - struct tag *ret = NULL; - MODULE *moduleHandle; - char *title; + char *path2 = g_strdup(path_fs); + MODULE *handle = Player_Load(path2, 128, 0); - path2 = g_strdup(file); - moduleHandle = Player_Load(path2, 128, 0); - g_free(path2); - - if (moduleHandle == NULL) { - g_debug("Failed to open file: %s", file); + if (handle == NULL) { + g_free(path2); + g_debug("Failed to open file: %s", path_fs); return NULL; } - Player_Free(moduleHandle); - ret = tag_new(); + Player_Free(handle); - ret->time = 0; + struct tag *tag = tag_new(); - path2 = g_strdup(file); - title = Player_LoadTitle(path2); + tag->time = 0; + + char *title = Player_LoadTitle(path2); g_free(path2); - if (title) { - tag_add_item(ret, TAG_ITEM_TITLE, title); + + if (title != NULL) { + tag_add_item(tag, TAG_TITLE, title); free(title); } - return ret; + return tag; } -static const char *const modSuffixes[] = { +static const char *const mikmod_decoder_suffixes[] = { "amf", "dsm", "far", @@ -250,9 +228,9 @@ static const char *const modSuffixes[] = { const struct decoder_plugin mikmod_decoder_plugin = { .name = "mikmod", - .init = mod_initMikMod, - .finish = mod_finishMikMod, - .file_decode = mod_decode, - .tag_dup = modTagDup, - .suffixes = modSuffixes, + .init = mikmod_decoder_init, + .finish = mikmod_decoder_finish, + .file_decode = mikmod_decoder_file_decode, + .tag_dup = mikmod_decoder_tag_dup, + .suffixes = mikmod_decoder_suffixes, }; diff --git a/src/decoder/modplug_plugin.c b/src/decoder/modplug_decoder_plugin.c index f636f2fa6..037c2fd74 100644 --- a/src/decoder/modplug_plugin.c +++ b/src/decoder/modplug_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../decoder_api.h" +#include "config.h" +#include "decoder_api.h" #include <glib.h> #include <modplug.h> +#include <assert.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "modplug" @@ -92,10 +94,8 @@ mod_decode(struct decoder *decoder, struct input_stream *is) ModPlug_Settings settings; GByteArray *bdatas; struct audio_format audio_format; - float total_time = 0.0; - int ret, current; + int ret; char audio_buffer[MODPLUG_FRAME_SIZE]; - float sec_perbyte; enum decoder_command cmd = DECODE_COMMAND_NONE; bdatas = mod_loadfile(decoder, is); @@ -121,37 +121,26 @@ mod_decode(struct decoder *decoder, struct input_stream *is) return; } - audio_format.bits = 16; - audio_format.sample_rate = 44100; - audio_format.channels = 2; - - sec_perbyte = - 1.0 / ((audio_format.bits * audio_format.channels / 8.0) * - (float)audio_format.sample_rate); - - total_time = ModPlug_GetLength(f) / 1000; + audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2); + assert(audio_format_valid(&audio_format)); decoder_initialized(decoder, &audio_format, - is->seekable, total_time); - - total_time = 0; + is->seekable, ModPlug_GetLength(f) / 1000.0); do { ret = ModPlug_Read(f, audio_buffer, MODPLUG_FRAME_SIZE); - - if (ret == 0) { + if (ret <= 0) break; - } - total_time += ret * sec_perbyte; cmd = decoder_data(decoder, NULL, audio_buffer, ret, - total_time, 0, NULL); + 0); if (cmd == DECODE_COMMAND_SEEK) { - total_time = decoder_seek_where(decoder); - current = total_time * 1000; - ModPlug_Seek(f, current); + float where = decoder_seek_where(decoder); + + ModPlug_Seek(f, (int)(where * 1000.0)); + decoder_command_finished(decoder); } @@ -160,43 +149,33 @@ mod_decode(struct decoder *decoder, struct input_stream *is) ModPlug_Unload(f); } -static struct tag *mod_tagdup(const char *file) +static struct tag * +modplug_stream_tag(struct input_stream *is) { ModPlugFile *f; struct tag *ret = NULL; GByteArray *bdatas; char *title; - struct input_stream is; - if (!input_stream_open(&is, file)) { - g_warning("cant open file %s\n", file); + bdatas = mod_loadfile(NULL, is); + if (!bdatas) return NULL; - } - - bdatas = mod_loadfile(NULL, &is); - if (!bdatas) { - g_warning("cant load file %s\n", file); - return NULL; - } f = ModPlug_Load(bdatas->data, bdatas->len); g_byte_array_free(bdatas, TRUE); - if (!f) { - g_warning("could not decode file %s\n", file); + if (f == NULL) return NULL; - } + ret = tag_new(); - ret->time = 0; + ret->time = ModPlug_GetLength(f) / 1000; title = g_strdup(ModPlug_GetName(f)); if (title) - tag_add_item(ret, TAG_ITEM_TITLE, title); + tag_add_item(ret, TAG_TITLE, title); g_free(title); ModPlug_Unload(f); - input_stream_close(&is); - return ret; } @@ -210,6 +189,6 @@ static const char *const mod_suffixes[] = { const struct decoder_plugin modplug_decoder_plugin = { .name = "modplug", .stream_decode = mod_decode, - .tag_dup = mod_tagdup, + .stream_tag = modplug_stream_tag, .suffixes = mod_suffixes, }; diff --git a/src/decoder/mp4ff_plugin.c b/src/decoder/mp4ff_decoder_plugin.c index 4d4d47c6c..861b08194 100644 --- a/src/decoder/mp4ff_plugin.c +++ b/src/decoder/mp4ff_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../decoder_api.h" #include "config.h" +#include "decoder_api.h" +#include "audio_check.h" #include "tag_table.h" #include <glib.h> @@ -35,7 +36,9 @@ /* all code here is either based on or copied from FAAD2's frontend code */ -struct mp4_context { +struct mp4ff_input_stream { + mp4ff_callback_t callback; + struct decoder *decoder; struct input_stream *input_stream; }; @@ -89,20 +92,38 @@ mp4_get_aac_track(mp4ff_t * infile, faacDecHandle decoder, static uint32_t mp4_read(void *user_data, void *buffer, uint32_t length) { - struct mp4_context *ctx = user_data; + struct mp4ff_input_stream *mis = user_data; - return decoder_read(ctx->decoder, ctx->input_stream, buffer, length); + return decoder_read(mis->decoder, mis->input_stream, buffer, length); } static uint32_t mp4_seek(void *user_data, uint64_t position) { - struct mp4_context *ctx = user_data; + struct mp4ff_input_stream *mis = user_data; - return input_stream_seek(ctx->input_stream, position, SEEK_SET) + return input_stream_seek(mis->input_stream, position, SEEK_SET, NULL) ? 0 : -1; } +static const mp4ff_callback_t mpd_mp4ff_callback = { + .read = mp4_read, + .seek = mp4_seek, +}; + +static mp4ff_t * +mp4ff_input_stream_open(struct mp4ff_input_stream *mis, + struct decoder *decoder, + struct input_stream *input_stream) +{ + mis->callback = mpd_mp4ff_callback; + mis->callback.user_data = mis; + mis->decoder = decoder; + mis->input_stream = input_stream; + + return mp4ff_open_read(&mis->callback); +} + static faacDecHandle mp4_faad_new(mp4ff_t *mp4fh, int *track_r, struct audio_format *audio_format) { @@ -111,6 +132,7 @@ mp4_faad_new(mp4ff_t *mp4fh, int *track_r, struct audio_format *audio_format) int track; uint32_t sample_rate; unsigned char channels; + GError *error = NULL; decoder = faacDecOpen(); @@ -131,37 +153,24 @@ mp4_faad_new(mp4ff_t *mp4fh, int *track_r, struct audio_format *audio_format) return NULL; } - *track_r = track; - *audio_format = (struct audio_format){ - .bits = 16, - .channels = channels, - .sample_rate = sample_rate, - }; - - if (!audio_format_valid(audio_format)) { - g_warning("Invalid audio format: %u:%u:%u\n", - audio_format->sample_rate, - audio_format->bits, - audio_format->channels); + if (!audio_format_init_checked(audio_format, sample_rate, + SAMPLE_FORMAT_S16, channels, + &error)) { + g_warning("%s", error->message); + g_error_free(error); faacDecClose(decoder); return NULL; } + *track_r = track; + return decoder; } static void mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream) { - struct mp4_context ctx = { - .decoder = mpd_decoder, - .input_stream = input_stream, - }; - mp4ff_callback_t callback = { - .read = mp4_read, - .seek = mp4_seek, - .user_data = &ctx, - }; + struct mp4ff_input_stream mis; mp4ff_t *mp4fh; int32_t track; float file_time, total_time; @@ -187,7 +196,7 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream) double seek_where = 0; enum decoder_command cmd = DECODE_COMMAND_NONE; - mp4fh = mp4ff_open_read(&callback); + mp4fh = mp4ff_input_stream_open(&mis, mpd_decoder, input_stream); if (!mp4fh) { g_warning("Input does not appear to be a mp4 stream.\n"); return; @@ -266,7 +275,7 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream) dur -= offset; file_time += ((float)dur) / scale; - if (seeking && file_time > seek_where) + if (seeking && file_time >= seek_where) seek_position_found = true; if (seeking && seek_position_found) { @@ -332,7 +341,7 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream) cmd = decoder_data(mpd_decoder, input_stream, sample_buffer, sample_buffer_length, - file_time, bit_rate, NULL); + bit_rate); } g_free(seek_table); @@ -341,9 +350,9 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream) } static const char *const mp4ff_tag_names[TAG_NUM_OF_ITEM_TYPES] = { - [TAG_ITEM_ALBUM_ARTIST] = "album artist", - [TAG_ITEM_COMPOSER] = "writer", - [TAG_ITEM_PERFORMER] = "band", + [TAG_ALBUM_ARTIST] = "album artist", + [TAG_COMPOSER] = "writer", + [TAG_PERFORMER] = "band", }; static enum tag_type @@ -353,57 +362,41 @@ mp4ff_tag_name_parse(const char *name) if (type == TAG_NUM_OF_ITEM_TYPES) type = tag_name_parse_i(name); + if (g_ascii_strcasecmp(name, "albumartist") == 0 || + g_ascii_strcasecmp(name, "album_artist") == 0) + return TAG_ALBUM_ARTIST; + return type; } static struct tag * -mp4_tag_dup(const char *file) +mp4_stream_tag(struct input_stream *is) { - struct tag *ret = NULL; - struct input_stream input_stream; - struct mp4_context ctx = { - .decoder = NULL, - .input_stream = &input_stream, - }; - mp4ff_callback_t callback = { - .read = mp4_read, - .seek = mp4_seek, - .user_data = &ctx, - }; - mp4ff_t *mp4fh; + struct mp4ff_input_stream mis; int32_t track; int32_t file_time; int32_t scale; int i; - if (!input_stream_open(&input_stream, file)) { - g_warning("Failed to open file: %s", file); + mp4ff_t *mp4fh = mp4ff_input_stream_open(&mis, NULL, is); + if (mp4fh == NULL) return NULL; - } - - mp4fh = mp4ff_open_read(&callback); - if (!mp4fh) { - input_stream_close(&input_stream); - return NULL; - } track = mp4_get_aac_track(mp4fh, NULL, NULL, NULL); if (track < 0) { mp4ff_close(mp4fh); - input_stream_close(&input_stream); return NULL; } - ret = tag_new(); file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track); scale = mp4ff_time_scale(mp4fh, track); if (scale < 0) { mp4ff_close(mp4fh); - input_stream_close(&input_stream); - tag_free(ret); return NULL; } - ret->time = ((float)file_time) / scale + 0.5; + + struct tag *tag = tag_new(); + tag->time = ((float)file_time) / scale + 0.5; for (i = 0; i < mp4ff_meta_get_num_items(mp4fh); i++) { char *item; @@ -413,16 +406,15 @@ mp4_tag_dup(const char *file) enum tag_type type = mp4ff_tag_name_parse(item); if (type != TAG_NUM_OF_ITEM_TYPES) - tag_add_item(ret, type, value); + tag_add_item(tag, type, value); free(item); free(value); } mp4ff_close(mp4fh); - input_stream_close(&input_stream); - return ret; + return tag; } static const char *const mp4_suffixes[] = { @@ -435,9 +427,9 @@ static const char *const mp4_suffixes[] = { static const char *const mp4_mime_types[] = { "audio/mp4", "audio/m4a", NULL }; const struct decoder_plugin mp4ff_decoder_plugin = { - .name = "mp4", + .name = "mp4ff", .stream_decode = mp4_decode, - .tag_dup = mp4_tag_dup, + .stream_tag = mp4_stream_tag, .suffixes = mp4_suffixes, .mime_types = mp4_mime_types, }; diff --git a/src/decoder/mpcdec_plugin.c b/src/decoder/mpcdec_decoder_plugin.c index 72a516f22..4df8dd218 100644 --- a/src/decoder/mpcdec_plugin.c +++ b/src/decoder/mpcdec_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../decoder_api.h" #include "config.h" +#include "decoder_api.h" +#include "audio_check.h" #ifdef MPC_IS_OLD_API #include <mpcdec/mpcdec.h> @@ -28,6 +29,7 @@ #endif #include <glib.h> +#include <assert.h> #include <unistd.h> #undef G_LOG_DOMAIN @@ -59,7 +61,7 @@ mpc_seek_cb(cb_first_arg, mpc_int32_t offset) { struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data; - return input_stream_seek(data->is, offset, SEEK_SET); + return input_stream_seek(data->is, offset, SEEK_SET, NULL); } static mpc_int32_t @@ -141,6 +143,7 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is) #endif mpc_reader reader; mpc_streaminfo info; + GError *error = NULL; struct audio_format audio_format; struct mpc_decoder_data data; @@ -150,11 +153,8 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is) mpc_uint32_t ret; int32_t chunk[G_N_ELEMENTS(sample_buffer)]; long bit_rate = 0; - unsigned long sample_pos = 0; mpc_uint32_t vbr_update_acc; mpc_uint32_t vbr_update_bits; - float total_time; - struct replay_gain_info *replay_gain_info = NULL; enum decoder_command cmd = DECODE_COMMAND_NONE; data.is = is; @@ -194,50 +194,47 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is) mpc_demux_get_info(demux, &info); #endif - audio_format.bits = 24; - audio_format.channels = info.channels; - audio_format.sample_rate = info.sample_freq; - - if (!audio_format_valid(&audio_format)) { + if (!audio_format_init_checked(&audio_format, info.sample_freq, + SAMPLE_FORMAT_S24_P32, + info.channels, &error)) { + g_warning("%s", error->message); + g_error_free(error); #ifndef MPC_IS_OLD_API mpc_demux_exit(demux); #endif - g_warning("Invalid audio format: %u:%u:%u\n", - audio_format.sample_rate, - audio_format.bits, - audio_format.channels); return; } - replay_gain_info = replay_gain_info_new(); + struct replay_gain_info replay_gain_info; + replay_gain_info_init(&replay_gain_info); #ifdef MPC_IS_OLD_API - replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = info.gain_album * 0.01; - replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = info.peak_album / 32767.0; - replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = info.gain_title * 0.01; - replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = info.peak_title / 32767.0; + replay_gain_info.tuples[REPLAY_GAIN_ALBUM].gain = info.gain_album * 0.01; + replay_gain_info.tuples[REPLAY_GAIN_ALBUM].peak = info.peak_album / 32767.0; + replay_gain_info.tuples[REPLAY_GAIN_TRACK].gain = info.gain_title * 0.01; + replay_gain_info.tuples[REPLAY_GAIN_TRACK].peak = info.peak_title / 32767.0; #else - replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.); - replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = pow(10, info.peak_album / 256. / 20) / 32767; - replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.); - replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = pow(10, info.peak_title / 256. / 20) / 32767; + replay_gain_info.tuples[REPLAY_GAIN_ALBUM].gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.); + replay_gain_info.tuples[REPLAY_GAIN_ALBUM].peak = pow(10, info.peak_album / 256. / 20) / 32767; + replay_gain_info.tuples[REPLAY_GAIN_TRACK].gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.); + replay_gain_info.tuples[REPLAY_GAIN_TRACK].peak = pow(10, info.peak_title / 256. / 20) / 32767; #endif + decoder_replay_gain(mpd_decoder, &replay_gain_info); + decoder_initialized(mpd_decoder, &audio_format, is->seekable, mpc_streaminfo_get_length(&info)); do { if (cmd == DECODE_COMMAND_SEEK) { - bool success; - - sample_pos = decoder_seek_where(mpd_decoder) * + mpc_int64_t where = decoder_seek_where(mpd_decoder) * audio_format.sample_rate; + bool success; #ifdef MPC_IS_OLD_API - success = mpc_decoder_seek_sample(&decoder, - sample_pos); + success = mpc_decoder_seek_sample(&decoder, where); #else - success = mpc_demux_seek_sample(demux, sample_pos) + success = mpc_demux_seek_sample(demux, where) == MPC_STATUS_OK; #endif if (success) @@ -268,33 +265,26 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is) ret = frame.samples; #endif - sample_pos += ret; - ret *= info.channels; mpc_to_mpd_buffer(chunk, sample_buffer, ret); - total_time = ((float)sample_pos) / audio_format.sample_rate; bit_rate = vbr_update_bits * audio_format.sample_rate / 1152 / 1000; cmd = decoder_data(mpd_decoder, is, chunk, ret * sizeof(chunk[0]), - total_time, - bit_rate, replay_gain_info); + bit_rate); } while (cmd != DECODE_COMMAND_STOP); - replay_gain_info_free(replay_gain_info); - #ifndef MPC_IS_OLD_API mpc_demux_exit(demux); #endif } static float -mpcdec_get_file_duration(const char *file) +mpcdec_get_file_duration(struct input_stream *is) { - struct input_stream is; float total_time = -1; mpc_reader reader; @@ -304,10 +294,7 @@ mpcdec_get_file_duration(const char *file) mpc_streaminfo info; struct mpc_decoder_data data; - if (!input_stream_open(&is, file)) - return -1; - - data.is = &is; + data.is = is; data.decoder = NULL; reader.read = mpc_read_cb; @@ -320,16 +307,12 @@ mpcdec_get_file_duration(const char *file) #ifdef MPC_IS_OLD_API mpc_streaminfo_init(&info); - if (mpc_streaminfo_read(&info, &reader) != ERROR_CODE_OK) { - input_stream_close(&is); + if (mpc_streaminfo_read(&info, &reader) != ERROR_CODE_OK) return -1; - } #else demux = mpc_demux_init(&reader); - if (demux == NULL) { - input_stream_close(&is); + if (demux == NULL) return -1; - } mpc_demux_get_info(demux, &info); mpc_demux_exit(demux); @@ -337,21 +320,17 @@ mpcdec_get_file_duration(const char *file) total_time = mpc_streaminfo_get_length(&info); - input_stream_close(&is); - return total_time; } static struct tag * -mpcdec_tag_dup(const char *file) +mpcdec_stream_tag(struct input_stream *is) { - float total_time = mpcdec_get_file_duration(file); + float total_time = mpcdec_get_file_duration(is); struct tag *tag; - if (total_time < 0) { - g_debug("Failed to get duration of file: %s", file); + if (total_time < 0) return NULL; - } tag = tag_new(); tag->time = total_time; @@ -363,6 +342,6 @@ static const char *const mpcdec_suffixes[] = { "mpc", NULL }; const struct decoder_plugin mpcdec_decoder_plugin = { .name = "mpcdec", .stream_decode = mpcdec_decode, - .tag_dup = mpcdec_tag_dup, + .stream_tag = mpcdec_stream_tag, .suffixes = mpcdec_suffixes, }; diff --git a/src/decoder/mpg123_decoder_plugin.c b/src/decoder/mpg123_decoder_plugin.c new file mode 100644 index 000000000..7b48ebfaf --- /dev/null +++ b/src/decoder/mpg123_decoder_plugin.c @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "decoder_api.h" +#include "audio_check.h" + +#include <glib.h> + +#include <mpg123.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "mpg123" + +static bool +mpd_mpg123_init(G_GNUC_UNUSED const struct config_param *param) +{ + mpg123_init(); + + return true; +} + +static void +mpd_mpg123_finish(void) +{ + mpg123_exit(); +} + +/** + * Opens a file with an existing #mpg123_handle. + * + * @param handle a handle which was created before; on error, this + * function will not free it + * @param audio_format this parameter is filled after successful + * return + * @return true on success + */ +static bool +mpd_mpg123_open(mpg123_handle *handle, const char *path_fs, + struct audio_format *audio_format) +{ + GError *gerror = NULL; + char *path_dup; + int error; + int channels, encoding; + long rate; + + /* mpg123_open() wants a writable string :-( */ + path_dup = g_strdup(path_fs); + + error = mpg123_open(handle, path_dup); + g_free(path_dup); + if (error != MPG123_OK) { + g_warning("libmpg123 failed to open %s: %s", + path_fs, mpg123_plain_strerror(error)); + return false; + } + + /* obtain the audio format */ + + error = mpg123_getformat(handle, &rate, &channels, &encoding); + if (error != MPG123_OK) { + g_warning("mpg123_getformat() failed: %s", + mpg123_plain_strerror(error)); + return false; + } + + if (encoding != MPG123_ENC_SIGNED_16) { + /* other formats not yet implemented */ + g_warning("expected MPG123_ENC_SIGNED_16, got %d", encoding); + return false; + } + + if (!audio_format_init_checked(audio_format, rate, SAMPLE_FORMAT_S16, + channels, &gerror)) { + g_warning("%s", gerror->message); + g_error_free(gerror); + return false; + } + + return true; +} + +static void +mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs) +{ + struct audio_format audio_format; + mpg123_handle *handle; + int error; + off_t num_samples; + enum decoder_command cmd; + + /* open the file */ + + handle = mpg123_new(NULL, &error); + if (handle == NULL) { + g_warning("mpg123_new() failed: %s", + mpg123_plain_strerror(error)); + return; + } + + if (!mpd_mpg123_open(handle, path_fs, &audio_format)) { + mpg123_delete(handle); + return; + } + + num_samples = mpg123_length(handle); + + /* tell MPD core we're ready */ + + decoder_initialized(decoder, &audio_format, false, + (float)num_samples / + (float)audio_format.sample_rate); + + /* the decoder main loop */ + + do { + unsigned char buffer[8192]; + size_t nbytes; + + /* decode */ + + error = mpg123_read(handle, buffer, sizeof(buffer), &nbytes); + if (error != MPG123_OK) { + if (error != MPG123_DONE) + g_warning("mpg123_read() failed: %s", + mpg123_plain_strerror(error)); + break; + } + + /* send to MPD */ + + cmd = decoder_data(decoder, NULL, buffer, nbytes, 0); + + /* seeking not yet implemented */ + } while (cmd == DECODE_COMMAND_NONE); + + /* cleanup */ + + mpg123_delete(handle); +} + +static struct tag * +mpd_mpg123_tag_dup(const char *path_fs) +{ + struct audio_format audio_format; + mpg123_handle *handle; + int error; + off_t num_samples; + struct tag *tag; + + handle = mpg123_new(NULL, &error); + if (handle == NULL) { + g_warning("mpg123_new() failed: %s", + mpg123_plain_strerror(error)); + return NULL; + } + + if (!mpd_mpg123_open(handle, path_fs, &audio_format)) { + mpg123_delete(handle); + return NULL; + } + + num_samples = mpg123_length(handle); + if (num_samples <= 0) { + mpg123_delete(handle); + return NULL; + } + + tag = tag_new(); + + tag->time = num_samples / audio_format.sample_rate; + + /* ID3 tag support not yet implemented */ + + mpg123_delete(handle); + return tag; +} + +static const char *const mpg123_suffixes[] = { + "mp3", + NULL +}; + +const struct decoder_plugin mpg123_decoder_plugin = { + .name = "mpg123", + .init = mpd_mpg123_init, + .finish = mpd_mpg123_finish, + .file_decode = mpd_mpg123_file_decode, + /* streaming not yet implemented */ + .tag_dup = mpd_mpg123_tag_dup, + .suffixes = mpg123_suffixes, +}; diff --git a/src/decoder/oggflac_plugin.c b/src/decoder/oggflac_decoder_plugin.c index bdd589ccb..7e5f48318 100644 --- a/src/decoder/oggflac_plugin.c +++ b/src/decoder/oggflac_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,19 +21,18 @@ * OggFLAC support (half-stolen from flac_plugin.c :)) */ +#include "config.h" /* must be first for large file support */ #include "_flac_common.h" #include "_ogg_common.h" +#include "flac_metadata.h" #include <glib.h> #include <OggFLAC/seekable_stream_decoder.h> #include <assert.h> #include <unistd.h> -static void oggflac_cleanup(struct flac_data *data, - OggFLAC__SeekableStreamDecoder * decoder) +static void oggflac_cleanup(OggFLAC__SeekableStreamDecoder * decoder) { - if (data->replay_gain_info) - replay_gain_info_free(data->replay_gain_info); if (decoder) OggFLAC__seekable_stream_decoder_delete(decoder); } @@ -67,7 +66,7 @@ static OggFLAC__SeekableStreamDecoderSeekStatus of_seek_cb(G_GNUC_UNUSED const { struct flac_data *data = (struct flac_data *) fdata; - if (!input_stream_seek(data->input_stream, offset, SEEK_SET)) + if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL)) return OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR; return OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK; @@ -156,13 +155,8 @@ oggflac_write_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecoder *decoder, void *vdata) { struct flac_data *data = (struct flac_data *) vdata; - FLAC__uint32 samples = frame->header.blocksize; - float time_change; - time_change = ((float)samples) / frame->header.sample_rate; - data->time += time_change; - - return flac_common_write(data, frame, buf); + return flac_common_write(data, frame, buf, 0); } /* used by TagDup */ @@ -173,17 +167,7 @@ static void of_metadata_dup_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecode assert(data->tag != NULL); - switch (block->type) { - case FLAC__METADATA_TYPE_STREAMINFO: - data->tag->time = ((float)block->data.stream_info. - total_samples) / - block->data.stream_info.sample_rate + 0.5; - return; - case FLAC__METADATA_TYPE_VORBIS_COMMENT: - flac_vorbis_comments_to_tag(data->tag, NULL, block); - default: - break; - } + flac_tag_apply_metadata(data->tag, NULL, block); } /* used by decode */ @@ -259,24 +243,20 @@ fail: /* public functions: */ static struct tag * -oggflac_tag_dup(const char *file) +oggflac_stream_tag(struct input_stream *is) { - struct input_stream input_stream; OggFLAC__SeekableStreamDecoder *decoder; struct flac_data data; + struct tag *tag; - if (!input_stream_open(&input_stream, file)) - return NULL; - if (ogg_stream_type_detect(&input_stream) != FLAC) { - input_stream_close(&input_stream); + if (ogg_stream_type_detect(is) != FLAC) return NULL; - } /* rewind the stream, because ogg_stream_type_detect() has moved it */ - input_stream_seek(&input_stream, 0, SEEK_SET); + input_stream_seek(is, 0, SEEK_SET, NULL); - flac_data_init(&data, NULL, &input_stream); + flac_data_init(&data, NULL, is); data.tag = tag_new(); @@ -284,15 +264,17 @@ oggflac_tag_dup(const char *file) * data.tag will be set or unset, that's all we care about */ decoder = full_decoder_init_and_read_metadata(&data, 1); - oggflac_cleanup(&data, decoder); - input_stream_close(&input_stream); + oggflac_cleanup(decoder); - if (!tag_is_defined(data.tag)) { - tag_free(data.tag); + if (tag_is_defined(data.tag)) { + tag = data.tag; data.tag = NULL; - } + } else + tag = NULL; - return data.tag; + flac_data_deinit(&data); + + return tag; } static void @@ -300,13 +282,14 @@ oggflac_decode(struct decoder * mpd_decoder, struct input_stream *input_stream) { OggFLAC__SeekableStreamDecoder *decoder = NULL; struct flac_data data; + struct audio_format audio_format; if (ogg_stream_type_detect(input_stream) != FLAC) return; /* rewind the stream, because ogg_stream_type_detect() has moved it */ - input_stream_seek(input_stream, 0, SEEK_SET); + input_stream_seek(input_stream, 0, SEEK_SET, NULL); flac_data_init(&data, mpd_decoder, input_stream); @@ -314,16 +297,13 @@ oggflac_decode(struct decoder * mpd_decoder, struct input_stream *input_stream) goto fail; } - if (!audio_format_valid(&data.audio_format)) { - g_warning("Invalid audio format: %u:%u:%u\n", - data.audio_format.sample_rate, - data.audio_format.bits, - data.audio_format.channels); + if (!data.initialized) goto fail; - } - decoder_initialized(mpd_decoder, &data.audio_format, - input_stream->seekable, data.total_time); + decoder_initialized(mpd_decoder, &audio_format, + input_stream->seekable, + (float)data.total_frames / + (float)data.audio_format.sample_rate); while (true) { OggFLAC__seekable_stream_decoder_process_single(decoder); @@ -333,11 +313,10 @@ oggflac_decode(struct decoder * mpd_decoder, struct input_stream *input_stream) } if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) { FLAC__uint64 seek_sample = decoder_seek_where(mpd_decoder) * - data.audio_format.sample_rate + 0.5; + data.audio_format.sample_rate; if (OggFLAC__seekable_stream_decoder_seek_absolute (decoder, seek_sample)) { - data.time = ((float)seek_sample) / - data.audio_format.sample_rate; + data.next_frame = seek_sample; data.position = 0; decoder_command_finished(mpd_decoder); } else @@ -352,7 +331,8 @@ oggflac_decode(struct decoder * mpd_decoder, struct input_stream *input_stream) } fail: - oggflac_cleanup(&data, decoder); + oggflac_cleanup(decoder); + flac_data_deinit(&data); } static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL }; @@ -368,7 +348,7 @@ static const char *const oggflac_mime_types[] = { const struct decoder_plugin oggflac_decoder_plugin = { .name = "oggflac", .stream_decode = oggflac_decode, - .tag_dup = oggflac_tag_dup, + .stream_tag = oggflac_stream_tag, .suffixes = oggflac_suffixes, .mime_types = oggflac_mime_types }; diff --git a/src/decoder/sidplay_decoder_plugin.cxx b/src/decoder/sidplay_decoder_plugin.cxx new file mode 100644 index 000000000..6fceeb30f --- /dev/null +++ b/src/decoder/sidplay_decoder_plugin.cxx @@ -0,0 +1,429 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +extern "C" { +#include "../decoder_api.h" +} + +#include <errno.h> +#include <stdlib.h> +#include <glib.h> + +#include <sidplay/sidplay2.h> +#include <sidplay/builders/resid.h> +#include <sidplay/utils/SidTuneMod.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "sidplay" + +#define SUBTUNE_PREFIX "tune_" + +static GPatternSpec *path_with_subtune; +static const char *songlength_file; +static GKeyFile *songlength_database; + +static bool all_files_are_containers; +static unsigned default_songlength; + +static bool filter_setting; + +static GKeyFile * +sidplay_load_songlength_db(const char *path) +{ + GError *error = NULL; + gchar *data; + gsize size; + + if (!g_file_get_contents(path, &data, &size, &error)) { + g_warning("unable to read songlengths file %s: %s", + path, error->message); + g_error_free(error); + return NULL; + } + + /* replace any ; comment characters with # */ + for (gsize i = 0; i < size; i++) + if (data[i] == ';') + data[i] = '#'; + + GKeyFile *db = g_key_file_new(); + bool success = g_key_file_load_from_data(db, data, size, + G_KEY_FILE_NONE, &error); + g_free(data); + if (!success) { + g_warning("unable to parse songlengths file %s: %s", + path, error->message); + g_error_free(error); + g_key_file_free(db); + return NULL; + } + + g_key_file_set_list_separator(db, ' '); + return db; +} + +static bool +sidplay_init(const struct config_param *param) +{ + /* read the songlengths database file */ + songlength_file=config_get_block_string(param, + "songlength_database", NULL); + if (songlength_file != NULL) + songlength_database = sidplay_load_songlength_db(songlength_file); + + default_songlength=config_get_block_unsigned(param, + "default_songlength", 0); + + all_files_are_containers=config_get_block_bool(param, + "all_files_are_containers", true); + + path_with_subtune=g_pattern_spec_new( + "*/" SUBTUNE_PREFIX "???.sid"); + + filter_setting=config_get_block_bool(param, "filter", true); + + return true; +} + +void +sidplay_finish() +{ + g_pattern_spec_free(path_with_subtune); + + if(songlength_database) + g_key_file_free(songlength_database); +} + +/** + * returns the file path stripped of any /tune_xxx.sid subtune + * suffix + */ +static char * +get_container_name(const char *path_fs) +{ + char *path_container=g_strdup(path_fs); + + if(!g_pattern_match(path_with_subtune, + strlen(path_container), path_container, NULL)) + return path_container; + + char *ptr=g_strrstr(path_container, "/" SUBTUNE_PREFIX); + if(ptr) *ptr='\0'; + + return path_container; +} + +/** + * returns tune number from file.sid/tune_xxx.sid style path or 1 if + * no subtune is appended + */ +static int +get_song_num(const char *path_fs) +{ + if(g_pattern_match(path_with_subtune, + strlen(path_fs), path_fs, NULL)) { + char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX); + if(!sub) return 1; + + sub+=strlen("/" SUBTUNE_PREFIX); + int song_num=strtol(sub, NULL, 10); + + if (errno == EINVAL) + return 1; + else + return song_num; + } else + return 1; +} + +/* get the song length in seconds */ +static int +get_song_length(const char *path_fs) +{ + if (songlength_database == NULL) + return -1; + + gchar *sid_file=get_container_name(path_fs); + SidTuneMod tune(sid_file); + g_free(sid_file); + if(!tune) { + g_warning("failed to load file for calculating md5 sum"); + return -1; + } + char md5sum[SIDTUNE_MD5_LENGTH+1]; + tune.createMD5(md5sum); + + int song_num=get_song_num(path_fs); + + gsize num_items; + gchar **values=g_key_file_get_string_list(songlength_database, + "Database", md5sum, &num_items, NULL); + if(!values || song_num>num_items) { + g_strfreev(values); + return -1; + } + + int minutes=strtol(values[song_num-1], NULL, 10); + if(errno==EINVAL) minutes=0; + + int seconds; + char *ptr=strchr(values[song_num-1], ':'); + if(ptr) { + seconds=strtol(ptr+1, NULL, 10); + if(errno==EINVAL) seconds=0; + } else + seconds=0; + + g_strfreev(values); + + return (minutes*60)+seconds; +} + +static void +sidplay_file_decode(struct decoder *decoder, const char *path_fs) +{ + int ret; + int channels; + + /* load the tune */ + + char *path_container=get_container_name(path_fs); + SidTune tune(path_container, NULL, true); + g_free(path_container); + if (!tune) { + g_warning("failed to load file"); + return; + } + + int song_num=get_song_num(path_fs); + tune.selectSong(song_num); + + int song_len=get_song_length(path_fs); + if(song_len==-1) song_len=default_songlength; + + /* initialize the player */ + + sidplay2 player; + int iret = player.load(&tune); + if (iret != 0) { + g_warning("sidplay2.load() failed: %s", player.error()); + return; + } + + /* initialize the builder */ + + ReSIDBuilder builder("ReSID"); + if (!builder) { + g_warning("failed to initialize ReSIDBuilder"); + return; + } + + builder.create(player.info().maxsids); + if (!builder) { + g_warning("ReSIDBuilder.create() failed"); + return; + } + + builder.filter(filter_setting); + if (!builder) { + g_warning("ReSIDBuilder.filter() failed"); + return; + } + + /* configure the player */ + + sid2_config_t config = player.config(); + + config.clockDefault = SID2_CLOCK_PAL; + config.clockForced = true; + config.clockSpeed = SID2_CLOCK_CORRECT; + config.frequency = 48000; + config.optimisation = SID2_DEFAULT_OPTIMISATION; + + config.precision = 16; + config.sidDefault = SID2_MOS6581; + config.sidEmulation = &builder; + config.sidModel = SID2_MODEL_CORRECT; + config.sidSamples = true; +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + config.sampleFormat = SID2_LITTLE_SIGNED; +#else + config.sampleFormat = SID2_BIG_SIGNED; +#endif + if (tune.isStereo()) { + config.playback = sid2_stereo; + channels = 2; + } else { + config.playback = sid2_mono; + channels = 1; + } + + iret = player.config(config); + if (iret != 0) { + g_warning("sidplay2.config() failed: %s", player.error()); + return; + } + + /* initialize the MPD decoder */ + + struct audio_format audio_format; + audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, channels); + assert(audio_format_valid(&audio_format)); + + decoder_initialized(decoder, &audio_format, true, (float)song_len); + + /* .. and play */ + + const unsigned timebase = player.timebase(); + song_len *= timebase; + + enum decoder_command cmd; + do { + char buffer[4096]; + size_t nbytes; + + nbytes = player.play(buffer, sizeof(buffer)); + if (nbytes == 0) + break; + + decoder_timestamp(decoder, (double)player.time() / timebase); + + cmd = decoder_data(decoder, NULL, buffer, nbytes, 0); + + if(cmd==DECODE_COMMAND_SEEK) { + unsigned data_time = player.time(); + unsigned target_time = (unsigned) + (decoder_seek_where(decoder) * timebase); + + /* can't rewind so return to zero and seek forward */ + if(target_time<data_time) { + player.stop(); + data_time=0; + } + + /* ignore data until target time is reached */ + while(data_time<target_time) { + nbytes=player.play(buffer, sizeof(buffer)); + if(nbytes==0) + break; + data_time = player.time(); + } + + decoder_command_finished(decoder); + } + + if (song_len > 0 && player.time() >= song_len) + break; + + } while (cmd != DECODE_COMMAND_STOP); +} + +static struct tag * +sidplay_tag_dup(const char *path_fs) +{ + int song_num=get_song_num(path_fs); + char *path_container=get_container_name(path_fs); + + SidTune tune(path_container, NULL, true); + g_free(path_container); + if (!tune) + return NULL; + + const SidTuneInfo &info = tune.getInfo(); + struct tag *tag = tag_new(); + + /* title */ + const char *title; + if (info.numberOfInfoStrings > 0 && info.infoString[0] != NULL) + title=info.infoString[0]; + else + title=""; + + if(info.songs>1) { + char *tag_title=g_strdup_printf("%s (%d/%d)", + title, song_num, info.songs); + tag_add_item(tag, TAG_TITLE, tag_title); + g_free(tag_title); + } else + tag_add_item(tag, TAG_TITLE, title); + + /* artist */ + if (info.numberOfInfoStrings > 1 && info.infoString[1] != NULL) + tag_add_item(tag, TAG_ARTIST, info.infoString[1]); + + /* track */ + char *track=g_strdup_printf("%d", song_num); + tag_add_item(tag, TAG_TRACK, track); + g_free(track); + + /* time */ + int song_len=get_song_length(path_fs); + if(song_len!=-1) tag->time=song_len; + + return tag; +} + +static char * +sidplay_container_scan(const char *path_fs, const unsigned int tnum) +{ + SidTune tune(path_fs, NULL, true); + if (!tune) + return NULL; + + const SidTuneInfo &info=tune.getInfo(); + + /* Don't treat sids containing a single tune + as containers */ + if(!all_files_are_containers && info.songs<2) + return NULL; + + /* Construct container/tune path names, eg. + Delta.sid/tune_001.sid */ + if(tnum<=info.songs) { + char *subtune= g_strdup_printf( + SUBTUNE_PREFIX "%03u.sid", tnum); + return subtune; + } else + return NULL; +} + +static const char *const sidplay_suffixes[] = { + "sid", + "mus", + "str", + "prg", + "P00", + NULL +}; + +extern const struct decoder_plugin sidplay_decoder_plugin; +const struct decoder_plugin sidplay_decoder_plugin = { + "sidplay", + sidplay_init, + sidplay_finish, + NULL, /* stream_decode() */ + sidplay_file_decode, + sidplay_tag_dup, + NULL, /* stream_tag() */ + sidplay_container_scan, + sidplay_suffixes, + NULL, /* mime_types */ +}; diff --git a/src/decoder/sidplay_plugin.cxx b/src/decoder/sidplay_plugin.cxx deleted file mode 100644 index c62e6b4b6..000000000 --- a/src/decoder/sidplay_plugin.cxx +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (C) 2003-2009 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -extern "C" { -#include "../decoder_api.h" -} - -#include <glib.h> - -#include <sidplay/sidplay2.h> -#include <sidplay/builders/resid.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "sidplay" - -static void -sidplay_file_decode(struct decoder *decoder, const char *path_fs) -{ - int ret; - - /* load the tune */ - - SidTune tune(path_fs, NULL, true); - if (!tune) { - g_warning("failed to load file"); - return; - } - - tune.selectSong(1); - - /* initialize the player */ - - sidplay2 player; - int iret = player.load(&tune); - if (iret != 0) { - g_warning("sidplay2.load() failed: %s", player.error()); - return; - } - - /* initialize the builder */ - - ReSIDBuilder builder("ReSID"); - if (!builder) { - g_warning("failed to initialize ReSIDBuilder"); - return; - } - - builder.create(player.info().maxsids); - if (!builder) { - g_warning("ReSIDBuilder.create() failed"); - return; - } - - builder.filter(false); - if (!builder) { - g_warning("ReSIDBuilder.filter() failed"); - return; - } - - /* configure the player */ - - sid2_config_t config = player.config(); - - config.clockDefault = SID2_CLOCK_PAL; - config.clockForced = true; - config.clockSpeed = SID2_CLOCK_CORRECT; - config.frequency = 48000; - config.optimisation = SID2_DEFAULT_OPTIMISATION; - config.playback = sid2_stereo; - config.precision = 16; - config.sidDefault = SID2_MOS6581; - config.sidEmulation = &builder; - config.sidModel = SID2_MODEL_CORRECT; - config.sidSamples = true; -#if G_BYTE_ORDER == G_LITTLE_ENDIAN - config.sampleFormat = SID2_LITTLE_SIGNED; -#else - config.sampleFormat = SID2_BIG_SIGNED; -#endif - - iret = player.config(config); - if (iret != 0) { - g_warning("sidplay2.config() failed: %s", player.error()); - return; - } - - /* initialize the MPD decoder */ - - struct audio_format audio_format; - audio_format.sample_rate = 48000; - audio_format.bits = 16; - audio_format.channels = 2; - - decoder_initialized(decoder, &audio_format, false, -1); - - /* .. and play */ - - enum decoder_command cmd; - do { - char buffer[4096]; - size_t nbytes; - - nbytes = player.play(buffer, sizeof(buffer)); - if (nbytes == 0) - break; - - cmd = decoder_data(decoder, NULL, buffer, nbytes, - 0, 0, NULL); - } while (cmd == DECODE_COMMAND_NONE); -} - -static struct tag * -sidplay_tag_dup(const char *path_fs) -{ - SidTune tune(path_fs, NULL, true); - if (!tune) - return NULL; - - const SidTuneInfo &info = tune.getInfo(); - struct tag *tag = tag_new(); - - if (info.numberOfInfoStrings > 0 && info.infoString[0] != NULL) - tag_add_item(tag, TAG_ITEM_TITLE, info.infoString[0]); - - if (info.numberOfInfoStrings > 1 && info.infoString[1] != NULL) - tag_add_item(tag, TAG_ITEM_ARTIST, info.infoString[1]); - - return tag; -} - -static const char *const sidplay_suffixes[] = { - "sid", - NULL -}; - -extern const struct decoder_plugin sidplay_decoder_plugin; -const struct decoder_plugin sidplay_decoder_plugin = { - "sidplay", - NULL, /* init() */ - NULL, /* finish() */ - NULL, /* stream_decode() */ - sidplay_file_decode, - sidplay_tag_dup, - NULL, /* container_scan */ - sidplay_suffixes, - NULL, /* mime_types */ -}; diff --git a/src/decoder/sndfile_decoder_plugin.c b/src/decoder/sndfile_decoder_plugin.c new file mode 100644 index 000000000..af68f117d --- /dev/null +++ b/src/decoder/sndfile_decoder_plugin.c @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "decoder_api.h" +#include "audio_check.h" + +#include <sndfile.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "sndfile" + +static sf_count_t +sndfile_vio_get_filelen(void *user_data) +{ + const struct input_stream *is = user_data; + + return is->size; +} + +static sf_count_t +sndfile_vio_seek(sf_count_t offset, int whence, void *user_data) +{ + struct input_stream *is = user_data; + bool success; + + success = input_stream_seek(is, offset, whence, NULL); + if (!success) + return -1; + + return is->offset; +} + +static sf_count_t +sndfile_vio_read(void *ptr, sf_count_t count, void *user_data) +{ + struct input_stream *is = user_data; + GError *error = NULL; + size_t nbytes; + + nbytes = input_stream_read(is, ptr, count, &error); + if (nbytes == 0 && error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + return -1; + } + + return nbytes; +} + +static sf_count_t +sndfile_vio_write(G_GNUC_UNUSED const void *ptr, + G_GNUC_UNUSED sf_count_t count, + G_GNUC_UNUSED void *user_data) +{ + /* no writing! */ + return -1; +} + +static sf_count_t +sndfile_vio_tell(void *user_data) +{ + const struct input_stream *is = user_data; + + return is->offset; +} + +/** + * This SF_VIRTUAL_IO implementation wraps MPD's #input_stream to a + * libsndfile stream. + */ +static SF_VIRTUAL_IO vio = { + .get_filelen = sndfile_vio_get_filelen, + .seek = sndfile_vio_seek, + .read = sndfile_vio_read, + .write = sndfile_vio_write, + .tell = sndfile_vio_tell, +}; + +/** + * Converts a frame number to a timestamp (in seconds). + */ +static float +frame_to_time(sf_count_t frame, const struct audio_format *audio_format) +{ + return (float)frame / (float)audio_format->sample_rate; +} + +/** + * Converts a timestamp (in seconds) to a frame number. + */ +static sf_count_t +time_to_frame(float t, const struct audio_format *audio_format) +{ + return (sf_count_t)(t * audio_format->sample_rate); +} + +static void +sndfile_stream_decode(struct decoder *decoder, struct input_stream *is) +{ + GError *error = NULL; + SNDFILE *sf; + SF_INFO info; + struct audio_format audio_format; + size_t frame_size; + sf_count_t read_frames, num_frames; + int buffer[4096]; + enum decoder_command cmd; + + info.format = 0; + + sf = sf_open_virtual(&vio, SFM_READ, &info, is); + if (sf == NULL) { + g_warning("sf_open_virtual() failed"); + return; + } + + /* for now, always read 32 bit samples. Later, we could lower + MPD's CPU usage by reading 16 bit samples with + sf_readf_short() on low-quality source files. */ + if (!audio_format_init_checked(&audio_format, info.samplerate, + SAMPLE_FORMAT_S32, + info.channels, &error)) { + g_warning("%s", error->message); + g_error_free(error); + return; + } + + decoder_initialized(decoder, &audio_format, info.seekable, + frame_to_time(info.frames, &audio_format)); + + frame_size = audio_format_frame_size(&audio_format); + read_frames = sizeof(buffer) / frame_size; + + do { + num_frames = sf_readf_int(sf, buffer, read_frames); + if (num_frames <= 0) + break; + + cmd = decoder_data(decoder, is, + buffer, num_frames * frame_size, + 0); + if (cmd == DECODE_COMMAND_SEEK) { + sf_count_t c = + time_to_frame(decoder_seek_where(decoder), + &audio_format); + c = sf_seek(sf, c, SEEK_SET); + if (c < 0) + decoder_seek_error(decoder); + else + decoder_command_finished(decoder); + cmd = DECODE_COMMAND_NONE; + } + } while (cmd == DECODE_COMMAND_NONE); + + sf_close(sf); +} + +static struct tag * +sndfile_tag_dup(const char *path_fs) +{ + SNDFILE *sf; + SF_INFO info; + struct tag *tag; + const char *p; + + info.format = 0; + + sf = sf_open(path_fs, SFM_READ, &info); + if (sf == NULL) + return NULL; + + if (!audio_valid_sample_rate(info.samplerate)) { + sf_close(sf); + g_warning("Invalid sample rate in %s\n", path_fs); + return NULL; + } + + tag = tag_new(); + tag->time = info.frames / info.samplerate; + + p = sf_get_string(sf, SF_STR_TITLE); + if (p != NULL) + tag_add_item(tag, TAG_TITLE, p); + + p = sf_get_string(sf, SF_STR_ARTIST); + if (p != NULL) + tag_add_item(tag, TAG_ARTIST, p); + + p = sf_get_string(sf, SF_STR_DATE); + if (p != NULL) + tag_add_item(tag, TAG_DATE, p); + + sf_close(sf); + + return tag; +} + +static const char *const sndfile_suffixes[] = { + "wav", "aiff", "aif", /* Microsoft / SGI / Apple */ + "au", "snd", /* Sun / DEC / NeXT */ + "paf", /* Paris Audio File */ + "iff", "svx", /* Commodore Amiga IFF / SVX */ + "sf", /* IRCAM */ + "voc", /* Creative */ + "w64", /* Soundforge */ + "pvf", /* Portable Voice Format */ + "xi", /* Fasttracker */ + "htk", /* HMM Tool Kit */ + "caf", /* Apple */ + "sd2", /* Sound Designer II */ + + /* libsndfile also supports FLAC and Ogg Vorbis, but only by + linking with libFLAC and libvorbis - we can do better, we + have native plugins for these libraries */ + + NULL +}; + +static const char *const sndfile_mime_types[] = { + "audio/x-wav", + "audio/x-aiff", + + /* what are the MIME types of the other supported formats? */ + + NULL +}; + +const struct decoder_plugin sndfile_decoder_plugin = { + .name = "sndfile", + .stream_decode = sndfile_stream_decode, + .tag_dup = sndfile_tag_dup, + .suffixes = sndfile_suffixes, + .mime_types = sndfile_mime_types, +}; diff --git a/src/decoder/vorbis_plugin.c b/src/decoder/vorbis_decoder_plugin.c index 7c782a779..0a3944ad6 100644 --- a/src/decoder/vorbis_plugin.c +++ b/src/decoder/vorbis_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,19 +17,19 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -/* TODO 'ogg' should probably be replaced with 'oggvorbis' in all instances */ - -#include "_ogg_common.h" #include "config.h" +#include "_ogg_common.h" +#include "audio_check.h" #include "uri.h" #ifndef HAVE_TREMOR +#define OV_EXCLUDE_STATIC_CALLBACKS #include <vorbis/vorbisfile.h> #else #include <tremor/ivorbisfile.h> /* Macros to make Tremor's API look like libogg. Tremor always returns host-byte-order 16-bit signed data, and uses integer - milliseconds where libogg uses double seconds. + milliseconds where libogg uses double seconds. */ #define ov_read(VF, BUFFER, LENGTH, BIGENDIANP, WORD, SGNED, BITSTREAM) \ ov_read(VF, BUFFER, LENGTH, BITSTREAM) @@ -55,46 +55,46 @@ #define OGG_DECODE_USE_BIGENDIAN 0 #endif -typedef struct _OggCallbackData { +struct vorbis_input_stream { struct decoder *decoder; struct input_stream *input_stream; bool seekable; -} OggCallbackData; +}; -static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *vdata) +static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data) { + struct vorbis_input_stream *vis = data; size_t ret; - OggCallbackData *data = (OggCallbackData *) vdata; - ret = decoder_read(data->decoder, data->input_stream, ptr, size * nmemb); + ret = decoder_read(vis->decoder, vis->input_stream, ptr, size * nmemb); errno = 0; return ret / size; } -static int ogg_seek_cb(void *vdata, ogg_int64_t offset, int whence) +static int ogg_seek_cb(void *data, ogg_int64_t offset, int whence) { - const OggCallbackData *data = (const OggCallbackData *) vdata; + struct vorbis_input_stream *vis = data; - return data->seekable && - decoder_get_command(data->decoder) != DECODE_COMMAND_STOP && - input_stream_seek(data->input_stream, offset, whence) + return vis->seekable && + (!vis->decoder || decoder_get_command(vis->decoder) != DECODE_COMMAND_STOP) && + input_stream_seek(vis->input_stream, offset, whence, NULL) ? 0 : -1; } /* TODO: check Ogg libraries API and see if we can just not have this func */ -static int ogg_close_cb(G_GNUC_UNUSED void *vdata) +static int ogg_close_cb(G_GNUC_UNUSED void *data) { return 0; } -static long ogg_tell_cb(void *vdata) +static long ogg_tell_cb(void *data) { - const OggCallbackData *data = (const OggCallbackData *) vdata; + const struct vorbis_input_stream *vis = data; - return (long)data->input_stream->offset; + return (long)vis->input_stream->offset; } static const ov_callbacks vorbis_is_callbacks = { @@ -105,6 +105,52 @@ static const ov_callbacks vorbis_is_callbacks = { }; static const char * +vorbis_strerror(int code) +{ + switch (code) { + case OV_EREAD: + return "read error"; + + case OV_ENOTVORBIS: + return "not vorbis stream"; + + case OV_EVERSION: + return "vorbis version mismatch"; + + case OV_EBADHEADER: + return "invalid vorbis header"; + + case OV_EFAULT: + return "internal logic error"; + + default: + return "unknown error"; + } +} + +static bool +vorbis_is_open(struct vorbis_input_stream *vis, OggVorbis_File *vf, + struct decoder *decoder, struct input_stream *input_stream) +{ + vis->decoder = decoder; + vis->input_stream = input_stream; + vis->seekable = input_stream->seekable && + (input_stream->uri == NULL || + !uri_has_scheme(input_stream->uri)); + + int ret = ov_open_callbacks(vis, vf, NULL, 0, vorbis_is_callbacks); + if (ret < 0) { + if (decoder == NULL || + decoder_get_command(decoder) == DECODE_COMMAND_NONE) + g_warning("Failed to open Ogg Vorbis stream: %s", + vorbis_strerror(ret)); + return false; + } + + return true; +} + +static const char * vorbis_comment_value(const char *comment, const char *needle) { size_t len = strlen(needle); @@ -116,14 +162,13 @@ vorbis_comment_value(const char *comment, const char *needle) return NULL; } -static struct replay_gain_info * -vorbis_comments_to_replay_gain(char **comments) +static bool +vorbis_comments_to_replay_gain(struct replay_gain_info *rgi, char **comments) { - struct replay_gain_info *rgi; const char *temp; bool found = false; - rgi = replay_gain_info_new(); + replay_gain_info_init(rgi); while (*comments) { if ((temp = @@ -147,12 +192,7 @@ vorbis_comments_to_replay_gain(char **comments) comments++; } - if (!found) { - replay_gain_info_free(rgi); - rgi = NULL; - } - - return rgi; + return found; } static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber"; @@ -183,11 +223,11 @@ vorbis_parse_comment(struct tag *tag, const char *comment) assert(tag != NULL); if (vorbis_copy_comment(tag, comment, VORBIS_COMMENT_TRACK_KEY, - TAG_ITEM_TRACK) || + TAG_TRACK) || vorbis_copy_comment(tag, comment, VORBIS_COMMENT_DISC_KEY, - TAG_ITEM_DISC) || + TAG_DISC) || vorbis_copy_comment(tag, comment, "album artist", - TAG_ITEM_ALBUM_ARTIST)) + TAG_ALBUM_ARTIST)) return; for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) @@ -226,42 +266,23 @@ vorbis_send_comments(struct decoder *decoder, struct input_stream *is, tag_free(tag); } -static bool -oggvorbis_seekable(struct decoder *decoder) -{ - char *uri; - bool seekable; - - uri = decoder_get_uri(decoder); - if (uri == NULL) - return false; - - /* disable seeking on remote streams, because libvorbis seeks - around like crazy, and due to being very expensive, this - delays song playback my 10 or 20 seconds */ - seekable = !uri_has_scheme(uri); - g_free(uri); - - return seekable; -} - /* public */ static void vorbis_stream_decode(struct decoder *decoder, struct input_stream *input_stream) { + GError *error = NULL; OggVorbis_File vf; - OggCallbackData data; + struct vorbis_input_stream vis; struct audio_format audio_format; + float total_time; int current_section; int prev_section = -1; long ret; char chunk[OGG_CHUNK_SIZE]; long bitRate = 0; long test; - struct replay_gain_info *replay_gain_info = NULL; - char **comments; - bool initialized = false; + const vorbis_info *vi; enum decoder_command cmd = DECODE_COMMAND_NONE; if (ogg_stream_type_detect(input_stream) != VORBIS) @@ -269,43 +290,30 @@ vorbis_stream_decode(struct decoder *decoder, /* rewind the stream, because ogg_stream_type_detect() has moved it */ - input_stream_seek(input_stream, 0, SEEK_SET); - - data.decoder = decoder; - data.input_stream = input_stream; - data.seekable = input_stream->seekable && oggvorbis_seekable(decoder); + input_stream_seek(input_stream, 0, SEEK_SET, NULL); - if ((ret = ov_open_callbacks(&data, &vf, NULL, 0, - vorbis_is_callbacks)) < 0) { - const char *error; - if (decoder_get_command(decoder) != DECODE_COMMAND_NONE) - return; + if (!vorbis_is_open(&vis, &vf, decoder, input_stream)) + return; - switch (ret) { - case OV_EREAD: - error = "read error"; - break; - case OV_ENOTVORBIS: - error = "not vorbis stream"; - break; - case OV_EVERSION: - error = "vorbis version mismatch"; - break; - case OV_EBADHEADER: - error = "invalid vorbis header"; - break; - case OV_EFAULT: - error = "internal logic error"; - break; - default: - error = "unknown error"; - break; - } + vi = ov_info(&vf, -1); + if (vi == NULL) { + g_warning("ov_info() has failed"); + return; + } - g_warning("Error decoding Ogg Vorbis stream: %s", error); + if (!audio_format_init_checked(&audio_format, vi->rate, + SAMPLE_FORMAT_S16, + vi->channels, &error)) { + g_warning("%s", error->message); + g_error_free(error); return; } - audio_format.bits = 16; + + total_time = ov_time_total(&vf, -1); + if (total_time < 0) + total_time = 0; + + decoder_initialized(decoder, &audio_format, vis.seekable, total_time); do { if (cmd == DECODE_COMMAND_SEEK) { @@ -325,83 +333,61 @@ vorbis_stream_decode(struct decoder *decoder, break; if (current_section != prev_section) { - /*printf("new song!\n"); */ - vorbis_info *vi = ov_info(&vf, -1); - struct replay_gain_info *new_rgi; - - audio_format.channels = vi->channels; - audio_format.sample_rate = vi->rate; - - if (!audio_format_valid(&audio_format)) { - g_warning("Invalid audio format: %u:%u:%u\n", - audio_format.sample_rate, - audio_format.bits, - audio_format.channels); + char **comments; + + vi = ov_info(&vf, -1); + if (vi == NULL) { + g_warning("ov_info() has failed"); break; } - if (!initialized) { - float total_time = ov_time_total(&vf, -1); - if (total_time < 0) - total_time = 0; - decoder_initialized(decoder, &audio_format, - data.seekable, - total_time); - initialized = true; + if (vi->rate != (long)audio_format.sample_rate || + vi->channels != (int)audio_format.channels) { + /* we don't support audio format + change yet */ + g_warning("audio format change, stopping here"); + break; } + comments = ov_comment(&vf, -1)->user_comments; vorbis_send_comments(decoder, input_stream, comments); - new_rgi = vorbis_comments_to_replay_gain(comments); - if (new_rgi != NULL) { - if (replay_gain_info != NULL) - replay_gain_info_free(replay_gain_info); - replay_gain_info = new_rgi; - } - } - prev_section = current_section; + struct replay_gain_info rgi; + if (vorbis_comments_to_replay_gain(&rgi, comments)) + decoder_replay_gain(decoder, &rgi); + + prev_section = current_section; + } if ((test = ov_bitrate_instant(&vf)) > 0) bitRate = test / 1000; cmd = decoder_data(decoder, input_stream, chunk, ret, - ov_pcm_tell(&vf) / audio_format.sample_rate, - bitRate, replay_gain_info); + bitRate); } while (cmd != DECODE_COMMAND_STOP); - if (replay_gain_info) - replay_gain_info_free(replay_gain_info); - ov_clear(&vf); } static struct tag * -vorbis_tag_dup(const char *file) +vorbis_stream_tag(struct input_stream *is) { - struct tag *ret; - FILE *fp; + struct vorbis_input_stream vis; OggVorbis_File vf; - fp = fopen(file, "r"); - if (!fp) { + if (!vorbis_is_open(&vis, &vf, NULL, is)) return NULL; - } - if (ov_open(fp, &vf, NULL, 0) < 0) { - fclose(fp); - return NULL; - } - - ret = vorbis_comments_to_tag(ov_comment(&vf, -1)->user_comments); + struct tag *tag = vorbis_comments_to_tag(ov_comment(&vf, -1)->user_comments); - if (!ret) - ret = tag_new(); - ret->time = (int)(ov_time_total(&vf, -1) + 0.5); + if (tag == NULL) + tag = tag_new(); + tag->time = (int)(ov_time_total(&vf, -1) + 0.5); ov_clear(&vf); - return ret; + return tag; } static const char *const vorbis_suffixes[] = { @@ -423,7 +409,7 @@ static const char *const vorbis_mime_types[] = { const struct decoder_plugin vorbis_decoder_plugin = { .name = "vorbis", .stream_decode = vorbis_stream_decode, - .tag_dup = vorbis_tag_dup, + .stream_tag = vorbis_stream_tag, .suffixes = vorbis_suffixes, .mime_types = vorbis_mime_types }; diff --git a/src/decoder/wavpack_plugin.c b/src/decoder/wavpack_decoder_plugin.c index 7ad3a62b0..efed98851 100644 --- a/src/decoder/wavpack_plugin.c +++ b/src/decoder/wavpack_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,9 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../decoder_api.h" -#include "../path.h" -#include "../utils.h" +#include "config.h" +#include "decoder_api.h" +#include "audio_check.h" +#include "path.h" +#include "utils.h" #include <wavpack/wavpack.h> #include <glib.h> @@ -41,17 +43,17 @@ static struct { const char *name; enum tag_type type; } tagtypes[] = { - { "artist", TAG_ITEM_ARTIST }, - { "album", TAG_ITEM_ALBUM }, - { "title", TAG_ITEM_TITLE }, - { "track", TAG_ITEM_TRACK }, - { "name", TAG_ITEM_NAME }, - { "genre", TAG_ITEM_GENRE }, - { "date", TAG_ITEM_DATE }, - { "composer", TAG_ITEM_COMPOSER }, - { "performer", TAG_ITEM_PERFORMER }, - { "comment", TAG_ITEM_COMMENT }, - { "disc", TAG_ITEM_DISC }, + { "artist", TAG_ARTIST }, + { "album", TAG_ALBUM }, + { "title", TAG_TITLE }, + { "track", TAG_TRACK }, + { "name", TAG_NAME }, + { "genre", TAG_GENRE }, + { "date", TAG_DATE }, + { "composer", TAG_COMPOSER }, + { "performer", TAG_PERFORMER }, + { "comment", TAG_COMMENT }, + { "disc", TAG_DISC }, }; /** A pointer type for format converter function. */ @@ -97,19 +99,11 @@ format_samples_int(int bytes_per_sample, void *buffer, uint32_t count) } break; } + case 3: + case 4: /* do nothing */ break; - case 4: { - uint32_t *dst = buffer; - assert_static(sizeof(*dst) <= sizeof(*src)); - - /* downsample to 24-bit */ - while (count--) { - *dst++ = *src++ >> 8; - } - break; - } } } @@ -129,38 +123,61 @@ format_samples_float(G_GNUC_UNUSED int bytes_per_sample, void *buffer, } } +/** + * Choose a MPD sample format from libwavpacks' number of bits. + */ +static enum sample_format +wavpack_bits_to_sample_format(bool is_float, int bytes_per_sample) +{ + if (is_float) + return SAMPLE_FORMAT_S24_P32; + + switch (bytes_per_sample) { + case 1: + return SAMPLE_FORMAT_S8; + + case 2: + return SAMPLE_FORMAT_S16; + + case 3: + return SAMPLE_FORMAT_S24_P32; + + case 4: + return SAMPLE_FORMAT_S32; + + default: + return SAMPLE_FORMAT_UNDEFINED; + } +} + /* * This does the main decoding thing. * Requires an already opened WavpackContext. */ static void -wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek, - struct replay_gain_info *replay_gain_info) +wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek) { + GError *error = NULL; + bool is_float; + enum sample_format sample_format; struct audio_format audio_format; format_samples_t format_samples; char chunk[CHUNK_SIZE]; int samples_requested, samples_got; - float total_time, current_time; + float total_time; int bytes_per_sample, output_sample_size; - int position; - audio_format.sample_rate = WavpackGetSampleRate(wpc); - audio_format.channels = WavpackGetReducedChannels(wpc); - audio_format.bits = WavpackGetBitsPerSample(wpc); - - /* round bitwidth to 8-bit units */ - audio_format.bits = (audio_format.bits + 7) & (~7); - /* mpd handles max 24-bit samples */ - if (audio_format.bits > 24) { - audio_format.bits = 24; - } - - if (!audio_format_valid(&audio_format)) { - g_warning("Invalid audio format: %u:%u:%u\n", - audio_format.sample_rate, - audio_format.bits, - audio_format.channels); + is_float = (WavpackGetMode(wpc) & MODE_FLOAT) != 0; + sample_format = + wavpack_bits_to_sample_format(is_float, + WavpackGetBytesPerSample(wpc)); + + if (!audio_format_init_checked(&audio_format, + WavpackGetSampleRate(wpc), + sample_format, + WavpackGetNumChannels(wpc), &error)) { + g_warning("%s", error->message); + g_error_free(error); return; } @@ -180,8 +197,6 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek, decoder_initialized(decoder, &audio_format, can_seek, total_time); - position = 0; - do { if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) { if (can_seek) { @@ -189,7 +204,6 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek, audio_format.sample_rate; if (WavpackSeekSample(wpc, where)) { - position = where; decoder_command_finished(decoder); } else { decoder_seek_error(decoder); @@ -209,9 +223,6 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek, if (samples_got > 0) { int bitrate = (int)(WavpackGetInstantBitrate(wpc) / 1000 + 0.5); - position += samples_got; - current_time = position; - current_time /= audio_format.sample_rate; format_samples( bytes_per_sample, chunk, @@ -221,8 +232,7 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek, decoder_data( decoder, NULL, chunk, samples_got * output_sample_size, - current_time, bitrate, - replay_gain_info + bitrate ); } } while (samples_got > 0); @@ -246,13 +256,13 @@ wavpack_tag_float(WavpackContext *wpc, const char *key, float *value_r) return true; } -static struct replay_gain_info * -wavpack_replaygain(WavpackContext *wpc) +static bool +wavpack_replaygain(struct replay_gain_info *replay_gain_info, + WavpackContext *wpc) { - struct replay_gain_info *replay_gain_info; bool found = false; - replay_gain_info = replay_gain_info_new(); + replay_gain_info_init(replay_gain_info); found |= wavpack_tag_float( wpc, "replaygain_track_gain", @@ -271,13 +281,7 @@ wavpack_replaygain(WavpackContext *wpc) &replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak ); - if (found) { - return replay_gain_info; - } - - replay_gain_info_free(replay_gain_info); - - return NULL; + return found; } /* @@ -397,13 +401,13 @@ wavpack_input_get_pos(void *id) static int wavpack_input_set_pos_abs(void *id, uint32_t pos) { - return input_stream_seek(wpin(id)->is, pos, SEEK_SET) ? 0 : -1; + return input_stream_seek(wpin(id)->is, pos, SEEK_SET, NULL) ? 0 : -1; } static int wavpack_input_set_pos_rel(void *id, int32_t delta, int mode) { - return input_stream_seek(wpin(id)->is, delta, mode) ? 0 : -1; + return input_stream_seek(wpin(id)->is, delta, mode, NULL) ? 0 : -1; } static int @@ -452,13 +456,12 @@ wavpack_input_init(struct wavpack_input *isp, struct decoder *decoder, isp->last_byte = EOF; } -static bool -wavpack_open_wvc(struct decoder *decoder, struct input_stream *is_wvc, +static struct input_stream * +wavpack_open_wvc(struct decoder *decoder, const char *uri, struct wavpack_input *wpi) { - char *utf8url; + struct input_stream *is_wvc; char *wvc_url = NULL; - bool ret; char first_byte; size_t nbytes; @@ -466,20 +469,15 @@ wavpack_open_wvc(struct decoder *decoder, struct input_stream *is_wvc, * As we use dc->utf8url, this function will be bad for * single files. utf8url is not absolute file path :/ */ - utf8url = decoder_get_uri(decoder); - if (utf8url == NULL) { + if (uri == NULL) return false; - } - wvc_url = g_strconcat(utf8url, "c", NULL); - g_free(utf8url); - - ret = input_stream_open(is_wvc, wvc_url); + wvc_url = g_strconcat(uri, "c", NULL); + is_wvc = input_stream_open(wvc_url, NULL); g_free(wvc_url); - if (!ret) { - return false; - } + if (is_wvc == NULL) + return NULL; /* * And we try to buffer in order to get know @@ -490,13 +488,13 @@ wavpack_open_wvc(struct decoder *decoder, struct input_stream *is_wvc, ); if (nbytes == 0) { input_stream_close(is_wvc); - return false; + return NULL; } /* push it back */ wavpack_input_init(wpi, decoder, is_wvc); wpi->last_byte = first_byte; - return true; + return is_wvc; } /* @@ -507,14 +505,15 @@ wavpack_streamdecode(struct decoder * decoder, struct input_stream *is) { char error[ERRORLEN]; WavpackContext *wpc; - struct input_stream is_wvc; - int open_flags = OPEN_2CH_MAX | OPEN_NORMALIZE; + struct input_stream *is_wvc; + int open_flags = OPEN_NORMALIZE; struct wavpack_input isp, isp_wvc; bool can_seek = is->seekable; - if (wavpack_open_wvc(decoder, &is_wvc, &isp_wvc)) { + is_wvc = wavpack_open_wvc(decoder, is->uri, &isp_wvc); + if (is_wvc != NULL) { open_flags |= OPEN_WVC; - can_seek &= is_wvc.seekable; + can_seek &= is_wvc->seekable; } if (!can_seek) { @@ -533,11 +532,11 @@ wavpack_streamdecode(struct decoder * decoder, struct input_stream *is) return; } - wavpack_decode(decoder, wpc, can_seek, NULL); + wavpack_decode(decoder, wpc, can_seek); WavpackCloseFile(wpc); if (open_flags & OPEN_WVC) { - input_stream_close(&is_wvc); + input_stream_close(is_wvc); } } @@ -549,11 +548,10 @@ wavpack_filedecode(struct decoder *decoder, const char *fname) { char error[ERRORLEN]; WavpackContext *wpc; - struct replay_gain_info *replay_gain_info; wpc = WavpackOpenFileInput( fname, error, - OPEN_TAGS | OPEN_WVC | OPEN_2CH_MAX | OPEN_NORMALIZE, 23 + OPEN_TAGS | OPEN_WVC | OPEN_NORMALIZE, 23 ); if (wpc == NULL) { g_warning( @@ -563,13 +561,11 @@ wavpack_filedecode(struct decoder *decoder, const char *fname) return; } - replay_gain_info = wavpack_replaygain(wpc); + struct replay_gain_info replay_gain_info; + if (wavpack_replaygain(&replay_gain_info, wpc)) + decoder_replay_gain(decoder, &replay_gain_info); - wavpack_decode(decoder, wpc, true, replay_gain_info); - - if (replay_gain_info) { - replay_gain_info_free(replay_gain_info); - } + wavpack_decode(decoder, wpc, true); WavpackCloseFile(wpc); } diff --git a/src/decoder/wildmidi_plugin.c b/src/decoder/wildmidi_decoder_plugin.c index b5e9810f9..66e6c61cf 100644 --- a/src/decoder/wildmidi_plugin.c +++ b/src/decoder/wildmidi_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,7 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../decoder_api.h" +#include "config.h" +#include "decoder_api.h" #include <glib.h> @@ -58,7 +59,7 @@ wildmidi_file_decode(struct decoder *decoder, const char *path_fs) { static const struct audio_format audio_format = { .sample_rate = WILDMIDI_SAMPLE_RATE, - .bits = 16, + .format = SAMPLE_FORMAT_S16, .channels = 2, }; midi *wm; @@ -90,10 +91,7 @@ wildmidi_file_decode(struct decoder *decoder, const char *path_fs) if (len <= 0) break; - cmd = decoder_data(decoder, NULL, buffer, len, - (float)info->current_sample / - (float)WILDMIDI_SAMPLE_RATE, - 0, NULL); + cmd = decoder_data(decoder, NULL, buffer, len, 0); if (cmd == DECODE_COMMAND_SEEK) { unsigned long seek_where = WILDMIDI_SAMPLE_RATE * @@ -116,21 +114,17 @@ wildmidi_file_decode(struct decoder *decoder, const char *path_fs) static struct tag * wildmidi_tag_dup(const char *path_fs) { - midi *wm; - const struct _WM_Info *info; - struct tag *tag; - - wm = WildMidi_Open(path_fs); + midi *wm = WildMidi_Open(path_fs); if (wm == NULL) return NULL; - info = WildMidi_GetInfo(wm); + const struct _WM_Info *info = WildMidi_GetInfo(wm); if (info == NULL) { WildMidi_Close(wm); return NULL; } - tag = tag_new(); + struct tag *tag = tag_new(); tag->time = info->approx_total_samples / WILDMIDI_SAMPLE_RATE; WildMidi_Close(wm); diff --git a/src/decoder_api.c b/src/decoder_api.c index c696ba101..fe34ea34a 100644 --- a/src/decoder_api.c +++ b/src/decoder_api.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "decoder_api.h" #include "decoder_internal.h" #include "decoder_control.h" @@ -24,10 +25,9 @@ #include "audio.h" #include "song.h" #include "buffer.h" - -#include "normalize.h" #include "pipe.h" #include "chunk.h" +#include "replay_gain_config.h" #include <glib.h> @@ -37,12 +37,16 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "decoder" -void decoder_initialized(G_GNUC_UNUSED struct decoder * decoder, - const struct audio_format *audio_format, - bool seekable, float total_time) +void +decoder_initialized(struct decoder *decoder, + const struct audio_format *audio_format, + bool seekable, float total_time) { - assert(dc.state == DECODE_STATE_START); - assert(dc.pipe != NULL); + struct decoder_control *dc = decoder->dc; + struct audio_format_string af_string; + + assert(dc->state == DECODE_STATE_START); + assert(dc->pipe != NULL); assert(decoder != NULL); assert(decoder->stream_tag == NULL); assert(decoder->decoder_tag == NULL); @@ -51,47 +55,49 @@ void decoder_initialized(G_GNUC_UNUSED struct decoder * decoder, assert(audio_format_defined(audio_format)); assert(audio_format_valid(audio_format)); - dc.in_audio_format = *audio_format; - getOutputAudioFormat(audio_format, &dc.out_audio_format); + dc->in_audio_format = *audio_format; + getOutputAudioFormat(audio_format, &dc->out_audio_format); + + dc->seekable = seekable; + dc->total_time = total_time; - dc.seekable = seekable; - dc.total_time = total_time; + decoder_lock(dc); + dc->state = DECODE_STATE_DECODE; + decoder_unlock(dc); - dc.state = DECODE_STATE_DECODE; - notify_signal(&pc.notify); + player_lock_signal(); - g_debug("audio_format=%u:%u:%u, seekable=%s", - dc.in_audio_format.sample_rate, dc.in_audio_format.bits, - dc.in_audio_format.channels, + g_debug("audio_format=%s, seekable=%s", + audio_format_to_string(&dc->in_audio_format, &af_string), seekable ? "true" : "false"); - if (!audio_format_equals(&dc.in_audio_format, &dc.out_audio_format)) - g_debug("converting to %u:%u:%u", - dc.out_audio_format.sample_rate, - dc.out_audio_format.bits, - dc.out_audio_format.channels); + if (!audio_format_equals(&dc->in_audio_format, + &dc->out_audio_format)) + g_debug("converting to %s", + audio_format_to_string(&dc->out_audio_format, + &af_string)); } -char *decoder_get_uri(G_GNUC_UNUSED struct decoder *decoder) +enum decoder_command decoder_get_command(G_GNUC_UNUSED struct decoder * decoder) { - assert(dc.pipe != NULL); + const struct decoder_control *dc = decoder->dc; + + assert(dc->pipe != NULL); - return song_get_uri(dc.current_song); + return dc->command; } -enum decoder_command decoder_get_command(G_GNUC_UNUSED struct decoder * decoder) +void +decoder_command_finished(struct decoder *decoder) { - assert(dc.pipe != NULL); + struct decoder_control *dc = decoder->dc; - return dc.command; -} + decoder_lock(dc); -void decoder_command_finished(G_GNUC_UNUSED struct decoder * decoder) -{ - assert(dc.command != DECODE_COMMAND_NONE); - assert(dc.command != DECODE_COMMAND_SEEK || - dc.seek_error || decoder->seeking); - assert(dc.pipe != NULL); + assert(dc->command != DECODE_COMMAND_NONE); + assert(dc->command != DECODE_COMMAND_SEEK || + dc->seek_error || decoder->seeking); + assert(dc->pipe != NULL); if (decoder->seeking) { decoder->seeking = false; @@ -99,33 +105,41 @@ void decoder_command_finished(G_GNUC_UNUSED struct decoder * decoder) /* delete frames from the old song position */ if (decoder->chunk != NULL) { - music_buffer_return(dc.buffer, decoder->chunk); + music_buffer_return(dc->buffer, decoder->chunk); decoder->chunk = NULL; } - music_pipe_clear(dc.pipe, dc.buffer); + music_pipe_clear(dc->pipe, dc->buffer); + + decoder->timestamp = dc->seek_where; } - dc.command = DECODE_COMMAND_NONE; - notify_signal(&pc.notify); + dc->command = DECODE_COMMAND_NONE; + decoder_unlock(dc); + + player_lock_signal(); } double decoder_seek_where(G_GNUC_UNUSED struct decoder * decoder) { - assert(dc.command == DECODE_COMMAND_SEEK); - assert(dc.pipe != NULL); + const struct decoder_control *dc = decoder->dc; + + assert(dc->command == DECODE_COMMAND_SEEK); + assert(dc->pipe != NULL); decoder->seeking = true; - return dc.seek_where; + return dc->seek_where; } void decoder_seek_error(struct decoder * decoder) { - assert(dc.command == DECODE_COMMAND_SEEK); - assert(dc.pipe != NULL); + struct decoder_control *dc = decoder->dc; + + assert(dc->command == DECODE_COMMAND_SEEK); + assert(dc->pipe != NULL); - dc.seek_error = true; + dc->seek_error = true; decoder->seeking = false; decoder_command_finished(decoder); @@ -135,11 +149,14 @@ size_t decoder_read(struct decoder *decoder, struct input_stream *is, void *buffer, size_t length) { + const struct decoder_control *dc = + decoder != NULL ? decoder->dc : NULL; + GError *error = NULL; size_t nbytes; assert(decoder == NULL || - dc.state == DECODE_STATE_START || - dc.state == DECODE_STATE_DECODE); + dc->state == DECODE_STATE_START || + dc->state == DECODE_STATE_DECODE); assert(is != NULL); assert(buffer != NULL); @@ -152,12 +169,19 @@ size_t decoder_read(struct decoder *decoder, /* ignore the SEEK command during initialization, the plugin should handle that after it has initialized successfully */ - (dc.command != DECODE_COMMAND_SEEK || - (dc.state != DECODE_STATE_START && !decoder->seeking)) && - dc.command != DECODE_COMMAND_NONE) + (dc->command != DECODE_COMMAND_SEEK || + (dc->state != DECODE_STATE_START && !decoder->seeking)) && + dc->command != DECODE_COMMAND_NONE) return 0; - nbytes = input_stream_read(is, buffer, length); + nbytes = input_stream_read(is, buffer, length, &error); + + if (G_UNLIKELY(nbytes == 0 && error != NULL)) { + g_warning("%s", error->message); + g_error_free(error); + return 0; + } + if (nbytes > 0 || input_stream_eof(is)) return nbytes; @@ -167,6 +191,15 @@ size_t decoder_read(struct decoder *decoder, } } +void +decoder_timestamp(struct decoder *decoder, double t) +{ + assert(decoder != NULL); + assert(t >= 0); + + decoder->timestamp = t; +} + /** * Sends a #tag as-is to the music pipe. Flushes the current chunk * (decoder.chunk) if there is one. @@ -181,15 +214,15 @@ do_send_tag(struct decoder *decoder, struct input_stream *is, /* there is a partial chunk - flush it, we want the tag in a new chunk */ decoder_flush_chunk(decoder); - notify_signal(&pc.notify); + player_lock_signal(); } assert(decoder->chunk == NULL); chunk = decoder_get_chunk(decoder, is); if (chunk == NULL) { - assert(dc.command != DECODE_COMMAND_NONE); - return dc.command; + assert(decoder->dc->command != DECODE_COMMAND_NONE); + return decoder->dc->command; } chunk->tag = tag_dup(tag); @@ -225,31 +258,34 @@ enum decoder_command decoder_data(struct decoder *decoder, struct input_stream *is, const void *_data, size_t length, - float data_time, uint16_t bitRate, - struct replay_gain_info *replay_gain_info) + uint16_t kbit_rate) { + struct decoder_control *dc = decoder->dc; const char *data = _data; + GError *error = NULL; + enum decoder_command cmd; + + assert(dc->state == DECODE_STATE_DECODE); + assert(dc->pipe != NULL); + assert(length % audio_format_frame_size(&dc->in_audio_format) == 0); - assert(dc.state == DECODE_STATE_DECODE); - assert(dc.pipe != NULL); - assert(length % audio_format_frame_size(&dc.in_audio_format) == 0); + decoder_lock(dc); + cmd = dc->command; + decoder_unlock(dc); - if (dc.command == DECODE_COMMAND_STOP || - dc.command == DECODE_COMMAND_SEEK || + if (cmd == DECODE_COMMAND_STOP || cmd == DECODE_COMMAND_SEEK || length == 0) - return dc.command; + return cmd; /* send stream tags */ if (update_stream_tag(decoder, is)) { - enum decoder_command cmd; - if (decoder->decoder_tag != NULL) { /* merge with tag from decoder plugin */ struct tag *tag; - tag = tag_merge(decoder->stream_tag, - decoder->decoder_tag); + tag = tag_merge(decoder->decoder_tag, + decoder->stream_tag); cmd = do_send_tag(decoder, is, tag); tag_free(tag); } else @@ -260,17 +296,18 @@ decoder_data(struct decoder *decoder, return cmd; } - if (!audio_format_equals(&dc.in_audio_format, &dc.out_audio_format)) { + if (!audio_format_equals(&dc->in_audio_format, &dc->out_audio_format)) { data = pcm_convert(&decoder->conv_state, - &dc.in_audio_format, data, length, - &dc.out_audio_format, &length); - - /* under certain circumstances, pcm_convert() may - return an empty buffer - this condition should be - investigated further, but for now, do this check as - a workaround: */ - if (data == NULL) - return DECODE_COMMAND_NONE; + &dc->in_audio_format, data, length, + &dc->out_audio_format, &length, + &error); + if (data == NULL) { + /* the PCM conversion has failed - stop + playback, since we have no better way to + bail out */ + g_warning("%s", error->message); + return DECODE_COMMAND_STOP; + } } while (length > 0) { @@ -281,16 +318,18 @@ decoder_data(struct decoder *decoder, chunk = decoder_get_chunk(decoder, is); if (chunk == NULL) { - assert(dc.command != DECODE_COMMAND_NONE); - return dc.command; + assert(dc->command != DECODE_COMMAND_NONE); + return dc->command; } - dest = music_chunk_write(chunk, &dc.out_audio_format, - data_time, bitRate, &nbytes); + dest = music_chunk_write(chunk, &dc->out_audio_format, + decoder->timestamp - + dc->song->start_ms / 1000.0, + kbit_rate, &nbytes); if (dest == NULL) { /* the chunk is full, flush it */ decoder_flush_chunk(decoder); - notify_signal(&pc.notify); + player_lock_signal(); continue; } @@ -303,26 +342,26 @@ decoder_data(struct decoder *decoder, memcpy(dest, data, nbytes); - /* apply replay gain or normalization */ - - if (replay_gain_info != NULL && - replay_gain_mode != REPLAY_GAIN_OFF) - replay_gain_apply(replay_gain_info, dest, nbytes, - &dc.out_audio_format); - else if (normalizationEnabled) - normalizeData(dest, nbytes, &dc.out_audio_format); - /* expand the music pipe chunk */ - full = music_chunk_expand(chunk, &dc.out_audio_format, nbytes); + full = music_chunk_expand(chunk, &dc->out_audio_format, nbytes); if (full) { /* the chunk is full, flush it */ decoder_flush_chunk(decoder); - notify_signal(&pc.notify); + player_lock_signal(); } data += nbytes; length -= nbytes; + + decoder->timestamp += (double)nbytes / + audio_format_time_to_size(&dc->out_audio_format); + + if (dc->song->end_ms > 0 && + decoder->timestamp >= dc->song->end_ms / 1000.0) + /* the end of this range has been reached: + stop decoding */ + return DECODE_COMMAND_STOP; } return DECODE_COMMAND_NONE; @@ -332,10 +371,11 @@ enum decoder_command decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is, const struct tag *tag) { + G_GNUC_UNUSED const struct decoder_control *dc = decoder->dc; enum decoder_command cmd; - assert(dc.state == DECODE_STATE_DECODE); - assert(dc.pipe != NULL); + assert(dc->state == DECODE_STATE_DECODE); + assert(dc->pipe != NULL); assert(tag != NULL); /* save the tag */ @@ -363,3 +403,52 @@ decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is, return cmd; } + +float +decoder_replay_gain(struct decoder *decoder, + const struct replay_gain_info *replay_gain_info) +{ + float return_db = 0; + assert(decoder != NULL); + + if (replay_gain_info != NULL) { + static unsigned serial; + if (++serial == 0) + serial = 1; + + if (REPLAY_GAIN_OFF != replay_gain_mode) { + return_db = 20.0 * log10f( + replay_gain_tuple_scale( + &replay_gain_info->tuples[replay_gain_get_real_mode()], + replay_gain_preamp, replay_gain_missing_preamp, + replay_gain_limit)); + } + + decoder->replay_gain_info = *replay_gain_info; + decoder->replay_gain_serial = serial; + + if (decoder->chunk != NULL) { + /* flush the current chunk because the new + replay gain values affect the following + samples */ + decoder_flush_chunk(decoder); + player_lock_signal(); + } + } else + decoder->replay_gain_serial = 0; + + return return_db; +} + +void +decoder_mixramp(struct decoder *decoder, float replay_gain_db, + char *mixramp_start, char *mixramp_end) +{ + assert(decoder != NULL); + struct decoder_control *dc = decoder->dc; + assert(dc != NULL); + + dc->replay_gain_db = replay_gain_db; + dc_mixramp_start(dc, mixramp_start); + dc_mixramp_end(dc, mixramp_end); +} diff --git a/src/decoder_api.h b/src/decoder_api.h index 37090d8d0..8b5f3d82b 100644 --- a/src/decoder_api.h +++ b/src/decoder_api.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,87 +17,157 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DECODER_API_H -#define MPD_DECODER_API_H - -/* +/*! \file + * \brief The MPD Decoder API + * * This is the public API which is used by decoder plugins to * communicate with the mpd core. - * */ +#ifndef MPD_DECODER_API_H +#define MPD_DECODER_API_H + +#include "check.h" #include "decoder_command.h" #include "decoder_plugin.h" #include "input_stream.h" -#include "replay_gain.h" +#include "replay_gain_info.h" #include "tag.h" #include "audio_format.h" #include "conf.h" #include <stdbool.h> - /** * Notify the player thread that it has finished initialization and * that it has read the song's meta data. + * + * @param decoder the decoder object + * @param audio_format the audio format which is going to be sent to + * decoder_data() + * @param seekable true if the song is seekable + * @param total_time the total number of seconds in this song; -1 if unknown */ -void decoder_initialized(struct decoder * decoder, - const struct audio_format *audio_format, - bool seekable, float total_time); +void +decoder_initialized(struct decoder *decoder, + const struct audio_format *audio_format, + bool seekable, float total_time); /** - * Returns the URI of the current song in UTF-8 encoding. + * Determines the pending decoder command. * - * The return value is allocated on the heap, and must be freed by the - * caller. + * @param decoder the decoder object + * @return the current command, or DECODE_COMMAND_NONE if there is no + * command pending */ -char *decoder_get_uri(struct decoder *decoder); - -enum decoder_command decoder_get_command(struct decoder * decoder); +enum decoder_command +decoder_get_command(struct decoder *decoder); /** * Called by the decoder when it has performed the requested command * (dc->command). This function resets dc->command and wakes up the * player thread. + * + * @param decoder the decoder object + */ +void +decoder_command_finished(struct decoder *decoder); + +/** + * Call this when you have received the DECODE_COMMAND_SEEK command. + * + * @param decoder the decoder object + * @return the destination position for the week */ -void decoder_command_finished(struct decoder * decoder); +double +decoder_seek_where(struct decoder *decoder); -double decoder_seek_where(struct decoder * decoder); +/** + * Call this right before decoder_command_finished() when seeking has + * failed. + * + * @param decoder the decoder object + */ +void +decoder_seek_error(struct decoder *decoder); -void decoder_seek_error(struct decoder * decoder); +/** + * Blocking read from the input stream. + * + * @param decoder the decoder object + * @param is the input stream to read from + * @param buffer the destination buffer + * @param length the maximum number of bytes to read + * @return the number of bytes read, or 0 if one of the following + * occurs: end of file; error; command (like SEEK or STOP). + */ +size_t +decoder_read(struct decoder *decoder, struct input_stream *is, + void *buffer, size_t length); /** - * Blocking read from the input stream. Returns the number of bytes - * read, or 0 if one of the following occurs: end of file; error; - * command (like SEEK or STOP). + * Sets the time stamp for the next data chunk [seconds]. The MPD + * core automatically counts it up, and a decoder plugin only needs to + * use this function if it thinks that adding to the time stamp based + * on the buffer size won't work. */ -size_t decoder_read(struct decoder *decoder, - struct input_stream *inStream, - void *buffer, size_t length); +void +decoder_timestamp(struct decoder *decoder, double t); /** * This function is called by the decoder plugin when it has * successfully decoded block of input data. * - * We send inStream for buffering the inputStream while waiting to - * send the next chunk + * @param decoder the decoder object + * @param is an input stream which is buffering while we are waiting + * for the player + * @param data the source buffer + * @param length the number of bytes in the buffer + * @return the current command, or DECODE_COMMAND_NONE if there is no + * command pending */ enum decoder_command -decoder_data(struct decoder *decoder, - struct input_stream *inStream, - const void *data, size_t datalen, - float data_time, uint16_t bitRate, - struct replay_gain_info *replay_gain_info); +decoder_data(struct decoder *decoder, struct input_stream *is, + const void *data, size_t length, + uint16_t kbit_rate); /** * This function is called by the decoder plugin when it has * successfully decoded a tag. * + * @param decoder the decoder object * @param is an input stream which is buffering while we are waiting * for the player + * @param tag the tag to send + * @return the current command, or DECODE_COMMAND_NONE if there is no + * command pending */ enum decoder_command decoder_tag(struct decoder *decoder, struct input_stream *is, const struct tag *tag); +/** + * Set replay gain values for the following chunks. + * + * @param decoder the decoder object + * @param rgi the replay_gain_info object; may be NULL to invalidate + * the previous replay gain values + * @return the replay gain adjustment used + */ +float +decoder_replay_gain(struct decoder *decoder, + const struct replay_gain_info *replay_gain_info); + +/** + * Store MixRamp tags. + * + * @param decoder the decoder object + * @param replay_gain_db the ReplayGain adjustment used for this song + * @param mixramp_start the mixramp_start tag; may be NULL to invalidate + * @param mixramp_end the mixramp_end tag; may be NULL to invalidate + */ +void +decoder_mixramp(struct decoder *decoder, float replay_gain_db, + char *mixramp_start, char *mixramp_end); + #endif diff --git a/src/decoder_buffer.c b/src/decoder_buffer.c index b6fa90004..8f8eb8545 100644 --- a/src/decoder_buffer.c +++ b/src/decoder_buffer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "decoder_buffer.h" #include "decoder_api.h" diff --git a/src/decoder_buffer.h b/src/decoder_buffer.h index 411e3bd88..b6051e122 100644 --- a/src/decoder_buffer.h +++ b/src/decoder_buffer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder_command.h b/src/decoder_command.h index faabaea09..4a2e49f3e 100644 --- a/src/decoder_command.h +++ b/src/decoder_command.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder_control.c b/src/decoder_control.c index 618c78e7e..a5e6e4ad3 100644 --- a/src/decoder_control.c +++ b/src/decoder_control.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,115 +17,180 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "decoder_control.h" +#include "player_control.h" #include "pipe.h" #include <assert.h> -struct decoder_control dc; +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "decoder_control" -void dc_init(void) +void +dc_init(struct decoder_control *dc) { - notify_init(&dc.notify); - dc.state = DECODE_STATE_STOP; - dc.command = DECODE_COMMAND_NONE; + dc->thread = NULL; + + dc->mutex = g_mutex_new(); + dc->cond = g_cond_new(); + + dc->state = DECODE_STATE_STOP; + dc->command = DECODE_COMMAND_NONE; + + dc->replay_gain_db = 0; + dc->replay_gain_prev_db = 0; + dc->mixramp_start = NULL; + dc->mixramp_end = NULL; + dc->mixramp_prev_end = NULL; } -void dc_deinit(void) +void +dc_deinit(struct decoder_control *dc) +{ + g_cond_free(dc->cond); + g_mutex_free(dc->mutex); + g_free(dc->mixramp_start); + g_free(dc->mixramp_end); + g_free(dc->mixramp_prev_end); + dc->mixramp_start = NULL; + dc->mixramp_end = NULL; + dc->mixramp_prev_end = NULL; +} + +static void +dc_command_wait_locked(struct decoder_control *dc) { - notify_deinit(&dc.notify); + while (dc->command != DECODE_COMMAND_NONE) + player_wait_decoder(dc); } void -dc_command_wait(struct notify *notify) +dc_command_wait(struct decoder_control *dc) { - while (dc.command != DECODE_COMMAND_NONE) { - notify_signal(&dc.notify); - notify_wait(notify); - } + decoder_lock(dc); + dc_command_wait_locked(dc); + decoder_unlock(dc); } static void -dc_command(struct notify *notify, enum decoder_command cmd) +dc_command_locked(struct decoder_control *dc, enum decoder_command cmd) { - dc.command = cmd; - dc_command_wait(notify); + dc->command = cmd; + decoder_signal(dc); + dc_command_wait_locked(dc); } -static void dc_command_async(enum decoder_command cmd) +static void +dc_command(struct decoder_control *dc, enum decoder_command cmd) { - dc.command = cmd; - notify_signal(&dc.notify); + decoder_lock(dc); + dc_command_locked(dc, cmd); + decoder_unlock(dc); } -void -dc_start(struct notify *notify, struct song *song, struct music_pipe *pipe) +static void +dc_command_async(struct decoder_control *dc, enum decoder_command cmd) { - assert(dc.pipe == NULL); - assert(song != NULL); - assert(pipe != NULL); - assert(music_pipe_empty(pipe)); + decoder_lock(dc); - dc.next_song = song; - dc.pipe = pipe; - dc_command(notify, DECODE_COMMAND_START); + dc->command = cmd; + decoder_signal(dc); + + decoder_unlock(dc); } void -dc_start_async(struct song *song, struct music_pipe *pipe) +dc_start(struct decoder_control *dc, struct song *song, + struct music_buffer *buffer, struct music_pipe *pipe) { - assert(dc.pipe == NULL); assert(song != NULL); + assert(buffer != NULL); assert(pipe != NULL); assert(music_pipe_empty(pipe)); - dc.next_song = song; - dc.pipe = pipe; - dc_command_async(DECODE_COMMAND_START); + dc->song = song; + dc->buffer = buffer; + dc->pipe = pipe; + dc_command(dc, DECODE_COMMAND_START); } void -dc_stop(struct notify *notify) +dc_stop(struct decoder_control *dc) { - if (dc.command != DECODE_COMMAND_NONE) + decoder_lock(dc); + + if (dc->command != DECODE_COMMAND_NONE) /* Attempt to cancel the current command. If it's too late and the decoder thread is already executing the old command, we'll call STOP again in this function (see below). */ - dc_command(notify, DECODE_COMMAND_STOP); + dc_command_locked(dc, DECODE_COMMAND_STOP); - if (dc.state != DECODE_STATE_STOP && dc.state != DECODE_STATE_ERROR) - dc_command(notify, DECODE_COMMAND_STOP); + if (dc->state != DECODE_STATE_STOP && dc->state != DECODE_STATE_ERROR) + dc_command_locked(dc, DECODE_COMMAND_STOP); + + decoder_unlock(dc); } bool -dc_seek(struct notify *notify, double where) +dc_seek(struct decoder_control *dc, double where) { - assert(dc.state != DECODE_STATE_START); + assert(dc->state != DECODE_STATE_START); assert(where >= 0.0); - if (dc.state == DECODE_STATE_STOP || - dc.state == DECODE_STATE_ERROR || !dc.seekable) + if (dc->state == DECODE_STATE_STOP || + dc->state == DECODE_STATE_ERROR || !dc->seekable) return false; - dc.seek_where = where; - dc.seek_error = false; - dc_command(notify, DECODE_COMMAND_SEEK); + dc->seek_where = where; + dc->seek_error = false; + dc_command(dc, DECODE_COMMAND_SEEK); - if (dc.seek_error) + if (dc->seek_error) return false; return true; } void -dc_quit(void) +dc_quit(struct decoder_control *dc) +{ + assert(dc->thread != NULL); + + dc->quit = true; + dc_command_async(dc, DECODE_COMMAND_STOP); + + g_thread_join(dc->thread); + dc->thread = NULL; +} + +void +dc_mixramp_start(struct decoder_control *dc, char *mixramp_start) +{ + assert(dc != NULL); + + g_free(dc->mixramp_start); + dc->mixramp_start = mixramp_start; + g_debug("mixramp_start = %s", mixramp_start ? mixramp_start : "NULL"); +} + +void +dc_mixramp_end(struct decoder_control *dc, char *mixramp_end) { - assert(dc.thread != NULL); + assert(dc != NULL); - dc.quit = true; - dc_command_async(DECODE_COMMAND_STOP); + g_free(dc->mixramp_end); + dc->mixramp_end = mixramp_end; + g_debug("mixramp_end = %s", mixramp_end ? mixramp_end : "NULL"); +} + +void +dc_mixramp_prev_end(struct decoder_control *dc, char *mixramp_prev_end) +{ + assert(dc != NULL); - g_thread_join(dc.thread); - dc.thread = NULL; + g_free(dc->mixramp_prev_end); + dc->mixramp_prev_end = mixramp_prev_end; + g_debug("mixramp_prev_end = %s", mixramp_prev_end ? mixramp_prev_end : "NULL"); } diff --git a/src/decoder_control.h b/src/decoder_control.h index febf53335..449e974b7 100644 --- a/src/decoder_control.h +++ b/src/decoder_control.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,7 +22,8 @@ #include "decoder_command.h" #include "audio_format.h" -#include "notify.h" + +#include <glib.h> #include <assert.h> @@ -45,14 +46,25 @@ struct decoder_control { thread isn't running */ GThread *thread; - struct notify notify; + /** + * This lock protects #state and #command. + */ + GMutex *mutex; + + /** + * Trigger this object after you have modified #command. This + * is also used by the decoder thread to notify the caller + * when it has finished a command. + */ + GCond *cond; + + enum decoder_state state; + enum decoder_command command; - volatile enum decoder_state state; - volatile enum decoder_command command; bool quit; bool seek_error; bool seekable; - volatile double seek_where; + double seek_where; /** the format of the song file */ struct audio_format in_audio_format; @@ -60,54 +72,145 @@ struct decoder_control { /** the format being sent to the music pipe */ struct audio_format out_audio_format; - struct song *current_song; - struct song *next_song; + /** + * The song currently being decoded. This attribute is set by + * the player thread, when it sends the #DECODE_COMMAND_START + * command. + */ + const struct song *song; + float total_time; /** the #music_chunk allocator */ struct music_buffer *buffer; - /** the destination pipe for decoded chunks */ + /** + * The destination pipe for decoded chunks. The caller thread + * owns this object, and is responsible for freeing it. + */ struct music_pipe *pipe; + + float replay_gain_db; + float replay_gain_prev_db; + char *mixramp_start; + char *mixramp_end; + char *mixramp_prev_end; }; -extern struct decoder_control dc; +void +dc_init(struct decoder_control *dc); -void dc_init(void); +void +dc_deinit(struct decoder_control *dc); -void dc_deinit(void); +/** + * Locks the #decoder_control object. + */ +static inline void +decoder_lock(struct decoder_control *dc) +{ + g_mutex_lock(dc->mutex); +} -static inline bool decoder_is_idle(void) +/** + * Unlocks the #decoder_control object. + */ +static inline void +decoder_unlock(struct decoder_control *dc) { - return (dc.state == DECODE_STATE_STOP || - dc.state == DECODE_STATE_ERROR) && - dc.command != DECODE_COMMAND_START; + g_mutex_unlock(dc->mutex); } -static inline bool decoder_is_starting(void) +/** + * Waits for a signal on the #decoder_control object. This function + * is only valid in the decoder thread. The object must be locked + * prior to calling this function. + */ +static inline void +decoder_wait(struct decoder_control *dc) { - return dc.command == DECODE_COMMAND_START || - dc.state == DECODE_STATE_START; + g_cond_wait(dc->cond, dc->mutex); } -static inline bool decoder_has_failed(void) +/** + * Signals the #decoder_control object. This function is only valid + * in the player thread. The object should be locked prior to calling + * this function. + */ +static inline void +decoder_signal(struct decoder_control *dc) { - assert(dc.command == DECODE_COMMAND_NONE); + g_cond_signal(dc->cond); +} + +static inline bool +decoder_is_idle(const struct decoder_control *dc) +{ + return dc->state == DECODE_STATE_STOP || + dc->state == DECODE_STATE_ERROR; +} - return dc.state == DECODE_STATE_ERROR; +static inline bool +decoder_is_starting(const struct decoder_control *dc) +{ + return dc->state == DECODE_STATE_START; } -static inline struct song * -decoder_current_song(void) +static inline bool +decoder_has_failed(const struct decoder_control *dc) { - switch (dc.state) { + assert(dc->command == DECODE_COMMAND_NONE); + + return dc->state == DECODE_STATE_ERROR; +} + +static inline bool +decoder_lock_is_idle(struct decoder_control *dc) +{ + bool ret; + + decoder_lock(dc); + ret = decoder_is_idle(dc); + decoder_unlock(dc); + + return ret; +} + +static inline bool +decoder_lock_is_starting(struct decoder_control *dc) +{ + bool ret; + + decoder_lock(dc); + ret = decoder_is_starting(dc); + decoder_unlock(dc); + + return ret; +} + +static inline bool +decoder_lock_has_failed(struct decoder_control *dc) +{ + bool ret; + + decoder_lock(dc); + ret = decoder_has_failed(dc); + decoder_unlock(dc); + + return ret; +} + +static inline const struct song * +decoder_current_song(const struct decoder_control *dc) +{ + switch (dc->state) { case DECODE_STATE_STOP: case DECODE_STATE_ERROR: return NULL; case DECODE_STATE_START: case DECODE_STATE_DECODE: - return dc.current_song; + return dc->song; } assert(false); @@ -115,21 +218,36 @@ decoder_current_song(void) } void -dc_command_wait(struct notify *notify); +dc_command_wait(struct decoder_control *dc); +/** + * Start the decoder. + * + * @param the decoder + * @param song the song to be decoded + * @param pipe the pipe which receives the decoded chunks (owned by + * the caller) + */ void -dc_start(struct notify *notify, struct song *song, struct music_pipe *pipe); +dc_start(struct decoder_control *dc, struct song *song, + struct music_buffer *buffer, struct music_pipe *pipe); void -dc_start_async(struct song *song, struct music_pipe *pipe); +dc_stop(struct decoder_control *dc); + +bool +dc_seek(struct decoder_control *dc, double where); void -dc_stop(struct notify *notify); +dc_quit(struct decoder_control *dc); -bool -dc_seek(struct notify *notify, double where); +void +dc_mixramp_start(struct decoder_control *dc, char *mixramp_start); + +void +dc_mixramp_end(struct decoder_control *dc, char *mixramp_end); void -dc_quit(void); +dc_mixramp_prev_end(struct decoder_control *dc, char *mixramp_prev_end); #endif diff --git a/src/decoder_internal.c b/src/decoder_internal.c index 4a56fa5f3..990d728e9 100644 --- a/src/decoder_internal.c +++ b/src/decoder_internal.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "decoder_internal.h" #include "decoder_control.h" #include "player_control.h" @@ -28,21 +29,45 @@ #include <assert.h> /** + * This is a wrapper for input_stream_buffer(). It assumes that the + * decoder is currently locked, and temporarily unlocks it while + * calling input_stream_buffer(). We shouldn't hold the lock during a + * potentially blocking operation. + */ +static bool +decoder_input_buffer(struct decoder_control *dc, struct input_stream *is) +{ + GError *error = NULL; + int ret; + + decoder_unlock(dc); + ret = input_stream_buffer(is, &error); + if (ret < 0) { + g_warning("%s", error->message); + g_error_free(error); + } + + decoder_lock(dc); + + return ret > 0; +} + +/** * All chunks are full of decoded data; wait for the player to free * one. */ static enum decoder_command -need_chunks(struct input_stream *is, bool do_wait) +need_chunks(struct decoder_control *dc, struct input_stream *is, bool do_wait) { - if (dc.command == DECODE_COMMAND_STOP || - dc.command == DECODE_COMMAND_SEEK) - return dc.command; + if (dc->command == DECODE_COMMAND_STOP || + dc->command == DECODE_COMMAND_SEEK) + return dc->command; - if ((is == NULL || input_stream_buffer(is) <= 0) && do_wait) { - notify_wait(&dc.notify); - notify_signal(&pc.notify); + if ((is == NULL || !decoder_input_buffer(dc, is)) && do_wait) { + decoder_wait(dc); + player_signal(); - return dc.command; + return dc->command; } return DECODE_COMMAND_NONE; @@ -51,6 +76,7 @@ need_chunks(struct input_stream *is, bool do_wait) struct music_chunk * decoder_get_chunk(struct decoder *decoder, struct input_stream *is) { + struct decoder_control *dc = decoder->dc; enum decoder_command cmd; assert(decoder != NULL); @@ -59,11 +85,20 @@ decoder_get_chunk(struct decoder *decoder, struct input_stream *is) return decoder->chunk; do { - decoder->chunk = music_buffer_allocate(dc.buffer); - if (decoder->chunk != NULL) + decoder->chunk = music_buffer_allocate(dc->buffer); + if (decoder->chunk != NULL) { + decoder->chunk->replay_gain_serial = + decoder->replay_gain_serial; + if (decoder->replay_gain_serial != 0) + decoder->chunk->replay_gain_info = + decoder->replay_gain_info; + return decoder->chunk; + } - cmd = need_chunks(is, true); + decoder_lock(dc); + cmd = need_chunks(dc, is, true); + decoder_unlock(dc); } while (cmd == DECODE_COMMAND_NONE); return NULL; @@ -72,13 +107,15 @@ decoder_get_chunk(struct decoder *decoder, struct input_stream *is) void decoder_flush_chunk(struct decoder *decoder) { + struct decoder_control *dc = decoder->dc; + assert(decoder != NULL); assert(decoder->chunk != NULL); if (music_chunk_is_empty(decoder->chunk)) - music_buffer_return(dc.buffer, decoder->chunk); + music_buffer_return(dc->buffer, decoder->chunk); else - music_pipe_push(dc.pipe, decoder->chunk); + music_pipe_push(dc->pipe, decoder->chunk); decoder->chunk = NULL; } diff --git a/src/decoder_internal.h b/src/decoder_internal.h index cf54dbf6d..9e7e2037a 100644 --- a/src/decoder_internal.h +++ b/src/decoder_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,12 +22,20 @@ #include "decoder_command.h" #include "pcm_convert.h" +#include "replay_gain_info.h" struct input_stream; struct decoder { + struct decoder_control *dc; + struct pcm_convert_state conv_state; + /** + * The time stamp of the next data chunk, in seconds. + */ + double timestamp; + bool seeking; /** @@ -45,6 +53,14 @@ struct decoder { /** the chunk currently being written to */ struct music_chunk *chunk; + + struct replay_gain_info replay_gain_info; + + /** + * A positive serial number for checking if replay gain info + * has changed since the last check. + */ + unsigned replay_gain_serial; }; /** diff --git a/src/decoder_list.c b/src/decoder_list.c index a42585e34..d76050023 100644 --- a/src/decoder_list.c +++ b/src/decoder_list.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,20 +17,23 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "decoder_list.h" #include "decoder_plugin.h" #include "utils.h" -#include "config.h" #include "conf.h" +#include "mpd_error.h" #include <glib.h> #include <string.h> extern const struct decoder_plugin mad_decoder_plugin; +extern const struct decoder_plugin mpg123_decoder_plugin; extern const struct decoder_plugin vorbis_decoder_plugin; extern const struct decoder_plugin flac_decoder_plugin; extern const struct decoder_plugin oggflac_decoder_plugin; +extern const struct decoder_plugin sndfile_decoder_plugin; extern const struct decoder_plugin audiofile_decoder_plugin; extern const struct decoder_plugin mp4ff_decoder_plugin; extern const struct decoder_plugin faad_decoder_plugin; @@ -42,11 +45,15 @@ extern const struct decoder_plugin sidplay_decoder_plugin; extern const struct decoder_plugin wildmidi_decoder_plugin; extern const struct decoder_plugin fluidsynth_decoder_plugin; extern const struct decoder_plugin ffmpeg_decoder_plugin; +extern const struct decoder_plugin gme_decoder_plugin; -static const struct decoder_plugin *const decoder_plugins[] = { +const struct decoder_plugin *const decoder_plugins[] = { #ifdef HAVE_MAD &mad_decoder_plugin, #endif +#ifdef HAVE_MPG123 + &mpg123_decoder_plugin, +#endif #ifdef ENABLE_VORBIS_DECODER &vorbis_decoder_plugin, #endif @@ -56,6 +63,9 @@ static const struct decoder_plugin *const decoder_plugins[] = { #ifdef HAVE_FLAC &flac_decoder_plugin, #endif +#ifdef ENABLE_SNDFILE + &sndfile_decoder_plugin, +#endif #ifdef HAVE_AUDIOFILE &audiofile_decoder_plugin, #endif @@ -89,32 +99,51 @@ static const struct decoder_plugin *const decoder_plugins[] = { #ifdef HAVE_FFMPEG &ffmpeg_decoder_plugin, #endif +#ifdef HAVE_GME + &gme_decoder_plugin, +#endif + NULL }; enum { - num_decoder_plugins = G_N_ELEMENTS(decoder_plugins), + num_decoder_plugins = G_N_ELEMENTS(decoder_plugins) - 1, }; /** which plugins have been initialized successfully? */ -static bool decoder_plugins_enabled[num_decoder_plugins]; +bool decoder_plugins_enabled[num_decoder_plugins]; -const struct decoder_plugin * -decoder_plugin_from_suffix(const char *suffix, unsigned int next) +static unsigned +decoder_plugin_index(const struct decoder_plugin *plugin) { - static unsigned i = num_decoder_plugins; + unsigned i = 0; + + while (decoder_plugins[i] != plugin) + ++i; + return i; +} + +static unsigned +decoder_plugin_next_index(const struct decoder_plugin *plugin) +{ + return plugin == 0 + ? 0 /* start with first plugin */ + : decoder_plugin_index(plugin) + 1; +} + +const struct decoder_plugin * +decoder_plugin_from_suffix(const char *suffix, + const struct decoder_plugin *plugin) +{ if (suffix == NULL) return NULL; - if (!next) - i = 0; - for (; i < num_decoder_plugins; ++i) { - const struct decoder_plugin *plugin = decoder_plugins[i]; + for (unsigned i = decoder_plugin_next_index(plugin); + decoder_plugins[i] != NULL; ++i) { + plugin = decoder_plugins[i]; if (decoder_plugins_enabled[i] && - stringFoundInStringArray(plugin->suffixes, suffix)) { - ++i; + decoder_plugin_supports_suffix(plugin, suffix)) return plugin; - } } return NULL; @@ -130,10 +159,10 @@ decoder_plugin_from_mime_type(const char *mimeType, unsigned int next) if (!next) i = 0; - for (; i < num_decoder_plugins; ++i) { + for (; decoder_plugins[i] != NULL; ++i) { const struct decoder_plugin *plugin = decoder_plugins[i]; if (decoder_plugins_enabled[i] && - stringFoundInStringArray(plugin->mime_types, mimeType)) { + decoder_plugin_supports_mime_type(plugin, mimeType)) { ++i; return plugin; } @@ -145,7 +174,7 @@ decoder_plugin_from_mime_type(const char *mimeType, unsigned int next) const struct decoder_plugin * decoder_plugin_from_name(const char *name) { - for (unsigned i = 0; i < num_decoder_plugins; ++i) { + for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) { const struct decoder_plugin *plugin = decoder_plugins[i]; if (decoder_plugins_enabled[i] && strcmp(plugin->name, name) == 0) @@ -155,27 +184,6 @@ decoder_plugin_from_name(const char *name) return NULL; } -void decoder_plugin_print_all_decoders(FILE * fp) -{ - for (unsigned i = 0; i < num_decoder_plugins; ++i) { - const struct decoder_plugin *plugin = decoder_plugins[i]; - const char *const*suffixes; - - if (!decoder_plugins_enabled[i]) - continue; - - fprintf(fp, "[%s]", plugin->name); - - for (suffixes = plugin->suffixes; - suffixes != NULL && *suffixes != NULL; - ++suffixes) { - fprintf(fp, " %s", *suffixes); - } - - fprintf(fp, "\n"); - } -} - /** * Find the "decoder" configuration block for the specified plugin. * @@ -191,8 +199,8 @@ decoder_plugin_config(const char *plugin_name) const char *name = config_get_block_string(param, "plugin", NULL); if (name == NULL) - g_error("decoder configuration without 'plugin' name in line %d", - param->line); + MPD_ERROR("decoder configuration without 'plugin' name in line %d", + param->line); if (strcmp(name, plugin_name) == 0) return param; @@ -203,7 +211,7 @@ decoder_plugin_config(const char *plugin_name) void decoder_plugin_init_all(void) { - for (unsigned i = 0; i < num_decoder_plugins; ++i) { + for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) { const struct decoder_plugin *plugin = decoder_plugins[i]; const struct config_param *param = decoder_plugin_config(plugin->name); @@ -219,7 +227,7 @@ void decoder_plugin_init_all(void) void decoder_plugin_deinit_all(void) { - for (unsigned i = 0; i < num_decoder_plugins; ++i) { + for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) { const struct decoder_plugin *plugin = decoder_plugins[i]; if (decoder_plugins_enabled[i]) diff --git a/src/decoder_list.h b/src/decoder_list.h index 23788189c..7041db0c9 100644 --- a/src/decoder_list.h +++ b/src/decoder_list.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,14 +20,25 @@ #ifndef MPD_DECODER_LIST_H #define MPD_DECODER_LIST_H -#include <stdio.h> +#include <stdbool.h> struct decoder_plugin; +extern const struct decoder_plugin *const decoder_plugins[]; +extern bool decoder_plugins_enabled[]; + /* interface for using plugins */ +/** + * Find the next enabled decoder plugin which supports the specified suffix. + * + * @param suffix the file name suffix + * @param plugin the previous plugin, or NULL to find the first plugin + * @return a plugin, or NULL if none matches + */ const struct decoder_plugin * -decoder_plugin_from_suffix(const char *suffix, unsigned int next); +decoder_plugin_from_suffix(const char *suffix, + const struct decoder_plugin *plugin); const struct decoder_plugin * decoder_plugin_from_mime_type(const char *mimeType, unsigned int next); @@ -35,8 +46,6 @@ decoder_plugin_from_mime_type(const char *mimeType, unsigned int next); const struct decoder_plugin * decoder_plugin_from_name(const char *name); -void decoder_plugin_print_all_decoders(FILE * fp); - /* this is where we "load" all the "plugins" ;-) */ void decoder_plugin_init_all(void); diff --git a/src/decoder_plugin.c b/src/decoder_plugin.c new file mode 100644 index 000000000..062dad364 --- /dev/null +++ b/src/decoder_plugin.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "decoder_plugin.h" +#include "utils.h" + +#include <assert.h> + +bool +decoder_plugin_supports_suffix(const struct decoder_plugin *plugin, + const char *suffix) +{ + assert(plugin != NULL); + assert(suffix != NULL); + + return plugin->suffixes != NULL && + string_array_contains(plugin->suffixes, suffix); + +} + +bool +decoder_plugin_supports_mime_type(const struct decoder_plugin *plugin, + const char *mime_type) +{ + assert(plugin != NULL); + assert(mime_type != NULL); + + return plugin->mime_types != NULL && + string_array_contains(plugin->mime_types, mime_type); +} diff --git a/src/decoder_plugin.h b/src/decoder_plugin.h index 66501a0a1..d8371ddb8 100644 --- a/src/decoder_plugin.h +++ b/src/decoder_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -77,6 +77,13 @@ struct decoder_plugin { struct tag *(*tag_dup)(const char *path_fs); /** + * Read the tags of a stream. + * + * @return NULL if the operation has failed + */ + struct tag *(*stream_tag)(struct input_stream *is); + + /** * @brief Return a "virtual" filename for subtracks in * container formats like flac * @param const char* pathname full pathname for the file on fs @@ -147,7 +154,21 @@ static inline struct tag * decoder_plugin_tag_dup(const struct decoder_plugin *plugin, const char *path_fs) { - return plugin->tag_dup(path_fs); + return plugin->tag_dup != NULL + ? plugin->tag_dup(path_fs) + : NULL; +} + +/** + * Read the tag of a stream. + */ +static inline struct tag * +decoder_plugin_stream_tag(const struct decoder_plugin *plugin, + struct input_stream *is) +{ + return plugin->stream_tag != NULL + ? plugin->stream_tag(is) + : NULL; } /** @@ -161,4 +182,18 @@ decoder_plugin_container_scan( const struct decoder_plugin *plugin, return plugin->container_scan(pathname, tnum); } +/** + * Does the plugin announce the specified file name suffix? + */ +bool +decoder_plugin_supports_suffix(const struct decoder_plugin *plugin, + const char *suffix); + +/** + * Does the plugin announce the specified MIME type? + */ +bool +decoder_plugin_supports_mime_type(const struct decoder_plugin *plugin, + const char *mime_type); + #endif diff --git a/src/decoder_print.c b/src/decoder_print.c new file mode 100644 index 000000000..a1c2da2e5 --- /dev/null +++ b/src/decoder_print.c @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "decoder_print.h" +#include "decoder_list.h" +#include "decoder_plugin.h" +#include "client.h" + +#include <assert.h> + +static void +decoder_plugin_print(struct client *client, + const struct decoder_plugin *plugin) +{ + const char *const*p; + + assert(plugin != NULL); + assert(plugin->name != NULL); + + client_printf(client, "plugin: %s\n", plugin->name); + + if (plugin->suffixes != NULL) + for (p = plugin->suffixes; *p != NULL; ++p) + client_printf(client, "suffix: %s\n", *p); + + if (plugin->mime_types != NULL) + for (p = plugin->mime_types; *p != NULL; ++p) + client_printf(client, "mime_type: %s\n", *p); +} + +void +decoder_list_print(struct client *client) +{ + for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) + if (decoder_plugins_enabled[i]) + decoder_plugin_print(client, decoder_plugins[i]); +} diff --git a/src/decoder_print.h b/src/decoder_print.h new file mode 100644 index 000000000..520438871 --- /dev/null +++ b/src/decoder_print.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_PRINT_H +#define MPD_DECODER_PRINT_H + +struct client; + +void +decoder_list_print(struct client *client); + +#endif diff --git a/src/decoder_thread.c b/src/decoder_thread.c index cbb670616..1a91b6566 100644 --- a/src/decoder_thread.c +++ b/src/decoder_thread.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,11 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "decoder_thread.h" #include "decoder_control.h" #include "decoder_internal.h" #include "decoder_list.h" #include "decoder_plugin.h" +#include "decoder_api.h" #include "input_stream.h" #include "player_control.h" #include "pipe.h" @@ -30,11 +32,73 @@ #include "mapper.h" #include "path.h" #include "uri.h" +#include "mpd_error.h" #include <glib.h> #include <unistd.h> +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "decoder_thread" + +static enum decoder_command +decoder_lock_get_command(struct decoder_control *dc) +{ + enum decoder_command command; + + decoder_lock(dc); + command = dc->command; + decoder_unlock(dc); + + return command; +} + +/** + * Opens the input stream with input_stream_open(), and waits until + * the stream gets ready. If a decoder STOP command is received + * during that, it cancels the operation (but does not close the + * stream). + * + * Unlock the decoder before calling this function. + * + * @return an input_stream on success or if #DECODE_COMMAND_STOP is + * received, NULL on error + */ +static struct input_stream * +decoder_input_stream_open(struct decoder_control *dc, const char *uri) +{ + GError *error = NULL; + struct input_stream *is; + + is = input_stream_open(uri, &error); + if (is == NULL) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + + return NULL; + } + + /* wait for the input stream to become ready; its metadata + will be available then */ + + while (!is->ready && + decoder_lock_get_command(dc) != DECODE_COMMAND_STOP) { + int ret; + + ret = input_stream_buffer(is, &error); + if (ret < 0) { + input_stream_close(is); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + } + + return is; +} + static bool decoder_stream_decode(const struct decoder_plugin *plugin, struct decoder *decoder, @@ -47,17 +111,24 @@ decoder_stream_decode(const struct decoder_plugin *plugin, assert(decoder->decoder_tag == NULL); assert(input_stream != NULL); assert(input_stream->ready); - assert(dc.state == DECODE_STATE_START); + assert(decoder->dc->state == DECODE_STATE_START); + + if (decoder->dc->command == DECODE_COMMAND_STOP) + return true; + + decoder_unlock(decoder->dc); /* rewind the stream, so each plugin gets a fresh start */ - input_stream_seek(input_stream, 0, SEEK_SET); + input_stream_seek(input_stream, 0, SEEK_SET, NULL); decoder_plugin_stream_decode(plugin, decoder, input_stream); - assert(dc.state == DECODE_STATE_START || - dc.state == DECODE_STATE_DECODE); + decoder_lock(decoder->dc); - return dc.state != DECODE_STATE_START; + assert(decoder->dc->state == DECODE_STATE_START || + decoder->dc->state == DECODE_STATE_DECODE); + + return decoder->dc->state != DECODE_STATE_START; } static bool @@ -70,155 +141,250 @@ decoder_file_decode(const struct decoder_plugin *plugin, assert(decoder->stream_tag == NULL); assert(decoder->decoder_tag == NULL); assert(path != NULL); - assert(path[0] == '/'); - assert(dc.state == DECODE_STATE_START); + assert(g_path_is_absolute(path)); + assert(decoder->dc->state == DECODE_STATE_START); + + if (decoder->dc->command == DECODE_COMMAND_STOP) + return true; + + decoder_unlock(decoder->dc); decoder_plugin_file_decode(plugin, decoder, path); - assert(dc.state == DECODE_STATE_START || - dc.state == DECODE_STATE_DECODE); + decoder_lock(decoder->dc); + + assert(decoder->dc->state == DECODE_STATE_START || + decoder->dc->state == DECODE_STATE_DECODE); - return dc.state != DECODE_STATE_START; + return decoder->dc->state != DECODE_STATE_START; } -static void decoder_run_song(const struct song *song, const char *uri) +/** + * Hack to allow tracking const decoder plugins in a GSList. + */ +static inline gpointer +deconst_plugin(const struct decoder_plugin *plugin) { - struct decoder decoder; - int ret; - bool close_instream = true; - struct input_stream input_stream; + union { + const struct decoder_plugin *in; + gpointer out; + } u = { .in = plugin }; + + return u.out; +} + +/** + * Try decoding a stream, using plugins matching the stream's MIME type. + * + * @param tried_r a list of plugins which were tried + */ +static bool +decoder_run_stream_mime_type(struct decoder *decoder, struct input_stream *is, + GSList **tried_r) +{ + assert(tried_r != NULL); + const struct decoder_plugin *plugin; + unsigned int next = 0; - close_instream = input_stream_open(&input_stream, uri); - if (!close_instream && !song_is_file(song)) { - dc.state = DECODE_STATE_ERROR; - return; + if (is->mime == NULL) + return false; + + while ((plugin = decoder_plugin_from_mime_type(is->mime, next++))) { + if (plugin->stream_decode == NULL) + continue; + + if (g_slist_find(*tried_r, plugin) != NULL) + /* don't try a plugin twice */ + continue; + + if (decoder_stream_decode(plugin, decoder, is)) + return true; + + *tried_r = g_slist_prepend(*tried_r, deconst_plugin(plugin)); } - decoder.seeking = false; - decoder.song_tag = song->tag != NULL && song_is_file(song) - ? tag_dup(song->tag) : NULL; - decoder.stream_tag = NULL; - decoder.decoder_tag = NULL; - decoder.chunk = NULL; + return false; +} - dc.state = DECODE_STATE_START; - dc.command = DECODE_COMMAND_NONE; - notify_signal(&pc.notify); +/** + * Try decoding a stream, using plugins matching the stream's URI + * suffix. + * + * @param tried_r a list of plugins which were tried + */ +static bool +decoder_run_stream_suffix(struct decoder *decoder, struct input_stream *is, + const char *uri, GSList **tried_r) +{ + assert(tried_r != NULL); - /* wait for the input stream to become ready; its metadata - will be available then */ + const char *suffix = uri_get_suffix(uri); + const struct decoder_plugin *plugin = NULL; - while (close_instream && !input_stream.ready) { - if (dc.command == DECODE_COMMAND_STOP) { - input_stream_close(&input_stream); - dc.state = DECODE_STATE_STOP; - return; - } + if (suffix == NULL) + return false; - ret = input_stream_buffer(&input_stream); - if (ret < 0) { - input_stream_close(&input_stream); - dc.state = DECODE_STATE_ERROR; - return; - } + while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) { + if (plugin->stream_decode == NULL) + continue; + + if (g_slist_find(*tried_r, plugin) != NULL) + /* don't try a plugin twice */ + continue; + + if (decoder_stream_decode(plugin, decoder, is)) + return true; + + *tried_r = g_slist_prepend(*tried_r, deconst_plugin(plugin)); } - if (dc.command == DECODE_COMMAND_STOP) { - if (close_instream) - input_stream_close(&input_stream); - dc.state = DECODE_STATE_STOP; - return; + return false; +} + +/** + * Try decoding a stream, using the fallback plugin. + */ +static bool +decoder_run_stream_fallback(struct decoder *decoder, struct input_stream *is) +{ + const struct decoder_plugin *plugin; + + plugin = decoder_plugin_from_name("mad"); + return plugin != NULL && plugin->stream_decode != NULL && + decoder_stream_decode(plugin, decoder, is); +} + +/** + * Try decoding a stream. + */ +static bool +decoder_run_stream(struct decoder *decoder, const char *uri) +{ + struct decoder_control *dc = decoder->dc; + struct input_stream *input_stream; + bool success; + + decoder_unlock(dc); + + input_stream = decoder_input_stream_open(dc, uri); + if (input_stream == NULL) { + decoder_lock(dc); + return false; } - pcm_convert_init(&decoder.conv_state); + decoder_lock(dc); - ret = false; - if (!song_is_file(song)) { - unsigned int next = 0; + GSList *tried = NULL; + success = dc->command == DECODE_COMMAND_STOP || /* first we try mime types: */ - while ((plugin = decoder_plugin_from_mime_type(input_stream.mime, next++))) { - if (plugin->stream_decode == NULL) + decoder_run_stream_mime_type(decoder, input_stream, &tried) || + /* if that fails, try suffix matching the URL: */ + decoder_run_stream_suffix(decoder, input_stream, uri, + &tried) || + /* fallback to mp3: this is needed for bastard streams + that don't have a suffix or set the mimeType */ + (tried == NULL && + decoder_run_stream_fallback(decoder, input_stream)); + + g_slist_free(tried); + + decoder_unlock(dc); + input_stream_close(input_stream); + decoder_lock(dc); + + return success; +} + +/** + * Try decoding a file. + */ +static bool +decoder_run_file(struct decoder *decoder, const char *path_fs) +{ + struct decoder_control *dc = decoder->dc; + const char *suffix = uri_get_suffix(path_fs); + const struct decoder_plugin *plugin = NULL; + + if (suffix == NULL) + return false; + + decoder_unlock(dc); + + while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) { + if (plugin->file_decode != NULL) { + decoder_lock(dc); + + if (decoder_file_decode(plugin, decoder, path_fs)) + return true; + + decoder_unlock(dc); + } else if (plugin->stream_decode != NULL) { + struct input_stream *input_stream; + bool success; + + input_stream = decoder_input_stream_open(dc, path_fs); + if (input_stream == NULL) continue; - ret = decoder_stream_decode(plugin, &decoder, - &input_stream); - if (ret) - break; - plugin = NULL; - } + decoder_lock(dc); - /* if that fails, try suffix matching the URL: */ - if (plugin == NULL) { - const char *s = uri_get_suffix(uri); - next = 0; - while ((plugin = decoder_plugin_from_suffix(s, next++))) { - if (plugin->stream_decode == NULL) - continue; - ret = decoder_stream_decode(plugin, &decoder, - &input_stream); - if (ret) - break; - - assert(dc.state == DECODE_STATE_START); - plugin = NULL; - } - } - /* fallback to mp3: */ - /* this is needed for bastard streams that don't have a suffix - or set the mimeType */ - if (plugin == NULL) { - /* we already know our mp3Plugin supports streams, no - * need to check for stream{Types,DecodeFunc} */ - if ((plugin = decoder_plugin_from_name("mad"))) { - ret = decoder_stream_decode(plugin, &decoder, - &input_stream); - } - } - } else { - unsigned int next = 0; - const char *s = uri_get_suffix(uri); - while ((plugin = decoder_plugin_from_suffix(s, next++))) { - if (plugin->file_decode != NULL) { - if (close_instream) { - input_stream_close(&input_stream); - close_instream = false; - } - - ret = decoder_file_decode(plugin, - &decoder, uri); - if (ret) - break; - } else if (plugin->stream_decode != NULL) { - if (!close_instream) { - /* the input_stream object has - been closed before - decoder_file_decode() - - reopen it */ - if (input_stream_open(&input_stream, uri)) - close_instream = true; - else - continue; - } - - ret = decoder_stream_decode(plugin, &decoder, - &input_stream); - if (ret) - break; + success = decoder_stream_decode(plugin, decoder, + input_stream); + + decoder_unlock(dc); + + input_stream_close(input_stream); + + if (success) { + decoder_lock(dc); + return true; } } } + decoder_lock(dc); + return false; +} + +static void +decoder_run_song(struct decoder_control *dc, + const struct song *song, const char *uri) +{ + struct decoder decoder = { + .dc = dc, + }; + int ret; + + decoder.timestamp = 0.0; + decoder.seeking = false; + decoder.song_tag = song->tag != NULL && song_is_file(song) + ? tag_dup(song->tag) : NULL; + decoder.stream_tag = NULL; + decoder.decoder_tag = NULL; + decoder.chunk = NULL; + + dc->state = DECODE_STATE_START; + dc->command = DECODE_COMMAND_NONE; + + player_signal(); + + pcm_convert_init(&decoder.conv_state); + + ret = song_is_file(song) + ? decoder_run_file(&decoder, uri) + : decoder_run_stream(&decoder, uri); + + decoder_unlock(dc); + pcm_convert_deinit(&decoder.conv_state); /* flush the last chunk */ + if (decoder.chunk != NULL) decoder_flush_chunk(&decoder); - if (close_instream) - input_stream_close(&input_stream); - if (decoder.song_tag != NULL) tag_free(decoder.song_tag); @@ -228,66 +394,91 @@ static void decoder_run_song(const struct song *song, const char *uri) if (decoder.decoder_tag != NULL) tag_free(decoder.decoder_tag); - dc.state = ret ? DECODE_STATE_STOP : DECODE_STATE_ERROR; + decoder_lock(dc); + + dc->state = ret ? DECODE_STATE_STOP : DECODE_STATE_ERROR; } -static void decoder_run(void) +static void +decoder_run(struct decoder_control *dc) { - struct song *song = dc.next_song; + const struct song *song = dc->song; char *uri; + assert(song != NULL); + if (song_is_file(song)) uri = map_song_fs(song); else uri = song_get_uri(song); if (uri == NULL) { - dc.state = DECODE_STATE_ERROR; + dc->state = DECODE_STATE_ERROR; return; } - dc.current_song = dc.next_song; /* NEED LOCK */ - decoder_run_song(song, uri); + decoder_run_song(dc, song, uri); g_free(uri); } -static gpointer decoder_task(G_GNUC_UNUSED gpointer arg) +static gpointer +decoder_task(gpointer arg) { + struct decoder_control *dc = arg; + + decoder_lock(dc); + do { - assert(dc.state == DECODE_STATE_STOP || - dc.state == DECODE_STATE_ERROR); + assert(dc->state == DECODE_STATE_STOP || + dc->state == DECODE_STATE_ERROR); - switch (dc.command) { + switch (dc->command) { case DECODE_COMMAND_START: + g_debug("clearing mixramp tags"); + dc_mixramp_start(dc, NULL); + dc_mixramp_prev_end(dc, dc->mixramp_end); + dc->mixramp_end = NULL; /* Don't free, it's copied above. */ + dc->replay_gain_prev_db = dc->replay_gain_db; + dc->replay_gain_db = 0; + + /* fall through */ + case DECODE_COMMAND_SEEK: - decoder_run(); + decoder_run(dc); - dc.command = DECODE_COMMAND_NONE; - notify_signal(&pc.notify); + dc->command = DECODE_COMMAND_NONE; + + player_signal(); break; case DECODE_COMMAND_STOP: - dc.command = DECODE_COMMAND_NONE; - notify_signal(&pc.notify); + dc->command = DECODE_COMMAND_NONE; + + player_signal(); break; case DECODE_COMMAND_NONE: - notify_wait(&dc.notify); + decoder_wait(dc); break; } - } while (dc.command != DECODE_COMMAND_NONE || !dc.quit); + } while (dc->command != DECODE_COMMAND_NONE || !dc->quit); + + decoder_unlock(dc); return NULL; } -void decoder_thread_start(void) +void +decoder_thread_start(struct decoder_control *dc) { GError *e = NULL; - assert(dc.thread == NULL); + assert(dc->thread == NULL); + + dc->quit = false; - dc.thread = g_thread_create(decoder_task, NULL, true, &e); - if (dc.thread == NULL) - g_error("Failed to spawn decoder task: %s", e->message); + dc->thread = g_thread_create(decoder_task, dc, true, &e); + if (dc->thread == NULL) + MPD_ERROR("Failed to spawn decoder task: %s", e->message); } diff --git a/src/decoder_thread.h b/src/decoder_thread.h index 50ed7116e..28042d7f8 100644 --- a/src/decoder_thread.h +++ b/src/decoder_thread.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,9 @@ #ifndef MPD_DECODER_THREAD_H #define MPD_DECODER_THREAD_H -void decoder_thread_start(void); +struct decoder_control; + +void +decoder_thread_start(struct decoder_control *dc); #endif diff --git a/src/directory.c b/src/directory.c index ef8c038a3..fa15d41b1 100644 --- a/src/directory.c +++ b/src/directory.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "directory.h" #include "song.h" #include "path.h" @@ -41,12 +42,16 @@ directory_new(const char *path, struct directory *parent) directory->parent = parent; memcpy(directory->path, path, pathlen + 1); + playlist_vector_init(&directory->playlists); + return directory; } void directory_free(struct directory *directory) { + playlist_vector_deinit(&directory->playlists); + for (unsigned i = 0; i < directory->songs.nr; ++i) song_free(directory->songs.base[i]); diff --git a/src/directory.h b/src/directory.h index 8207bd3a2..dde741a71 100644 --- a/src/directory.h +++ b/src/directory.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,20 +20,25 @@ #ifndef MPD_DIRECTORY_H #define MPD_DIRECTORY_H +#include "check.h" #include "dirvec.h" #include "songvec.h" +#include "playlist_vector.h" #include <stdbool.h> #include <sys/types.h> #define DIRECTORY_DIR "directory: " -#define DEVICE_INARCHIVE (unsigned)(-1) -#define DEVICE_CONTAINER (unsigned)(-2) +#define DEVICE_INARCHIVE (dev_t)(-1) +#define DEVICE_CONTAINER (dev_t)(-2) struct directory { struct dirvec children; struct songvec songs; + + struct playlist_vector playlists; + struct directory *parent; time_t mtime; ino_t inode; diff --git a/src/directory_print.c b/src/directory_print.c index 1c9f23d69..74ff26cb3 100644 --- a/src/directory_print.c +++ b/src/directory_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,19 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "directory_print.h" #include "directory.h" #include "client.h" #include "song_print.h" +#include "mapper.h" +#include "decoder_list.h" +#include "path.h" +#include "uri.h" +#include "input_stream.h" + +#include <sys/types.h> +#include <dirent.h> static void dirvec_print(struct client *client, const struct dirvec *dv) @@ -32,9 +41,34 @@ dirvec_print(struct client *client, const struct dirvec *dv) directory_get_path(dv->base[i])); } +static void +print_playlist_in_directory(struct client *client, + const struct directory *directory, + const char *name_utf8) +{ + if (directory_is_root(directory)) + client_printf(client, "playlist: %s\n", name_utf8); + else + client_printf(client, "playlist: %s/%s\n", + directory_get_path(directory), name_utf8); +} + +/** + * Print a list of playlists in the specified directory. + */ +static void +directory_print_playlists(struct client *client, + const struct directory *directory) +{ + for (const struct playlist_metadata *pm = directory->playlists.head; + pm != NULL; pm = pm->next) + print_playlist_in_directory(client, directory, pm->name); +} + void directory_print(struct client *client, const struct directory *directory) { dirvec_print(client, &directory->children); songvec_print(client, &directory->songs); + directory_print_playlists(client, directory); } diff --git a/src/directory_print.h b/src/directory_print.h index 7c0110502..0933f5a97 100644 --- a/src/directory_print.h +++ b/src/directory_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/directory_save.c b/src/directory_save.c index 132508447..55896c289 100644 --- a/src/directory_save.c +++ b/src/directory_save.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,11 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "directory_save.h" #include "directory.h" #include "song.h" -#include "path.h" +#include "text_file.h" #include "song_save.h" +#include "playlist_database.h" #include <assert.h> #include <string.h> @@ -39,109 +41,152 @@ directory_quark(void) return g_quark_from_static_string("directory"); } -/* TODO error checking */ -int +void directory_save(FILE *fp, struct directory *directory) { struct dirvec *children = &directory->children; size_t i; - int retv; if (!directory_is_root(directory)) { fprintf(fp, DIRECTORY_MTIME "%lu\n", (unsigned long)directory->mtime); - retv = fprintf(fp, "%s%s\n", DIRECTORY_BEGIN, - directory_get_path(directory)); - if (retv < 0) - return -1; + fprintf(fp, "%s%s\n", DIRECTORY_BEGIN, + directory_get_path(directory)); } for (i = 0; i < children->nr; ++i) { struct directory *cur = children->base[i]; char *base = g_path_get_basename(cur->path); - retv = fprintf(fp, DIRECTORY_DIR "%s\n", base); + fprintf(fp, DIRECTORY_DIR "%s\n", base); g_free(base); - if (retv < 0) - return -1; - if (directory_save(fp, cur) < 0) - return -1; + + directory_save(fp, cur); + + if (ferror(fp)) + return; } songvec_save(fp, &directory->songs); - if (!directory_is_root(directory) && - fprintf(fp, DIRECTORY_END "%s\n", - directory_get_path(directory)) < 0) - return -1; - return 0; + playlist_vector_save(fp, &directory->playlists); + + if (!directory_is_root(directory)) + fprintf(fp, DIRECTORY_END "%s\n", + directory_get_path(directory)); } -bool -directory_load(FILE *fp, struct directory *directory, GError **error) +static struct directory * +directory_load_subdir(FILE *fp, struct directory *parent, const char *name, + GString *buffer, GError **error_r) { - char buffer[MPD_PATH_MAX * 2]; - char key[MPD_PATH_MAX * 2]; - char *name; + struct directory *directory; + const char *line; bool success; - while (fgets(buffer, sizeof(buffer), fp) - && !g_str_has_prefix(buffer, DIRECTORY_END)) { - if (g_str_has_prefix(buffer, DIRECTORY_DIR)) { - struct directory *subdir; + if (directory_get_child(parent, name) != NULL) { + g_set_error(error_r, directory_quark(), 0, + "Duplicate subdirectory '%s'", name); + return NULL; + } - g_strchomp(buffer); - strcpy(key, &(buffer[strlen(DIRECTORY_DIR)])); - if (!fgets(buffer, sizeof(buffer), fp)) { - g_set_error(error, directory_quark(), 0, - "Unexpected end of file"); - return false; - } + if (directory_is_root(parent)) { + directory = directory_new(name, parent); + } else { + char *path = g_strconcat(directory_get_path(parent), "/", + name, NULL); + directory = directory_new(path, parent); + g_free(path); + } - if (g_str_has_prefix(buffer, DIRECTORY_MTIME)) { - directory->mtime = - g_ascii_strtoull(buffer + sizeof(DIRECTORY_MTIME) - 1, - NULL, 10); + line = read_text_line(fp, buffer); + if (line == NULL) { + g_set_error(error_r, directory_quark(), 0, + "Unexpected end of file"); + directory_free(directory); + return NULL; + } - if (!fgets(buffer, sizeof(buffer), fp)) { - g_set_error(error, directory_quark(), 0, - "Unexpected end of file"); - return false; - } - } + if (g_str_has_prefix(line, DIRECTORY_MTIME)) { + directory->mtime = + g_ascii_strtoull(line + sizeof(DIRECTORY_MTIME) - 1, + NULL, 10); + + line = read_text_line(fp, buffer); + if (line == NULL) { + g_set_error(error_r, directory_quark(), 0, + "Unexpected end of file"); + directory_free(directory); + return NULL; + } + } - if (!g_str_has_prefix(buffer, DIRECTORY_BEGIN)) { - g_set_error(error, directory_quark(), 0, - "Malformed line: %s", buffer); + if (!g_str_has_prefix(line, DIRECTORY_BEGIN)) { + g_set_error(error_r, directory_quark(), 0, + "Malformed line: %s", line); + directory_free(directory); + return NULL; + } + + success = directory_load(fp, directory, buffer, error_r); + if (!success) { + directory_free(directory); + return NULL; + } + + return directory; +} + +bool +directory_load(FILE *fp, struct directory *directory, + GString *buffer, GError **error) +{ + const char *line; + + while ((line = read_text_line(fp, buffer)) != NULL && + !g_str_has_prefix(line, DIRECTORY_END)) { + if (g_str_has_prefix(line, DIRECTORY_DIR)) { + struct directory *subdir = + directory_load_subdir(fp, directory, + line + sizeof(DIRECTORY_DIR) - 1, + buffer, error); + if (subdir == NULL) return false; - } - g_strchomp(buffer); - name = &(buffer[strlen(DIRECTORY_BEGIN)]); - if (!g_str_has_prefix(name, directory->path) != 0) { + dirvec_add(&directory->children, subdir); + } else if (g_str_has_prefix(line, SONG_BEGIN)) { + const char *name = line + sizeof(SONG_BEGIN) - 1; + struct song *song; + + if (songvec_find(&directory->songs, name) != NULL) { g_set_error(error, directory_quark(), 0, - "Wrong path in database: '%s' in '%s'", - name, directory->path); - return false; + "Duplicate song '%s'", name); + return NULL; } - subdir = directory_get_child(directory, name); - if (subdir != NULL) { - assert(subdir->parent == directory); - } else { - subdir = directory_new(name, directory); - dirvec_add(&directory->children, subdir); - } + song = song_load(fp, directory, name, + buffer, error); + if (song == NULL) + return false; - success = directory_load(fp, subdir, error); - if (!success) + songvec_add(&directory->songs, song); + } else if (g_str_has_prefix(line, PLAYLIST_META_BEGIN)) { + /* duplicate the name, because + playlist_metadata_load() will overwrite the + buffer */ + char *name = g_strdup(line + sizeof(PLAYLIST_META_BEGIN) - 1); + + if (!playlist_metadata_load(fp, &directory->playlists, + name, buffer, error)) { + g_free(name); return false; - } else if (g_str_has_prefix(buffer, SONG_BEGIN)) { - readSongInfoIntoList(fp, &directory->songs, directory); + } + + g_free(name); } else { g_set_error(error, directory_quark(), 0, - "Malformed line: %s", buffer); + "Malformed line: %s", line); return false; } } diff --git a/src/directory_save.h b/src/directory_save.h index 28ec094ad..9193b6c41 100644 --- a/src/directory_save.h +++ b/src/directory_save.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,10 +27,11 @@ struct directory; -int +void directory_save(FILE *fp, struct directory *directory); bool -directory_load(FILE *fp, struct directory *directory, GError **error); +directory_load(FILE *fp, struct directory *directory, + GString *buffer, GError **error); #endif diff --git a/src/dirvec.c b/src/dirvec.c index 3ccb5d413..89b32a4f4 100644 --- a/src/dirvec.c +++ b/src/dirvec.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "dirvec.h" #include "directory.h" diff --git a/src/dirvec.h b/src/dirvec.h index d0898e0ca..a1a97d9f1 100644 --- a/src/dirvec.h +++ b/src/dirvec.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/encoder/flac_encoder.c b/src/encoder/flac_encoder.c new file mode 100644 index 000000000..73328fe81 --- /dev/null +++ b/src/encoder/flac_encoder.c @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "encoder_api.h" +#include "encoder_plugin.h" +#include "audio_format.h" +#include "pcm_buffer.h" + +#include <assert.h> +#include <string.h> + +#include <FLAC/stream_encoder.h> + +struct flac_encoder { + struct encoder encoder; + + struct audio_format audio_format; + unsigned compression; + + FLAC__StreamEncoder *fse; + + struct pcm_buffer expand_buffer; + + struct pcm_buffer buffer; + size_t buffer_length; +}; + +extern const struct encoder_plugin flac_encoder_plugin; + + +static inline GQuark +flac_encoder_quark(void) +{ + return g_quark_from_static_string("flac_encoder"); +} + +static bool +flac_encoder_configure(struct flac_encoder *encoder, + const struct config_param *param, G_GNUC_UNUSED GError **error) +{ + encoder->compression = config_get_block_unsigned(param, + "compression", 5); + + return true; +} + +static struct encoder * +flac_encoder_init(const struct config_param *param, GError **error) +{ + struct flac_encoder *encoder; + + encoder = g_new(struct flac_encoder, 1); + encoder_struct_init(&encoder->encoder, &flac_encoder_plugin); + + /* load configuration from "param" */ + if (!flac_encoder_configure(encoder, param, error)) { + /* configuration has failed, roll back and return error */ + g_free(encoder); + return NULL; + } + + return &encoder->encoder; +} + +static void +flac_encoder_finish(struct encoder *_encoder) +{ + struct flac_encoder *encoder = (struct flac_encoder *)_encoder; + + /* the real libFLAC cleanup was already performed by + flac_encoder_close(), so no real work here */ + g_free(encoder); +} + +static bool +flac_encoder_setup(struct flac_encoder *encoder, unsigned bits_per_sample, + GError **error) +{ +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 +#else + if ( !FLAC__stream_encoder_set_compression_level(encoder->fse, + encoder->compression)) { + g_set_error(error, flac_encoder_quark(), 0, + "error setting flac compression to %d", + encoder->compression); + return false; + } +#endif + if ( !FLAC__stream_encoder_set_channels(encoder->fse, + encoder->audio_format.channels)) { + g_set_error(error, flac_encoder_quark(), 0, + "error setting flac channels num to %d", + encoder->audio_format.channels); + return false; + } + if ( !FLAC__stream_encoder_set_bits_per_sample(encoder->fse, + bits_per_sample)) { + g_set_error(error, flac_encoder_quark(), 0, + "error setting flac bit format to %d", + bits_per_sample); + return false; + } + if ( !FLAC__stream_encoder_set_sample_rate(encoder->fse, + encoder->audio_format.sample_rate)) { + g_set_error(error, flac_encoder_quark(), 0, + "error setting flac sample rate to %d", + encoder->audio_format.sample_rate); + return false; + } + return true; +} + +static FLAC__StreamEncoderWriteStatus +flac_write_callback(G_GNUC_UNUSED const FLAC__StreamEncoder *fse, + const FLAC__byte data[], +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 + unsigned bytes, +#else + size_t bytes, +#endif + G_GNUC_UNUSED unsigned samples, + G_GNUC_UNUSED unsigned current_frame, void *client_data) +{ + struct flac_encoder *encoder = (struct flac_encoder *) client_data; + + char *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length + bytes); + + //transfer data to buffer + memcpy( buffer + encoder->buffer_length, data, bytes); + encoder->buffer_length += bytes; + + return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; +} + +static void +flac_encoder_close(struct encoder *_encoder) +{ + struct flac_encoder *encoder = (struct flac_encoder *)_encoder; + + FLAC__stream_encoder_delete(encoder->fse); + + pcm_buffer_deinit(&encoder->buffer); + pcm_buffer_deinit(&encoder->expand_buffer); +} + +static bool +flac_encoder_open(struct encoder *_encoder, struct audio_format *audio_format, + GError **error) +{ + struct flac_encoder *encoder = (struct flac_encoder *)_encoder; + unsigned bits_per_sample; + + encoder->audio_format = *audio_format; + + /* FIXME: flac should support 32bit as well */ + switch (audio_format->format) { + case SAMPLE_FORMAT_S8: + bits_per_sample = 8; + break; + + case SAMPLE_FORMAT_S16: + bits_per_sample = 16; + break; + + case SAMPLE_FORMAT_S24_P32: + bits_per_sample = 24; + break; + + default: + bits_per_sample = 24; + audio_format->format = SAMPLE_FORMAT_S24_P32; + } + + /* allocate the encoder */ + encoder->fse = FLAC__stream_encoder_new(); + if (encoder->fse == NULL) { + g_set_error(error, flac_encoder_quark(), 0, + "flac_new() failed"); + return false; + } + + if (!flac_encoder_setup(encoder, bits_per_sample, error)) { + FLAC__stream_encoder_delete(encoder->fse); + return false; + } + + encoder->buffer_length = 0; + pcm_buffer_init(&encoder->buffer); + pcm_buffer_init(&encoder->expand_buffer); + + /* this immediatelly outputs data throught callback */ + +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 + { + FLAC__StreamEncoderState init_status; + + FLAC__stream_encoder_set_write_callback(encoder->fse, + flac_write_callback); + + init_status = FLAC__stream_encoder_init(encoder->fse); + + if (init_status != FLAC__STREAM_ENCODER_OK) { + g_set_error(error, flac_encoder_quark(), 0, + "failed to initialize encoder: %s\n", + FLAC__StreamEncoderStateString[init_status]); + flac_encoder_close(_encoder); + return false; + } + } +#else + { + FLAC__StreamEncoderInitStatus init_status; + + init_status = FLAC__stream_encoder_init_stream(encoder->fse, + flac_write_callback, + NULL, NULL, NULL, encoder); + + if(init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) { + g_set_error(error, flac_encoder_quark(), 0, + "failed to initialize encoder: %s\n", + FLAC__StreamEncoderInitStatusString[init_status]); + flac_encoder_close(_encoder); + return false; + } + } +#endif + + return true; +} + + +static bool +flac_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error) +{ + struct flac_encoder *encoder = (struct flac_encoder *)_encoder; + + (void) FLAC__stream_encoder_finish(encoder->fse); + return true; +} + +static inline void +pcm8_to_flac(int32_t *out, const int8_t *in, unsigned num_samples) +{ + while (num_samples > 0) { + *out++ = *in++; + --num_samples; + } +} + +static inline void +pcm16_to_flac(int32_t *out, const int16_t *in, unsigned num_samples) +{ + while (num_samples > 0) { + *out++ = *in++; + --num_samples; + } +} + +static bool +flac_encoder_write(struct encoder *_encoder, + const void *data, size_t length, + G_GNUC_UNUSED GError **error) +{ + struct flac_encoder *encoder = (struct flac_encoder *)_encoder; + unsigned num_frames, num_samples; + void *exbuffer; + const void *buffer = NULL; + + /* format conversion */ + + num_frames = length / audio_format_frame_size(&encoder->audio_format); + num_samples = num_frames * encoder->audio_format.channels; + + switch (encoder->audio_format.format) { + case SAMPLE_FORMAT_S8: + exbuffer = pcm_buffer_get(&encoder->expand_buffer, length*4); + pcm8_to_flac(exbuffer, data, num_samples); + buffer = exbuffer; + break; + + case SAMPLE_FORMAT_S16: + exbuffer = pcm_buffer_get(&encoder->expand_buffer, length*2); + pcm16_to_flac(exbuffer, data, num_samples); + buffer = exbuffer; + break; + + case SAMPLE_FORMAT_S24_P32: + case SAMPLE_FORMAT_S32: + /* nothing need to be done; format is the same for + both mpd and libFLAC */ + buffer = data; + break; + } + + /* feed samples to encoder */ + + if (!FLAC__stream_encoder_process_interleaved(encoder->fse, buffer, + num_frames)) { + g_set_error(error, flac_encoder_quark(), 0, + "flac encoder process failed"); + return false; + } + + return true; +} + +static size_t +flac_encoder_read(struct encoder *_encoder, void *dest, size_t length) +{ + struct flac_encoder *encoder = (struct flac_encoder *)_encoder; + char *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length); + + if (length > encoder->buffer_length) + length = encoder->buffer_length; + + memcpy(dest, buffer, length); + + encoder->buffer_length -= length; + memmove(buffer, buffer + length, encoder->buffer_length); + + return length; +} + +static const char * +flac_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder) +{ + return "audio/flac"; +} + +const struct encoder_plugin flac_encoder_plugin = { + .name = "flac", + .init = flac_encoder_init, + .finish = flac_encoder_finish, + .open = flac_encoder_open, + .close = flac_encoder_close, + .flush = flac_encoder_flush, + .write = flac_encoder_write, + .read = flac_encoder_read, + .get_mime_type = flac_encoder_get_mime_type, +}; + diff --git a/src/encoder/lame_encoder.c b/src/encoder/lame_encoder.c index acaf4470f..a8ef72020 100644 --- a/src/encoder/lame_encoder.c +++ b/src/encoder/lame_encoder.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "encoder_api.h" #include "encoder_plugin.h" #include "audio_format.h" @@ -184,7 +185,7 @@ lame_encoder_open(struct encoder *_encoder, struct audio_format *audio_format, { struct lame_encoder *encoder = (struct lame_encoder *)_encoder; - audio_format->bits = 16; + audio_format->format = SAMPLE_FORMAT_S16; audio_format->channels = 2; encoder->audio_format = *audio_format; @@ -274,6 +275,12 @@ lame_encoder_read(struct encoder *_encoder, void *dest, size_t length) return length; } +static const char * +lame_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder) +{ + return "audio/mpeg"; +} + const struct encoder_plugin lame_encoder_plugin = { .name = "lame", .init = lame_encoder_init, @@ -282,4 +289,5 @@ const struct encoder_plugin lame_encoder_plugin = { .close = lame_encoder_close, .write = lame_encoder_write, .read = lame_encoder_read, + .get_mime_type = lame_encoder_get_mime_type, }; diff --git a/src/encoder/null_encoder.c b/src/encoder/null_encoder.c new file mode 100644 index 000000000..bf7e61c3b --- /dev/null +++ b/src/encoder/null_encoder.c @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "encoder_api.h" +#include "encoder_plugin.h" +#include "pcm_buffer.h" + +#include <assert.h> +#include <string.h> + +struct null_encoder { + struct encoder encoder; + + struct pcm_buffer buffer; + size_t buffer_length; +}; + +extern const struct encoder_plugin null_encoder_plugin; + +static inline GQuark +null_encoder_quark(void) +{ + return g_quark_from_static_string("null_encoder"); +} + +static struct encoder * +null_encoder_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error) +{ + struct null_encoder *encoder; + + encoder = g_new(struct null_encoder, 1); + encoder_struct_init(&encoder->encoder, &null_encoder_plugin); + + return &encoder->encoder; +} + +static void +null_encoder_finish(struct encoder *_encoder) +{ + struct null_encoder *encoder = (struct null_encoder *)_encoder; + + g_free(encoder); +} + +static void +null_encoder_close(struct encoder *_encoder) +{ + struct null_encoder *encoder = (struct null_encoder *)_encoder; + + pcm_buffer_deinit(&encoder->buffer); +} + + +static bool +null_encoder_open(struct encoder *_encoder, + G_GNUC_UNUSED struct audio_format *audio_format, + G_GNUC_UNUSED GError **error) +{ + struct null_encoder *encoder = (struct null_encoder *)_encoder; + + encoder->buffer_length = 0; + pcm_buffer_init(&encoder->buffer); + + return true; +} + +static bool +null_encoder_write(struct encoder *_encoder, + const void *data, size_t length, + G_GNUC_UNUSED GError **error) +{ + struct null_encoder *encoder = (struct null_encoder *)_encoder; + char *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length + length); + + memcpy(buffer+encoder->buffer_length, data, length); + + encoder->buffer_length += length; + return true; +} + +static size_t +null_encoder_read(struct encoder *_encoder, void *dest, size_t length) +{ + struct null_encoder *encoder = (struct null_encoder *)_encoder; + char *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length); + + if (length > encoder->buffer_length) + length = encoder->buffer_length; + + memcpy(dest, buffer, length); + + encoder->buffer_length -= length; + memmove(buffer, buffer + length, encoder->buffer_length); + + return length; +} + +const struct encoder_plugin null_encoder_plugin = { + .name = "null", + .init = null_encoder_init, + .finish = null_encoder_finish, + .open = null_encoder_open, + .close = null_encoder_close, + .write = null_encoder_write, + .read = null_encoder_read, +}; diff --git a/src/encoder/twolame_encoder.c b/src/encoder/twolame_encoder.c new file mode 100644 index 000000000..d20af551b --- /dev/null +++ b/src/encoder/twolame_encoder.c @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "encoder_api.h" +#include "encoder_plugin.h" +#include "audio_format.h" + +#include <twolame.h> +#include <assert.h> +#include <string.h> + +struct twolame_encoder { + struct encoder encoder; + + struct audio_format audio_format; + float quality; + int bitrate; + + twolame_options *options; + + unsigned char buffer[32768]; + size_t buffer_length; + + /** + * Call libtwolame's flush function when the buffer is empty? + */ + bool flush; +}; + +extern const struct encoder_plugin twolame_encoder_plugin; + +static inline GQuark +twolame_encoder_quark(void) +{ + return g_quark_from_static_string("twolame_encoder"); +} + +static bool +twolame_encoder_configure(struct twolame_encoder *encoder, + const struct config_param *param, GError **error) +{ + const char *value; + char *endptr; + + value = config_get_block_string(param, "quality", NULL); + if (value != NULL) { + /* a quality was configured (VBR) */ + + encoder->quality = g_ascii_strtod(value, &endptr); + + if (*endptr != '\0' || encoder->quality < -1.0 || + encoder->quality > 10.0) { + g_set_error(error, twolame_encoder_quark(), 0, + "quality \"%s\" is not a number in the " + "range -1 to 10, line %i", + value, param->line); + return false; + } + + if (config_get_block_string(param, "bitrate", NULL) != NULL) { + g_set_error(error, twolame_encoder_quark(), 0, + "quality and bitrate are " + "both defined (line %i)", + param->line); + return false; + } + } else { + /* a bit rate was configured */ + + value = config_get_block_string(param, "bitrate", NULL); + if (value == NULL) { + g_set_error(error, twolame_encoder_quark(), 0, + "neither bitrate nor quality defined " + "at line %i", + param->line); + return false; + } + + encoder->quality = -2.0; + encoder->bitrate = g_ascii_strtoll(value, &endptr, 10); + + if (*endptr != '\0' || encoder->bitrate <= 0) { + g_set_error(error, twolame_encoder_quark(), 0, + "bitrate at line %i should be a positive integer", + param->line); + return false; + } + } + + return true; +} + +static struct encoder * +twolame_encoder_init(const struct config_param *param, GError **error) +{ + struct twolame_encoder *encoder; + + g_debug("libtwolame version %s", get_twolame_version()); + + encoder = g_new(struct twolame_encoder, 1); + encoder_struct_init(&encoder->encoder, &twolame_encoder_plugin); + + /* load configuration from "param" */ + if (!twolame_encoder_configure(encoder, param, error)) { + /* configuration has failed, roll back and return error */ + g_free(encoder); + return NULL; + } + + return &encoder->encoder; +} + +static void +twolame_encoder_finish(struct encoder *_encoder) +{ + struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder; + + /* the real libtwolame cleanup was already performed by + twolame_encoder_close(), so no real work here */ + g_free(encoder); +} + +static bool +twolame_encoder_setup(struct twolame_encoder *encoder, GError **error) +{ + if (encoder->quality >= -1.0) { + /* a quality was configured (VBR) */ + + if (0 != twolame_set_VBR(encoder->options, true)) { + g_set_error(error, twolame_encoder_quark(), 0, + "error setting twolame VBR mode"); + return false; + } + if (0 != twolame_set_VBR_q(encoder->options, encoder->quality)) { + g_set_error(error, twolame_encoder_quark(), 0, + "error setting twolame VBR quality"); + return false; + } + } else { + /* a bit rate was configured */ + + if (0 != twolame_set_brate(encoder->options, encoder->bitrate)) { + g_set_error(error, twolame_encoder_quark(), 0, + "error setting twolame bitrate"); + return false; + } + } + + if (0 != twolame_set_num_channels(encoder->options, + encoder->audio_format.channels)) { + g_set_error(error, twolame_encoder_quark(), 0, + "error setting twolame num channels"); + return false; + } + + if (0 != twolame_set_in_samplerate(encoder->options, + encoder->audio_format.sample_rate)) { + g_set_error(error, twolame_encoder_quark(), 0, + "error setting twolame sample rate"); + return false; + } + + if (0 > twolame_init_params(encoder->options)) { + g_set_error(error, twolame_encoder_quark(), 0, + "error initializing twolame params"); + return false; + } + + return true; +} + +static bool +twolame_encoder_open(struct encoder *_encoder, struct audio_format *audio_format, + GError **error) +{ + struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder; + + audio_format->format = SAMPLE_FORMAT_S16; + audio_format->channels = 2; + + encoder->audio_format = *audio_format; + + encoder->options = twolame_init(); + if (encoder->options == NULL) { + g_set_error(error, twolame_encoder_quark(), 0, + "twolame_init() failed"); + return false; + } + + if (!twolame_encoder_setup(encoder, error)) { + twolame_close(&encoder->options); + return false; + } + + encoder->buffer_length = 0; + encoder->flush = false; + + return true; +} + +static void +twolame_encoder_close(struct encoder *_encoder) +{ + struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder; + + twolame_close(&encoder->options); +} + +static bool +twolame_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error) +{ + struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder; + + encoder->flush = true; + return true; +} + +static bool +twolame_encoder_write(struct encoder *_encoder, + const void *data, size_t length, + G_GNUC_UNUSED GError **error) +{ + struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder; + unsigned num_frames; + const int16_t *src = (const int16_t*)data; + int bytes_out; + + assert(encoder->buffer_length == 0); + + num_frames = + length / audio_format_frame_size(&encoder->audio_format); + + bytes_out = twolame_encode_buffer_interleaved(encoder->options, + src, num_frames, + encoder->buffer, + sizeof(encoder->buffer)); + if (bytes_out < 0) { + g_set_error(error, twolame_encoder_quark(), 0, + "twolame encoder failed"); + return false; + } + + encoder->buffer_length = (size_t)bytes_out; + return true; +} + +static size_t +twolame_encoder_read(struct encoder *_encoder, void *dest, size_t length) +{ + struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder; + + if (encoder->buffer_length == 0 && encoder->flush) { + int ret = twolame_encode_flush(encoder->options, + encoder->buffer, + sizeof(encoder->buffer)); + if (ret > 0) + encoder->buffer_length = (size_t)ret; + + encoder->flush = false; + } + + if (length > encoder->buffer_length) + length = encoder->buffer_length; + + memcpy(dest, encoder->buffer, length); + + encoder->buffer_length -= length; + memmove(encoder->buffer, encoder->buffer + length, + encoder->buffer_length); + + return length; +} + +static const char * +twolame_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder) +{ + return "audio/mpeg"; +} + +const struct encoder_plugin twolame_encoder_plugin = { + .name = "twolame", + .init = twolame_encoder_init, + .finish = twolame_encoder_finish, + .open = twolame_encoder_open, + .close = twolame_encoder_close, + .flush = twolame_encoder_flush, + .write = twolame_encoder_write, + .read = twolame_encoder_read, + .get_mime_type = twolame_encoder_get_mime_type, +}; diff --git a/src/encoder/vorbis_encoder.c b/src/encoder/vorbis_encoder.c index a5f6387f6..9dac3564d 100644 --- a/src/encoder/vorbis_encoder.c +++ b/src/encoder/vorbis_encoder.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "encoder_api.h" #include "encoder_plugin.h" #include "tag.h" #include "audio_format.h" +#include "mpd_error.h" #include <vorbis/vorbisenc.h> @@ -211,7 +213,7 @@ vorbis_encoder_open(struct encoder *_encoder, struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; bool ret; - audio_format->bits = 16; + audio_format->format = SAMPLE_FORMAT_S16; encoder->audio_format = *audio_format; @@ -373,7 +375,7 @@ vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length) if (nbytes > length) /* XXX better error handling */ - g_error("buffer too small"); + MPD_ERROR("buffer too small"); memcpy(dest, page.header, page.header_len); memcpy(dest + page.header_len, page.body, page.body_len); @@ -381,6 +383,12 @@ vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length) return nbytes; } +static const char * +vorbis_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder) +{ + return "audio/ogg"; +} + const struct encoder_plugin vorbis_encoder_plugin = { .name = "vorbis", .init = vorbis_encoder_init, @@ -391,4 +399,5 @@ const struct encoder_plugin vorbis_encoder_plugin = { .tag = vorbis_encoder_tag, .write = vorbis_encoder_write, .read = vorbis_encoder_read, + .get_mime_type = vorbis_encoder_get_mime_type, }; diff --git a/src/encoder/wave_encoder.c b/src/encoder/wave_encoder.c new file mode 100644 index 000000000..7398b45c7 --- /dev/null +++ b/src/encoder/wave_encoder.c @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "encoder_api.h" +#include "encoder_plugin.h" +#include "pcm_buffer.h" + +#include <assert.h> +#include <string.h> + +struct wave_encoder { + struct encoder encoder; + unsigned bits; + + struct pcm_buffer buffer; + size_t buffer_length; +}; + +struct wave_header { + uint32_t id_riff; + uint32_t riff_size; + uint32_t id_wave; + uint32_t id_fmt; + uint32_t fmt_size; + uint16_t format; + uint16_t channels; + uint32_t freq; + uint32_t byterate; + uint16_t blocksize; + uint16_t bits; + uint32_t id_data; + uint32_t data_size; +}; + +extern const struct encoder_plugin wave_encoder_plugin; + +static inline GQuark +wave_encoder_quark(void) +{ + return g_quark_from_static_string("wave_encoder"); +} + +static void +fill_wave_header(struct wave_header *header, int channels, int bits, + int freq, int block_size) +{ + int data_size = 0x0FFFFFFF; + + /* constants */ + header->id_riff = GUINT32_TO_LE(0x46464952); + header->id_wave = GUINT32_TO_LE(0x45564157); + header->id_fmt = GUINT32_TO_LE(0x20746d66); + header->id_data = GUINT32_TO_LE(0x61746164); + + /* wave format */ + header->format = GUINT16_TO_LE(1); // PCM_FORMAT + header->channels = GUINT16_TO_LE(channels); + header->bits = GUINT16_TO_LE(bits); + header->freq = GUINT32_TO_LE(freq); + header->blocksize = GUINT16_TO_LE(block_size); + header->byterate = GUINT32_TO_LE(freq * block_size); + + /* chunk sizes (fake data length) */ + header->fmt_size = GUINT32_TO_LE(16); + header->data_size = GUINT32_TO_LE(data_size); + header->riff_size = GUINT32_TO_LE(4 + (8 + 16) + + (8 + data_size)); +} + +static struct encoder * +wave_encoder_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error) +{ + struct wave_encoder *encoder; + + encoder = g_new(struct wave_encoder, 1); + encoder_struct_init(&encoder->encoder, &wave_encoder_plugin); + pcm_buffer_init(&encoder->buffer); + + return &encoder->encoder; +} + +static void +wave_encoder_finish(struct encoder *_encoder) +{ + struct wave_encoder *encoder = (struct wave_encoder *)_encoder; + + pcm_buffer_deinit(&encoder->buffer); + g_free(encoder); +} + +static bool +wave_encoder_open(struct encoder *_encoder, + G_GNUC_UNUSED struct audio_format *audio_format, + G_GNUC_UNUSED GError **error) +{ + struct wave_encoder *encoder = (struct wave_encoder *)_encoder; + void *buffer; + + assert(audio_format_valid(audio_format)); + + switch (audio_format->format) { + case SAMPLE_FORMAT_S8: + encoder->bits = 8; + break; + + case SAMPLE_FORMAT_S16: + encoder->bits = 16; + break; + + case SAMPLE_FORMAT_S24_P32: + encoder->bits = 24; + break; + + case SAMPLE_FORMAT_S32: + encoder->bits = 32; + break; + + default: + audio_format->format = SAMPLE_FORMAT_S16; + encoder->bits = 16; + break; + } + + buffer = pcm_buffer_get(&encoder->buffer, sizeof(struct wave_header) ); + + /* create PCM wave header in initial buffer */ + fill_wave_header((struct wave_header *) buffer, + audio_format->channels, + encoder->bits, + audio_format->sample_rate, + (encoder->bits / 8) * audio_format->channels ); + + encoder->buffer_length = sizeof(struct wave_header); + return true; +} + +static inline size_t +pcm16_to_wave(uint16_t *dst16, const uint16_t *src16, size_t length) +{ + size_t cnt = length >> 1; + while (cnt > 0) { + *dst16++ = GUINT16_TO_LE(*src16++); + cnt--; + } + return length; +} + +static inline size_t +pcm32_to_wave(uint32_t *dst32, const uint32_t *src32, size_t length) +{ + size_t cnt = length >> 2; + while (cnt > 0){ + *dst32++ = GUINT32_TO_LE(*src32++); + cnt--; + } + return length; +} + +static inline size_t +pcm24_to_wave(uint8_t *dst8, const uint32_t *src32, size_t length) +{ + uint32_t value; + uint8_t *dst_old = dst8; + + length = length >> 2; + while (length > 0){ + value = *src32++; + *dst8++ = (value) & 0xFF; + *dst8++ = (value >> 8) & 0xFF; + *dst8++ = (value >> 16) & 0xFF; + length--; + } + //correct buffer length + return (dst8 - dst_old); +} + +static bool +wave_encoder_write(struct encoder *_encoder, + const void *src, size_t length, + G_GNUC_UNUSED GError **error) +{ + struct wave_encoder *encoder = (struct wave_encoder *)_encoder; + void *dst; + + dst = pcm_buffer_get(&encoder->buffer, encoder->buffer_length + length); + +#if (G_BYTE_ORDER == G_LITTLE_ENDIAN) + switch (encoder->bits) { + case 8: + case 16: + case 32:// optimized cases + memcpy(dst, src, length); + break; + case 24: + length = pcm24_to_wave(dst, src, length); + break; + } +#elif (G_BYTE_ORDER == G_BIG_ENDIAN) + switch (encoder->bits) { + case 8: + memcpy(dst, src, length); + break; + case 16: + length = pcm16_to_wave(dst, src, length); + break; + case 24: + length = pcm24_to_wave(dst, src, length); + break; + case 32: + length = pcm32_to_wave(dst, src, length); + break; + } +#else +#error G_BYTE_ORDER set to G_PDP_ENDIAN is not supported by wave_encoder +#endif + + encoder->buffer_length += length; + return true; +} + +static size_t +wave_encoder_read(struct encoder *_encoder, void *dest, size_t length) +{ + struct wave_encoder *encoder = (struct wave_encoder *)_encoder; + uint8_t *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length ); + + if (length > encoder->buffer_length) + length = encoder->buffer_length; + + memcpy(dest, buffer, length); + + encoder->buffer_length -= length; + memmove(buffer, buffer + length, encoder->buffer_length); + + return length; +} + +static const char * +wave_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder) +{ + return "audio/wav"; +} + +const struct encoder_plugin wave_encoder_plugin = { + .name = "wave", + .init = wave_encoder_init, + .finish = wave_encoder_finish, + .open = wave_encoder_open, + .write = wave_encoder_write, + .read = wave_encoder_read, + .get_mime_type = wave_encoder_get_mime_type, +}; diff --git a/src/encoder_api.h b/src/encoder_api.h index b1d4044ba..5df486ebd 100644 --- a/src/encoder_api.h +++ b/src/encoder_api.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/encoder_list.c b/src/encoder_list.c index d563b6bc8..f49ad48f7 100644 --- a/src/encoder_list.c +++ b/src/encoder_list.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,22 +17,36 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "encoder_list.h" #include "encoder_plugin.h" -#include "config.h" #include <string.h> +extern const struct encoder_plugin null_encoder_plugin; extern const struct encoder_plugin vorbis_encoder_plugin; extern const struct encoder_plugin lame_encoder_plugin; +extern const struct encoder_plugin twolame_encoder_plugin; +extern const struct encoder_plugin wave_encoder_plugin; +extern const struct encoder_plugin flac_encoder_plugin; static const struct encoder_plugin *encoder_plugins[] = { + &null_encoder_plugin, #ifdef ENABLE_VORBIS_ENCODER &vorbis_encoder_plugin, #endif #ifdef ENABLE_LAME_ENCODER &lame_encoder_plugin, #endif +#ifdef ENABLE_TWOLAME_ENCODER + &twolame_encoder_plugin, +#endif +#ifdef ENABLE_WAVE_ENCODER + &wave_encoder_plugin, +#endif +#ifdef ENABLE_FLAC_ENCODER + &flac_encoder_plugin, +#endif NULL }; @@ -45,3 +59,13 @@ encoder_plugin_get(const char *name) return NULL; } + +void +encoder_plugin_print_all_types(FILE * fp) +{ + for (unsigned i = 0; encoder_plugins[i] != NULL; ++i) + fprintf(fp, "%s ", encoder_plugins[i]->name); + + fprintf(fp, "\n"); + fflush(fp); +} diff --git a/src/encoder_list.h b/src/encoder_list.h index bc20ad8c5..95f853004 100644 --- a/src/encoder_list.h +++ b/src/encoder_list.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,8 @@ #ifndef MPD_ENCODER_LIST_H #define MPD_ENCODER_LIST_H +#include <stdio.h> + struct encoder_plugin; /** @@ -32,4 +34,7 @@ struct encoder_plugin; const struct encoder_plugin * encoder_plugin_get(const char *name); +void +encoder_plugin_print_all_types(FILE * fp); + #endif diff --git a/src/encoder_plugin.h b/src/encoder_plugin.h index 958fe97cf..13fb231f4 100644 --- a/src/encoder_plugin.h +++ b/src/encoder_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -58,6 +58,8 @@ struct encoder_plugin { GError **error); size_t (*read)(struct encoder *encoder, void *dest, size_t length); + + const char *(*get_mime_type)(struct encoder *encoder); }; /** @@ -192,4 +194,19 @@ encoder_read(struct encoder *encoder, void *dest, size_t length) return encoder->plugin->read(encoder, dest, length); } +/** + * Get mime type of encoded content. + * + * @param plugin the encoder plugin + * @return an constant string, NULL on failure + */ +static inline const char * +encoder_get_mime_type(struct encoder *encoder) +{ + /* this method is optional */ + return encoder->plugin->get_mime_type != NULL + ? encoder->plugin->get_mime_type(encoder) + : NULL; +} + #endif diff --git a/src/event_pipe.c b/src/event_pipe.c index 3e5009150..5b519984f 100644 --- a/src/event_pipe.c +++ b/src/event_pipe.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "event_pipe.h" -#include "utils.h" +#include "fd_util.h" +#include "mpd_error.h" #include <stdbool.h> #include <assert.h> @@ -37,6 +39,7 @@ #define G_LOG_DOMAIN "event_pipe" static int event_pipe[2]; +static GIOChannel *event_channel; static guint event_pipe_source_id; static GMutex *event_pipe_mutex; static bool pipe_events[PIPE_EVENT_MAX]; @@ -60,12 +63,15 @@ main_notify_event(G_GNUC_UNUSED GIOChannel *source, G_GNUC_UNUSED gpointer data) { char buffer[256]; - ssize_t r = read(event_pipe[0], buffer, sizeof(buffer)); - bool events[PIPE_EVENT_MAX]; - - if (r < 0 && errno != EAGAIN && errno != EINTR) - g_error("error reading from pipe: %s", strerror(errno)); + gsize bytes_read; + GError *error = NULL; + GIOStatus status = g_io_channel_read_chars(event_channel, + buffer, sizeof(buffer), + &bytes_read, &error); + if (status == G_IO_STATUS_ERROR) + MPD_ERROR("error reading from pipe: %s", error->message); + bool events[PIPE_EVENT_MAX]; g_mutex_lock(event_pipe_mutex); memcpy(events, pipe_events, sizeof(events)); memset(pipe_events, 0, sizeof(pipe_events)); @@ -84,22 +90,22 @@ void event_pipe_init(void) GIOChannel *channel; int ret; -#ifdef WIN32 - ret = _pipe(event_pipe, 512, _O_BINARY); -#else - ret = pipe(event_pipe); -#endif + ret = pipe_cloexec_nonblock(event_pipe); if (ret < 0) - g_error("Couldn't open pipe: %s", strerror(errno)); -#ifndef WIN32 - if (set_nonblocking(event_pipe[1]) < 0) - g_error("Couldn't set non-blocking I/O: %s", strerror(errno)); -#endif + MPD_ERROR("Couldn't open pipe: %s", strerror(errno)); +#ifndef G_OS_WIN32 channel = g_io_channel_unix_new(event_pipe[0]); +#else + channel = g_io_channel_win32_new_fd(event_pipe[0]); +#endif + g_io_channel_set_encoding(channel, NULL, NULL); + g_io_channel_set_buffered(channel, false); + event_pipe_source_id = g_io_add_watch(channel, G_IO_IN, main_notify_event, NULL); - g_io_channel_unref(channel); + + event_channel = channel; event_pipe_mutex = g_mutex_new(); } @@ -109,8 +115,12 @@ void event_pipe_deinit(void) g_mutex_free(event_pipe_mutex); g_source_remove(event_pipe_source_id); + g_io_channel_unref(event_channel); +#ifndef WIN32 + /* By some strange reason this call hangs on Win32 */ close(event_pipe[0]); +#endif close(event_pipe[1]); } @@ -141,7 +151,7 @@ void event_pipe_emit(enum pipe_event event) w = write(event_pipe[1], "", 1); if (w < 0 && errno != EAGAIN && errno != EINTR) - g_error("error writing to pipe: %s", strerror(errno)); + MPD_ERROR("error writing to pipe: %s", strerror(errno)); } void event_pipe_emit_fast(enum pipe_event event) diff --git a/src/event_pipe.h b/src/event_pipe.h index ecb7ec9e8..923544bf4 100644 --- a/src/event_pipe.h +++ b/src/event_pipe.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -32,7 +32,7 @@ enum pipe_event { /** an idle event was emitted */ PIPE_EVENT_IDLE, - /** must call syncPlayerAndPlaylist() */ + /** must call playlist_sync() */ PIPE_EVENT_PLAYLIST, /** the current song's tag has changed */ @@ -41,6 +41,12 @@ enum pipe_event { /** SIGHUP received: reload configuration, roll log file */ PIPE_EVENT_RELOAD, + /** a hardware mixer plugin has detected a change */ + PIPE_EVENT_MIXER, + + /** shutdown requested */ + PIPE_EVENT_SHUTDOWN, + PIPE_EVENT_MAX }; diff --git a/src/exclude.c b/src/exclude.c new file mode 100644 index 000000000..dd46b58c7 --- /dev/null +++ b/src/exclude.c @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * The .mpdignore backend code. + * + */ + +#include "config.h" +#include "exclude.h" +#include "path.h" + +#include <assert.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> + +GSList * +exclude_list_load(const char *path_fs) +{ + FILE *file; + char line[1024]; + GSList *list = NULL; + + assert(path_fs != NULL); + + file = fopen(path_fs, "r"); + if (file == NULL) { + if (errno != ENOENT) { + char *path_utf8 = fs_charset_to_utf8(path_fs); + g_debug("Failed to open %s: %s", + path_utf8, g_strerror(errno)); + g_free(path_utf8); + } + + return NULL; + } + + while (fgets(line, sizeof(line), file) != NULL) { + char *p = strchr(line, '#'); + if (p != NULL) + *p = 0; + + p = g_strstrip(line); + if (*p != 0) + list = g_slist_prepend(list, g_pattern_spec_new(p)); + } + + fclose(file); + + return list; +} + +void +exclude_list_free(GSList *list) +{ + while (list != NULL) { + GPatternSpec *pattern = list->data; + g_pattern_spec_free(pattern); + list = g_slist_remove(list, list->data); + } +} + +bool +exclude_list_check(GSList *list, const char *name_fs) +{ + assert(name_fs != NULL); + + /* XXX include full path name in check */ + + for (; list != NULL; list = list->next) { + GPatternSpec *pattern = list->data; + + if (g_pattern_match_string(pattern, name_fs)) + return true; + } + + return false; +} diff --git a/src/exclude.h b/src/exclude.h new file mode 100644 index 000000000..fd7cf8795 --- /dev/null +++ b/src/exclude.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * The .mpdignore backend code. + * + */ + +#ifndef MPD_EXCLUDE_H +#define MPD_EXCLUDE_H + +#include <glib.h> + +#include <stdbool.h> + +/** + * Loads and parses a .mpdignore file. + */ +GSList * +exclude_list_load(const char *path_fs); + +/** + * Frees a list returned by exclude_list_load(). + */ +void +exclude_list_free(GSList *list); + +/** + * Checks whether one of the patterns in the .mpdignore file matches + * the specified file name. + */ +bool +exclude_list_check(GSList *list, const char *name_fs); + +#endif diff --git a/src/fd_util.c b/src/fd_util.c new file mode 100644 index 000000000..1f3004d0c --- /dev/null +++ b/src/fd_util.c @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" /* must be first for large file support */ +#include "fd_util.h" + +#if !defined(_GNU_SOURCE) && (defined(HAVE_PIPE2) || defined(HAVE_ACCEPT4)) +#define _GNU_SOURCE +#endif + +#include <assert.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#ifdef WIN32 +#include <ws2tcpip.h> +#include <winsock2.h> +#else +#include <sys/socket.h> +#endif + +#ifdef HAVE_INOTIFY_INIT +#include <sys/inotify.h> +#endif + +#ifndef WIN32 + +static int +fd_mask_flags(int fd, int and_mask, int xor_mask) +{ + int ret; + + assert(fd >= 0); + + ret = fcntl(fd, F_GETFD, 0); + if (ret < 0) + return ret; + + return fcntl(fd, F_SETFD, (ret & and_mask) ^ xor_mask); +} + +#endif /* !WIN32 */ + +static int +fd_set_cloexec(int fd, bool enable) +{ +#ifndef WIN32 + return fd_mask_flags(fd, ~FD_CLOEXEC, enable ? FD_CLOEXEC : 0); +#else + (void)fd; + (void)enable; + + return 0; +#endif +} + +/** + * Enables non-blocking mode for the specified file descriptor. On + * WIN32, this function only works for sockets. + */ +static int +fd_set_nonblock(int fd) +{ +#ifdef WIN32 + u_long val = 1; + return ioctlsocket(fd, FIONBIO, &val); +#else + int flags; + + assert(fd >= 0); + + flags = fcntl(fd, F_GETFL); + if (flags < 0) + return flags; + + return fcntl(fd, F_SETFL, flags | O_NONBLOCK); +#endif +} + +int +dup_cloexec(int oldfd) +{ + int newfd = dup(oldfd); + if (newfd >= 0) + fd_set_nonblock(newfd); + + return newfd; +} + +int +open_cloexec(const char *path_fs, int flags, int mode) +{ + int fd; + +#ifdef O_CLOEXEC + flags |= O_CLOEXEC; +#endif + +#ifdef O_NOCTTY + flags |= O_NOCTTY; +#endif + + fd = open(path_fs, flags, mode); + if (fd >= 0) + fd_set_cloexec(fd, true); + + return fd; +} + +int +pipe_cloexec(int fd[2]) +{ +#ifdef WIN32 + return _pipe(fd, 512, _O_BINARY); +#else + int ret; + +#ifdef HAVE_PIPE2 + ret = pipe2(fd, O_CLOEXEC); + if (ret >= 0 || errno != ENOSYS) + return ret; +#endif + + ret = pipe(fd); + if (ret >= 0) { + fd_set_cloexec(fd[0], true); + fd_set_cloexec(fd[1], true); + } + + return ret; +#endif +} + +int +pipe_cloexec_nonblock(int fd[2]) +{ +#ifdef WIN32 + return _pipe(fd, 512, _O_BINARY); +#else + int ret; + +#ifdef HAVE_PIPE2 + ret = pipe2(fd, O_CLOEXEC|O_NONBLOCK); + if (ret >= 0 || errno != ENOSYS) + return ret; +#endif + + ret = pipe(fd); + if (ret >= 0) { + fd_set_cloexec(fd[0], true); + fd_set_cloexec(fd[1], true); + + fd_set_nonblock(fd[0]); + fd_set_nonblock(fd[1]); + } + + return ret; +#endif +} + +#ifndef WIN32 + +int +socketpair_cloexec(int domain, int type, int protocol, int sv[2]) +{ + int ret; + +#ifdef SOCK_CLOEXEC + ret = socketpair(domain, type | SOCK_CLOEXEC, protocol, sv); + if (ret >= 0 || errno != EINVAL) + return ret; +#endif + + ret = socketpair(domain, type, protocol, sv); + if (ret >= 0) { + fd_set_cloexec(sv[0], true); + fd_set_cloexec(sv[1], true); + } + + return ret; +} + +#endif + +int +socket_cloexec_nonblock(int domain, int type, int protocol) +{ + int fd; + +#if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) + fd = socket(domain, type | SOCK_CLOEXEC | SOCK_NONBLOCK, protocol); + if (fd >= 0 || errno != EINVAL) + return fd; +#endif + + fd = socket(domain, type, protocol); + if (fd >= 0) { + fd_set_cloexec(fd, true); + fd_set_nonblock(fd); + } + + return fd; +} + +int +accept_cloexec_nonblock(int fd, struct sockaddr *address, + size_t *address_length_r) +{ + int ret; + socklen_t address_length = *address_length_r; + +#ifdef HAVE_ACCEPT4 + ret = accept4(fd, address, &address_length, + SOCK_CLOEXEC|SOCK_NONBLOCK); + if (ret >= 0 || errno != ENOSYS) { + if (ret >= 0) + *address_length_r = address_length; + + return ret; + } +#endif + + ret = accept(fd, address, &address_length); + if (ret >= 0) { + fd_set_cloexec(ret, true); + fd_set_nonblock(ret); + *address_length_r = address_length; + } + + return ret; +} + +#ifndef WIN32 + +ssize_t +recvmsg_cloexec(int sockfd, struct msghdr *msg, int flags) +{ +#ifdef MSG_CMSG_CLOEXEC + flags |= MSG_CMSG_CLOEXEC; +#endif + + ssize_t result = recvmsg(sockfd, msg, flags); + if (result >= 0) { + struct cmsghdr *cmsg = CMSG_FIRSTHDR(msg); + while (cmsg != NULL) { + if (cmsg->cmsg_type == SCM_RIGHTS) { + const int *fd_p = (const int *)CMSG_DATA(cmsg); + fd_set_cloexec(*fd_p, true); + } + + cmsg = CMSG_NXTHDR(msg, cmsg); + } + } + + return result; +} + +#endif + +#ifdef HAVE_INOTIFY_INIT + +int +inotify_init_cloexec(void) +{ + int fd; + +#ifdef HAVE_INOTIFY_INIT1 + fd = inotify_init1(IN_CLOEXEC); + if (fd >= 0 || errno != ENOSYS) + return fd; +#endif + + fd = inotify_init(); + if (fd >= 0) + fd_set_cloexec(fd, true); + + return fd; +} + +#endif diff --git a/src/fd_util.h b/src/fd_util.h new file mode 100644 index 000000000..5b80df2c7 --- /dev/null +++ b/src/fd_util.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This library provides easy helper functions for working with file + * descriptors. It has wrappers for taking advantage of Linux 2.6 + * specific features like O_CLOEXEC. + * + */ + +#ifndef FD_UTIL_H +#define FD_UTIL_H + +#include <stdbool.h> +#include <stddef.h> + +#ifndef WIN32 +#if !defined(_GNU_SOURCE) && (defined(HAVE_PIPE2) || defined(HAVE_ACCEPT4)) +#define _GNU_SOURCE +#endif + +#include <sys/types.h> +#endif + +struct sockaddr; + +/** + * Wrapper for dup(), which sets the CLOEXEC flag on the new + * descriptor. + */ +int +dup_cloexec(int oldfd); + +/** + * Wrapper for open(), which sets the CLOEXEC flag (atomically if + * supported by the OS). + */ +int +open_cloexec(const char *path_fs, int flags, int mode); + +/** + * Wrapper for pipe(), which sets the CLOEXEC flag (atomically if + * supported by the OS). + */ +int +pipe_cloexec(int fd[2]); + +/** + * Wrapper for pipe(), which sets the CLOEXEC flag (atomically if + * supported by the OS). + * + * On systems that supports it (everybody except for Windows), it also + * sets the NONBLOCK flag. + */ +int +pipe_cloexec_nonblock(int fd[2]); + +#ifndef WIN32 + +/** + * Wrapper for socketpair(), which sets the CLOEXEC flag (atomically + * if supported by the OS). + */ +int +socketpair_cloexec(int domain, int type, int protocol, int sv[2]); + +#endif + +/** + * Wrapper for socket(), which sets the CLOEXEC and the NONBLOCK flag + * (atomically if supported by the OS). + */ +int +socket_cloexec_nonblock(int domain, int type, int protocol); + +/** + * Wrapper for accept(), which sets the CLOEXEC and the NONBLOCK flags + * (atomically if supported by the OS). + */ +int +accept_cloexec_nonblock(int fd, struct sockaddr *address, + size_t *address_length_r); + + +#ifndef WIN32 + +struct msghdr; + +/** + * Wrapper for recvmsg(), which sets the CLOEXEC flag (atomically if + * supported by the OS). + */ +ssize_t +recvmsg_cloexec(int sockfd, struct msghdr *msg, int flags); + +#endif + +/** + * Wrapper for inotify_init(), which sets the CLOEXEC flag (atomically + * if supported by the OS). + */ +int +inotify_init_cloexec(void); + +#endif diff --git a/src/fifo_buffer.c b/src/fifo_buffer.c index adee438c0..9ac7270bb 100644 --- a/src/fifo_buffer.c +++ b/src/fifo_buffer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * Redistribution and use in source and binary forms, with or without @@ -28,6 +28,7 @@ * OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include "config.h" #include "fifo_buffer.h" #include <glib.h> diff --git a/src/fifo_buffer.h b/src/fifo_buffer.h index 4af6bde3b..661dfd57e 100644 --- a/src/fifo_buffer.h +++ b/src/fifo_buffer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * Redistribution and use in source and binary forms, with or without diff --git a/src/filter/autoconvert_filter_plugin.c b/src/filter/autoconvert_filter_plugin.c new file mode 100644 index 000000000..9e197a5f6 --- /dev/null +++ b/src/filter/autoconvert_filter_plugin.c @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "filter/autoconvert_filter_plugin.h" +#include "filter/convert_filter_plugin.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "conf.h" +#include "pcm_convert.h" +#include "audio_format.h" +#include "poison.h" + +#include <assert.h> +#include <string.h> + +struct autoconvert_filter { + struct filter base; + + /** + * The audio format being fed to the underlying filter. This + * plugin actually doesn't need this variable, we have it here + * just so our open() method doesn't return a stack pointer. + */ + struct audio_format in_audio_format; + + /** + * The underlying filter. + */ + struct filter *filter; + + /** + * A convert_filter, just in case conversion is needed. NULL + * if unused. + */ + struct filter *convert; +}; + +static void +autoconvert_filter_finish(struct filter *_filter) +{ + struct autoconvert_filter *filter = + (struct autoconvert_filter *)_filter; + + filter_free(filter->filter); + g_free(filter); +} + +static const struct audio_format * +autoconvert_filter_open(struct filter *_filter, + struct audio_format *in_audio_format, + GError **error_r) +{ + struct autoconvert_filter *filter = + (struct autoconvert_filter *)_filter; + const struct audio_format *out_audio_format; + + assert(audio_format_valid(in_audio_format)); + + /* open the "real" filter */ + + filter->in_audio_format = *in_audio_format; + + out_audio_format = filter_open(filter->filter, + &filter->in_audio_format, error_r); + if (out_audio_format == NULL) + return NULL; + + /* need to convert? */ + + if (!audio_format_equals(&filter->in_audio_format, in_audio_format)) { + /* yes - create a convert_filter */ + struct audio_format audio_format2 = *in_audio_format; + const struct audio_format *audio_format3; + + filter->convert = filter_new(&convert_filter_plugin, NULL, + error_r); + if (filter->convert == NULL) { + filter_close(filter->filter); + return NULL; + } + + audio_format3 = filter_open(filter->convert, &audio_format2, + error_r); + if (audio_format3 == NULL) { + filter_free(filter->convert); + filter_close(filter->filter); + return NULL; + } + + assert(audio_format_equals(&audio_format2, in_audio_format)); + + convert_filter_set(filter->convert, &filter->in_audio_format); + } else + /* no */ + filter->convert = NULL; + + return out_audio_format; +} + +static void +autoconvert_filter_close(struct filter *_filter) +{ + struct autoconvert_filter *filter = + (struct autoconvert_filter *)_filter; + + if (filter->convert != NULL) { + filter_close(filter->convert); + filter_free(filter->convert); + } + + filter_close(filter->filter); +} + +static const void * +autoconvert_filter_filter(struct filter *_filter, const void *src, + size_t src_size, size_t *dest_size_r, + GError **error_r) +{ + struct autoconvert_filter *filter = + (struct autoconvert_filter *)_filter; + + if (filter->convert != NULL) { + src = filter_filter(filter->convert, src, src_size, &src_size, + error_r); + if (src == NULL) + return NULL; + } + + return filter_filter(filter->filter, src, src_size, dest_size_r, + error_r); +} + +static const struct filter_plugin autoconvert_filter_plugin = { + .name = "convert", + .finish = autoconvert_filter_finish, + .open = autoconvert_filter_open, + .close = autoconvert_filter_close, + .filter = autoconvert_filter_filter, +}; + +struct filter * +autoconvert_filter_new(struct filter *_filter) +{ + struct autoconvert_filter *filter = + g_new(struct autoconvert_filter, 1); + + filter_init(&filter->base, &autoconvert_filter_plugin); + filter->filter = _filter; + + return &filter->base; +} diff --git a/src/buffer2array.h b/src/filter/autoconvert_filter_plugin.h index bed23a29f..730db197d 100644 --- a/src/buffer2array.h +++ b/src/filter/autoconvert_filter_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,15 +17,18 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_BUFFER_2_ARRAY_H -#define MPD_BUFFER_2_ARRAY_H +#ifndef AUTOCONVERT_FILTER_PLUGIN_H +#define AUTOCONVERT_FILTER_PLUGIN_H -/* tokenizes up to max elements in buffer (a null-terminated string) and - * stores the result in array (which must be capable of holding up to - * max elements). Tokenization is based on C string quoting rules. - * The arguments buffer and array are modified. - * Returns the number of elements tokenized. +struct filter; + +/** + * Creates a new "autoconvert" filter. When opened, it ensures that + * the input audio format isn't changed. If the underlying filter + * requests a different format, it automatically creates a + * convert_filter. */ -int buffer2array(char *buffer, char *array[], const int max); +struct filter * +autoconvert_filter_new(struct filter *filter); #endif diff --git a/src/filter/chain_filter_plugin.c b/src/filter/chain_filter_plugin.c new file mode 100644 index 000000000..06d4d0e6b --- /dev/null +++ b/src/filter/chain_filter_plugin.c @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "conf.h" +#include "filter/chain_filter_plugin.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "audio_format.h" + +#include <assert.h> + +struct filter_chain { + /** the base class */ + struct filter base; + + GSList *children; +}; + +static inline GQuark +filter_quark(void) +{ + return g_quark_from_static_string("filter"); +} + +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_open_child(struct filter *filter, + const struct audio_format *prev_audio_format, + GError **error_r) +{ + struct audio_format conv_audio_format = *prev_audio_format; + const struct audio_format *next_audio_format; + + next_audio_format = filter_open(filter, &conv_audio_format, error_r); + if (next_audio_format == NULL) + return NULL; + + if (!audio_format_equals(&conv_audio_format, prev_audio_format)) { + struct audio_format_string s; + + filter_close(filter); + g_set_error(error_r, filter_quark(), 0, + "Audio format not supported by filter '%s': %s", + filter->plugin->name, + audio_format_to_string(prev_audio_format, &s)); + return NULL; + } + + return next_audio_format; +} + +static const struct audio_format * +chain_filter_open(struct filter *_filter, struct audio_format *in_audio_format, + GError **error_r) +{ + struct filter_chain *chain = (struct filter_chain *)_filter; + const struct audio_format *audio_format = in_audio_format; + + for (GSList *i = chain->children; i != NULL; i = g_slist_next(i)) { + struct filter *filter = i->data; + + audio_format = chain_open_child(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) + return NULL; + } + + /* 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..42c6a9b78 --- /dev/null +++ b/src/filter/chain_filter_plugin.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2010 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..cb9e0940a --- /dev/null +++ b/src/filter/convert_filter_plugin.c @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "filter/convert_filter_plugin.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "conf.h" +#include "pcm_convert.h" +#include "audio_format.h" +#include "poison.h" + +#include <assert.h> +#include <string.h> + +struct convert_filter { + struct filter base; + + /** + * The current convert, from 0 to #PCM_CONVERT_1. + */ + unsigned convert; + + /** + * The input audio format; PCM data is passed to the filter() + * method in this format. + */ + struct audio_format in_audio_format; + + /** + * The output audio format; the consumer of this plugin + * expects PCM data in this format. This defaults to + * #in_audio_format, and can be set with convert_filter_set(). + */ + struct audio_format out_audio_format; + + struct pcm_convert_state state; +}; + +static struct filter * +convert_filter_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct convert_filter *filter = g_new(struct convert_filter, 1); + + filter_init(&filter->base, &convert_filter_plugin); + return &filter->base; +} + +static void +convert_filter_finish(struct filter *filter) +{ + g_free(filter); +} + +static const struct audio_format * +convert_filter_open(struct filter *_filter, 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 &filter->in_audio_format; +} + +static void +convert_filter_close(struct filter *_filter) +{ + struct convert_filter *filter = (struct convert_filter *)_filter; + + pcm_convert_deinit(&filter->state); + + poison_undefined(&filter->in_audio_format, + sizeof(filter->in_audio_format)); + poison_undefined(&filter->out_audio_format, + sizeof(filter->out_audio_format)); +} + +static const void * +convert_filter_filter(struct filter *_filter, const void *src, size_t src_size, + size_t *dest_size_r, GError **error_r) +{ + struct convert_filter *filter = (struct convert_filter *)_filter; + const void *dest; + + if (audio_format_equals(&filter->in_audio_format, + &filter->out_audio_format)) { + /* optimized special case: no-op */ + *dest_size_r = src_size; + return src; + } + + dest = pcm_convert(&filter->state, &filter->in_audio_format, + src, src_size, + &filter->out_audio_format, dest_size_r, + error_r); + if (dest == NULL) + return NULL; + + return dest; +} + +const struct filter_plugin convert_filter_plugin = { + .name = "convert", + .init = convert_filter_init, + .finish = convert_filter_finish, + .open = convert_filter_open, + .close = convert_filter_close, + .filter = convert_filter_filter, +}; + +void +convert_filter_set(struct filter *_filter, + const struct audio_format *out_audio_format) +{ + struct convert_filter *filter = (struct convert_filter *)_filter; + + assert(filter != NULL); + assert(audio_format_valid(&filter->in_audio_format)); + assert(audio_format_valid(&filter->out_audio_format)); + assert(out_audio_format != NULL); + assert(audio_format_valid(out_audio_format)); + assert(filter->in_audio_format.reverse_endian == 0); + + filter->out_audio_format = *out_audio_format; +} diff --git a/src/filter/convert_filter_plugin.h b/src/filter/convert_filter_plugin.h new file mode 100644 index 000000000..ba9180e64 --- /dev/null +++ b/src/filter/convert_filter_plugin.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef CONVERT_FILTER_PLUGIN_H +#define CONVERT_FILTER_PLUGIN_H + +struct filter; +struct audio_format; + +/** + * Sets the output audio format for the specified filter. You must + * call this after the filter has been opened. Since this audio + * format switch is a violation of the filter API, this filter must be + * the last in a chain. + */ +void +convert_filter_set(struct filter *filter, + const struct audio_format *out_audio_format); + +#endif diff --git a/src/filter/normalize_filter_plugin.c b/src/filter/normalize_filter_plugin.c new file mode 100644 index 000000000..63bbb6e4f --- /dev/null +++ b/src/filter/normalize_filter_plugin.c @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "pcm_buffer.h" +#include "audio_format.h" +#include "AudioCompress/compress.h" + +#include <assert.h> +#include <string.h> + +struct normalize_filter { + struct filter filter; + + struct Compressor *compressor; + + struct pcm_buffer buffer; +}; + +static inline GQuark +normalize_quark(void) +{ + return g_quark_from_static_string("normalize"); +} + +static struct filter * +normalize_filter_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct normalize_filter *filter = g_new(struct normalize_filter, 1); + + filter_init(&filter->filter, &normalize_filter_plugin); + + return &filter->filter; +} + +static void +normalize_filter_finish(struct filter *filter) +{ + g_free(filter); +} + +static const struct audio_format * +normalize_filter_open(struct filter *_filter, + struct audio_format *audio_format, + G_GNUC_UNUSED GError **error_r) +{ + struct normalize_filter *filter = (struct normalize_filter *)_filter; + + audio_format->format = SAMPLE_FORMAT_S16; + audio_format->reverse_endian = false; + + filter->compressor = Compressor_new(0); + + pcm_buffer_init(&filter->buffer); + + return audio_format; +} + +static void +normalize_filter_close(struct filter *_filter) +{ + struct normalize_filter *filter = (struct normalize_filter *)_filter; + + pcm_buffer_deinit(&filter->buffer); + Compressor_delete(filter->compressor); +} + +static const void * +normalize_filter_filter(struct filter *_filter, + const void *src, size_t src_size, size_t *dest_size_r, + G_GNUC_UNUSED GError **error_r) +{ + struct normalize_filter *filter = (struct normalize_filter *)_filter; + void *dest; + + dest = pcm_buffer_get(&filter->buffer, src_size); + + memcpy(dest, src, src_size); + + Compressor_Process_int16(filter->compressor, dest, src_size / 2); + + *dest_size_r = src_size; + return dest; +} + +const struct filter_plugin normalize_filter_plugin = { + .name = "normalize", + .init = normalize_filter_init, + .finish = normalize_filter_finish, + .open = normalize_filter_open, + .close = normalize_filter_close, + .filter = normalize_filter_filter, +}; diff --git a/src/filter/null_filter_plugin.c b/src/filter/null_filter_plugin.c new file mode 100644 index 000000000..650f95bc4 --- /dev/null +++ b/src/filter/null_filter_plugin.c @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * This filter plugin does nothing. That is not quite useful, except + * for testing the filter core, or as a template for new filter + * plugins. + */ + +#include "config.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" + +#include <assert.h> + +struct null_filter { + struct filter filter; +}; + +static struct filter * +null_filter_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct null_filter *filter = g_new(struct null_filter, 1); + + filter_init(&filter->filter, &null_filter_plugin); + return &filter->filter; +} + +static void +null_filter_finish(struct filter *_filter) +{ + struct null_filter *filter = (struct null_filter *)_filter; + + g_free(filter); +} + +static const struct audio_format * +null_filter_open(struct filter *_filter, 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/replay_gain_filter_plugin.c b/src/filter/replay_gain_filter_plugin.c new file mode 100644 index 000000000..3a0af66ff --- /dev/null +++ b/src/filter/replay_gain_filter_plugin.c @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "filter/replay_gain_filter_plugin.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "audio_format.h" +#include "pcm_buffer.h" +#include "pcm_volume.h" +#include "replay_gain_info.h" +#include "replay_gain_config.h" +#include "mixer_control.h" + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "replay_gain" + +struct replay_gain_filter { + struct filter filter; + + /** + * If set, then this hardware mixer is used for applying + * replay gain, instead of the software volume library. + */ + struct mixer *mixer; + + /** + * The base volume level for scale=1.0, between 1 and 100 + * (including). + */ + unsigned base; + + enum replay_gain_mode mode; + + struct replay_gain_info info; + + /** + * The current volume, between 0 and a value that may or may not exceed + * #PCM_VOLUME_1. + * + * If the default value of true is used for replaygain_limit, the + * application of the volume to the signal will never cause clipping. + * + * On the other hand, if the user has set replaygain_limit to false, + * the chance of clipping is explicitly preferred if that's required to + * maintain a consistent audio level. Whether clipping will actually + * occur depends on what value the user is using for replaygain_preamp. + */ + unsigned volume; + + struct audio_format audio_format; + + struct pcm_buffer buffer; +}; + +static inline GQuark +replay_gain_quark(void) +{ + return g_quark_from_static_string("replay_gain"); +} + +/** + * Recalculates the new volume after a property was changed. + */ +static void +replay_gain_filter_update(struct replay_gain_filter *filter) +{ + if (filter->mode != REPLAY_GAIN_OFF) { + float scale = replay_gain_tuple_scale(&filter->info.tuples[filter->mode], + replay_gain_preamp, replay_gain_missing_preamp, replay_gain_limit); + g_debug("scale=%f\n", (double)scale); + + filter->volume = pcm_float_to_volume(scale); + } else + filter->volume = PCM_VOLUME_1; + + if (filter->mixer != NULL) { + /* update the hardware mixer volume */ + + unsigned volume = (filter->volume * filter->base) / PCM_VOLUME_1; + if (volume > 100) + volume = 100; + + GError *error = NULL; + if (!mixer_set_volume(filter->mixer, volume, &error)) { + g_warning("Failed to update hardware mixer: %s", + error->message); + g_error_free(error); + } + } +} + +static struct filter * +replay_gain_filter_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct replay_gain_filter *filter = g_new(struct replay_gain_filter, 1); + + filter_init(&filter->filter, &replay_gain_filter_plugin); + filter->mixer = NULL; + + filter->mode = replay_gain_get_real_mode(); + replay_gain_info_init(&filter->info); + filter->volume = PCM_VOLUME_1; + + return &filter->filter; +} + +static void +replay_gain_filter_finish(struct filter *filter) +{ + g_free(filter); +} + +static const struct audio_format * +replay_gain_filter_open(struct filter *_filter, + struct audio_format *audio_format, + G_GNUC_UNUSED GError **error_r) +{ + struct replay_gain_filter *filter = + (struct replay_gain_filter *)_filter; + + audio_format->reverse_endian = false; + + filter->audio_format = *audio_format; + pcm_buffer_init(&filter->buffer); + + return &filter->audio_format; +} + +static void +replay_gain_filter_close(struct filter *_filter) +{ + struct replay_gain_filter *filter = + (struct replay_gain_filter *)_filter; + + pcm_buffer_deinit(&filter->buffer); +} + +static const void * +replay_gain_filter_filter(struct filter *_filter, + const void *src, size_t src_size, + size_t *dest_size_r, GError **error_r) +{ + struct replay_gain_filter *filter = + (struct replay_gain_filter *)_filter; + bool success; + void *dest; + enum replay_gain_mode rg_mode; + + /* check if the mode has been changed since the last call */ + rg_mode = replay_gain_get_real_mode(); + + if (filter->mode != rg_mode) { + g_debug("replay gain mode has changed %d->%d\n", filter->mode, rg_mode); + filter->mode = rg_mode; + replay_gain_filter_update(filter); + } + + *dest_size_r = src_size; + + if (filter->volume == PCM_VOLUME_1) + /* optimized special case: 100% volume = no-op */ + return src; + + dest = pcm_buffer_get(&filter->buffer, 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, replay_gain_quark(), 0, + "pcm_volume() has failed"); + return NULL; + } + + return dest; +} + +const struct filter_plugin replay_gain_filter_plugin = { + .name = "replay_gain", + .init = replay_gain_filter_init, + .finish = replay_gain_filter_finish, + .open = replay_gain_filter_open, + .close = replay_gain_filter_close, + .filter = replay_gain_filter_filter, +}; + +void +replay_gain_filter_set_mixer(struct filter *_filter, struct mixer *mixer, + unsigned base) +{ + struct replay_gain_filter *filter = + (struct replay_gain_filter *)_filter; + + assert(mixer == NULL || (base > 0 && base <= 100)); + + filter->mixer = mixer; + filter->base = base; + + replay_gain_filter_update(filter); +} + +void +replay_gain_filter_set_info(struct filter *_filter, + const struct replay_gain_info *info) +{ + struct replay_gain_filter *filter = + (struct replay_gain_filter *)_filter; + + if (info != NULL) { + filter->info = *info; + replay_gain_info_complete(&filter->info); + } else + replay_gain_info_init(&filter->info); + + replay_gain_filter_update(filter); +} diff --git a/src/filter/replay_gain_filter_plugin.h b/src/filter/replay_gain_filter_plugin.h new file mode 100644 index 000000000..348b4f50c --- /dev/null +++ b/src/filter/replay_gain_filter_plugin.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2003-2010 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 REPLAY_GAIN_FILTER_PLUGIN_H +#define REPLAY_GAIN_FILTER_PLUGIN_H + +#include "replay_gain_info.h" + +struct filter; +struct mixer; + +/** + * Enables or disables the hardware mixer for applying replay gain. + * + * @param mixer the hardware mixer, or NULL to fall back to software + * volume + * @param base the base volume level for scale=1.0, between 1 and 100 + * (including). + */ +void +replay_gain_filter_set_mixer(struct filter *_filter, struct mixer *mixer, + unsigned base); + +/** + * Sets a new #replay_gain_info at the beginning of a new song. + * + * @param info the new #replay_gain_info value, or NULL if no replay + * gain data is available for the current song + */ +void +replay_gain_filter_set_info(struct filter *filter, + const struct replay_gain_info *info); + +#endif diff --git a/src/filter/route_filter_plugin.c b/src/filter/route_filter_plugin.c new file mode 100644 index 000000000..6b9aa2a2f --- /dev/null +++ b/src/filter/route_filter_plugin.c @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * This filter copies audio data between channels. Useful for + * upmixing mono/stereo audio to surround speaker configurations. + * + * Its configuration consists of a "filter" section with a single + * "routes" entry, formatted as: \\ + * routes "0>1, 1>0, 2>2, 3>3, 3>4" \\ + * where each pair of numbers signifies a set of channels. + * Each source>dest pair leads to the data from channel #source + * being copied to channel #dest in the output. + * + * Example: \\ + * routes "0>0, 1>1, 0>2, 1>3"\\ + * upmixes stereo audio to a 4-speaker system, copying the front-left + * (0) to front left (0) and rear left (2), copying front-right (1) to + * front-right (1) and rear-right (3). + * + * If multiple sources are copied to the same destination channel, only + * one of them takes effect. + */ + +#include "config.h" +#include "conf.h" +#include "audio_format.h" +#include "audio_check.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "pcm_buffer.h" + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + + +struct route_filter { + + /** + * Inherit (and support cast to/from) filter + */ + struct filter base; + + /** + * The minimum number of channels we need for output + * to be able to perform all the copies the user has specified + */ + unsigned char min_output_channels; + + /** + * The minimum number of input channels we need to + * copy all the data the user has requested. If fewer + * than this many are supplied by the input, undefined + * copy operations are given zeroed sources in stead. + */ + unsigned char min_input_channels; + + /** + * The set of copy operations to perform on each sample + * The index is an output channel to use, the value is + * a corresponding input channel from which to take the + * data. A -1 means "no source" + */ + signed char* sources; + + /** + * The actual input format of our signal, once opened + */ + struct audio_format input_format; + + /** + * The decided upon output format, once opened + */ + struct audio_format output_format; + + /** + * The size, in bytes, of each multichannel frame in the + * input buffer + */ + size_t input_frame_size; + + /** + * The size, in bytes, of each multichannel frame in the + * output buffer + */ + size_t output_frame_size; + + /** + * The output buffer used last time around, can be reused if the size doesn't differ. + */ + struct pcm_buffer output_buffer; + +}; + +/** + * Parse the "routes" section, a string on the form + * a>b, c>d, e>f, ... + * where a... are non-unique, non-negative integers + * and input channel a gets copied to output channel b, etc. + * @param param the configuration block to read + * @param filter a route_filter whose min_channels and sources[] to set + * @return true on success, false on error + */ +static bool +route_filter_parse(const struct config_param *param, + struct route_filter *filter, + GError **error_r) { + + /* TODO: + * With a more clever way of marking "don't copy to output N", + * This could easily be merged into a single loop with some + * dynamic g_realloc() instead of one count run and one g_malloc(). + */ + + gchar **tokens; + int number_of_copies; + + // A cowardly default, just passthrough stereo + const char *routes = + config_get_block_string(param, "routes", "0>0, 1>1"); + + filter->min_input_channels = 0; + filter->min_output_channels = 0; + + tokens = g_strsplit(routes, ",", 255); + number_of_copies = g_strv_length(tokens); + + // Start by figuring out a few basic things about the routing set + for (int c=0; c<number_of_copies; ++c) { + + // String and int representations of the source/destination + gchar **sd; + int source, dest; + + // Squeeze whitespace + g_strstrip(tokens[c]); + + // Split the a>b string into source and destination + sd = g_strsplit(tokens[c], ">", 2); + if (g_strv_length(sd) != 2) { + g_set_error(error_r, config_quark(), 1, + "Invalid copy around %d in routes spec: %s", + param->line, tokens[c]); + g_strfreev(sd); + g_strfreev(tokens); + return false; + } + + source = strtol(sd[0], NULL, 10); + dest = strtol(sd[1], NULL, 10); + + // Keep track of the highest channel numbers seen + // as either in- or outputs + if (source >= filter->min_input_channels) + filter->min_input_channels = source + 1; + if (dest >= filter->min_output_channels) + filter->min_output_channels = dest + 1; + + g_strfreev(sd); + } + + if (!audio_valid_channel_count(filter->min_output_channels)) { + g_strfreev(tokens); + g_set_error(error_r, audio_format_quark(), 0, + "Invalid number of output channels requested: %d", + filter->min_output_channels); + return false; + } + + // Allocate a map of "copy nothing to me" + filter->sources = + g_malloc(filter->min_output_channels * sizeof(signed char)); + + for (int i=0; i<filter->min_output_channels; ++i) + filter->sources[i] = -1; + + // Run through the spec again, and save the + // actual mapping output <- input + for (int c=0; c<number_of_copies; ++c) { + + // String and int representations of the source/destination + gchar **sd; + int source, dest; + + // Split the a>b string into source and destination + sd = g_strsplit(tokens[c], ">", 2); + if (g_strv_length(sd) != 2) { + g_set_error(error_r, config_quark(), 1, + "Invalid copy around %d in routes spec: %s", + param->line, tokens[c]); + g_strfreev(sd); + g_strfreev(tokens); + return false; + } + + source = strtol(sd[0], NULL, 10); + dest = strtol(sd[1], NULL, 10); + + filter->sources[dest] = source; + + g_strfreev(sd); + } + + g_strfreev(tokens); + + return true; +} + +static struct filter * +route_filter_init(const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct route_filter *filter = g_new(struct route_filter, 1); + filter_init(&filter->base, &route_filter_plugin); + + // Allocate and set the filter->sources[] array + route_filter_parse(param, filter, error_r); + + return &filter->base; +} + +static void +route_filter_finish(struct filter *_filter) +{ + struct route_filter *filter = (struct route_filter *)_filter; + + g_free(filter->sources); + g_free(filter); +} + +static const struct audio_format * +route_filter_open(struct filter *_filter, struct audio_format *audio_format, + G_GNUC_UNUSED GError **error_r) +{ + struct route_filter *filter = (struct route_filter *)_filter; + + // Copy the input format for later reference + filter->input_format = *audio_format; + filter->input_frame_size = + audio_format_frame_size(&filter->input_format); + + // Decide on an output format which has enough channels, + // and is otherwise identical + filter->output_format = *audio_format; + filter->output_format.channels = filter->min_output_channels; + + // Precalculate this simple value, to speed up allocation later + filter->output_frame_size = + audio_format_frame_size(&filter->output_format); + + // This buffer grows as needed + pcm_buffer_init(&filter->output_buffer); + + return &filter->output_format; +} + +static void +route_filter_close(struct filter *_filter) +{ + struct route_filter *filter = (struct route_filter *)_filter; + + pcm_buffer_deinit(&filter->output_buffer); +} + +static const void * +route_filter_filter(struct filter *_filter, + const void *src, size_t src_size, + size_t *dest_size_r, G_GNUC_UNUSED GError **error_r) +{ + struct route_filter *filter = (struct route_filter *)_filter; + + size_t number_of_frames = src_size / filter->input_frame_size; + + size_t bytes_per_frame_per_channel = + audio_format_sample_size(&filter->input_format); + + // A moving pointer that always refers to channel 0 in the input, at the currently handled frame + const uint8_t *base_source = src; + + // A moving pointer that always refers to the currently filled channel of the currently handled frame, in the output + uint8_t *chan_destination; + + // Grow our reusable buffer, if needed, and set the moving pointer + *dest_size_r = number_of_frames * filter->output_frame_size; + chan_destination = pcm_buffer_get(&filter->output_buffer, *dest_size_r); + + + // Perform our copy operations, with N input channels and M output channels + for (unsigned int s=0; s<number_of_frames; ++s) { + + // Need to perform one copy per output channel + for (unsigned int c=0; c<filter->min_output_channels; ++c) { + if (filter->sources[c] == -1 || + (unsigned)filter->sources[c] >= filter->input_format.channels) { + // No source for this destination output, + // give it zeroes as input + memset(chan_destination, + 0x00, + bytes_per_frame_per_channel); + } else { + // Get the data from channel sources[c] + // and copy it to the output + const uint8_t *data = base_source + + (filter->sources[c] * bytes_per_frame_per_channel); + memcpy(chan_destination, + data, + bytes_per_frame_per_channel); + } + // Move on to the next output channel + chan_destination += bytes_per_frame_per_channel; + } + + + // Go on to the next N input samples + base_source += filter->input_frame_size; + } + + // Here it is, ladies and gentlemen! Rerouted data! + return (void *) filter->output_buffer.buffer; +} + +const struct filter_plugin route_filter_plugin = { + .name = "route", + .init = route_filter_init, + .finish = route_filter_finish, + .open = route_filter_open, + .close = route_filter_close, + .filter = route_filter_filter, +}; diff --git a/src/filter/volume_filter_plugin.c b/src/filter/volume_filter_plugin.c new file mode 100644 index 000000000..42311ca5e --- /dev/null +++ b/src/filter/volume_filter_plugin.c @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "filter/volume_filter_plugin.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "conf.h" +#include "pcm_buffer.h" +#include "pcm_volume.h" +#include "audio_format.h" +#include "player_control.h" + +#include <assert.h> +#include <string.h> + +struct volume_filter { + struct filter filter; + + /** + * The current volume, from 0 to #PCM_VOLUME_1. + */ + unsigned volume; + + struct audio_format audio_format; + + struct pcm_buffer buffer; +}; + +static inline GQuark +volume_quark(void) +{ + return g_quark_from_static_string("pcm_volume"); +} + +static struct filter * +volume_filter_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct volume_filter *filter = g_new(struct volume_filter, 1); + + filter_init(&filter->filter, &volume_filter_plugin); + filter->volume = PCM_VOLUME_1; + + return &filter->filter; +} + +static void +volume_filter_finish(struct filter *filter) +{ + g_free(filter); +} + +static const struct audio_format * +volume_filter_open(struct filter *_filter, struct audio_format *audio_format, + G_GNUC_UNUSED GError **error_r) +{ + struct volume_filter *filter = (struct volume_filter *)_filter; + + audio_format->reverse_endian = 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; + + *dest_size_r = src_size; + + if (filter->volume >= PCM_VOLUME_1) + /* optimized special case: 100% volume = no-op */ + return src; + + dest = pcm_buffer_get(&filter->buffer, 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/normalize.h b/src/filter/volume_filter_plugin.h index a8144951d..ad3b2c6f1 100644 --- a/src/normalize.h +++ b/src/filter/volume_filter_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,18 +17,15 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_NORMALIZE_H -#define MPD_NORMALIZE_H +#ifndef VOLUME_FILTER_PLUGIN_H +#define VOLUME_FILTER_PLUGIN_H -struct audio_format; +struct filter; -extern int normalizationEnabled; +unsigned +volume_filter_get(const struct filter *filter); -void initNormalization(void); +void +volume_filter_set(struct filter *filter, unsigned volume); -void finishNormalization(void); - -void normalizeData(char *buffer, int bufferSize, - const struct audio_format *format); - -#endif /* !NORMALIZE_H */ +#endif diff --git a/src/filter_config.c b/src/filter_config.c new file mode 100644 index 000000000..90de199b7 --- /dev/null +++ b/src/filter_config.c @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "filter_config.h" +#include "config.h" +#include "conf.h" +#include "filter/chain_filter_plugin.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" + +#include <string.h> + + +static GQuark +filter_quark(void) +{ + return g_quark_from_static_string("filter"); +} + +/** + * Find the "filter" configuration block for the specified name. + * + * @param filter_template_name the name of the filter template + * @param error_r space to return an error description + * @return the configuration block, or NULL if none was configured + */ +static const struct config_param * +filter_plugin_config(const char *filter_template_name, GError **error_r) +{ + const struct config_param *param = NULL; + + while ((param = config_get_next_param(CONF_AUDIO_FILTER, param)) != NULL) { + const char *name = + config_get_block_string(param, "name", NULL); + if (name == NULL) { + g_set_error(error_r, filter_quark(), 1, + "filter configuration without 'name' name in line %d", + param->line); + return NULL; + } + + if (strcmp(name, filter_template_name) == 0) + return param; + } + + g_set_error(error_r, filter_quark(), 1, + "filter template not found: %s", + filter_template_name); + + return NULL; +} + +/** + * Builds a filter chain from a configuration string on the form + * "name1, name2, name3, ..." by looking up each name among the + * configured filter sections. + * @param chain the chain to append filters on + * @param spec the filter chain specification + * @param error_r space to return an error description + * @return the number of filters which were successfully added + */ +unsigned int +filter_chain_parse(struct filter *chain, const char *spec, GError **error_r) +{ + + // Split on comma + gchar** tokens = g_strsplit_set(spec, ",", 255); + + int added_filters = 0; + + // Add each name to the filter chain by instantiating an actual filter + char **template_names = tokens; + while (*template_names != NULL) { + struct filter *f; + const struct config_param *cfg; + + // Squeeze whitespace + g_strstrip(*template_names); + + cfg = filter_plugin_config(*template_names, error_r); + if (cfg == NULL) { + // The error has already been set, just stop. + break; + } + + // Instantiate one of those filter plugins with the template name as a hint + f = filter_configured_new(cfg, error_r); + if (f == NULL) { + // The error has already been set, just stop. + break; + } + + filter_chain_append(chain, f); + ++added_filters; + + ++template_names; + } + + g_strfreev(tokens); + + return added_filters; +} diff --git a/src/filter_config.h b/src/filter_config.h new file mode 100644 index 000000000..9ed4d204b --- /dev/null +++ b/src/filter_config.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * Utility functions for filter configuration + */ + +#ifndef MPD_FILTER_CONFIG_H +#define MPD_FILTER_CONFIG_H + +#include "conf.h" +#include "filter/chain_filter_plugin.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" + + +/** + * Builds a filter chain from a configuration string on the form + * "name1, name2, name3, ..." by looking up each name among the + * configured filter sections. + * @param chain the chain to append filters on + * @param spec the filter chain specification + * @param error_r space to return an error description + * @return the number of filters which were successfully added + */ +unsigned int +filter_chain_parse(struct filter *chain, const char *spec, GError **error_r); + +#endif diff --git a/src/filter_internal.h b/src/filter_internal.h new file mode 100644 index 000000000..8dd6da491 --- /dev/null +++ b/src/filter_internal.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2010 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..492d703ac --- /dev/null +++ b/src/filter_plugin.c @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "conf.h" + +#ifndef NDEBUG +#include "audio_format.h" +#endif + +#include <assert.h> + +struct filter * +filter_new(const struct filter_plugin *plugin, + const struct config_param *param, GError **error_r) +{ + assert(plugin != NULL); + assert(error_r == NULL || *error_r == NULL); + + return plugin->init(param, error_r); +} + +struct filter * +filter_configured_new(const struct config_param *param, GError **error_r) +{ + const char *plugin_name; + const struct filter_plugin *plugin; + + assert(param != NULL); + assert(error_r == NULL || *error_r == NULL); + + plugin_name = config_get_block_string(param, "plugin", NULL); + if (plugin_name == NULL) { + g_set_error(error_r, config_quark(), 0, + "No filter plugin specified"); + return NULL; + } + + plugin = filter_plugin_by_name(plugin_name); + if (plugin == NULL) { + g_set_error(error_r, config_quark(), 0, + "No such filter plugin: %s", plugin_name); + return NULL; + } + + return filter_new(plugin, param, error_r); +} + +void +filter_free(struct filter *filter) +{ + assert(filter != NULL); + + filter->plugin->finish(filter); +} + +const struct audio_format * +filter_open(struct filter *filter, struct audio_format *audio_format, + GError **error_r) +{ + const struct audio_format *out_audio_format; + + assert(filter != NULL); + assert(audio_format != NULL); + assert(audio_format_valid(audio_format)); + assert(error_r == NULL || *error_r == NULL); + + out_audio_format = filter->plugin->open(filter, audio_format, error_r); + + assert(out_audio_format == NULL || audio_format_valid(audio_format)); + assert(out_audio_format == NULL || + audio_format_valid(out_audio_format)); + + return out_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..ac6b34522 --- /dev/null +++ b/src/filter_plugin.h @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2003-2010 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. + * + * @param audio_format the audio format of incoming data; the + * plugin may modify the object to enforce another input + * format + */ + const struct audio_format * + (*open)(struct filter *filter, + struct audio_format *audio_format, + GError **error_r); + + /** + * Closes a filter. + */ + void (*close)(struct filter *filter); + + /** + * Filters a block of PCM data. + */ + const void *(*filter)(struct filter *filter, + const void *src, size_t src_size, + size_t *dest_buffer_r, + GError **error_r); +}; + +/** + * Creates a new instance of the specified filter plugin. + * + * @param plugin the filter plugin + * @param param optional configuration section + * @param error location to store the error occuring, or NULL to + * ignore errors. + * @return a new filter object, or NULL on error + */ +struct filter * +filter_new(const struct filter_plugin *plugin, + const struct config_param *param, GError **error_r); + +/** + * Creates a new filter, loads configuration and the plugin name from + * the specified configuration section. + * + * @param param the configuration section + * @param error location to store the error occuring, or NULL to + * ignore errors. + * @return a new filter object, or NULL on error + */ +struct filter * +filter_configured_new(const struct config_param *param, GError **error_r); + +/** + * Deletes a filter. It must be closed prior to calling this + * function, see filter_close(). + * + * @param filter the filter object + */ +void +filter_free(struct filter *filter); + +/** + * Opens the filter, preparing it for filter_filter(). + * + * @param filter the filter object + * @param audio_format the audio format of incoming data; the plugin + * may modify the object to enforce another input format + * @param error location to store the error occuring, or NULL to + * ignore errors. + * @return the format of outgoing data + */ +const struct audio_format * +filter_open(struct filter *filter, 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..150043cc5 --- /dev/null +++ b/src/filter_registry.c @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "filter_registry.h" +#include "filter_plugin.h" + +#include <stddef.h> +#include <string.h> + +const struct filter_plugin *const filter_plugins[] = { + &null_filter_plugin, + &route_filter_plugin, + &normalize_filter_plugin, + &volume_filter_plugin, + &replay_gain_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..551a7afa1 --- /dev/null +++ b/src/filter_registry.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * This library manages all filter plugins which are enabled at + * compile time. + */ + +#ifndef MPD_FILTER_REGISTRY_H +#define MPD_FILTER_REGISTRY_H + +extern const struct filter_plugin null_filter_plugin; +extern const struct filter_plugin chain_filter_plugin; +extern const struct filter_plugin convert_filter_plugin; +extern const struct filter_plugin route_filter_plugin; +extern const struct filter_plugin normalize_filter_plugin; +extern const struct filter_plugin volume_filter_plugin; +extern const struct filter_plugin replay_gain_filter_plugin; + +const struct filter_plugin * +filter_plugin_by_name(const char *name); + +#endif @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/glib_compat.h b/src/glib_compat.h new file mode 100644 index 000000000..4d0e7040d --- /dev/null +++ b/src/glib_compat.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Compatibility with older GLib versions. Some of this isn't + * implemented properly, just "good enough" to allow users with older + * operating systems to run MPD. + */ + +#ifndef MPD_GLIB_COMPAT_H +#define MPD_GLIB_COMPAT_H + +#include <glib.h> + +#if !GLIB_CHECK_VERSION(2,14,0) + +#define g_queue_clear(q) do { g_queue_free(q); q = g_queue_new(); } while (0) + +static inline guint +g_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data) +{ + return g_timeout_add(interval * 1000, function, data); +} + +#endif /* !2.14 */ + +#if !GLIB_CHECK_VERSION(2,16,0) + +static inline void +g_propagate_prefixed_error(GError **dest_r, GError *src, + G_GNUC_UNUSED const gchar *format, ...) +{ + g_propagate_error(dest_r, src); +} + +static inline char * +g_uri_escape_string(const char *unescaped, + G_GNUC_UNUSED const char *reserved_chars_allowed, + G_GNUC_UNUSED gboolean allow_utf8) +{ + return g_strdup(unescaped); +} + +#endif /* !2.16 */ + +#if !GLIB_CHECK_VERSION(2,16,0) + +#include <string.h> + +static inline char * +g_uri_parse_scheme(const char *uri) +{ + const char *end = strstr(uri, "://"); + if (end == NULL) + return NULL; + return g_strndup(uri, end - uri); +} + +#endif + +#endif diff --git a/src/icy_metadata.c b/src/icy_metadata.c index 69aa89092..6a79121cf 100644 --- a/src/icy_metadata.c +++ b/src/icy_metadata.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "icy_metadata.h" #include "tag.h" @@ -95,7 +96,7 @@ icy_parse_tag_item(struct tag *tag, const char *item) if (p[0] != NULL && p[1] != NULL) { if (strcmp(p[0], "StreamTitle") == 0) - icy_add_item(tag, TAG_ITEM_TITLE, p[1]); + icy_add_item(tag, TAG_TITLE, p[1]); else g_debug("unknown icy-tag: '%s'", p[0]); } diff --git a/src/icy_metadata.h b/src/icy_metadata.h index f8eac4e91..4a51b4cf0 100644 --- a/src/icy_metadata.h +++ b/src/icy_metadata.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/icy_server.c b/src/icy_server.c index 50b10c6ca..62a2c67af 100644 --- a/src/icy_server.c +++ b/src/icy_server.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "icy_server.h" #include <glib.h> diff --git a/src/icy_server.h b/src/icy_server.h index b48014c29..3ce4ab635 100644 --- a/src/icy_server.h +++ b/src/icy_server.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/idle.c b/src/idle.c index 11b57376d..eccb62322 100644 --- a/src/idle.c +++ b/src/idle.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,6 +22,7 @@ * */ +#include "config.h" #include "idle.h" #include "event_pipe.h" @@ -40,6 +41,7 @@ static const char *const idle_names[] = { "output", "options", "sticker", + "update", NULL }; diff --git a/src/idle.h b/src/idle.h index a69acabb0..7caeb4a8c 100644 --- a/src/idle.h +++ b/src/idle.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -50,6 +50,9 @@ enum { /** a sticker has been modified. */ IDLE_STICKER = 0x80, + + /** a database update has started or finished. */ + IDLE_UPDATE = 0x100, }; /** diff --git a/src/inotify_queue.c b/src/inotify_queue.c new file mode 100644 index 000000000..5391a1715 --- /dev/null +++ b/src/inotify_queue.c @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "inotify_queue.h" +#include "update.h" + +#include <glib.h> + +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "inotify" + +enum { + /** + * Wait this long after the last change before calling + * update_enqueue(). This increases the probability that + * updates can be bundled. + */ + INOTIFY_UPDATE_DELAY_S = 5, +}; + +static GSList *inotify_queue; +static guint queue_source_id; + +void +mpd_inotify_queue_init(void) +{ +} + +static void +free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) +{ + g_free(data); +} + +void +mpd_inotify_queue_finish(void) +{ + if (queue_source_id != 0) + g_source_remove(queue_source_id); + + g_slist_foreach(inotify_queue, free_callback, NULL); + g_slist_free(inotify_queue); +} + +static gboolean +mpd_inotify_run_update(G_GNUC_UNUSED gpointer data) +{ + unsigned id; + + while (inotify_queue != NULL) { + char *uri_utf8 = inotify_queue->data; + + id = update_enqueue(uri_utf8, false); + if (id == 0) + /* retry later */ + return true; + + g_debug("updating '%s' job=%u", uri_utf8, id); + + g_free(uri_utf8); + inotify_queue = g_slist_delete_link(inotify_queue, + inotify_queue); + } + + /* done, remove the timer event by returning false */ + queue_source_id = 0; + return false; +} + +static bool +path_in(const char *path, const char *possible_parent) +{ + size_t length = strlen(possible_parent); + + return path[0] == 0 || + (memcmp(possible_parent, path, length) == 0 && + (path[length] == 0 || path[length] == '/')); +} + +void +mpd_inotify_enqueue(char *uri_utf8) +{ + GSList *old_queue = inotify_queue; + + if (queue_source_id != 0) + g_source_remove(queue_source_id); + queue_source_id = g_timeout_add_seconds(INOTIFY_UPDATE_DELAY_S, + mpd_inotify_run_update, NULL); + + inotify_queue = NULL; + while (old_queue != NULL) { + char *current_uri = old_queue->data; + + if (path_in(uri_utf8, current_uri)) { + /* already enqueued */ + g_free(uri_utf8); + inotify_queue = g_slist_concat(inotify_queue, + old_queue); + return; + } + + old_queue = g_slist_delete_link(old_queue, old_queue); + + if (path_in(current_uri, uri_utf8)) + /* existing path is a sub-path of the new + path; we can dequeue the existing path and + update the new path instead */ + g_free(current_uri); + else + /* move the existing path to the new queue */ + inotify_queue = g_slist_prepend(inotify_queue, + current_uri); + } + + inotify_queue = g_slist_prepend(inotify_queue, uri_utf8); +} diff --git a/src/inotify_queue.h b/src/inotify_queue.h new file mode 100644 index 000000000..2e43d2f25 --- /dev/null +++ b/src/inotify_queue.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INOTIFY_QUEUE_H +#define MPD_INOTIFY_QUEUE_H + +void +mpd_inotify_queue_init(void); + +void +mpd_inotify_queue_finish(void); + +void +mpd_inotify_enqueue(char *uri_utf8); + +#endif diff --git a/src/inotify_source.c b/src/inotify_source.c new file mode 100644 index 000000000..3a986cbad --- /dev/null +++ b/src/inotify_source.c @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "inotify_source.h" +#include "fifo_buffer.h" +#include "fd_util.h" +#include "mpd_error.h" + +#include <sys/inotify.h> +#include <unistd.h> +#include <errno.h> +#include <stdbool.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "inotify" + +struct mpd_inotify_source { + int fd; + + GIOChannel *channel; + + /** + * The channel's source id in the GLib main loop. + */ + guint id; + + struct fifo_buffer *buffer; + + mpd_inotify_callback_t callback; + void *callback_ctx; +}; + +/** + * A GQuark for GError instances. + */ +static inline GQuark +mpd_inotify_quark(void) +{ + return g_quark_from_static_string("inotify"); +} + +static gboolean +mpd_inotify_in_event(G_GNUC_UNUSED GIOChannel *_source, + G_GNUC_UNUSED GIOCondition condition, + gpointer data) +{ + struct mpd_inotify_source *source = data; + void *dest; + size_t length; + ssize_t nbytes; + const struct inotify_event *event; + + dest = fifo_buffer_write(source->buffer, &length); + if (dest == NULL) + MPD_ERROR("buffer full"); + + nbytes = read(source->fd, dest, length); + if (nbytes < 0) + MPD_ERROR("failed to read from inotify: %s", + g_strerror(errno)); + if (nbytes == 0) + MPD_ERROR("end of file from inotify"); + + fifo_buffer_append(source->buffer, nbytes); + + while (true) { + const char *name; + + event = fifo_buffer_read(source->buffer, &length); + if (event == NULL || length < sizeof(*event) || + length < sizeof(*event) + event->len) + break; + + if (event->len > 0 && event->name[event->len - 1] == 0) + name = event->name; + else + name = NULL; + + source->callback(event->wd, event->mask, name, + source->callback_ctx); + fifo_buffer_consume(source->buffer, + sizeof(*event) + event->len); + } + + return true; +} + +struct mpd_inotify_source * +mpd_inotify_source_new(mpd_inotify_callback_t callback, void *callback_ctx, + GError **error_r) +{ + struct mpd_inotify_source *source = + g_new(struct mpd_inotify_source, 1); + + source->fd = inotify_init_cloexec(); + if (source->fd < 0) { + g_set_error(error_r, mpd_inotify_quark(), errno, + "inotify_init() has failed: %s", + g_strerror(errno)); + g_free(source); + return NULL; + } + + source->buffer = fifo_buffer_new(4096); + + source->channel = g_io_channel_unix_new(source->fd); + source->id = g_io_add_watch(source->channel, G_IO_IN, + mpd_inotify_in_event, source); + + source->callback = callback; + source->callback_ctx = callback_ctx; + + return source; +} + +void +mpd_inotify_source_free(struct mpd_inotify_source *source) +{ + g_source_remove(source->id); + g_io_channel_unref(source->channel); + fifo_buffer_free(source->buffer); + close(source->fd); + g_free(source); +} + +int +mpd_inotify_source_add(struct mpd_inotify_source *source, + const char *path_fs, unsigned mask, + GError **error_r) +{ + int wd = inotify_add_watch(source->fd, path_fs, mask); + if (wd < 0) + g_set_error(error_r, mpd_inotify_quark(), errno, + "inotify_add_watch() has failed: %s", + g_strerror(errno)); + + return wd; +} + +void +mpd_inotify_source_rm(struct mpd_inotify_source *source, unsigned wd) +{ + int ret = inotify_rm_watch(source->fd, wd); + if (ret < 0 && errno != EINVAL) + g_warning("inotify_rm_watch() has failed: %s", + g_strerror(errno)); + + /* EINVAL may happen here when the file has been deleted; the + kernel seems to auto-unregister deleted files */ +} diff --git a/src/inotify_source.h b/src/inotify_source.h new file mode 100644 index 000000000..e78b92c0f --- /dev/null +++ b/src/inotify_source.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INOTIFY_SOURCE_H +#define MPD_INOTIFY_SOURCE_H + +#include <glib.h> + +typedef void (*mpd_inotify_callback_t)(int wd, unsigned mask, + const char *name, void *ctx); + +struct mpd_inotify_source; + +/** + * Creates a new inotify source and registers it in the GLib main + * loop. + * + * @param a callback invoked for events received from the kernel + */ +struct mpd_inotify_source * +mpd_inotify_source_new(mpd_inotify_callback_t callback, void *callback_ctx, + GError **error_r); + +void +mpd_inotify_source_free(struct mpd_inotify_source *source); + +/** + * Adds a path to the notify list. + * + * @return a watch descriptor or -1 on error + */ +int +mpd_inotify_source_add(struct mpd_inotify_source *source, + const char *path_fs, unsigned mask, + GError **error_r); + +/** + * Removes a path from the notify list. + * + * @param wd the watch descriptor returned by mpd_inotify_source_add() + */ +void +mpd_inotify_source_rm(struct mpd_inotify_source *source, unsigned wd); + +#endif diff --git a/src/inotify_update.c b/src/inotify_update.c new file mode 100644 index 000000000..8d9657961 --- /dev/null +++ b/src/inotify_update.c @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "inotify_update.h" +#include "inotify_source.h" +#include "inotify_queue.h" +#include "database.h" +#include "mapper.h" +#include "path.h" + +#include <assert.h> +#include <sys/inotify.h> +#include <sys/stat.h> +#include <stdbool.h> +#include <string.h> +#include <dirent.h> +#include <errno.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "inotify" + +enum { + IN_MASK = IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF + |IN_MOVE|IN_MOVE_SELF +#ifdef IN_ONLYDIR + |IN_ONLYDIR +#endif +}; + +struct watch_directory { + struct watch_directory *parent; + + char *name; + + int descriptor; + + GList *children; +}; + +static struct mpd_inotify_source *inotify_source; + +static unsigned inotify_max_depth; +static struct watch_directory inotify_root; +static GTree *inotify_directories; + +static gint +compare(gconstpointer a, gconstpointer b) +{ + if (a < b) + return -1; + else if (a > b) + return 1; + else + return 0; +} + +static void +tree_add_watch_directory(struct watch_directory *directory) +{ + g_tree_insert(inotify_directories, + GINT_TO_POINTER(directory->descriptor), directory); +} + +static void +tree_remove_watch_directory(struct watch_directory *directory) +{ + G_GNUC_UNUSED + bool found = g_tree_remove(inotify_directories, + GINT_TO_POINTER(directory->descriptor)); + assert(found); +} + +static struct watch_directory * +tree_find_watch_directory(int wd) +{ + return g_tree_lookup(inotify_directories, GINT_TO_POINTER(wd)); +} + +static void +remove_watch_directory(struct watch_directory *directory) +{ + assert(directory != NULL); + + if (directory->parent == NULL) { + g_warning("music directory was removed - " + "cannot continue to watch it"); + return; + } + + assert(directory->parent->children != NULL); + + tree_remove_watch_directory(directory); + + while (directory->children != NULL) + remove_watch_directory(directory->children->data); + + directory->parent->children = + g_list_remove(directory->parent->children, directory); + + mpd_inotify_source_rm(inotify_source, directory->descriptor); + g_free(directory->name); + g_slice_free(struct watch_directory, directory); +} + +static char * +watch_directory_get_uri_fs(const struct watch_directory *directory) +{ + char *parent_uri, *uri; + + if (directory->parent == NULL) + return NULL; + + parent_uri = watch_directory_get_uri_fs(directory->parent); + if (parent_uri == NULL) + return g_strdup(directory->name); + + uri = g_strconcat(parent_uri, "/", directory->name, NULL); + g_free(parent_uri); + + return uri; +} + +/* we don't look at "." / ".." nor files with newlines in their name */ +static bool skip_path(const char *path) +{ + return (path[0] == '.' && path[1] == 0) || + (path[0] == '.' && path[1] == '.' && path[2] == 0) || + strchr(path, '\n') != NULL; +} + +static void +recursive_watch_subdirectories(struct watch_directory *directory, + const char *path_fs, unsigned depth) +{ + GError *error = NULL; + DIR *dir; + struct dirent *ent; + + assert(directory != NULL); + assert(depth <= inotify_max_depth); + assert(path_fs != NULL); + + ++depth; + + if (depth > inotify_max_depth) + return; + + dir = opendir(path_fs); + if (dir == NULL) { + g_warning("Failed to open directory %s: %s", + path_fs, g_strerror(errno)); + return; + } + + while ((ent = readdir(dir))) { + char *child_path_fs; + struct stat st; + int ret; + struct watch_directory *child; + + if (skip_path(ent->d_name)) + continue; + + child_path_fs = g_strconcat(path_fs, "/", ent->d_name, NULL); + ret = stat(child_path_fs, &st); + if (ret < 0) { + g_warning("Failed to stat %s: %s", + child_path_fs, g_strerror(errno)); + g_free(child_path_fs); + continue; + } + + if (!S_ISDIR(st.st_mode)) { + g_free(child_path_fs); + continue; + } + + ret = mpd_inotify_source_add(inotify_source, child_path_fs, + IN_MASK, &error); + if (ret < 0) { + g_warning("Failed to register %s: %s", + child_path_fs, error->message); + g_error_free(error); + error = NULL; + g_free(child_path_fs); + continue; + } + + child = tree_find_watch_directory(ret); + if (child != NULL) { + /* already being watched */ + g_free(child_path_fs); + continue; + } + + child = g_slice_new(struct watch_directory); + child->parent = directory; + child->name = g_strdup(ent->d_name); + child->descriptor = ret; + child->children = NULL; + + directory->children = g_list_prepend(directory->children, + child); + + tree_add_watch_directory(child); + + recursive_watch_subdirectories(child, child_path_fs, depth); + g_free(child_path_fs); + } + + closedir(dir); +} + +G_GNUC_PURE +static unsigned +watch_directory_depth(const struct watch_directory *d) +{ + assert(d != NULL); + + unsigned depth = 0; + while ((d = d->parent) != NULL) + ++depth; + + return depth; +} + +static void +mpd_inotify_callback(int wd, unsigned mask, + G_GNUC_UNUSED const char *name, G_GNUC_UNUSED void *ctx) +{ + struct watch_directory *directory; + char *uri_fs; + + /*g_debug("wd=%d mask=0x%x name='%s'", wd, mask, name);*/ + + directory = tree_find_watch_directory(wd); + if (directory == NULL) + return; + + uri_fs = watch_directory_get_uri_fs(directory); + + if ((mask & (IN_DELETE_SELF|IN_MOVE_SELF)) != 0) { + g_free(uri_fs); + remove_watch_directory(directory); + return; + } + + if ((mask & (IN_ATTRIB|IN_CREATE|IN_MOVE)) != 0 && + (mask & IN_ISDIR) != 0) { + /* a sub directory was changed: register those in + inotify */ + char *root = map_directory_fs(db_get_root()); + char *path_fs; + + if (uri_fs != NULL) { + path_fs = g_strconcat(root, "/", uri_fs, NULL); + g_free(root); + } else + path_fs = root; + + recursive_watch_subdirectories(directory, path_fs, + watch_directory_depth(directory)); + g_free(path_fs); + } + + if ((mask & (IN_CLOSE_WRITE|IN_MOVE|IN_DELETE)) != 0 || + /* at the maximum depth, we watch out for newly created + directories */ + (watch_directory_depth(directory) == inotify_max_depth && + (mask & (IN_CREATE|IN_ISDIR)) == (IN_CREATE|IN_ISDIR))) { + /* a file was changed, or a directory was + moved/deleted: queue a database update */ + char *uri_utf8 = uri_fs != NULL + ? fs_charset_to_utf8(uri_fs) + : g_strdup(""); + + if (uri_utf8 != NULL) + /* this function will take care of freeing + uri_utf8 */ + mpd_inotify_enqueue(uri_utf8); + } + + g_free(uri_fs); +} + +void +mpd_inotify_init(unsigned max_depth) +{ + struct directory *root; + char *path; + GError *error = NULL; + + g_debug("initializing inotify"); + + root = db_get_root(); + if (root == NULL) { + g_debug("no music directory configured"); + return; + } + + path = map_directory_fs(root); + if (path == NULL) { + g_warning("mapper has failed"); + return; + } + + inotify_source = mpd_inotify_source_new(mpd_inotify_callback, NULL, + &error); + if (inotify_source == NULL) { + g_warning("%s", error->message); + g_error_free(error); + g_free(path); + return; + } + + inotify_max_depth = max_depth; + + inotify_root.name = path; + inotify_root.descriptor = mpd_inotify_source_add(inotify_source, path, + IN_MASK, &error); + if (inotify_root.descriptor < 0) { + g_warning("%s", error->message); + g_error_free(error); + mpd_inotify_source_free(inotify_source); + inotify_source = NULL; + g_free(path); + return; + } + + inotify_directories = g_tree_new(compare); + tree_add_watch_directory(&inotify_root); + + recursive_watch_subdirectories(&inotify_root, path, 0); + + mpd_inotify_queue_init(); + + g_debug("watching music directory"); +} + +static gboolean +free_watch_directory(G_GNUC_UNUSED gpointer key, gpointer value, + G_GNUC_UNUSED gpointer data) +{ + struct watch_directory *directory = value; + + g_free(directory->name); + g_list_free(directory->children); + + if (directory != &inotify_root) + g_slice_free(struct watch_directory, directory); + + return false; +} + +void +mpd_inotify_finish(void) +{ + if (inotify_source == NULL) + return; + + mpd_inotify_queue_finish(); + mpd_inotify_source_free(inotify_source); + + g_tree_foreach(inotify_directories, free_watch_directory, NULL); + g_tree_destroy(inotify_directories); +} diff --git a/src/inotify_update.h b/src/inotify_update.h new file mode 100644 index 000000000..92b4e0cc6 --- /dev/null +++ b/src/inotify_update.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INOTIFY_UPDATE_H +#define MPD_INOTIFY_UPDATE_H + +#include "check.h" + +#ifdef HAVE_INOTIFY_INIT + +void +mpd_inotify_init(unsigned max_depth); + +void +mpd_inotify_finish(void); + +#else /* !HAVE_INOTIFY_INIT */ + +static inline void +mpd_inotify_init(G_GNUC_UNUSED unsigned max_depth) +{ +} + +static inline void +mpd_inotify_finish(void) +{ +} + +#endif /* !HAVE_INOTIFY_INIT */ + +#endif diff --git a/src/input/archive_input_plugin.c b/src/input/archive_input_plugin.c index 8e897f0c2..97e4836ff 100644 --- a/src/input/archive_input_plugin.c +++ b/src/input/archive_input_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "input/archive_input_plugin.h" #include "archive_api.h" #include "archive_list.h" @@ -32,23 +33,23 @@ * parent_stream so tar plugin fetches file data from gzip * plugin and gzip fetches file from disk */ -static bool -input_archive_open(struct input_stream *is, const char *pathname) +static struct input_stream * +input_archive_open(const char *pathname, GError **error_r) { const struct archive_plugin *arplug; struct archive_file *file; char *archive, *filename, *suffix, *pname; - bool opened; + struct input_stream *is; - if (pathname[0] != '/') - return false; + if (!g_path_is_absolute(pathname)) + return NULL; pname = g_strdup(pathname); // archive_lookup will modify pname when true is returned if (!archive_lookup(pname, &archive, &filename, &suffix)) { g_debug("not an archive, lookup %s failed\n", pname); g_free(pname); - return false; + return NULL; } //check which archive plugin to use (by ext) @@ -56,22 +57,19 @@ input_archive_open(struct input_stream *is, const char *pathname) if (!arplug) { g_warning("can't handle archive %s\n",archive); g_free(pname); - return false; + return NULL; } - file = arplug->open(archive); + file = archive_file_open(arplug, archive, error_r); + if (file == NULL) + return NULL; //setup fileops - opened = arplug->open_stream(file, is, filename); - - if (!opened) { - g_warning("open inarchive file %s failed\n\n",filename); - arplug->close(file); - } else { - is->ready = true; - } + is = archive_file_open_stream(file, filename, error_r); + archive_file_close(file); g_free(pname); - return opened; + + return is; } const struct input_plugin input_plugin_archive = { diff --git a/src/input/archive_input_plugin.h b/src/input/archive_input_plugin.h index 482392a01..20568cfbe 100644 --- a/src/input/archive_input_plugin.h +++ b/src/input/archive_input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/input/curl_input_plugin.c b/src/input/curl_input_plugin.c index 3893aef1c..ae645bddf 100644 --- a/src/input/curl_input_plugin.c +++ b/src/input/curl_input_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,12 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "input/curl_input_plugin.h" #include "input_plugin.h" #include "conf.h" -#include "config.h" #include "tag.h" #include "icy_metadata.h" +#include "glib_compat.h" #include <assert.h> @@ -56,6 +57,8 @@ struct buffer { }; struct input_curl { + struct input_stream base; + /* some buffers which were passed to libcurl, which we have too free */ char *url, *range; @@ -96,8 +99,15 @@ static struct curl_slist *http_200_aliases; static const char *proxy, *proxy_user, *proxy_password; static unsigned proxy_port; +static inline GQuark +curl_quark(void) +{ + return g_quark_from_static_string("curl"); +} + static bool -input_curl_init(const struct config_param *param) +input_curl_init(const struct config_param *param, + G_GNUC_UNUSED GError **error_r) { CURLcode code = curl_global_init(CURL_GLOBAL_ALL); if (code != CURLE_OK) { @@ -144,11 +154,6 @@ buffer_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) g_free(data); } -/* g_queue_clear() was introduced in GLib 2.14 */ -#if !GLIB_CHECK_VERSION(2,14,0) -#define g_queue_clear(q) do { g_queue_free(q); q = g_queue_new(); } while (0) -#endif - /** * Frees the current "libcurl easy" handle, and everything associated * with it. @@ -176,10 +181,8 @@ input_curl_easy_free(struct input_curl *c) * Frees this stream (but not the input_stream struct itself). */ static void -input_curl_free(struct input_stream *is) +input_curl_free(struct input_curl *c) { - struct input_curl *c = is->data; - if (c->tag != NULL) tag_free(c->tag); g_free(c->meta_name); @@ -192,13 +195,14 @@ input_curl_free(struct input_stream *is) g_queue_free(c->buffers); g_free(c->url); + input_stream_deinit(&c->base); g_free(c); } static struct tag * input_curl_tag(struct input_stream *is) { - struct input_curl *c = is->data; + struct input_curl *c = (struct input_curl *)is; struct tag *tag = c->tag; c->tag = NULL; @@ -206,9 +210,8 @@ input_curl_tag(struct input_stream *is) } static bool -input_curl_multi_info_read(struct input_stream *is) +input_curl_multi_info_read(struct input_curl *c, GError **error_r) { - struct input_curl *c = is->data; CURLMsg *msg; int msgs_in_queue; @@ -216,11 +219,12 @@ input_curl_multi_info_read(struct input_stream *is) &msgs_in_queue)) != NULL) { if (msg->msg == CURLMSG_DONE) { c->eof = true; - is->ready = true; + c->base.ready = true; if (msg->data.result != CURLE_OK) { - g_warning("curl failed: %s\n", c->error); - is->error = -1; + g_set_error(error_r, curl_quark(), + msg->data.result, + "curl failed: %s", c->error); return false; } } @@ -236,7 +240,7 @@ input_curl_multi_info_read(struct input_stream *is) * available */ static int -input_curl_select(struct input_curl *c) +input_curl_select(struct input_curl *c, GError **error_r) { fd_set rfds, wfds, efds; int max_fd, ret; @@ -254,8 +258,9 @@ input_curl_select(struct input_curl *c) mcode = curl_multi_fdset(c->multi, &rfds, &wfds, &efds, &max_fd); if (mcode != CURLM_OK) { - g_warning("curl_multi_fdset() failed: %s\n", - curl_multi_strerror(mcode)); + g_set_error(error_r, curl_quark(), mcode, + "curl_multi_fdset() failed: %s", + curl_multi_strerror(mcode)); return -1; } @@ -279,15 +284,17 @@ input_curl_select(struct input_curl *c) ret = select(max_fd + 1, &rfds, &wfds, &efds, &timeout); if (ret < 0) - g_warning("select() failed: %s\n", strerror(errno)); + g_set_error(error_r, g_quark_from_static_string("errno"), + errno, + "select() failed: %s\n", g_strerror(errno)); return ret; } static bool -fill_buffer(struct input_stream *is) +fill_buffer(struct input_stream *is, GError **error_r) { - struct input_curl *c = is->data; + struct input_curl *c = (struct input_curl *)is; CURLMcode mcode = CURLM_CALL_MULTI_PERFORM; while (!c->eof && g_queue_is_empty(c->buffers)) { @@ -297,7 +304,7 @@ fill_buffer(struct input_stream *is) if (mcode != CURLM_CALL_MULTI_PERFORM) { /* if we're still here, there is no input yet - wait for input */ - int ret = input_curl_select(c); + int ret = input_curl_select(c, error_r); if (ret <= 0) /* no data yet or error */ return false; @@ -305,14 +312,15 @@ fill_buffer(struct input_stream *is) mcode = curl_multi_perform(c->multi, &running_handles); if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) { - g_warning("curl_multi_perform() failed: %s\n", - curl_multi_strerror(mcode)); + g_set_error(error_r, curl_quark(), mcode, + "curl_multi_perform() failed: %s", + curl_multi_strerror(mcode)); c->eof = true; is->ready = true; return false; } - bret = input_curl_multi_info_read(is); + bret = input_curl_multi_info_read(c, error_r); if (!bret) return false; } @@ -404,16 +412,17 @@ copy_icy_tag(struct input_curl *c) if (c->tag != NULL) tag_free(c->tag); - if (c->meta_name != NULL && !tag_has_type(tag, TAG_ITEM_NAME)) - tag_add_item(tag, TAG_ITEM_NAME, c->meta_name); + if (c->meta_name != NULL && !tag_has_type(tag, TAG_NAME)) + tag_add_item(tag, TAG_NAME, c->meta_name); c->tag = tag; } static size_t -input_curl_read(struct input_stream *is, void *ptr, size_t size) +input_curl_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) { - struct input_curl *c = is->data; + struct input_curl *c = (struct input_curl *)is; bool success; size_t nbytes = 0; char *dest = ptr; @@ -421,7 +430,7 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size) do { /* fill the buffer */ - success = fill_buffer(is); + success = fill_buffer(is, error_r); if (!success) return 0; @@ -439,7 +448,7 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size) if (icy_defined(&c->icy_metadata)) copy_icy_tag(c); - is->offset += (off_t)nbytes; + is->offset += (goffset)nbytes; return nbytes; } @@ -447,21 +456,23 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size) static void input_curl_close(struct input_stream *is) { - input_curl_free(is); + struct input_curl *c = (struct input_curl *)is; + + input_curl_free(c); } static bool input_curl_eof(G_GNUC_UNUSED struct input_stream *is) { - struct input_curl *c = is->data; + struct input_curl *c = (struct input_curl *)is; return c->eof && g_queue_is_empty(c->buffers); } static int -input_curl_buffer(struct input_stream *is) +input_curl_buffer(struct input_stream *is, GError **error_r) { - struct input_curl *c = is->data; + struct input_curl *c = (struct input_curl *)is; CURLMcode mcode; int running_handles; bool ret; @@ -472,7 +483,8 @@ input_curl_buffer(struct input_stream *is) /* not ready yet means the caller is waiting in a busy loop; relax that by calling select() on the socket */ - input_curl_select(c); + if (input_curl_select(c, error_r) < 0) + return -1; do { mcode = curl_multi_perform(c->multi, &running_handles); @@ -480,14 +492,15 @@ input_curl_buffer(struct input_stream *is) g_queue_is_empty(c->buffers)); if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) { - g_warning("curl_multi_perform() failed: %s\n", - curl_multi_strerror(mcode)); + g_set_error(error_r, curl_quark(), mcode, + "curl_multi_perform() failed: %s", + curl_multi_strerror(mcode)); c->eof = true; is->ready = true; return -1; } - ret = input_curl_multi_info_read(is); + ret = input_curl_multi_info_read(c, error_r); if (!ret) return -1; @@ -498,8 +511,7 @@ input_curl_buffer(struct input_stream *is) static size_t input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) { - struct input_stream *is = stream; - struct input_curl *c = is->data; + struct input_curl *c = (struct input_curl *)stream; const char *header = ptr, *end, *value; char name[64]; @@ -528,7 +540,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) if (g_ascii_strcasecmp(name, "accept-ranges") == 0) { /* a stream with icy-metadata is not seekable */ if (!icy_defined(&c->icy_metadata)) - is->seekable = true; + c->base.seekable = true; } else if (g_ascii_strcasecmp(name, "content-length") == 0) { char buffer[64]; @@ -538,10 +550,10 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) memcpy(buffer, value, end - value); buffer[end - value] = 0; - is->size = is->offset + g_ascii_strtoull(buffer, NULL, 10); + c->base.size = c->base.offset + g_ascii_strtoull(buffer, NULL, 10); } else if (g_ascii_strcasecmp(name, "content-type") == 0) { - g_free(is->mime); - is->mime = g_strndup(value, end - value); + g_free(c->base.mime); + c->base.mime = g_strndup(value, end - value); } else if (g_ascii_strcasecmp(name, "icy-name") == 0 || g_ascii_strcasecmp(name, "ice-name") == 0 || g_ascii_strcasecmp(name, "x-audiocast-name") == 0) { @@ -552,7 +564,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) tag_free(c->tag); c->tag = tag_new(); - tag_add_item(c->tag, TAG_ITEM_NAME, c->meta_name); + tag_add_item(c->tag, TAG_NAME, c->meta_name); } else if (g_ascii_strcasecmp(name, "icy-metaint") == 0) { char buffer[64]; size_t icy_metaint; @@ -572,7 +584,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) /* a stream with icy-metadata is not seekable */ - is->seekable = false; + c->base.seekable = false; } } @@ -583,8 +595,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) static size_t input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream) { - struct input_stream *is = stream; - struct input_curl *c = is->data; + struct input_curl *c = (struct input_curl *)stream; struct buffer *buffer; size *= nmemb; @@ -598,15 +609,14 @@ input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream) g_queue_push_tail(c->buffers, buffer); c->buffered = true; - is->ready = true; + c->base.ready = true; return size; } static bool -input_curl_easy_init(struct input_stream *is) +input_curl_easy_init(struct input_curl *c, GError **error_r) { - struct input_curl *c = is->data; CURLcode code; CURLMcode mcode; @@ -614,22 +624,27 @@ input_curl_easy_init(struct input_stream *is) c->easy = curl_easy_init(); if (c->easy == NULL) { - g_warning("curl_easy_init() failed\n"); + g_set_error(error_r, curl_quark(), 0, + "curl_easy_init() failed"); return false; } mcode = curl_multi_add_handle(c->multi, c->easy); - if (mcode != CURLM_OK) + if (mcode != CURLM_OK) { + g_set_error(error_r, curl_quark(), mcode, + "curl_multi_add_handle() failed: %s", + curl_multi_strerror(mcode)); return false; + } curl_easy_setopt(c->easy, CURLOPT_USERAGENT, "Music Player Daemon " VERSION); curl_easy_setopt(c->easy, CURLOPT_HEADERFUNCTION, input_curl_headerfunction); - curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, is); + curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, c); curl_easy_setopt(c->easy, CURLOPT_WRITEFUNCTION, input_curl_writefunction); - curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, is); + curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, c); curl_easy_setopt(c->easy, CURLOPT_HTTP200ALIASES, http_200_aliases); curl_easy_setopt(c->easy, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5); @@ -650,8 +665,12 @@ input_curl_easy_init(struct input_stream *is) } code = curl_easy_setopt(c->easy, CURLOPT_URL, c->url); - if (code != CURLE_OK) + if (code != CURLE_OK) { + g_set_error(error_r, curl_quark(), code, + "curl_easy_setopt() failed: %s", + curl_easy_strerror(code)); return false; + } c->request_headers = NULL; c->request_headers = curl_slist_append(c->request_headers, @@ -664,9 +683,9 @@ input_curl_easy_init(struct input_stream *is) void input_curl_reinit(struct input_stream *is) { - struct input_curl *c = is->data; + struct input_curl *c = (struct input_curl *)is; - assert(is->plugin == &input_plugin_curl); + assert(c->base.plugin == &input_plugin_curl); assert(c->easy != NULL); curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, is); @@ -674,7 +693,7 @@ input_curl_reinit(struct input_stream *is) } static bool -input_curl_send_request(struct input_curl *c) +input_curl_send_request(struct input_curl *c, GError **error_r) { CURLMcode mcode; int running_handles; @@ -684,8 +703,9 @@ input_curl_send_request(struct input_curl *c) } while (mcode == CURLM_CALL_MULTI_PERFORM); if (mcode != CURLM_OK) { - g_warning("curl_multi_perform() failed: %s\n", - curl_multi_strerror(mcode)); + g_set_error(error_r, curl_quark(), mcode, + "curl_multi_perform() failed: %s", + curl_multi_strerror(mcode)); return false; } @@ -693,9 +713,10 @@ input_curl_send_request(struct input_curl *c) } static bool -input_curl_seek(struct input_stream *is, off_t offset, int whence) +input_curl_seek(struct input_stream *is, goffset offset, int whence, + GError **error_r) { - struct input_curl *c = is->data; + struct input_curl *c = (struct input_curl *)is; bool ret; assert(is->ready); @@ -741,7 +762,7 @@ input_curl_seek(struct input_stream *is, off_t offset, int whence) buffer = (struct buffer *)g_queue_pop_head(c->buffers); length = buffer->size - buffer->consumed; - if (offset - is->offset < (off_t)length) + if (offset - is->offset < (goffset)length) length = offset - is->offset; buffer = consume_buffer(buffer, length); @@ -767,7 +788,7 @@ input_curl_seek(struct input_stream *is, off_t offset, int whence) return true; } - ret = input_curl_easy_init(is); + ret = input_curl_easy_init(c, error_r); if (!ret) return false; @@ -778,59 +799,58 @@ input_curl_seek(struct input_stream *is, off_t offset, int whence) curl_easy_setopt(c->easy, CURLOPT_RANGE, c->range); } - ret = input_curl_send_request(c); + ret = input_curl_send_request(c, error_r); if (!ret) return false; - return input_curl_multi_info_read(is); + return input_curl_multi_info_read(c, error_r); } -static bool -input_curl_open(struct input_stream *is, const char *url) +static struct input_stream * +input_curl_open(const char *url, GError **error_r) { struct input_curl *c; bool ret; if (strncmp(url, "http://", 7) != 0) - return false; + return NULL; c = g_new0(struct input_curl, 1); + input_stream_init(&c->base, &input_plugin_curl, url); + c->url = g_strdup(url); c->buffers = g_queue_new(); - is->plugin = &input_plugin_curl; - is->data = c; - c->multi = curl_multi_init(); if (c->multi == NULL) { - g_warning("curl_multi_init() failed\n"); - - input_curl_free(is); - return false; + g_set_error(error_r, curl_quark(), 0, + "curl_multi_init() failed"); + input_curl_free(c); + return NULL; } icy_clear(&c->icy_metadata); c->tag = NULL; - ret = input_curl_easy_init(is); + ret = input_curl_easy_init(c, error_r); if (!ret) { - input_curl_free(is); - return false; + input_curl_free(c); + return NULL; } - ret = input_curl_send_request(c); + ret = input_curl_send_request(c, error_r); if (!ret) { - input_curl_free(is); - return false; + input_curl_free(c); + return NULL; } - ret = input_curl_multi_info_read(is); + ret = input_curl_multi_info_read(c, error_r); if (!ret) { - input_curl_free(is); - return false; + input_curl_free(c); + return NULL; } - return true; + return &c->base; } const struct input_plugin input_plugin_curl = { diff --git a/src/input/curl_input_plugin.h b/src/input/curl_input_plugin.h index 63ac0dc23..be7db4e26 100644 --- a/src/input/curl_input_plugin.h +++ b/src/input/curl_input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/input/ffmpeg_input_plugin.c b/src/input/ffmpeg_input_plugin.c new file mode 100644 index 000000000..0a6be29bc --- /dev/null +++ b/src/input/ffmpeg_input_plugin.c @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "input/ffmpeg_input_plugin.h" +#include "input_plugin.h" + +#ifdef OLD_FFMPEG_INCLUDES +#include <avio.h> +#include <avformat.h> +#else +#include <libavformat/avio.h> +#include <libavformat/avformat.h> +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "input_ffmpeg" + +struct input_ffmpeg { + struct input_stream base; + + URLContext *h; + + bool eof; +}; + +static inline GQuark +ffmpeg_quark(void) +{ + return g_quark_from_static_string("ffmpeg"); +} + +static bool +input_ffmpeg_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + av_register_all(); + +#if LIBAVFORMAT_VERSION_MAJOR >= 52 + /* disable this plugin if there's no registered protocol */ + if (av_protocol_next(NULL) == NULL) { + g_set_error(error_r, ffmpeg_quark(), 0, + "No protocol"); + return false; + } +#endif + + return true; +} + +static struct input_stream * +input_ffmpeg_open(const char *uri, GError **error_r) +{ + struct input_ffmpeg *i; + + if (!g_str_has_prefix(uri, "gopher://") && + !g_str_has_prefix(uri, "rtp://") && + !g_str_has_prefix(uri, "rtsp://") && + !g_str_has_prefix(uri, "rtmp://") && + !g_str_has_prefix(uri, "rtmpt://") && + !g_str_has_prefix(uri, "rtmps://")) + return NULL; + + i = g_new(struct input_ffmpeg, 1); + input_stream_init(&i->base, &input_plugin_ffmpeg, uri); + + int ret = url_open(&i->h, uri, URL_RDONLY); + if (ret != 0) { + g_free(i); + g_set_error(error_r, ffmpeg_quark(), ret, + "libavformat failed to open the URI"); + return NULL; + } + + i->eof = false; + + i->base.ready = true; + i->base.seekable = !i->h->is_streamed; + i->base.size = url_filesize(i->h); + + /* hack to make MPD select the "ffmpeg" decoder plugin - since + avio.h doesn't tell us the MIME type of the resource, we + can't select a decoder plugin, but the "ffmpeg" plugin is + quite good at auto-detection */ + i->base.mime = g_strdup("audio/x-mpd-ffmpeg"); + + return &i->base; +} + +static size_t +input_ffmpeg_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) +{ + struct input_ffmpeg *i = (struct input_ffmpeg *)is; + + int ret = url_read(i->h, ptr, size); + if (ret <= 0) { + if (ret < 0) + g_set_error(error_r, ffmpeg_quark(), 0, + "url_read() failed"); + + i->eof = true; + return false; + } + + is->offset += ret; + return (size_t)ret; +} + +static void +input_ffmpeg_close(struct input_stream *is) +{ + struct input_ffmpeg *i = (struct input_ffmpeg *)is; + + url_close(i->h); + input_stream_deinit(&i->base); + g_free(i); +} + +static bool +input_ffmpeg_eof(struct input_stream *is) +{ + struct input_ffmpeg *i = (struct input_ffmpeg *)is; + + return i->eof; +} + +static bool +input_ffmpeg_seek(struct input_stream *is, goffset offset, int whence, + G_GNUC_UNUSED GError **error_r) +{ + struct input_ffmpeg *i = (struct input_ffmpeg *)is; + int64_t ret = url_seek(i->h, offset, whence); + + if (ret >= 0) { + i->eof = false; + return true; + } else { + g_set_error(error_r, ffmpeg_quark(), 0, "url_seek() failed"); + return false; + } +} + +const struct input_plugin input_plugin_ffmpeg = { + .name = "ffmpeg", + .init = input_ffmpeg_init, + .open = input_ffmpeg_open, + .close = input_ffmpeg_close, + .read = input_ffmpeg_read, + .eof = input_ffmpeg_eof, + .seek = input_ffmpeg_seek, +}; diff --git a/src/input/ffmpeg_input_plugin.h b/src/input/ffmpeg_input_plugin.h new file mode 100644 index 000000000..ff87064be --- /dev/null +++ b/src/input/ffmpeg_input_plugin.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2010 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_FFMPEG_INPUT_PLUGIN_H +#define MPD_FFMPEG_INPUT_PLUGIN_H + +/** + * An input plugin based on libavformat's "avio" library. + */ +extern const struct input_plugin input_plugin_ffmpeg; + +#endif diff --git a/src/input/file_input_plugin.c b/src/input/file_input_plugin.c index bda1777ac..3646c656e 100644 --- a/src/input/file_input_plugin.c +++ b/src/input/file_input_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,11 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" /* must be first for large file support */ #include "input/file_input_plugin.h" #include "input_plugin.h" +#include "fd_util.h" +#include "open.h" #include <sys/stat.h> -#include <fcntl.h> #include <unistd.h> #include <errno.h> #include <string.h> @@ -30,60 +32,79 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "input_file" -static bool -input_file_open(struct input_stream *is, const char *filename) +struct file_input_stream { + struct input_stream base; + + int fd; +}; + +static inline GQuark +file_quark(void) +{ + return g_quark_from_static_string("file"); +} + +static struct input_stream * +input_file_open(const char *filename, GError **error_r) { int fd, ret; struct stat st; + struct file_input_stream *fis; - if (filename[0] != '/') + if (!g_path_is_absolute(filename)) return false; - fd = open(filename, O_RDONLY); + fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0); if (fd < 0) { - is->error = errno; - g_debug("Failed to open \"%s\": %s", - filename, g_strerror(errno)); + if (errno != ENOENT && errno != ENOTDIR) + g_set_error(error_r, file_quark(), errno, + "Failed to open \"%s\": %s", + filename, g_strerror(errno)); return false; } - is->seekable = true; - ret = fstat(fd, &st); if (ret < 0) { - is->error = errno; + g_set_error(error_r, file_quark(), errno, + "Failed to stat \"%s\": %s", + filename, g_strerror(errno)); close(fd); return false; } if (!S_ISREG(st.st_mode)) { - g_debug("Not a regular file: %s", filename); - is->error = EINVAL; + g_set_error(error_r, file_quark(), 0, + "Not a regular file: %s", filename); close(fd); return false; } - is->size = st.st_size; - #ifdef POSIX_FADV_SEQUENTIAL - posix_fadvise(fd, (off_t)0, is->size, POSIX_FADV_SEQUENTIAL); + posix_fadvise(fd, (off_t)0, st.st_size, POSIX_FADV_SEQUENTIAL); #endif - is->plugin = &input_plugin_file; - is->data = GINT_TO_POINTER(fd); - is->ready = true; + fis = g_new(struct file_input_stream, 1); + input_stream_init(&fis->base, &input_plugin_file, filename); - return true; + fis->base.size = st.st_size; + fis->base.seekable = true; + fis->base.ready = true; + + fis->fd = fd; + + return &fis->base; } static bool -input_file_seek(struct input_stream *is, off_t offset, int whence) +input_file_seek(struct input_stream *is, goffset offset, int whence, + GError **error_r) { - int fd = GPOINTER_TO_INT(is->data); + struct file_input_stream *fis = (struct file_input_stream *)is; - offset = lseek(fd, offset, whence); + offset = (goffset)lseek(fis->fd, (off_t)offset, whence); if (offset < 0) { - is->error = errno; + g_set_error(error_r, file_quark(), errno, + "Failed to seek: %s", g_strerror(errno)); return false; } @@ -92,16 +113,16 @@ input_file_seek(struct input_stream *is, off_t offset, int whence) } static size_t -input_file_read(struct input_stream *is, void *ptr, size_t size) +input_file_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) { - int fd = GPOINTER_TO_INT(is->data); + struct file_input_stream *fis = (struct file_input_stream *)is; ssize_t nbytes; - nbytes = read(fd, ptr, size); + nbytes = read(fis->fd, ptr, size); if (nbytes < 0) { - is->error = errno; - g_debug("input_file_read: error reading: %s\n", - strerror(is->error)); + g_set_error(error_r, file_quark(), errno, + "Failed to read: %s", g_strerror(errno)); return 0; } @@ -112,9 +133,11 @@ input_file_read(struct input_stream *is, void *ptr, size_t size) static void input_file_close(struct input_stream *is) { - int fd = GPOINTER_TO_INT(is->data); + struct file_input_stream *fis = (struct file_input_stream *)is; - close(fd); + close(fis->fd); + input_stream_deinit(&fis->base); + g_free(fis); } static bool diff --git a/src/input/file_input_plugin.h b/src/input/file_input_plugin.h index d7610f5d7..40340e8bd 100644 --- a/src/input/file_input_plugin.h +++ b/src/input/file_input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/input/lastfm_input_plugin.c b/src/input/lastfm_input_plugin.c deleted file mode 100644 index 8e13a60a9..000000000 --- a/src/input/lastfm_input_plugin.c +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (C) 2003-2009 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "input/lastfm_input_plugin.h" -#include "input/curl_input_plugin.h" -#include "input_plugin.h" -#include "conf.h" - -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "input_lastfm" - -static const char *lastfm_user, *lastfm_password; - -static bool -lastfm_input_init(const struct config_param *param) -{ - lastfm_user = config_get_block_string(param, "user", NULL); - lastfm_password = config_get_block_string(param, "password", NULL); - - return lastfm_user != NULL && lastfm_password != NULL; -} - -static char * -lastfm_get(const char *url) -{ - struct input_stream input_stream; - bool success; - int ret; - char buffer[4096]; - size_t length = 0, nbytes; - - success = input_stream_open(&input_stream, url); - if (!success) - return NULL; - - while (!input_stream.ready) { - ret = input_stream_buffer(&input_stream); - if (ret < 0) { - input_stream_close(&input_stream); - return NULL; - } - } - - do { - nbytes = input_stream_read(&input_stream, buffer + length, - sizeof(buffer) - length); - if (nbytes == 0) { - if (input_stream_eof(&input_stream)) - break; - - /* I/O error */ - input_stream_close(&input_stream); - return NULL; - } - - length += nbytes; - } while (length < sizeof(buffer)); - - input_stream_close(&input_stream); - return g_strndup(buffer, length); -} - -static char * -lastfm_find(const char *response, const char *name) -{ - size_t name_length = strlen(name); - - while (true) { - const char *eol = strchr(response, '\n'); - if (eol == NULL) - return NULL; - - if (strncmp(response, name, name_length) == 0 && - response[name_length] == '=') { - response += name_length + 1; - return g_strndup(response, eol - response); - } - - response = eol + 1; - } -} - -static bool -lastfm_input_open(struct input_stream *is, const char *url) -{ - char *md5, *p, *q, *response, *session, *stream_url; - bool success; - - if (strncmp(url, "lastfm://", 9) != 0) - return false; - - /* handshake */ - -#if GLIB_CHECK_VERSION(2,16,0) - q = g_uri_escape_string(lastfm_user, NULL, false); -#else - q = g_strdup(lastfm_user); -#endif - -#if GLIB_CHECK_VERSION(2,16,0) - if (strlen(lastfm_password) != 32) - md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5, - lastfm_password, - strlen(lastfm_password)); - else -#endif - md5 = g_strdup(lastfm_password); - - p = g_strconcat("http://ws.audioscrobbler.com/radio/handshake.php?" - "version=1.1.1&platform=linux&" - "username=", q, "&" - "passwordmd5=", md5, "&debug=0&partner=", NULL); - g_free(q); - g_free(md5); - - response = lastfm_get(p); - g_free(p); - if (response == NULL) - return false; - - /* extract session id from response */ - - session = lastfm_find(response, "session"); - stream_url = lastfm_find(response, "stream_url"); - g_free(response); - if (session == NULL || stream_url == NULL) { - g_free(session); - g_free(stream_url); - return false; - } - -#if GLIB_CHECK_VERSION(2,16,0) - q = g_uri_escape_string(session, NULL, false); - g_free(session); - session = q; -#endif - - /* "adjust" last.fm radio */ - - if (strlen(url) > 9) { - char *escaped_url; - -#if GLIB_CHECK_VERSION(2,16,0) - escaped_url = g_uri_escape_string(url, NULL, false); -#else - escaped_url = g_strdup(url); -#endif - - p = g_strconcat("http://ws.audioscrobbler.com/radio/adjust.php?" - "session=", session, "&url=", escaped_url, "&debug=0", - NULL); - g_free(escaped_url); - - response = lastfm_get(p); - g_free(response); - g_free(p); - - if (response == NULL) { - g_free(session); - g_free(stream_url); - return false; - } - } - - /* load the last.fm playlist */ - - p = g_strconcat("http://ws.audioscrobbler.com/radio/xspf.php?" - "sk=", session, "&discovery=0&desktop=1.5.1.31879", - NULL); - g_free(session); - - response = lastfm_get(p); - g_free(p); - - if (response == NULL) { - g_free(stream_url); - return false; - } - - p = strstr(response, "<location>"); - if (p == NULL) { - g_free(response); - g_free(stream_url); - return false; - } - - p += 10; - q = strchr(p, '<'); - - if (q == NULL) { - g_free(response); - g_free(stream_url); - return false; - } - - g_free(stream_url); - stream_url = g_strndup(p, q - p); - g_free(response); - - /* now really open the last.fm radio stream */ - - success = input_stream_open(is, stream_url); - g_free(stream_url); - return success; -} - -const struct input_plugin lastfm_input_plugin = { - .name = "lastfm", - .init = lastfm_input_init, - .open = lastfm_input_open, -}; diff --git a/src/input/mms_input_plugin.c b/src/input/mms_input_plugin.c index 25e3129d9..834d111b8 100644 --- a/src/input/mms_input_plugin.c +++ b/src/input/mms_input_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "input/mms_input_plugin.h" #include "input_plugin.h" @@ -30,13 +31,21 @@ #define G_LOG_DOMAIN "input_mms" struct input_mms { + struct input_stream base; + mmsx_t *mms; bool eof; }; -static bool -input_mms_open(struct input_stream *is, const char *url) +static inline GQuark +mms_quark(void) +{ + return g_quark_from_static_string("mms"); +} + +static struct input_stream * +input_mms_open(const char *url, GError **error_r) { struct input_mms *m; @@ -44,39 +53,42 @@ input_mms_open(struct input_stream *is, const char *url) !g_str_has_prefix(url, "mmsh://") && !g_str_has_prefix(url, "mmst://") && !g_str_has_prefix(url, "mmsu://")) - return false; + return NULL; m = g_new(struct input_mms, 1); + input_stream_init(&m->base, &input_plugin_mms, url); + m->mms = mmsx_connect(NULL, NULL, url, 128 * 1024); if (m->mms == NULL) { g_free(m); - g_warning("mmsx_connect() failed"); - return false; + g_set_error(error_r, mms_quark(), 0, "mmsx_connect() failed"); + return NULL; } m->eof = false; /* XX is this correct? at least this selects the ffmpeg decoder, which seems to work fine*/ - is->mime = g_strdup("audio/x-ms-wma"); + m->base.mime = g_strdup("audio/x-ms-wma"); + + m->base.ready = true; - is->plugin = &input_plugin_mms; - is->data = m; - is->ready = true; - return true; + return &m->base; } static size_t -input_mms_read(struct input_stream *is, void *ptr, size_t size) +input_mms_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) { - struct input_mms *m = is->data; + struct input_mms *m = (struct input_mms *)is; int ret; ret = mmsx_read(NULL, m->mms, ptr, size); if (ret <= 0) { if (ret < 0) { - is->error = errno; - g_warning("mmsx_read() failed: %s", g_strerror(errno)); + g_set_error(error_r, mms_quark(), errno, + "mmsx_read() failed: %s", + g_strerror(errno)); } m->eof = true; @@ -91,29 +103,25 @@ input_mms_read(struct input_stream *is, void *ptr, size_t size) static void input_mms_close(struct input_stream *is) { - struct input_mms *m = is->data; + struct input_mms *m = (struct input_mms *)is; mmsx_close(m->mms); + input_stream_deinit(&m->base); g_free(m); } static bool input_mms_eof(struct input_stream *is) { - struct input_mms *m = is->data; + struct input_mms *m = (struct input_mms *)is; return m->eof; } -static int -input_mms_buffer(G_GNUC_UNUSED struct input_stream *is) -{ - return 0; -} - static bool input_mms_seek(G_GNUC_UNUSED struct input_stream *is, - G_GNUC_UNUSED off_t offset, G_GNUC_UNUSED int whence) + G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence, + G_GNUC_UNUSED GError **error_r) { return false; } @@ -122,7 +130,6 @@ const struct input_plugin input_plugin_mms = { .name = "mms", .open = input_mms_open, .close = input_mms_close, - .buffer = input_mms_buffer, .read = input_mms_read, .eof = input_mms_eof, .seek = input_mms_seek, diff --git a/src/input/mms_input_plugin.h b/src/input/mms_input_plugin.h index 3417278c2..2e10cfbb9 100644 --- a/src/input/mms_input_plugin.h +++ b/src/input/mms_input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/input/rewind_input_plugin.c b/src/input/rewind_input_plugin.c index 9e57e6999..6325a978e 100644 --- a/src/input/rewind_input_plugin.c +++ b/src/input/rewind_input_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,9 +20,6 @@ #include "config.h" #include "input/rewind_input_plugin.h" #include "input/curl_input_plugin.h" -#ifdef ENABLE_MMS -#include "input/mms_input_plugin.h" -#endif #include "input_plugin.h" #include "tag.h" @@ -35,7 +32,9 @@ #define G_LOG_DOMAIN "input_rewind" struct input_rewind { - struct input_stream input; + struct input_stream base; + + struct input_stream *input; /** * The read position within the buffer. Undefined as long as @@ -64,11 +63,9 @@ struct input_rewind { * contain more data for the next read operation? */ static bool -reading_from_buffer(const struct input_stream *is) +reading_from_buffer(const struct input_rewind *r) { - const struct input_rewind *r = is->data; - - return r->tail > 0 && is->offset < r->input.offset; + return r->tail > 0 && r->base.offset < r->input->offset; } /** @@ -78,17 +75,16 @@ reading_from_buffer(const struct input_stream *is) * attributes. */ static void -copy_attributes(struct input_stream *dest) +copy_attributes(struct input_rewind *r) { - const struct input_rewind *r = dest->data; - const struct input_stream *src = &r->input; + struct input_stream *dest = &r->base; + const struct input_stream *src = r->input; assert(dest != src); assert(src->mime == NULL || dest->mime != src->mime); dest->ready = src->ready; dest->seekable = src->seekable; - dest->error = src->error; dest->size = src->size; dest->offset = src->offset; @@ -101,43 +97,45 @@ copy_attributes(struct input_stream *dest) static void input_rewind_close(struct input_stream *is) { - struct input_rewind *r = is->data; + struct input_rewind *r = (struct input_rewind *)is; - input_stream_close(&r->input); + input_stream_close(r->input); + input_stream_deinit(&r->base); g_free(r); } static struct tag * input_rewind_tag(struct input_stream *is) { - struct input_rewind *r = is->data; + struct input_rewind *r = (struct input_rewind *)is; - return input_stream_tag(&r->input); + return input_stream_tag(r->input); } static int -input_rewind_buffer(struct input_stream *is) +input_rewind_buffer(struct input_stream *is, GError **error_r) { - struct input_rewind *r = is->data; + struct input_rewind *r = (struct input_rewind *)is; - int ret = input_stream_buffer(&r->input); - if (ret < 0 || !reading_from_buffer(is)) - copy_attributes(is); + int ret = input_stream_buffer(r->input, error_r); + if (ret < 0 || !reading_from_buffer(r)) + copy_attributes(r); return ret; } static size_t -input_rewind_read(struct input_stream *is, void *ptr, size_t size) +input_rewind_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) { - struct input_rewind *r = is->data; + struct input_rewind *r = (struct input_rewind *)is; - if (reading_from_buffer(is)) { + if (reading_from_buffer(r)) { /* buffered read */ assert(r->head == (size_t)is->offset); - assert(r->tail == (size_t)r->input.offset); + assert(r->tail == (size_t)r->input->offset); if (size > r->tail - r->head) size = r->tail - r->head; @@ -150,9 +148,9 @@ input_rewind_read(struct input_stream *is, void *ptr, size_t size) } else { /* pass method call to underlying stream */ - size_t nbytes = input_stream_read(&r->input, ptr, size); + size_t nbytes = input_stream_read(r->input, ptr, size, error_r); - if (r->input.offset > (off_t)sizeof(r->buffer)) + if (r->input->offset > (goffset)sizeof(r->buffer)) /* disable buffering */ r->tail = 0; else if (r->tail == (size_t)is->offset) { @@ -161,44 +159,46 @@ input_rewind_read(struct input_stream *is, void *ptr, size_t size) memcpy(r->buffer + r->tail, ptr, nbytes); r->tail += nbytes; - assert(r->tail == (size_t)r->input.offset); + assert(r->tail == (size_t)r->input->offset); } - copy_attributes(is); + copy_attributes(r); return nbytes; } } static bool -input_rewind_eof(G_GNUC_UNUSED struct input_stream *is) +input_rewind_eof(struct input_stream *is) { - struct input_rewind *r = is->data; + struct input_rewind *r = (struct input_rewind *)is; - return !reading_from_buffer(is) && input_stream_eof(&r->input); + return !reading_from_buffer(r) && input_stream_eof(r->input); } static bool -input_rewind_seek(struct input_stream *is, off_t offset, int whence) +input_rewind_seek(struct input_stream *is, goffset offset, int whence, + GError **error_r) { - struct input_rewind *r = is->data; + struct input_rewind *r = (struct input_rewind *)is; assert(is->ready); - if (whence == SEEK_SET && r->tail > 0 && offset <= (off_t)r->tail) { + if (whence == SEEK_SET && r->tail > 0 && offset <= (goffset)r->tail) { /* buffered seek */ - assert(!reading_from_buffer(is) || + assert(!reading_from_buffer(r) || r->head == (size_t)is->offset); - assert(r->tail == (size_t)r->input.offset); + assert(r->tail == (size_t)r->input->offset); r->head = (size_t)offset; is->offset = offset; return true; } else { - bool success = input_stream_seek(&r->input, offset, whence); - copy_attributes(is); + bool success = input_stream_seek(r->input, offset, whence, + error_r); + copy_attributes(r); /* disable the buffer, because r->input has left the buffered range now */ @@ -217,7 +217,7 @@ static const struct input_plugin rewind_input_plugin = { .seek = input_rewind_seek, }; -void +struct input_stream * input_rewind_open(struct input_stream *is) { struct input_rewind *c; @@ -225,26 +225,14 @@ input_rewind_open(struct input_stream *is) assert(is != NULL); assert(is->offset == 0); - if (is->plugin != &input_plugin_curl -#ifdef ENABLE_MMS - && is->plugin != &input_plugin_mms -#endif - ) - /* due to limitations in the input_plugin API, we only - (explicitly) support the CURL input plugin */ - return; + if (is->seekable) + /* seekable resources don't need this plugin */ + return is; c = g_new(struct input_rewind, 1); + input_stream_init(&c->base, &rewind_input_plugin, is->uri); c->tail = 0; + c->input = is; - /* move the CURL input stream to c->input */ - c->input = *is; - if (is->plugin == &input_plugin_curl) - input_curl_reinit(&c->input); - - /* convert the existing input_stream pointer to a "rewind" - input stream */ - is->plugin = &rewind_input_plugin; - is->data = c; - is->mime = g_strdup(c->input.mime); + return &c->base; } diff --git a/src/input/rewind_input_plugin.h b/src/input/rewind_input_plugin.h index 33fedf4e1..23d25d94d 100644 --- a/src/input/rewind_input_plugin.h +++ b/src/input/rewind_input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,23 +27,11 @@ #ifndef MPD_INPUT_REWIND_H #define MPD_INPUT_REWIND_H -#include "config.h" +#include "check.h" struct input_stream; -#ifdef HAVE_CURL - -void +struct input_stream * input_rewind_open(struct input_stream *is); -#else - -static inline void -input_rewind_open(struct input_stream *is) -{ - (void)is; -} - -#endif - #endif diff --git a/src/input_init.c b/src/input_init.c new file mode 100644 index 000000000..1438c3e52 --- /dev/null +++ b/src/input_init.c @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "input_init.h" +#include "input_plugin.h" +#include "input_registry.h" +#include "conf.h" +#include "glib_compat.h" + +#include <string.h> + +static inline GQuark +input_quark(void) +{ + return g_quark_from_static_string("input"); +} + +/** + * Find the "input" configuration block for the specified plugin. + * + * @param plugin_name the name of the input plugin + * @return the configuration block, or NULL if none was configured + */ +static const struct config_param * +input_plugin_config(const char *plugin_name, GError **error_r) +{ + const struct config_param *param = NULL; + + while ((param = config_get_next_param(CONF_INPUT, param)) != NULL) { + const char *name = + config_get_block_string(param, "plugin", NULL); + if (name == NULL) { + g_set_error(error_r, input_quark(), 0, + "input configuration without 'plugin' name in line %d", + param->line); + return NULL; + } + + if (strcmp(name, plugin_name) == 0) + return param; + } + + return NULL; +} + +bool +input_stream_global_init(GError **error_r) +{ + GError *error = NULL; + + for (unsigned i = 0; input_plugins[i] != NULL; ++i) { + const struct input_plugin *plugin = input_plugins[i]; + const struct config_param *param = + input_plugin_config(plugin->name, &error); + if (param == NULL && error != NULL) { + g_propagate_error(error_r, error); + return false; + } + + if (!config_get_block_bool(param, "enabled", true)) + /* the plugin is disabled in mpd.conf */ + continue; + + if (plugin->init == NULL || plugin->init(param, &error)) + input_plugins_enabled[i] = true; + else { + g_propagate_prefixed_error(error_r, error, + "Failed to initialize input plugin '%s': ", + plugin->name); + return false; + } + } + + return true; +} + +void input_stream_global_finish(void) +{ + for (unsigned i = 0; input_plugins[i] != NULL; ++i) + if (input_plugins_enabled[i] && + input_plugins[i]->finish != NULL) + input_plugins[i]->finish(); +} diff --git a/src/input_init.h b/src/input_init.h new file mode 100644 index 000000000..eded15fa9 --- /dev/null +++ b/src/input_init.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INPUT_INIT_H +#define MPD_INPUT_INIT_H + +#include "check.h" + +#include <glib.h> +#include <stdbool.h> + +/** + * Initializes this library and all input_stream implementations. + * + * @param error_r location to store the error occuring, or NULL to + * ignore errors + */ +bool +input_stream_global_init(GError **error_r); + +/** + * Deinitializes this library and all input_stream implementations. + */ +void input_stream_global_finish(void); + +#endif diff --git a/src/input_plugin.h b/src/input_plugin.h index 8fe852bc6..10be48dbb 100644 --- a/src/input_plugin.h +++ b/src/input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -35,10 +35,12 @@ struct input_plugin { /** * Global initialization. This method is called when MPD starts. * + * @param error_r location to store the error occuring, or + * NULL to ignore errors * @return true on success, false if the plugin should be * disabled */ - bool (*init)(const struct config_param *param); + bool (*init)(const struct config_param *param, GError **error_r); /** * Global deinitialization. Called once before MPD shuts @@ -46,14 +48,16 @@ struct input_plugin { */ void (*finish)(void); - bool (*open)(struct input_stream *is, const char *url); + struct input_stream *(*open)(const char *uri, GError **error_r); void (*close)(struct input_stream *is); struct tag *(*tag)(struct input_stream *is); - int (*buffer)(struct input_stream *is); - size_t (*read)(struct input_stream *is, void *ptr, size_t size); + int (*buffer)(struct input_stream *is, GError **error_r); + size_t (*read)(struct input_stream *is, void *ptr, size_t size, + GError **error_r); bool (*eof)(struct input_stream *is); - bool (*seek)(struct input_stream *is, off_t offset, int whence); + bool (*seek)(struct input_stream *is, goffset offset, int whence, + GError **error_r); }; #endif diff --git a/src/input_registry.c b/src/input_registry.c new file mode 100644 index 000000000..0b9b47d10 --- /dev/null +++ b/src/input_registry.c @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "input_registry.h" +#include "input/file_input_plugin.h" + +#ifdef ENABLE_ARCHIVE +#include "input/archive_input_plugin.h" +#endif + +#ifdef ENABLE_CURL +#include "input/curl_input_plugin.h" +#endif + +#ifdef HAVE_FFMPEG +#include "input/ffmpeg_input_plugin.h" +#endif + +#ifdef ENABLE_MMS +#include "input/mms_input_plugin.h" +#endif + +#include <glib.h> + +const struct input_plugin *const input_plugins[] = { + &input_plugin_file, +#ifdef ENABLE_ARCHIVE + &input_plugin_archive, +#endif +#ifdef ENABLE_CURL + &input_plugin_curl, +#endif +#ifdef HAVE_FFMPEG + &input_plugin_ffmpeg, +#endif +#ifdef ENABLE_MMS + &input_plugin_mms, +#endif + NULL +}; + +bool input_plugins_enabled[G_N_ELEMENTS(input_plugins) - 1]; diff --git a/src/input_registry.h b/src/input_registry.h new file mode 100644 index 000000000..e85d6be8e --- /dev/null +++ b/src/input_registry.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INPUT_REGISTRY_H +#define MPD_INPUT_REGISTRY_H + +#include "check.h" + +#include <stdbool.h> + +/** + * NULL terminated list of all input plugins which were enabled at + * compile time. + */ +extern const struct input_plugin *const input_plugins[]; + +extern bool input_plugins_enabled[]; + +#endif diff --git a/src/input_stream.c b/src/input_stream.c index 6a1b5841b..e769adb92 100644 --- a/src/input_stream.c +++ b/src/input_stream.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,137 +17,64 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "input_plugin.h" #include "config.h" -#include "conf.h" - -#include "input/file_input_plugin.h" +#include "input_stream.h" +#include "input_registry.h" +#include "input_plugin.h" #include "input/rewind_input_plugin.h" -#ifdef ENABLE_ARCHIVE -#include "input/archive_input_plugin.h" -#endif - -#ifdef HAVE_CURL -#include "input/curl_input_plugin.h" -#endif - -#include "input/lastfm_input_plugin.h" - -#ifdef ENABLE_MMS -#include "input/mms_input_plugin.h" -#endif - #include <glib.h> #include <assert.h> -#include <string.h> - -static const struct input_plugin *const input_plugins[] = { - &input_plugin_file, -#ifdef ENABLE_ARCHIVE - &input_plugin_archive, -#endif -#ifdef HAVE_CURL - &input_plugin_curl, -#endif -#ifdef ENABLE_LASTFM - &lastfm_input_plugin, -#endif -#ifdef ENABLE_MMS - &input_plugin_mms, -#endif -}; - -static bool input_plugins_enabled[G_N_ELEMENTS(input_plugins)]; - -static const unsigned num_input_plugins = - sizeof(input_plugins) / sizeof(input_plugins[0]); - -/** - * Find the "input" configuration block for the specified plugin. - * - * @param plugin_name the name of the input plugin - * @return the configuration block, or NULL if none was configured - */ -static const struct config_param * -input_plugin_config(const char *plugin_name) -{ - const struct config_param *param = NULL; - - while ((param = config_get_next_param(CONF_INPUT, param)) != NULL) { - const char *name = - config_get_block_string(param, "plugin", NULL); - if (name == NULL) - g_error("input configuration without 'plugin' name in line %d", - param->line); - if (strcmp(name, plugin_name) == 0) - return param; - } - - return NULL; -} - -void input_stream_global_init(void) +static inline GQuark +input_quark(void) { - for (unsigned i = 0; i < num_input_plugins; ++i) { - const struct input_plugin *plugin = input_plugins[i]; - const struct config_param *param = - input_plugin_config(plugin->name); - - if (!config_get_block_bool(param, "enabled", true)) - /* the plugin is disabled in mpd.conf */ - continue; - - if (plugin->init == NULL || plugin->init(param)) - input_plugins_enabled[i] = true; - } + return g_quark_from_static_string("input"); } -void input_stream_global_finish(void) +struct input_stream * +input_stream_open(const char *url, GError **error_r) { - for (unsigned i = 0; i < num_input_plugins; ++i) - if (input_plugins_enabled[i] && - input_plugins[i]->finish != NULL) - input_plugins[i]->finish(); -} + GError *error = NULL; -bool -input_stream_open(struct input_stream *is, const char *url) -{ - is->seekable = false; - is->ready = false; - is->offset = 0; - is->size = -1; - is->error = 0; - is->mime = NULL; - - for (unsigned i = 0; i < num_input_plugins; ++i) { + assert(error_r == NULL || *error_r == NULL); + + for (unsigned i = 0; input_plugins[i] != NULL; ++i) { const struct input_plugin *plugin = input_plugins[i]; + struct input_stream *is; - if (input_plugins_enabled[i] && plugin->open(is, url)) { + if (!input_plugins_enabled[i]) + continue; + + is = plugin->open(url, &error); + if (is != NULL) { assert(is->plugin != NULL); assert(is->plugin->close != NULL); assert(is->plugin->read != NULL); assert(is->plugin->eof != NULL); assert(!is->seekable || is->plugin->seek != NULL); - input_rewind_open(is); + is = input_rewind_open(is); - return true; + return is; + } else if (error != NULL) { + g_propagate_error(error_r, error); + return NULL; } } + g_set_error(error_r, input_quark(), 0, "Unrecognized URI"); return false; } bool -input_stream_seek(struct input_stream *is, off_t offset, int whence) +input_stream_seek(struct input_stream *is, goffset offset, int whence, + GError **error_r) { if (is->plugin->seek == NULL) return false; - return is->plugin->seek(is, offset, whence); + return is->plugin->seek(is, offset, whence, error_r); } struct tag * @@ -161,19 +88,18 @@ input_stream_tag(struct input_stream *is) } size_t -input_stream_read(struct input_stream *is, void *ptr, size_t size) +input_stream_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) { assert(ptr != NULL); assert(size > 0); - return is->plugin->read(is, ptr, size); + return is->plugin->read(is, ptr, size, error_r); } void input_stream_close(struct input_stream *is) { is->plugin->close(is); - - g_free(is->mime); } bool input_stream_eof(struct input_stream *is) @@ -181,10 +107,11 @@ bool input_stream_eof(struct input_stream *is) return is->plugin->eof(is); } -int input_stream_buffer(struct input_stream *is) +int +input_stream_buffer(struct input_stream *is, GError **error_r) { if (is->plugin->buffer == NULL) return 0; - return is->plugin->buffer(is); + return is->plugin->buffer(is, error_r); } diff --git a/src/input_stream.h b/src/input_stream.h index 35b0d44fd..056d008a7 100644 --- a/src/input_stream.h +++ b/src/input_stream.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,11 +20,17 @@ #ifndef MPD_INPUT_STREAM_H #define MPD_INPUT_STREAM_H +#include "check.h" + +#include <glib.h> + #include <stddef.h> #include <stdbool.h> #include <sys/types.h> -struct input_stream; +#if !GLIB_CHECK_VERSION(2,14,0) +typedef gint64 goffset; +#endif struct input_stream { /** @@ -33,9 +39,10 @@ struct input_stream { const struct input_plugin *plugin; /** - * an opaque pointer managed by the plugin + * The absolute URI which was used to open this stream. May + * be NULL if this is unknown. */ - void *data; + char *uri; /** * indicates whether the stream is ready for reading and @@ -49,19 +56,14 @@ struct input_stream { bool seekable; /** - * an optional errno error code, set to non-zero after an error occured - */ - int error; - - /** * the size of the resource, or -1 if unknown */ - off_t size; + goffset size; /** * the current offset within the stream */ - off_t offset; + goffset offset; /** * the MIME content type of the resource, or NULL if unknown @@ -69,30 +71,37 @@ struct input_stream { char *mime; }; -/** - * Initializes this library and all input_stream implementations. - */ -void input_stream_global_init(void); - -/** - * Deinitializes this library and all input_stream implementations. - */ -void input_stream_global_finish(void); +static inline void +input_stream_init(struct input_stream *is, const struct input_plugin *plugin, + const char *uri) +{ + is->plugin = plugin; + is->uri = g_strdup(uri); + is->ready = false; + is->seekable = false; + is->size = -1; + is->offset = 0; + is->mime = NULL; +} + +static inline void +input_stream_deinit(struct input_stream *is) +{ + g_free(is->uri); + g_free(is->mime); +} /** * Opens a new input stream. You may not access it until the "ready" * flag is set. * - * @param is the input_stream object allocated by the caller - * @return true on success + * @return an #input_stream object on success, NULL on error */ -bool -input_stream_open(struct input_stream *is, const char *url); +struct input_stream * +input_stream_open(const char *uri, GError **error_r); /** - * Closes the input stream and free resources. This does not free the - * input_stream pointer itself, because it is assumed to be allocated - * by the caller. + * Close the input stream and free resources. */ void input_stream_close(struct input_stream *is); @@ -106,7 +115,8 @@ input_stream_close(struct input_stream *is); * @param whence the base of the seek, one of SEEK_SET, SEEK_CUR, SEEK_END */ bool -input_stream_seek(struct input_stream *is, off_t offset, int whence); +input_stream_seek(struct input_stream *is, goffset offset, int whence, + GError **error_r); /** * Returns true if the stream has reached end-of-file. @@ -130,7 +140,7 @@ input_stream_tag(struct input_stream *is); * The semantics of this function are not well-defined, and it will * eventually be removed. */ -int input_stream_buffer(struct input_stream *is); +int input_stream_buffer(struct input_stream *is, GError **error_r); /** * Reads data from the stream into the caller-supplied buffer. @@ -142,6 +152,7 @@ int input_stream_buffer(struct input_stream *is); * @return the number of bytes read */ size_t -input_stream_read(struct input_stream *is, void *ptr, size_t size); +input_stream_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r); #endif diff --git a/src/listen.c b/src/listen.c index d6cade855..da2e79909 100644 --- a/src/listen.c +++ b/src/listen.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,337 +17,51 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "listen.h" -#include "socket_util.h" +#include "server_socket.h" #include "client.h" #include "conf.h" -#include "utils.h" -#include "config.h" +#include "glib_compat.h" -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> #include <string.h> -#include <errno.h> -#include <unistd.h> -#include <stdlib.h> #include <assert.h> -#ifdef WIN32 -#include <ws2tcpip.h> -#include <winsock.h> -#else -#include <netinet/in.h> -#include <sys/socket.h> -#include <sys/un.h> -#include <netdb.h> -#endif - #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "listen" #define DEFAULT_PORT 6600 -struct listen_socket { - struct listen_socket *next; - - int fd; - - guint source_id; -}; - -static struct listen_socket *listen_sockets; +static struct server_socket *listen_socket; int listen_port; -static GQuark -listen_quark(void) -{ - return g_quark_from_static_string("listen"); -} - -static gboolean -listen_in_event(GIOChannel *source, GIOCondition condition, gpointer data); - -static bool -listen_add_address(int pf, const struct sockaddr *addrp, socklen_t addrlen, - GError **error) -{ - char *address_string; - int fd; - struct listen_socket *ls; - GIOChannel *channel; - - address_string = sockaddr_to_string(addrp, addrlen, NULL); - if (address_string != NULL) { - g_debug("binding to socket address %s", address_string); - g_free(address_string); - } - - fd = socket_bind_listen(pf, SOCK_STREAM, 0, addrp, addrlen, 5, error); - if (fd < 0) - return false; - - ls = g_new(struct listen_socket, 1); - ls->fd = fd; - - channel = g_io_channel_unix_new(fd); - ls->source_id = g_io_add_watch(channel, G_IO_IN, - listen_in_event, GINT_TO_POINTER(fd)); - g_io_channel_unref(channel); - - ls->next = listen_sockets; - listen_sockets = ls; - - return true; -} - -#ifdef HAVE_TCP - -/** - * Add a listener on a port on all IPv4 interfaces. - * - * @param port the TCP port - * @param error location to store the error occuring, or NULL to ignore errors - * @return true on success - */ -static bool -listen_add_port_ipv4(unsigned int port, GError **error) -{ - struct sockaddr_in sin; - const struct sockaddr *addrp = (const struct sockaddr *)&sin; - socklen_t addrlen = sizeof(sin); - - memset(&sin, 0, sizeof(sin)); - sin.sin_port = htons(port); - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = INADDR_ANY; - - return listen_add_address(PF_INET, addrp, addrlen, error); -} - -#ifdef HAVE_IPV6 -/** - * Add a listener on a port on all IPv6 interfaces. - * - * @param port the TCP port - * @param error location to store the error occuring, or NULL to ignore errors - * @return true on success - */ -static bool -listen_add_port_ipv6(unsigned int port, GError **error) -{ - struct sockaddr_in6 sin; - const struct sockaddr *addrp = (const struct sockaddr *)&sin; - socklen_t addrlen = sizeof(sin); - - memset(&sin, 0, sizeof(sin)); - sin.sin6_port = htons(port); - sin.sin6_family = AF_INET6; - - return listen_add_address(PF_INET6, addrp, addrlen, error); -} -#endif /* HAVE_IPV6 */ - -#endif /* HAVE_TCP */ - -/** - * Add a listener on a port on all interfaces. - * - * @param port the TCP port - * @param error location to store the error occuring, or NULL to ignore errors - * @return true on success - */ -static bool -listen_add_port(unsigned int port, GError **error) -{ -#ifdef HAVE_TCP - bool success; -#ifdef HAVE_IPV6 - bool success6; - GError *error2 = NULL; -#endif - - g_debug("binding to any address"); - -#ifdef HAVE_IPV6 - success6 = listen_add_port_ipv6(port, &error2); - if (!success6) { - if (error2->domain != listen_quark() || - (error2->code != EAFNOSUPPORT && error2->code != EINVAL && - error2->code != EPROTONOSUPPORT)) { - g_propagate_error(error, error2); - return false; - } - - /* although MPD was compiled with IPv6 support, this - host does not have it - ignore this error */ - g_error_free(error2); - } -#endif - - success = listen_add_port_ipv4(port, error); - if (!success) { -#ifdef HAVE_IPV6 - if (success6) - /* non-critical: IPv6 listener is - already set up */ - g_clear_error(error); - else -#endif - return false; - } - - return true; -#else /* HAVE_TCP */ - (void)port; - - g_set_error(error, listen_quark(), 0, - "TCP support is disabled"); - return false; -#endif /* HAVE_TCP */ -} - -/** - * Resolves a host name, and adds listeners on all addresses in the - * result set. - * - * @param hostname the host name to be resolved - * @param port the TCP port - * @param error location to store the error occuring, or NULL to ignore errors - * @return true on success - */ -static bool -listen_add_host(const char *hostname, unsigned port, GError **error_r) -{ -#ifdef HAVE_TCP - struct addrinfo hints, *ai, *i; - char service[20]; - int ret; - bool success; - - g_debug("binding to address for %s", hostname); - - memset(&hints, 0, sizeof(hints)); - hints.ai_flags = AI_PASSIVE; -#ifdef AI_ADDRCONFIG - hints.ai_flags |= AI_ADDRCONFIG; -#endif - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - g_snprintf(service, sizeof(service), "%u", port); - - ret = getaddrinfo(hostname, service, &hints, &ai); - if (ret != 0) { - g_set_error(error_r, listen_quark(), ret, - "Failed to look up host \"%s\": %s", - hostname, gai_strerror(ret)); - return false; - } - - for (i = ai; i != NULL; i = i->ai_next) { - GError *error = NULL; - - success = listen_add_address(i->ai_family, i->ai_addr, - i->ai_addrlen, &error); - if (!success) { - if (i == ai) { - /* first bind has failed: fatal - error */ - g_propagate_error(error_r, error); - return false; - } else { - char *address_string = - sockaddr_to_string(i->ai_addr, - i->ai_addrlen, - NULL); - if (address_string == NULL) - address_string = g_strdup("[unknown]"); - - g_warning("bind to %s failed: %s " - "(continuing anyway, because at " - "least one address is bound)", - address_string, error->message); - g_free(address_string); - g_error_free(error); - } - } - } - - freeaddrinfo(ai); - - return true; -#else /* HAVE_TCP */ - - (void)hostname; - (void)port; - - g_set_error(error_r, listen_quark(), 0, - "TCP support is disabled"); - return false; -#endif /* HAVE_TCP */ -} - -#ifdef HAVE_UN -/** - * Add a listener on a Unix domain socket. - * - * @param path the absolute socket path - * @param error location to store the error occuring, or NULL to ignore errors - * @return true on success - */ -static bool -listen_add_path(const char *path, GError **error) +static void +listen_callback(int fd, const struct sockaddr *address, + size_t address_length, int uid, G_GNUC_UNUSED void *ctx) { - size_t path_length; - struct sockaddr_un s_un; - const struct sockaddr *addrp = (const struct sockaddr *)&s_un; - socklen_t addrlen = sizeof(s_un); - bool success; - - path_length = strlen(path); - if (path_length >= sizeof(s_un.sun_path)) { - g_set_error(error, listen_quark(), 0, - "unix socket path is too long"); - return false; - } - - unlink(path); - - s_un.sun_family = AF_UNIX; - memcpy(s_un.sun_path, path, path_length + 1); - - success = listen_add_address(PF_UNIX, addrp, addrlen, error); - if (!success) - return false; - - /* allow everybody to connect */ - chmod(path, 0666); - - return true; + client_new(fd, address, address_length, uid); } -#endif /* HAVE_UN */ static bool listen_add_config_param(unsigned int port, const struct config_param *param, - GError **error) + GError **error_r) { assert(param != NULL); if (0 == strcmp(param->value, "any")) { - return listen_add_port(port, error); -#ifdef HAVE_UN + return server_socket_add_port(listen_socket, port, error_r); } else if (param->value[0] == '/') { - return listen_add_path(param->value, error); -#endif /* HAVE_UN */ + return server_socket_add_path(listen_socket, param->value, + error_r); } else { - return listen_add_host(param->value, port, error); + return server_socket_add_host(listen_socket, param->value, + port, error_r); } } -void listen_global_init(void) +bool +listen_global_init(GError **error_r) { int port = config_get_positive(CONF_PORT, DEFAULT_PORT); const struct config_param *param = @@ -355,16 +69,20 @@ void listen_global_init(void) bool success; GError *error = NULL; + listen_socket = server_socket_new(listen_callback, NULL); + if (param != NULL) { /* "bind_to_address" is configured, create listeners for all values */ do { success = listen_add_config_param(port, param, &error); - if (!success) - g_error("Failed to listen on %s (line %i): %s", - param->value, param->line, - error->message); + if (!success) { + g_propagate_prefixed_error(error_r, error, + "Failed to listen on %s (line %i): ", + param->value, param->line); + return false; + } param = config_get_next_param(CONF_BIND_TO_ADDRESS, param); @@ -373,69 +91,27 @@ void listen_global_init(void) /* no "bind_to_address" configured, bind the configured port on all interfaces */ - success = listen_add_port(port, &error); - if (!success) - g_error("Failed to listen on *:%d: %s", - port, error->message); + success = server_socket_add_port(listen_socket, port, error_r); + if (!success) { + g_propagate_prefixed_error(error_r, error, + "Failed to listen on *:%d: ", + port); + return false; + } } + if (!server_socket_open(listen_socket, error_r)) + return false; + listen_port = port; + return true; } void listen_global_finish(void) { g_debug("listen_global_finish called"); - while (listen_sockets != NULL) { - struct listen_socket *ls = listen_sockets; - listen_sockets = ls->next; + assert(listen_socket != NULL); - g_source_remove(ls->source_id); - close(ls->fd); - g_free(ls); - } -} - -static int get_remote_uid(int fd) -{ -#ifdef HAVE_STRUCT_UCRED - struct ucred cred; - socklen_t len = sizeof (cred); - - if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0) - return 0; - - return cred.uid; -#else -#ifdef HAVE_GETPEEREID - uid_t euid; - gid_t egid; - - if (getpeereid(fd, &euid, &egid) == 0) - return euid; -#endif - return -1; -#endif -} - -static gboolean -listen_in_event(G_GNUC_UNUSED GIOChannel *source, - G_GNUC_UNUSED GIOCondition condition, - gpointer data) -{ - int listen_fd = GPOINTER_TO_INT(data), fd; - struct sockaddr_storage sa; - socklen_t sa_length = sizeof(sa); - - fd = accept(listen_fd, (struct sockaddr*)&sa, &sa_length); - if (fd >= 0) { - set_nonblocking(fd); - - client_new(fd, (struct sockaddr*)&sa, sa_length, - get_remote_uid(fd)); - } else if (fd < 0 && errno != EINTR) { - g_warning("Problems accept()'ing"); - } - - return true; + server_socket_free(listen_socket); } diff --git a/src/listen.h b/src/listen.h index 63253fc53..449b5ebae 100644 --- a/src/listen.h +++ b/src/listen.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,9 +20,14 @@ #ifndef MPD_LISTEN_H #define MPD_LISTEN_H +#include <glib.h> + +#include <stdbool.h> + extern int listen_port; -void listen_global_init(void); +bool +listen_global_init(GError **error_r); void listen_global_finish(void); diff --git a/src/locate.c b/src/locate.c index 7b4721fa9..e27858a0e 100644 --- a/src/locate.c +++ b/src/locate.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "locate.h" #include "path.h" #include "tag.h" diff --git a/src/locate.h b/src/locate.h index d0bdfa136..0283f551b 100644 --- a/src/locate.h +++ b/src/locate.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "log.h" #include "conf.h" #include "utils.h" -#include "config.h" +#include "fd_util.h" +#include "mpd_error.h" #include <assert.h> #include <sys/types.h> @@ -59,9 +61,9 @@ static void redirect_logs(int fd) { assert(fd >= 0); if (dup2(fd, STDOUT_FILENO) < 0) - g_error("problems dup2 stdout : %s\n", strerror(errno)); + MPD_ERROR("problems dup2 stdout : %s\n", strerror(errno)); if (dup2(fd, STDERR_FILENO) < 0) - g_error("problems dup2 stderr : %s\n", strerror(errno)); + MPD_ERROR("problems dup2 stderr : %s\n", strerror(errno)); } static const char *log_date(void) @@ -128,7 +130,7 @@ open_log_file(void) { assert(out_filename != NULL); - return open(out_filename, O_CREAT | O_WRONLY | O_APPEND, 0666); + return open_cloexec(out_filename, O_CREAT | O_WRONLY | O_APPEND, 0666); } static void @@ -137,8 +139,8 @@ log_init_file(const char *path, unsigned line) out_filename = path; out_fd = open_log_file(); if (out_fd < 0) - g_error("problem opening log file \"%s\" (config line %u) for " - "writing\n", path, line); + MPD_ERROR("problem opening log file \"%s\" (config line %u) " + "for writing\n", path, line); g_log_set_default_handler(file_log_func, NULL); } @@ -215,8 +217,8 @@ parse_log_level(const char *value, unsigned line) else if (0 == strcmp(value, "verbose")) return G_LOG_LEVEL_DEBUG; else { - g_error("unknown log level \"%s\" at line %u\n", - value, line); + MPD_ERROR("unknown log level \"%s\" at line %u\n", + value, line); return G_LOG_LEVEL_MESSAGE; } } @@ -251,8 +253,8 @@ void log_init(bool verbose, bool use_stdout) available) */ log_init_syslog(); #else - g_error("config parameter \"%s\" not found\n", - CONF_LOG_FILE); + MPD_ERROR("config parameter \"%s\" not found\n", + CONF_LOG_FILE); #endif #ifdef HAVE_SYSLOG } else if (strcmp(param->value, "syslog") == 0) { @@ -271,7 +273,12 @@ void setup_log_output(bool use_stdout) { fflush(NULL); if (!use_stdout) { - if (out_filename != NULL) { +#ifndef WIN32 + if (out_filename == NULL) + out_fd = open("/dev/null", O_WRONLY); +#endif + + if (out_fd >= 0) { redirect_logs(out_fd); close(out_fd); } @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "ls.h" #include "uri.h" #include "client.h" -#include "config.h" #include <assert.h> #include <string.h> @@ -32,18 +32,23 @@ * connected by IPC socket. */ static const char *remoteUrlPrefixes[] = { -#ifdef HAVE_CURL +#ifdef ENABLE_CURL "http://", #endif -#ifdef ENABLE_LASTFM - "lastfm://", -#endif #ifdef ENABLE_MMS "mms://", "mmsh://", "mmst://", "mmsu://", #endif +#ifdef HAVE_FFMPEG + "gopher://", + "rtp://", + "rtsp://", + "rtmp://", + "rtmpt://", + "rtmps://", +#endif NULL }; @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/main.c b/src/main.c index 5035a4836..a500e2934 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "main.h" #include "daemon.h" #include "client.h" @@ -33,7 +34,6 @@ #include "path.h" #include "mapper.h" #include "chunk.h" -#include "decoder_control.h" #include "player_control.h" #include "stats.h" #include "sig_handlers.h" @@ -42,19 +42,23 @@ #include "volume.h" #include "log.h" #include "permission.h" -#include "replay_gain.h" +#include "replay_gain_config.h" #include "decoder_list.h" -#include "input_stream.h" +#include "input_init.h" +#include "playlist_list.h" #include "state_file.h" #include "tag.h" #include "dbUtils.h" -#include "config.h" -#include "normalize.h" #include "zeroconf.h" #include "event_pipe.h" #include "dirvec.h" #include "songvec.h" #include "tag_pool.h" +#include "mpd_error.h" + +#ifdef ENABLE_INOTIFY +#include "inotify_update.h" +#endif #ifdef ENABLE_SQLITE #include "sticker.h" @@ -88,7 +92,34 @@ enum { GThread *main_task; GMainLoop *main_loop; -struct notify main_notify; +GCond *main_cond; + +static void +glue_daemonize_init(const struct options *options) +{ + daemonize_init(config_get_string(CONF_USER, NULL), + config_get_string(CONF_GROUP, NULL), + config_get_path(CONF_PID_FILE)); + + if (options->kill) + daemonize_kill(); +} + +static void +glue_mapper_init(void) +{ + const char *music_dir, *playlist_dir; + + music_dir = config_get_path(CONF_MUSIC_DIR); +#if GLIB_CHECK_VERSION(2,14,0) + if (music_dir == NULL) + music_dir = g_get_user_special_dir(G_USER_DIRECTORY_MUSIC); +#endif + + playlist_dir = config_get_path(CONF_PLAYLIST_DIR); + + mapper_init(music_dir, playlist_dir); +} /** * Returns the database. If this function returns false, this has not @@ -96,7 +127,7 @@ struct notify main_notify; * process has been daemonized. */ static bool -openDB(const Options *options) +glue_db_init_and_load(void) { const char *path = config_get_path(CONF_DB_FILE); bool ret; @@ -111,23 +142,15 @@ openDB(const Options *options) } if (path == NULL) - g_error(CONF_DB_FILE " setting missing"); + MPD_ERROR(CONF_DB_FILE " setting missing"); db_init(path); - if (options->createDB > 0) - /* don't attempt to load the old database */ - return false; - ret = db_load(&error); if (!ret) { g_warning("Failed to load database: %s", error->message); g_error_free(error); - if (options->createDB < 0) - g_error("can't open db file and using " - "\"--no-create-db\" command line option"); - if (!db_check()) exit(EXIT_FAILURE); @@ -141,29 +164,51 @@ 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) + MPD_ERROR("%s", error->message); +#endif +} + +static void +glue_state_file_init(void) +{ + state_file_init(config_get_path(CONF_STATE_FILE)); +} + +/** * Windows-only initialization of the Winsock2 library. */ -#ifdef WIN32 static void winsock_init(void) { +#ifdef WIN32 WSADATA sockinfo; int retval; retval = WSAStartup(MAKEWORD(2, 2), &sockinfo); if(retval != 0) { - g_error("Attempt to open Winsock2 failed; error code %d\n", + MPD_ERROR("Attempt to open Winsock2 failed; error code %d\n", retval); } if (LOBYTE(sockinfo.wVersion) != 2) { - g_error("We use Winsock2 but your version is either too new or " - "old; please install Winsock 2.x\n"); + MPD_ERROR("We use Winsock2 but your version is either too new " + "or old; please install Winsock 2.x\n"); } - -} #endif +} /** * Initialize the decoder and player core, including the music pipe. @@ -182,8 +227,8 @@ initialize_decoder_and_player(void) if (param != NULL) { buffer_size = strtol(param->value, &test, 10); if (*test != '\0' || buffer_size <= 0) - g_error("buffer size \"%s\" is not a positive integer, " - "line %i\n", param->value, param->line); + MPD_ERROR("buffer size \"%s\" is not a positive integer, " + "line %i\n", param->value, param->line); } else buffer_size = DEFAULT_BUFFER_SIZE; @@ -192,15 +237,15 @@ initialize_decoder_and_player(void) buffered_chunks = buffer_size / CHUNK_SIZE; if (buffered_chunks >= 1 << 15) - g_error("buffer size \"%li\" is too big\n", (long)buffer_size); + MPD_ERROR("buffer size \"%li\" is too big\n", (long)buffer_size); param = config_get_param(CONF_BUFFER_BEFORE_PLAY); if (param != NULL) { perc = strtod(param->value, &test); if (*test != '%' || perc < 0 || perc > 100) { - g_error("buffered before play \"%s\" is not a positive " - "percentage and less than 100 percent, line %i", - param->value, param->line); + MPD_ERROR("buffered before play \"%s\" is not a positive " + "percentage and less than 100 percent, line %i", + param->value, param->line); } } else perc = DEFAULT_BUFFER_BEFORE_PLAY; @@ -210,7 +255,6 @@ initialize_decoder_and_player(void) buffered_before_play = buffered_chunks; pc_init(buffered_chunks, buffered_before_play); - dc_init(); } /** @@ -226,11 +270,31 @@ idle_event_emitted(void) client_manager_idle_add(flags); } +/** + * event_pipe callback function for PIPE_EVENT_SHUTDOWN + */ +static void +shutdown_event_emitted(void) +{ + g_main_loop_quit(main_loop); +} + int main(int argc, char *argv[]) { - Options options; +#ifdef WIN32 + return win32_main(argc, argv); +#else + return mpd_main(argc, argv); +#endif +} + +int mpd_main(int argc, char *argv[]) +{ + struct options options; clock_t start; bool create_db; + GError *error = NULL; + bool success; daemonize_close_stdin(); @@ -239,45 +303,52 @@ int main(int argc, char *argv[]) setlocale(LC_CTYPE,""); #endif + g_set_application_name("Music Player Daemon"); + /* enable GLib's thread safety code */ g_thread_init(NULL); -#ifdef WIN32 winsock_init(); -#endif idle_init(); dirvec_init(); songvec_init(); tag_pool_init(); config_global_init(); - parseOptions(argc, argv, &options); - - daemonize_init(config_get_string(CONF_USER, NULL), - config_get_path(CONF_PID_FILE)); + success = parse_cmdline(argc, argv, &options, &error); + if (!success) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } - if (options.kill) - daemonize_kill(); + glue_daemonize_init(&options); stats_global_init(); tag_lib_init(); - log_init(options.verbose, options.stdOutput); + log_init(options.verbose, options.log_stderr); - listen_global_init(); + success = listen_global_init(&error); + if (!success) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } daemonize_set_user(); main_task = g_thread_self(); main_loop = g_main_loop_new(NULL, FALSE); - notify_init(&main_notify); + main_cond = g_cond_new(); event_pipe_init(); event_pipe_register(PIPE_EVENT_IDLE, idle_event_emitted); + event_pipe_register(PIPE_EVENT_SHUTDOWN, shutdown_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 +356,9 @@ int main(int argc, char *argv[]) decoder_plugin_init_all(); update_global_init(); - create_db = !openDB(&options); + create_db = !glue_db_init_and_load(); -#ifdef ENABLE_SQLITE - sticker_global_init(config_get_path(CONF_STICKER_FILE)); -#endif + glue_sticker_init(); command_init(); initialize_decoder_and_player(); @@ -298,12 +367,18 @@ int main(int argc, char *argv[]) audio_output_all_init(); client_manager_init(); replay_gain_global_init(); - initNormalization(); - input_stream_global_init(); + + if (!input_stream_global_init(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + playlist_list_global_init(); daemonize(options.daemon); - setup_log_output(options.stdOutput); + setup_log_output(options.log_stderr); initSigHandlers(); @@ -312,30 +387,56 @@ int main(int argc, char *argv[]) player_create(); if (create_db) { - /* the database failed to load, or MPD was started - with --create-db: recreate a new database */ - unsigned job = directory_update_init(NULL); + /* the database failed to load: recreate the + database */ + unsigned job = update_enqueue(NULL, true); if (job == 0) - g_error("directory update failed"); + MPD_ERROR("directory update failed"); } + glue_state_file_init(); - state_file_init(config_get_path(CONF_STATE_FILE)); + success = config_get_bool(CONF_AUTO_UPDATE, false); +#ifdef ENABLE_INOTIFY + if (success && mapper_has_music_directory()) + mpd_inotify_init(config_get_unsigned(CONF_AUTO_UPDATE_DEPTH, + G_MAXUINT)); +#else + if (success) + g_warning("inotify: auto_update was disabled. enable during compilation phase"); +#endif - /* run the main loop */ + config_global_check(); + /* enable all audio outputs (if not already done by + playlist_state_restore() */ + pc_update_audio(); + +#ifdef WIN32 + win32_app_started(); +#endif + + /* run the main loop */ g_main_loop_run(main_loop); +#ifdef WIN32 + win32_app_stopping(); +#endif + /* cleanup */ g_main_loop_unref(main_loop); +#ifdef ENABLE_INOTIFY + mpd_inotify_finish(); +#endif + state_file_finish(); - playerKill(); + pc_kill(); finishZeroconf(); client_manager_deinit(); listen_global_finish(); - finishPlaylist(); + playlist_global_finish(); start = clock(); db_finish(); @@ -346,18 +447,16 @@ int main(int argc, char *argv[]) sticker_global_finish(); #endif - notify_deinit(&main_notify); + g_cond_free(main_cond); event_pipe_deinit(); + playlist_list_global_finish(); input_stream_global_finish(); - finishNormalization(); audio_output_all_finish(); - finishAudioConfig(); volume_finish(); mapper_finish(); path_global_finish(); finishPermissions(); - dc_deinit(); pc_deinit(); command_finish(); update_global_finish(); diff --git a/src/main.h b/src/main.h index 8ed02bf5d..9b9cba018 100644 --- a/src/main.h +++ b/src/main.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,6 +26,47 @@ extern GThread *main_task; extern GMainLoop *main_loop; -extern struct notify main_notify; +extern GCond *main_cond; + +/** + * A entry point for application. + * On non-Windows platforms this is called directly from main() + * On Windows platform this is called from win32_main() + * after doing some initialization. + */ +int mpd_main(int argc, char *argv[]); + +#ifdef WIN32 + +/** + * If program is run as windows service performs nessesary initialization + * and then calls mpd_main() with specified arguments. + * If program is run as a regular application calls mpd_main() immediately. + */ +int +win32_main(int argc, char *argv[]); + +/** + * When running as a service reports to service control manager + * that our service is started. + * When running as a console application enables console handler that will + * trigger PIPE_EVENT_SHUTDOWN when user closes console window + * or presses Ctrl+C. + * This function should be called just before entering main loop. + */ +void +win32_app_started(void); + +/** + * When running as a service reports to service control manager + * that our service is about to stop. + * When running as a console application enables console handler that will + * catch all shutdown requests and ignore them. + * This function should be called just after leaving main loop. + */ +void +win32_app_stopping(void); + +#endif #endif diff --git a/src/main_win32.c b/src/main_win32.c new file mode 100644 index 000000000..543d8ba81 --- /dev/null +++ b/src/main_win32.c @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "main.h" + +#ifdef WIN32 + +#include "mpd_error.h" +#include "event_pipe.h" + +#include <glib.h> + +#define WINVER 0x0501 +#include <windows.h> + +static int service_argc; +static char **service_argv; +static char service_name[] = ""; +static BOOL ignore_console_events; +static SERVICE_STATUS_HANDLE service_handle; + +static void WINAPI +service_main(DWORD argc, CHAR *argv[]); + +static SERVICE_TABLE_ENTRY service_registry[] = { + {service_name, service_main}, + {NULL, NULL} +}; + +static void +service_notify_status(DWORD status_code) +{ + SERVICE_STATUS current_status; + + current_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + current_status.dwControlsAccepted = status_code == SERVICE_START_PENDING + ? 0 + : SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP; + + current_status.dwCurrentState = status_code; + current_status.dwWin32ExitCode = NO_ERROR; + current_status.dwCheckPoint = 0; + current_status.dwWaitHint = 1000; + + SetServiceStatus(service_handle, ¤t_status); +} + +static DWORD WINAPI +service_dispatcher(G_GNUC_UNUSED DWORD control, G_GNUC_UNUSED DWORD event_type, + G_GNUC_UNUSED void *event_data, G_GNUC_UNUSED void *context) +{ + switch (control) { + case SERVICE_CONTROL_SHUTDOWN: + case SERVICE_CONTROL_STOP: + event_pipe_emit(PIPE_EVENT_SHUTDOWN); + return NO_ERROR; + default: + return NO_ERROR; + } +} + +static void WINAPI +service_main(G_GNUC_UNUSED DWORD argc, G_GNUC_UNUSED CHAR *argv[]) +{ + DWORD error_code; + gchar* error_message; + + service_handle = + RegisterServiceCtrlHandlerEx(service_name, + service_dispatcher, NULL); + + if (service_handle == 0) { + error_code = GetLastError(); + error_message = g_win32_error_message(error_code); + MPD_ERROR("RegisterServiceCtrlHandlerEx() failed: %s", + error_message); + } + + service_notify_status(SERVICE_START_PENDING); + mpd_main(service_argc, service_argv); + service_notify_status(SERVICE_STOPPED); +} + +static BOOL WINAPI +console_handler(DWORD event) +{ + switch (event) { + case CTRL_C_EVENT: + case CTRL_CLOSE_EVENT: + if (!ignore_console_events) + event_pipe_emit(PIPE_EVENT_SHUTDOWN); + return TRUE; + default: + return FALSE; + } +} + +int win32_main(int argc, char *argv[]) +{ + DWORD error_code; + gchar* error_message; + + service_argc = argc; + service_argv = argv; + + if (StartServiceCtrlDispatcher(service_registry)) + return 0; /* run as service successefully */ + + error_code = GetLastError(); + if (error_code == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) { + /* running as console app */ + SetConsoleTitle("Music Player Daemon"); + ignore_console_events = TRUE; + SetConsoleCtrlHandler(console_handler, TRUE); + return mpd_main(argc, argv); + } + + error_message = g_win32_error_message(error_code); + MPD_ERROR("StartServiceCtrlDispatcher() failed: %s", error_message); +} + +void win32_app_started() +{ + if (service_handle != 0) + service_notify_status(SERVICE_RUNNING); + else + ignore_console_events = FALSE; +} + +void win32_app_stopping() +{ + if (service_handle != 0) + service_notify_status(SERVICE_STOP_PENDING); + else + ignore_console_events = TRUE; +} + +#endif diff --git a/src/mapper.c b/src/mapper.c index 5518cb79e..108de9531 100644 --- a/src/mapper.c +++ b/src/mapper.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,20 +21,16 @@ * Maps directory and song objects to file system paths. */ +#include "config.h" #include "mapper.h" #include "directory.h" #include "song.h" #include "path.h" -#include "conf.h" #include <glib.h> #include <assert.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> #include <string.h> -#include <errno.h> static char *music_dir; static size_t music_dir_length; @@ -58,17 +54,10 @@ strdup_chop_slash(const char *path_fs) static void mapper_set_music_dir(const char *path) { - int ret; - struct stat st; - music_dir = strdup_chop_slash(path); music_dir_length = strlen(music_dir); - ret = stat(music_dir, &st); - if (ret < 0) - g_warning("failed to stat music directory \"%s\": %s", - music_dir, g_strerror(errno)); - else if (!S_ISDIR(st.st_mode)) + if (!g_file_test(music_dir, G_FILE_TEST_IS_DIR)) g_warning("music directory is not a directory: \"%s\"", music_dir); } @@ -76,38 +65,20 @@ mapper_set_music_dir(const char *path) static void mapper_set_playlist_dir(const char *path) { - int ret; - struct stat st; - playlist_dir = g_strdup(path); - ret = stat(playlist_dir, &st); - if (ret < 0) - g_warning("failed to stat playlist directory \"%s\": %s", - playlist_dir, g_strerror(errno)); - else if (!S_ISDIR(st.st_mode)) + if (!g_file_test(playlist_dir, G_FILE_TEST_IS_DIR)) g_warning("playlist directory is not a directory: \"%s\"", playlist_dir); } -void mapper_init(void) +void mapper_init(const char *_music_dir, const char *_playlist_dir) { - const char *path; - - path = config_get_path(CONF_MUSIC_DIR); - if (path != NULL) - mapper_set_music_dir(path); -#if GLIB_CHECK_VERSION(2,14,0) - else { - path = g_get_user_special_dir(G_USER_DIRECTORY_MUSIC); - if (path != NULL) - mapper_set_music_dir(path); - } -#endif + if (_music_dir != NULL) + mapper_set_music_dir(_music_dir); - path = config_get_path(CONF_PLAYLIST_DIR); - if (path != NULL) - mapper_set_playlist_dir(path); + if (_playlist_dir != NULL) + mapper_set_playlist_dir(_playlist_dir); } void mapper_finish(void) @@ -122,6 +93,16 @@ mapper_has_music_directory(void) return music_dir != NULL; } +const char * +map_to_relative_path(const char *path_utf8) +{ + return music_dir != NULL && + memcmp(path_utf8, music_dir, music_dir_length) == 0 && + G_IS_DIR_SEPARATOR(path_utf8[music_dir_length]) + ? path_utf8 + music_dir_length + 1 + : path_utf8; +} + char * map_uri_fs(const char *uri) { @@ -189,9 +170,9 @@ map_song_fs(const struct song *song) assert(song_is_file(song)); if (song_in_database(song)) - return map_directory_child_fs(song->parent, song->url); + return map_directory_child_fs(song->parent, song->uri); else - return utf8_to_fs_charset(song->url); + return utf8_to_fs_charset(song->uri); } char * @@ -199,10 +180,10 @@ map_fs_to_utf8(const char *path_fs) { if (music_dir != NULL && strncmp(path_fs, music_dir, music_dir_length) == 0 && - path_fs[music_dir_length] == '/') + G_IS_DIR_SEPARATOR(path_fs[music_dir_length])) /* remove musicDir prefix */ path_fs += music_dir_length + 1; - else if (path_fs[0] == '/') + else if (G_IS_DIR_SEPARATOR(path_fs[0])) /* not within musicDir */ return NULL; diff --git a/src/mapper.h b/src/mapper.h index f109de0bd..9f84f96fe 100644 --- a/src/mapper.h +++ b/src/mapper.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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); @@ -42,6 +42,14 @@ bool mapper_has_music_directory(void); /** + * If the specified absolute path points inside the music directory, + * this function converts it to a relative path. If not, it returns + * the unmodified string pointer. + */ +const char * +map_to_relative_path(const char *path_utf8); + +/** * Determines the absolute file system path of a relative URI. This * is basically done by converting the URI to the file system charset * and prepending the music directory. diff --git a/src/mixer/alsa_mixer.c b/src/mixer/alsa_mixer_plugin.c index 52e553cc5..38f36cb8f 100644 --- a/src/mixer/alsa_mixer.c +++ b/src/mixer/alsa_mixer_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../output_api.h" -#include "../mixer_api.h" +#include "config.h" +#include "mixer_api.h" +#include "output_api.h" #include <glib.h> #include <alsa/asoundlib.h> @@ -42,12 +43,22 @@ struct alsa_mixer { int volume_set; }; +/** + * The quark used for GError.domain. + */ +static inline GQuark +alsa_mixer_quark(void) +{ + return g_quark_from_static_string("alsa_mixer"); +} + static struct mixer * -alsa_mixer_init(const struct config_param *param) +alsa_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param, + G_GNUC_UNUSED GError **error_r) { struct alsa_mixer *am = g_new(struct alsa_mixer, 1); - mixer_init(&am->base, &alsa_mixer); + mixer_init(&am->base, &alsa_mixer_plugin); am->device = config_get_block_string(param, "mixer_device", VOLUME_MIXER_ALSA_DEFAULT); @@ -81,7 +92,7 @@ alsa_mixer_close(struct mixer *data) } static bool -alsa_mixer_open(struct mixer *data) +alsa_mixer_open(struct mixer *data, GError **error_r) { struct alsa_mixer *am = (struct alsa_mixer *)data; int err; @@ -91,29 +102,33 @@ alsa_mixer_open(struct mixer *data) err = snd_mixer_open(&am->handle, 0); if (err < 0) { - g_warning("problems opening alsa mixer: %s\n", snd_strerror(err)); + g_set_error(error_r, alsa_mixer_quark(), err, + "snd_mixer_open() failed: %s", snd_strerror(err)); return false; } if ((err = snd_mixer_attach(am->handle, am->device)) < 0) { - g_warning("problems attaching alsa mixer: %s\n", - snd_strerror(err)); alsa_mixer_close(data); + g_set_error(error_r, alsa_mixer_quark(), err, + "failed to attach to %s: %s", + am->device, snd_strerror(err)); return false; } if ((err = snd_mixer_selem_register(am->handle, NULL, NULL)) < 0) { - g_warning("problems snd_mixer_selem_register'ing: %s\n", - snd_strerror(err)); alsa_mixer_close(data); + g_set_error(error_r, alsa_mixer_quark(), err, + "snd_mixer_selem_register() failed: %s", + snd_strerror(err)); return false; } if ((err = snd_mixer_load(am->handle)) < 0) { - g_warning("problems snd_mixer_selem_register'ing: %s\n", - snd_strerror(err)); alsa_mixer_close(data); + g_set_error(error_r, alsa_mixer_quark(), err, + "snd_mixer_load() failed: %s\n", + snd_strerror(err)); return false; } @@ -138,14 +153,14 @@ alsa_mixer_open(struct mixer *data) return true; } - g_warning("can't find alsa mixer control \"%s\"\n", am->control); - alsa_mixer_close(data); + g_set_error(error_r, alsa_mixer_quark(), 0, + "no such mixer control: %s", am->control); return false; } static int -alsa_mixer_get_volume(struct mixer *mixer) +alsa_mixer_get_volume(struct mixer *mixer, GError **error_r) { struct alsa_mixer *am = (struct alsa_mixer *)mixer; int err; @@ -156,8 +171,9 @@ alsa_mixer_get_volume(struct mixer *mixer) err = snd_mixer_handle_events(am->handle); if (err < 0) { - g_warning("problems getting alsa volume: %s (snd_mixer_%s)\n", - snd_strerror(err), "handle_events"); + g_set_error(error_r, alsa_mixer_quark(), err, + "snd_mixer_handle_events() failed: %s", + snd_strerror(err)); return false; } @@ -165,8 +181,9 @@ alsa_mixer_get_volume(struct mixer *mixer) SND_MIXER_SCHN_FRONT_LEFT, &level); if (err < 0) { - g_warning("problems getting alsa volume: %s (snd_mixer_%s)\n", - snd_strerror(err), "selem_get_playback_volume"); + g_set_error(error_r, alsa_mixer_quark(), err, + "failed to read ALSA volume: %s", + snd_strerror(err)); return false; } @@ -183,7 +200,7 @@ alsa_mixer_get_volume(struct mixer *mixer) } static bool -alsa_mixer_set_volume(struct mixer *mixer, unsigned volume) +alsa_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) { struct alsa_mixer *am = (struct alsa_mixer *)mixer; float vol; @@ -203,15 +220,16 @@ alsa_mixer_set_volume(struct mixer *mixer, unsigned volume) err = snd_mixer_selem_set_playback_volume_all(am->elem, level); if (err < 0) { - g_warning("problems setting alsa volume: %s\n", - snd_strerror(err)); + g_set_error(error_r, alsa_mixer_quark(), err, + "failed to set ALSA volume: %s", + snd_strerror(err)); return false; } return true; } -const struct mixer_plugin alsa_mixer = { +const struct mixer_plugin alsa_mixer_plugin = { .init = alsa_mixer_init, .finish = alsa_mixer_finish, .open = alsa_mixer_open, diff --git a/src/mixer/oss_mixer.c b/src/mixer/oss_mixer_plugin.c index f2db01ff4..418068ac2 100644 --- a/src/mixer/oss_mixer.c +++ b/src/mixer/oss_mixer_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../output_api.h" -#include "../mixer_api.h" +#include "config.h" +#include "mixer_api.h" +#include "output_api.h" +#include "fd_util.h" #include <glib.h> @@ -49,6 +51,15 @@ struct oss_mixer { int volume_control; }; +/** + * The quark used for GError.domain. + */ +static inline GQuark +oss_mixer_quark(void) +{ + return g_quark_from_static_string("oss_mixer"); +} + static int oss_find_mixer(const char *name) { @@ -65,11 +76,12 @@ oss_find_mixer(const char *name) } static struct mixer * -oss_mixer_init(const struct config_param *param) +oss_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param, + GError **error_r) { struct oss_mixer *om = g_new(struct oss_mixer, 1); - mixer_init(&om->base, &oss_mixer); + mixer_init(&om->base, &oss_mixer_plugin); om->device = config_get_block_string(param, "mixer_device", VOLUME_MIXER_OSS_DEFAULT); @@ -78,9 +90,9 @@ oss_mixer_init(const struct config_param *param) if (om->control != NULL) { om->volume_control = oss_find_mixer(om->control); if (om->volume_control < 0) { - g_warning("mixer control \"%s\" not found", - om->control); g_free(om); + g_set_error(error_r, oss_mixer_quark(), 0, + "no such mixer control: %s", om->control); return NULL; } } else @@ -108,13 +120,15 @@ oss_mixer_close(struct mixer *data) } static bool -oss_mixer_open(struct mixer *data) +oss_mixer_open(struct mixer *data, GError **error_r) { struct oss_mixer *om = (struct oss_mixer *) data; - om->device_fd = open(om->device, O_RDONLY); + om->device_fd = open_cloexec(om->device, O_RDONLY, 0); if (om->device_fd < 0) { - g_warning("Unable to open oss mixer \"%s\"\n", om->device); + g_set_error(error_r, oss_mixer_quark(), errno, + "failed to open %s: %s", + om->device, g_strerror(errno)); return false; } @@ -122,14 +136,17 @@ oss_mixer_open(struct mixer *data) int devmask = 0; if (ioctl(om->device_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) { - g_warning("errors getting read_devmask for oss mixer\n"); + g_set_error(error_r, oss_mixer_quark(), errno, + "READ_DEVMASK failed: %s", + g_strerror(errno)); oss_mixer_close(data); return false; } if (((1 << om->volume_control) & devmask) == 0) { - g_warning("mixer control \"%s\" not usable\n", - om->control); + g_set_error(error_r, oss_mixer_quark(), 0, + "mixer control \"%s\" not usable", + om->control); oss_mixer_close(data); return false; } @@ -138,7 +155,7 @@ oss_mixer_open(struct mixer *data) } static int -oss_mixer_get_volume(struct mixer *mixer) +oss_mixer_get_volume(struct mixer *mixer, GError **error_r) { struct oss_mixer *om = (struct oss_mixer *)mixer; int left, right, level; @@ -148,7 +165,9 @@ oss_mixer_get_volume(struct mixer *mixer) ret = ioctl(om->device_fd, MIXER_READ(om->volume_control), &level); if (ret < 0) { - g_warning("unable to read oss volume\n"); + g_set_error(error_r, oss_mixer_quark(), errno, + "failed to read OSS volume: %s", + g_strerror(errno)); return false; } @@ -164,7 +183,7 @@ oss_mixer_get_volume(struct mixer *mixer) } static bool -oss_mixer_set_volume(struct mixer *mixer, unsigned volume) +oss_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) { struct oss_mixer *om = (struct oss_mixer *)mixer; int level; @@ -177,14 +196,16 @@ oss_mixer_set_volume(struct mixer *mixer, unsigned volume) ret = ioctl(om->device_fd, MIXER_WRITE(om->volume_control), &level); if (ret < 0) { - g_warning("unable to set oss volume\n"); + g_set_error(error_r, oss_mixer_quark(), errno, + "failed to set OSS volume: %s", + g_strerror(errno)); return false; } return true; } -const struct mixer_plugin oss_mixer = { +const struct mixer_plugin oss_mixer_plugin = { .init = oss_mixer_init, .finish = oss_mixer_finish, .open = oss_mixer_open, diff --git a/src/mixer/pulse_mixer.c b/src/mixer/pulse_mixer.c deleted file mode 100644 index 5d9ce0475..000000000 --- a/src/mixer/pulse_mixer.c +++ /dev/null @@ -1,382 +0,0 @@ -/* - * Copyright (C) 2003-2009 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "mixer_api.h" -#include "conf.h" - -#include <glib.h> -#include <pulse/volume.h> -#include <pulse/pulseaudio.h> - -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "pulse_mixer" - -struct pulse_mixer { - struct mixer base; - - const char *server; - const char *sink; - const char *output_name; - - uint32_t index; - bool online; - - struct pa_context *context; - struct pa_threaded_mainloop *mainloop; - struct pa_cvolume volume; - -}; - -/** - * \brief waits for a pulseaudio operation to finish, frees it and - * unlocks the mainloop - * \param operation the operation to wait for - * \return true if operation has finished normally (DONE state), - * false otherwise - */ -static bool -pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop, - struct pa_operation *operation) -{ - pa_operation_state_t state; - - assert(mainloop != NULL); - assert(operation != NULL); - - state = pa_operation_get_state(operation); - while (state == PA_OPERATION_RUNNING) { - pa_threaded_mainloop_wait(mainloop); - state = pa_operation_get_state(operation); - } - - pa_operation_unref(operation); - - return state == PA_OPERATION_DONE; -} - -static void -sink_input_cb(G_GNUC_UNUSED pa_context *context, const pa_sink_input_info *i, - int eol, void *userdata) -{ - - struct pulse_mixer *pm = userdata; - - if (eol) { - g_debug("eol error sink_input_cb"); - return; - } - - if (i == NULL) { - g_debug("Sink input callback failure"); - return; - } - - g_debug("sink input cb %s, index %d ",i->name,i->index); - - if (strcmp(i->name,pm->output_name) == 0) { - pm->index = i->index; - pm->online = true; - pm->volume = i->volume; - } else - g_debug("bad name"); -} - -static void -sink_input_vol(G_GNUC_UNUSED pa_context *context, const pa_sink_input_info *i, - int eol, void *userdata) -{ - - struct pulse_mixer *pm = userdata; - - if (eol) { - g_debug("eol error sink_input_vol"); - return; - } - - if (i == NULL) { - g_debug("Sink input callback failure"); - return; - } - - g_debug("sink input vol %s, index %d ", i->name, i->index); - - pm->volume = i->volume; - - pa_threaded_mainloop_signal(pm->mainloop, 0); -} - -static void -subscribe_cb(pa_context *c, pa_subscription_event_type_t t, - uint32_t idx, void *userdata) -{ - - struct pulse_mixer *pm = userdata; - - g_debug("subscribe call back"); - - switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { - case PA_SUBSCRIPTION_EVENT_SINK_INPUT: - if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == - PA_SUBSCRIPTION_EVENT_REMOVE && - pm->index == idx) - pm->online = false; - else { - pa_operation *o; - - o = pa_context_get_sink_input_info(c, idx, - sink_input_cb, pm); - if (o == NULL) { - g_debug("pa_context_get_sink_input_info() failed"); - return; - } - - pa_operation_unref(o); - } - - break; - } -} - -static void -context_state_cb(pa_context *context, void *userdata) -{ - struct pulse_mixer *pm = userdata; - - switch (pa_context_get_state(context)) { - case PA_CONTEXT_READY: { - pa_operation *o; - - pa_context_set_subscribe_callback(context, subscribe_cb, pm); - - o = pa_context_subscribe(context, - (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT, - NULL, NULL); - if (o == NULL) { - g_debug("pa_context_subscribe() failed"); - return; - } - - pa_operation_unref(o); - - o = pa_context_get_sink_input_info_list(context, - sink_input_cb, pm); - if (o == NULL) { - g_debug("pa_context_get_sink_input_info_list() failed"); - return; - } - - pa_operation_unref(o); - - pa_threaded_mainloop_signal(pm->mainloop, 0); - break; - } - - case PA_CONTEXT_UNCONNECTED: - case PA_CONTEXT_CONNECTING: - case PA_CONTEXT_AUTHORIZING: - case PA_CONTEXT_SETTING_NAME: - break; - case PA_CONTEXT_TERMINATED: - case PA_CONTEXT_FAILED: - pa_threaded_mainloop_signal(pm->mainloop, 0); - break; - } -} - - -static struct mixer * -pulse_mixer_init(const struct config_param *param) -{ - struct pulse_mixer *pm = g_new(struct pulse_mixer,1); - mixer_init(&pm->base, &pulse_mixer); - - pm->online = false; - - pm->server = config_get_block_string(param, "server", NULL); - pm->sink = config_get_block_string(param, "sink", NULL); - pm->output_name = config_get_block_string(param, "name", NULL); - - return &pm->base; -} - -static void -pulse_mixer_finish(struct mixer *data) -{ - struct pulse_mixer *pm = (struct pulse_mixer *) data; - - g_free(pm); -} - -static bool -pulse_mixer_setup(struct pulse_mixer *pm) -{ - pa_context_set_state_callback(pm->context, context_state_cb, pm); - - if (pa_context_connect(pm->context, pm->server, - (pa_context_flags_t)0, NULL) < 0) { - g_debug("context server fail"); - return false; - } - - pa_threaded_mainloop_lock(pm->mainloop); - - if (pa_threaded_mainloop_start(pm->mainloop) < 0) { - pa_threaded_mainloop_unlock(pm->mainloop); - g_debug("error start mainloop"); - return false; - } - - pa_threaded_mainloop_wait(pm->mainloop); - - if (pa_context_get_state(pm->context) != PA_CONTEXT_READY) { - pa_threaded_mainloop_unlock(pm->mainloop); - g_debug("error context not ready"); - return false; - } - - pa_threaded_mainloop_unlock(pm->mainloop); - - return true; -} - -static bool -pulse_mixer_open(struct mixer *data) -{ - struct pulse_mixer *pm = (struct pulse_mixer *) data; - - g_debug("pulse mixer open"); - - pm->index = 0; - pm->online = false; - - pm->mainloop = pa_threaded_mainloop_new(); - if (pm->mainloop == NULL) { - g_debug("failed mainloop"); - return false; - } - - pm->context = pa_context_new(pa_threaded_mainloop_get_api(pm->mainloop), - "Mixer mpd"); - if (pm->context == NULL) { - pa_threaded_mainloop_stop(pm->mainloop); - pa_threaded_mainloop_free(pm->mainloop); - g_debug("failed context"); - return false; - } - - if (!pulse_mixer_setup(pm)) { - pa_threaded_mainloop_stop(pm->mainloop); - pa_context_disconnect(pm->context); - pa_context_unref(pm->context); - pa_threaded_mainloop_free(pm->mainloop); - return false; - } - - return true; -} - -static void -pulse_mixer_close(struct mixer *data) -{ - struct pulse_mixer *pm = (struct pulse_mixer *) data; - - pa_threaded_mainloop_stop(pm->mainloop); - pa_context_disconnect(pm->context); - pa_context_unref(pm->context); - pa_threaded_mainloop_free(pm->mainloop); - - pm->online = false; -} - -static int -pulse_mixer_get_volume(struct mixer *mixer) -{ - struct pulse_mixer *pm = (struct pulse_mixer *) mixer; - int ret; - pa_operation *o; - - pa_threaded_mainloop_lock(pm->mainloop); - - if (!pm->online) { - pa_threaded_mainloop_unlock(pm->mainloop); - return false; - } - - o = pa_context_get_sink_input_info(pm->context, pm->index, - sink_input_vol, pm); - if (o == NULL) { - pa_threaded_mainloop_unlock(pm->mainloop); - g_debug("pa_context_get_sink_input_info() failed"); - return false; - } - - if (!pulse_wait_for_operation(pm->mainloop, o)) { - pa_threaded_mainloop_unlock(pm->mainloop); - return false; - } - - ret = pm->online - ? (int)((100*(pa_cvolume_avg(&pm->volume)+1))/PA_VOLUME_NORM) - : -1; - - pa_threaded_mainloop_unlock(pm->mainloop); - - return ret; -} - -static bool -pulse_mixer_set_volume(struct mixer *mixer, unsigned volume) -{ - struct pulse_mixer *pm = (struct pulse_mixer *) mixer; - struct pa_cvolume cvolume; - pa_operation *o; - - pa_threaded_mainloop_lock(pm->mainloop); - - if (!pm->online) { - pa_threaded_mainloop_unlock(pm->mainloop); - return false; - } - - pa_cvolume_set(&cvolume, pm->volume.channels, - (pa_volume_t)volume * PA_VOLUME_NORM / 100 + 0.5); - - o = pa_context_set_sink_input_volume(pm->context, pm->index, - &cvolume, NULL, NULL); - pa_threaded_mainloop_unlock(pm->mainloop); - if (o == NULL) { - g_debug("pa_context_set_sink_input_volume() failed"); - return false; - } - - pa_operation_unref(o); - - return true; -} - -const struct mixer_plugin pulse_mixer = { - .init = pulse_mixer_init, - .finish = pulse_mixer_finish, - .open = pulse_mixer_open, - .close = pulse_mixer_close, - .get_volume = pulse_mixer_get_volume, - .set_volume = pulse_mixer_set_volume, -}; diff --git a/src/mixer/pulse_mixer_plugin.c b/src/mixer/pulse_mixer_plugin.c new file mode 100644 index 000000000..2be0b8266 --- /dev/null +++ b/src/mixer/pulse_mixer_plugin.c @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "pulse_mixer_plugin.h" +#include "mixer_api.h" +#include "output/pulse_output_plugin.h" +#include "conf.h" +#include "event_pipe.h" + +#include <glib.h> + +#include <pulse/thread-mainloop.h> +#include <pulse/context.h> +#include <pulse/introspect.h> +#include <pulse/stream.h> +#include <pulse/subscribe.h> +#include <pulse/error.h> + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "pulse_mixer" + +struct pulse_mixer { + struct mixer base; + + struct pulse_output *output; + + bool online; + struct pa_cvolume volume; + +}; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +pulse_mixer_quark(void) +{ + return g_quark_from_static_string("pulse_mixer"); +} + +static void +pulse_mixer_offline(struct pulse_mixer *pm) +{ + if (!pm->online) + return; + + pm->online = false; + + event_pipe_emit(PIPE_EVENT_MIXER); +} + +/** + * Callback invoked by pulse_mixer_update(). Receives the new mixer + * value. + */ +static void +pulse_mixer_volume_cb(G_GNUC_UNUSED pa_context *context, const pa_sink_input_info *i, + int eol, void *userdata) +{ + struct pulse_mixer *pm = userdata; + + if (eol) + return; + + if (i == NULL) { + pulse_mixer_offline(pm); + return; + } + + pm->online = true; + pm->volume = i->volume; + + event_pipe_emit(PIPE_EVENT_MIXER); +} + +static void +pulse_mixer_update(struct pulse_mixer *pm, + struct pa_context *context, struct pa_stream *stream) +{ + pa_operation *o; + + assert(context != NULL); + assert(stream != NULL); + assert(pa_stream_get_state(stream) == PA_STREAM_READY); + + o = pa_context_get_sink_input_info(context, + pa_stream_get_index(stream), + pulse_mixer_volume_cb, pm); + if (o == NULL) { + g_warning("pa_context_get_sink_input_info() failed: %s", + pa_strerror(pa_context_errno(context))); + pulse_mixer_offline(pm); + return; + } + + pa_operation_unref(o); +} + +void +pulse_mixer_on_connect(G_GNUC_UNUSED struct pulse_mixer *pm, + struct pa_context *context) +{ + pa_operation *o; + + assert(context != NULL); + + o = pa_context_subscribe(context, + (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT, + NULL, NULL); + if (o == NULL) { + g_warning("pa_context_subscribe() failed: %s", + pa_strerror(pa_context_errno(context))); + return; + } + + pa_operation_unref(o); +} + +void +pulse_mixer_on_disconnect(struct pulse_mixer *pm) +{ + pulse_mixer_offline(pm); +} + +void +pulse_mixer_on_change(struct pulse_mixer *pm, + struct pa_context *context, struct pa_stream *stream) +{ + pulse_mixer_update(pm, context, stream); +} + +static struct mixer * +pulse_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param, + GError **error_r) +{ + struct pulse_mixer *pm; + struct pulse_output *po = ao; + + if (ao == NULL) { + g_set_error(error_r, pulse_mixer_quark(), 0, + "The pulse mixer cannot work without the audio output"); + return false; + } + + pm = g_new(struct pulse_mixer,1); + mixer_init(&pm->base, &pulse_mixer_plugin); + + pm->online = false; + pm->output = po; + + pulse_output_set_mixer(po, pm); + + return &pm->base; +} + +static void +pulse_mixer_finish(struct mixer *data) +{ + struct pulse_mixer *pm = (struct pulse_mixer *) data; + + pulse_output_clear_mixer(pm->output, pm); + + /* free resources */ + + g_free(pm); +} + +static int +pulse_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r) +{ + struct pulse_mixer *pm = (struct pulse_mixer *) mixer; + int ret; + + pa_threaded_mainloop_lock(pm->output->mainloop); + + ret = pm->online + ? (int)((100*(pa_cvolume_avg(&pm->volume)+1))/PA_VOLUME_NORM) + : -1; + + pa_threaded_mainloop_unlock(pm->output->mainloop); + + return ret; +} + +static bool +pulse_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) +{ + struct pulse_mixer *pm = (struct pulse_mixer *) mixer; + struct pa_cvolume cvolume; + bool success; + + pa_threaded_mainloop_lock(pm->output->mainloop); + if (!pm->online) { + pa_threaded_mainloop_unlock(pm->output->mainloop); + g_set_error(error_r, pulse_mixer_quark(), 0, "disconnected"); + return false; + } + + pa_cvolume_set(&cvolume, pm->volume.channels, + (pa_volume_t)volume * PA_VOLUME_NORM / 100 + 0.5); + success = pulse_output_set_volume(pm->output, &cvolume, error_r); + if (success) + pm->volume = cvolume; + pa_threaded_mainloop_unlock(pm->output->mainloop); + + return success; +} + +const struct mixer_plugin pulse_mixer_plugin = { + .init = pulse_mixer_init, + .finish = pulse_mixer_finish, + .get_volume = pulse_mixer_get_volume, + .set_volume = pulse_mixer_set_volume, +}; diff --git a/src/mixer/pulse_mixer_plugin.h b/src/mixer/pulse_mixer_plugin.h new file mode 100644 index 000000000..be199f688 --- /dev/null +++ b/src/mixer/pulse_mixer_plugin.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PULSE_MIXER_PLUGIN_H +#define MPD_PULSE_MIXER_PLUGIN_H + +#include <pulse/def.h> + +struct pulse_mixer; +struct pa_context; +struct pa_stream; + +void +pulse_mixer_on_connect(struct pulse_mixer *pm, struct pa_context *context); + +void +pulse_mixer_on_disconnect(struct pulse_mixer *pm); + +void +pulse_mixer_on_change(struct pulse_mixer *pm, + struct pa_context *context, struct pa_stream *stream); + +#endif diff --git a/src/mixer/software_mixer_plugin.c b/src/mixer/software_mixer_plugin.c new file mode 100644 index 000000000..93802e977 --- /dev/null +++ b/src/mixer/software_mixer_plugin.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "software_mixer_plugin.h" +#include "mixer_api.h" +#include "filter_plugin.h" +#include "filter_registry.h" +#include "filter/volume_filter_plugin.h" +#include "pcm_volume.h" + +#include <assert.h> +#include <math.h> + +struct software_mixer { + /** the base mixer class */ + struct mixer base; + + struct filter *filter; + + unsigned volume; +}; + +static struct mixer * +software_mixer_init(G_GNUC_UNUSED void *ao, + G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct software_mixer *sm = g_new(struct software_mixer, 1); + + mixer_init(&sm->base, &software_mixer_plugin); + + sm->filter = filter_new(&volume_filter_plugin, NULL, NULL); + assert(sm->filter != NULL); + + sm->volume = 100; + + return &sm->base; +} + +static void +software_mixer_finish(struct mixer *data) +{ + struct software_mixer *sm = (struct software_mixer *)data; + + g_free(sm); +} + +static int +software_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r) +{ + struct software_mixer *sm = (struct software_mixer *)mixer; + + return sm->volume; +} + +static bool +software_mixer_set_volume(struct mixer *mixer, unsigned volume, + G_GNUC_UNUSED GError **error_r) +{ + struct software_mixer *sm = (struct software_mixer *)mixer; + + assert(volume <= 100); + + sm->volume = volume; + + if (volume >= 100) + volume = PCM_VOLUME_1; + else if (volume > 0) + volume = pcm_float_to_volume((exp(volume / 25.0) - 1) / + (54.5981500331F - 1)); + + volume_filter_set(sm->filter, volume); + return true; +} + +const struct mixer_plugin software_mixer_plugin = { + .init = software_mixer_init, + .finish = software_mixer_finish, + .get_volume = software_mixer_get_volume, + .set_volume = software_mixer_set_volume, + .global = true, +}; + +struct filter * +software_mixer_get_filter(struct mixer *mixer) +{ + struct software_mixer *sm = (struct software_mixer *)mixer; + + assert(sm->base.plugin == &software_mixer_plugin); + + return sm->filter; +} diff --git a/src/mixer/software_mixer_plugin.h b/src/mixer/software_mixer_plugin.h new file mode 100644 index 000000000..3bd07ac62 --- /dev/null +++ b/src/mixer/software_mixer_plugin.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2010 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/winmm_mixer_plugin.c b/src/mixer/winmm_mixer_plugin.c new file mode 100644 index 000000000..e1e8a7a45 --- /dev/null +++ b/src/mixer/winmm_mixer_plugin.c @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "mixer_api.h" +#include "output_api.h" +#include "output/winmm_output_plugin.h" + +#include <assert.h> +#include <math.h> +#include <windows.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "winmm_mixer" + +struct winmm_mixer { + struct mixer base; + struct winmm_output *output; +}; + +static inline GQuark +winmm_mixer_quark(void) +{ + return g_quark_from_static_string("winmm_mixer"); +} + +static inline int +winmm_volume_decode(DWORD volume) +{ + return lround((volume & 0xFFFF) / 655.35); +} + +static inline DWORD +winmm_volume_encode(int volume) +{ + int value = lround(volume * 655.35); + return MAKELONG(value, value); +} + +static struct mixer * +winmm_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + assert(ao != NULL); + + struct winmm_mixer *wm = g_new(struct winmm_mixer, 1); + mixer_init(&wm->base, &winmm_mixer_plugin); + wm->output = (struct winmm_output *) ao; + + return &wm->base; +} + +static void +winmm_mixer_finish(struct mixer *data) +{ + g_free(data); +} + +static int +winmm_mixer_get_volume(struct mixer *mixer, GError **error_r) +{ + struct winmm_mixer *wm = (struct winmm_mixer *) mixer; + DWORD volume; + HWAVEOUT handle = winmm_output_get_handle(wm->output); + MMRESULT result = waveOutGetVolume(handle, &volume); + + if (result != MMSYSERR_NOERROR) { + g_set_error(error_r, 0, winmm_mixer_quark(), + "Failed to get winmm volume"); + return -1; + } + + return winmm_volume_decode(volume); +} + +static bool +winmm_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) +{ + struct winmm_mixer *wm = (struct winmm_mixer *) mixer; + DWORD value = winmm_volume_encode(volume); + HWAVEOUT handle = winmm_output_get_handle(wm->output); + MMRESULT result = waveOutSetVolume(handle, value); + + if (result != MMSYSERR_NOERROR) { + g_set_error(error_r, 0, winmm_mixer_quark(), + "Failed to set winmm volume"); + return false; + } + + return true; +} + +const struct mixer_plugin winmm_mixer_plugin = { + .init = winmm_mixer_init, + .finish = winmm_mixer_finish, + .get_volume = winmm_mixer_get_volume, + .set_volume = winmm_mixer_set_volume, +}; diff --git a/src/mixer_all.c b/src/mixer_all.c index 252cb61ab..ffe610b91 100644 --- a/src/mixer_all.c +++ b/src/mixer_all.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,11 +17,15 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "mixer_all.h" #include "mixer_control.h" #include "output_all.h" #include "output_plugin.h" #include "output_internal.h" +#include "pcm_volume.h" +#include "mixer_api.h" +#include "mixer_list.h" #include <glib.h> @@ -35,6 +39,8 @@ output_mixer_get_volume(unsigned i) { struct audio_output *output; struct mixer *mixer; + int volume; + GError *error = NULL; assert(i < audio_output_count()); @@ -46,7 +52,14 @@ output_mixer_get_volume(unsigned i) if (mixer == NULL) return -1; - return mixer_get_volume(mixer); + volume = mixer_get_volume(mixer, &error); + if (volume < 0 && error != NULL) { + g_warning("Failed to read mixer for '%s': %s", + output->name, error->message); + g_error_free(error); + } + + return volume; } int @@ -70,12 +83,15 @@ mixer_all_get_volume(void) } static bool -output_mixer_set_volume(unsigned i, int volume, bool relative) +output_mixer_set_volume(unsigned i, unsigned volume) { struct audio_output *output; struct mixer *mixer; + bool success; + GError *error = NULL; assert(i < audio_output_count()); + assert(volume <= 100); output = audio_output_get(i); if (!output->enabled) @@ -85,31 +101,81 @@ output_mixer_set_volume(unsigned i, int volume, bool relative) if (mixer == NULL) return false; - if (relative) { - int prev = mixer_get_volume(mixer); - if (prev < 0) - return false; - - volume += prev; + success = mixer_set_volume(mixer, volume, &error); + if (!success && error != NULL) { + g_warning("Failed to set mixer for '%s': %s", + output->name, error->message); + g_error_free(error); } - if (volume > 100) - volume = 100; - else if (volume < 0) - volume = 0; - - return mixer_set_volume(mixer, volume); + return success; } bool -mixer_all_set_volume(int volume, bool relative) +mixer_all_set_volume(unsigned volume) { bool success = false; unsigned count = audio_output_count(); + assert(volume <= 100); + for (unsigned i = 0; i < count; i++) - success = output_mixer_set_volume(i, volume, relative) + success = output_mixer_set_volume(i, volume) || success; return success; } + +static int +output_mixer_get_software_volume(unsigned i) +{ + struct audio_output *output; + struct mixer *mixer; + + assert(i < audio_output_count()); + + output = audio_output_get(i); + if (!output->enabled) + return -1; + + mixer = output->mixer; + if (mixer == NULL || mixer->plugin != &software_mixer_plugin) + return -1; + + return mixer_get_volume(mixer, NULL); +} + +int +mixer_all_get_software_volume(void) +{ + unsigned count = audio_output_count(), ok = 0; + int volume, total = 0; + + for (unsigned i = 0; i < count; i++) { + volume = output_mixer_get_software_volume(i); + if (volume >= 0) { + total += volume; + ++ok; + } + } + + if (ok == 0) + return -1; + + return total / ok; +} + +void +mixer_all_set_software_volume(unsigned volume) +{ + unsigned count = audio_output_count(); + + assert(volume <= PCM_VOLUME_1); + + for (unsigned i = 0; i < count; i++) { + struct audio_output *output = audio_output_get(i); + if (output->mixer != NULL && + output->mixer->plugin == &software_mixer_plugin) + mixer_set_volume(output->mixer, volume, NULL); + } +} diff --git a/src/mixer_all.h b/src/mixer_all.h index 66c4988de..cece23292 100644 --- a/src/mixer_all.h +++ b/src/mixer_all.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -37,11 +37,26 @@ mixer_all_get_volume(void); /** * Sets the volume on all available mixers. * - * @param volume the volume (range 0..100 or -100..100 if #relative) - * @param relative if true, then the #volume is added to the current value + * @param volume the volume (range 0..100) * @return true on success, false on failure */ bool -mixer_all_set_volume(int volume, bool relative); +mixer_all_set_volume(unsigned volume); + +/** + * Similar to mixer_all_get_volume(), but gets the volume only for + * software mixers. See #software_mixer_plugin. This function fails + * if no software mixer is configured. + */ +int +mixer_all_get_software_volume(void); + +/** + * Similar to mixer_all_set_volume(), but sets the volume only for + * software mixers. See #software_mixer_plugin. This function cannot + * fail, because the underlying software mixers cannot fail either. + */ +void +mixer_all_set_software_volume(unsigned volume); #endif diff --git a/src/mixer_api.c b/src/mixer_api.c index cff23a397..4c8959fb8 100644 --- a/src/mixer_api.c +++ b/src/mixer_api.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "mixer_api.h" #undef G_LOG_DOMAIN diff --git a/src/mixer_api.h b/src/mixer_api.h index fe27f5119..26c001703 100644 --- a/src/mixer_api.h +++ b/src/mixer_api.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/mixer_control.c b/src/mixer_control.c index e19b82d65..458b3abc1 100644 --- a/src/mixer_control.c +++ b/src/mixer_control.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,38 +17,26 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "mixer_control.h" #include "mixer_api.h" -#include <glib.h> - #include <assert.h> #include <stddef.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "mixer" -static bool mixers_enabled = true; - -void -mixer_disable_all(void) -{ - g_debug("mixer api is disabled"); - mixers_enabled = false; -} - struct mixer * -mixer_new(const struct mixer_plugin *plugin, const struct config_param *param) +mixer_new(const struct mixer_plugin *plugin, void *ao, + const struct config_param *param, + GError **error_r) { struct mixer *mixer; - //mixers are disabled (by using software volume) - if (!mixers_enabled) { - return NULL; - } assert(plugin != NULL); - mixer = plugin->init(param); + mixer = plugin->init(ao, param, error_r); assert(mixer == NULL || mixer->plugin == plugin); @@ -72,7 +60,7 @@ mixer_free(struct mixer *mixer) } bool -mixer_open(struct mixer *mixer) +mixer_open(struct mixer *mixer, GError **error_r) { bool success; @@ -83,8 +71,10 @@ mixer_open(struct mixer *mixer) if (mixer->open) success = true; + else if (mixer->plugin->open == NULL) + success = mixer->open = true; else - success = mixer->open = mixer->plugin->open(mixer); + success = mixer->open = mixer->plugin->open(mixer, error_r); mixer->failed = !success; @@ -100,7 +90,9 @@ mixer_close_internal(struct mixer *mixer) assert(mixer->plugin != NULL); assert(mixer->open); - mixer->plugin->close(mixer); + if (mixer->plugin->close != NULL) + mixer->plugin->close(mixer); + mixer->open = false; } @@ -140,21 +132,26 @@ mixer_failed(struct mixer *mixer) } int -mixer_get_volume(struct mixer *mixer) +mixer_get_volume(struct mixer *mixer, GError **error_r) { int volume; assert(mixer != NULL); - if (mixer->plugin->global && !mixer->failed && !mixer_open(mixer)) + if (mixer->plugin->global && !mixer->failed && + !mixer_open(mixer, error_r)) return -1; g_mutex_lock(mixer->mutex); if (mixer->open) { - volume = mixer->plugin->get_volume(mixer); - if (volume < 0) + GError *error = NULL; + + volume = mixer->plugin->get_volume(mixer, &error); + if (volume < 0 && error != NULL) { + g_propagate_error(error_r, error); mixer_failed(mixer); + } } else volume = -1; @@ -164,22 +161,21 @@ mixer_get_volume(struct mixer *mixer) } bool -mixer_set_volume(struct mixer *mixer, unsigned volume) +mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) { bool success; assert(mixer != NULL); assert(volume <= 100); - if (mixer->plugin->global && !mixer->failed && !mixer_open(mixer)) + if (mixer->plugin->global && !mixer->failed && + !mixer_open(mixer, error_r)) return false; g_mutex_lock(mixer->mutex); if (mixer->open) { - success = mixer->plugin->set_volume(mixer, volume); - if (!success) - mixer_failed(mixer); + success = mixer->plugin->set_volume(mixer, volume, error_r); } else success = false; diff --git a/src/mixer_control.h b/src/mixer_control.h index 0f73e8f75..1f48e8ca5 100644 --- a/src/mixer_control.h +++ b/src/mixer_control.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,23 +25,24 @@ #ifndef MPD_MIXER_CONTROL_H #define MPD_MIXER_CONTROL_H +#include <glib.h> + #include <stdbool.h> struct mixer; struct mixer_plugin; struct config_param; -void -mixer_disable_all(void); - struct mixer * -mixer_new(const struct mixer_plugin *plugin, const struct config_param *param); +mixer_new(const struct mixer_plugin *plugin, void *ao, + const struct config_param *param, + GError **error_r); void mixer_free(struct mixer *mixer); bool -mixer_open(struct mixer *mixer); +mixer_open(struct mixer *mixer, GError **error_r); void mixer_close(struct mixer *mixer); @@ -54,9 +55,9 @@ void mixer_auto_close(struct mixer *mixer); int -mixer_get_volume(struct mixer *mixer); +mixer_get_volume(struct mixer *mixer, GError **error_r); bool -mixer_set_volume(struct mixer *mixer, unsigned volume); +mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r); #endif diff --git a/src/mixer_list.h b/src/mixer_list.h index 7db4a00d8..a472c8807 100644 --- a/src/mixer_list.h +++ b/src/mixer_list.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,8 +25,10 @@ #ifndef MPD_MIXER_LIST_H #define MPD_MIXER_LIST_H -extern const struct mixer_plugin alsa_mixer; -extern const struct mixer_plugin oss_mixer; -extern const struct mixer_plugin pulse_mixer; +extern const struct mixer_plugin software_mixer_plugin; +extern const struct mixer_plugin alsa_mixer_plugin; +extern const struct mixer_plugin oss_mixer_plugin; +extern const struct mixer_plugin pulse_mixer_plugin; +extern const struct mixer_plugin winmm_mixer_plugin; #endif diff --git a/src/mixer_plugin.h b/src/mixer_plugin.h index 2b9b440e5..0915a03f3 100644 --- a/src/mixer_plugin.h +++ b/src/mixer_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,6 +27,8 @@ #ifndef MPD_MIXER_PLUGIN_H #define MPD_MIXER_PLUGIN_H +#include <glib.h> + #include <stdbool.h> struct config_param; @@ -35,8 +37,16 @@ struct mixer; struct mixer_plugin { /** * Alocates and configures a mixer device. + * + * @param ao the pointer returned by audio_output_plugin.init + * @param param the configuration section, or NULL if there is + * no configuration + * @param error_r location to store the error occuring, or + * NULL to ignore errors + * @return a mixer object, or NULL on error */ - struct mixer *(*init)(const struct config_param *param); + struct mixer *(*init)(void *ao, const struct config_param *param, + GError **error_r); /** * Finish and free mixer data @@ -45,8 +55,12 @@ struct mixer_plugin { /** * Open mixer device + * + * @param error_r location to store the error occuring, or + * NULL to ignore errors + * @return true on success, false on error */ - bool (*open)(struct mixer *data); + bool (*open)(struct mixer *data, GError **error_r); /** * Close mixer device @@ -56,18 +70,23 @@ struct mixer_plugin { /** * Reads the current volume. * - * @return the current volume (0..100 including) or -1 on - * error + * @param error_r location to store the error occuring, or + * NULL to ignore errors + * @return the current volume (0..100 including) or -1 if + * unavailable or on error (error_r set, mixer will be closed) */ - int (*get_volume)(struct mixer *mixer); + int (*get_volume)(struct mixer *mixer, GError **error_r); /** * Sets the volume. * + * @param error_r location to store the error occuring, or + * NULL to ignore errors * @param volume the new volume (0..100 including) - * @return true on success + * @return true on success, false on error */ - bool (*set_volume)(struct mixer *mixer, unsigned volume); + bool (*set_volume)(struct mixer *mixer, unsigned volume, + GError **error_r); /** * If true, then the mixer is automatically opened, even if diff --git a/src/mixer_type.c b/src/mixer_type.c new file mode 100644 index 000000000..4f347dd94 --- /dev/null +++ b/src/mixer_type.c @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "mixer_type.h" + +#include <assert.h> +#include <string.h> + +enum mixer_type +mixer_type_parse(const char *input) +{ + assert(input != NULL); + + if (strcmp(input, "none") == 0 || strcmp(input, "disabled") == 0) + return MIXER_TYPE_NONE; + else if (strcmp(input, "hardware") == 0) + return MIXER_TYPE_HARDWARE; + else if (strcmp(input, "software") == 0) + return MIXER_TYPE_SOFTWARE; + else + return MIXER_TYPE_UNKNOWN; +} diff --git a/src/mixer_type.h b/src/mixer_type.h new file mode 100644 index 000000000..fd1c5576c --- /dev/null +++ b/src/mixer_type.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2010 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/mpd_error.h b/src/mpd_error.h new file mode 100644 index 000000000..47618d03c --- /dev/null +++ b/src/mpd_error.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2010 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_ERROR_H +#define MPD_ERROR_H + +#include <stdlib.h> + +/* This macro is used as an intermediate step to a proper error handling + * using GError in mpd. It is used for unrecoverable error conditions + * and exits immediately. The long-term goal is to replace this macro by + * proper error handling. */ + +#define MPD_ERROR(...) \ + do { \ + g_critical(__VA_ARGS__); \ + exit(EXIT_FAILURE); \ + } while(0) + +#endif diff --git a/src/notify.c b/src/notify.c index 3b45c22b4..d148a4bfc 100644 --- a/src/notify.c +++ b/src/notify.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "notify.h" void notify_init(struct notify *notify) diff --git a/src/notify.h b/src/notify.h index 0655bc6b7..0c657f2fb 100644 --- a/src/notify.h +++ b/src/notify.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/open.h b/src/open.h new file mode 100644 index 000000000..e39c64a97 --- /dev/null +++ b/src/open.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2010 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 + * + * Portability macros for opening files. + */ + +#ifndef MPD_OPEN_H +#define MPD_OPEN_H + +#include <fcntl.h> + +/* On Windows, files are opened in "text" mode by default, and the C + library will mangle data being read/written; this must be switched + off by specifying the proprietary "O_BINARY" flag. That sucks! */ +#ifndef O_BINARY +#ifdef _O_BINARY +#define O_BINARY _O_BINARY +#else +#define O_BINARY 0 +#endif +#endif + +#endif diff --git a/src/output/alsa_plugin.c b/src/output/alsa_plugin.c index 818c83ca2..9177fabe4 100644 --- a/src/output/alsa_plugin.c +++ b/src/output/alsa_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,7 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../output_api.h" +#include "config.h" +#include "output_api.h" #include "mixer_list.h" #include <glib.h> @@ -69,6 +70,16 @@ struct alsa_data { /** the size of one audio frame */ size_t frame_size; + + /** + * The size of one period, in number of frames. + */ + snd_pcm_uframes_t period_frames; + + /** + * The number of frames written in the current period. + */ + snd_pcm_uframes_t period_position; }; /** @@ -172,15 +183,148 @@ alsa_test_default_device(void) } static snd_pcm_format_t -get_bitformat(const struct audio_format *af) +get_bitformat(enum sample_format sample_format) +{ + switch (sample_format) { + case SAMPLE_FORMAT_S8: + return SND_PCM_FORMAT_S8; + + case SAMPLE_FORMAT_S16: + return SND_PCM_FORMAT_S16; + + case SAMPLE_FORMAT_S24_P32: + return SND_PCM_FORMAT_S24; + + case SAMPLE_FORMAT_S24: + return G_BYTE_ORDER == G_BIG_ENDIAN + ? SND_PCM_FORMAT_S24_3BE + : SND_PCM_FORMAT_S24_3LE; + + case SAMPLE_FORMAT_S32: + return SND_PCM_FORMAT_S32; + + default: + return SND_PCM_FORMAT_UNKNOWN; + } +} + +static snd_pcm_format_t +byteswap_bitformat(snd_pcm_format_t fmt) +{ + switch(fmt) { + case SND_PCM_FORMAT_S16_LE: return SND_PCM_FORMAT_S16_BE; + case SND_PCM_FORMAT_S24_LE: return SND_PCM_FORMAT_S24_BE; + case SND_PCM_FORMAT_S32_LE: return SND_PCM_FORMAT_S32_BE; + case SND_PCM_FORMAT_S16_BE: return SND_PCM_FORMAT_S16_LE; + case SND_PCM_FORMAT_S24_BE: return SND_PCM_FORMAT_S24_LE; + + case SND_PCM_FORMAT_S24_3BE: + return SND_PCM_FORMAT_S24_3LE; + + case SND_PCM_FORMAT_S24_3LE: + return SND_PCM_FORMAT_S24_3BE; + + case SND_PCM_FORMAT_S32_BE: return SND_PCM_FORMAT_S32_LE; + default: return SND_PCM_FORMAT_UNKNOWN; + } +} + +/** + * Attempts to configure the specified sample format. + */ +static int +alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + struct audio_format *audio_format, + enum sample_format sample_format) +{ + snd_pcm_format_t alsa_format = get_bitformat(sample_format); + if (alsa_format == SND_PCM_FORMAT_UNKNOWN) + return -EINVAL; + + int err = snd_pcm_hw_params_set_format(pcm, hwparams, alsa_format); + if (err == 0) + audio_format->format = sample_format; + + return err; +} + +/** + * Attempts to configure the specified sample format with reversed + * host byte order. + */ +static int +alsa_output_try_reverse(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + struct audio_format *audio_format, + enum sample_format sample_format) +{ + snd_pcm_format_t alsa_format = + byteswap_bitformat(get_bitformat(sample_format)); + if (alsa_format == SND_PCM_FORMAT_UNKNOWN) + return -EINVAL; + + int err = snd_pcm_hw_params_set_format(pcm, hwparams, alsa_format); + if (err == 0) { + audio_format->format = sample_format; + audio_format->reverse_endian = true; + } + + return err; +} + +/** + * Attempts to configure the specified sample format, and tries the + * reversed host byte order if was not supported. + */ +static int +alsa_output_try_format_both(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + struct audio_format *audio_format, + enum sample_format sample_format) +{ + int err = alsa_output_try_format(pcm, hwparams, audio_format, + sample_format); + if (err == -EINVAL) + err = alsa_output_try_reverse(pcm, hwparams, audio_format, + sample_format); + + return err; +} + +/** + * Configure a sample format, and probe other formats if that fails. + */ +static int +alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + struct audio_format *audio_format) { - switch (af->bits) { - case 8: return SND_PCM_FORMAT_S8; - case 16: return SND_PCM_FORMAT_S16; - case 24: return SND_PCM_FORMAT_S24; - case 32: return SND_PCM_FORMAT_S32; + /* try the input format first */ + + int err = alsa_output_try_format_both(pcm, hwparams, audio_format, + audio_format->format); + if (err != -EINVAL) + return err; + + /* if unsupported by the hardware, try other formats */ + + static const enum sample_format probe_formats[] = { + SAMPLE_FORMAT_S24_P32, + SAMPLE_FORMAT_S32, + SAMPLE_FORMAT_S24, + SAMPLE_FORMAT_S16, + SAMPLE_FORMAT_S8, + SAMPLE_FORMAT_UNDEFINED, + }; + + for (unsigned i = 0; probe_formats[i] != SAMPLE_FORMAT_UNDEFINED; ++i) { + if (probe_formats[i] == audio_format->format) + continue; + + err = alsa_output_try_format_both(pcm, hwparams, audio_format, + probe_formats[i]); + if (err != -EINVAL) + return err; } - return SND_PCM_FORMAT_UNKNOWN; + + return -EINVAL; } /** @@ -189,7 +333,6 @@ get_bitformat(const struct audio_format *af) */ static bool alsa_setup(struct alsa_data *ad, struct audio_format *audio_format, - snd_pcm_format_t bitformat, GError **error) { snd_pcm_hw_params_t *hwparams; @@ -208,7 +351,6 @@ alsa_setup(struct alsa_data *ad, struct audio_format *audio_format, configure_hw: /* configure HW params */ snd_pcm_hw_params_alloca(&hwparams); - cmd = "snd_pcm_hw_params_any"; err = snd_pcm_hw_params_any(ad->pcm, hwparams); if (err < 0) @@ -235,31 +377,12 @@ configure_hw: ad->writei = snd_pcm_writei; } - err = snd_pcm_hw_params_set_format(ad->pcm, hwparams, bitformat); - if (err == -EINVAL && (audio_format->bits == 24 || - audio_format->bits == 16)) { - /* fall back to 32 bit, let pcm_convert.c do the conversion */ - err = snd_pcm_hw_params_set_format(ad->pcm, hwparams, - SND_PCM_FORMAT_S32); - if (err == 0) - audio_format->bits = 32; - } - - if (err == -EINVAL && audio_format->bits != 16) { - /* fall back to 16 bit, let pcm_convert.c do the conversion */ - err = snd_pcm_hw_params_set_format(ad->pcm, hwparams, - SND_PCM_FORMAT_S16); - if (err == 0) { - g_debug("ALSA device \"%s\": converting %u bit to 16 bit\n", - alsa_device(ad), audio_format->bits); - audio_format->bits = 16; - } - } - + err = alsa_output_setup_format(ad->pcm, hwparams, audio_format); if (err < 0) { g_set_error(error, alsa_output_quark(), err, - "ALSA device \"%s\" does not support %u bit audio: %s", - alsa_device(ad), audio_format->bits, + "ALSA device \"%s\" does not support format %s: %s", + alsa_device(ad), + sample_format_to_string(audio_format->format), snd_strerror(-err)); return false; } @@ -285,6 +408,26 @@ configure_hw: } audio_format->sample_rate = sample_rate; + snd_pcm_uframes_t buffer_size_min, buffer_size_max; + snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min); + snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max); + unsigned buffer_time_min, buffer_time_max; + snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time_min, 0); + snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time_max, 0); + g_debug("buffer: size=%u..%u time=%u..%u", + (unsigned)buffer_size_min, (unsigned)buffer_size_max, + buffer_time_min, buffer_time_max); + + snd_pcm_uframes_t period_size_min, period_size_max; + snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, 0); + snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, 0); + unsigned period_time_min, period_time_max; + snd_pcm_hw_params_get_period_time_min(hwparams, &period_time_min, 0); + snd_pcm_hw_params_get_period_time_max(hwparams, &period_time_max, 0); + g_debug("period: size=%u..%u time=%u..%u", + (unsigned)period_size_min, (unsigned)period_size_max, + period_time_min, period_time_max); + if (ad->buffer_time > 0) { buffer_time = ad->buffer_time; cmd = "snd_pcm_hw_params_set_buffer_time_near"; @@ -365,6 +508,9 @@ configure_hw: g_debug("buffer_size=%u period_size=%u", (unsigned)alsa_buffer_size, (unsigned)alsa_period_size); + ad->period_frames = alsa_period_size; + ad->period_position = 0; + return true; error: @@ -378,19 +524,9 @@ static bool alsa_open(void *data, struct audio_format *audio_format, GError **error) { struct alsa_data *ad = data; - snd_pcm_format_t bitformat; int err; bool success; - bitformat = get_bitformat(audio_format); - if (bitformat == SND_PCM_FORMAT_UNKNOWN) { - /* sample format is not supported by this plugin - - fall back to 16 bit samples */ - - audio_format->bits = 16; - bitformat = SND_PCM_FORMAT_S16; - } - err = snd_pcm_open(&ad->pcm, alsa_device(ad), SND_PCM_STREAM_PLAYBACK, ad->mode); if (err < 0) { @@ -400,7 +536,7 @@ alsa_open(void *data, struct audio_format *audio_format, GError **error) return false; } - success = alsa_setup(ad, audio_format, bitformat, error); + success = alsa_setup(ad, audio_format, error); if (!success) { snd_pcm_close(ad->pcm); return false; @@ -431,6 +567,7 @@ alsa_recover(struct alsa_data *ad, int err) /* fall-through to snd_pcm_prepare: */ case SND_PCM_STATE_SETUP: case SND_PCM_STATE_XRUN: + ad->period_position = 0; err = snd_pcm_prepare(ad->pcm); break; case SND_PCM_STATE_DISCONNECTED: @@ -448,11 +585,47 @@ alsa_recover(struct alsa_data *ad, int err) } static void +alsa_drain(void *data) +{ + struct alsa_data *ad = data; + + if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING) + return; + + if (ad->period_position > 0) { + /* generate some silence to finish the partial + period */ + snd_pcm_uframes_t nframes = + ad->period_frames - ad->period_position; + size_t nbytes = nframes * ad->frame_size; + void *buffer = g_malloc(nbytes); + snd_pcm_hw_params_t *params; + snd_pcm_format_t format; + unsigned channels; + + snd_pcm_hw_params_alloca(¶ms); + snd_pcm_hw_params_current(ad->pcm, params); + snd_pcm_hw_params_get_format(params, &format); + snd_pcm_hw_params_get_channels(params, &channels); + + snd_pcm_format_set_silence(format, buffer, nframes * channels); + ad->writei(ad->pcm, buffer, nframes); + g_free(buffer); + } + + snd_pcm_drain(ad->pcm); + + ad->period_position = 0; +} + +static void alsa_cancel(void *data) { struct alsa_data *ad = data; - alsa_recover(ad, snd_pcm_drop(ad->pcm)); + ad->period_position = 0; + + snd_pcm_drop(ad->pcm); } static void @@ -460,9 +633,6 @@ alsa_close(void *data) { struct alsa_data *ad = data; - if (snd_pcm_state(ad->pcm) == SND_PCM_STATE_RUNNING) - snd_pcm_drain(ad->pcm); - snd_pcm_close(ad->pcm); } @@ -475,8 +645,11 @@ alsa_play(void *data, const void *chunk, size_t size, GError **error) while (true) { snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size); - if (ret > 0) + if (ret > 0) { + ad->period_position = (ad->period_position + ret) + % ad->period_frames; return ret * ad->frame_size; + } if (ret < 0 && ret != -EAGAIN && ret != -EINTR && alsa_recover(ad, ret) < 0) { @@ -494,7 +667,9 @@ const struct audio_output_plugin alsaPlugin = { .finish = alsa_finish, .open = alsa_open, .play = alsa_play, + .drain = alsa_drain, .cancel = alsa_cancel, .close = alsa_close, - .mixer_plugin = &alsa_mixer, + + .mixer_plugin = &alsa_mixer_plugin, }; diff --git a/src/output/ao_plugin.c b/src/output/ao_plugin.c index 12d2b7552..d5c95018c 100644 --- a/src/output/ao_plugin.c +++ b/src/output/ao_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,7 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../output_api.h" +#include "config.h" +#include "output_api.h" #include <ao/ao.h> #include <glib.h> @@ -169,13 +170,24 @@ ao_output_open(void *data, struct audio_format *audio_format, ao_sample_format format; struct ao_data *ad = (struct ao_data *)data; - /* support for 24 bit samples in libao is currently dubious, - and until we have sorted that out, resample everything to - 16 bit */ - if (audio_format->bits > 16) - audio_format->bits = 16; + switch (audio_format->format) { + case SAMPLE_FORMAT_S8: + format.bits = 8; + break; + + case SAMPLE_FORMAT_S16: + format.bits = 16; + break; + + default: + /* support for 24 bit samples in libao is currently + dubious, and until we have sorted that out, + convert everything to 16 bit */ + audio_format->format = SAMPLE_FORMAT_S16; + format.bits = 16; + break; + } - format.bits = audio_format->bits; format.rate = audio_format->sample_rate; format.byte_format = AO_FMT_NATIVE; format.channels = audio_format->channels; diff --git a/src/output/ffado_output_plugin.c b/src/output/ffado_output_plugin.c new file mode 100644 index 000000000..723698ed0 --- /dev/null +++ b/src/output/ffado_output_plugin.c @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2003-2010 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. + */ + +/* + * Warning: this plugin was not tested successfully. I just couldn't + * keep libffado2 from crashing. Use at your own risk. + * + * For details, see my Debian bug reports: + * + * http://bugs.debian.org/601657 + * http://bugs.debian.org/601659 + * http://bugs.debian.org/601663 + * + */ + +#include "config.h" +#include "output_api.h" +#include "timer.h" + +#include <glib.h> +#include <assert.h> + +#include <libffado/ffado.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "ffado" + +enum { + MAX_STREAMS = 8, +}; + +struct mpd_ffado_stream { + /** libffado's stream number */ + int number; + + float *buffer; +}; + +struct mpd_ffado_device { + char *device_name; + int verbose; + unsigned period_size, nb_buffers; + + ffado_device_t *dev; + + /** + * The current sample position inside the stream buffers. New + * samples get appended at this position on all streams at the + * same time. When the buffers are full + * (buffer_position==period_size), + * ffado_streaming_transfer_playback_buffers() gets called to + * hand them over to libffado. + */ + unsigned buffer_position; + + /** + * The number of streams which are really used by MPD. + */ + int num_streams; + struct mpd_ffado_stream streams[MAX_STREAMS]; +}; + +static inline GQuark +ffado_output_quark(void) +{ + return g_quark_from_static_string("ffado_output"); +} + +static void * +ffado_init(G_GNUC_UNUSED const struct audio_format *audio_format, + const struct config_param *param, + GError **error_r) +{ + g_debug("using libffado version %s, API=%d", + ffado_get_version(), ffado_get_api_version()); + + struct mpd_ffado_device *fd = g_new(struct mpd_ffado_device, 1); + fd->device_name = config_dup_block_string(param, "device", NULL); + fd->verbose = config_get_block_unsigned(param, "verbose", 0); + + fd->period_size = config_get_block_unsigned(param, "period_size", + 1024); + if (fd->period_size == 0 || fd->period_size > 1024 * 1024) { + g_set_error(error_r, ffado_output_quark(), 0, + "invalid period_size setting"); + return false; + } + + fd->nb_buffers = config_get_block_unsigned(param, "nb_buffers", 3); + if (fd->nb_buffers == 0 || fd->nb_buffers > 1024) { + g_set_error(error_r, ffado_output_quark(), 0, + "invalid nb_buffers setting"); + return false; + } + + return fd; +} + +static void +ffado_finish(void *data) +{ + struct mpd_ffado_device *fd = data; + + g_free(fd->device_name); + g_free(fd); +} + +static bool +ffado_configure_stream(ffado_device_t *dev, struct mpd_ffado_stream *stream, + GError **error_r) +{ + char *buffer = (char *)stream->buffer; + if (ffado_streaming_set_playback_stream_buffer(dev, stream->number, + buffer) != 0) { + g_set_error(error_r, ffado_output_quark(), 0, + "failed to configure stream buffer"); + return false; + } + + if (ffado_streaming_playback_stream_onoff(dev, stream->number, + 1) != 0) { + g_set_error(error_r, ffado_output_quark(), 0, + "failed to disable stream"); + return false; + } + + return true; +} + +static bool +ffado_configure(struct mpd_ffado_device *fd, struct audio_format *audio_format, + GError **error_r) +{ + assert(fd != NULL); + assert(fd->dev != NULL); + assert(audio_format->channels <= MAX_STREAMS); + + if (ffado_streaming_set_audio_datatype(fd->dev, + ffado_audio_datatype_float) != 0) { + g_set_error(error_r, ffado_output_quark(), 0, + "ffado_streaming_set_audio_datatype() failed"); + return false; + } + + int num_streams = ffado_streaming_get_nb_playback_streams(fd->dev); + if (num_streams < 0) { + g_set_error(error_r, ffado_output_quark(), 0, + "ffado_streaming_get_nb_playback_streams() failed"); + return false; + } + + g_debug("there are %d playback streams", num_streams); + + fd->num_streams = 0; + for (int i = 0; i < num_streams; ++i) { + char name[256]; + ffado_streaming_get_playback_stream_name(fd->dev, i, name, + sizeof(name) - 1); + + ffado_streaming_stream_type type = + ffado_streaming_get_playback_stream_type(fd->dev, i); + if (type != ffado_stream_type_audio) { + g_debug("stream %d name='%s': not an audio stream", + i, name); + continue; + } + + if (fd->num_streams >= audio_format->channels) { + g_debug("stream %d name='%s': ignoring", + i, name); + continue; + } + + g_debug("stream %d name='%s'", i, name); + + struct mpd_ffado_stream *stream = + &fd->streams[fd->num_streams++]; + + stream->number = i; + + /* allocated buffer is zeroed = silence */ + stream->buffer = g_new0(float, fd->period_size); + + if (!ffado_configure_stream(fd->dev, stream, error_r)) + return false; + } + + if (!audio_valid_channel_count(fd->num_streams)) { + g_set_error(error_r, ffado_output_quark(), 0, + "invalid channel count from libffado: %u", + audio_format->channels); + return false; + } + + g_debug("configured %d audio streams", fd->num_streams); + + if (ffado_streaming_prepare(fd->dev) != 0) { + g_set_error(error_r, ffado_output_quark(), 0, + "ffado_streaming_prepare() failed"); + return false; + } + + if (ffado_streaming_start(fd->dev) != 0) { + g_set_error(error_r, ffado_output_quark(), 0, + "ffado_streaming_start() failed"); + return false; + } + + audio_format->channels = fd->num_streams; + return true; +} + +static bool +ffado_open(void *data, struct audio_format *audio_format, GError **error_r) +{ + struct mpd_ffado_device *fd = data; + + /* will be converted to floating point, choose best input + format */ + audio_format->format = SAMPLE_FORMAT_S24_P32; + + ffado_device_info_t device_info; + memset(&device_info, 0, sizeof(device_info)); + if (fd->device_name != NULL) { + device_info.nb_device_spec_strings = 1; + device_info.device_spec_strings = &fd->device_name; + } + + ffado_options_t options; + memset(&options, 0, sizeof(options)); + options.sample_rate = audio_format->sample_rate; + options.period_size = fd->period_size; + options.nb_buffers = fd->nb_buffers; + options.verbose = fd->verbose; + + fd->dev = ffado_streaming_init(device_info, options); + if (fd->dev == NULL) { + g_set_error(error_r, ffado_output_quark(), 0, + "ffado_streaming_init() failed"); + return false; + } + + if (!ffado_configure(fd, audio_format, error_r)) { + ffado_streaming_finish(fd->dev); + + for (int i = 0; i < fd->num_streams; ++i) { + struct mpd_ffado_stream *stream = &fd->streams[i]; + g_free(stream->buffer); + } + + return false; + } + + fd->buffer_position = 0; + + return true; +} + +static void +ffado_close(void *data) +{ + struct mpd_ffado_device *fd = data; + + ffado_streaming_stop(fd->dev); + ffado_streaming_finish(fd->dev); + + for (int i = 0; i < fd->num_streams; ++i) { + struct mpd_ffado_stream *stream = &fd->streams[i]; + g_free(stream->buffer); + } +} + +static size_t +ffado_play(void *data, const void *chunk, size_t size, GError **error_r) +{ + struct mpd_ffado_device *fd = data; + + /* wait for prefious buffer to finish (if it was full) */ + + if (fd->buffer_position >= fd->period_size) { + switch (ffado_streaming_wait(fd->dev)) { + case ffado_wait_ok: + case ffado_wait_xrun: + break; + + default: + g_set_error(error_r, ffado_output_quark(), 0, + "ffado_streaming_wait() failed"); + return 0; + } + + fd->buffer_position = 0; + } + + /* copy samples to stream buffers, non-interleaved */ + + const int32_t *p = chunk; + unsigned num_frames = size / sizeof(*p) / fd->num_streams; + if (num_frames > fd->period_size - fd->buffer_position) + num_frames = fd->period_size - fd->buffer_position; + + for (unsigned i = num_frames; i > 0; --i) { + for (int stream = 0; stream < fd->num_streams; ++stream) + fd->streams[stream].buffer[fd->buffer_position] = + *p++ / (float)(1 << 23); + ++fd->buffer_position; + } + + /* if buffer full, transfer to device */ + + if (fd->buffer_position >= fd->period_size && + /* libffado documentation says this function returns -1 on + error, but that is a lie - it returns a boolean value, + and "false" means error */ + !ffado_streaming_transfer_playback_buffers(fd->dev)) { + g_set_error(error_r, ffado_output_quark(), 0, + "ffado_streaming_transfer_playback_buffers() failed"); + return 0; + } + + return num_frames * sizeof(*p) * fd->num_streams; +} + +const struct audio_output_plugin ffado_output_plugin = { + .name = "ffado", + .init = ffado_init, + .finish = ffado_finish, + .open = ffado_open, + .close = ffado_close, + .play = ffado_play, +}; diff --git a/src/output/fifo_plugin.c b/src/output/fifo_output_plugin.c index 76bbe8cfa..f4217ec4d 100644 --- a/src/output/fifo_plugin.c +++ b/src/output/fifo_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,15 +17,17 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../output_api.h" -#include "../utils.h" -#include "../timer.h" +#include "config.h" +#include "output_api.h" +#include "utils.h" +#include "timer.h" +#include "fd_util.h" +#include "open.h" #include <glib.h> #include <sys/types.h> #include <sys/stat.h> -#include <fcntl.h> #include <errno.h> #include <string.h> #include <unistd.h> @@ -152,7 +154,7 @@ fifo_open(struct fifo_data *fd, GError **error) if (!fifo_check(fd, error)) return false; - fd->input = open(fd->path, O_RDONLY|O_NONBLOCK); + fd->input = open_cloexec(fd->path, O_RDONLY|O_NONBLOCK|O_BINARY, 0); if (fd->input < 0) { g_set_error(error, fifo_output_quark(), errno, "Could not open FIFO \"%s\" for reading: %s", @@ -161,7 +163,7 @@ fifo_open(struct fifo_data *fd, GError **error) return false; } - fd->output = open(fd->path, O_WRONLY|O_NONBLOCK); + fd->output = open_cloexec(fd->path, O_WRONLY|O_NONBLOCK|O_BINARY, 0); if (fd->output < 0) { g_set_error(error, fifo_output_quark(), errno, "Could not open FIFO \"%s\" for writing: %s", diff --git a/src/output/httpd_client.c b/src/output/httpd_client.c index 52a398e3b..6bd095838 100644 --- a/src/output/httpd_client.c +++ b/src/output/httpd_client.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,11 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "httpd_client.h" #include "httpd_internal.h" #include "fifo_buffer.h" #include "page.h" #include "icy_server.h" +#include "glib_compat.h" #include <stdbool.h> #include <assert.h> @@ -280,11 +282,12 @@ httpd_client_send_response(struct httpd_client *client) } else { gchar *metadata_header; - metadata_header = icy_server_metadata_header("Add config information here!", /* TODO */ - "Add config information here!", /* TODO */ - "Add config information here!", /* TODO */ - client->httpd->content_type, - client->metaint); + metadata_header = icy_server_metadata_header( + client->httpd->name, + client->httpd->genre, + client->httpd->website, + client->httpd->content_type, + client->metaint); g_strlcpy(buffer, metadata_header, sizeof(buffer)); @@ -482,11 +485,6 @@ httpd_client_queue_size(const struct httpd_client *client) return size; } -/* g_queue_clear() was introduced in GLib 2.14 */ -#if !GLIB_CHECK_VERSION(2,14,0) -#define g_queue_clear(q) do { g_queue_free(q); q = g_queue_new(); } while (0) -#endif - void httpd_client_cancel(struct httpd_client *client) { diff --git a/src/output/httpd_client.h b/src/output/httpd_client.h index 4a2912f80..7ebd0bbc0 100644 --- a/src/output/httpd_client.h +++ b/src/output/httpd_client.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/httpd_internal.h b/src/output/httpd_internal.h index 2257e27a2..fee72c07f 100644 --- a/src/output/httpd_internal.h +++ b/src/output/httpd_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -29,30 +29,34 @@ #include <glib.h> -#include <sys/socket.h> +#include <stdbool.h> struct httpd_client; struct httpd_output { /** - * The configured encoder plugin. + * True if the audio output is open and accepts client + * connections. */ - struct encoder *encoder; + bool open; /** - * The MIME type produced by the #encoder. + * The configured encoder plugin. */ - const char *content_type; + struct encoder *encoder; /** - * The configured address of the listener socket. + * Number of bytes which were fed into the encoder, without + * ever receiving new output. This is used to estimate + * whether MPD should manually flush the encoder, to avoid + * buffer underruns in the client. */ - struct sockaddr_storage address; + size_t unflushed_input; /** - * The size of #address. + * The MIME type produced by the #encoder. */ - socklen_t address_size; + const char *content_type; /** * This mutex protects the listener socket and the client @@ -69,12 +73,7 @@ struct httpd_output { /** * The listener socket. */ - int fd; - - /** - * A GLib main loop source id for the listener socket. - */ - guint source_id; + struct server_socket *server_socket; /** * The header page, which is sent to every client on connect. @@ -87,6 +86,19 @@ struct httpd_output { struct page *metadata; /** + * The configured name. + */ + char const *name; + /** + * The configured genre. + */ + char const *genre; + /** + * The configured website address. + */ + char const *website; + + /** * A linked list containing all clients which are currently * connected. */ @@ -97,6 +109,12 @@ struct httpd_output { * function. */ char buffer[32768]; + + /** + * The maximum and current number of clients connected + * at the same time. + */ + guint clients_max, clients_cnt; }; /** diff --git a/src/output/httpd_output_plugin.c b/src/output/httpd_output_plugin.c index 026e8d9d8..72994018a 100644 --- a/src/output/httpd_output_plugin.c +++ b/src/output/httpd_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "httpd_internal.h" #include "httpd_client.h" #include "output_api.h" @@ -25,15 +26,19 @@ #include "socket_util.h" #include "page.h" #include "icy_server.h" +#include "fd_util.h" +#include "server_socket.h" #include <assert.h> #include <sys/types.h> -#include <netinet/in.h> -#include <netdb.h> #include <unistd.h> #include <errno.h> +#ifdef HAVE_LIBWRAP +#include <tcpd.h> +#endif + #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "httpd_output" @@ -46,18 +51,49 @@ httpd_output_quark(void) return g_quark_from_static_string("httpd_output"); } +static void +httpd_listen_in_event(int fd, const struct sockaddr *address, + size_t address_length, int uid, void *ctx); + +static bool +httpd_output_bind(struct httpd_output *httpd, GError **error_r) +{ + httpd->open = false; + + g_mutex_lock(httpd->mutex); + bool success = server_socket_open(httpd->server_socket, error_r); + g_mutex_unlock(httpd->mutex); + + return success; +} + +static void +httpd_output_unbind(struct httpd_output *httpd) +{ + assert(!httpd->open); + + g_mutex_lock(httpd->mutex); + server_socket_close(httpd->server_socket); + g_mutex_unlock(httpd->mutex); +} + static void * httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, const struct config_param *param, GError **error) { struct httpd_output *httpd = g_new(struct httpd_output, 1); - const char *encoder_name; + const char *encoder_name, *bind_to_address; const struct encoder_plugin *encoder_plugin; guint port; - struct sockaddr_in *sin; /* read configuration */ + httpd->name = + config_get_block_string(param, "name", "Set name in config"); + httpd->genre = + config_get_block_string(param, "genre", "Set genre in config"); + httpd->website = + config_get_block_string(param, "website", "Set website in config"); port = config_get_block_unsigned(param, "port", 8000); @@ -69,21 +105,21 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, return NULL; } - if (strcmp(encoder_name, "vorbis") == 0) - httpd->content_type = "audio/ogg"; - else if (strcmp(encoder_name, "lame") == 0) - httpd->content_type = "audio/mpeg"; - else - httpd->content_type = "application/octet-stream"; + httpd->clients_max = config_get_block_unsigned(param,"max_clients", 0); + + /* set up bind_to_address */ - /* initialize listen address */ + httpd->server_socket = server_socket_new(httpd_listen_in_event, httpd); - sin = (struct sockaddr_in *)&httpd->address; - memset(sin, 0, sizeof(sin)); - sin->sin_port = htons(port); - sin->sin_family = AF_INET; - sin->sin_addr.s_addr = INADDR_ANY; - httpd->address_size = sizeof(*sin); + bind_to_address = + config_get_block_string(param, "bind_to_address", NULL); + bool success = bind_to_address != NULL && + strcmp(bind_to_address, "any") != 0 + ? server_socket_add_host(httpd->server_socket, bind_to_address, + port, error) + : server_socket_add_port(httpd->server_socket, port, error); + if (!success) + return NULL; /* initialize metadata */ httpd->metadata = NULL; @@ -94,6 +130,12 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, if (httpd->encoder == NULL) return NULL; + /* determine content type */ + httpd->content_type = encoder_get_mime_type(httpd->encoder); + if (httpd->content_type == NULL) { + httpd->content_type = "application/octet-stream"; + } + httpd->mutex = g_mutex_new(); return httpd; @@ -108,6 +150,7 @@ httpd_output_finish(void *data) page_unref(httpd->metadata); encoder_finish(httpd->encoder); + server_socket_free(httpd->server_socket); g_mutex_free(httpd->mutex); g_free(httpd); } @@ -124,36 +167,64 @@ httpd_client_add(struct httpd_output *httpd, int fd) httpd->encoder->plugin->tag == NULL); httpd->clients = g_list_prepend(httpd->clients, client); + httpd->clients_cnt++; /* pass metadata to client */ if (httpd->metadata) httpd_client_send_metadata(client, httpd->metadata); } -static gboolean -httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source, - G_GNUC_UNUSED GIOCondition condition, - gpointer data) +static void +httpd_listen_in_event(int fd, const struct sockaddr *address, + size_t address_length, G_GNUC_UNUSED int uid, void *ctx) { - struct httpd_output *httpd = data; - int fd; - struct sockaddr_storage sa; - socklen_t sa_length = sizeof(sa); - - g_mutex_lock(httpd->mutex); + struct httpd_output *httpd = ctx; /* the listener socket has become readable - a client has connected */ - fd = accept(httpd->fd, (struct sockaddr*)&sa, &sa_length); - if (fd >= 0) - httpd_client_add(httpd, fd); - else if (fd < 0 && errno != EINTR) +#ifdef HAVE_LIBWRAP + if (address->sa_family != AF_UNIX) { + char *hostaddr = sockaddr_to_string(address, address_length, NULL); + const char *progname = g_get_prgname(); + + struct request_info req; + request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0); + + fromhost(&req); + + if (!hosts_access(&req)) { + /* tcp wrappers says no */ + g_warning("libwrap refused connection (libwrap=%s) from %s", + progname, hostaddr); + g_free(hostaddr); + close(fd); + g_mutex_unlock(httpd->mutex); + return; + } + + g_free(hostaddr); + } +#else + (void)address; + (void)address_length; +#endif /* HAVE_WRAP */ + + g_mutex_lock(httpd->mutex); + + if (fd >= 0) { + /* can we allow additional client */ + if (httpd->open && + (httpd->clients_max == 0 || + httpd->clients_cnt < httpd->clients_max)) + httpd_client_add(httpd, fd); + else + close(fd); + } else if (fd < 0 && errno != EINTR) { g_warning("accept() failed: %s", g_strerror(errno)); + } g_mutex_unlock(httpd->mutex); - - return true; } /** @@ -165,12 +236,22 @@ httpd_output_read_page(struct httpd_output *httpd) { size_t size = 0, nbytes; + if (httpd->unflushed_input >= 65536) { + /* we have fed a lot of input into the encoder, but it + didn't give anything back yet - flush now to avoid + buffer underruns */ + encoder_flush(httpd->encoder, NULL); + httpd->unflushed_input = 0; + } + do { nbytes = encoder_read(httpd->encoder, httpd->buffer + size, sizeof(httpd->buffer) - size); if (nbytes == 0) break; + httpd->unflushed_input = 0; + size += nbytes; } while (size < sizeof(httpd->buffer)); @@ -195,41 +276,41 @@ httpd_output_encoder_open(struct httpd_output *httpd, bytes of encoder output after opening it, because it has to be sent to every new client */ httpd->header = httpd_output_read_page(httpd); + + httpd->unflushed_input = 0; + return true; } static bool +httpd_output_enable(void *data, GError **error_r) +{ + struct httpd_output *httpd = data; + + return httpd_output_bind(httpd, error_r); +} + +static void +httpd_output_disable(void *data) +{ + struct httpd_output *httpd = data; + + httpd_output_unbind(httpd); +} + +static bool httpd_output_open(void *data, struct audio_format *audio_format, GError **error) { struct httpd_output *httpd = data; bool success; - GIOChannel *channel; g_mutex_lock(httpd->mutex); - /* create and set up listener socket */ - - httpd->fd = socket_bind_listen(PF_INET, SOCK_STREAM, 0, - (struct sockaddr *)&httpd->address, - httpd->address_size, - 16, error); - if (httpd->fd < 0) { - g_mutex_unlock(httpd->mutex); - return false; - } - - channel = g_io_channel_unix_new(httpd->fd); - httpd->source_id = g_io_add_watch(channel, G_IO_IN, - httpd_listen_in_event, httpd); - g_io_channel_unref(channel); - /* open the encoder */ success = httpd_output_encoder_open(httpd, audio_format, error); if (!success) { - g_source_remove(httpd->source_id); - close(httpd->fd); g_mutex_unlock(httpd->mutex); return false; } @@ -237,8 +318,11 @@ httpd_output_open(void *data, struct audio_format *audio_format, /* initialize other attributes */ httpd->clients = NULL; + httpd->clients_cnt = 0; httpd->timer = timer_new(audio_format); + httpd->open = true; + g_mutex_unlock(httpd->mutex); return true; } @@ -257,6 +341,8 @@ static void httpd_output_close(void *data) g_mutex_lock(httpd->mutex); + httpd->open = false; + timer_free(httpd->timer); g_list_foreach(httpd->clients, httpd_client_delete, NULL); @@ -267,9 +353,6 @@ static void httpd_output_close(void *data) encoder_close(httpd->encoder); - g_source_remove(httpd->source_id); - close(httpd->fd); - g_mutex_unlock(httpd->mutex); } @@ -281,6 +364,7 @@ httpd_output_remove_client(struct httpd_output *httpd, assert(client != NULL); httpd->clients = g_list_remove(httpd->clients, client); + httpd->clients_cnt--; } void @@ -291,6 +375,16 @@ httpd_output_send_header(struct httpd_output *httpd, httpd_client_send(client, httpd->header); } +static unsigned +httpd_output_delay(void *data) +{ + struct httpd_output *httpd = data; + + return httpd->timer->started + ? timer_delay(httpd->timer) + : 0; +} + static void httpd_client_check_queue(gpointer data, G_GNUC_UNUSED gpointer user_data) { @@ -352,6 +446,8 @@ httpd_output_encode_and_play(struct httpd_output *httpd, if (!success) return false; + httpd->unflushed_input += size; + httpd_output_encoder_to_clients(httpd); return true; @@ -378,13 +474,29 @@ httpd_output_play(void *data, const void *chunk, size_t size, GError **error) if (!httpd->timer->started) timer_start(httpd->timer); - else - timer_sync(httpd->timer); timer_add(httpd->timer, size); return size; } +static bool +httpd_output_pause(void *data) +{ + struct httpd_output *httpd = data; + + g_mutex_lock(httpd->mutex); + bool has_clients = httpd->clients != NULL; + g_mutex_unlock(httpd->mutex); + + if (has_clients) { + static const char silence[1020]; + return httpd_output_play(data, silence, sizeof(silence), NULL); + } else { + g_usleep(100000); + return true; + } +} + static void httpd_send_metadata(gpointer data, gpointer user_data) { @@ -433,9 +545,8 @@ httpd_output_tag(void *data, const struct tag *tag) page_unref (httpd->metadata); httpd->metadata = - icy_server_metadata_page(tag, TAG_ITEM_ALBUM, - TAG_ITEM_ARTIST, - TAG_ITEM_TITLE, + icy_server_metadata_page(tag, TAG_ALBUM, + TAG_ARTIST, TAG_TITLE, TAG_NUM_OF_ITEM_TYPES); if (httpd->metadata != NULL) { g_mutex_lock(httpd->mutex); @@ -468,9 +579,13 @@ const struct audio_output_plugin httpd_output_plugin = { .name = "httpd", .init = httpd_output_init, .finish = httpd_output_finish, + .enable = httpd_output_enable, + .disable = httpd_output_disable, .open = httpd_output_open, .close = httpd_output_close, + .delay = httpd_output_delay, .send_tag = httpd_output_tag, .play = httpd_output_play, + .pause = httpd_output_pause, .cancel = httpd_output_cancel, }; diff --git a/src/output/jack_output_plugin.c b/src/output/jack_output_plugin.c new file mode 100644 index 000000000..110ee5f26 --- /dev/null +++ b/src/output/jack_output_plugin.c @@ -0,0 +1,722 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "output_api.h" + +#include <assert.h> + +#include <glib.h> +#include <jack/jack.h> +#include <jack/types.h> +#include <jack/ringbuffer.h> + +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> +#include <unistd.h> +#include <errno.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "jack" + +enum { + MAX_PORTS = 16, +}; + +static const size_t sample_size = sizeof(jack_default_audio_sample_t); + +struct jack_data { + /** + * libjack options passed to jack_client_open(). + */ + jack_options_t options; + + const char *name; + + const char *server_name; + + /* configuration */ + + char *source_ports[MAX_PORTS]; + unsigned num_source_ports; + + char *destination_ports[MAX_PORTS]; + unsigned num_destination_ports; + + size_t ringbuffer_size; + + /* the current audio format */ + struct audio_format audio_format; + + /* jack library stuff */ + jack_port_t *ports[MAX_PORTS]; + jack_client_t *client; + jack_ringbuffer_t *ringbuffer[MAX_PORTS]; + + bool shutdown; + + /** + * While this flag is set, the "process" callback generates + * silence. + */ + bool pause; +}; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +jack_output_quark(void) +{ + return g_quark_from_static_string("jack_output"); +} + +/** + * Determine the number of frames guaranteed to be available on all + * channels. + */ +static jack_nframes_t +mpd_jack_available(const struct jack_data *jd) +{ + size_t min = jack_ringbuffer_read_space(jd->ringbuffer[0]); + + for (unsigned i = 1; i < jd->audio_format.channels; ++i) { + size_t current = jack_ringbuffer_read_space(jd->ringbuffer[i]); + if (current < min) + min = current; + } + + assert(min % sample_size == 0); + + return min / sample_size; +} + +static int +mpd_jack_process(jack_nframes_t nframes, void *arg) +{ + struct jack_data *jd = (struct jack_data *) arg; + jack_default_audio_sample_t *out; + + if (nframes <= 0) + return 0; + + if (jd->pause) { + /* empty the ring buffers */ + + const jack_nframes_t available = mpd_jack_available(jd); + for (unsigned i = 0; i < jd->audio_format.channels; ++i) + jack_ringbuffer_read_advance(jd->ringbuffer[i], + available * sample_size); + + /* generate silence while MPD is paused */ + + for (unsigned i = 0; i < jd->audio_format.channels; ++i) { + out = jack_port_get_buffer(jd->ports[i], nframes); + + for (jack_nframes_t f = 0; f < nframes; ++f) + out[f] = 0.0; + } + + return 0; + } + + jack_nframes_t available = mpd_jack_available(jd); + if (available > nframes) + available = nframes; + + for (unsigned i = 0; i < jd->audio_format.channels; ++i) { + out = jack_port_get_buffer(jd->ports[i], nframes); + jack_ringbuffer_read(jd->ringbuffer[i], + (char *)out, available * sample_size); + + for (jack_nframes_t f = available; f < nframes; ++f) + /* ringbuffer underrun, fill with silence */ + out[f] = 0.0; + } + + /* generate silence for the unused source ports */ + + for (unsigned i = jd->audio_format.channels; + i < jd->num_source_ports; ++i) { + out = jack_port_get_buffer(jd->ports[i], nframes); + + for (jack_nframes_t f = 0; f < nframes; ++f) + out[f] = 0.0; + } + + return 0; +} + +static void +mpd_jack_shutdown(void *arg) +{ + struct jack_data *jd = (struct jack_data *) arg; + jd->shutdown = true; +} + +static void +set_audioformat(struct jack_data *jd, struct audio_format *audio_format) +{ + audio_format->sample_rate = jack_get_sample_rate(jd->client); + + if (jd->num_source_ports == 1) + audio_format->channels = 1; + else if (audio_format->channels > jd->num_source_ports) + audio_format->channels = 2; + + if (audio_format->format != SAMPLE_FORMAT_S16 && + audio_format->format != SAMPLE_FORMAT_S24_P32) + audio_format->format = SAMPLE_FORMAT_S24_P32; +} + +static void +mpd_jack_error(const char *msg) +{ + g_warning("%s", msg); +} + +#ifdef HAVE_JACK_SET_INFO_FUNCTION +static void +mpd_jack_info(const char *msg) +{ + g_message("%s", msg); +} +#endif + +/** + * Disconnect the JACK client. + */ +static void +mpd_jack_disconnect(struct jack_data *jd) +{ + assert(jd != NULL); + assert(jd->client != NULL); + + jack_deactivate(jd->client); + jack_client_close(jd->client); + jd->client = NULL; +} + +/** + * Connect the JACK client and performs some basic setup + * (e.g. register callbacks). + */ +static bool +mpd_jack_connect(struct jack_data *jd, GError **error_r) +{ + jack_status_t status; + + assert(jd != NULL); + + jd->shutdown = false; + + jd->client = jack_client_open(jd->name, jd->options, &status, + jd->server_name); + if (jd->client == NULL) { + g_set_error(error_r, jack_output_quark(), 0, + "Failed to connect to JACK server, status=%d", + status); + return false; + } + + jack_set_process_callback(jd->client, mpd_jack_process, jd); + jack_on_shutdown(jd->client, mpd_jack_shutdown, jd); + + for (unsigned i = 0; i < jd->num_source_ports; ++i) { + jd->ports[i] = jack_port_register(jd->client, + jd->source_ports[i], + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + if (jd->ports[i] == NULL) { + g_set_error(error_r, jack_output_quark(), 0, + "Cannot register output port \"%s\"", + jd->source_ports[i]); + mpd_jack_disconnect(jd); + return false; + } + } + + return true; +} + +static bool +mpd_jack_test_default_device(void) +{ + return true; +} + +static unsigned +parse_port_list(int line, const char *source, char **dest, GError **error_r) +{ + char **list = g_strsplit(source, ",", 0); + unsigned n = 0; + + for (n = 0; list[n] != NULL; ++n) { + if (n >= MAX_PORTS) { + g_set_error(error_r, jack_output_quark(), 0, + "too many port names in line %d", + line); + return 0; + } + + dest[n] = list[n]; + } + + g_free(list); + + if (n == 0) { + g_set_error(error_r, jack_output_quark(), 0, + "at least one port name expected in line %d", + line); + return 0; + } + + return n; +} + +static void * +mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format, + const struct config_param *param, GError **error_r) +{ + struct jack_data *jd; + const char *value; + + jd = g_new(struct jack_data, 1); + jd->options = JackNullOption; + + jd->name = config_get_block_string(param, "client_name", NULL); + if (jd->name != NULL) + jd->options |= JackUseExactName; + else + /* if there's a no configured client name, we don't + care about the JackUseExactName option */ + jd->name = "Music Player Daemon"; + + jd->server_name = config_get_block_string(param, "server_name", NULL); + if (jd->server_name != NULL) + jd->options |= JackServerName; + + if (!config_get_block_bool(param, "autostart", false)) + jd->options |= JackNoStartServer; + + /* configure the source ports */ + + value = config_get_block_string(param, "source_ports", "left,right"); + jd->num_source_ports = parse_port_list(param->line, value, + jd->source_ports, error_r); + if (jd->num_source_ports == 0) + return NULL; + + /* configure the destination ports */ + + value = config_get_block_string(param, "destination_ports", NULL); + if (value == NULL) { + /* compatibility with MPD < 0.16 */ + value = config_get_block_string(param, "ports", NULL); + if (value != NULL) + g_warning("deprecated option 'ports' in line %d", + param->line); + } + + if (value != NULL) { + jd->num_destination_ports = + parse_port_list(param->line, value, + jd->destination_ports, error_r); + if (jd->num_destination_ports == 0) + return NULL; + } else { + jd->num_destination_ports = 0; + } + + if (jd->num_destination_ports > 0 && + jd->num_destination_ports != jd->num_source_ports) + g_warning("number of source ports (%u) mismatches the " + "number of destination ports (%u) in line %d", + jd->num_source_ports, jd->num_destination_ports, + param->line); + + jd->ringbuffer_size = + config_get_block_unsigned(param, "ringbuffer_size", 32768); + + jack_set_error_function(mpd_jack_error); + +#ifdef HAVE_JACK_SET_INFO_FUNCTION + jack_set_info_function(mpd_jack_info); +#endif + + return jd; +} + +static void +mpd_jack_finish(void *data) +{ + struct jack_data *jd = data; + + for (unsigned i = 0; i < jd->num_source_ports; ++i) + g_free(jd->source_ports[i]); + + for (unsigned i = 0; i < jd->num_destination_ports; ++i) + g_free(jd->destination_ports[i]); + + g_free(jd); +} + +static bool +mpd_jack_enable(void *data, GError **error_r) +{ + struct jack_data *jd = (struct jack_data *)data; + + for (unsigned i = 0; i < jd->num_source_ports; ++i) + jd->ringbuffer[i] = NULL; + + return mpd_jack_connect(jd, error_r); +} + +static void +mpd_jack_disable(void *data) +{ + struct jack_data *jd = (struct jack_data *)data; + + if (jd->client != NULL) + mpd_jack_disconnect(jd); + + for (unsigned i = 0; i < jd->num_source_ports; ++i) { + if (jd->ringbuffer[i] != NULL) { + jack_ringbuffer_free(jd->ringbuffer[i]); + jd->ringbuffer[i] = NULL; + } + } +} + +/** + * Stops the playback on the JACK connection. + */ +static void +mpd_jack_stop(struct jack_data *jd) +{ + assert(jd != NULL); + + if (jd->client == NULL) + return; + + if (jd->shutdown) + /* the connection has failed; close it */ + mpd_jack_disconnect(jd); + else + /* the connection is alive: just stop playback */ + jack_deactivate(jd->client); +} + +static bool +mpd_jack_start(struct jack_data *jd, GError **error_r) +{ + const char *destination_ports[MAX_PORTS], **jports; + const char *duplicate_port = NULL; + unsigned num_destination_ports; + + assert(jd->client != NULL); + assert(jd->audio_format.channels <= jd->num_source_ports); + + /* allocate the ring buffers on the first open(); these + persist until MPD exits. It's too unsafe to delete them + because we can never know when mpd_jack_process() gets + called */ + for (unsigned i = 0; i < jd->num_source_ports; ++i) { + if (jd->ringbuffer[i] == NULL) + jd->ringbuffer[i] = + jack_ringbuffer_create(jd->ringbuffer_size); + + /* clear the ring buffer to be sure that data from + previous playbacks are gone */ + jack_ringbuffer_reset(jd->ringbuffer[i]); + } + + if ( jack_activate(jd->client) ) { + g_set_error(error_r, jack_output_quark(), 0, + "cannot activate client"); + mpd_jack_stop(jd); + return false; + } + + if (jd->num_destination_ports == 0) { + /* no output ports were configured - ask libjack for + defaults */ + jports = jack_get_ports(jd->client, NULL, NULL, + JackPortIsPhysical | JackPortIsInput); + if (jports == NULL) { + g_set_error(error_r, jack_output_quark(), 0, + "no ports found"); + mpd_jack_stop(jd); + return false; + } + + assert(*jports != NULL); + + for (num_destination_ports = 0; + num_destination_ports < MAX_PORTS && + jports[num_destination_ports] != NULL; + ++num_destination_ports) { + g_debug("destination_port[%u] = '%s'\n", + num_destination_ports, + jports[num_destination_ports]); + destination_ports[num_destination_ports] = + jports[num_destination_ports]; + } + } else { + /* use the configured output ports */ + + num_destination_ports = jd->num_destination_ports; + memcpy(destination_ports, jd->destination_ports, + num_destination_ports * sizeof(*destination_ports)); + + jports = NULL; + } + + assert(num_destination_ports > 0); + + if (jd->audio_format.channels >= 2 && num_destination_ports == 1) { + /* mix stereo signal on one speaker */ + + while (num_destination_ports < jd->audio_format.channels) + destination_ports[num_destination_ports++] = + destination_ports[0]; + } else if (num_destination_ports > jd->audio_format.channels) { + if (jd->audio_format.channels == 1 && num_destination_ports > 2) { + /* mono input file: connect the one source + channel to the both destination channels */ + duplicate_port = destination_ports[1]; + num_destination_ports = 1; + } else + /* connect only as many ports as we need */ + num_destination_ports = jd->audio_format.channels; + } + + assert(num_destination_ports <= jd->num_source_ports); + + for (unsigned i = 0; i < num_destination_ports; ++i) { + int ret; + + ret = jack_connect(jd->client, jack_port_name(jd->ports[i]), + destination_ports[i]); + if (ret != 0) { + g_set_error(error_r, jack_output_quark(), 0, + "Not a valid JACK port: %s", + destination_ports[i]); + + if (jports != NULL) + free(jports); + + mpd_jack_stop(jd); + return false; + } + } + + if (duplicate_port != NULL) { + /* mono input file: connect the one source channel to + the both destination channels */ + int ret; + + ret = jack_connect(jd->client, jack_port_name(jd->ports[0]), + duplicate_port); + if (ret != 0) { + g_set_error(error_r, jack_output_quark(), 0, + "Not a valid JACK port: %s", + duplicate_port); + + if (jports != NULL) + free(jports); + + mpd_jack_stop(jd); + return false; + } + } + + if (jports != NULL) + free(jports); + + return true; +} + +static bool +mpd_jack_open(void *data, struct audio_format *audio_format, GError **error_r) +{ + struct jack_data *jd = data; + + assert(jd != NULL); + + jd->pause = false; + + if (jd->client == NULL && !mpd_jack_connect(jd, error_r)) + return false; + + set_audioformat(jd, audio_format); + jd->audio_format = *audio_format; + + if (!mpd_jack_start(jd, error_r)) + return false; + + return true; +} + +static void +mpd_jack_close(G_GNUC_UNUSED void *data) +{ + struct jack_data *jd = data; + + mpd_jack_stop(jd); +} + +static inline jack_default_audio_sample_t +sample_16_to_jack(int16_t sample) +{ + return sample / (jack_default_audio_sample_t)(1 << (16 - 1)); +} + +static void +mpd_jack_write_samples_16(struct jack_data *jd, const int16_t *src, + unsigned num_samples) +{ + jack_default_audio_sample_t sample; + unsigned i; + + while (num_samples-- > 0) { + for (i = 0; i < jd->audio_format.channels; ++i) { + sample = sample_16_to_jack(*src++); + jack_ringbuffer_write(jd->ringbuffer[i], (void*)&sample, + sizeof(sample)); + } + } +} + +static inline jack_default_audio_sample_t +sample_24_to_jack(int32_t sample) +{ + return sample / (jack_default_audio_sample_t)(1 << (24 - 1)); +} + +static void +mpd_jack_write_samples_24(struct jack_data *jd, const int32_t *src, + unsigned num_samples) +{ + jack_default_audio_sample_t sample; + unsigned i; + + while (num_samples-- > 0) { + for (i = 0; i < jd->audio_format.channels; ++i) { + sample = sample_24_to_jack(*src++); + jack_ringbuffer_write(jd->ringbuffer[i], (void*)&sample, + sizeof(sample)); + } + } +} + +static void +mpd_jack_write_samples(struct jack_data *jd, const void *src, + unsigned num_samples) +{ + switch (jd->audio_format.format) { + case SAMPLE_FORMAT_S16: + mpd_jack_write_samples_16(jd, (const int16_t*)src, + num_samples); + break; + + case SAMPLE_FORMAT_S24_P32: + mpd_jack_write_samples_24(jd, (const int32_t*)src, + num_samples); + break; + + default: + assert(false); + } +} + +static size_t +mpd_jack_play(void *data, const void *chunk, size_t size, GError **error_r) +{ + struct jack_data *jd = data; + const size_t frame_size = audio_format_frame_size(&jd->audio_format); + size_t space = 0, space1; + + jd->pause = false; + + assert(size % frame_size == 0); + size /= frame_size; + + while (true) { + if (jd->shutdown) { + g_set_error(error_r, jack_output_quark(), 0, + "Refusing to play, because " + "there is no client thread"); + return 0; + } + + space = jack_ringbuffer_write_space(jd->ringbuffer[0]); + for (unsigned i = 1; i < jd->audio_format.channels; ++i) { + space1 = jack_ringbuffer_write_space(jd->ringbuffer[i]); + if (space > space1) + /* send data symmetrically */ + space = space1; + } + + if (space >= frame_size) + break; + + /* XXX do something more intelligent to + synchronize */ + g_usleep(1000); + } + + space /= sample_size; + if (space < size) + size = space; + + mpd_jack_write_samples(jd, chunk, size); + return size * frame_size; +} + +static bool +mpd_jack_pause(void *data) +{ + struct jack_data *jd = data; + + if (jd->shutdown) + return false; + + jd->pause = true; + + /* due to a MPD API limitation, we have to sleep a little bit + here, to avoid hogging the CPU */ + g_usleep(50000); + + return true; +} + +const struct audio_output_plugin jack_output_plugin = { + .name = "jack", + .test_default_device = mpd_jack_test_default_device, + .init = mpd_jack_init, + .finish = mpd_jack_finish, + .enable = mpd_jack_enable, + .disable = mpd_jack_disable, + .open = mpd_jack_open, + .play = mpd_jack_play, + .pause = mpd_jack_pause, + .close = mpd_jack_close, +}; diff --git a/src/output/jack_plugin.c b/src/output/jack_plugin.c deleted file mode 100644 index 5dc1eca24..000000000 --- a/src/output/jack_plugin.c +++ /dev/null @@ -1,450 +0,0 @@ -/* - * Copyright (C) 2003-2009 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "../output_api.h" -#include "config.h" - -#include <assert.h> - -#include <glib.h> -#include <jack/jack.h> -#include <jack/types.h> -#include <jack/ringbuffer.h> - -#include <stdlib.h> -#include <stdio.h> -#include <sys/types.h> -#include <unistd.h> -#include <errno.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "jack" - -static const size_t sample_size = sizeof(jack_default_audio_sample_t); - -static const char *const port_names[2] = { - "left", "right", -}; - -struct jack_data { - const char *name; - - /* configuration */ - char *output_ports[2]; - int ringbuffer_size; - - /* the current audio format */ - struct audio_format audio_format; - - /* jack library stuff */ - jack_port_t *ports[2]; - jack_client_t *client; - jack_ringbuffer_t *ringbuffer[2]; - - bool shutdown; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -jack_output_quark(void) -{ - return g_quark_from_static_string("jack_output"); -} - -static void -mpd_jack_client_free(struct jack_data *jd) -{ - assert(jd != NULL); - - if (jd->client != NULL) { - jack_deactivate(jd->client); - jack_client_close(jd->client); - jd->client = NULL; - } - - for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i) { - if (jd->ringbuffer[i] != NULL) { - jack_ringbuffer_free(jd->ringbuffer[i]); - jd->ringbuffer[i] = NULL; - } - } -} - -static void -mpd_jack_free(struct jack_data *jd) -{ - assert(jd != NULL); - - for (unsigned i = 0; i < G_N_ELEMENTS(jd->output_ports); ++i) - g_free(jd->output_ports[i]); - - g_free(jd); -} - -static void -mpd_jack_finish(void *data) -{ - struct jack_data *jd = data; - mpd_jack_free(jd); -} - -static int -mpd_jack_process(jack_nframes_t nframes, void *arg) -{ - struct jack_data *jd = (struct jack_data *) arg; - jack_default_audio_sample_t *out; - size_t available; - - if (nframes <= 0) - return 0; - - for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i) { - available = jack_ringbuffer_read_space(jd->ringbuffer[i]); - assert(available % sample_size == 0); - available /= sample_size; - if (available > nframes) - available = nframes; - - out = jack_port_get_buffer(jd->ports[i], nframes); - jack_ringbuffer_read(jd->ringbuffer[i], - (char *)out, available * sample_size); - - while (available < nframes) - /* ringbuffer underrun, fill with silence */ - out[available++] = 0.0; - } - - return 0; -} - -static void -mpd_jack_shutdown(void *arg) -{ - struct jack_data *jd = (struct jack_data *) arg; - jd->shutdown = true; -} - -static void -set_audioformat(struct jack_data *jd, struct audio_format *audio_format) -{ - audio_format->sample_rate = jack_get_sample_rate(jd->client); - audio_format->channels = 2; - - if (audio_format->bits != 16 && audio_format->bits != 24) - audio_format->bits = 24; -} - -static void -mpd_jack_error(const char *msg) -{ - g_warning("%s", msg); -} - -#ifdef HAVE_JACK_SET_INFO_FUNCTION -static void -mpd_jack_info(const char *msg) -{ - g_message("%s", msg); -} -#endif - -static void * -mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, GError **error) -{ - struct jack_data *jd; - const char *value; - - jd = g_new(struct jack_data, 1); - jd->name = config_get_block_string(param, "name", "mpd_jack"); - - g_debug("mpd_jack_init (pid=%d)", getpid()); - - value = config_get_block_string(param, "ports", NULL); - if (value != NULL) { - char **ports = g_strsplit(value, ",", 0); - - if (ports[0] == NULL || ports[1] == NULL || ports[2] != NULL) { - g_set_error(error, jack_output_quark(), 0, - "two port names expected in line %d", - param->line); - return NULL; - } - - jd->output_ports[0] = ports[0]; - jd->output_ports[1] = ports[1]; - - g_free(ports); - } else { - jd->output_ports[0] = NULL; - jd->output_ports[1] = NULL; - } - - jd->ringbuffer_size = - config_get_block_unsigned(param, "ringbuffer_size", 32768); - - jack_set_error_function(mpd_jack_error); - -#ifdef HAVE_JACK_SET_INFO_FUNCTION - jack_set_info_function(mpd_jack_info); -#endif - - return jd; -} - -static bool -mpd_jack_test_default_device(void) -{ - return true; -} - -static bool -mpd_jack_connect(struct jack_data *jd, GError **error) -{ - const char *output_ports[2], **jports; - - for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i) - jd->ringbuffer[i] = - jack_ringbuffer_create(jd->ringbuffer_size); - - jd->shutdown = false; - - if ((jd->client = jack_client_new(jd->name)) == NULL) { - g_set_error(error, jack_output_quark(), 0, - "Failed to connect to JACK server"); - return false; - } - - jack_set_process_callback(jd->client, mpd_jack_process, jd); - jack_on_shutdown(jd->client, mpd_jack_shutdown, jd); - - for (unsigned i = 0; i < G_N_ELEMENTS(jd->ports); ++i) { - jd->ports[i] = jack_port_register(jd->client, port_names[i], - JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput, 0); - if (jd->ports[i] == NULL) { - g_set_error(error, jack_output_quark(), 0, - "Cannot register output port \"%s\"", - port_names[i]); - return false; - } - } - - if ( jack_activate(jd->client) ) { - g_set_error(error, jack_output_quark(), 0, - "cannot activate client"); - return false; - } - - if (jd->output_ports[1] == NULL) { - /* no output ports were configured - ask libjack for - defaults */ - jports = jack_get_ports(jd->client, NULL, NULL, - JackPortIsPhysical | JackPortIsInput); - if (jports == NULL) { - g_set_error(error, jack_output_quark(), 0, - "no ports found"); - return false; - } - - output_ports[0] = jports[0]; - output_ports[1] = jports[1] != NULL ? jports[1] : jports[0]; - - g_debug("output_ports: %s %s", jports[0], jports[1]); - } else { - /* use the configured output ports */ - - output_ports[0] = jd->output_ports[0]; - output_ports[1] = jd->output_ports[1]; - - jports = NULL; - } - - for (unsigned i = 0; i < G_N_ELEMENTS(jd->ports); ++i) { - int ret; - - ret = jack_connect(jd->client, jack_port_name(jd->ports[i]), - output_ports[i]); - if (ret != 0) { - g_set_error(error, jack_output_quark(), 0, - "Not a valid JACK port: %s", - output_ports[i]); - - if (jports != NULL) - free(jports); - - return false; - } - } - - if (jports != NULL) - free(jports); - - return true; -} - -static bool -mpd_jack_open(void *data, struct audio_format *audio_format, GError **error) -{ - struct jack_data *jd = data; - - assert(jd != NULL); - - if (!mpd_jack_connect(jd, error)) { - mpd_jack_client_free(jd); - return false; - } - - set_audioformat(jd, audio_format); - jd->audio_format = *audio_format; - - return true; -} - -static void -mpd_jack_close(G_GNUC_UNUSED void *data) -{ - struct jack_data *jd = data; - - mpd_jack_client_free(jd); -} - -static void -mpd_jack_cancel (G_GNUC_UNUSED void *data) -{ -} - -static inline jack_default_audio_sample_t -sample_16_to_jack(int16_t sample) -{ - return sample / (jack_default_audio_sample_t)(1 << (16 - 1)); -} - -static void -mpd_jack_write_samples_16(struct jack_data *jd, const int16_t *src, - unsigned num_samples) -{ - jack_default_audio_sample_t sample; - - while (num_samples-- > 0) { - sample = sample_16_to_jack(*src++); - jack_ringbuffer_write(jd->ringbuffer[0], (void*)&sample, - sizeof(sample)); - - sample = sample_16_to_jack(*src++); - jack_ringbuffer_write(jd->ringbuffer[1], (void*)&sample, - sizeof(sample)); - } -} - -static inline jack_default_audio_sample_t -sample_24_to_jack(int32_t sample) -{ - return sample / (jack_default_audio_sample_t)(1 << (24 - 1)); -} - -static void -mpd_jack_write_samples_24(struct jack_data *jd, const int32_t *src, - unsigned num_samples) -{ - jack_default_audio_sample_t sample; - - while (num_samples-- > 0) { - sample = sample_24_to_jack(*src++); - jack_ringbuffer_write(jd->ringbuffer[0], (void*)&sample, - sizeof(sample)); - - sample = sample_24_to_jack(*src++); - jack_ringbuffer_write(jd->ringbuffer[1], (void*)&sample, - sizeof(sample)); - } -} - -static void -mpd_jack_write_samples(struct jack_data *jd, const void *src, - unsigned num_samples) -{ - switch (jd->audio_format.bits) { - case 16: - mpd_jack_write_samples_16(jd, (const int16_t*)src, - num_samples); - break; - - case 24: - mpd_jack_write_samples_24(jd, (const int32_t*)src, - num_samples); - break; - - default: - assert(false); - } -} - -static size_t -mpd_jack_play(void *data, const void *chunk, size_t size, GError **error) -{ - struct jack_data *jd = data; - const size_t frame_size = audio_format_frame_size(&jd->audio_format); - size_t space = 0, space1; - - assert(size % frame_size == 0); - size /= frame_size; - - while (true) { - if (jd->shutdown) { - g_set_error(error, jack_output_quark(), 0, - "Refusing to play, because " - "there is no client thread"); - return 0; - } - - space = jack_ringbuffer_write_space(jd->ringbuffer[0]); - space1 = jack_ringbuffer_write_space(jd->ringbuffer[1]); - if (space > space1) - /* send data symmetrically */ - space = space1; - - if (space >= frame_size) - break; - - /* XXX do something more intelligent to - synchronize */ - g_usleep(1000); - } - - space /= sample_size; - if (space < size) - size = space; - - mpd_jack_write_samples(jd, chunk, size); - return size * frame_size; -} - -const struct audio_output_plugin jackPlugin = { - .name = "jack", - .test_default_device = mpd_jack_test_default_device, - .init = mpd_jack_init, - .finish = mpd_jack_finish, - .open = mpd_jack_open, - .play = mpd_jack_play, - .cancel = mpd_jack_cancel, - .close = mpd_jack_close, -}; diff --git a/src/output/mvp_plugin.c b/src/output/mvp_plugin.c index 96f9435a8..20587f5c5 100644 --- a/src/output/mvp_plugin.c +++ b/src/output/mvp_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,7 +22,9 @@ * http://mvpmc.sourceforge.net/ */ -#include "../output_api.h" +#include "config.h" +#include "output_api.h" +#include "fd_util.h" #include <glib.h> @@ -115,7 +117,7 @@ mvp_output_test_default_device(void) { int fd; - fd = open("/dev/adec_pcm", O_WRONLY); + fd = open_cloexec("/dev/adec_pcm", O_WRONLY, 0); if (fd >= 0) { close(fd); @@ -170,19 +172,19 @@ mvp_set_pcm_params(struct mvp_data *md, struct audio_format *audio_format, } /* 0,1=24bit(24) , 2,3=16bit */ - switch (audio_format->bits) { - case 16: + switch (audio_format->format) { + case SAMPLE_FORMAT_S16: mix[1] = 2; break; - case 24: + case SAMPLE_FORMAT_S24_P32: mix[1] = 0; break; default: - g_debug("unsupported sample format %u - falling back to stereo", - audio_format->bits); - audio_format->bits = 16; + g_debug("unsupported sample format %s - falling back to 16 bit", + sample_format_to_string(audio_format->format)); + audio_format->format = SAMPLE_FORMAT_S16; mix[1] = 2; break; } @@ -230,7 +232,8 @@ mvp_output_open(void *data, struct audio_format *audio_format, GError **error) int mix[5] = { 0, 2, 7, 1, 0 }; bool success; - if ((md->fd = open("/dev/adec_pcm", O_RDWR | O_NONBLOCK)) < 0) { + md->fd = open_cloexec("/dev/adec_pcm", O_RDWR | O_NONBLOCK, 0); + if (md->fd < 0) { g_set_error(error, mvp_output_quark(), errno, "Error opening /dev/adec_pcm: %s", strerror(errno)); diff --git a/src/output/null_plugin.c b/src/output/null_plugin.c index e9731b019..89abbd91f 100644 --- a/src/output/null_plugin.c +++ b/src/output/null_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../output_api.h" -#include "../timer.h" +#include "config.h" +#include "output_api.h" +#include "timer.h" #include <glib.h> diff --git a/src/output/openal_plugin.c b/src/output/openal_plugin.c new file mode 100644 index 000000000..767b3eb17 --- /dev/null +++ b/src/output/openal_plugin.c @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "output_api.h" +#include "timer.h" + +#include <glib.h> + +#ifndef HAVE_OSX +#include <AL/al.h> +#include <AL/alc.h> +#else +#include <OpenAL/al.h> +#include <OpenAL/alc.h> +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "openal" + +/* should be enough for buffer size = 2048 */ +#define NUM_BUFFERS 16 + +struct openal_data { + const char *device_name; + ALCdevice *device; + ALCcontext *context; + Timer *timer; + ALuint buffers[NUM_BUFFERS]; + int filled; + ALuint source; + ALenum format; + ALuint frequency; +}; + +static inline GQuark +openal_output_quark(void) +{ + return g_quark_from_static_string("openal_output"); +} + +static ALenum +openal_audio_format(struct audio_format *audio_format) +{ + switch (audio_format->format) { + case SAMPLE_FORMAT_S16: + if (audio_format->channels == 2) + return AL_FORMAT_STEREO16; + if (audio_format->channels == 1) + return AL_FORMAT_MONO16; + break; + + case SAMPLE_FORMAT_S8: + if (audio_format->channels == 2) + return AL_FORMAT_STEREO8; + if (audio_format->channels == 1) + return AL_FORMAT_MONO8; + break; + + default: + /* fall back to 16 bit */ + audio_format->format = SAMPLE_FORMAT_S16; + if (audio_format->channels == 2) + return AL_FORMAT_STEREO16; + if (audio_format->channels == 1) + return AL_FORMAT_MONO16; + break; + } + + return 0; +} + +static bool +openal_setup_context(struct openal_data *od, + GError **error) +{ + od->device = alcOpenDevice(od->device_name); + + if (od->device == NULL) { + g_set_error(error, openal_output_quark(), 0, + "Error opening OpenAL device \"%s\"\n", + od->device_name); + return false; + } + + od->context = alcCreateContext(od->device, NULL); + + if (od->context == NULL) { + g_set_error(error, openal_output_quark(), 0, + "Error creating context for \"%s\"\n", + od->device_name); + alcCloseDevice(od->device); + return false; + } + + return true; +} + +static void +openal_unqueue_buffers(struct openal_data *od) +{ + ALint num; + ALuint buffer; + + alGetSourcei(od->source, AL_BUFFERS_QUEUED, &num); + + while (num--) { + alSourceUnqueueBuffers(od->source, 1, &buffer); + } +} + +static void * +openal_init(G_GNUC_UNUSED const struct audio_format *audio_format, + const struct config_param *param, + G_GNUC_UNUSED GError **error) +{ + const char *device_name = config_get_block_string(param, "device", NULL); + struct openal_data *od; + + if (device_name == NULL) { + device_name = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER); + } + + od = g_new(struct openal_data, 1); + od->device_name = device_name; + + return od; +} + +static void +openal_finish(void *data) +{ + struct openal_data *od = data; + + g_free(od); +} + +static bool +openal_open(void *data, struct audio_format *audio_format, + GError **error) +{ + struct openal_data *od = data; + + od->format = openal_audio_format(audio_format); + + if (!od->format) { + struct audio_format_string s; + g_set_error(error, openal_output_quark(), 0, + "Unsupported audio format: %s", + audio_format_to_string(audio_format, &s)); + return false; + } + + if (!openal_setup_context(od, error)) { + return false; + } + + alcMakeContextCurrent(od->context); + alGenBuffers(NUM_BUFFERS, od->buffers); + + if (alGetError() != AL_NO_ERROR) { + g_set_error(error, openal_output_quark(), 0, + "Failed to generate buffers"); + return false; + } + + alGenSources(1, &od->source); + + if (alGetError() != AL_NO_ERROR) { + g_set_error(error, openal_output_quark(), 0, + "Failed to generate source"); + alDeleteBuffers(NUM_BUFFERS, od->buffers); + return false; + } + + od->filled = 0; + od->timer = timer_new(audio_format); + od->frequency = audio_format->sample_rate; + + return true; +} + +static void +openal_close(void *data) +{ + struct openal_data *od = data; + + timer_free(od->timer); + alcMakeContextCurrent(od->context); + alDeleteSources(1, &od->source); + alDeleteBuffers(NUM_BUFFERS, od->buffers); + alcDestroyContext(od->context); + alcCloseDevice(od->device); +} + +static size_t +openal_play(void *data, const void *chunk, size_t size, + G_GNUC_UNUSED GError **error) +{ + struct openal_data *od = data; + ALuint buffer; + ALint num, state; + + if (alcGetCurrentContext() != od->context) { + alcMakeContextCurrent(od->context); + } + + alGetSourcei(od->source, AL_BUFFERS_PROCESSED, &num); + + if (od->filled < NUM_BUFFERS) { + /* fill all buffers */ + buffer = od->buffers[od->filled]; + od->filled++; + } else { + /* wait for processed buffer */ + while (num < 1) { + if (!od->timer->started) { + timer_start(od->timer); + } else { + timer_sync(od->timer); + } + + timer_add(od->timer, size); + + alGetSourcei(od->source, AL_BUFFERS_PROCESSED, &num); + } + + alSourceUnqueueBuffers(od->source, 1, &buffer); + } + + alBufferData(buffer, od->format, chunk, size, od->frequency); + alSourceQueueBuffers(od->source, 1, &buffer); + alGetSourcei(od->source, AL_SOURCE_STATE, &state); + + if (state != AL_PLAYING) { + alSourcePlay(od->source); + } + + return size; +} + +static void +openal_cancel(void *data) +{ + struct openal_data *od = data; + + od->filled = 0; + alcMakeContextCurrent(od->context); + alSourceStop(od->source); + openal_unqueue_buffers(od); +} + +const struct audio_output_plugin openal_output_plugin = { + .name = "openal", + .init = openal_init, + .finish = openal_finish, + .open = openal_open, + .close = openal_close, + .play = openal_play, + .cancel = openal_cancel, +}; diff --git a/src/output/oss_plugin.c b/src/output/oss_plugin.c index a66bc0598..bb4164990 100644 --- a/src/output/oss_plugin.c +++ b/src/output/oss_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../output_api.h" +#include "config.h" +#include "output_api.h" #include "mixer_list.h" +#include "fd_util.h" #include <glib.h> @@ -28,6 +30,7 @@ #include <errno.h> #include <stdlib.h> #include <unistd.h> +#include <assert.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "oss" @@ -38,33 +41,15 @@ # include <sys/soundcard.h> #endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ -#if G_BYTE_ORDER == G_BIG_ENDIAN -# define AFMT_S16_MPD AFMT_S16_BE -#else -# define AFMT_S16_MPD AFMT_S16_LE -#endif - struct oss_data { int fd; const char *device; - struct audio_format audio_format; - int bitFormat; - int *supported[3]; - unsigned num_supported[3]; - int *unsupported[3]; - unsigned num_unsupported[3]; -}; - -enum oss_support { - OSS_SUPPORTED = 1, - OSS_UNSUPPORTED = 0, - OSS_UNKNOWN = -1, -}; -enum oss_param { - OSS_RATE = 0, - OSS_CHANNELS = 1, - OSS_BITS = 2, + /** + * The current input audio format. This is needed to reopen + * the device after cancel(). + */ + struct audio_format audio_format; }; /** @@ -76,188 +61,6 @@ oss_output_quark(void) return g_quark_from_static_string("oss_output"); } -static enum oss_param -oss_param_from_ioctl(unsigned param) -{ - enum oss_param idx = OSS_RATE; - - switch (param) { - case SNDCTL_DSP_SPEED: - idx = OSS_RATE; - break; - case SNDCTL_DSP_CHANNELS: - idx = OSS_CHANNELS; - break; - case SNDCTL_DSP_SAMPLESIZE: - idx = OSS_BITS; - break; - } - - return idx; -} - -static bool -oss_find_supported_param(struct oss_data *od, unsigned param, int val) -{ - enum oss_param idx = oss_param_from_ioctl(param); - - for (unsigned i = 0; i < od->num_supported[idx]; i++) - if (od->supported[idx][i] == val) - return true; - - return false; -} - -static bool -oss_can_convert(int idx, int val) -{ - switch (idx) { - case OSS_BITS: - if (val != 16) - return false; - break; - case OSS_CHANNELS: - if (val != 2) - return false; - break; - } - - return true; -} - -static int -oss_get_supported_param(struct oss_data *od, unsigned param, int val) -{ - enum oss_param idx = oss_param_from_ioctl(param); - int ret = -1; - int least = val; - int diff; - - for (unsigned i = 0; i < od->num_supported[idx]; i++) { - diff = od->supported[idx][i] - val; - if (diff < 0) - diff = -diff; - if (diff < least) { - if (!oss_can_convert(idx, od->supported[idx][i])) - continue; - - least = diff; - ret = od->supported[idx][i]; - } - } - - return ret; -} - -static bool -oss_find_unsupported_param(struct oss_data *od, unsigned param, int val) -{ - enum oss_param idx = oss_param_from_ioctl(param); - - for (unsigned i = 0; i < od->num_unsupported[idx]; i++) { - if (od->unsupported[idx][i] == val) - return true; - } - - return false; -} - -static void -oss_add_supported_param(struct oss_data *od, unsigned param, int val) -{ - enum oss_param idx = oss_param_from_ioctl(param); - - od->num_supported[idx]++; - od->supported[idx] = g_realloc(od->supported[idx], - od->num_supported[idx] * sizeof(int)); - od->supported[idx][od->num_supported[idx] - 1] = val; -} - -static void -oss_add_unsupported_param(struct oss_data *od, unsigned param, int val) -{ - enum oss_param idx = oss_param_from_ioctl(param); - - od->num_unsupported[idx]++; - od->unsupported[idx] = g_realloc(od->unsupported[idx], - od->num_unsupported[idx] * - sizeof(int)); - od->unsupported[idx][od->num_unsupported[idx] - 1] = val; -} - -static void -oss_remove_supported_param(struct oss_data *od, unsigned param, int val) -{ - unsigned j = 0; - enum oss_param idx = oss_param_from_ioctl(param); - - for (unsigned i = 0; i < od->num_supported[idx] - 1; i++) { - if (od->supported[idx][i] == val) - j = 1; - od->supported[idx][i] = od->supported[idx][i + j]; - } - - od->num_supported[idx]--; - od->supported[idx] = g_realloc(od->supported[idx], - od->num_supported[idx] * sizeof(int)); -} - -static void -oss_remove_unsupported_param(struct oss_data *od, unsigned param, int val) -{ - unsigned j = 0; - enum oss_param idx = oss_param_from_ioctl(param); - - for (unsigned i = 0; i < od->num_unsupported[idx] - 1; i++) { - if (od->unsupported[idx][i] == val) - j = 1; - od->unsupported[idx][i] = od->unsupported[idx][i + j]; - } - - od->num_unsupported[idx]--; - od->unsupported[idx] = g_realloc(od->unsupported[idx], - od->num_unsupported[idx] * - sizeof(int)); -} - -static enum oss_support -oss_param_is_supported(struct oss_data *od, unsigned param, int val) -{ - if (oss_find_supported_param(od, param, val)) - return OSS_SUPPORTED; - if (oss_find_unsupported_param(od, param, val)) - return OSS_UNSUPPORTED; - return OSS_UNKNOWN; -} - -static void -oss_set_supported(struct oss_data *od, unsigned param, int val) -{ - enum oss_support supported = oss_param_is_supported(od, param, val); - - if (supported == OSS_SUPPORTED) - return; - - if (supported == OSS_UNSUPPORTED) - oss_remove_unsupported_param(od, param, val); - - oss_add_supported_param(od, param, val); -} - -static void -oss_set_unsupported(struct oss_data *od, unsigned param, int val) -{ - enum oss_support supported = oss_param_is_supported(od, param, val); - - if (supported == OSS_UNSUPPORTED) - return; - - if (supported == OSS_SUPPORTED) - oss_remove_supported_param(od, param, val); - - oss_add_unsupported_param(od, param, val); -} - static struct oss_data * oss_data_new(void) { @@ -266,38 +69,12 @@ oss_data_new(void) ret->device = NULL; ret->fd = -1; - ret->supported[OSS_RATE] = NULL; - ret->supported[OSS_CHANNELS] = NULL; - ret->supported[OSS_BITS] = NULL; - ret->unsupported[OSS_RATE] = NULL; - ret->unsupported[OSS_CHANNELS] = NULL; - ret->unsupported[OSS_BITS] = NULL; - - ret->num_supported[OSS_RATE] = 0; - ret->num_supported[OSS_CHANNELS] = 0; - ret->num_supported[OSS_BITS] = 0; - ret->num_unsupported[OSS_RATE] = 0; - ret->num_unsupported[OSS_CHANNELS] = 0; - ret->num_unsupported[OSS_BITS] = 0; - - oss_set_supported(ret, SNDCTL_DSP_SPEED, 48000); - oss_set_supported(ret, SNDCTL_DSP_SPEED, 44100); - oss_set_supported(ret, SNDCTL_DSP_CHANNELS, 2); - oss_set_supported(ret, SNDCTL_DSP_SAMPLESIZE, 16); - return ret; } static void oss_data_free(struct oss_data *od) { - g_free(od->supported[OSS_RATE]); - g_free(od->supported[OSS_CHANNELS]); - g_free(od->supported[OSS_BITS]); - g_free(od->unsupported[OSS_RATE]); - g_free(od->unsupported[OSS_CHANNELS]); - g_free(od->unsupported[OSS_BITS]); - g_free(od); } @@ -343,7 +120,9 @@ oss_output_test_default_device(void) int fd, i; for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) { - if ((fd = open(default_devices[i], O_WRONLY)) >= 0) { + fd = open_cloexec(default_devices[i], O_WRONLY, 0); + + if (fd >= 0) { close(fd); return true; } @@ -419,113 +198,386 @@ oss_output_finish(void *data) oss_data_free(od); } -static int -oss_set_param(struct oss_data *od, unsigned param, int *value) +static void +oss_close(struct oss_data *od) { - int val = *value; - int copy; - enum oss_support supported = oss_param_is_supported(od, param, val); - - do { - if (supported == OSS_UNSUPPORTED) { - val = oss_get_supported_param(od, param, val); - if (copy < 0) - return -1; - } - copy = val; - if (ioctl(od->fd, param, ©)) { - oss_set_unsupported(od, param, val); - supported = OSS_UNSUPPORTED; - } else { - if (supported == OSS_UNKNOWN) { - oss_set_supported(od, param, val); - supported = OSS_SUPPORTED; - } - val = copy; - } - } while (supported == OSS_UNSUPPORTED); + if (od->fd >= 0) + close(od->fd); + od->fd = -1; +} + +/** + * A tri-state type for oss_try_ioctl(). + */ +enum oss_setup_result { + SUCCESS, + ERROR, + UNSUPPORTED, +}; + +/** + * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is + * returned. If the parameter is not supported, UNSUPPORTED is + * returned. Any other failure returns ERROR and allocates a GError. + */ +static enum oss_setup_result +oss_try_ioctl_r(int fd, unsigned long request, int *value_r, + const char *msg, GError **error_r) +{ + assert(fd >= 0); + assert(value_r != NULL); + assert(msg != NULL); + assert(error_r == NULL || *error_r == NULL); - *value = val; + int ret = ioctl(fd, request, value_r); + if (ret >= 0) + return SUCCESS; - return 0; + if (errno == EINVAL) + return UNSUPPORTED; + + g_set_error(error_r, oss_output_quark(), errno, + "%s: %s", msg, g_strerror(errno)); + return ERROR; } -static void -oss_close(struct oss_data *od) +/** + * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is + * returned. If the parameter is not supported, UNSUPPORTED is + * returned. Any other failure returns ERROR and allocates a GError. + */ +static enum oss_setup_result +oss_try_ioctl(int fd, unsigned long request, int value, + const char *msg, GError **error_r) { - if (od->fd >= 0) - while (close(od->fd) && errno == EINTR) ; - od->fd = -1; + return oss_try_ioctl_r(fd, request, &value, msg, error_r); } /** - * Sets up the OSS device which was opened before. + * Set up the channel number, and attempts to find alternatives if the + * specified number is not supported. */ static bool -oss_setup(struct oss_data *od, GError **error) +oss_setup_channels(int fd, struct audio_format *audio_format, GError **error_r) { - int tmp; - - tmp = od->audio_format.channels; - if (oss_set_param(od, SNDCTL_DSP_CHANNELS, &tmp)) { - g_set_error(error, oss_output_quark(), errno, - "OSS device \"%s\" does not support %u channels: %s", - od->device, od->audio_format.channels, - strerror(errno)); + const char *const msg = "Failed to set channel count"; + int channels = audio_format->channels; + enum oss_setup_result result = + oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, msg, error_r); + switch (result) { + case SUCCESS: + if (!audio_valid_channel_count(channels)) + break; + + audio_format->channels = channels; + return true; + + case ERROR: return false; + + case UNSUPPORTED: + break; } - od->audio_format.channels = tmp; - tmp = od->audio_format.sample_rate; - if (oss_set_param(od, SNDCTL_DSP_SPEED, &tmp)) { - g_set_error(error, oss_output_quark(), errno, - "OSS device \"%s\" does not support %u Hz audio: %s", - od->device, od->audio_format.sample_rate, - strerror(errno)); - return false; + for (unsigned i = 1; i < 2; ++i) { + if (i == audio_format->channels) + /* don't try that again */ + continue; + + channels = i; + result = oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, + msg, error_r); + switch (result) { + case SUCCESS: + if (!audio_valid_channel_count(channels)) + break; + + audio_format->channels = channels; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } } - od->audio_format.sample_rate = tmp; - switch (od->audio_format.bits) { - case 8: - tmp = AFMT_S8; - break; - case 16: - tmp = AFMT_S16_MPD; + g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg); + return false; +} + +/** + * Set up the sample rate, and attempts to find alternatives if the + * specified sample rate is not supported. + */ +static bool +oss_setup_sample_rate(int fd, struct audio_format *audio_format, + GError **error_r) +{ + const char *const msg = "Failed to set sample rate"; + int sample_rate = audio_format->sample_rate; + enum oss_setup_result result = + oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate, + msg, error_r); + switch (result) { + case SUCCESS: + if (!audio_valid_sample_rate(sample_rate)) + break; + + audio_format->sample_rate = sample_rate; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: break; + } + + static const int sample_rates[] = { 48000, 44100, 0 }; + for (unsigned i = 0; sample_rates[i] != 0; ++i) { + sample_rate = sample_rates[i]; + if (sample_rate == (int)audio_format->sample_rate) + continue; + + result = oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate, + msg, error_r); + switch (result) { + case SUCCESS: + if (!audio_valid_sample_rate(sample_rate)) + break; + + audio_format->sample_rate = sample_rate; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + } + + g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg); + return false; +} + +/** + * Convert a MPD sample format to its OSS counterpart. Returns + * AFMT_QUERY if there is no direct counterpart. + */ +static int +sample_format_to_oss(enum sample_format format) +{ + switch (format) { + case SAMPLE_FORMAT_UNDEFINED: + return AFMT_QUERY; + + case SAMPLE_FORMAT_S8: + return AFMT_S8; + + case SAMPLE_FORMAT_S16: + return AFMT_S16_NE; + + case SAMPLE_FORMAT_S24: +#ifdef AFMT_S24_PACKED + return AFMT_S24_PACKED; +#else + return AFMT_QUERY; +#endif + + case SAMPLE_FORMAT_S24_P32: +#ifdef AFMT_S24_NE + return AFMT_S24_NE; +#else + return AFMT_QUERY; +#endif + + case SAMPLE_FORMAT_S32: +#ifdef AFMT_S32_NE + return AFMT_S32_NE; +#else + return AFMT_QUERY; +#endif + } + + return AFMT_QUERY; +} + +/** + * Convert an OSS sample format to its MPD counterpart. Returns + * SAMPLE_FORMAT_UNDEFINED if there is no direct counterpart. + */ +static enum sample_format +sample_format_from_oss(int format) +{ + switch (format) { + case AFMT_S8: + return SAMPLE_FORMAT_S8; + + case AFMT_S16_NE: + return SAMPLE_FORMAT_S16; + +#ifdef AFMT_S24_PACKED + case AFMT_S24_PACKED: + return SAMPLE_FORMAT_S24; +#endif + +#ifdef AFMT_S24_NE + case AFMT_S24_NE: + return SAMPLE_FORMAT_S24_P32; +#endif + +#ifdef AFMT_S32_NE + case AFMT_S32_NE: + return SAMPLE_FORMAT_S32; +#endif default: - /* not supported by OSS - fall back to 16 bit */ - od->audio_format.bits = 16; - tmp = AFMT_S16_MPD; - break; + return SAMPLE_FORMAT_UNDEFINED; } +} - if (oss_set_param(od, SNDCTL_DSP_SAMPLESIZE, &tmp)) { - g_set_error(error, oss_output_quark(), errno, - "OSS device \"%s\" does not support %u bit audio: %s", - od->device, tmp, strerror(errno)); +/** + * Set up the sample format, and attempts to find alternatives if the + * specified format is not supported. + */ +static bool +oss_setup_sample_format(int fd, struct audio_format *audio_format, + GError **error_r) +{ + const char *const msg = "Failed to set sample format"; + int oss_format = sample_format_to_oss(audio_format->format); + enum oss_setup_result result = oss_format != AFMT_QUERY + ? oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, + &oss_format, msg, error_r) + : UNSUPPORTED; + enum sample_format mpd_format; + switch (result) { + case SUCCESS: + mpd_format = sample_format_from_oss(oss_format); + if (mpd_format == SAMPLE_FORMAT_UNDEFINED) + break; + + audio_format->format = mpd_format; + return true; + + case ERROR: return false; + + case UNSUPPORTED: + break; } - return true; + /* the requested sample format is not available - probe for + other formats supported by MPD */ + + static const enum sample_format sample_formats[] = { + SAMPLE_FORMAT_S24_P32, + SAMPLE_FORMAT_S32, + SAMPLE_FORMAT_S24, + SAMPLE_FORMAT_S16, + SAMPLE_FORMAT_S8, + SAMPLE_FORMAT_UNDEFINED /* sentinel */ + }; + + for (unsigned i = 0; sample_formats[i] != SAMPLE_FORMAT_UNDEFINED; ++i) { + mpd_format = sample_formats[i]; + if (mpd_format == audio_format->format) + /* don't try that again */ + continue; + + oss_format = sample_format_to_oss(mpd_format); + if (oss_format == AFMT_QUERY) + /* not supported by this OSS version */ + continue; + + result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, + &oss_format, msg, error_r); + switch (result) { + case SUCCESS: + mpd_format = sample_format_from_oss(oss_format); + if (mpd_format == SAMPLE_FORMAT_UNDEFINED) + break; + + audio_format->format = mpd_format; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + } + + g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg); + return false; } +/** + * Sets up the OSS device which was opened before. + */ static bool -oss_open(struct oss_data *od, GError **error) +oss_setup(struct oss_data *od, struct audio_format *audio_format, + GError **error_r) { - bool success; + return oss_setup_channels(od->fd, audio_format, error_r) && + oss_setup_sample_rate(od->fd, audio_format, error_r) && + oss_setup_sample_format(od->fd, audio_format, error_r); +} - if ((od->fd = open(od->device, O_WRONLY)) < 0) { - g_set_error(error, oss_output_quark(), errno, +/** + * Reopen the device with the saved audio_format, without any probing. + */ +static bool +oss_reopen(struct oss_data *od, GError **error_r) +{ + assert(od->fd < 0); + + od->fd = open_cloexec(od->device, O_WRONLY, 0); + if (od->fd < 0) { + g_set_error(error_r, oss_output_quark(), errno, "Error opening OSS device \"%s\": %s", od->device, strerror(errno)); return false; } - success = oss_setup(od, error); - if (!success) { + enum oss_setup_result result; + + const char *const msg1 = "Failed to set channel count"; + result = oss_try_ioctl(od->fd, SNDCTL_DSP_CHANNELS, + od->audio_format.channels, msg1, error_r); + if (result != SUCCESS) { + oss_close(od); + if (result == UNSUPPORTED) + g_set_error(error_r, oss_output_quark(), EINVAL, + "%s", msg1); + return false; + } + + const char *const msg2 = "Failed to set sample rate"; + result = oss_try_ioctl(od->fd, SNDCTL_DSP_SPEED, + od->audio_format.sample_rate, msg2, error_r); + if (result != SUCCESS) { + oss_close(od); + if (result == UNSUPPORTED) + g_set_error(error_r, oss_output_quark(), EINVAL, + "%s", msg2); + return false; + } + + const char *const msg3 = "Failed to set sample format"; + assert(sample_format_to_oss(od->audio_format.format) != AFMT_QUERY); + result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE, + sample_format_to_oss(od->audio_format.format), + msg3, error_r); + if (result != SUCCESS) { oss_close(od); + if (result == UNSUPPORTED) + g_set_error(error_r, oss_output_quark(), EINVAL, + "%s", msg3); return false; } @@ -535,18 +587,23 @@ oss_open(struct oss_data *od, GError **error) static bool oss_output_open(void *data, struct audio_format *audio_format, GError **error) { - bool ret; struct oss_data *od = data; - od->audio_format = *audio_format; - - ret = oss_open(od, error); - if (!ret) + od->fd = open_cloexec(od->device, O_WRONLY, 0); + if (od->fd < 0) { + g_set_error(error, oss_output_quark(), errno, + "Error opening OSS device \"%s\": %s", + od->device, strerror(errno)); return false; + } - *audio_format = od->audio_format; + if (!oss_setup(od, audio_format, error)) { + oss_close(od); + return false; + } - return ret; + od->audio_format = *audio_format; + return true; } static void @@ -575,7 +632,7 @@ oss_output_play(void *data, const void *chunk, size_t size, GError **error) ssize_t ret; /* reopen the device since it was closed by dropBufferedAudio */ - if (od->fd < 0 && !oss_open(od, error)) + if (od->fd < 0 && !oss_reopen(od, error)) return 0; while (true) { @@ -601,5 +658,6 @@ const struct audio_output_plugin oss_output_plugin = { .close = oss_output_close, .play = oss_output_play, .cancel = oss_output_cancel, - .mixer_plugin = &oss_mixer, + + .mixer_plugin = &oss_mixer_plugin, }; diff --git a/src/output/osx_plugin.c b/src/output/osx_plugin.c index 04173bf79..17d138d35 100644 --- a/src/output/osx_plugin.c +++ b/src/output/osx_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,7 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../output_api.h" +#include "config.h" +#include "output_api.h" #include <glib.h> #include <AudioUnit/AudioUnit.h> @@ -165,9 +166,6 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error) OSStatus status; ComponentResult result; - if (audio_format->bits > 16) - audio_format->bits = 16; - desc.componentType = kAudioUnitType_Output; desc.componentSubType = kAudioUnitSubType_DefaultOutput; desc.componentManufacturer = kAudioUnitManufacturer_Apple; @@ -225,7 +223,21 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error) stream_description.mFramesPerPacket = 1; stream_description.mBytesPerFrame = stream_description.mBytesPerPacket; stream_description.mChannelsPerFrame = audio_format->channels; - stream_description.mBitsPerChannel = audio_format->bits; + + switch (audio_format->format) { + case SAMPLE_FORMAT_S8: + stream_description.mBitsPerChannel = 8; + break; + + case SAMPLE_FORMAT_S16: + stream_description.mBitsPerChannel = 16; + break; + + default: + audio_format->format = SAMPLE_FORMAT_S16; + stream_description.mBitsPerChannel = 16; + break; + } result = AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, diff --git a/src/output/pipe_output_plugin.c b/src/output/pipe_output_plugin.c index 610ad9e8d..1d1aec7b1 100644 --- a/src/output/pipe_output_plugin.c +++ b/src/output/pipe_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "output_api.h" #include <stdio.h> diff --git a/src/output/pulse_output_plugin.c b/src/output/pulse_output_plugin.c new file mode 100644 index 000000000..d29fbd705 --- /dev/null +++ b/src/output/pulse_output_plugin.c @@ -0,0 +1,825 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "pulse_output_plugin.h" +#include "output_api.h" +#include "mixer_list.h" +#include "mixer/pulse_mixer_plugin.h" + +#include <glib.h> + +#include <pulse/thread-mainloop.h> +#include <pulse/context.h> +#include <pulse/stream.h> +#include <pulse/introspect.h> +#include <pulse/subscribe.h> +#include <pulse/error.h> + +#include <assert.h> + +#define MPD_PULSE_NAME "Music Player Daemon" + +/** + * The quark used for GError.domain. + */ +static inline GQuark +pulse_output_quark(void) +{ + return g_quark_from_static_string("pulse_output"); +} + +void +pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm) +{ + assert(po != NULL); + assert(po->mixer == NULL); + assert(pm != NULL); + + po->mixer = pm; + + if (po->mainloop == NULL) + return; + + pa_threaded_mainloop_lock(po->mainloop); + + if (po->context != NULL && + pa_context_get_state(po->context) == PA_CONTEXT_READY) { + pulse_mixer_on_connect(pm, po->context); + + if (po->stream != NULL && + pa_stream_get_state(po->stream) == PA_STREAM_READY) + pulse_mixer_on_change(pm, po->context, po->stream); + } + + pa_threaded_mainloop_unlock(po->mainloop); +} + +void +pulse_output_clear_mixer(struct pulse_output *po, struct pulse_mixer *pm) +{ + assert(po != NULL); + assert(pm != NULL); + assert(po->mixer == pm); + + po->mixer = NULL; +} + +bool +pulse_output_set_volume(struct pulse_output *po, + const struct pa_cvolume *volume, GError **error_r) +{ + pa_operation *o; + + if (po->context == NULL || po->stream == NULL || + pa_stream_get_state(po->stream) != PA_STREAM_READY) { + g_set_error(error_r, pulse_output_quark(), 0, "disconnected"); + return false; + } + + o = pa_context_set_sink_input_volume(po->context, + pa_stream_get_index(po->stream), + volume, NULL, NULL); + if (o == NULL) { + g_set_error(error_r, pulse_output_quark(), 0, + "failed to set PulseAudio volume: %s", + pa_strerror(pa_context_errno(po->context))); + return false; + } + + pa_operation_unref(o); + return true; +} + +/** + * \brief waits for a pulseaudio operation to finish, frees it and + * unlocks the mainloop + * \param operation the operation to wait for + * \return true if operation has finished normally (DONE state), + * false otherwise + */ +static bool +pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop, + struct pa_operation *operation) +{ + pa_operation_state_t state; + + assert(mainloop != NULL); + assert(operation != NULL); + + state = pa_operation_get_state(operation); + while (state == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(mainloop); + state = pa_operation_get_state(operation); + } + + pa_operation_unref(operation); + + return state == PA_OPERATION_DONE; +} + +/** + * Callback function for stream operation. It just sends a signal to + * the caller thread, to wake pulse_wait_for_operation() up. + */ +static void +pulse_output_stream_success_cb(G_GNUC_UNUSED pa_stream *s, + G_GNUC_UNUSED int success, void *userdata) +{ + struct pulse_output *po = userdata; + + pa_threaded_mainloop_signal(po->mainloop, 0); +} + +static void +pulse_output_context_state_cb(struct pa_context *context, void *userdata) +{ + struct pulse_output *po = userdata; + + switch (pa_context_get_state(context)) { + case PA_CONTEXT_READY: + if (po->mixer != NULL) + pulse_mixer_on_connect(po->mixer, context); + + pa_threaded_mainloop_signal(po->mainloop, 0); + break; + + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + if (po->mixer != NULL) + pulse_mixer_on_disconnect(po->mixer); + + /* the caller thread might be waiting for these + states */ + pa_threaded_mainloop_signal(po->mainloop, 0); + break; + + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + } +} + +static void +pulse_output_subscribe_cb(pa_context *context, + pa_subscription_event_type_t t, + uint32_t idx, void *userdata) +{ + struct pulse_output *po = userdata; + pa_subscription_event_type_t facility + = t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK; + pa_subscription_event_type_t type + = t & PA_SUBSCRIPTION_EVENT_TYPE_MASK; + + if (po->mixer != NULL && + facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT && + po->stream != NULL && + pa_stream_get_state(po->stream) == PA_STREAM_READY && + idx == pa_stream_get_index(po->stream) && + (type == PA_SUBSCRIPTION_EVENT_NEW || + type == PA_SUBSCRIPTION_EVENT_CHANGE)) + pulse_mixer_on_change(po->mixer, context, po->stream); +} + +/** + * Attempt to connect asynchronously to the PulseAudio server. + * + * @return true on success, false on error + */ +static bool +pulse_output_connect(struct pulse_output *po, GError **error_r) +{ + int error; + + error = pa_context_connect(po->context, po->server, + (pa_context_flags_t)0, NULL); + if (error < 0) { + g_set_error(error_r, pulse_output_quark(), 0, + "pa_context_connect() has failed: %s", + pa_strerror(pa_context_errno(po->context))); + return false; + } + + return true; +} + +/** + * Create, set up and connect a context. + * + * @return true on success, false on error + */ +static bool +pulse_output_setup_context(struct pulse_output *po, GError **error_r) +{ + po->context = pa_context_new(pa_threaded_mainloop_get_api(po->mainloop), + MPD_PULSE_NAME); + if (po->context == NULL) { + g_set_error(error_r, pulse_output_quark(), 0, + "pa_context_new() has failed"); + return false; + } + + pa_context_set_state_callback(po->context, + pulse_output_context_state_cb, po); + pa_context_set_subscribe_callback(po->context, + pulse_output_subscribe_cb, po); + + if (!pulse_output_connect(po, error_r)) { + pa_context_unref(po->context); + po->context = NULL; + return false; + } + + return true; +} + +/** + * Frees and clears the context. + */ +static void +pulse_output_delete_context(struct pulse_output *po) +{ + pa_context_disconnect(po->context); + pa_context_unref(po->context); + po->context = NULL; +} + +static void * +pulse_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, + const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct pulse_output *po; + + g_setenv("PULSE_PROP_media.role", "music", true); + + po = g_new(struct pulse_output, 1); + po->name = config_get_block_string(param, "name", "mpd_pulse"); + po->server = config_get_block_string(param, "server", NULL); + po->sink = config_get_block_string(param, "sink", NULL); + + po->mixer = NULL; + po->mainloop = NULL; + po->context = NULL; + po->stream = NULL; + + return po; +} + +static void +pulse_output_finish(void *data) +{ + struct pulse_output *po = data; + + g_free(po); +} + +static bool +pulse_output_enable(void *data, GError **error_r) +{ + struct pulse_output *po = data; + + assert(po->mainloop == NULL); + assert(po->context == NULL); + + /* create the libpulse mainloop and start the thread */ + + po->mainloop = pa_threaded_mainloop_new(); + if (po->mainloop == NULL) { + g_free(po); + + g_set_error(error_r, pulse_output_quark(), 0, + "pa_threaded_mainloop_new() has failed"); + return false; + } + + pa_threaded_mainloop_lock(po->mainloop); + + if (pa_threaded_mainloop_start(po->mainloop) < 0) { + pa_threaded_mainloop_unlock(po->mainloop); + pa_threaded_mainloop_free(po->mainloop); + po->mainloop = NULL; + + g_set_error(error_r, pulse_output_quark(), 0, + "pa_threaded_mainloop_start() has failed"); + return false; + } + + pa_threaded_mainloop_unlock(po->mainloop); + + /* create the libpulse context and connect it */ + + pa_threaded_mainloop_lock(po->mainloop); + + if (!pulse_output_setup_context(po, error_r)) { + pa_threaded_mainloop_unlock(po->mainloop); + pa_threaded_mainloop_stop(po->mainloop); + pa_threaded_mainloop_free(po->mainloop); + po->mainloop = NULL; + return false; + } + + pa_threaded_mainloop_unlock(po->mainloop); + + return true; +} + +static void +pulse_output_disable(void *data) +{ + struct pulse_output *po = data; + + pa_threaded_mainloop_stop(po->mainloop); + if (po->context != NULL) + pulse_output_delete_context(po); + pa_threaded_mainloop_free(po->mainloop); + po->mainloop = NULL; +} + +/** + * Check if the context is (already) connected, and waits if not. If + * the context has been disconnected, retry to connect. + * + * @return true on success, false on error + */ +static bool +pulse_output_wait_connection(struct pulse_output *po, GError **error_r) +{ + pa_context_state_t state; + + pa_threaded_mainloop_lock(po->mainloop); + + if (po->context == NULL && !pulse_output_setup_context(po, error_r)) + return false; + + while (true) { + state = pa_context_get_state(po->context); + switch (state) { + case PA_CONTEXT_READY: + /* nothing to do */ + pa_threaded_mainloop_unlock(po->mainloop); + return true; + + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + /* failure */ + g_set_error(error_r, pulse_output_quark(), 0, + "failed to connect: %s", + pa_strerror(pa_context_errno(po->context))); + pulse_output_delete_context(po); + pa_threaded_mainloop_unlock(po->mainloop); + return false; + + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + /* wait some more */ + pa_threaded_mainloop_wait(po->mainloop); + break; + } + } +} + +static void +pulse_output_stream_state_cb(pa_stream *stream, void *userdata) +{ + struct pulse_output *po = userdata; + + switch (pa_stream_get_state(stream)) { + case PA_STREAM_READY: + if (po->mixer != NULL) + pulse_mixer_on_change(po->mixer, po->context, stream); + + pa_threaded_mainloop_signal(po->mainloop, 0); + break; + + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + if (po->mixer != NULL) + pulse_mixer_on_disconnect(po->mixer); + + pa_threaded_mainloop_signal(po->mainloop, 0); + break; + + case PA_STREAM_UNCONNECTED: + case PA_STREAM_CREATING: + break; + } +} + +static void +pulse_output_stream_write_cb(G_GNUC_UNUSED pa_stream *stream, size_t nbytes, + void *userdata) +{ + struct pulse_output *po = userdata; + + po->writable = nbytes; + pa_threaded_mainloop_signal(po->mainloop, 0); +} + +static bool +pulse_output_open(void *data, struct audio_format *audio_format, + GError **error_r) +{ + struct pulse_output *po = data; + pa_sample_spec ss; + int error; + + if (po->context != NULL) { + switch (pa_context_get_state(po->context)) { + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + /* the connection was closed meanwhile; delete + it, and pulse_output_wait_connection() will + reopen it */ + pulse_output_delete_context(po); + break; + + case PA_CONTEXT_READY: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + } + } + + if (!pulse_output_wait_connection(po, error_r)) + return false; + + /* MPD doesn't support the other pulseaudio sample formats, so + we just force MPD to send us everything as 16 bit */ + audio_format->format = SAMPLE_FORMAT_S16; + + ss.format = PA_SAMPLE_S16NE; + ss.rate = audio_format->sample_rate; + ss.channels = audio_format->channels; + + pa_threaded_mainloop_lock(po->mainloop); + + /* create a stream .. */ + + po->stream = pa_stream_new(po->context, po->name, &ss, NULL); + if (po->stream == NULL) { + g_set_error(error_r, pulse_output_quark(), 0, + "pa_stream_new() has failed: %s", + pa_strerror(pa_context_errno(po->context))); + pa_threaded_mainloop_unlock(po->mainloop); + return false; + } + + pa_stream_set_state_callback(po->stream, + pulse_output_stream_state_cb, po); + pa_stream_set_write_callback(po->stream, + pulse_output_stream_write_cb, po); + + /* .. and connect it (asynchronously) */ + + error = pa_stream_connect_playback(po->stream, po->sink, + NULL, 0, NULL, NULL); + if (error < 0) { + pa_stream_unref(po->stream); + po->stream = NULL; + + g_set_error(error_r, pulse_output_quark(), 0, + "pa_stream_connect_playback() has failed: %s", + pa_strerror(pa_context_errno(po->context))); + pa_threaded_mainloop_unlock(po->mainloop); + return false; + } + + pa_threaded_mainloop_unlock(po->mainloop); + +#if !PA_CHECK_VERSION(0,9,11) + po->pause = false; +#endif + + return true; +} + +static void +pulse_output_close(void *data) +{ + struct pulse_output *po = data; + pa_operation *o; + + pa_threaded_mainloop_lock(po->mainloop); + + if (pa_stream_get_state(po->stream) == PA_STREAM_READY) { + o = pa_stream_drain(po->stream, + pulse_output_stream_success_cb, po); + if (o == NULL) { + g_warning("pa_stream_drain() has failed: %s", + pa_strerror(pa_context_errno(po->context))); + } else + pulse_wait_for_operation(po->mainloop, o); + } + + pa_stream_disconnect(po->stream); + pa_stream_unref(po->stream); + po->stream = NULL; + + if (po->context != NULL && + pa_context_get_state(po->context) != PA_CONTEXT_READY) + pulse_output_delete_context(po); + + pa_threaded_mainloop_unlock(po->mainloop); +} + +/** + * Check if the stream is (already) connected, and waits for a signal + * if not. The mainloop must be locked before calling this function. + * + * @return the current stream state + */ +static pa_stream_state_t +pulse_output_check_stream(struct pulse_output *po) +{ + pa_stream_state_t state = pa_stream_get_state(po->stream); + + switch (state) { + case PA_STREAM_READY: + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + case PA_STREAM_UNCONNECTED: + break; + + case PA_STREAM_CREATING: + pa_threaded_mainloop_wait(po->mainloop); + state = pa_stream_get_state(po->stream); + break; + } + + return state; +} + +/** + * Check if the stream is (already) connected, and waits if not. The + * mainloop must be locked before calling this function. + * + * @return true on success, false on error + */ +static bool +pulse_output_wait_stream(struct pulse_output *po, GError **error_r) +{ + pa_stream_state_t state = pa_stream_get_state(po->stream); + + switch (state) { + case PA_STREAM_READY: + return true; + + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + case PA_STREAM_UNCONNECTED: + g_set_error(error_r, pulse_output_quark(), 0, + "disconnected"); + return false; + + case PA_STREAM_CREATING: + break; + } + + do { + state = pulse_output_check_stream(po); + } while (state == PA_STREAM_CREATING); + + if (state != PA_STREAM_READY) { + g_set_error(error_r, pulse_output_quark(), 0, + "failed to connect the stream: %s", + pa_strerror(pa_context_errno(po->context))); + return false; + } + + return true; +} + +/** + * Determines whether the stream is paused. On libpulse older than + * 0.9.11, it uses a custom pause flag. + */ +static bool +pulse_output_stream_is_paused(struct pulse_output *po) +{ + assert(po->stream != NULL); + +#if !defined(PA_CHECK_VERSION) || !PA_CHECK_VERSION(0,9,11) + return po->pause; +#else + return pa_stream_is_corked(po->stream); +#endif +} + +/** + * Sets cork mode on the stream. + */ +static bool +pulse_output_stream_pause(struct pulse_output *po, bool pause, + GError **error_r) +{ + pa_operation *o; + + assert(po->stream != NULL); + + o = pa_stream_cork(po->stream, pause, + pulse_output_stream_success_cb, po); + if (o == NULL) { + g_set_error(error_r, pulse_output_quark(), 0, + "pa_stream_cork() has failed: %s", + pa_strerror(pa_context_errno(po->context))); + return false; + } + + if (!pulse_wait_for_operation(po->mainloop, o)) { + g_set_error(error_r, pulse_output_quark(), 0, + "pa_stream_cork() has failed: %s", + pa_strerror(pa_context_errno(po->context))); + return false; + } + +#if !PA_CHECK_VERSION(0,9,11) + po->pause = pause; +#endif + return true; +} + +static size_t +pulse_output_play(void *data, const void *chunk, size_t size, GError **error_r) +{ + struct pulse_output *po = data; + int error; + + assert(po->stream != NULL); + + pa_threaded_mainloop_lock(po->mainloop); + + /* check if the stream is (already) connected */ + + if (!pulse_output_wait_stream(po, error_r)) { + pa_threaded_mainloop_unlock(po->mainloop); + return 0; + } + + assert(po->context != NULL); + + /* unpause if previously paused */ + + if (pulse_output_stream_is_paused(po) && + !pulse_output_stream_pause(po, false, error_r)) + return 0; + + /* wait until the server allows us to write */ + + while (po->writable == 0) { + pa_threaded_mainloop_wait(po->mainloop); + + if (pa_stream_get_state(po->stream) != PA_STREAM_READY) { + pa_threaded_mainloop_unlock(po->mainloop); + g_set_error(error_r, pulse_output_quark(), 0, + "disconnected"); + return false; + } + } + + /* now write */ + + if (size > po->writable) + /* don't send more than possible */ + size = po->writable; + + po->writable -= size; + + error = pa_stream_write(po->stream, chunk, size, NULL, + 0, PA_SEEK_RELATIVE); + pa_threaded_mainloop_unlock(po->mainloop); + if (error < 0) { + g_set_error(error_r, pulse_output_quark(), error, + "%s", pa_strerror(error)); + return 0; + } + + return size; +} + +static void +pulse_output_cancel(void *data) +{ + struct pulse_output *po = data; + pa_operation *o; + + assert(po->stream != NULL); + + pa_threaded_mainloop_lock(po->mainloop); + + if (pa_stream_get_state(po->stream) != PA_STREAM_READY) { + /* no need to flush when the stream isn't connected + yet */ + pa_threaded_mainloop_unlock(po->mainloop); + return; + } + + assert(po->context != NULL); + + o = pa_stream_flush(po->stream, pulse_output_stream_success_cb, po); + if (o == NULL) { + g_warning("pa_stream_flush() has failed: %s", + pa_strerror(pa_context_errno(po->context))); + pa_threaded_mainloop_unlock(po->mainloop); + return; + } + + pulse_wait_for_operation(po->mainloop, o); + pa_threaded_mainloop_unlock(po->mainloop); +} + +static bool +pulse_output_pause(void *data) +{ + struct pulse_output *po = data; + GError *error = NULL; + + assert(po->stream != NULL); + + pa_threaded_mainloop_lock(po->mainloop); + + /* check if the stream is (already/still) connected */ + + if (!pulse_output_wait_stream(po, &error)) { + pa_threaded_mainloop_unlock(po->mainloop); + g_warning("%s", error->message); + g_error_free(error); + return false; + } + + assert(po->context != NULL); + + /* cork the stream */ + + if (pulse_output_stream_is_paused(po)) { + /* already paused; due to a MPD API limitation, we + have to sleep a little bit here, to avoid hogging + the CPU */ + + g_usleep(50000); + } else if (!pulse_output_stream_pause(po, true, &error)) { + pa_threaded_mainloop_unlock(po->mainloop); + g_warning("%s", error->message); + g_error_free(error); + return false; + } + + pa_threaded_mainloop_unlock(po->mainloop); + + return true; +} + +static bool +pulse_output_test_default_device(void) +{ + struct pulse_output *po; + bool success; + + po = pulse_output_init(NULL, NULL, NULL); + if (po == NULL) + return false; + + success = pulse_output_wait_connection(po, NULL); + pulse_output_finish(po); + + return success; +} + +const struct audio_output_plugin pulse_output_plugin = { + .name = "pulse", + + .test_default_device = pulse_output_test_default_device, + .init = pulse_output_init, + .finish = pulse_output_finish, + .enable = pulse_output_enable, + .disable = pulse_output_disable, + .open = pulse_output_open, + .play = pulse_output_play, + .cancel = pulse_output_cancel, + .pause = pulse_output_pause, + .close = pulse_output_close, + + .mixer_plugin = &pulse_mixer_plugin, +}; diff --git a/src/output/pulse_output_plugin.h b/src/output/pulse_output_plugin.h new file mode 100644 index 000000000..06e3aec43 --- /dev/null +++ b/src/output/pulse_output_plugin.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PULSE_OUTPUT_PLUGIN_H +#define MPD_PULSE_OUTPUT_PLUGIN_H + +#include <stdbool.h> +#include <stddef.h> + +#include <glib.h> + +#include <pulse/version.h> + +#if !defined(PA_CHECK_VERSION) +/** + * This macro was implemented in libpulse 0.9.16. + */ +#define PA_CHECK_VERSION(a,b,c) false +#endif + +struct pa_operation; +struct pa_cvolume; + +struct pulse_output { + const char *name; + const char *server; + const char *sink; + + struct pulse_mixer *mixer; + + struct pa_threaded_mainloop *mainloop; + struct pa_context *context; + struct pa_stream *stream; + + size_t writable; + +#if !PA_CHECK_VERSION(0,9,11) + /** + * We need this variable because pa_stream_is_corked() wasn't + * added before 0.9.11. + */ + bool pause; +#endif +}; + +void +pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm); + +void +pulse_output_clear_mixer(struct pulse_output *po, struct pulse_mixer *pm); + +bool +pulse_output_set_volume(struct pulse_output *po, + const struct pa_cvolume *volume, GError **error_r); + +#endif diff --git a/src/output/pulse_plugin.c b/src/output/pulse_plugin.c deleted file mode 100644 index ffc7abc8b..000000000 --- a/src/output/pulse_plugin.c +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 2003-2009 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "../output_api.h" -#include "mixer_list.h" - -#include <glib.h> -#include <pulse/simple.h> -#include <pulse/error.h> - -#define MPD_PULSE_NAME "mpd" - -struct pulse_data { - const char *name; - const char *server; - const char *sink; - - pa_simple *s; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -pulse_output_quark(void) -{ - return g_quark_from_static_string("pulse_output"); -} - -static struct pulse_data *pulse_new_data(void) -{ - struct pulse_data *ret; - - ret = g_new(struct pulse_data, 1); - - ret->server = NULL; - ret->sink = NULL; - - return ret; -} - -static void pulse_free_data(struct pulse_data *pd) -{ - g_free(pd); -} - -static void * -pulse_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, G_GNUC_UNUSED GError **error) -{ - struct pulse_data *pd; - - pd = pulse_new_data(); - pd->name = config_get_block_string(param, "name", "mpd_pulse"); - pd->server = config_get_block_string(param, "server", NULL); - pd->sink = config_get_block_string(param, "sink", NULL); - - return pd; -} - -static void pulse_finish(void *data) -{ - struct pulse_data *pd = data; - - pulse_free_data(pd); -} - -static bool pulse_test_default_device(void) -{ - pa_simple *s; - pa_sample_spec ss; - int error; - - ss.format = PA_SAMPLE_S16NE; - ss.rate = 44100; - ss.channels = 2; - - s = pa_simple_new(NULL, MPD_PULSE_NAME, PA_STREAM_PLAYBACK, NULL, - MPD_PULSE_NAME, &ss, NULL, NULL, &error); - if (!s) { - g_message("Cannot connect to default PulseAudio server: %s\n", - pa_strerror(error)); - return false; - } - - pa_simple_free(s); - - return true; -} - -static bool -pulse_open(void *data, struct audio_format *audio_format, GError **error_r) -{ - struct pulse_data *pd = data; - pa_sample_spec ss; - int error; - - /* MPD doesn't support the other pulseaudio sample formats, so - we just force MPD to send us everything as 16 bit */ - audio_format->bits = 16; - - ss.format = PA_SAMPLE_S16NE; - ss.rate = audio_format->sample_rate; - ss.channels = audio_format->channels; - - pd->s = pa_simple_new(pd->server, MPD_PULSE_NAME, PA_STREAM_PLAYBACK, - pd->sink, pd->name, - &ss, NULL, NULL, - &error); - if (!pd->s) { - g_set_error(error_r, pulse_output_quark(), error, - "Cannot connect to PulseAudio server: %s", - pa_strerror(error)); - return false; - } - - return true; -} - -static void pulse_cancel(void *data) -{ - struct pulse_data *pd = data; - int error; - - if (pa_simple_flush(pd->s, &error) < 0) - g_warning("Flush failed in PulseAudio output \"%s\": %s\n", - pd->name, pa_strerror(error)); -} - -static void pulse_close(void *data) -{ - struct pulse_data *pd = data; - - pa_simple_drain(pd->s, NULL); - pa_simple_free(pd->s); -} - -static size_t -pulse_play(void *data, const void *chunk, size_t size, GError **error_r) -{ - struct pulse_data *pd = data; - int error; - - if (pa_simple_write(pd->s, chunk, size, &error) < 0) { - g_set_error(error_r, pulse_output_quark(), error, - "%s", pa_strerror(error)); - return 0; - } - - return size; -} - -const struct audio_output_plugin pulse_plugin = { - .name = "pulse", - .test_default_device = pulse_test_default_device, - .init = pulse_init, - .finish = pulse_finish, - .open = pulse_open, - .play = pulse_play, - .cancel = pulse_cancel, - .close = pulse_close, - .mixer_plugin = &pulse_mixer, -}; diff --git a/src/output/recorder_output_plugin.c b/src/output/recorder_output_plugin.c new file mode 100644 index 000000000..c01d927c4 --- /dev/null +++ b/src/output/recorder_output_plugin.c @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "output_api.h" +#include "encoder_plugin.h" +#include "encoder_list.h" +#include "fd_util.h" +#include "open.h" + +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "recorder" + +struct recorder_output { + /** + * The configured encoder plugin. + */ + struct encoder *encoder; + + /** + * The destination file name. + */ + const char *path; + + /** + * The destination file descriptor. + */ + int fd; + + /** + * The buffer for encoder_read(). + */ + char buffer[32768]; +}; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +recorder_output_quark(void) +{ + return g_quark_from_static_string("recorder_output"); +} + +static void * +recorder_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, + const struct config_param *param, GError **error_r) +{ + struct recorder_output *recorder = g_new(struct recorder_output, 1); + const char *encoder_name; + const struct encoder_plugin *encoder_plugin; + + /* read configuration */ + + encoder_name = config_get_block_string(param, "encoder", "vorbis"); + encoder_plugin = encoder_plugin_get(encoder_name); + if (encoder_plugin == NULL) { + g_set_error(error_r, recorder_output_quark(), 0, + "No such encoder: %s", encoder_name); + return NULL; + } + + recorder->path = config_get_block_string(param, "path", NULL); + if (recorder->path == NULL) { + g_set_error(error_r, recorder_output_quark(), 0, + "'path' not configured"); + return NULL; + } + + /* initialize encoder */ + + recorder->encoder = encoder_init(encoder_plugin, param, error_r); + if (recorder->encoder == NULL) + return NULL; + + return recorder; +} + +static void +recorder_output_finish(void *data) +{ + struct recorder_output *recorder = data; + + encoder_finish(recorder->encoder); + g_free(recorder); +} + +/** + * Writes pending data from the encoder to the output file. + */ +static bool +recorder_output_encoder_to_file(struct recorder_output *recorder, + GError **error_r) +{ + size_t size = 0, position, nbytes; + + assert(recorder->fd >= 0); + + /* read from the encoder */ + + size = encoder_read(recorder->encoder, recorder->buffer, + sizeof(recorder->buffer)); + if (size == 0) + return true; + + /* write everything into the file */ + + position = 0; + while (true) { + nbytes = write(recorder->fd, recorder->buffer + position, + size - position); + if (nbytes > 0) { + position += (size_t)nbytes; + if (position >= size) + return true; + } else if (nbytes == 0) { + /* shouldn't happen for files */ + g_set_error(error_r, recorder_output_quark(), 0, + "write() returned 0"); + return false; + } else if (errno != EINTR) { + g_set_error(error_r, recorder_output_quark(), 0, + "Failed to write to '%s': %s", + recorder->path, g_strerror(errno)); + return false; + } + } +} + +static bool +recorder_output_open(void *data, struct audio_format *audio_format, + GError **error_r) +{ + struct recorder_output *recorder = data; + bool success; + + /* create the output file */ + + recorder->fd = open_cloexec(recorder->path, + O_CREAT|O_WRONLY|O_TRUNC|O_BINARY, + 0666); + if (recorder->fd < 0) { + g_set_error(error_r, recorder_output_quark(), 0, + "Failed to create '%s': %s", + recorder->path, g_strerror(errno)); + return false; + } + + /* open the encoder */ + + success = encoder_open(recorder->encoder, audio_format, error_r); + if (!success) { + close(recorder->fd); + unlink(recorder->path); + return false; + } + + return true; +} + +static void +recorder_output_close(void *data) +{ + struct recorder_output *recorder = data; + + /* flush the encoder and write the rest to the file */ + + if (encoder_flush(recorder->encoder, NULL)) + recorder_output_encoder_to_file(recorder, NULL); + + /* now really close everything */ + + encoder_close(recorder->encoder); + + close(recorder->fd); +} + +static size_t +recorder_output_play(void *data, const void *chunk, size_t size, + GError **error_r) +{ + struct recorder_output *recorder = data; + + return encoder_write(recorder->encoder, chunk, size, error_r) && + recorder_output_encoder_to_file(recorder, error_r) + ? size : 0; +} + +const struct audio_output_plugin recorder_output_plugin = { + .name = "recorder", + .init = recorder_output_init, + .finish = recorder_output_finish, + .open = recorder_output_open, + .close = recorder_output_close, + .play = recorder_output_play, +}; diff --git a/src/output/shout_plugin.c b/src/output/shout_plugin.c index dbc56f337..baaeccf92 100644 --- a/src/output/shout_plugin.c +++ b/src/output/shout_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,9 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "output_api.h" #include "encoder_plugin.h" #include "encoder_list.h" +#include "mpd_error.h" #include <shout/shout.h> #include <glib.h> @@ -100,8 +102,8 @@ static void free_shout_data(struct shout_data *sd) #define check_block_param(name) { \ block_param = config_get_block_param(param, name); \ if (!block_param) { \ - g_error("no \"%s\" defined for shout device defined at line " \ - "%i\n", name, param->line); \ + MPD_ERROR("no \"%s\" defined for shout device defined at line " \ + "%i\n", name, param->line); \ } \ } @@ -126,6 +128,13 @@ my_shout_init_driver(const struct audio_format *audio_format, struct block_param *block_param; int public; + if (audio_format == NULL || + !audio_format_fully_defined(audio_format)) { + g_set_error(error, shout_output_quark(), 0, + "Need full audio format specification"); + return NULL; + } + sd = new_shout_data(); if (shout_init_count == 0) @@ -191,8 +200,6 @@ my_shout_init_driver(const struct audio_format *audio_format, } } - check_block_param("format"); - encoding = config_get_block_string(param, "encoding", "ogg"); encoder_plugin = shout_encoder_plugin_get(encoding); if (encoder_plugin == NULL) { @@ -335,7 +342,6 @@ write_page(struct shout_data *sd, GError **error) if (sd->buf.len == 0) return true; - shout_sync(sd->shout_conn); err = shout_send(sd->shout_conn, sd->buf.data, sd->buf.len); if (!handle_shout_error(sd, err, error)) return false; @@ -434,6 +440,18 @@ my_shout_open_device(void *data, struct audio_format *audio_format, return true; } +static unsigned +my_shout_delay(void *data) +{ + struct shout_data *sd = (struct shout_data *)data; + + int delay = shout_delay(sd->shout_conn); + if (delay < 0) + delay = 0; + + return delay; +} + static size_t my_shout_play(void *data, const void *chunk, size_t size, GError **error) { @@ -448,15 +466,8 @@ my_shout_play(void *data, const void *chunk, size_t size, GError **error) static bool my_shout_pause(void *data) { - struct shout_data *sd = (struct shout_data *)data; static const char silence[1020]; - if (shout_delay(sd->shout_conn) > 500) { - /* cap the latency for unpause */ - g_usleep(500000); - return true; - } - return my_shout_play(data, silence, sizeof(silence), NULL); } @@ -471,10 +482,10 @@ shout_tag_to_metadata(const struct tag *tag, char *dest, size_t size) for (unsigned i = 0; i < tag->num_items; i++) { switch (tag->items[i]->type) { - case TAG_ITEM_ARTIST: + case TAG_ARTIST: strncpy(artist, tag->items[i]->value, size); break; - case TAG_ITEM_TITLE: + case TAG_TITLE: strncpy(title, tag->items[i]->value, size); break; @@ -533,6 +544,7 @@ const struct audio_output_plugin shoutPlugin = { .init = my_shout_init_driver, .finish = my_shout_finish_driver, .open = my_shout_open_device, + .delay = my_shout_delay, .play = my_shout_play, .pause = my_shout_pause, .cancel = my_shout_drop_buffered_audio, diff --git a/src/output/solaris_output_plugin.c b/src/output/solaris_output_plugin.c index 5febf0afc..deb3298a5 100644 --- a/src/output/solaris_output_plugin.c +++ b/src/output/solaris_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,7 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "output_api.h" +#include "fd_util.h" #include <glib.h> @@ -87,11 +89,11 @@ solaris_output_open(void *data, struct audio_format *audio_format, /* support only 16 bit mono/stereo for now; nothing else has been tested */ - audio_format->bits = 16; + audio_format->format = SAMPLE_FORMAT_S16; /* open the device in non-blocking mode */ - so->fd = open(so->device, O_WRONLY|O_NONBLOCK); + so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK); if (so->fd < 0) { g_set_error(error, solaris_output_quark(), errno, "Failed to open %s: %s", @@ -117,7 +119,7 @@ solaris_output_open(void *data, struct audio_format *audio_format, info.play.sample_rate = audio_format->sample_rate; info.play.channels = audio_format->channels; - info.play.precision = audio_format->bits; + info.play.precision = 16; info.play.encoding = AUDIO_ENCODING_LINEAR; ret = ioctl(so->fd, AUDIO_SETINFO, &info); diff --git a/src/output/winmm_output_plugin.c b/src/output/winmm_output_plugin.c new file mode 100644 index 000000000..b9687874d --- /dev/null +++ b/src/output/winmm_output_plugin.c @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "output_api.h" +#include "pcm_buffer.h" +#include "mixer_list.h" +#include "winmm_output_plugin.h" + +#include <stdlib.h> +#include <string.h> +#include <windows.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "winmm_output" + +struct winmm_buffer { + struct pcm_buffer buffer; + + WAVEHDR hdr; +}; + +struct winmm_output { + UINT device_id; + HWAVEOUT handle; + + /** + * This event is triggered by Windows when a buffer is + * finished. + */ + HANDLE event; + + struct winmm_buffer buffers[8]; + unsigned next_buffer; +}; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +winmm_output_quark(void) +{ + return g_quark_from_static_string("winmm_output"); +} + +HWAVEOUT +winmm_output_get_handle(struct winmm_output* output) +{ + return output->handle; +} + +static bool +winmm_output_test_default_device(void) +{ + return waveOutGetNumDevs() > 0; +} + +static UINT +get_device_id(const char *device_name) +{ + /* if device is not specified use wave mapper */ + if (device_name == NULL) + return WAVE_MAPPER; + + /* check for device id */ + char *endptr; + UINT id = strtoul(device_name, &endptr, 0); + if (endptr > device_name && *endptr == 0) + return id; + + /* check for device name */ + for (UINT i = 0; i < waveOutGetNumDevs(); i++) { + WAVEOUTCAPS caps; + MMRESULT result = waveOutGetDevCaps(i, &caps, sizeof(caps)); + if (result != MMSYSERR_NOERROR) + continue; + /* szPname is only 32 chars long, so it is often truncated. + Use partial match to work around this. */ + if (strstr(device_name, caps.szPname) == device_name) + return i; + } + + /* fallback to wave mapper */ + return WAVE_MAPPER; +} + +static void * +winmm_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, + G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error) +{ + struct winmm_output *wo = g_new(struct winmm_output, 1); + const char *device = config_get_block_string(param, "device", NULL); + wo->device_id = get_device_id(device); + return wo; +} + +static void +winmm_output_finish(void *data) +{ + struct winmm_output *wo = data; + + g_free(wo); +} + +static bool +winmm_output_open(void *data, struct audio_format *audio_format, + GError **error_r) +{ + struct winmm_output *wo = data; + + wo->event = CreateEvent(NULL, false, false, NULL); + if (wo->event == NULL) { + g_set_error(error_r, winmm_output_quark(), 0, + "CreateEvent() failed"); + return false; + } + + switch (audio_format->format) { + case SAMPLE_FORMAT_S8: + case SAMPLE_FORMAT_S16: + break; + + case SAMPLE_FORMAT_S24: + case SAMPLE_FORMAT_S24_P32: + case SAMPLE_FORMAT_S32: + case SAMPLE_FORMAT_UNDEFINED: + /* we havn't tested formats other than S16 */ + audio_format->format = SAMPLE_FORMAT_S16; + break; + } + + if (audio_format->channels > 2) + /* same here: more than stereo was not tested */ + audio_format->channels = 2; + + WAVEFORMATEX format; + format.wFormatTag = WAVE_FORMAT_PCM; + format.nChannels = audio_format->channels; + format.nSamplesPerSec = audio_format->sample_rate; + format.nBlockAlign = audio_format_frame_size(audio_format); + format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; + format.wBitsPerSample = audio_format_sample_size(audio_format) * 8; + format.cbSize = 0; + + MMRESULT result = waveOutOpen(&wo->handle, wo->device_id, &format, + (DWORD_PTR)wo->event, 0, CALLBACK_EVENT); + if (result != MMSYSERR_NOERROR) { + CloseHandle(wo->event); + g_set_error(error_r, winmm_output_quark(), result, + "waveOutOpen() failed"); + return false; + } + + for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) { + pcm_buffer_init(&wo->buffers[i].buffer); + memset(&wo->buffers[i].hdr, 0, sizeof(wo->buffers[i].hdr)); + } + + wo->next_buffer = 0; + + return true; +} + +static void +winmm_output_close(void *data) +{ + struct winmm_output *wo = data; + + for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) + pcm_buffer_deinit(&wo->buffers[i].buffer); + + waveOutClose(wo->handle); + + CloseHandle(wo->event); +} + +/** + * Copy data into a buffer, and prepare the wave header. + */ +static bool +winmm_set_buffer(struct winmm_output *wo, struct winmm_buffer *buffer, + const void *data, size_t size, + GError **error_r) +{ + void *dest = pcm_buffer_get(&buffer->buffer, size); + if (dest == NULL) { + g_set_error(error_r, winmm_output_quark(), 0, + "Out of memory"); + return false; + } + + memcpy(dest, data, size); + + memset(&buffer->hdr, 0, sizeof(buffer->hdr)); + buffer->hdr.lpData = dest; + buffer->hdr.dwBufferLength = size; + + MMRESULT result = waveOutPrepareHeader(wo->handle, &buffer->hdr, + sizeof(buffer->hdr)); + if (result != MMSYSERR_NOERROR) { + g_set_error(error_r, winmm_output_quark(), result, + "waveOutPrepareHeader() failed"); + return false; + } + + return true; +} + +/** + * Wait until the buffer is finished. + */ +static bool +winmm_drain_buffer(struct winmm_output *wo, struct winmm_buffer *buffer, + GError **error_r) +{ + if ((buffer->hdr.dwFlags & WHDR_DONE) == WHDR_DONE) + /* already finished */ + return true; + + while (true) { + MMRESULT result = waveOutUnprepareHeader(wo->handle, + &buffer->hdr, + sizeof(buffer->hdr)); + if (result == MMSYSERR_NOERROR) + return true; + else if (result != WAVERR_STILLPLAYING) { + g_set_error(error_r, winmm_output_quark(), result, + "waveOutUnprepareHeader() failed"); + return false; + } + + /* wait some more */ + WaitForSingleObject(wo->event, INFINITE); + } +} + +static size_t +winmm_output_play(void *data, const void *chunk, size_t size, GError **error_r) +{ + struct winmm_output *wo = data; + + /* get the next buffer from the ring and prepare it */ + struct winmm_buffer *buffer = &wo->buffers[wo->next_buffer]; + if (!winmm_drain_buffer(wo, buffer, error_r) || + !winmm_set_buffer(wo, buffer, chunk, size, error_r)) + return 0; + + /* enqueue the buffer */ + MMRESULT result = waveOutWrite(wo->handle, &buffer->hdr, + sizeof(buffer->hdr)); + if (result != MMSYSERR_NOERROR) { + waveOutUnprepareHeader(wo->handle, &buffer->hdr, + sizeof(buffer->hdr)); + g_set_error(error_r, winmm_output_quark(), result, + "waveOutWrite() failed"); + return 0; + } + + /* mark our buffer as "used" */ + wo->next_buffer = (wo->next_buffer + 1) % + G_N_ELEMENTS(wo->buffers); + + return size; +} + +static bool +winmm_drain_all_buffers(struct winmm_output *wo, GError **error_r) +{ + for (unsigned i = wo->next_buffer; i < G_N_ELEMENTS(wo->buffers); ++i) + if (!winmm_drain_buffer(wo, &wo->buffers[i], error_r)) + return false; + + for (unsigned i = 0; i < wo->next_buffer; ++i) + if (!winmm_drain_buffer(wo, &wo->buffers[i], error_r)) + return false; + + return true; +} + +static void +winmm_stop(struct winmm_output *wo) +{ + waveOutReset(wo->handle); + + for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) { + struct winmm_buffer *buffer = &wo->buffers[i]; + waveOutUnprepareHeader(wo->handle, &buffer->hdr, + sizeof(buffer->hdr)); + } +} + +static void +winmm_output_drain(void *data) +{ + struct winmm_output *wo = data; + + if (!winmm_drain_all_buffers(wo, NULL)) + winmm_stop(wo); +} + +static void +winmm_output_cancel(void *data) +{ + struct winmm_output *wo = data; + + winmm_stop(wo); +} + +const struct audio_output_plugin winmm_output_plugin = { + .name = "winmm", + .test_default_device = winmm_output_test_default_device, + .init = winmm_output_init, + .finish = winmm_output_finish, + .open = winmm_output_open, + .close = winmm_output_close, + .play = winmm_output_play, + .drain = winmm_output_drain, + .cancel = winmm_output_cancel, + .mixer_plugin = &winmm_mixer_plugin, +}; diff --git a/src/output/winmm_output_plugin.h b/src/output/winmm_output_plugin.h new file mode 100644 index 000000000..39507275a --- /dev/null +++ b/src/output/winmm_output_plugin.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2003-2010 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_WINMM_OUTPUT_PLUGIN_H +#define MPD_WINMM_OUTPUT_PLUGIN_H + +#include <windows.h> + +struct winmm_output; + +HWAVEOUT winmm_output_get_handle(struct winmm_output*); + +#endif diff --git a/src/output_all.c b/src/output_all.c index 86e78e13e..19c0f0166 100644 --- a/src/output_all.c +++ b/src/output_all.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "output_all.h" #include "output_internal.h" #include "output_control.h" @@ -25,6 +26,7 @@ #include "pipe.h" #include "buffer.h" #include "player_control.h" +#include "mpd_error.h" #ifndef NDEBUG #include "chunk.h" @@ -52,6 +54,11 @@ static struct music_buffer *g_music_buffer; */ static struct music_pipe *g_mp; +/** + * The "elapsed_time" stamp of the most recently finished chunk. + */ +static float audio_output_all_elapsed_time = -1.0; + unsigned int audio_output_count(void) { return num_audio_outputs; @@ -116,17 +123,17 @@ audio_output_all_init(void) if (!audio_output_init(output, param, &error)) { if (param != NULL) - g_error("line %i: %s", - param->line, error->message); + MPD_ERROR("line %i: %s", + param->line, error->message); else - g_error("%s", error->message); + MPD_ERROR("%s", error->message); } /* require output names to be unique: */ for (j = 0; j < i; j++) { if (!strcmp(output->name, audio_outputs[j].name)) { - g_error("output devices with identical " - "names: %s\n", output->name); + MPD_ERROR("output devices with identical " + "names: %s\n", output->name); } } } @@ -138,6 +145,7 @@ audio_output_all_finish(void) unsigned int i; for (i = 0; i < num_audio_outputs; i++) { + audio_output_disable(&audio_outputs[i]); audio_output_finish(&audio_outputs[i]); } @@ -148,6 +156,25 @@ audio_output_all_finish(void) notify_deinit(&audio_output_client_notify); } +void +audio_output_all_enable_disable(void) +{ + for (unsigned i = 0; i < num_audio_outputs; i++) { + struct audio_output *ao = &audio_outputs[i]; + bool enabled; + + g_mutex_lock(ao->mutex); + enabled = ao->really_enabled; + g_mutex_unlock(ao->mutex); + + if (ao->enabled != enabled) { + if (ao->enabled) + audio_output_enable(ao); + else + audio_output_disable(ao); + } + } +} /** * Determine if all (active) outputs have finished the current @@ -156,10 +183,18 @@ audio_output_all_finish(void) static bool audio_output_all_finished(void) { - for (unsigned i = 0; i < num_audio_outputs; ++i) - if (audio_output_is_open(&audio_outputs[i]) && - !audio_output_command_is_finished(&audio_outputs[i])) + for (unsigned i = 0; i < num_audio_outputs; ++i) { + struct audio_output *ao = &audio_outputs[i]; + bool not_finished; + + g_mutex_lock(ao->mutex); + not_finished = audio_output_is_open(ao) && + !audio_output_command_is_finished(ao); + g_mutex_unlock(ao->mutex); + + if (not_finished) return false; + } return true; } @@ -170,6 +205,29 @@ static void audio_output_wait_all(void) notify_wait(&audio_output_client_notify); } +/** + * Signals the audio output if it is open. This function locks the + * mutex. + */ +static void +audio_output_lock_signal(struct audio_output *ao) +{ + g_mutex_lock(ao->mutex); + if (audio_output_is_open(ao)) + g_cond_signal(ao->cond); + g_mutex_unlock(ao->mutex); +} + +/** + * Signals all audio outputs which are open. + */ +static void +audio_output_signal_all(void) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) + audio_output_lock_signal(&audio_outputs[i]); +} + static void audio_output_reset_reopen(struct audio_output *ao) { @@ -237,8 +295,7 @@ audio_output_all_play(struct music_chunk *chunk) music_pipe_push(g_mp, chunk); for (i = 0; i < num_audio_outputs; ++i) - if (audio_output_is_open(&audio_outputs[i])) - audio_output_play(&audio_outputs[i]); + audio_output_play(&audio_outputs[i]); return true; } @@ -273,6 +330,7 @@ audio_output_all_open(const struct audio_format *audio_format, input_audio_format = *audio_format; audio_output_all_reset_reopen(); + audio_output_all_enable_disable(); audio_output_all_update(); for (i = 0; i < num_audio_outputs; ++i) { @@ -385,6 +443,11 @@ audio_output_all_check(void) this chunk */ return music_pipe_size(g_mp); + if (chunk->length > 0 && chunk->times >= 0.0) + /* only update elapsed_time if the chunk + provides a defined value */ + audio_output_all_elapsed_time = chunk->times; + is_tail = chunk->next == NULL; if (is_tail) /* this is the tail of the pipe - clear the @@ -412,10 +475,15 @@ audio_output_all_check(void) bool audio_output_all_wait(unsigned threshold) { - if (audio_output_all_check() < threshold) + player_lock(); + + if (audio_output_all_check() < threshold) { + player_unlock(); return true; + } - notify_wait(&pc.notify); + player_wait(); + player_unlock(); return audio_output_all_check() < threshold; } @@ -428,8 +496,16 @@ audio_output_all_pause(void) audio_output_all_update(); for (i = 0; i < num_audio_outputs; ++i) - if (audio_output_is_open(&audio_outputs[i])) - audio_output_pause(&audio_outputs[i]); + audio_output_pause(&audio_outputs[i]); + + audio_output_wait_all(); +} + +void +audio_output_all_drain(void) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) + audio_output_drain_async(&audio_outputs[i]); audio_output_wait_all(); } @@ -441,10 +517,8 @@ audio_output_all_cancel(void) /* send the cancel() command to all audio outputs */ - for (i = 0; i < num_audio_outputs; ++i) { - if (audio_output_is_open(&audio_outputs[i])) - audio_output_cancel(&audio_outputs[i]); - } + for (i = 0; i < num_audio_outputs; ++i) + audio_output_cancel(&audio_outputs[i]); audio_output_wait_all(); @@ -452,6 +526,15 @@ audio_output_all_cancel(void) if (g_mp != NULL) music_pipe_clear(g_mp, g_music_buffer); + + /* the audio outputs are now waiting for a signal, to + synchronize the cleared music pipe */ + + audio_output_signal_all(); + + /* invalidate elapsed_time */ + + audio_output_all_elapsed_time = -1.0; } void @@ -473,4 +556,43 @@ audio_output_all_close(void) g_music_buffer = NULL; audio_format_clear(&input_audio_format); + + audio_output_all_elapsed_time = -1.0; +} + +void +audio_output_all_release(void) +{ + unsigned int i; + + for (i = 0; i < num_audio_outputs; ++i) + audio_output_release(&audio_outputs[i]); + + if (g_mp != NULL) { + assert(g_music_buffer != NULL); + + music_pipe_clear(g_mp, g_music_buffer); + music_pipe_free(g_mp); + g_mp = NULL; + } + + g_music_buffer = NULL; + + audio_format_clear(&input_audio_format); + + audio_output_all_elapsed_time = -1.0; +} + +void +audio_output_all_song_border(void) +{ + /* clear the elapsed_time pointer at the beginning of a new + song */ + audio_output_all_elapsed_time = 0.0; +} + +float +audio_output_all_get_elapsed_time(void) +{ + return audio_output_all_elapsed_time; } diff --git a/src/output_all.h b/src/output_all.h index 2a09514b2..a579bf5f1 100644 --- a/src/output_all.h +++ b/src/output_all.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -66,6 +66,13 @@ struct audio_output * audio_output_find(const char *name); /** + * Checks the "enabled" flag of all audio outputs, and if one has + * changed, commit the change. + */ +void +audio_output_all_enable_disable(void); + +/** * Opens all audio outputs which are not disabled. * * @param audio_format the preferred audio format, or NULL to reuse @@ -85,6 +92,13 @@ void audio_output_all_close(void); /** + * Closes all audio outputs. Outputs with the "always_on" flag are + * put into pause mode. + */ +void +audio_output_all_release(void); + +/** * Enqueue a #music_chunk object for playing, i.e. pushes it to a * #music_pipe. * @@ -123,9 +137,29 @@ void audio_output_all_pause(void); /** + * Drain all audio outputs. + */ +void +audio_output_all_drain(void); + +/** * Try to cancel data which may still be in the device's buffers. */ void audio_output_all_cancel(void); +/** + * Indicate that a new song will begin now. + */ +void +audio_output_all_song_border(void); + +/** + * Returns the "elapsed_time" stamp of the most recently finished + * chunk. A negative value is returned when no chunk has been + * finished yet. + */ +float +audio_output_all_get_elapsed_time(void); + #endif diff --git a/src/output_api.h b/src/output_api.h index c31893bc6..8e002dd48 100644 --- a/src/output_api.h +++ b/src/output_api.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output_command.c b/src/output_command.c index 5da176dde..825884e8e 100644 --- a/src/output_command.c +++ b/src/output_command.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,13 +24,17 @@ * */ +#include "config.h" #include "output_command.h" #include "output_all.h" #include "output_internal.h" #include "output_plugin.h" #include "mixer_control.h" +#include "player_control.h" #include "idle.h" +extern unsigned audio_output_state_version; + bool audio_output_enable_index(unsigned idx) { @@ -40,10 +44,16 @@ audio_output_enable_index(unsigned idx) return false; ao = audio_output_get(idx); + if (ao->enabled) + return true; ao->enabled = true; idle_add(IDLE_OUTPUT); + pc_update_audio(); + + ++audio_output_state_version; + return true; } @@ -57,6 +67,8 @@ audio_output_disable_index(unsigned idx) return false; ao = audio_output_get(idx); + if (!ao->enabled) + return true; ao->enabled = false; idle_add(IDLE_OUTPUT); @@ -67,5 +79,9 @@ audio_output_disable_index(unsigned idx) idle_add(IDLE_MIXER); } + pc_update_audio(); + + ++audio_output_state_version; + return true; } diff --git a/src/output_command.h b/src/output_command.h index d92ff5ec8..fab015c3f 100644 --- a/src/output_command.h +++ b/src/output_command.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output_control.c b/src/output_control.c index ecd226821..161404f78 100644 --- a/src/output_control.c +++ b/src/output_control.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,12 +17,15 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "output_control.h" #include "output_api.h" #include "output_internal.h" #include "output_thread.h" #include "mixer_control.h" #include "mixer_plugin.h" +#include "filter_plugin.h" +#include "notify.h" #include <assert.h> #include <stdlib.h> @@ -38,8 +41,9 @@ struct notify audio_output_client_notify; static void ao_command_wait(struct audio_output *ao) { while (ao->command != AO_COMMAND_NONE) { - notify_signal(&ao->notify); + g_mutex_unlock(ao->mutex); notify_wait(&audio_output_client_notify); + g_mutex_lock(ao->mutex); } } @@ -47,29 +51,55 @@ static void ao_command(struct audio_output *ao, enum audio_output_command cmd) { assert(ao->command == AO_COMMAND_NONE); ao->command = cmd; + g_cond_signal(ao->cond); ao_command_wait(ao); } -/** - * Like ao_command(), but assumes the object is locked by the caller. - */ -static void -ao_command_locked(struct audio_output *ao, enum audio_output_command cmd) +static void ao_command_async(struct audio_output *ao, + enum audio_output_command cmd) { assert(ao->command == AO_COMMAND_NONE); ao->command = cmd; + g_cond_signal(ao->cond); +} + +void +audio_output_enable(struct audio_output *ao) +{ + if (ao->thread == NULL) { + if (ao->plugin->enable == NULL) { + /* don't bother to start the thread now if the + device doesn't even have a enable() method; + just assign the variable and we're done */ + ao->really_enabled = true; + return; + } + + audio_output_thread_start(ao); + } - g_mutex_unlock(ao->mutex); - ao_command_wait(ao); g_mutex_lock(ao->mutex); + ao_command(ao, AO_COMMAND_ENABLE); + g_mutex_unlock(ao->mutex); } -static void ao_command_async(struct audio_output *ao, - enum audio_output_command cmd) +void +audio_output_disable(struct audio_output *ao) { - assert(ao->command == AO_COMMAND_NONE); - ao->command = cmd; - notify_signal(&ao->notify); + if (ao->thread == NULL) { + if (ao->plugin->disable == NULL) + ao->really_enabled = false; + else + /* if there's no thread yet, the device cannot + be enabled */ + assert(!ao->really_enabled); + + return; + } + + g_mutex_lock(ao->mutex); + ao_command(ao, AO_COMMAND_DISABLE); + g_mutex_unlock(ao->mutex); } static void @@ -94,9 +124,13 @@ audio_output_open(struct audio_output *ao, if (ao->open && audio_format_equals(audio_format, &ao->in_audio_format)) { - assert(ao->pipe == mp); + assert(ao->pipe == mp || + (ao->always_on && ao->pause)); if (ao->pause) { + ao->chunk = NULL; + ao->pipe = mp; + /* unpause with the CANCEL command; this is a hack, but suits well for forcing the thread to leave the ao_pause() thread, and we need @@ -104,7 +138,11 @@ audio_output_open(struct audio_output *ao, /* we're not using audio_output_cancel() here, because that function is asynchronous */ - ao_command_locked(ao, AO_COMMAND_CANCEL); + ao_command(ao, AO_COMMAND_CANCEL); + + /* the audio output is now waiting for a + signal; wake it up immediately */ + g_cond_signal(ao->cond); } return true; @@ -113,33 +151,49 @@ 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_locked(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_locked(ao, AO_COMMAND_OPEN); - open = ao->open; - } - if (open && ao->mixer != NULL) - mixer_open(ao->mixer); + if (open && ao->mixer != NULL) { + GError *error = NULL; + + if (!mixer_open(ao->mixer, &error)) { + g_warning("Failed to open mixer for '%s': %s", + ao->name, error->message); + g_error_free(error); + } + } return open; } +/** + * Same as audio_output_close(), but expects the lock to be held by + * the caller. + */ +static void +audio_output_close_locked(struct audio_output *ao) +{ + assert(ao != NULL); + + if (ao->mixer != NULL) + mixer_auto_close(ao->mixer); + + assert(!ao->open || ao->fail_timer == NULL); + + if (ao->open) + ao_command(ao, AO_COMMAND_CLOSE); + else if (ao->fail_timer != NULL) { + g_timer_destroy(ao->fail_timer); + ao->fail_timer = NULL; + } +} + bool audio_output_update(struct audio_output *ao, const struct audio_format *audio_format, @@ -149,28 +203,29 @@ audio_output_update(struct audio_output *ao, g_mutex_lock(ao->mutex); - if (ao->enabled) { + if (ao->enabled && ao->really_enabled) { if (ao->fail_timer == NULL || g_timer_elapsed(ao->fail_timer, NULL) > REOPEN_AFTER) { - bool ret = audio_output_open(ao, audio_format, mp); + bool success = audio_output_open(ao, audio_format, mp); g_mutex_unlock(ao->mutex); - return ret; + return success; } } else if (audio_output_is_open(ao)) audio_output_close_locked(ao); g_mutex_unlock(ao->mutex); - return false; } void audio_output_play(struct audio_output *ao) { - if (!ao->open) - return; + g_mutex_lock(ao->mutex); - notify_signal(&ao->notify); + if (audio_output_is_open(ao)) + g_cond_signal(ao->cond); + + g_mutex_unlock(ao->mutex); } void audio_output_pause(struct audio_output *ao) @@ -181,29 +236,36 @@ void audio_output_pause(struct audio_output *ao) mixer_auto_close()) */ mixer_auto_close(ao->mixer); - ao_command_async(ao, AO_COMMAND_PAUSE); + g_mutex_lock(ao->mutex); + if (audio_output_is_open(ao)) + ao_command_async(ao, AO_COMMAND_PAUSE); + g_mutex_unlock(ao->mutex); } -void audio_output_cancel(struct audio_output *ao) +void +audio_output_drain_async(struct audio_output *ao) { - ao_command_async(ao, AO_COMMAND_CANCEL); + g_mutex_lock(ao->mutex); + if (audio_output_is_open(ao)) + ao_command_async(ao, AO_COMMAND_DRAIN); + g_mutex_unlock(ao->mutex); } -static void -audio_output_close_locked(struct audio_output *ao) +void audio_output_cancel(struct audio_output *ao) { - assert(ao != NULL); - assert(!ao->open || ao->fail_timer == NULL); - - if (ao->mixer != NULL) - mixer_auto_close(ao->mixer); + g_mutex_lock(ao->mutex); + if (audio_output_is_open(ao)) + ao_command_async(ao, AO_COMMAND_CANCEL); + g_mutex_unlock(ao->mutex); +} - if (ao->open) - ao_command_locked(ao, AO_COMMAND_CLOSE); - else if (ao->fail_timer != NULL) { - g_timer_destroy(ao->fail_timer); - ao->fail_timer = NULL; - } +void +audio_output_release(struct audio_output *ao) +{ + if (ao->always_on) + audio_output_pause(ao); + else + audio_output_close(ao); } void audio_output_close(struct audio_output *ao) @@ -223,7 +285,9 @@ void audio_output_finish(struct audio_output *ao) assert(ao->fail_timer == NULL); if (ao->thread != NULL) { + g_mutex_lock(ao->mutex); ao_command(ao, AO_COMMAND_KILL); + g_mutex_unlock(ao->mutex); g_thread_join(ao->thread); } @@ -232,6 +296,16 @@ void audio_output_finish(struct audio_output *ao) ao_plugin_finish(ao->plugin, ao->data); - notify_deinit(&ao->notify); + g_cond_free(ao->cond); g_mutex_free(ao->mutex); + + if (ao->replay_gain_filter != NULL) + filter_free(ao->replay_gain_filter); + + if (ao->other_replay_gain_filter != NULL) + filter_free(ao->other_replay_gain_filter); + + filter_free(ao->filter); + + pcm_buffer_deinit(&ao->cross_fade_buffer); } diff --git a/src/output_control.h b/src/output_control.h index ce3abe3f6..7f4f4a53c 100644 --- a/src/output_control.h +++ b/src/output_control.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -38,7 +38,19 @@ audio_output_quark(void) bool audio_output_init(struct audio_output *ao, const struct config_param *param, - GError **error); + GError **error_r); + +/** + * Enables the device. + */ +void +audio_output_enable(struct audio_output *ao); + +/** + * Disables the device. + */ +void +audio_output_disable(struct audio_output *ao); /** * Opens or closes the device, depending on the "enabled" flag. @@ -55,8 +67,20 @@ audio_output_play(struct audio_output *ao); void audio_output_pause(struct audio_output *ao); +void +audio_output_drain_async(struct audio_output *ao); + void audio_output_cancel(struct audio_output *ao); + void audio_output_close(struct audio_output *ao); + +/** + * Closes the audio output, but if the "always_on" flag is set, put it + * into pause mode instead. + */ +void +audio_output_release(struct audio_output *ao); + void audio_output_finish(struct audio_output *ao); #endif diff --git a/src/output_init.c b/src/output_init.c index 927424324..f4700dfb2 100644 --- a/src/output_init.c +++ b/src/output_init.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,21 +17,34 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "output_control.h" #include "output_api.h" #include "output_internal.h" #include "output_list.h" #include "audio_parser.h" #include "mixer_control.h" +#include "mixer_type.h" +#include "mixer_list.h" +#include "mixer/software_mixer_plugin.h" +#include "filter_plugin.h" +#include "filter_registry.h" +#include "filter_config.h" +#include "filter/chain_filter_plugin.h" +#include "filter/autoconvert_filter_plugin.h" +#include "filter/replay_gain_filter_plugin.h" #include <glib.h> +#include <assert.h> + #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "output" #define AUDIO_OUTPUT_TYPE "type" #define AUDIO_OUTPUT_NAME "name" #define AUDIO_OUTPUT_FORMAT "format" +#define AUDIO_FILTERS "filters" static const struct audio_output_plugin * audio_output_detect(GError **error) @@ -56,46 +69,109 @@ audio_output_detect(GError **error) return NULL; } +/** + * Determines the mixer type which should be used for the specified + * configuration block. + * + * This handles the deprecated options mixer_type (global) and + * mixer_enabled, if the mixer_type setting is not configured. + */ +static enum mixer_type +audio_output_mixer_type(const struct config_param *param) +{ + /* read the local "mixer_type" setting */ + const char *p = config_get_block_string(param, "mixer_type", NULL); + if (p != NULL) + return mixer_type_parse(p); + + /* try the local "mixer_enabled" setting next (deprecated) */ + if (!config_get_block_bool(param, "mixer_enabled", true)) + return MIXER_TYPE_NONE; + + /* fall back to the global "mixer_type" setting (also + deprecated) */ + return mixer_type_parse(config_get_string("mixer_type", "hardware")); +} + +static struct mixer * +audio_output_load_mixer(void *ao, const struct config_param *param, + const struct mixer_plugin *plugin, + struct filter *filter_chain, + GError **error_r) +{ + struct mixer *mixer; + + switch (audio_output_mixer_type(param)) { + case MIXER_TYPE_NONE: + case MIXER_TYPE_UNKNOWN: + return NULL; + + case MIXER_TYPE_HARDWARE: + if (plugin == NULL) + return NULL; + + return mixer_new(plugin, ao, param, error_r); + + case MIXER_TYPE_SOFTWARE: + mixer = mixer_new(&software_mixer_plugin, NULL, NULL, NULL); + assert(mixer != NULL); + + filter_chain_append(filter_chain, + software_mixer_get_filter(mixer)); + return mixer; + } + + assert(false); + return NULL; +} + bool audio_output_init(struct audio_output *ao, const struct config_param *param, - GError **error) + GError **error_r) { - const char *format; const struct audio_output_plugin *plugin = NULL; + GError *error = NULL; if (param) { - const char *type = NULL; + const char *p; - type = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL); - if (type == NULL) { - g_set_error(error, audio_output_quark(), 0, + p = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL); + if (p == NULL) { + g_set_error(error_r, audio_output_quark(), 0, "Missing \"type\" configuration"); return false; } - plugin = audio_output_plugin_get(type); + plugin = audio_output_plugin_get(p); if (plugin == NULL) { - g_set_error(error, audio_output_quark(), 0, - "No such audio output plugin: %s", - type); + g_set_error(error_r, audio_output_quark(), 0, + "No such audio output plugin: %s", p); return false; } ao->name = config_get_block_string(param, AUDIO_OUTPUT_NAME, NULL); if (ao->name == NULL) { - g_set_error(error, audio_output_quark(), 0, + g_set_error(error_r, audio_output_quark(), 0, "Missing \"name\" configuration"); return false; } - format = config_get_block_string(param, AUDIO_OUTPUT_FORMAT, + p = config_get_block_string(param, AUDIO_OUTPUT_FORMAT, NULL); + if (p != NULL) { + bool success = + audio_format_parse(&ao->config_audio_format, + p, true, error_r); + if (!success) + return false; + } else + audio_format_clear(&ao->config_audio_format); } else { g_warning("No \"%s\" defined in config file\n", CONF_AUDIO_OUTPUT); - plugin = audio_output_detect(error); + plugin = audio_output_detect(error_r); if (plugin == NULL) return false; @@ -103,44 +179,115 @@ audio_output_init(struct audio_output *ao, const struct config_param *param, plugin->name); ao->name = "default detected output"; - format = NULL; + + audio_format_clear(&ao->config_audio_format); } ao->plugin = plugin; + ao->always_on = config_get_block_bool(param, "always_on", false); ao->enabled = config_get_block_bool(param, "enabled", true); + ao->really_enabled = false; ao->open = false; ao->pause = false; ao->fail_timer = NULL; - pcm_convert_init(&ao->convert_state); + pcm_buffer_init(&ao->cross_fade_buffer); - ao->config_audio_format = format != NULL; - if (ao->config_audio_format) { - bool ret; + /* set up the filter chain */ - ret = audio_format_parse(&ao->out_audio_format, format, - error); - if (!ret) - return false; + ao->filter = filter_chain_new(); + assert(ao->filter != NULL); + + /* create the replay_gain filter */ + + const char *replay_gain_handler = + config_get_block_string(param, "replay_gain_handler", + "software"); + + if (strcmp(replay_gain_handler, "none") != 0) { + ao->replay_gain_filter = filter_new(&replay_gain_filter_plugin, + param, NULL); + assert(ao->replay_gain_filter != NULL); + + ao->replay_gain_serial = 0; + + ao->other_replay_gain_filter = filter_new(&replay_gain_filter_plugin, + param, NULL); + assert(ao->other_replay_gain_filter != NULL); + + ao->other_replay_gain_serial = 0; + } else { + ao->replay_gain_filter = NULL; + ao->other_replay_gain_filter = NULL; + } + + /* create the normalization filter (if configured) */ + + if (config_get_bool(CONF_VOLUME_NORMALIZATION, false)) { + struct filter *normalize_filter = + filter_new(&normalize_filter_plugin, NULL, NULL); + assert(normalize_filter != NULL); + + filter_chain_append(ao->filter, + autoconvert_filter_new(normalize_filter)); + } + + filter_chain_parse(ao->filter, + config_get_block_string(param, AUDIO_FILTERS, ""), + &error + ); + + // It's not really fatal - Part of the filter chain has been set up already + // and even an empty one will work (if only with unexpected behaviour) + if (error != NULL) { + g_warning("Failed to initialize filter chain for '%s': %s", + ao->name, error->message); + g_error_free(error); } ao->thread = NULL; - notify_init(&ao->notify); ao->command = AO_COMMAND_NONE; ao->mutex = g_mutex_new(); + ao->cond = g_cond_new(); ao->data = ao_plugin_init(plugin, - ao->config_audio_format - ? &ao->out_audio_format : NULL, - param, error); + &ao->config_audio_format, + param, error_r); if (ao->data == NULL) return false; - if (plugin->mixer_plugin != NULL && - config_get_block_bool(param, "mixer_enabled", true)) - ao->mixer = mixer_new(plugin->mixer_plugin, param); - else - ao->mixer = NULL; + ao->mixer = audio_output_load_mixer(ao->data, param, + plugin->mixer_plugin, + ao->filter, &error); + if (ao->mixer == NULL && error != NULL) { + g_warning("Failed to initialize hardware mixer for '%s': %s", + ao->name, error->message); + g_error_free(error); + } + + /* use the hardware mixer for replay gain? */ + + if (strcmp(replay_gain_handler, "mixer") == 0) { + if (ao->mixer != NULL) + replay_gain_filter_set_mixer(ao->replay_gain_filter, + ao->mixer, 100); + else + g_warning("No such mixer for output '%s'", ao->name); + } else if (strcmp(replay_gain_handler, "software") != 0 && + ao->replay_gain_filter != NULL) { + g_set_error(error_r, audio_output_quark(), 0, + "Invalid \"replay_gain_handler\" value"); + return false; + } + + /* 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 b863c9ecc..18d431352 100644 --- a/src/output_internal.h +++ b/src/output_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,16 +21,33 @@ #define MPD_OUTPUT_INTERNAL_H #include "audio_format.h" -#include "pcm_convert.h" -#include "notify.h" +#include "pcm_buffer.h" + +#include <glib.h> #include <time.h> enum audio_output_command { AO_COMMAND_NONE = 0, + AO_COMMAND_ENABLE, + AO_COMMAND_DISABLE, AO_COMMAND_OPEN, + + /** + * This command is invoked when the input audio format + * changes. + */ + AO_COMMAND_REOPEN, + AO_COMMAND_CLOSE, AO_COMMAND_PAUSE, + + /** + * Drains the internal (hardware) buffers of the device. This + * operation may take a while to complete. + */ + AO_COMMAND_DRAIN, + AO_COMMAND_CANCEL, AO_COMMAND_KILL }; @@ -60,10 +77,10 @@ struct audio_output { struct mixer *mixer; /** - * This flag is true, when the audio_format of this device is - * configured in mpd.conf. + * Shall this output always play something (i.e. silence), + * even when playback is stopped? */ - bool config_audio_format; + bool always_on; /** * Has the user enabled this device? @@ -71,6 +88,12 @@ struct audio_output { bool enabled; /** + * Is this device actually enabled, i.e. the "enable" method + * has succeeded? + */ + bool really_enabled; + + /** * Is the device (already) open and functional? * * This attribute may only be modified by the output thread. @@ -94,6 +117,11 @@ struct audio_output { GTimer *fail_timer; /** + * The configured audio format. + */ + struct audio_format config_audio_format; + + /** * The audio_format in which audio data is received from the * player thread (which in turn receives it from the decoder). */ @@ -107,18 +135,55 @@ struct audio_output { */ struct audio_format out_audio_format; - struct pcm_convert_state convert_state; + /** + * The buffer used to allocate the cross-fading result. + */ + struct pcm_buffer cross_fade_buffer; /** - * The thread handle, or NULL if the output thread isn't - * running. + * The filter object of this audio output. This is an + * instance of chain_filter_plugin. */ - GThread *thread; + struct filter *filter; /** - * Notify object for the thread. + * The replay_gain_filter_plugin instance of this audio + * output. */ - struct notify notify; + struct filter *replay_gain_filter; + + /** + * The serial number of the last replay gain info. 0 means no + * replay gain info was available. + */ + unsigned replay_gain_serial; + + /** + * The replay_gain_filter_plugin instance of this audio + * output, to be applied to the second chunk during + * cross-fading. + */ + struct filter *other_replay_gain_filter; + + /** + * The serial number of the last replay gain info by the + * "other" chunk during cross-fading. + */ + unsigned other_replay_gain_serial; + + /** + * 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 + * running. + */ + GThread *thread; /** * The next command to be performed by the output thread. @@ -137,6 +202,12 @@ struct audio_output { GMutex *mutex; /** + * This condition object wakes up the output thread after + * #command has been set. + */ + GCond *cond; + + /** * The #music_chunk which is currently being played. All * chunks before this one may be returned to the * #music_buffer, because they are not going to be used by diff --git a/src/output_list.c b/src/output_list.c index 81de16649..8238f581b 100644 --- a/src/output_list.c +++ b/src/output_list.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,9 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "output_list.h" #include "output_api.h" -#include "config.h" extern const struct audio_output_plugin shoutPlugin; extern const struct audio_output_plugin null_output_plugin; @@ -28,12 +28,16 @@ extern const struct audio_output_plugin pipe_output_plugin; extern const struct audio_output_plugin alsaPlugin; extern const struct audio_output_plugin ao_output_plugin; extern const struct audio_output_plugin oss_output_plugin; +extern const struct audio_output_plugin openal_output_plugin; extern const struct audio_output_plugin osxPlugin; extern const struct audio_output_plugin solaris_output_plugin; -extern const struct audio_output_plugin pulse_plugin; +extern const struct audio_output_plugin pulse_output_plugin; extern const struct audio_output_plugin mvp_output_plugin; -extern const struct audio_output_plugin jackPlugin; +extern const struct audio_output_plugin jack_output_plugin; extern const struct audio_output_plugin httpd_output_plugin; +extern const struct audio_output_plugin recorder_output_plugin; +extern const struct audio_output_plugin winmm_output_plugin; +extern const struct audio_output_plugin ffado_output_plugin; const struct audio_output_plugin *audio_output_plugins[] = { #ifdef HAVE_SHOUT @@ -55,6 +59,9 @@ const struct audio_output_plugin *audio_output_plugins[] = { #ifdef HAVE_OSS &oss_output_plugin, #endif +#ifdef HAVE_OPENAL + &openal_output_plugin, +#endif #ifdef HAVE_OSX &osxPlugin, #endif @@ -62,17 +69,26 @@ const struct audio_output_plugin *audio_output_plugins[] = { &solaris_output_plugin, #endif #ifdef HAVE_PULSE - &pulse_plugin, + &pulse_output_plugin, #endif #ifdef HAVE_MVP &mvp_output_plugin, #endif #ifdef HAVE_JACK - &jackPlugin, + &jack_output_plugin, #endif #ifdef ENABLE_HTTPD_OUTPUT &httpd_output_plugin, #endif +#ifdef ENABLE_RECORDER_OUTPUT + &recorder_output_plugin, +#endif +#ifdef ENABLE_WINMM_OUTPUT + &winmm_output_plugin, +#endif +#ifdef ENABLE_FFADO_OUTPUT + &ffado_output_plugin, +#endif NULL }; diff --git a/src/output_list.h b/src/output_list.h index b6f82163c..d72bc224b 100644 --- a/src/output_list.h +++ b/src/output_list.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output_plugin.h b/src/output_plugin.h index 13dba0d0b..36e17ed1b 100644 --- a/src/output_plugin.h +++ b/src/output_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -67,6 +67,24 @@ struct audio_output_plugin { void (*finish)(void *data); /** + * Enable the device. This may allocate resources, preparing + * for the device to be opened. Enabling a device cannot + * fail: if an error occurs during that, it should be reported + * by the open() method. + * + * @param error_r location to store the error occuring, or + * NULL to ignore errors + * @return true on success, false on error + */ + bool (*enable)(void *data, GError **error_r); + + /** + * Disables the device. It is closed before this method is + * called. + */ + void (*disable)(void *data); + + /** * Really open the device. * * @param audio_format the audio format in which data is going @@ -83,6 +101,16 @@ struct audio_output_plugin { void (*close)(void *data); /** + * Returns a positive number if the output thread shall delay + * the next call to play() or pause(). This should be + * implemented instead of doing a sleep inside the plugin, + * because this allows MPD to listen to commands meanwhile. + * + * @return the number of milliseconds to wait + */ + unsigned (*delay)(void *data); + + /** * Display metadata for the next chunk. Optional method, * because not all devices can display metadata. */ @@ -99,6 +127,11 @@ struct audio_output_plugin { GError **error); /** + * Wait until the device has finished playing. + */ + void (*drain)(void *data); + + /** * Try to cancel data which may still be in the device's * buffers. */ @@ -150,6 +183,22 @@ ao_plugin_finish(const struct audio_output_plugin *plugin, void *data) } static inline bool +ao_plugin_enable(const struct audio_output_plugin *plugin, void *data, + GError **error_r) +{ + return plugin->enable != NULL + ? plugin->enable(data, error_r) + : true; +} + +static inline void +ao_plugin_disable(const struct audio_output_plugin *plugin, void *data) +{ + if (plugin->disable != NULL) + plugin->disable(data); +} + +static inline bool ao_plugin_open(const struct audio_output_plugin *plugin, void *data, struct audio_format *audio_format, GError **error) @@ -163,6 +212,14 @@ ao_plugin_close(const struct audio_output_plugin *plugin, void *data) plugin->close(data); } +static inline unsigned +ao_plugin_delay(const struct audio_output_plugin *plugin, void *data) +{ + return plugin->delay != NULL + ? plugin->delay(data) + : 0; +} + static inline void ao_plugin_send_tag(const struct audio_output_plugin *plugin, void *data, const struct tag *tag) @@ -180,6 +237,13 @@ ao_plugin_play(const struct audio_output_plugin *plugin, } static inline void +ao_plugin_drain(const struct audio_output_plugin *plugin, void *data) +{ + if (plugin->drain != NULL) + plugin->drain(data); +} + +static inline void ao_plugin_cancel(const struct audio_output_plugin *plugin, void *data) { if (plugin->cancel != NULL) diff --git a/src/output_print.c b/src/output_print.c index 11e53c32c..7a747ad2f 100644 --- a/src/output_print.c +++ b/src/output_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,6 +22,7 @@ * */ +#include "config.h" #include "output_print.h" #include "output_internal.h" #include "output_all.h" diff --git a/src/output_print.h b/src/output_print.h index aec6f0f87..5ad7e34c7 100644 --- a/src/output_print.h +++ b/src/output_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output_state.c b/src/output_state.c index c7e6c8579..e1187b951 100644 --- a/src/output_state.c +++ b/src/output_state.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,6 +22,7 @@ * */ +#include "config.h" #include "output_state.h" #include "output_internal.h" #include "output_all.h" @@ -34,8 +35,10 @@ #define AUDIO_DEVICE_STATE "audio_device_state:" +unsigned audio_output_state_version; + void -saveAudioDevicesState(FILE *fp) +audio_output_state_save(FILE *fp) { unsigned n = audio_output_count(); @@ -49,35 +52,40 @@ saveAudioDevicesState(FILE *fp) } } -void -readAudioDevicesState(FILE *fp) +bool +audio_output_state_read(const char *line) { - char buffer[1024]; + long value; + char *endptr; + const char *name; + struct audio_output *ao; - while (fgets(buffer, sizeof(buffer), fp)) { - char *c, *name; - struct audio_output *ao; + if (!g_str_has_prefix(line, AUDIO_DEVICE_STATE)) + return false; - g_strchomp(buffer); + line += sizeof(AUDIO_DEVICE_STATE) - 1; - if (!g_str_has_prefix(buffer, AUDIO_DEVICE_STATE)) - continue; + value = strtol(line, &endptr, 10); + if (*endptr != ':' || (value != 0 && value != 1)) + return false; - c = strchr(buffer, ':'); - if (!c || !(++c)) - goto errline; + if (value != 0) + /* state is "enabled": no-op */ + return true; - name = strchr(c, ':'); - if (!name || !(++name)) - goto errline; + name = endptr + 1; + ao = audio_output_find(name); + if (ao == NULL) { + g_debug("Ignoring device state for '%s'", name); + return true; + } - ao = audio_output_find(name); - if (ao != NULL && atoi(c) == 0) - ao->enabled = false; + ao->enabled = false; + return true; +} - continue; -errline: - /* nonfatal */ - g_warning("invalid line in state_file: %s\n", buffer); - } +unsigned +audio_output_state_get_version(void) +{ + return audio_output_state_version; } diff --git a/src/output_state.h b/src/output_state.h index 8592574ab..962ccd97a 100644 --- a/src/output_state.h +++ b/src/output_state.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,12 +25,21 @@ #ifndef OUTPUT_STATE_H #define OUTPUT_STATE_H +#include <stdbool.h> #include <stdio.h> -void -readAudioDevicesState(FILE *fp); +bool +audio_output_state_read(const char *line); void -saveAudioDevicesState(FILE *fp); +audio_output_state_save(FILE *fp); + +/** + * Generates a version number for the current state of the audio + * outputs. This is used by timer_save_state_file() to determine + * whether the state has changed and the state file should be saved. + */ +unsigned +audio_output_state_get_version(void); #endif diff --git a/src/output_thread.c b/src/output_thread.c index 035cf99c1..380956fac 100644 --- a/src/output_thread.c +++ b/src/output_thread.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,12 +17,18 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "output_thread.h" #include "output_api.h" #include "output_internal.h" #include "chunk.h" #include "pipe.h" #include "player_control.h" +#include "pcm_mix.h" +#include "filter_plugin.h" +#include "filter/convert_filter_plugin.h" +#include "filter/replay_gain_filter_plugin.h" +#include "mpd_error.h" #include <glib.h> @@ -37,71 +43,423 @@ static void ao_command_finished(struct audio_output *ao) { assert(ao->command != AO_COMMAND_NONE); ao->command = AO_COMMAND_NONE; + + g_mutex_unlock(ao->mutex); notify_signal(&audio_output_client_notify); + g_mutex_lock(ao->mutex); +} + +static bool +ao_enable(struct audio_output *ao) +{ + GError *error = NULL; + bool success; + + if (ao->really_enabled) + return true; + + g_mutex_unlock(ao->mutex); + success = ao_plugin_enable(ao->plugin, ao->data, &error); + g_mutex_lock(ao->mutex); + if (!success) { + g_warning("Failed to enable \"%s\" [%s]: %s\n", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + return false; + } + + ao->really_enabled = true; + return true; +} + +static void +ao_close(struct audio_output *ao, bool drain); + +static void +ao_disable(struct audio_output *ao) +{ + if (ao->open) + ao_close(ao, false); + + if (ao->really_enabled) { + ao->really_enabled = false; + + g_mutex_unlock(ao->mutex); + ao_plugin_disable(ao->plugin, ao->data); + g_mutex_lock(ao->mutex); + } +} + +static const struct audio_format * +ao_filter_open(struct audio_output *ao, + struct audio_format *audio_format, + GError **error_r) +{ + /* the replay_gain filter cannot fail here */ + if (ao->replay_gain_filter != NULL) + filter_open(ao->replay_gain_filter, audio_format, error_r); + if (ao->other_replay_gain_filter != NULL) + filter_open(ao->other_replay_gain_filter, audio_format, + error_r); + + const struct audio_format *af + = filter_open(ao->filter, audio_format, error_r); + if (af == NULL) { + if (ao->replay_gain_filter != NULL) + filter_close(ao->replay_gain_filter); + if (ao->other_replay_gain_filter != NULL) + filter_close(ao->other_replay_gain_filter); + } + + return af; +} + +static void +ao_filter_close(struct audio_output *ao) +{ + if (ao->replay_gain_filter != NULL) + filter_close(ao->replay_gain_filter); + if (ao->other_replay_gain_filter != NULL) + filter_close(ao->other_replay_gain_filter); + + filter_close(ao->filter); +} + +static void +ao_open(struct audio_output *ao) +{ + bool success; + GError *error = NULL; + const struct audio_format *filter_audio_format; + struct audio_format_string af_string; + + assert(!ao->open); + assert(ao->pipe != NULL); + assert(ao->chunk == NULL); + + if (ao->fail_timer != NULL) { + /* this can only happen when this + output thread fails while + audio_output_open() is run in the + player thread */ + g_timer_destroy(ao->fail_timer); + ao->fail_timer = NULL; + } + + /* enable the device (just in case the last enable has failed) */ + + if (!ao_enable(ao)) + /* still no luck */ + return; + + /* open the filter */ + + filter_audio_format = ao_filter_open(ao, &ao->in_audio_format, &error); + if (filter_audio_format == NULL) { + g_warning("Failed to open filter for \"%s\" [%s]: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + + ao->fail_timer = g_timer_new(); + return; + } + + ao->out_audio_format = *filter_audio_format; + audio_format_mask_apply(&ao->out_audio_format, + &ao->config_audio_format); + + g_mutex_unlock(ao->mutex); + success = ao_plugin_open(ao->plugin, ao->data, + &ao->out_audio_format, + &error); + g_mutex_lock(ao->mutex); + + assert(!ao->open); + + if (!success) { + g_warning("Failed to open \"%s\" [%s]: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + + ao_filter_close(ao); + ao->fail_timer = g_timer_new(); + return; + } + + convert_filter_set(ao->convert_filter, &ao->out_audio_format); + + ao->open = true; + + g_debug("opened plugin=%s name=\"%s\" " + "audio_format=%s", + ao->plugin->name, ao->name, + audio_format_to_string(&ao->out_audio_format, &af_string)); + + if (!audio_format_equals(&ao->in_audio_format, + &ao->out_audio_format)) + g_debug("converting from %s", + audio_format_to_string(&ao->in_audio_format, + &af_string)); } static void -ao_close(struct audio_output *ao) +ao_close(struct audio_output *ao, bool drain) { assert(ao->open); ao->pipe = NULL; - g_mutex_lock(ao->mutex); ao->chunk = NULL; ao->open = false; + g_mutex_unlock(ao->mutex); + if (drain) + ao_plugin_drain(ao->plugin, ao->data); + else + ao_plugin_cancel(ao->plugin, ao->data); + ao_plugin_close(ao->plugin, ao->data); - pcm_convert_deinit(&ao->convert_state); + ao_filter_close(ao); + + g_mutex_lock(ao->mutex); g_debug("closed plugin=%s name=\"%s\"", ao->plugin->name, ao->name); } -static bool -ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) +static void +ao_reopen_filter(struct audio_output *ao) { - const char *data = chunk->data; - size_t size = chunk->length; + const struct audio_format *filter_audio_format; GError *error = NULL; + ao_filter_close(ao); + filter_audio_format = ao_filter_open(ao, &ao->in_audio_format, &error); + if (filter_audio_format == NULL) { + g_warning("Failed to open filter for \"%s\" [%s]: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + + /* this is a little code duplication fro ao_close(), + but we cannot call this function because we must + not call filter_close(ao->filter) again */ + + ao->pipe = NULL; + + ao->chunk = NULL; + ao->open = false; + ao->fail_timer = g_timer_new(); + + g_mutex_unlock(ao->mutex); + ao_plugin_close(ao->plugin, ao->data); + g_mutex_lock(ao->mutex); + + return; + } + + convert_filter_set(ao->convert_filter, &ao->out_audio_format); +} + +static void +ao_reopen(struct audio_output *ao) +{ + if (!audio_format_fully_defined(&ao->config_audio_format)) { + if (ao->open) { + const struct music_pipe *mp = ao->pipe; + ao_close(ao, true); + ao->pipe = mp; + } + + /* no audio format is configured: copy in->out, let + the output's open() method determine the effective + out_audio_format */ + ao->out_audio_format = ao->in_audio_format; + audio_format_mask_apply(&ao->out_audio_format, + &ao->config_audio_format); + } + + if (ao->open) + /* the audio format has changed, and all filters have + to be reconfigured */ + ao_reopen_filter(ao); + else + ao_open(ao); +} + +/** + * Wait until the output's delay reaches zero. + * + * @return true if playback should be continued, false if a command + * was issued + */ +static bool +ao_wait(struct audio_output *ao) +{ + while (true) { + unsigned delay = ao_plugin_delay(ao->plugin, ao->data); + if (delay == 0) + return true; + + GTimeVal tv; + g_get_current_time(&tv); + g_time_val_add(&tv, delay * 1000); + g_cond_timed_wait(ao->cond, ao->mutex, &tv); + + if (ao->command != AO_COMMAND_NONE) + return false; + } +} + +static const char * +ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk, + struct filter *replay_gain_filter, + unsigned *replay_gain_serial_p, + size_t *length_r) +{ + assert(chunk != NULL); assert(!music_chunk_is_empty(chunk)); assert(music_chunk_check_format(chunk, &ao->in_audio_format)); - assert(size % audio_format_frame_size(&ao->in_audio_format) == 0); - if (chunk->tag != NULL) + const char *data = chunk->data; + size_t length = chunk->length; + + (void)ao; + + assert(length % audio_format_frame_size(&ao->in_audio_format) == 0); + + if (length > 0 && replay_gain_filter != NULL) { + if (chunk->replay_gain_serial != *replay_gain_serial_p) { + replay_gain_filter_set_info(replay_gain_filter, + chunk->replay_gain_serial != 0 + ? &chunk->replay_gain_info + : NULL); + *replay_gain_serial_p = chunk->replay_gain_serial; + } + + GError *error = NULL; + data = filter_filter(replay_gain_filter, data, length, + &length, &error); + if (data == NULL) { + g_warning("\"%s\" [%s] failed to filter: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + return NULL; + } + } + + *length_r = length; + return data; +} + +static const char * +ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk, + size_t *length_r) +{ + GError *error = NULL; + + size_t length; + const char *data = ao_chunk_data(ao, chunk, ao->replay_gain_filter, + &ao->replay_gain_serial, &length); + if (data == NULL) + return NULL; + + if (length == 0) { + /* empty chunk, nothing to do */ + *length_r = 0; + return data; + } + + /* cross-fade */ + + if (chunk->other != NULL) { + size_t other_length; + const char *other_data = + ao_chunk_data(ao, chunk->other, + ao->other_replay_gain_filter, + &ao->other_replay_gain_serial, + &other_length); + if (other_data == NULL) + return NULL; + + if (other_length == 0) { + *length_r = 0; + return data; + } + + /* if the "other" chunk is longer, then that trailer + is used as-is, without mixing; it is part of the + "next" song being faded in, and if there's a rest, + it means cross-fading ends here */ + + if (length > other_length) + length = other_length; + + char *dest = pcm_buffer_get(&ao->cross_fade_buffer, + other_length); + memcpy(dest, other_data, other_length); + pcm_mix(dest, data, length, &ao->in_audio_format, + 1.0 - chunk->mix_ratio); + + data = dest; + length = other_length; + } + + /* apply filter chain */ + + data = filter_filter(ao->filter, data, length, &length, &error); + if (data == NULL) { + g_warning("\"%s\" [%s] failed to filter: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + return NULL; + } + + *length_r = length; + return data; +} + +static bool +ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) +{ + GError *error = NULL; + + assert(ao != NULL); + assert(ao->filter != NULL); + + if (chunk->tag != NULL) { + g_mutex_unlock(ao->mutex); ao_plugin_send_tag(ao->plugin, ao->data, chunk->tag); + g_mutex_lock(ao->mutex); + } - if (size == 0) - return true; + size_t size; + const char *data = ao_filter_chunk(ao, chunk, &size); + if (data == NULL) { + ao_close(ao, false); - 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; + /* don't automatically reopen this device for 10 + seconds */ + ao->fail_timer = g_timer_new(); + return false; } while (size > 0 && ao->command == AO_COMMAND_NONE) { size_t nbytes; + if (!ao_wait(ao)) + break; + + g_mutex_unlock(ao->mutex); nbytes = ao_plugin_play(ao->plugin, ao->data, data, size, &error); + g_mutex_lock(ao->mutex); if (nbytes == 0) { /* play()==0 means failure */ g_warning("\"%s\" [%s] failed to play: %s", ao->name, ao->plugin->name, error->message); g_error_free(error); - ao_plugin_cancel(ao->plugin, ao->data); - ao_close(ao); + ao_close(ao, false); /* don't automatically reopen this device for 10 seconds */ @@ -124,32 +482,45 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) return true; } -static void ao_play(struct audio_output *ao) +static const struct music_chunk * +ao_next_chunk(struct audio_output *ao) +{ + return ao->chunk != NULL + /* continue the previous play() call */ + ? ao->chunk->next + /* get the first chunk from the pipe */ + : music_pipe_peek(ao->pipe); +} + +/** + * Plays all remaining chunks, until the tail of the pipe has been + * reached (and no more chunks are queued), or until a command is + * received. + * + * @return true if at least one chunk has been available, false if the + * tail of the pipe was already reached + */ +static bool +ao_play(struct audio_output *ao) { bool success; const struct music_chunk *chunk; assert(ao->pipe != NULL); - g_mutex_lock(ao->mutex); - chunk = ao->chunk; - if (chunk != NULL) - /* continue the previous play() call */ - chunk = chunk->next; - else - chunk = music_pipe_peek(ao->pipe); + chunk = ao_next_chunk(ao); + if (chunk == NULL) + /* no chunk available */ + return false; + ao->chunk_finished = false; while (chunk != NULL && ao->command == AO_COMMAND_NONE) { assert(!ao->chunk_finished); ao->chunk = chunk; - g_mutex_unlock(ao->mutex); success = ao_play_chunk(ao, chunk); - - g_mutex_lock(ao->mutex); - if (!success) { assert(ao->chunk == NULL); break; @@ -160,23 +531,35 @@ static void ao_play(struct audio_output *ao) } ao->chunk_finished = true; + g_mutex_unlock(ao->mutex); + player_lock_signal(); + g_mutex_lock(ao->mutex); - notify_signal(&pc.notify); + return true; } static void ao_pause(struct audio_output *ao) { bool ret; + g_mutex_unlock(ao->mutex); ao_plugin_cancel(ao->plugin, ao->data); + g_mutex_lock(ao->mutex); + ao->pause = true; ao_command_finished(ao); do { + if (!ao_wait(ao)) + break; + + g_mutex_unlock(ao->mutex); ret = ao_plugin_pause(ao->plugin, ao->data); + g_mutex_lock(ao->mutex); + if (!ret) { - ao_close(ao); + ao_close(ao, false); break; } } while (ao->command == AO_COMMAND_NONE); @@ -187,64 +570,31 @@ static void ao_pause(struct audio_output *ao) static gpointer audio_output_task(gpointer arg) { struct audio_output *ao = arg; - bool ret; - GError *error; + + g_mutex_lock(ao->mutex); while (1) { switch (ao->command) { case AO_COMMAND_NONE: break; - case AO_COMMAND_OPEN: - assert(!ao->open); - assert(ao->pipe != NULL); - assert(ao->chunk == NULL); - - if (ao->fail_timer != NULL) { - /* this can only happen when this - output thread fails while - audio_output_open() is run in the - player thread */ - g_timer_destroy(ao->fail_timer); - ao->fail_timer = NULL; - } - - error = NULL; - ret = ao_plugin_open(ao->plugin, ao->data, - &ao->out_audio_format, - &error); - - assert(!ao->open); - if (ret) { - pcm_convert_init(&ao->convert_state); + case AO_COMMAND_ENABLE: + ao_enable(ao); + ao_command_finished(ao); + break; - g_mutex_lock(ao->mutex); - ao->open = true; - g_mutex_unlock(ao->mutex); + case AO_COMMAND_DISABLE: + ao_disable(ao); + ao_command_finished(ao); + break; - g_debug("opened plugin=%s name=\"%s\" " - "audio_format=%u:%u:%u", - ao->plugin->name, - ao->name, - ao->out_audio_format.sample_rate, - ao->out_audio_format.bits, - ao->out_audio_format.channels); - - if (!audio_format_equals(&ao->in_audio_format, - &ao->out_audio_format)) - g_debug("converting from %u:%u:%u", - ao->in_audio_format.sample_rate, - ao->in_audio_format.bits, - ao->in_audio_format.channels); - } else { - g_warning("Failed to open \"%s\" [%s]: %s", - ao->name, ao->plugin->name, - error->message); - g_error_free(error); - - ao->fail_timer = g_timer_new(); - } + case AO_COMMAND_OPEN: + ao_open(ao); + ao_command_finished(ao); + break; + case AO_COMMAND_REOPEN: + ao_reopen(ao); ao_command_finished(ao); break; @@ -252,11 +602,7 @@ static gpointer audio_output_task(gpointer arg) assert(ao->open); assert(ao->pipe != NULL); - ao->pipe = NULL; - ao->chunk = NULL; - - ao_plugin_cancel(ao->plugin, ao->data); - ao_close(ao); + ao_close(ao, false); ao_command_finished(ao); break; @@ -277,38 +623,46 @@ static gpointer audio_output_task(gpointer arg) the new command first */ continue; + case AO_COMMAND_DRAIN: + if (ao->open) { + assert(ao->chunk == NULL); + assert(music_pipe_peek(ao->pipe) == NULL); + + g_mutex_unlock(ao->mutex); + ao_plugin_drain(ao->plugin, ao->data); + g_mutex_lock(ao->mutex); + } + + ao_command_finished(ao); + continue; + case AO_COMMAND_CANCEL: ao->chunk = NULL; if (ao->open) ao_plugin_cancel(ao->plugin, ao->data); - - /* we must clear the notification now, because - the notify_wait() call below must wait - until audio_output_all_cancel() has cleared - the pipe; if another notification happens - to be still pending, we get a race - condition with a crash or an assertion - failure */ - notify_clear(&ao->notify); - ao_command_finished(ao); /* the player thread will now clear our music pipe - wait for a notify, to give it some time */ - notify_wait(&ao->notify); + if (ao->command == AO_COMMAND_NONE) + g_cond_wait(ao->cond, ao->mutex); continue; case AO_COMMAND_KILL: ao->chunk = NULL; ao_command_finished(ao); + g_mutex_unlock(ao->mutex); return NULL; } - if (ao->open) - ao_play(ao); + if (ao->open && ao_play(ao)) + /* don't wait for an event if there are more + chunks in the pipe */ + continue; - notify_wait(&ao->notify); + if (ao->command == AO_COMMAND_NONE) + g_cond_wait(ao->cond, ao->mutex); } } @@ -319,5 +673,5 @@ void audio_output_thread_start(struct audio_output *ao) assert(ao->command == AO_COMMAND_NONE); if (!(ao->thread = g_thread_create(audio_output_task, ao, true, &e))) - g_error("Failed to spawn output task: %s\n", e->message); + MPD_ERROR("Failed to spawn output task: %s\n", e->message); } diff --git a/src/output_thread.h b/src/output_thread.h index a79c3b250..1ee0856f2 100644 --- a/src/output_thread.h +++ b/src/output_thread.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/page.c b/src/page.c index 5ea03cd02..59369cb34 100644 --- a/src/page.c +++ b/src/page.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "page.h" #include <glib.h> diff --git a/src/page.h b/src/page.h index a150e3123..652c4ad6e 100644 --- a/src/page.h +++ b/src/page.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/path.c b/src/path.c index fc73ee7c9..5e39c1636 100644 --- a/src/path.c +++ b/src/path.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "path.h" #include "conf.h" +#include "mpd_error.h" #include <glib.h> @@ -63,7 +65,7 @@ path_set_fs_charset(const char *charset) /* convert a space to ensure that the charset is valid */ test = g_convert(" ", 1, charset, "UTF-8", NULL, NULL, NULL); if (test == NULL) - g_error("invalid filesystem charset: %s", charset); + MPD_ERROR("invalid filesystem charset: %s", charset); g_free(test); g_free(fs_charset); diff --git a/src/path.h b/src/path.h index be845d9b1..512cd13ea 100644 --- a/src/path.h +++ b/src/path.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_buffer.h b/src/pcm_buffer.h index b143bd98f..73959ea03 100644 --- a/src/pcm_buffer.h +++ b/src/pcm_buffer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -65,8 +65,8 @@ pcm_buffer_get(struct pcm_buffer *buffer, size_t size) /* free the old buffer */ g_free(buffer->buffer); - /* allocate a new buffer; align at 64kB boundaries */ - buffer->size = (size | 0xffff) + 1; + /* allocate a new buffer; align at 8 kB boundaries */ + buffer->size = ((size - 1) | 0x1fff) + 1; buffer->buffer = g_malloc(buffer->size); } diff --git a/src/pcm_byteswap.c b/src/pcm_byteswap.c new file mode 100644 index 000000000..967c574cb --- /dev/null +++ b/src/pcm_byteswap.c @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "pcm_byteswap.h" +#include "pcm_buffer.h" + +#include <glib.h> + +#include <assert.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "pcm" + +static inline uint16_t swab16(uint16_t x) +{ + return (x << 8) | (x >> 8); +} + +const int16_t *pcm_byteswap_16(struct pcm_buffer *buffer, + const int16_t *src, size_t len) +{ + unsigned i; + int16_t *buf = pcm_buffer_get(buffer, len); + + assert(buf != NULL); + + for (i = 0; i < len / 2; i++) + buf[i] = swab16(src[i]); + + return buf; +} + +static inline uint32_t swab32(uint32_t x) +{ + return (x << 24) | + ((x & 0xff00) << 8) | + ((x & 0xff0000) >> 8) | + (x >> 24); +} + +const int32_t *pcm_byteswap_32(struct pcm_buffer *buffer, + const int32_t *src, size_t len) +{ + unsigned i; + int32_t *buf = pcm_buffer_get(buffer, len); + + assert(buf != NULL); + + for (i = 0; i < len / 4; i++) + buf[i] = swab32(src[i]); + + return buf; +} diff --git a/src/pcm_byteswap.h b/src/pcm_byteswap.h new file mode 100644 index 000000000..005e75ded --- /dev/null +++ b/src/pcm_byteswap.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PCM_BYTESWAP_H +#define MPD_PCM_BYTESWAP_H + +#include <stdint.h> +#include <stddef.h> + +struct pcm_buffer; + +/** + * Changes the endianness of 16 bit PCM data. + * + * @param buffer the destination pcm_buffer object + * @param src the source PCM buffer + * @param src_size the number of bytes in #src + * @return the destination buffer + */ +const int16_t *pcm_byteswap_16(struct pcm_buffer *buffer, + const int16_t *src, size_t len); + +/** + * Changes the endianness of 32-bit (or 24-bit) PCM data. + * + * @param buffer the destination pcm_buffer object + * @param src the source PCM buffer + * @param src_size the number of bytes in #src + * @return the destination buffer + */ +const int32_t *pcm_byteswap_32(struct pcm_buffer *buffer, + const int32_t *src, size_t len); + +#endif diff --git a/src/pcm_channels.c b/src/pcm_channels.c index 969ddff32..34e72ca4e 100644 --- a/src/pcm_channels.c +++ b/src/pcm_channels.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,16 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "pcm_channels.h" #include "pcm_buffer.h" -#include <glib.h> - #include <assert.h> -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "pcm" - static void pcm_convert_channels_16_1_to_2(int16_t *dest, const int16_t *src, unsigned num_frames) @@ -75,8 +71,8 @@ pcm_convert_channels_16_n_to_2(int16_t *dest, const int16_t * pcm_convert_channels_16(struct pcm_buffer *buffer, - int8_t dest_channels, - int8_t src_channels, const int16_t *src, + uint8_t dest_channels, + uint8_t src_channels, const int16_t *src, size_t src_size, size_t *dest_size_r) { unsigned num_frames = src_size / src_channels / sizeof(*src); @@ -92,11 +88,8 @@ pcm_convert_channels_16(struct pcm_buffer *buffer, else if (dest_channels == 2) pcm_convert_channels_16_n_to_2(dest, src_channels, src, num_frames); - else { - g_warning("conversion %u->%u channels is not supported", - src_channels, dest_channels); + else return NULL; - } return dest; } @@ -149,8 +142,8 @@ pcm_convert_channels_24_n_to_2(int32_t *dest, const int32_t * pcm_convert_channels_24(struct pcm_buffer *buffer, - int8_t dest_channels, - int8_t src_channels, const int32_t *src, + uint8_t dest_channels, + uint8_t src_channels, const int32_t *src, size_t src_size, size_t *dest_size_r) { unsigned num_frames = src_size / src_channels / sizeof(*src); @@ -166,11 +159,8 @@ pcm_convert_channels_24(struct pcm_buffer *buffer, else if (dest_channels == 2) pcm_convert_channels_24_n_to_2(dest, src_channels, src, num_frames); - else { - g_warning("conversion %u->%u channels is not supported", - src_channels, dest_channels); + else return NULL; - } return dest; } @@ -218,8 +208,8 @@ pcm_convert_channels_32_n_to_2(int32_t *dest, const int32_t * pcm_convert_channels_32(struct pcm_buffer *buffer, - int8_t dest_channels, - int8_t src_channels, const int32_t *src, + uint8_t dest_channels, + uint8_t src_channels, const int32_t *src, size_t src_size, size_t *dest_size_r) { unsigned num_frames = src_size / src_channels / sizeof(*src); @@ -235,11 +225,8 @@ pcm_convert_channels_32(struct pcm_buffer *buffer, else if (dest_channels == 2) pcm_convert_channels_32_n_to_2(dest, src_channels, src, num_frames); - else { - g_warning("conversion %u->%u channels is not supported", - src_channels, dest_channels); + else return NULL; - } return dest; } diff --git a/src/pcm_channels.h b/src/pcm_channels.h index accf4b07b..a23cbd364 100644 --- a/src/pcm_channels.h +++ b/src/pcm_channels.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -38,8 +38,8 @@ struct pcm_buffer; */ const int16_t * pcm_convert_channels_16(struct pcm_buffer *buffer, - int8_t dest_channels, - int8_t src_channels, const int16_t *src, + uint8_t dest_channels, + uint8_t src_channels, const int16_t *src, size_t src_size, size_t *dest_size_r); /** @@ -56,8 +56,8 @@ pcm_convert_channels_16(struct pcm_buffer *buffer, */ const int32_t * pcm_convert_channels_24(struct pcm_buffer *buffer, - int8_t dest_channels, - int8_t src_channels, const int32_t *src, + uint8_t dest_channels, + uint8_t src_channels, const int32_t *src, size_t src_size, size_t *dest_size_r); /** @@ -73,8 +73,8 @@ pcm_convert_channels_24(struct pcm_buffer *buffer, */ const int32_t * pcm_convert_channels_32(struct pcm_buffer *buffer, - int8_t dest_channels, - int8_t src_channels, const int32_t *src, + uint8_t dest_channels, + uint8_t src_channels, const int32_t *src, size_t src_size, size_t *dest_size_r); #endif diff --git a/src/pcm_convert.c b/src/pcm_convert.c index ebb4adff5..5fe89b53a 100644 --- a/src/pcm_convert.c +++ b/src/pcm_convert.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,9 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "pcm_convert.h" #include "pcm_channels.h" #include "pcm_format.h" +#include "pcm_byteswap.h" +#include "pcm_pack.h" #include "audio_format.h" #include <assert.h> @@ -38,7 +41,9 @@ void pcm_convert_init(struct pcm_convert_state *state) pcm_dither_24_init(&state->dither); pcm_buffer_init(&state->format_buffer); + pcm_buffer_init(&state->pack_buffer); pcm_buffer_init(&state->channels_buffer); + pcm_buffer_init(&state->byteswap_buffer); } void pcm_convert_deinit(struct pcm_convert_state *state) @@ -46,42 +51,62 @@ void pcm_convert_deinit(struct pcm_convert_state *state) pcm_resample_deinit(&state->resample); pcm_buffer_deinit(&state->format_buffer); + pcm_buffer_deinit(&state->pack_buffer); pcm_buffer_deinit(&state->channels_buffer); + pcm_buffer_deinit(&state->byteswap_buffer); } static const int16_t * pcm_convert_16(struct pcm_convert_state *state, const struct audio_format *src_format, const void *src_buffer, size_t src_size, - const struct audio_format *dest_format, - size_t *dest_size_r) + const struct audio_format *dest_format, size_t *dest_size_r, + GError **error_r) { const int16_t *buf; size_t len; - assert(dest_format->bits == 16); + assert(dest_format->format == SAMPLE_FORMAT_S16); buf = pcm_convert_to_16(&state->format_buffer, &state->dither, - src_format->bits, src_buffer, src_size, + src_format->format, src_buffer, src_size, &len); - if (!buf) - g_error("pcm_convert_to_16() failed"); + if (buf == NULL) { + g_set_error(error_r, pcm_convert_quark(), 0, + "Conversion from %s to 16 bit is not implemented", + sample_format_to_string(src_format->format)); + return NULL; + } if (src_format->channels != dest_format->channels) { buf = pcm_convert_channels_16(&state->channels_buffer, dest_format->channels, src_format->channels, buf, len, &len); - if (!buf) - g_error("pcm_convert_channels_16() failed"); + if (buf == NULL) { + g_set_error(error_r, pcm_convert_quark(), 0, + "Conversion from %u to %u channels " + "is not implemented", + src_format->channels, + dest_format->channels); + return NULL; + } } - if (src_format->sample_rate != dest_format->sample_rate) + if (src_format->sample_rate != dest_format->sample_rate) { buf = pcm_resample_16(&state->resample, dest_format->channels, src_format->sample_rate, buf, len, - dest_format->sample_rate, - &len); + dest_format->sample_rate, &len, + error_r); + if (buf == NULL) + return NULL; + } + + if (dest_format->reverse_endian) { + buf = pcm_byteswap_16(&state->byteswap_buffer, buf, len); + assert(buf != NULL); + } *dest_size_r = len; return buf; @@ -91,71 +116,146 @@ static const int32_t * pcm_convert_24(struct pcm_convert_state *state, const struct audio_format *src_format, const void *src_buffer, size_t src_size, - const struct audio_format *dest_format, - size_t *dest_size_r) + const struct audio_format *dest_format, size_t *dest_size_r, + GError **error_r) { const int32_t *buf; size_t len; - assert(dest_format->bits == 24); + assert(dest_format->format == SAMPLE_FORMAT_S24_P32); - buf = pcm_convert_to_24(&state->format_buffer, src_format->bits, + buf = pcm_convert_to_24(&state->format_buffer, src_format->format, src_buffer, src_size, &len); - if (!buf) - g_error("pcm_convert_to_24() failed"); + if (buf == NULL) { + g_set_error(error_r, pcm_convert_quark(), 0, + "Conversion from %s to 24 bit is not implemented", + sample_format_to_string(src_format->format)); + return NULL; + } if (src_format->channels != dest_format->channels) { buf = pcm_convert_channels_24(&state->channels_buffer, dest_format->channels, src_format->channels, buf, len, &len); - if (!buf) - g_error("pcm_convert_channels_24() failed"); + if (buf == NULL) { + g_set_error(error_r, pcm_convert_quark(), 0, + "Conversion from %u to %u channels " + "is not implemented", + src_format->channels, + dest_format->channels); + return NULL; + } } - if (src_format->sample_rate != dest_format->sample_rate) + if (src_format->sample_rate != dest_format->sample_rate) { buf = pcm_resample_24(&state->resample, dest_format->channels, src_format->sample_rate, buf, len, - dest_format->sample_rate, - &len); + dest_format->sample_rate, &len, + error_r); + if (buf == NULL) + return NULL; + } + + if (dest_format->reverse_endian) { + buf = pcm_byteswap_32(&state->byteswap_buffer, buf, len); + assert(buf != NULL); + } *dest_size_r = len; return buf; } +/** + * Convert to 24 bit packed samples (aka S24_3LE / S24_3BE). + */ +static const void * +pcm_convert_24_packed(struct pcm_convert_state *state, + const struct audio_format *src_format, + const void *src_buffer, size_t src_size, + const struct audio_format *dest_format, + size_t *dest_size_r, + GError **error_r) +{ + assert(dest_format->format == SAMPLE_FORMAT_S24); + + /* use the normal 24 bit conversion first */ + + struct audio_format audio_format; + audio_format_init(&audio_format, dest_format->sample_rate, + SAMPLE_FORMAT_S24_P32, dest_format->channels); + + const int32_t *buffer; + size_t buffer_size; + + buffer = pcm_convert_24(state, src_format, src_buffer, src_size, + &audio_format, &buffer_size, error_r); + if (buffer == NULL) + return NULL; + + /* now convert to packed 24 bit */ + + unsigned num_samples = buffer_size / 4; + size_t dest_size = num_samples * 3; + + uint8_t *dest = pcm_buffer_get(&state->pack_buffer, dest_size); + pcm_pack_24(dest, buffer, num_samples, dest_format->reverse_endian); + + *dest_size_r = dest_size; + return dest; +} + static const int32_t * pcm_convert_32(struct pcm_convert_state *state, const struct audio_format *src_format, const void *src_buffer, size_t src_size, - const struct audio_format *dest_format, - size_t *dest_size_r) + const struct audio_format *dest_format, size_t *dest_size_r, + GError **error_r) { const int32_t *buf; size_t len; - assert(dest_format->bits == 32); + assert(dest_format->format == SAMPLE_FORMAT_S32); - buf = pcm_convert_to_32(&state->format_buffer, src_format->bits, + buf = pcm_convert_to_32(&state->format_buffer, src_format->format, src_buffer, src_size, &len); - if (!buf) - g_error("pcm_convert_to_32() failed"); + if (buf == NULL) { + g_set_error(error_r, pcm_convert_quark(), 0, + "Conversion from %s to 24 bit is not implemented", + sample_format_to_string(src_format->format)); + return NULL; + } if (src_format->channels != dest_format->channels) { buf = pcm_convert_channels_32(&state->channels_buffer, dest_format->channels, src_format->channels, buf, len, &len); - if (!buf) - g_error("pcm_convert_channels_32() failed"); + if (buf == NULL) { + g_set_error(error_r, pcm_convert_quark(), 0, + "Conversion from %u to %u channels " + "is not implemented", + src_format->channels, + dest_format->channels); + return NULL; + } } - if (src_format->sample_rate != dest_format->sample_rate) + if (src_format->sample_rate != dest_format->sample_rate) { buf = pcm_resample_32(&state->resample, dest_format->channels, src_format->sample_rate, buf, len, - dest_format->sample_rate, - &len); + dest_format->sample_rate, &len, + error_r); + if (buf == NULL) + return buf; + } + + if (dest_format->reverse_endian) { + buf = pcm_byteswap_32(&state->byteswap_buffer, buf, len); + assert(buf != NULL); + } *dest_size_r = len; return buf; @@ -166,26 +266,38 @@ pcm_convert(struct pcm_convert_state *state, const struct audio_format *src_format, const void *src, size_t src_size, const struct audio_format *dest_format, - size_t *dest_size_r) + size_t *dest_size_r, + GError **error_r) { - switch (dest_format->bits) { - case 16: + switch (dest_format->format) { + case SAMPLE_FORMAT_S16: return pcm_convert_16(state, src_format, src, src_size, - dest_format, dest_size_r); + dest_format, dest_size_r, + error_r); + + case SAMPLE_FORMAT_S24: + return pcm_convert_24_packed(state, + src_format, src, src_size, + dest_format, dest_size_r, + error_r); - case 24: + case SAMPLE_FORMAT_S24_P32: return pcm_convert_24(state, src_format, src, src_size, - dest_format, dest_size_r); + dest_format, dest_size_r, + error_r); - case 32: + case SAMPLE_FORMAT_S32: return pcm_convert_32(state, src_format, src, src_size, - dest_format, dest_size_r); + dest_format, dest_size_r, + error_r); default: - g_error("cannot convert to %u bit\n", dest_format->bits); + g_set_error(error_r, pcm_convert_quark(), 0, + "PCM conversion to %s is not implemented", + sample_format_to_string(dest_format->format)); return NULL; } } diff --git a/src/pcm_convert.h b/src/pcm_convert.h index be08ad8a8..01ba2c787 100644 --- a/src/pcm_convert.h +++ b/src/pcm_convert.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -39,10 +39,22 @@ struct pcm_convert_state { /** the buffer for converting the sample format */ struct pcm_buffer format_buffer; + /** the buffer for converting to/from packed samples */ + struct pcm_buffer pack_buffer; + /** the buffer for converting the channel count */ struct pcm_buffer channels_buffer; + + /** the buffer for swapping the byte order */ + struct pcm_buffer byteswap_buffer; }; +static inline GQuark +pcm_convert_quark(void) +{ + return g_quark_from_static_string("pcm_convert"); +} + /** * Initializes a pcm_convert_state object. */ @@ -63,13 +75,16 @@ void pcm_convert_deinit(struct pcm_convert_state *state); * @param src_size the size of #src in bytes * @param dest_format the requested destination audio format * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer + * @param error_r location to store the error occuring, or NULL to + * ignore errors + * @return the destination buffer, or NULL on error */ const void * pcm_convert(struct pcm_convert_state *state, const struct audio_format *src_format, const void *src, size_t src_size, const struct audio_format *dest_format, - size_t *dest_size_r); + size_t *dest_size_r, + GError **error_r); #endif diff --git a/src/pcm_dither.c b/src/pcm_dither.c index 45c11790c..03388f0e0 100644 --- a/src/pcm_dither.c +++ b/src/pcm_dither.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "pcm_dither.h" #include "pcm_prng.h" diff --git a/src/pcm_dither.h b/src/pcm_dither.h index a5c0c3bae..dafae957f 100644 --- a/src/pcm_dither.h +++ b/src/pcm_dither.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_format.c b/src/pcm_format.c index 0e686e17c..3fd76a987 100644 --- a/src/pcm_format.c +++ b/src/pcm_format.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,11 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "pcm_format.h" #include "pcm_dither.h" #include "pcm_buffer.h" - -#include <glib.h> +#include "pcm_pack.h" static void pcm_convert_8_to_16(int16_t *out, const int8_t *in, @@ -49,16 +49,29 @@ pcm_convert_32_to_16(struct pcm_dither *dither, pcm_dither_32_to_16(dither, out, in, num_samples); } +static int32_t * +pcm_convert_24_to_24p32(struct pcm_buffer *buffer, const uint8_t *src, + unsigned num_samples) +{ + int32_t *dest = pcm_buffer_get(buffer, num_samples * 4); + pcm_unpack_24(dest, src, num_samples, false); + return dest; +} + const int16_t * pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, - uint8_t bits, const void *src, + enum sample_format src_format, const void *src, size_t src_size, size_t *dest_size_r) { unsigned num_samples; int16_t *dest; + int32_t *dest32; + + switch (src_format) { + case SAMPLE_FORMAT_UNDEFINED: + break; - switch (bits) { - case 8: + case SAMPLE_FORMAT_S8: num_samples = src_size; *dest_size_r = src_size * sizeof(*dest); dest = pcm_buffer_get(buffer, *dest_size_r); @@ -68,11 +81,24 @@ pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, num_samples); return dest; - case 16: + case SAMPLE_FORMAT_S16: *dest_size_r = src_size; return src; - case 24: + case SAMPLE_FORMAT_S24: + /* convert to S24_P32 first */ + num_samples = src_size / 3; + + dest32 = pcm_convert_24_to_24p32(buffer, src, num_samples); + dest = (int16_t *)dest32; + + /* convert to 16 bit in-place */ + *dest_size_r = num_samples * sizeof(*dest); + pcm_convert_24_to_16(dither, dest, dest32, + num_samples); + return dest; + + case SAMPLE_FORMAT_S24_P32: num_samples = src_size / 4; *dest_size_r = num_samples * sizeof(*dest); dest = pcm_buffer_get(buffer, *dest_size_r); @@ -82,7 +108,7 @@ pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, num_samples); return dest; - case 32: + case SAMPLE_FORMAT_S32: num_samples = src_size / 4; *dest_size_r = num_samples * sizeof(*dest); dest = pcm_buffer_get(buffer, *dest_size_r); @@ -93,7 +119,6 @@ pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, return dest; } - g_warning("only 8 or 16 bits are supported for conversion!\n"); return NULL; } @@ -129,14 +154,17 @@ pcm_convert_32_to_24(int32_t *out, const int16_t *in, const int32_t * pcm_convert_to_24(struct pcm_buffer *buffer, - uint8_t bits, const void *src, + enum sample_format src_format, const void *src, size_t src_size, size_t *dest_size_r) { unsigned num_samples; int32_t *dest; - switch (bits) { - case 8: + switch (src_format) { + case SAMPLE_FORMAT_UNDEFINED: + break; + + case SAMPLE_FORMAT_S8: num_samples = src_size; *dest_size_r = src_size * sizeof(*dest); dest = pcm_buffer_get(buffer, *dest_size_r); @@ -145,7 +173,7 @@ pcm_convert_to_24(struct pcm_buffer *buffer, num_samples); return dest; - case 16: + case SAMPLE_FORMAT_S16: num_samples = src_size / 2; *dest_size_r = num_samples * sizeof(*dest); dest = pcm_buffer_get(buffer, *dest_size_r); @@ -154,11 +182,17 @@ pcm_convert_to_24(struct pcm_buffer *buffer, num_samples); return dest; - case 24: + case SAMPLE_FORMAT_S24: + num_samples = src_size / 3; + *dest_size_r = num_samples * sizeof(*dest); + + return pcm_convert_24_to_24p32(buffer, src, num_samples); + + case SAMPLE_FORMAT_S24_P32: *dest_size_r = src_size; return src; - case 32: + case SAMPLE_FORMAT_S32: num_samples = src_size / 4; *dest_size_r = num_samples * sizeof(*dest); dest = pcm_buffer_get(buffer, *dest_size_r); @@ -168,7 +202,6 @@ pcm_convert_to_24(struct pcm_buffer *buffer, return dest; } - g_warning("only 8 or 24 bits are supported for conversion!\n"); return NULL; } @@ -204,14 +237,17 @@ pcm_convert_24_to_32(int32_t *out, const int32_t *in, const int32_t * pcm_convert_to_32(struct pcm_buffer *buffer, - uint8_t bits, const void *src, + enum sample_format src_format, const void *src, size_t src_size, size_t *dest_size_r) { unsigned num_samples; int32_t *dest; - switch (bits) { - case 8: + switch (src_format) { + case SAMPLE_FORMAT_UNDEFINED: + break; + + case SAMPLE_FORMAT_S8: num_samples = src_size; *dest_size_r = src_size * sizeof(*dest); dest = pcm_buffer_get(buffer, *dest_size_r); @@ -220,7 +256,7 @@ pcm_convert_to_32(struct pcm_buffer *buffer, num_samples); return dest; - case 16: + case SAMPLE_FORMAT_S16: num_samples = src_size / 2; *dest_size_r = num_samples * sizeof(*dest); dest = pcm_buffer_get(buffer, *dest_size_r); @@ -229,7 +265,18 @@ pcm_convert_to_32(struct pcm_buffer *buffer, num_samples); return dest; - case 24: + case SAMPLE_FORMAT_S24: + /* convert to S24_P32 first */ + num_samples = src_size / 3; + + dest = pcm_convert_24_to_24p32(buffer, src, num_samples); + + /* convert to 32 bit in-place */ + *dest_size_r = num_samples * sizeof(*dest); + pcm_convert_24_to_32(dest, dest, num_samples); + return dest; + + case SAMPLE_FORMAT_S24_P32: num_samples = src_size / 4; *dest_size_r = num_samples * sizeof(*dest); dest = pcm_buffer_get(buffer, *dest_size_r); @@ -238,11 +285,10 @@ pcm_convert_to_32(struct pcm_buffer *buffer, num_samples); return dest; - case 32: + case SAMPLE_FORMAT_S32: *dest_size_r = src_size; return src; } - g_warning("only 8 or 32 bits are supported for conversion!\n"); return NULL; } diff --git a/src/pcm_format.h b/src/pcm_format.h index 350566827..3e96fc65f 100644 --- a/src/pcm_format.h +++ b/src/pcm_format.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,8 @@ #ifndef PCM_FORMAT_H #define PCM_FORMAT_H +#include "audio_format.h" + #include <stdint.h> #include <stddef.h> @@ -40,7 +42,7 @@ struct pcm_dither; */ const int16_t * pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, - uint8_t bits, const void *src, + enum sample_format src_format, const void *src, size_t src_size, size_t *dest_size_r); /** @@ -55,7 +57,7 @@ pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, */ const int32_t * pcm_convert_to_24(struct pcm_buffer *buffer, - uint8_t bits, const void *src, + enum sample_format src_format, const void *src, size_t src_size, size_t *dest_size_r); /** @@ -70,7 +72,7 @@ pcm_convert_to_24(struct pcm_buffer *buffer, */ const int32_t * pcm_convert_to_32(struct pcm_buffer *buffer, - uint8_t bits, const void *src, + enum sample_format src_format, const void *src, size_t src_size, size_t *dest_size_r); #endif diff --git a/src/pcm_mix.c b/src/pcm_mix.c index d1e716731..3145c07be 100644 --- a/src/pcm_mix.c +++ b/src/pcm_mix.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "pcm_mix.h" #include "pcm_volume.h" #include "pcm_utils.h" #include "audio_format.h" +#include "mpd_error.h" #include <glib.h> @@ -30,8 +32,8 @@ #define G_LOG_DOMAIN "pcm" static void -pcm_add_8(int8_t *buffer1, const int8_t *buffer2, - unsigned num_samples, int volume1, int volume2) +pcm_add_vol_8(int8_t *buffer1, const int8_t *buffer2, + unsigned num_samples, int volume1, int volume2) { while (num_samples > 0) { int32_t sample1 = *buffer1; @@ -47,8 +49,8 @@ pcm_add_8(int8_t *buffer1, const int8_t *buffer2, } static void -pcm_add_16(int16_t *buffer1, const int16_t *buffer2, - unsigned num_samples, int volume1, int volume2) +pcm_add_vol_16(int16_t *buffer1, const int16_t *buffer2, + unsigned num_samples, int volume1, int volume2) { while (num_samples > 0) { int32_t sample1 = *buffer1; @@ -64,8 +66,8 @@ pcm_add_16(int16_t *buffer1, const int16_t *buffer2, } static void -pcm_add_24(int32_t *buffer1, const int32_t *buffer2, - unsigned num_samples, unsigned volume1, unsigned volume2) +pcm_add_vol_24(int32_t *buffer1, const int32_t *buffer2, + unsigned num_samples, unsigned volume1, unsigned volume2) { while (num_samples > 0) { int64_t sample1 = *buffer1; @@ -81,29 +83,134 @@ pcm_add_24(int32_t *buffer1, const int32_t *buffer2, } static void +pcm_add_vol_32(int32_t *buffer1, const int32_t *buffer2, + unsigned num_samples, unsigned volume1, unsigned volume2) +{ + while (num_samples > 0) { + int64_t sample1 = *buffer1; + int64_t sample2 = *buffer2++; + + sample1 = ((sample1 * volume1 + sample2 * volume2) + + pcm_volume_dither() + PCM_VOLUME_1 / 2) + / PCM_VOLUME_1; + + *buffer1++ = pcm_range_64(sample1, 32); + --num_samples; + } +} + +static void +pcm_add_vol(void *buffer1, const void *buffer2, size_t size, + int vol1, int vol2, + const struct audio_format *format) +{ + switch (format->format) { + case SAMPLE_FORMAT_S8: + pcm_add_vol_8((int8_t *)buffer1, (const int8_t *)buffer2, + size, vol1, vol2); + break; + + case SAMPLE_FORMAT_S16: + pcm_add_vol_16((int16_t *)buffer1, (const int16_t *)buffer2, + size / 2, vol1, vol2); + break; + + case SAMPLE_FORMAT_S24_P32: + pcm_add_vol_24((int32_t *)buffer1, (const int32_t *)buffer2, + size / 4, vol1, vol2); + break; + + case SAMPLE_FORMAT_S32: + pcm_add_vol_32((int32_t *)buffer1, (const int32_t *)buffer2, + size / 4, vol1, vol2); + break; + + default: + MPD_ERROR("format %s not supported by pcm_add_vol", + sample_format_to_string(format->format)); + } +} + +static void +pcm_add_8(int8_t *buffer1, const int8_t *buffer2, unsigned num_samples) +{ + while (num_samples > 0) { + int32_t sample1 = *buffer1; + int32_t sample2 = *buffer2++; + + sample1 += sample2; + + *buffer1++ = pcm_range(sample1, 8); + --num_samples; + } +} + +static void +pcm_add_16(int16_t *buffer1, const int16_t *buffer2, unsigned num_samples) +{ + while (num_samples > 0) { + int32_t sample1 = *buffer1; + int32_t sample2 = *buffer2++; + + sample1 += sample2; + + *buffer1++ = pcm_range(sample1, 16); + --num_samples; + } +} + +static void +pcm_add_24(int32_t *buffer1, const int32_t *buffer2, unsigned num_samples) +{ + while (num_samples > 0) { + int64_t sample1 = *buffer1; + int64_t sample2 = *buffer2++; + + sample1 += sample2; + + *buffer1++ = pcm_range(sample1, 24); + --num_samples; + } +} + +static void +pcm_add_32(int32_t *buffer1, const int32_t *buffer2, unsigned num_samples) +{ + while (num_samples > 0) { + int64_t sample1 = *buffer1; + int64_t sample2 = *buffer2++; + + sample1 += sample2; + + *buffer1++ = pcm_range_64(sample1, 32); + --num_samples; + } +} + +static void pcm_add(void *buffer1, const void *buffer2, size_t size, - int vol1, int vol2, const struct audio_format *format) { - switch (format->bits) { - case 8: - pcm_add_8((int8_t *)buffer1, (const int8_t *)buffer2, - size, vol1, vol2); + switch (format->format) { + case SAMPLE_FORMAT_S8: + pcm_add_8((int8_t *)buffer1, (const int8_t *)buffer2, size); break; - case 16: - pcm_add_16((int16_t *)buffer1, (const int16_t *)buffer2, - size / 2, vol1, vol2); + case SAMPLE_FORMAT_S16: + pcm_add_16((int16_t *)buffer1, (const int16_t *)buffer2, size / 2); break; - case 24: - pcm_add_24((int32_t*)buffer1, - (const int32_t*)buffer2, - size / 4, vol1, vol2); + case SAMPLE_FORMAT_S24_P32: + pcm_add_24((int32_t *)buffer1, (const int32_t *)buffer2, size / 4); + break; + + case SAMPLE_FORMAT_S32: + pcm_add_32((int32_t *)buffer1, (const int32_t *)buffer2, size / 4); break; default: - g_error("%u bits not supported by pcm_add!\n", format->bits); + MPD_ERROR("format %s not supported by pcm_add", + sample_format_to_string(format->format)); } } @@ -112,11 +219,20 @@ pcm_mix(void *buffer1, const void *buffer2, size_t size, const struct audio_format *format, float portion1) { int vol1; - float s = sin(M_PI_2 * portion1); + float s; + + /* portion1 is between 0.0 and 1.0 for crossfading, MixRamp uses NaN + * to signal mixing rather than fading */ + if (isnan(portion1)) { + pcm_add(buffer1, buffer2, size, format); + return; + } + + s = sin(M_PI_2 * portion1); s *= s; vol1 = s * PCM_VOLUME_1 + 0.5; vol1 = vol1 > PCM_VOLUME_1 ? PCM_VOLUME_1 : (vol1 < 0 ? 0 : vol1); - pcm_add(buffer1, buffer2, size, vol1, PCM_VOLUME_1 - vol1, format); + pcm_add_vol(buffer1, buffer2, size, vol1, PCM_VOLUME_1 - vol1, format); } diff --git a/src/pcm_mix.h b/src/pcm_mix.h index 6605585bd..086d5501e 100644 --- a/src/pcm_mix.h +++ b/src/pcm_mix.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -35,7 +35,8 @@ struct audio_format; * @param size the size of both buffers in bytes * @param format the audio format of both buffers * @param portion1 a number between 0.0 and 1.0 specifying the portion - * of the first buffer in the mix; portion2 = (1.0 - portion1) + * of the first buffer in the mix; portion2 = (1.0 - portion1). The value + * NaN is used by the MixRamp code to specify that simple addition is required. */ void pcm_mix(void *buffer1, const void *buffer2, size_t size, diff --git a/src/pcm_pack.c b/src/pcm_pack.c new file mode 100644 index 000000000..9af0ab1ed --- /dev/null +++ b/src/pcm_pack.c @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2003-2010 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 "pcm_pack.h" + +#include <glib.h> + +static void +pack_sample(uint8_t *dest, const int32_t *src0, bool reverse_endian) +{ + const uint8_t *src = (const uint8_t *)src0; + + if ((G_BYTE_ORDER == G_BIG_ENDIAN) != reverse_endian) + ++src; + + *dest++ = *src++; + *dest++ = *src++; + *dest++ = *src++; +} + +void +pcm_pack_24(uint8_t *dest, const int32_t *src, unsigned num_samples, + bool reverse_endian) +{ + /* duplicate loop to help the compiler's optimizer (constant + parameter to the pack_sample() inline function) */ + + if (G_LIKELY(!reverse_endian)) { + while (num_samples-- > 0) { + pack_sample(dest, src++, false); + dest += 3; + } + } else { + while (num_samples-- > 0) { + pack_sample(dest, src++, true); + dest += 3; + } + } +} + +static void +unpack_sample(int32_t *dest0, const uint8_t *src, bool reverse_endian) +{ + uint8_t *dest = (uint8_t *)dest0; + + if ((G_BYTE_ORDER == G_BIG_ENDIAN) != reverse_endian) + /* extend the sign bit to the most fourth byte */ + *dest++ = *src & 0x80 ? 0xff : 0x00; + + *dest++ = *src++; + *dest++ = *src++; + *dest++ = *src; + + if ((G_BYTE_ORDER == G_LITTLE_ENDIAN) != reverse_endian) + /* extend the sign bit to the most fourth byte */ + *dest++ = *src & 0x80 ? 0xff : 0x00; +} + +void +pcm_unpack_24(int32_t *dest, const uint8_t *src, unsigned num_samples, + bool reverse_endian) +{ + /* duplicate loop to help the compiler's optimizer (constant + parameter to the unpack_sample() inline function) */ + + if (G_LIKELY(!reverse_endian)) { + while (num_samples-- > 0) { + unpack_sample(dest++, src, false); + src += 3; + } + } else { + while (num_samples-- > 0) { + unpack_sample(dest++, src, true); + src += 3; + } + } +} diff --git a/src/pcm_pack.h b/src/pcm_pack.h new file mode 100644 index 000000000..3c99eaa35 --- /dev/null +++ b/src/pcm_pack.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2010 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 + * + * Library for working with packed 24 bit samples. + */ + +#ifndef PCM_PACK_H +#define PCM_PACK_H + +#include <stdbool.h> +#include <stdint.h> + +/** + * Converts padded 24 bit samples (4 bytes per sample) to packed 24 + * bit samples (3 bytes per sample). + * + * This function can be used to convert a buffer in-place. + * + * @param dest the destination buffer (array of triples) + * @param src the source buffer + * @param num_samples the number of samples to convert + * @param reverse_endian is src and dest in non-host byte order? + */ +void +pcm_pack_24(uint8_t *dest, const int32_t *src, unsigned num_samples, + bool reverse_endian); + +/** + * Converts packed 24 bit samples (3 bytes per sample) to padded 24 + * bit samples (4 bytes per sample). + * + * @param dest the destination buffer + * @param src the source buffer (array of triples) + * @param num_samples the number of samples to convert + * @param reverse_endian is src and dest in non-host byte order? + */ +void +pcm_unpack_24(int32_t *dest, const uint8_t *src, unsigned num_samples, + bool reverse_endian); + +#endif diff --git a/src/pcm_prng.h b/src/pcm_prng.h index ea5983588..186ed9d0e 100644 --- a/src/pcm_prng.h +++ b/src/pcm_prng.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_resample.c b/src/pcm_resample.c index d1360d02a..4a7578e09 100644 --- a/src/pcm_resample.c +++ b/src/pcm_resample.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "pcm_resample_internal.h" #include "config.h" +#include "pcm_resample_internal.h" #ifdef HAVE_LIBSAMPLERATE #include "conf.h" @@ -62,16 +62,18 @@ void pcm_resample_deinit(struct pcm_resample_state *state) const int16_t * pcm_resample_16(struct pcm_resample_state *state, uint8_t channels, - unsigned src_rate, - const int16_t *src_buffer, size_t src_size, - unsigned dest_rate, - size_t *dest_size_r) + unsigned src_rate, const int16_t *src_buffer, size_t src_size, + unsigned dest_rate, size_t *dest_size_r, + GError **error_r) { #ifdef HAVE_LIBSAMPLERATE if (pcm_resample_lsr_enabled()) return pcm_resample_lsr_16(state, channels, src_rate, src_buffer, src_size, - dest_rate, dest_size_r); + dest_rate, dest_size_r, + error_r); +#else + (void)error_r; #endif return pcm_resample_fallback_16(state, channels, @@ -82,16 +84,18 @@ pcm_resample_16(struct pcm_resample_state *state, const int32_t * pcm_resample_32(struct pcm_resample_state *state, uint8_t channels, - unsigned src_rate, - const int32_t *src_buffer, size_t src_size, - unsigned dest_rate, - size_t *dest_size_r) + unsigned src_rate, const int32_t *src_buffer, size_t src_size, + unsigned dest_rate, size_t *dest_size_r, + GError **error_r) { #ifdef HAVE_LIBSAMPLERATE if (pcm_resample_lsr_enabled()) return pcm_resample_lsr_32(state, channels, src_rate, src_buffer, src_size, - dest_rate, dest_size_r); + dest_rate, dest_size_r, + error_r); +#else + (void)error_r; #endif return pcm_resample_fallback_32(state, channels, diff --git a/src/pcm_resample.h b/src/pcm_resample.h index 44720f7b2..24d17ff9b 100644 --- a/src/pcm_resample.h +++ b/src/pcm_resample.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,8 +20,8 @@ #ifndef MPD_PCM_RESAMPLE_H #define MPD_PCM_RESAMPLE_H +#include "check.h" #include "pcm_buffer.h" -#include "config.h" #include <stdint.h> #include <stddef.h> @@ -48,7 +48,7 @@ struct pcm_resample_state { uint8_t channels; } prev; - bool error; + int error; #endif struct pcm_buffer buffer; @@ -82,8 +82,8 @@ pcm_resample_16(struct pcm_resample_state *state, uint8_t channels, unsigned src_rate, const int16_t *src_buffer, size_t src_size, - unsigned dest_rate, - size_t *dest_size_r); + unsigned dest_rate, size_t *dest_size_r, + GError **error_r); /** * Resamples 32 bit PCM data. @@ -102,8 +102,8 @@ pcm_resample_32(struct pcm_resample_state *state, uint8_t channels, unsigned src_rate, const int32_t *src_buffer, size_t src_size, - unsigned dest_rate, - size_t *dest_size_r); + unsigned dest_rate, size_t *dest_size_r, + GError **error_r); /** * Resamples 24 bit PCM data. @@ -122,14 +122,14 @@ pcm_resample_24(struct pcm_resample_state *state, uint8_t channels, unsigned src_rate, const int32_t *src_buffer, size_t src_size, - unsigned dest_rate, - size_t *dest_size_r) + unsigned dest_rate, size_t *dest_size_r, + GError **error_r) { /* reuse the 32 bit code - the resampler code doesn't care if the upper 8 bits are actually used */ return pcm_resample_32(state, channels, src_rate, src_buffer, src_size, - dest_rate, dest_size_r); + dest_rate, dest_size_r, error_r); } #endif diff --git a/src/pcm_resample_fallback.c b/src/pcm_resample_fallback.c index 36af51ad0..0c75d8ba4 100644 --- a/src/pcm_resample_fallback.c +++ b/src/pcm_resample_fallback.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "pcm_resample_internal.h" #include <assert.h> -#include <glib.h> void pcm_resample_fallback_deinit(struct pcm_resample_state *state) @@ -74,8 +74,7 @@ const int32_t * pcm_resample_fallback_32(struct pcm_resample_state *state, uint8_t channels, unsigned src_rate, - const int32_t *src_buffer, - G_GNUC_UNUSED size_t src_size, + const int32_t *src_buffer, size_t src_size, unsigned dest_rate, size_t *dest_size_r) { diff --git a/src/pcm_resample_internal.h b/src/pcm_resample_internal.h index a10ba08cd..26acc809d 100644 --- a/src/pcm_resample_internal.h +++ b/src/pcm_resample_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,8 +27,8 @@ #ifndef MPD_PCM_RESAMPLE_INTERNAL_H #define MPD_PCM_RESAMPLE_INTERNAL_H +#include "check.h" #include "pcm_resample.h" -#include "config.h" #ifdef HAVE_LIBSAMPLERATE @@ -40,8 +40,8 @@ pcm_resample_lsr_16(struct pcm_resample_state *state, uint8_t channels, unsigned src_rate, const int16_t *src_buffer, size_t src_size, - unsigned dest_rate, - size_t *dest_size_r); + unsigned dest_rate, size_t *dest_size_r, + GError **error_r); const int32_t * pcm_resample_lsr_32(struct pcm_resample_state *state, @@ -49,8 +49,8 @@ pcm_resample_lsr_32(struct pcm_resample_state *state, unsigned src_rate, const int32_t *src_buffer, G_GNUC_UNUSED size_t src_size, - unsigned dest_rate, - size_t *dest_size_r); + unsigned dest_rate, size_t *dest_size_r, + GError **error_r); #endif diff --git a/src/pcm_resample_libsamplerate.c b/src/pcm_resample_libsamplerate.c index 6d019e892..99ca53da4 100644 --- a/src/pcm_resample_libsamplerate.c +++ b/src/pcm_resample_libsamplerate.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,9 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "pcm_resample_internal.h" #include "conf.h" -#include "config.h" #include <glib.h> @@ -30,6 +30,12 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "pcm" +static inline GQuark +libsamplerate_quark(void) +{ + return g_quark_from_static_string("libsamplerate"); +} + void pcm_resample_lsr_deinit(struct pcm_resample_state *state) { @@ -77,9 +83,10 @@ out: return convalgo; } -static void +static bool pcm_resample_set(struct pcm_resample_state *state, - uint8_t channels, unsigned src_rate, unsigned dest_rate) + uint8_t channels, unsigned src_rate, unsigned dest_rate, + GError **error_r) { static int convalgo = -1; int error; @@ -92,9 +99,9 @@ pcm_resample_set(struct pcm_resample_state *state, if (channels == state->prev.channels && src_rate == state->prev.src_rate && dest_rate == state->prev.dest_rate) - return; + return true; - state->error = false; + state->error = 0; state->prev.channels = channels; state->prev.src_rate = src_rate; state->prev.dest_rate = dest_rate; @@ -104,16 +111,18 @@ pcm_resample_set(struct pcm_resample_state *state, state->state = src_new(convalgo, channels, &error); if (!state->state) { - g_warning("cannot create new libsamplerate state: %s", - src_strerror(error)); - state->error = true; - return; + g_set_error(error_r, libsamplerate_quark(), state->error, + "libsamplerate initialization has failed: %s", + src_strerror(error)); + return false; } data->src_ratio = (double)dest_rate / (double)src_rate; g_debug("setting samplerate conversion ratio to %.2lf", data->src_ratio); src_set_ratio(state->state, data->src_ratio); + + return true; } const int16_t * @@ -121,9 +130,10 @@ pcm_resample_lsr_16(struct pcm_resample_state *state, uint8_t channels, unsigned src_rate, const int16_t *src_buffer, size_t src_size, - unsigned dest_rate, - size_t *dest_size_r) + unsigned dest_rate, size_t *dest_size_r, + GError **error_r) { + bool success; SRC_DATA *data = &state->data; size_t data_in_size; size_t data_out_size; @@ -132,11 +142,18 @@ pcm_resample_lsr_16(struct pcm_resample_state *state, assert((src_size % (sizeof(*src_buffer) * channels)) == 0); - pcm_resample_set(state, channels, src_rate, dest_rate); + success = pcm_resample_set(state, channels, src_rate, dest_rate, + error_r); + if (!success) + return NULL; /* there was an error previously, and nothing has changed */ - if (state->error) + if (state->error) { + g_set_error(error_r, libsamplerate_quark(), state->error, + "libsamplerate has failed: %s", + src_strerror(state->error)); return NULL; + } data->input_frames = src_size / sizeof(*src_buffer) / channels; data_in_size = data->input_frames * sizeof(float) * channels; @@ -151,9 +168,10 @@ pcm_resample_lsr_16(struct pcm_resample_state *state, error = src_process(state->state, data); if (error) { - g_warning("error processing samples with libsamplerate: %s", - src_strerror(error)); - state->error = true; + g_set_error(error_r, libsamplerate_quark(), error, + "libsamplerate has failed: %s", + src_strerror(error)); + state->error = error; return NULL; } @@ -191,9 +209,10 @@ pcm_resample_lsr_32(struct pcm_resample_state *state, uint8_t channels, unsigned src_rate, const int32_t *src_buffer, size_t src_size, - unsigned dest_rate, - size_t *dest_size_r) + unsigned dest_rate, size_t *dest_size_r, + GError **error_r) { + bool success; SRC_DATA *data = &state->data; size_t data_in_size; size_t data_out_size; @@ -202,11 +221,18 @@ pcm_resample_lsr_32(struct pcm_resample_state *state, assert((src_size % (sizeof(*src_buffer) * channels)) == 0); - pcm_resample_set(state, channels, src_rate, dest_rate); + success = pcm_resample_set(state, channels, src_rate, dest_rate, + error_r); + if (!success) + return NULL; /* there was an error previously, and nothing has changed */ - if (state->error) + if (state->error) { + g_set_error(error_r, libsamplerate_quark(), state->error, + "libsamplerate has failed: %s", + src_strerror(state->error)); return NULL; + } data->input_frames = src_size / sizeof(*src_buffer) / channels; data_in_size = data->input_frames * sizeof(float) * channels; @@ -221,9 +247,10 @@ pcm_resample_lsr_32(struct pcm_resample_state *state, error = src_process(state->state, data); if (error) { - g_warning("error processing samples with libsamplerate: %s", - src_strerror(error)); - state->error = true; + g_set_error(error_r, libsamplerate_quark(), error, + "libsamplerate has failed: %s", + src_strerror(error)); + state->error = error; return NULL; } diff --git a/src/pcm_utils.h b/src/pcm_utils.h index 93f414231..15f9e1b10 100644 --- a/src/pcm_utils.h +++ b/src/pcm_utils.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -38,4 +38,18 @@ pcm_range(int32_t sample, unsigned bits) return sample; } +/** + * Check if the value is within the range of the provided bit size, + * and caps it if necessary. + */ +static inline int64_t +pcm_range_64(int64_t sample, unsigned bits) +{ + if (G_UNLIKELY(sample < ((int64_t)-1 << (bits - 1)))) + return (int64_t)-1 << (bits - 1); + if (G_UNLIKELY(sample >= ((int64_t)1 << (bits - 1)))) + return ((int64_t)1 << (bits - 1)) - 1; + return sample; +} + #endif diff --git a/src/pcm_volume.c b/src/pcm_volume.c index 2a94c1890..240c779d8 100644 --- a/src/pcm_volume.c +++ b/src/pcm_volume.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "pcm_volume.h" #include "pcm_utils.h" #include "audio_format.h" @@ -113,6 +114,29 @@ pcm_volume_change_24(int32_t *buffer, unsigned num_samples, int volume) } } +static void +pcm_volume_change_32(int32_t *buffer, unsigned num_samples, int volume) +{ + while (num_samples > 0) { +#ifdef __i386__ + /* assembly version for i386 */ + int32_t sample = *buffer; + + *buffer++ = pcm_volume_sample_24(sample, volume, 0); +#else + /* portable version */ + int64_t sample = *buffer; + + sample = (sample * volume + pcm_volume_dither() + + PCM_VOLUME_1 / 2) + / PCM_VOLUME_1; + *buffer++ = pcm_range_64(sample, 32); +#endif + + --num_samples; + } +} + bool pcm_volume(void *buffer, int length, const struct audio_format *format, @@ -126,21 +150,26 @@ pcm_volume(void *buffer, int length, return true; } - switch (format->bits) { - case 8: + switch (format->format) { + case SAMPLE_FORMAT_S8: pcm_volume_change_8((int8_t *)buffer, length, volume); return true; - case 16: + case SAMPLE_FORMAT_S16: pcm_volume_change_16((int16_t *)buffer, length / 2, volume); return true; - case 24: + case SAMPLE_FORMAT_S24_P32: pcm_volume_change_24((int32_t*)buffer, length / 4, volume); return true; + case SAMPLE_FORMAT_S32: + pcm_volume_change_32((int32_t*)buffer, length / 4, + volume); + return true; + default: return false; } diff --git a/src/pcm_volume.h b/src/pcm_volume.h index 5cff35cb8..eb61e9526 100644 --- a/src/pcm_volume.h +++ b/src/pcm_volume.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/permission.c b/src/permission.c index 7df4e27fc..17b443e0a 100644 --- a/src/permission.c +++ b/src/permission.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "permission.h" #include "conf.h" +#include "mpd_error.h" #include <glib.h> @@ -58,7 +60,7 @@ static unsigned parsePermissions(const char *string) } else if (strcmp(temp, PERMISSION_ADMIN_STRING) == 0) { permission |= PERMISSION_ADMIN; } else { - g_error("unknown permission \"%s\"", temp); + MPD_ERROR("unknown permission \"%s\"", temp); } } @@ -89,7 +91,7 @@ void initPermissions(void) strchr(param->value, PERMISSION_PASSWORD_CHAR); if (separator == NULL) - g_error("\"%c\" not found in password string " + MPD_ERROR("\"%c\" not found in password string " "\"%s\", line %i", PERMISSION_PASSWORD_CHAR, param->value, param->line); @@ -111,7 +113,7 @@ void initPermissions(void) permission_default = parsePermissions(param->value); } -int getPermissionFromPassword(char *password, unsigned *permission) +int getPermissionFromPassword(char const* password, unsigned* permission) { bool found; gpointer key, value; diff --git a/src/permission.h b/src/permission.h index bad26aa3c..9b3a60a66 100644 --- a/src/permission.h +++ b/src/permission.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,7 +27,7 @@ #define PERMISSION_ADMIN 8 -int getPermissionFromPassword(char *password, unsigned *permission); +int getPermissionFromPassword(char const* password, unsigned* permission); void finishPermissions(void); diff --git a/src/pipe.c b/src/pipe.c index c9f0d159c..7e4b0d081 100644 --- a/src/pipe.c +++ b/src/pipe.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "pipe.h" #include "buffer.h" #include "chunk.h" diff --git a/src/pipe.h b/src/pipe.h index 2825b320a..7eabb6e54 100644 --- a/src/pipe.h +++ b/src/pipe.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/player_control.c b/src/player_control.c index ac4b006dd..a190bbd8b 100644 --- a/src/player_control.c +++ b/src/player_control.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,7 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "player_control.h" +#include "decoder_control.h" #include "path.h" #include "log.h" #include "tag.h" @@ -28,24 +30,41 @@ #include <assert.h> #include <stdio.h> +#include <math.h> struct player_control pc; +static void +pc_enqueue_song_locked(struct song *song); + void pc_init(unsigned buffer_chunks, unsigned int buffered_before_play) { pc.buffer_chunks = buffer_chunks; pc.buffered_before_play = buffered_before_play; - notify_init(&pc.notify); + + pc.mutex = g_mutex_new(); + pc.cond = g_cond_new(); + pc.command = PLAYER_COMMAND_NONE; pc.error = PLAYER_ERROR_NOERROR; pc.state = PLAYER_STATE_STOP; pc.cross_fade_seconds = 0; - pc.software_volume = PCM_VOLUME_1; + pc.mixramp_db = 0; + pc.mixramp_delay_seconds = nanf(""); } void pc_deinit(void) { - notify_deinit(&pc.notify); + g_cond_free(pc.cond); + g_mutex_free(pc.mutex); +} + +void +player_wait_decoder(struct decoder_control *dc) +{ + /* during this function, the decoder lock is held, because + we're waiting for the decoder thread */ + g_cond_wait(pc.cond, dc->mutex); } void @@ -57,27 +76,48 @@ pc_song_deleted(const struct song *song) } } -static void player_command(enum player_command cmd) +static void +player_command_wait_locked(void) +{ + while (pc.command != PLAYER_COMMAND_NONE) + g_cond_wait(main_cond, pc.mutex); +} + +static void +player_command_locked(enum player_command cmd) { assert(pc.command == PLAYER_COMMAND_NONE); pc.command = cmd; - while (pc.command != PLAYER_COMMAND_NONE) { - notify_signal(&pc.notify); - notify_wait(&main_notify); - } + player_signal(); + player_command_wait_locked(); +} + +static void +player_command(enum player_command cmd) +{ + player_lock(); + player_command_locked(cmd); + player_unlock(); } void -playerPlay(struct song *song) +pc_play(struct song *song) { assert(song != NULL); + player_lock(); + if (pc.state != PLAYER_STATE_STOP) - player_command(PLAYER_COMMAND_STOP); + player_command_locked(PLAYER_COMMAND_STOP); - pc.next_song = song; - player_command(PLAYER_COMMAND_PLAY); + assert(pc.next_song == NULL); + + pc_enqueue_song_locked(song); + + assert(pc.next_song == NULL); + + player_unlock(); idle_add(IDLE_PLAYER); } @@ -85,16 +125,26 @@ playerPlay(struct song *song) void pc_cancel(void) { player_command(PLAYER_COMMAND_CANCEL); + assert(pc.next_song == NULL); } -void playerWait(void) +void +pc_stop(void) { player_command(PLAYER_COMMAND_CLOSE_AUDIO); + assert(pc.next_song == NULL); idle_add(IDLE_PLAYER); } -void playerKill(void) +void +pc_update_audio(void) +{ + player_command(PLAYER_COMMAND_UPDATE_AUDIO); +} + +void +pc_kill(void) { assert(pc.thread != NULL); @@ -105,57 +155,86 @@ void playerKill(void) idle_add(IDLE_PLAYER); } -void playerPause(void) +void +pc_pause(void) +{ + player_lock(); + + if (pc.state != PLAYER_STATE_STOP) { + player_command_locked(PLAYER_COMMAND_PAUSE); + idle_add(IDLE_PLAYER); + } + + player_unlock(); +} + +static void +pc_pause_locked(void) { if (pc.state != PLAYER_STATE_STOP) { - player_command(PLAYER_COMMAND_PAUSE); + player_command_locked(PLAYER_COMMAND_PAUSE); idle_add(IDLE_PLAYER); } } -void playerSetPause(int pause_flag) +void +pc_set_pause(bool pause_flag) { + player_lock(); + switch (pc.state) { case PLAYER_STATE_STOP: break; case PLAYER_STATE_PLAY: if (pause_flag) - playerPause(); + pc_pause_locked(); break; + case PLAYER_STATE_PAUSE: if (!pause_flag) - playerPause(); + pc_pause_locked(); break; } -} -int getPlayerElapsedTime(void) -{ - return (int)(pc.elapsed_time + 0.5); + player_unlock(); } -unsigned long getPlayerBitRate(void) +void +pc_get_status(struct player_status *status) { - return pc.bit_rate; -} + player_lock(); + player_command_locked(PLAYER_COMMAND_REFRESH); -int getPlayerTotalTime(void) -{ - return (int)(pc.total_time + 0.5); + status->state = pc.state; + + if (pc.state != PLAYER_STATE_STOP) { + status->bit_rate = pc.bit_rate; + status->audio_format = pc.audio_format; + status->total_time = pc.total_time; + status->elapsed_time = pc.elapsed_time; + } + + player_unlock(); } -enum player_state getPlayerState(void) +enum player_state +pc_get_state(void) { return pc.state; } -void clearPlayerError(void) +void +pc_clear_error(void) { - pc.error = 0; + player_lock(); + pc.error = PLAYER_ERROR_NOERROR; + pc.errored_song = NULL; + player_unlock(); } -enum player_error getPlayerError(void) +enum player_error +pc_get_error(void) { return pc.error; } @@ -166,58 +245,63 @@ pc_errored_song_uri(void) return song_get_uri(pc.errored_song); } -char *getPlayerErrorStr(void) +char * +pc_get_error_message(void) { - /* static OK here, only one user in main task */ - static char error[MPD_PATH_MAX + 64]; /* still too much */ - static const size_t errorlen = sizeof(error); + char *error; char *uri; - *error = '\0'; /* likely */ - switch (pc.error) { case PLAYER_ERROR_NOERROR: - break; + return NULL; case PLAYER_ERROR_FILENOTFOUND: uri = pc_errored_song_uri(); - snprintf(error, errorlen, - "file \"%s\" does not exist or is inaccessible", uri); + error = g_strdup_printf("file \"%s\" does not exist or is inaccessible", uri); g_free(uri); - break; + return error; case PLAYER_ERROR_FILE: uri = pc_errored_song_uri(); - snprintf(error, errorlen, "problems decoding \"%s\"", uri); + error = g_strdup_printf("problems decoding \"%s\"", uri); g_free(uri); - break; + return error; case PLAYER_ERROR_AUDIO: - strcpy(error, "problems opening audio device"); - break; + return g_strdup("problems opening audio device"); case PLAYER_ERROR_SYSTEM: - strcpy(error, "system error occured"); - break; + return g_strdup("system error occured"); case PLAYER_ERROR_UNKTYPE: uri = pc_errored_song_uri(); - snprintf(error, errorlen, - "file type of \"%s\" is unknown", uri); + error = g_strdup_printf("file type of \"%s\" is unknown", uri); g_free(uri); - break; + return error; } - return *error ? error : NULL; + + assert(false); + return NULL; } -void -queueSong(struct song *song) +static void +pc_enqueue_song_locked(struct song *song) { assert(song != NULL); assert(pc.next_song == NULL); pc.next_song = song; - player_command(PLAYER_COMMAND_QUEUE); + player_command_locked(PLAYER_COMMAND_QUEUE); +} + +void +pc_enqueue_song(struct song *song) +{ + assert(song != NULL); + + player_lock(); + pc_enqueue_song_locked(song); + player_unlock(); } bool @@ -228,9 +312,11 @@ pc_seek(struct song *song, float seek_time) if (pc.state == PLAYER_STATE_STOP) return false; + player_lock(); pc.next_song = song; pc.seek_where = seek_time; - player_command(PLAYER_COMMAND_SEEK); + player_command_locked(PLAYER_COMMAND_SEEK); + player_unlock(); assert(pc.next_song == NULL); @@ -239,31 +325,52 @@ pc_seek(struct song *song, float seek_time) return true; } -float getPlayerCrossFade(void) +float +pc_get_cross_fade(void) { return pc.cross_fade_seconds; } -void setPlayerCrossFade(float crossFadeInSeconds) +void +pc_set_cross_fade(float cross_fade_seconds) +{ + if (cross_fade_seconds < 0) + cross_fade_seconds = 0; + pc.cross_fade_seconds = cross_fade_seconds; + + idle_add(IDLE_OPTIONS); +} + +float +pc_get_mixramp_db(void) +{ + return pc.mixramp_db; +} + +void +pc_set_mixramp_db(float mixramp_db) { - if (crossFadeInSeconds < 0) - crossFadeInSeconds = 0; - pc.cross_fade_seconds = crossFadeInSeconds; + pc.mixramp_db = mixramp_db; idle_add(IDLE_OPTIONS); } -void setPlayerSoftwareVolume(int volume) +float +pc_get_mixramp_delay(void) { - if (volume > PCM_VOLUME_1) - volume = PCM_VOLUME_1; - else if (volume < 0) - volume = 0; + return pc.mixramp_delay_seconds; +} - pc.software_volume = volume; +void +pc_set_mixramp_delay(float mixramp_delay_seconds) +{ + pc.mixramp_delay_seconds = mixramp_delay_seconds; + + idle_add(IDLE_OPTIONS); } -double getPlayerTotalPlayTime(void) +double +pc_get_total_play_time(void) { return pc.total_play_time; } diff --git a/src/player_control.h b/src/player_control.h index b1f7481cd..76c47609a 100644 --- a/src/player_control.h +++ b/src/player_control.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,6 +25,8 @@ #include <stdint.h> +struct decoder_control; + enum player_state { PLAYER_STATE_STOP = 0, PLAYER_STATE_PAUSE, @@ -35,11 +37,16 @@ enum player_command { PLAYER_COMMAND_NONE = 0, PLAYER_COMMAND_EXIT, PLAYER_COMMAND_STOP, - PLAYER_COMMAND_PLAY, PLAYER_COMMAND_PAUSE, PLAYER_COMMAND_SEEK, PLAYER_COMMAND_CLOSE_AUDIO, + /** + * At least one audio_output.enabled flag has been modified; + * commit those changes to the output threads. + */ + PLAYER_COMMAND_UPDATE_AUDIO, + /** player_control.next_song has been updated */ PLAYER_COMMAND_QUEUE, @@ -49,6 +56,12 @@ enum player_command { * stop */ PLAYER_COMMAND_CANCEL, + + /** + * Refresh status information in the #player_control struct, + * e.g. elapsed_time. + */ + PLAYER_COMMAND_REFRESH, }; enum player_error { @@ -60,6 +73,14 @@ enum player_error { PLAYER_ERROR_FILENOTFOUND, }; +struct player_status { + enum player_state state; + uint16_t bit_rate; + struct audio_format audio_format; + float total_time; + float elapsed_time; +}; + struct player_control { unsigned buffer_chunks; @@ -69,19 +90,29 @@ struct player_control { thread isn't running */ GThread *thread; - struct notify notify; - volatile enum player_command command; - volatile enum player_state state; - volatile enum player_error error; + /** + * This lock protects #command, #state, #error. + */ + GMutex *mutex; + + /** + * Trigger this object after you have modified #command. + */ + GCond *cond; + + enum player_command command; + enum player_state state; + enum player_error error; uint16_t bit_rate; struct audio_format audio_format; float total_time; float elapsed_time; - struct song *volatile next_song; - struct song *errored_song; - volatile double seek_where; + struct song *next_song; + const struct song *errored_song; + double seek_where; float cross_fade_seconds; - uint16_t software_volume; + float mixramp_db; + float mixramp_delay_seconds; double total_play_time; }; @@ -92,6 +123,67 @@ void pc_init(unsigned buffer_chunks, unsigned buffered_before_play); void pc_deinit(void); /** + * Locks the #player_control object. + */ +static inline void +player_lock(void) +{ + g_mutex_lock(pc.mutex); +} + +/** + * Unlocks the #player_control object. + */ +static inline void +player_unlock(void) +{ + g_mutex_unlock(pc.mutex); +} + +/** + * Waits for a signal on the #player_control object. This function is + * only valid in the player thread. The object must be locked prior + * to calling this function. + */ +static inline void +player_wait(void) +{ + g_cond_wait(pc.cond, pc.mutex); +} + +/** + * Waits for a signal on the #player_control object. This function is + * only valid in the player thread. The #decoder_control object must + * be locked prior to calling this function. + * + * Note the small difference to the player_wait() function! + */ +void +player_wait_decoder(struct decoder_control *dc); + +/** + * Signals the #player_control object. The object should be locked + * prior to calling this function. + */ +static inline void +player_signal(void) +{ + g_cond_signal(pc.cond); +} + +/** + * Signals the #player_control object. The object is temporarily + * locked by this function. + */ +static inline void +player_lock_signal(void) +{ + player_lock(); + player_signal(); + player_unlock(); +} + +/** * Call this function when the specified song pointer is about to be * invalidated. This makes sure that player_control.errored_song does * not point to an invalid pointer. @@ -100,37 +192,50 @@ void pc_song_deleted(const struct song *song); void -playerPlay(struct song *song); +pc_play(struct song *song); /** * see PLAYER_COMMAND_CANCEL */ void pc_cancel(void); -void playerSetPause(int pause_flag); - -void playerPause(void); +void +pc_set_pause(bool pause_flag); -void playerKill(void); +void +pc_pause(void); -int getPlayerTotalTime(void); +void +pc_kill(void); -int getPlayerElapsedTime(void); +void +pc_get_status(struct player_status *status); -unsigned long getPlayerBitRate(void); +enum player_state +pc_get_state(void); -enum player_state getPlayerState(void); +void +pc_clear_error(void); -void clearPlayerError(void); +/** + * Returns the human-readable message describing the last error during + * playback, NULL if no error occurred. The caller has to free the + * returned string. + */ +char * +pc_get_error_message(void); -char *getPlayerErrorStr(void); +enum player_error +pc_get_error(void); -enum player_error getPlayerError(void); +void +pc_stop(void); -void playerWait(void); +void +pc_update_audio(void); void -queueSong(struct song *song); +pc_enqueue_song(struct song *song); /** * Makes the player thread seek the specified song to a position. @@ -141,20 +246,25 @@ queueSong(struct song *song); bool pc_seek(struct song *song, float seek_time); -void setPlayerCrossFade(float crossFadeInSeconds); +void +pc_set_cross_fade(float cross_fade_seconds); + +float +pc_get_cross_fade(void); -float getPlayerCrossFade(void); +void +pc_set_mixramp_db(float mixramp_db); -void setPlayerSoftwareVolume(int volume); +float +pc_get_mixramp_db(void); -double getPlayerTotalPlayTime(void); +void +pc_set_mixramp_delay(float mixramp_delay_seconds); -static inline const struct audio_format * -player_get_audio_format(void) -{ - return &pc.audio_format; -} +float +pc_get_mixramp_delay(void); -void playerInit(void); +double +pc_get_total_play_time(void); #endif diff --git a/src/player_thread.c b/src/player_thread.c index d428484c7..2d8822eb0 100644 --- a/src/player_thread.c +++ b/src/player_thread.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "player_thread.h" #include "player_control.h" #include "decoder_control.h" @@ -33,6 +34,7 @@ #include "idle.h" #include "main.h" #include "buffer.h" +#include "mpd_error.h" #include <glib.h> @@ -46,6 +48,8 @@ enum xfade_state { }; struct player { + struct decoder_control *dc; + struct music_pipe *pipe; /** @@ -102,20 +106,46 @@ struct player { struct audio_format play_audio_format; /** - * Coefficient for converting a PCM buffer size into a time - * span. + * The time stamp of the chunk most recently sent to the + * output thread. This attribute is only used if + * audio_output_all_get_elapsed_time() didn't return a usable + * value; the output thread can estimate the elapsed time more + * precisly. */ - double size_to_time; + float elapsed_time; }; static struct music_buffer *player_buffer; -static void player_command_finished(void) +static void player_command_finished_locked(void) { assert(pc.command != PLAYER_COMMAND_NONE); pc.command = PLAYER_COMMAND_NONE; - notify_signal(&main_notify); + g_cond_signal(main_cond); +} + +static void player_command_finished(void) +{ + player_lock(); + player_command_finished_locked(); + player_unlock(); +} + +/** + * Start the decoder. + * + * Player lock is not held. + */ +static void +player_dc_start(struct player *player, struct music_pipe *pipe) +{ + struct decoder_control *dc = player->dc; + + assert(player->queued || pc.command == PLAYER_COMMAND_SEEK); + assert(pc.next_song != NULL); + + dc_start(dc, pc.next_song, player_buffer, pipe); } /** @@ -130,38 +160,41 @@ player_dc_at_current_song(const struct player *player) assert(player != NULL); assert(player->pipe != NULL); - return dc.pipe == player->pipe; + return player->dc->pipe == player->pipe; } /** - * Has the decoder already begun decoding the next song? - * - * Note: this function does not check if the decoder is already - * finished. + * Returns true if the decoder is decoding the next song (or has begun + * decoding it, or has finished doing it), and the player hasn't + * switched to that song yet. */ static bool player_dc_at_next_song(const struct player *player) { - return dc.pipe != NULL && !player_dc_at_current_song(player); + return player->dc->pipe != NULL && !player_dc_at_current_song(player); } /** * Stop the decoder and clears (and frees) its music pipe. + * + * Player lock is not held. */ static void player_dc_stop(struct player *player) { - dc_stop(&pc.notify); + struct decoder_control *dc = player->dc; - if (dc.pipe != NULL) { + dc_stop(dc); + + if (dc->pipe != NULL) { /* clear and free the decoder pipe */ - music_pipe_clear(dc.pipe, player_buffer); + music_pipe_clear(dc->pipe, player_buffer); - if (dc.pipe != player->pipe) - music_pipe_free(dc.pipe); + if (dc->pipe != player->pipe) + music_pipe_free(dc->pipe); - dc.pipe = NULL; + dc->pipe = NULL; } } @@ -169,35 +202,48 @@ player_dc_stop(struct player *player) * After the decoder has been started asynchronously, wait for the * "START" command to finish. The decoder may not be initialized yet, * i.e. there is no audio_format information yet. + * + * The player lock is not held. */ static bool player_wait_for_decoder(struct player *player) { - dc_command_wait(&pc.notify); + struct decoder_control *dc = player->dc; + + assert(player->queued || pc.command == PLAYER_COMMAND_SEEK); + assert(pc.next_song != NULL); + + player->queued = false; - if (decoder_has_failed()) { - assert(dc.next_song == NULL || dc.next_song->url != NULL); - pc.errored_song = dc.next_song; + if (decoder_lock_has_failed(dc)) { + player_lock(); + pc.errored_song = dc->song; pc.error = PLAYER_ERROR_FILE; pc.next_song = NULL; - player->queued = false; + player_unlock(); + return false; } - pc.total_time = pc.next_song->tag != NULL - ? pc.next_song->tag->time : 0; - pc.bit_rate = 0; - audio_format_clear(&pc.audio_format); - player->song = pc.next_song; - pc.next_song = NULL; - pc.elapsed_time = 0; - player->queued = false; + player->elapsed_time = 0.0; /* set the "starting" flag, which will be cleared by player_check_decoder_startup() */ player->decoder_starting = true; + player_lock(); + + /* update player_control's song information */ + pc.total_time = song_get_duration(pc.next_song); + pc.bit_rate = 0; + audio_format_clear(&pc.audio_format); + + /* clear the queued song */ + pc.next_song = NULL; + + player_unlock(); + /* call syncPlaylistWithQueue() in the main thread */ event_pipe_emit(PIPE_EVENT_PLAYLIST); @@ -205,54 +251,86 @@ player_wait_for_decoder(struct player *player) } /** + * Returns the real duration of the song, comprising the duration + * indicated by the decoder plugin. + */ +static double +real_song_duration(const struct song *song, double decoder_duration) +{ + assert(song != NULL); + + if (decoder_duration <= 0.0) + /* the decoder plugin didn't provide information; fall + back to song_get_duration() */ + return song_get_duration(song); + + if (song->end_ms > 0 && song->end_ms / 1000.0 < decoder_duration) + return (song->end_ms - song->start_ms) / 1000.0; + + return decoder_duration - song->start_ms / 1000.0; +} + +/** * The decoder has acknowledged the "START" command (see * player_wait_for_decoder()). This function checks if the decoder * initialization has completed yet. + * + * The player lock is not held. */ static bool player_check_decoder_startup(struct player *player) { + struct decoder_control *dc = player->dc; + assert(player->decoder_starting); - if (decoder_has_failed()) { + decoder_lock(dc); + + if (decoder_has_failed(dc)) { /* the decoder failed */ - assert(dc.next_song == NULL || dc.next_song->url != NULL); + decoder_unlock(dc); - pc.errored_song = dc.next_song; + player_lock(); + pc.errored_song = dc->song; pc.error = PLAYER_ERROR_FILE; + player_unlock(); return false; - } else if (!decoder_is_starting()) { + } else if (!decoder_is_starting(dc)) { /* the decoder is ready and ok */ + decoder_unlock(dc); + if (audio_format_defined(&player->play_audio_format) && !audio_output_all_wait(1)) /* the output devices havn't finished playing all chunks yet - wait for that */ return true; - pc.total_time = dc.total_time; - pc.audio_format = dc.in_audio_format; - player->play_audio_format = dc.out_audio_format; - player->size_to_time = - audioFormatSizeToTime(&dc.out_audio_format); + player_lock(); + pc.total_time = real_song_duration(dc->song, dc->total_time); + pc.audio_format = dc->in_audio_format; + player_unlock(); + + player->play_audio_format = dc->out_audio_format; player->decoder_starting = false; if (!player->paused && - !audio_output_all_open(&dc.out_audio_format, + !audio_output_all_open(&dc->out_audio_format, player_buffer)) { - char *uri = song_get_uri(dc.next_song); + char *uri = song_get_uri(dc->song); g_warning("problems opening audio device " "while playing \"%s\"", uri); g_free(uri); - assert(dc.next_song == NULL || dc.next_song->url != NULL); - pc.errored_song = dc.next_song; + player_lock(); pc.error = PLAYER_ERROR_AUDIO; /* pause: the user may resume playback as soon as an audio output becomes available */ pc.state = PLAYER_STATE_PAUSE; + player_unlock(); + player->paused = true; return true; } @@ -261,7 +339,8 @@ player_check_decoder_startup(struct player *player) } else { /* the decoder is not yet ready; wait some more */ - notify_wait(&pc.notify); + player_wait_decoder(dc); + decoder_unlock(dc); return true; } @@ -271,6 +350,8 @@ player_check_decoder_startup(struct player *player) * Sends a chunk of silence to the audio outputs. This is called when * there is not enough decoded data in the pipe yet, to prevent * underruns in the hardware buffers. + * + * The player lock is not held. */ static bool player_send_silence(struct player *player) @@ -294,6 +375,7 @@ player_send_silence(struct player *player) chunk->audio_format = player->play_audio_format; #endif + chunk->times = -1.0; /* undefined time stamp */ chunk->length = num_frames * frame_size; memset(chunk->data, 0, chunk->length); @@ -307,15 +389,19 @@ player_send_silence(struct player *player) /** * This is the handler for the #PLAYER_COMMAND_SEEK command. + * + * The player lock is not held. */ static bool player_seek_decoder(struct player *player) { + struct song *song = pc.next_song; + struct decoder_control *dc = player->dc; double where; bool ret; assert(pc.next_song != NULL); - if (decoder_current_song() != pc.next_song) { + if (decoder_current_song(dc) != song) { /* the decoder is already decoding the "next" song - stop it and start the previous song again */ @@ -326,7 +412,7 @@ static bool player_seek_decoder(struct player *player) music_pipe_clear(player->pipe, player_buffer); /* re-start the decoder */ - dc_start_async(pc.next_song, player->pipe); + player_dc_start(player, player->pipe); ret = player_wait_for_decoder(player); if (!ret) { /* decoder failure */ @@ -339,7 +425,7 @@ static bool player_seek_decoder(struct player *player) but it is the same song file; exchange the pipe */ music_pipe_clear(player->pipe, player_buffer); music_pipe_free(player->pipe); - player->pipe = dc.pipe; + player->pipe = dc->pipe; } pc.next_song = NULL; @@ -365,14 +451,15 @@ static bool player_seek_decoder(struct player *player) if (where < 0.0) where = 0.0; - ret = dc_seek(&pc.notify, where); + ret = dc_seek(dc, where + song->start_ms / 1000.0); if (!ret) { /* decoder failure */ player_command_finished(); return false; } - pc.elapsed_time = where; + player->elapsed_time = where; + player_command_finished(); player->xfade = XFADE_UNKNOWN; @@ -385,53 +472,73 @@ static bool player_seek_decoder(struct player *player) return true; } +/** + * Player lock must be held before calling. + */ static void player_process_command(struct player *player) { + G_GNUC_UNUSED struct decoder_control *dc = player->dc; + switch (pc.command) { case PLAYER_COMMAND_NONE: - case PLAYER_COMMAND_PLAY: case PLAYER_COMMAND_STOP: case PLAYER_COMMAND_EXIT: case PLAYER_COMMAND_CLOSE_AUDIO: break; + case PLAYER_COMMAND_UPDATE_AUDIO: + player_unlock(); + audio_output_all_enable_disable(); + player_lock(); + player_command_finished_locked(); + break; + case PLAYER_COMMAND_QUEUE: assert(pc.next_song != NULL); assert(!player->queued); assert(!player_dc_at_next_song(player)); player->queued = true; - player_command_finished(); + player_command_finished_locked(); break; case PLAYER_COMMAND_PAUSE: + player_unlock(); + player->paused = !player->paused; if (player->paused) { audio_output_all_pause(); + player_lock(); + pc.state = PLAYER_STATE_PAUSE; } else if (!audio_format_defined(&player->play_audio_format)) { /* the decoder hasn't provided an audio format yet - don't open the audio device yet */ + player_lock(); pc.state = PLAYER_STATE_PLAY; } else if (audio_output_all_open(&player->play_audio_format, player_buffer)) { /* unpaused, continue playing */ + player_lock(); + pc.state = PLAYER_STATE_PLAY; } else { /* the audio device has failed - rollback to pause mode */ - assert(dc.next_song == NULL || dc.next_song->url != NULL); - pc.errored_song = dc.next_song; pc.error = PLAYER_ERROR_AUDIO; player->paused = true; + + player_lock(); } - player_command_finished(); + player_command_finished_locked(); break; case PLAYER_COMMAND_SEEK: + player_unlock(); player_seek_decoder(player); + player_lock(); break; case PLAYER_COMMAND_CANCEL: @@ -443,80 +550,91 @@ static void player_process_command(struct player *player) return; } - if (player_dc_at_next_song(player)) + if (player_dc_at_next_song(player)) { /* the decoder is already decoding the song - stop it and reset the position */ + player_unlock(); player_dc_stop(player); + player_lock(); + } pc.next_song = NULL; player->queued = false; - player_command_finished(); + player_command_finished_locked(); + break; + + case PLAYER_COMMAND_REFRESH: + if (audio_format_defined(&player->play_audio_format) && + !player->paused) { + player_unlock(); + audio_output_all_check(); + player_lock(); + } + + pc.elapsed_time = audio_output_all_get_elapsed_time(); + if (pc.elapsed_time < 0.0) + pc.elapsed_time = player->elapsed_time; + + player_command_finished_locked(); break; } } +static void +update_song_tag(struct song *song, const struct tag *new_tag) +{ + struct tag *old_tag; + + if (song_is_file(song)) + /* don't update tags of local files, only remote + streams may change tags dynamically */ + return; + + old_tag = song->tag; + song->tag = tag_dup(new_tag); + + if (old_tag != NULL) + tag_free(old_tag); + + /* the main thread will update the playlist version when he + receives this event */ + event_pipe_emit(PIPE_EVENT_TAG); + + /* notify all clients that the tag of the current song has + changed */ + idle_add(IDLE_PLAYER); +} + /** * Plays a #music_chunk object (after applying software volume). If * it contains a (stream) tag, copy it to the current song, so MPD's * playlist reflects the new stream tag. + * + * Player lock is not held. */ static bool play_chunk(struct song *song, struct music_chunk *chunk, - const struct audio_format *format, double sizeToTime) + const struct audio_format *format) { - bool success; - assert(music_chunk_check_format(chunk, format)); - if (chunk->tag != NULL) { - if (!song_is_file(song)) { - /* always update the tag of remote streams */ - struct tag *old_tag = song->tag; - - song->tag = tag_dup(chunk->tag); - - if (old_tag != NULL) - tag_free(old_tag); - - /* the main thread will update the playlist - version when he receives this event */ - event_pipe_emit(PIPE_EVENT_TAG); - - /* notify all clients that the tag of the - current song has changed */ - idle_add(IDLE_PLAYER); - } - } + if (chunk->tag != NULL) + update_song_tag(song, chunk->tag); if (chunk->length == 0) { music_buffer_return(player_buffer, chunk); return true; } - pc.elapsed_time = chunk->times; pc.bit_rate = chunk->bit_rate; - /* apply software volume */ - - success = pcm_volume(chunk->data, chunk->length, - format, pc.software_volume); - if (!success) { - g_warning("pcm_volume() failed on %u:%u:%u", - format->sample_rate, format->bits, format->channels); - pc.errored_song = dc.current_song; - pc.error = PLAYER_ERROR_AUDIO; - return false; - } - /* send the chunk to the audio outputs */ - if (!audio_output_all_play(chunk)) { - pc.errored_song = dc.current_song; - pc.error = PLAYER_ERROR_AUDIO; + if (!audio_output_all_play(chunk)) return false; - } - pc.total_play_time += sizeToTime * chunk->length; + pc.total_play_time += (double)chunk->length / + audio_format_time_to_size(format); return true; } @@ -529,6 +647,7 @@ play_chunk(struct song *song, struct music_chunk *chunk, static bool play_next_chunk(struct player *player) { + struct decoder_control *dc = player->dc; struct music_chunk *chunk = NULL; unsigned cross_fade_position; bool success; @@ -544,7 +663,7 @@ play_next_chunk(struct player *player) <= player->cross_fade_chunks) { /* perform cross fade */ struct music_chunk *other_chunk = - music_pipe_shift(dc.pipe); + music_pipe_shift(dc->pipe); if (!player->cross_fading) { /* beginning of the cross fade - adjust @@ -558,6 +677,7 @@ play_next_chunk(struct player *player) if (other_chunk != NULL) { chunk = music_pipe_shift(player->pipe); assert(chunk != NULL); + assert(chunk->other == NULL); /* don't send the tags of the new song (which is being faded in) yet; postpone it until @@ -567,21 +687,30 @@ play_next_chunk(struct player *player) other_chunk->tag); other_chunk->tag = NULL; - cross_fade_apply(chunk, other_chunk, - &dc.out_audio_format, - cross_fade_position, - player->cross_fade_chunks); - music_buffer_return(player_buffer, other_chunk); + if (isnan(pc.mixramp_delay_seconds)) { + chunk->mix_ratio = ((float)cross_fade_position) + / player->cross_fade_chunks; + } else { + chunk->mix_ratio = nan(""); + } + + chunk->other = other_chunk; } else { /* there are not enough decoded chunks yet */ - if (decoder_is_idle()) { + + decoder_lock(dc); + + if (decoder_is_idle(dc)) { /* the decoder isn't running, abort cross fading */ + decoder_unlock(dc); + player->xfade = XFADE_DISABLED; } else { /* wait for the decoder */ - notify_signal(&dc.notify); - notify_wait(&pc.notify); + decoder_signal(dc); + player_wait_decoder(dc); + decoder_unlock(dc); return true; } @@ -603,27 +732,34 @@ play_next_chunk(struct player *player) /* play the current chunk */ - success = play_chunk(player->song, chunk, &player->play_audio_format, - player->size_to_time); + success = play_chunk(player->song, chunk, &player->play_audio_format); if (!success) { music_buffer_return(player_buffer, chunk); + player_lock(); + + pc.error = PLAYER_ERROR_AUDIO; + /* pause: the user may resume playback as soon as an audio output becomes available */ pc.state = PLAYER_STATE_PAUSE; player->paused = true; + player_unlock(); + return false; } /* this formula should prevent that the decoder gets woken up with each chunk; it is more efficient to make it decode a larger block at a time */ - if (!decoder_is_idle() && - music_pipe_size(dc.pipe) <= (pc.buffered_before_play + + decoder_lock(dc); + if (!decoder_is_idle(dc) && + music_pipe_size(dc->pipe) <= (pc.buffered_before_play + music_buffer_size(player_buffer) * 3) / 4) - notify_signal(&dc.notify); + decoder_signal(dc); + decoder_unlock(dc); return true; } @@ -633,15 +769,25 @@ play_next_chunk(struct player *player) * has consumed all chunks of the current song, and we should start * sending chunks from the next one. * + * The player lock is not held. + * * @return true on success, false on error (playback will be stopped) */ static bool player_song_border(struct player *player) { + char *uri; + player->xfade = XFADE_UNKNOWN; + uri = song_get_uri(player->song); + g_message("played \"%s\"", uri); + g_free(uri); + music_pipe_free(player->pipe); - player->pipe = dc.pipe; + player->pipe = player->dc->pipe; + + audio_output_all_song_border(); if (!player_wait_for_decoder(player)) return false; @@ -654,53 +800,59 @@ player_song_border(struct player *player) * basically a state machine, which multiplexes data between the * decoder thread and the output threads. */ -static void do_play(void) +static void do_play(struct decoder_control *dc) { struct player player = { + .dc = dc, .buffering = true, .decoder_starting = false, .paused = false, - .queued = false, + .queued = true, .song = NULL, .xfade = XFADE_UNKNOWN, .cross_fading = false, .cross_fade_chunks = 0, .cross_fade_tag = NULL, - .size_to_time = 0.0, + .elapsed_time = 0.0, }; + player_unlock(); + player.pipe = music_pipe_new(); - dc.buffer = player_buffer; - dc_start(&pc.notify, pc.next_song, player.pipe); + player_dc_start(&player, player.pipe); if (!player_wait_for_decoder(&player)) { player_dc_stop(&player); player_command_finished(); music_pipe_free(player.pipe); event_pipe_emit(PIPE_EVENT_PLAYLIST); + player_lock(); return; } - pc.elapsed_time = 0; + player_lock(); pc.state = PLAYER_STATE_PLAY; - player_command_finished(); + player_command_finished_locked(); while (true) { player_process_command(&player); if (pc.command == PLAYER_COMMAND_STOP || pc.command == PLAYER_COMMAND_EXIT || pc.command == PLAYER_COMMAND_CLOSE_AUDIO) { + player_unlock(); audio_output_all_cancel(); break; } + player_unlock(); + if (player.buffering) { /* buffering at the start of the song - wait until the buffer is large enough, to prevent stuttering on slow machines */ if (music_pipe_size(player.pipe) < pc.buffered_before_play && - !decoder_is_idle()) { + !decoder_lock_is_idle(dc)) { /* not enough decoded buffer space yet */ if (!player.paused && @@ -709,7 +861,11 @@ static void do_play(void) !player_send_silence(&player)) break; - notify_wait(&pc.notify); + decoder_lock(dc); + /* XXX race condition: check decoder again */ + player_wait_decoder(dc); + decoder_unlock(dc); + player_lock(); continue; } else { /* buffering is complete */ @@ -720,10 +876,19 @@ static void do_play(void) if (player.decoder_starting) { /* wait until the decoder is initialized completely */ bool success; + const struct song *song; success = player_check_decoder_startup(&player); if (!success) break; + + /* seek to the beginning of the range */ + song = decoder_current_song(dc); + if (song != NULL && song->start_ms > 0 && + !dc_seek(dc, song->start_ms / 1000.0)) + player_dc_stop(&player); + + player_lock(); continue; } @@ -731,31 +896,35 @@ static void do_play(void) /* music_pipe_check_format(&play_audio_format, player.next_song_chunk, - &dc.out_audio_format); + &dc->out_audio_format); */ #endif - if (decoder_is_idle() && player.queued) { + if (decoder_lock_is_idle(dc) && player.queued && + dc->pipe == player.pipe) { /* the decoder has finished the current song; make it decode the next song */ - assert(pc.next_song != NULL); - assert(!player_dc_at_next_song(&player)); - dc.pipe = NULL; + assert(dc->pipe == NULL || dc->pipe == player.pipe); - player.queued = false; - dc_start_async(pc.next_song, music_pipe_new()); + player_dc_start(&player, music_pipe_new()); } if (player_dc_at_next_song(&player) && player.xfade == XFADE_UNKNOWN && - !decoder_is_starting()) { + !decoder_lock_is_starting(dc)) { /* enable cross fading in this song? if yes, calculate how many chunks will be required for it */ player.cross_fade_chunks = - cross_fade_calc(pc.cross_fade_seconds, dc.total_time, - &dc.out_audio_format, + cross_fade_calc(pc.cross_fade_seconds, dc->total_time, + pc.mixramp_db, + pc.mixramp_delay_seconds, + dc->replay_gain_db, + dc->replay_gain_prev_db, + dc->mixramp_start, + dc->mixramp_prev_end, + &dc->out_audio_format, &player.play_audio_format, music_buffer_size(player_buffer) - pc.buffered_before_play); @@ -768,9 +937,13 @@ static void do_play(void) player.xfade = XFADE_DISABLED; } - if (player.paused) - notify_wait(&pc.notify); - else if (!music_pipe_empty(player.pipe)) { + if (player.paused) { + player_lock(); + + if (pc.command == PLAYER_COMMAND_NONE) + player_wait(); + continue; + } else if (!music_pipe_empty(player.pipe)) { /* at least one music chunk is ready - send it to the audio output */ @@ -787,12 +960,16 @@ static void do_play(void) if (!player_song_border(&player)) break; - } else if (decoder_is_idle()) { + } else if (decoder_lock_is_idle(dc)) { /* check the size of the pipe again, because the decoder thread may have added something since we last checked */ - if (music_pipe_empty(player.pipe)) + if (music_pipe_empty(player.pipe)) { + /* wait for the hardware to finish + playback */ + audio_output_all_drain(); break; + } } else { /* the decoder is too busy and hasn't provided new PCM data in time: send silence (if the @@ -800,11 +977,8 @@ static void do_play(void) if (!player_send_silence(&player)) break; } - } - if (player.queued) { - assert(pc.next_song != NULL); - pc.next_song = NULL; + player_lock(); } player_dc_stop(&player); @@ -815,38 +989,61 @@ static void do_play(void) if (player.cross_fade_tag != NULL) tag_free(player.cross_fade_tag); + player_lock(); + + if (player.queued) { + assert(pc.next_song != NULL); + pc.next_song = NULL; + } + pc.state = PLAYER_STATE_STOP; + + player_unlock(); + event_pipe_emit(PIPE_EVENT_PLAYLIST); + + player_lock(); } static gpointer player_task(G_GNUC_UNUSED gpointer arg) { - decoder_thread_start(); + struct decoder_control dc; + + dc_init(&dc); + decoder_thread_start(&dc); player_buffer = music_buffer_new(pc.buffer_chunks); + player_lock(); + while (1) { switch (pc.command) { - case PLAYER_COMMAND_PLAY: case PLAYER_COMMAND_QUEUE: assert(pc.next_song != NULL); - do_play(); + do_play(&dc); break; case PLAYER_COMMAND_STOP: + player_unlock(); audio_output_all_cancel(); + player_lock(); + /* fall through */ case PLAYER_COMMAND_SEEK: case PLAYER_COMMAND_PAUSE: pc.next_song = NULL; - player_command_finished(); + player_command_finished_locked(); break; case PLAYER_COMMAND_CLOSE_AUDIO: - audio_output_all_close(); - player_command_finished(); + player_unlock(); + + audio_output_all_release(); + + player_lock(); + player_command_finished_locked(); #ifndef NDEBUG /* in the DEBUG build, check for leaked @@ -858,25 +1055,39 @@ static gpointer player_task(G_GNUC_UNUSED gpointer arg) break; + case PLAYER_COMMAND_UPDATE_AUDIO: + player_unlock(); + audio_output_all_enable_disable(); + player_lock(); + player_command_finished_locked(); + break; + case PLAYER_COMMAND_EXIT: - dc_quit(); + player_unlock(); + + dc_quit(&dc); + dc_deinit(&dc); audio_output_all_close(); music_buffer_free(player_buffer); + player_command_finished(); - g_thread_exit(NULL); - break; + return NULL; case PLAYER_COMMAND_CANCEL: pc.next_song = NULL; - player_command_finished(); + player_command_finished_locked(); + break; + + case PLAYER_COMMAND_REFRESH: + /* no-op when not playing */ + player_command_finished_locked(); break; case PLAYER_COMMAND_NONE: - notify_wait(&pc.notify); + player_wait(); break; } } - return NULL; } void player_create(void) @@ -887,5 +1098,5 @@ void player_create(void) pc.thread = g_thread_create(player_task, NULL, true, &e); if (pc.thread == NULL) - g_error("Failed to spawn player task: %s", e->message); + MPD_ERROR("Failed to spawn player task: %s", e->message); } diff --git a/src/player_thread.h b/src/player_thread.h index 51ad28fcc..e645b1d09 100644 --- a/src/player_thread.h +++ b/src/player_thread.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist.c b/src/playlist.c index 660dd6a83..4a1e54814 100644 --- a/src/playlist.c +++ b/src/playlist.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "playlist_internal.h" #include "playlist_save.h" #include "player_control.h" @@ -34,7 +35,8 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "playlist" -void playlistVersionChange(struct playlist *playlist) +void +playlist_increment_version_all(struct playlist *playlist) { queue_modify_all(&playlist->queue); idle_add(IDLE_PLAYLIST); @@ -61,16 +63,12 @@ playlist_init(struct playlist *playlist) playlist->queued = -1; playlist->current = -1; - - playlist->prev_elapsed = g_timer_new(); } void playlist_finish(struct playlist *playlist) { queue_finish(&playlist->queue); - - g_timer_destroy(playlist->prev_elapsed); } /** @@ -91,28 +89,34 @@ playlist_queue_song_order(struct playlist *playlist, unsigned order) g_debug("queue song %i:\"%s\"", playlist->queued, uri); g_free(uri); - queueSong(song); + pc_enqueue_song(song); } /** - * Check if the player thread has already started playing the "queued" - * song. + * Called if the player thread has started playing the "queued" song. */ -static void syncPlaylistWithQueue(struct playlist *playlist) +static void +playlist_song_started(struct playlist *playlist) { - if (pc.next_song == NULL && playlist->queued != -1) { - /* queued song has started: copy queued to current, - and notify the clients */ + assert(pc.next_song == NULL); + assert(playlist->queued >= -1); - int current = playlist->current; - playlist->current = playlist->queued; - playlist->queued = -1; + /* queued song has started: copy queued to current, + and notify the clients */ - if(playlist->queue.consume) - deleteFromPlaylist(playlist, queue_order_to_position(&playlist->queue, current)); + int current = playlist->current; + playlist->current = playlist->queued; + playlist->queued = -1; - idle_add(IDLE_PLAYER); + /* Pause if we are in single mode. */ + if(playlist->queue.single && !playlist->queue.repeat) { + pc_set_pause(true); } + + if(playlist->queue.consume) + playlist_delete(playlist, queue_order_to_position(&playlist->queue, current)); + + idle_add(IDLE_PLAYER); } const struct song * @@ -179,7 +183,7 @@ playlist_update_queued_song(struct playlist *playlist, const struct song *prev) } void -playPlaylistOrderNumber(struct playlist *playlist, int orderNum) +playlist_play_order(struct playlist *playlist, int orderNum) { struct song *song; char *uri; @@ -193,38 +197,45 @@ playPlaylistOrderNumber(struct playlist *playlist, int orderNum) g_debug("play %i:\"%s\"", orderNum, uri); g_free(uri); - playerPlay(song); + pc_play(song); playlist->current = orderNum; } static void -playPlaylistIfPlayerStopped(struct playlist *playlist); +playlist_resume_playback(struct playlist *playlist); /** * This is the "PLAYLIST" event handler. It is invoked by the player * thread whenever it requests a new queued song, or when it exits. */ -void syncPlayerAndPlaylist(struct playlist *playlist) +void +playlist_sync(struct playlist *playlist) { if (!playlist->playing) /* this event has reached us out of sync: we aren't playing anymore; ignore the event */ return; - if (getPlayerState() == PLAYER_STATE_STOP) + player_lock(); + enum player_state pc_state = pc_get_state(); + const struct song *pc_next_song = pc.next_song; + player_unlock(); + + if (pc_state == PLAYER_STATE_STOP) /* the player thread has stopped: check if playback should be restarted with the next song. That can happen if the playlist isn't filling the queue fast enough */ - playPlaylistIfPlayerStopped(playlist); + playlist_resume_playback(playlist); else { /* check if the player thread has already started playing the queued song */ - syncPlaylistWithQueue(playlist); + if (pc_next_song == NULL && playlist->queued != -1) + playlist_song_started(playlist); /* make sure the queued song is always set (if possible) */ - if (pc.next_song == NULL) + if (pc.next_song == NULL && playlist->queued < 0) playlist_update_queued_song(playlist, NULL); } } @@ -234,14 +245,14 @@ void syncPlayerAndPlaylist(struct playlist *playlist) * decide whether to re-start playback */ static void -playPlaylistIfPlayerStopped(struct playlist *playlist) +playlist_resume_playback(struct playlist *playlist) { enum player_error error; assert(playlist->playing); - assert(getPlayerState() == PLAYER_STATE_STOP); + assert(pc_get_state() == PLAYER_STATE_STOP); - error = getPlayerError(); + error = pc_get_error(); if (error == PLAYER_ERROR_NOERROR) playlist->error_count = 0; else @@ -252,37 +263,38 @@ playPlaylistIfPlayerStopped(struct playlist *playlist) playlist->error_count >= queue_length(&playlist->queue)) /* too many errors, or critical error: stop playback */ - stopPlaylist(playlist); + playlist_stop(playlist); else /* continue playback at the next song */ - nextSongInPlaylist(playlist); + playlist_next(playlist); } bool -getPlaylistRepeatStatus(const struct playlist *playlist) +playlist_get_repeat(const struct playlist *playlist) { return playlist->queue.repeat; } bool -getPlaylistRandomStatus(const struct playlist *playlist) +playlist_get_random(const struct playlist *playlist) { return playlist->queue.random; } bool -getPlaylistSingleStatus(const struct playlist *playlist) +playlist_get_single(const struct playlist *playlist) { return playlist->queue.single; } bool -getPlaylistConsumeStatus(const struct playlist *playlist) +playlist_get_consume(const struct playlist *playlist) { return playlist->queue.consume; } -void setPlaylistRepeatStatus(struct playlist *playlist, bool status) +void +playlist_set_repeat(struct playlist *playlist, bool status) { if (status == playlist->queue.repeat) return; @@ -297,7 +309,8 @@ void setPlaylistRepeatStatus(struct playlist *playlist, bool status) idle_add(IDLE_OPTIONS); } -static void orderPlaylist(struct playlist *playlist) +static void +playlist_order(struct playlist *playlist) { if (playlist->current >= 0) /* update playlist.current, order==position now */ @@ -307,7 +320,8 @@ static void orderPlaylist(struct playlist *playlist) queue_restore_order(&playlist->queue); } -void setPlaylistSingleStatus(struct playlist *playlist, bool status) +void +playlist_set_single(struct playlist *playlist, bool status) { if (status == playlist->queue.single) return; @@ -322,7 +336,8 @@ void setPlaylistSingleStatus(struct playlist *playlist, bool status) idle_add(IDLE_OPTIONS); } -void setPlaylistConsumeStatus(struct playlist *playlist, bool status) +void +playlist_set_consume(struct playlist *playlist, bool status) { if (status == playlist->queue.consume) return; @@ -331,7 +346,8 @@ void setPlaylistConsumeStatus(struct playlist *playlist, bool status) idle_add(IDLE_OPTIONS); } -void setPlaylistRandomStatus(struct playlist *playlist, bool status) +void +playlist_set_random(struct playlist *playlist, bool status) { const struct song *queued; @@ -366,14 +382,15 @@ void setPlaylistRandomStatus(struct playlist *playlist, bool status) } else playlist->current = -1; } else - orderPlaylist(playlist); + playlist_order(playlist); playlist_update_queued_song(playlist, queued); idle_add(IDLE_OPTIONS); } -int getPlaylistCurrentSong(const struct playlist *playlist) +int +playlist_get_current_song(const struct playlist *playlist) { if (playlist->current >= 0) return queue_order_to_position(&playlist->queue, @@ -382,19 +399,15 @@ int getPlaylistCurrentSong(const struct playlist *playlist) return -1; } -int getPlaylistNextSong(const struct playlist *playlist) +int +playlist_get_next_song(const struct playlist *playlist) { if (playlist->current >= 0) { - if (playlist->queue.single == 1) - { - if (playlist->queue.repeat == 1) - return queue_order_to_position(&playlist->queue, - playlist->current); - else - return -1; - } - if (playlist->current + 1 < (int)queue_length(&playlist->queue)) + if (playlist->queue.single == 1 && playlist->queue.repeat == 1) + return queue_order_to_position(&playlist->queue, + playlist->current); + else if (playlist->current + 1 < (int)queue_length(&playlist->queue)) return queue_order_to_position(&playlist->queue, playlist->current + 1); else if (playlist->queue.repeat == 1) @@ -405,19 +418,19 @@ int getPlaylistNextSong(const struct playlist *playlist) } unsigned long -getPlaylistVersion(const struct playlist *playlist) +playlist_get_version(const struct playlist *playlist) { return playlist->queue.version; } int -getPlaylistLength(const struct playlist *playlist) +playlist_get_length(const struct playlist *playlist) { return queue_length(&playlist->queue); } unsigned -getPlaylistSongId(const struct playlist *playlist, unsigned song) +playlist_get_song_id(const struct playlist *playlist, unsigned song) { return queue_position_to_id(&playlist->queue, song); } diff --git a/src/playlist.h b/src/playlist.h index 57b2450fa..3ba90ff91 100644 --- a/src/playlist.h +++ b/src/playlist.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,7 +23,6 @@ #include "queue.h" #include <stdbool.h> -#include <stdio.h> #define PLAYLIST_COMMENT '#' @@ -82,21 +81,16 @@ struct playlist { * This variable is only valid if #playing is true. */ int queued; - - /** - * This timer tracks the time elapsed since the last "prev" - * command. If that is less than one second ago, "prev" jumps - * to the previous song instead of rewinding the current song. - */ - GTimer *prev_elapsed; }; /** the global playlist object */ extern struct playlist g_playlist; -void initPlaylist(void); +void +playlist_global_init(void); -void finishPlaylist(void); +void +playlist_global_finish(void); void playlist_init(struct playlist *playlist); @@ -116,11 +110,8 @@ playlist_get_queue(const struct playlist *playlist) return &playlist->queue; } -void readPlaylistState(FILE *); - -void savePlaylistState(FILE *); - -void clearPlaylist(struct playlist *playlist); +void +playlist_clear(struct playlist *playlist); #ifndef WIN32 /** @@ -133,90 +124,111 @@ playlist_append_file(struct playlist *playlist, const char *path, int uid, #endif enum playlist_result -addToPlaylist(struct playlist *playlist, const char *file, unsigned *added_id); +playlist_append_uri(struct playlist *playlist, const char *file, + unsigned *added_id); enum playlist_result -addSongToPlaylist(struct playlist *playlist, +playlist_append_song(struct playlist *playlist, struct song *song, unsigned *added_id); enum playlist_result -deleteFromPlaylist(struct playlist *playlist, unsigned song); +playlist_delete(struct playlist *playlist, unsigned song); + +/** + * Deletes a range of songs from the playlist. + * + * @param start the position of the first song to delete + * @param end the position after the last song to delete + */ +enum playlist_result +playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end); enum playlist_result -deleteFromPlaylistById(struct playlist *playlist, unsigned song); +playlist_delete_id(struct playlist *playlist, unsigned song); -void stopPlaylist(struct playlist *playlist); +void +playlist_stop(struct playlist *playlist); enum playlist_result -playPlaylist(struct playlist *playlist, int song); +playlist_play(struct playlist *playlist, int song); enum playlist_result -playPlaylistById(struct playlist *playlist, int song); +playlist_play_id(struct playlist *playlist, int song); -void nextSongInPlaylist(struct playlist *playlist); +void +playlist_next(struct playlist *playlist); -void syncPlayerAndPlaylist(struct playlist *playlist); +void +playlist_sync(struct playlist *playlist); -void previousSongInPlaylist(struct playlist *playlist); +void +playlist_previous(struct playlist *playlist); -void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end); +void +playlist_shuffle(struct playlist *playlist, unsigned start, unsigned end); void -deleteASongFromPlaylist(struct playlist *playlist, const struct song *song); +playlist_delete_song(struct playlist *playlist, const struct song *song); enum playlist_result -moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end, int to); +playlist_move_range(struct playlist *playlist, unsigned start, unsigned end, int to); enum playlist_result -moveSongInPlaylistById(struct playlist *playlist, unsigned id, int to); +playlist_move_id(struct playlist *playlist, unsigned id, int to); enum playlist_result -swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2); +playlist_swap_songs(struct playlist *playlist, unsigned song1, unsigned song2); enum playlist_result -swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2); +playlist_swap_songs_id(struct playlist *playlist, unsigned id1, unsigned id2); bool -getPlaylistRepeatStatus(const struct playlist *playlist); +playlist_get_repeat(const struct playlist *playlist); -void setPlaylistRepeatStatus(struct playlist *playlist, bool status); +void +playlist_set_repeat(struct playlist *playlist, bool status); bool -getPlaylistRandomStatus(const struct playlist *playlist); +playlist_get_random(const struct playlist *playlist); -void setPlaylistRandomStatus(struct playlist *playlist, bool status); +void +playlist_set_random(struct playlist *playlist, bool status); bool -getPlaylistSingleStatus(const struct playlist *playlist); +playlist_get_single(const struct playlist *playlist); -void setPlaylistSingleStatus(struct playlist *playlist, bool status); +void +playlist_set_single(struct playlist *playlist, bool status); bool -getPlaylistConsumeStatus(const struct playlist *playlist); +playlist_get_consume(const struct playlist *playlist); -void setPlaylistConsumeStatus(struct playlist *playlist, bool status); +void +playlist_set_consume(struct playlist *playlist, bool status); -int getPlaylistCurrentSong(const struct playlist *playlist); +int +playlist_get_current_song(const struct playlist *playlist); -int getPlaylistNextSong(const struct playlist *playlist); +int +playlist_get_next_song(const struct playlist *playlist); unsigned -getPlaylistSongId(const struct playlist *playlist, unsigned song); +playlist_get_song_id(const struct playlist *playlist, unsigned song); -int getPlaylistLength(const struct playlist *playlist); +int +playlist_get_length(const struct playlist *playlist); unsigned long -getPlaylistVersion(const struct playlist *playlist); +playlist_get_version(const struct playlist *playlist); enum playlist_result -seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time); +playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time); enum playlist_result -seekSongInPlaylistById(struct playlist *playlist, +playlist_seek_song_id(struct playlist *playlist, unsigned id, float seek_time); -void playlistVersionChange(struct playlist *playlist); - -int is_valid_playlist_name(const char *utf8path); +void +playlist_increment_version_all(struct playlist *playlist); #endif diff --git a/src/playlist/asx_playlist_plugin.c b/src/playlist/asx_playlist_plugin.c new file mode 100644 index 000000000..39513e710 --- /dev/null +++ b/src/playlist/asx_playlist_plugin.c @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist/asx_playlist_plugin.h" +#include "playlist_plugin.h" +#include "input_stream.h" +#include "song.h" +#include "tag.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "asx" + +/** + * This is the state object for the GLib XML parser. + */ +struct asx_parser { + /** + * The list of songs (in reverse order because that's faster + * while adding). + */ + GSList *songs; + + /** + * The current position in the XML file. + */ + enum { + ROOT, ENTRY, + } state; + + /** + * The current tag within the "entry" element. This is only + * valid if state==ENTRY. TAG_NUM_OF_ITEM_TYPES means there + * is no (known) tag. + */ + enum tag_type tag; + + /** + * The current song. It is allocated after the "location" + * element. + */ + struct song *song; +}; + +static const gchar * +get_attribute(const gchar **attribute_names, const gchar **attribute_values, + const gchar *name) +{ + for (unsigned i = 0; attribute_names[i] != NULL; ++i) + if (g_ascii_strcasecmp(attribute_names[i], name) == 0) + return attribute_values[i]; + + return NULL; +} + +static void +asx_start_element(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + struct asx_parser *parser = user_data; + + switch (parser->state) { + case ROOT: + if (g_ascii_strcasecmp(element_name, "entry") == 0) { + parser->state = ENTRY; + parser->song = song_remote_new("asx:"); + parser->tag = TAG_NUM_OF_ITEM_TYPES; + } + + break; + + case ENTRY: + if (g_ascii_strcasecmp(element_name, "ref") == 0) { + const gchar *href = get_attribute(attribute_names, + attribute_values, + "href"); + if (href != NULL) { + /* create new song object, and copy + the existing tag over; we cannot + replace the existing song's URI, + because that attribute is + immutable */ + struct song *song = song_remote_new(href); + + if (parser->song != NULL) { + song->tag = parser->song->tag; + parser->song->tag = NULL; + song_free(parser->song); + } + + parser->song = song; + } + } else if (g_ascii_strcasecmp(element_name, "author") == 0) + /* is that correct? or should it be COMPOSER + or PERFORMER? */ + parser->tag = TAG_ARTIST; + else if (g_ascii_strcasecmp(element_name, "title") == 0) + parser->tag = TAG_TITLE; + + break; + } +} + +static void +asx_end_element(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + struct asx_parser *parser = user_data; + + switch (parser->state) { + case ROOT: + break; + + case ENTRY: + if (g_ascii_strcasecmp(element_name, "entry") == 0) { + if (strcmp(parser->song->uri, "asx:") != 0) + parser->songs = g_slist_prepend(parser->songs, + parser->song); + else + song_free(parser->song); + + parser->state = ROOT; + } else + parser->tag = TAG_NUM_OF_ITEM_TYPES; + + break; + } +} + +static void +asx_text(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *text, gsize text_len, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + struct asx_parser *parser = user_data; + + switch (parser->state) { + case ROOT: + break; + + case ENTRY: + if (parser->tag != TAG_NUM_OF_ITEM_TYPES) { + if (parser->song->tag == NULL) + parser->song->tag = tag_new(); + tag_add_item_n(parser->song->tag, parser->tag, + text, text_len); + } + + break; + } +} + +static const GMarkupParser asx_parser = { + .start_element = asx_start_element, + .end_element = asx_end_element, + .text = asx_text, +}; + +static void +song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) +{ + struct song *song = data; + + song_free(song); +} + +static void +asx_parser_destroy(gpointer data) +{ + struct asx_parser *parser = data; + + if (parser->state >= ENTRY) + song_free(parser->song); + + g_slist_foreach(parser->songs, song_free_callback, NULL); + g_slist_free(parser->songs); +} + +/* + * The playlist object + * + */ + +struct asx_playlist { + struct playlist_provider base; + + GSList *songs; +}; + +static struct playlist_provider * +asx_open_stream(struct input_stream *is) +{ + struct asx_parser parser = { + .songs = NULL, + .state = ROOT, + }; + struct asx_playlist *playlist; + GMarkupParseContext *context; + char buffer[1024]; + size_t nbytes; + bool success; + GError *error = NULL; + + /* parse the ASX XML file */ + + context = g_markup_parse_context_new(&asx_parser, + G_MARKUP_TREAT_CDATA_AS_TEXT, + &parser, asx_parser_destroy); + + while (true) { + nbytes = input_stream_read(is, buffer, sizeof(buffer), &error); + if (nbytes == 0) { + if (error != NULL) { + g_markup_parse_context_free(context); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + + break; + } + + success = g_markup_parse_context_parse(context, buffer, nbytes, + &error); + if (!success) { + g_warning("XML parser failed: %s", error->message); + g_error_free(error); + g_markup_parse_context_free(context); + return NULL; + } + } + + success = g_markup_parse_context_end_parse(context, &error); + if (!success) { + g_warning("XML parser failed: %s", error->message); + g_error_free(error); + g_markup_parse_context_free(context); + return NULL; + } + + /* create a #asx_playlist object from the parsed song list */ + + playlist = g_new(struct asx_playlist, 1); + playlist_provider_init(&playlist->base, &asx_playlist_plugin); + playlist->songs = g_slist_reverse(parser.songs); + parser.songs = NULL; + + g_markup_parse_context_free(context); + + return &playlist->base; +} + +static void +asx_close(struct playlist_provider *_playlist) +{ + struct asx_playlist *playlist = (struct asx_playlist *)_playlist; + + g_slist_foreach(playlist->songs, song_free_callback, NULL); + g_slist_free(playlist->songs); + g_free(playlist); +} + +static struct song * +asx_read(struct playlist_provider *_playlist) +{ + struct asx_playlist *playlist = (struct asx_playlist *)_playlist; + struct song *song; + + if (playlist->songs == NULL) + return NULL; + + song = playlist->songs->data; + playlist->songs = g_slist_remove(playlist->songs, song); + + return song; +} + +static const char *const asx_suffixes[] = { + "asx", + NULL +}; + +static const char *const asx_mime_types[] = { + "video/x-ms-asf", + NULL +}; + +const struct playlist_plugin asx_playlist_plugin = { + .name = "asx", + + .open_stream = asx_open_stream, + .close = asx_close, + .read = asx_read, + + .suffixes = asx_suffixes, + .mime_types = asx_mime_types, +}; diff --git a/src/playlist/asx_playlist_plugin.h b/src/playlist/asx_playlist_plugin.h new file mode 100644 index 000000000..7ce91aa41 --- /dev/null +++ b/src/playlist/asx_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_ASX_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_ASX_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin asx_playlist_plugin; + +#endif diff --git a/src/playlist/cue_playlist_plugin.c b/src/playlist/cue_playlist_plugin.c new file mode 100644 index 000000000..b22712bc7 --- /dev/null +++ b/src/playlist/cue_playlist_plugin.c @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist/cue_playlist_plugin.h" +#include "playlist_plugin.h" +#include "tag.h" +#include "song.h" +#include "cue/cue_tag.h" + +#include <glib.h> +#include <libcue/libcue.h> +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cue" + +struct cue_playlist { + struct playlist_provider base; + + struct Cd *cd; + + unsigned next; +}; + +static struct playlist_provider * +cue_playlist_open_uri(const char *uri) +{ + struct cue_playlist *playlist; + FILE *file; + struct Cd *cd; + + file = fopen(uri, "rt"); + if (file == NULL) + return NULL; + + cd = cue_parse_file(file); + fclose(file); + if (cd == NULL) + return NULL; + + playlist = g_new(struct cue_playlist, 1); + playlist_provider_init(&playlist->base, &cue_playlist_plugin); + playlist->cd = cd; + playlist->next = 1; + + return &playlist->base; +} + +static void +cue_playlist_close(struct playlist_provider *_playlist) +{ + struct cue_playlist *playlist = (struct cue_playlist *)_playlist; + + cd_delete(playlist->cd); + g_free(playlist); +} + +static struct song * +cue_playlist_read(struct playlist_provider *_playlist) +{ + struct cue_playlist *playlist = (struct cue_playlist *)_playlist; + struct Track *track; + struct tag *tag; + const char *filename; + struct song *song; + + track = cd_get_track(playlist->cd, playlist->next); + if (track == NULL) + return NULL; + + tag = cue_tag(playlist->cd, playlist->next); + if (tag == NULL) + return NULL; + + ++playlist->next; + + filename = track_get_filename(track); + if (*filename == 0 || filename[0] == '.' || + strchr(filename, '/') != NULL) { + /* unsafe characters found, bail out */ + tag_free(tag); + return NULL; + } + + song = song_remote_new(filename); + song->tag = tag; + song->start_ms = ((track_get_start(track) + + track_get_index(track, 1) + - track_get_zero_pre(track)) * 1000) / 75; + + /* append pregap of the next track to the end of this one */ + track = cd_get_track(playlist->cd, playlist->next); + if (track != NULL) + song->end_ms = ((track_get_start(track) + + track_get_index(track, 1) + - track_get_zero_pre(track)) * 1000) / 75; + else + song->end_ms = 0; + + return song; +} + +static const char *const cue_playlist_suffixes[] = { + "cue", + NULL +}; + +static const char *const cue_playlist_mime_types[] = { + "application/x-cue", + NULL +}; + +const struct playlist_plugin cue_playlist_plugin = { + .name = "cue", + + .open_uri = cue_playlist_open_uri, + .close = cue_playlist_close, + .read = cue_playlist_read, + + .suffixes = cue_playlist_suffixes, + .mime_types = cue_playlist_mime_types, +}; diff --git a/src/playlist/cue_playlist_plugin.h b/src/playlist/cue_playlist_plugin.h new file mode 100644 index 000000000..c89ec55c5 --- /dev/null +++ b/src/playlist/cue_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_CUE_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_CUE_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin cue_playlist_plugin; + +#endif diff --git a/src/playlist/extm3u_playlist_plugin.c b/src/playlist/extm3u_playlist_plugin.c new file mode 100644 index 000000000..9a04aa066 --- /dev/null +++ b/src/playlist/extm3u_playlist_plugin.c @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist/extm3u_playlist_plugin.h" +#include "playlist_plugin.h" +#include "text_input_stream.h" +#include "uri.h" +#include "song.h" +#include "tag.h" + +#include <glib.h> + +#include <string.h> +#include <stdlib.h> + +struct extm3u_playlist { + struct playlist_provider base; + + struct text_input_stream *tis; +}; + +static struct playlist_provider * +extm3u_open_stream(struct input_stream *is) +{ + struct extm3u_playlist *playlist; + const char *line; + + playlist = g_new(struct extm3u_playlist, 1); + playlist->tis = text_input_stream_new(is); + + line = text_input_stream_read(playlist->tis); + if (line == NULL || strcmp(line, "#EXTM3U") != 0) { + /* no EXTM3U header: fall back to the plain m3u + plugin */ + text_input_stream_free(playlist->tis); + g_free(playlist); + return NULL; + } + + playlist_provider_init(&playlist->base, &extm3u_playlist_plugin); + return &playlist->base; +} + +static void +extm3u_close(struct playlist_provider *_playlist) +{ + struct extm3u_playlist *playlist = (struct extm3u_playlist *)_playlist; + + text_input_stream_free(playlist->tis); + g_free(playlist); +} + +/** + * Parse a EXTINF line. + * + * @param line the rest of the input line after the colon + */ +static struct tag * +extm3u_parse_tag(const char *line) +{ + long duration; + char *endptr; + const char *name; + struct tag *tag; + + duration = strtol(line, &endptr, 10); + if (endptr[0] != ',') + /* malformed line */ + return NULL; + + if (duration < 0) + /* 0 means unknown duration */ + duration = 0; + + name = g_strchug(endptr + 1); + if (*name == 0 && duration == 0) + /* no information available; don't allocate a tag + object */ + return NULL; + + tag = tag_new(); + tag->time = duration; + + /* unfortunately, there is no real specification for the + EXTM3U format, so we must assume that the string after the + comma is opaque, and is just the song name*/ + if (*name != 0) + tag_add_item(tag, TAG_NAME, name); + + return tag; +} + +static struct song * +extm3u_read(struct playlist_provider *_playlist) +{ + struct extm3u_playlist *playlist = (struct extm3u_playlist *)_playlist; + struct tag *tag = NULL; + const char *line; + struct song *song; + + do { + line = text_input_stream_read(playlist->tis); + if (line == NULL) { + if (tag != NULL) + tag_free(tag); + return NULL; + } + + if (g_str_has_prefix(line, "#EXTINF:")) { + if (tag != NULL) + tag_free(tag); + tag = extm3u_parse_tag(line + 8); + continue; + } + + while (*line != 0 && g_ascii_isspace(*line)) + ++line; + } while (line[0] == '#' || *line == 0); + + song = song_remote_new(line); + song->tag = tag; + return song; +} + +static const char *const extm3u_suffixes[] = { + "m3u", + NULL +}; + +static const char *const extm3u_mime_types[] = { + "audio/x-mpegurl", + NULL +}; + +const struct playlist_plugin extm3u_playlist_plugin = { + .name = "extm3u", + + .open_stream = extm3u_open_stream, + .close = extm3u_close, + .read = extm3u_read, + + .suffixes = extm3u_suffixes, + .mime_types = extm3u_mime_types, +}; diff --git a/src/playlist/extm3u_playlist_plugin.h b/src/playlist/extm3u_playlist_plugin.h new file mode 100644 index 000000000..fa726c5f6 --- /dev/null +++ b/src/playlist/extm3u_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_EXTM3U_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_EXTM3U_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin extm3u_playlist_plugin; + +#endif diff --git a/src/playlist/flac_playlist_plugin.c b/src/playlist/flac_playlist_plugin.c new file mode 100644 index 000000000..9d66fb331 --- /dev/null +++ b/src/playlist/flac_playlist_plugin.c @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist/flac_playlist_plugin.h" +#include "playlist_plugin.h" +#include "tag.h" +#include "song.h" +#include "decoder/flac_metadata.h" + +#include <FLAC/metadata.h> + +#include <glib.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "flac" + +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + +struct flac_playlist { + struct playlist_provider base; + + char *uri; + + FLAC__StreamMetadata *cuesheet; + FLAC__StreamMetadata streaminfo; + + unsigned next_track; +}; + +static struct playlist_provider * +flac_playlist_open_uri(const char *uri) +{ + if (!g_path_is_absolute(uri)) + /* only local files supported */ + return NULL; + + FLAC__StreamMetadata *cuesheet; + if (!FLAC__metadata_get_cuesheet(uri, &cuesheet)) + return NULL; + + struct flac_playlist *playlist = g_new(struct flac_playlist, 1); + playlist_provider_init(&playlist->base, &flac_playlist_plugin); + + if (!FLAC__metadata_get_streaminfo(uri, &playlist->streaminfo)) { + FLAC__metadata_object_delete(playlist->cuesheet); + g_free(playlist); + return NULL; + } + + playlist->uri = g_strdup(uri); + playlist->cuesheet = cuesheet; + playlist->next_track = 0; + + return &playlist->base; +} + +static void +flac_playlist_close(struct playlist_provider *_playlist) +{ + struct flac_playlist *playlist = (struct flac_playlist *)_playlist; + + g_free(playlist->uri); + FLAC__metadata_object_delete(playlist->cuesheet); + g_free(playlist); +} + +static struct song * +flac_playlist_read(struct playlist_provider *_playlist) +{ + struct flac_playlist *playlist = (struct flac_playlist *)_playlist; + const FLAC__StreamMetadata_CueSheet *cs = + &playlist->cuesheet->data.cue_sheet; + + /* find the next audio track */ + + while (playlist->next_track < cs->num_tracks && + (cs->tracks[playlist->next_track].number > cs->num_tracks || + cs->tracks[playlist->next_track].type != 0)) + ++playlist->next_track; + + if (playlist->next_track >= cs->num_tracks) + return NULL; + + FLAC__uint64 start = cs->tracks[playlist->next_track].offset; + ++playlist->next_track; + FLAC__uint64 end = playlist->next_track < cs->num_tracks + ? cs->tracks[playlist->next_track].offset + : playlist->streaminfo.data.stream_info.total_samples; + + struct song *song = song_file_new(playlist->uri, NULL); + song->start_ms = start * 1000 / + playlist->streaminfo.data.stream_info.sample_rate; + song->end_ms = end * 1000 / + playlist->streaminfo.data.stream_info.sample_rate; + + char track[16]; + g_snprintf(track, sizeof(track), "%u", playlist->next_track); + song->tag = flac_tag_load(playlist->uri, track); + if (song->tag == NULL) + song->tag = tag_new(); + + song->tag->time = end > start + ? ((end - start - 1 + + playlist->streaminfo.data.stream_info.sample_rate) / + playlist->streaminfo.data.stream_info.sample_rate) + : 0; + + tag_clear_items_by_type(song->tag, TAG_TRACK); + tag_add_item(song->tag, TAG_TRACK, track); + + return song; +} + +static const char *const flac_playlist_suffixes[] = { + "flac", + NULL +}; + +static const char *const flac_playlist_mime_types[] = { + "application/flac", + "application/x-flac", + "audio/flac", + "audio/x-flac", + NULL +}; + +const struct playlist_plugin flac_playlist_plugin = { + .name = "flac", + + .open_uri = flac_playlist_open_uri, + .close = flac_playlist_close, + .read = flac_playlist_read, + + .suffixes = flac_playlist_suffixes, + .mime_types = flac_playlist_mime_types, +}; + +#else /* FLAC_API_VERSION_CURRENT <= 7 */ + +static bool +flac_playlist_init(G_GNUC_UNUSED const struct config_param *param) +{ + /* this libFLAC version does not support embedded CUE sheets; + disable this plugin */ + return false; +} + +const struct playlist_plugin flac_playlist_plugin = { + .name = "flac", + .init = flac_playlist_init, +}; + +#endif diff --git a/src/playlist/flac_playlist_plugin.h b/src/playlist/flac_playlist_plugin.h new file mode 100644 index 000000000..7b141264f --- /dev/null +++ b/src/playlist/flac_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_FLAC_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_FLAC_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin flac_playlist_plugin; + +#endif diff --git a/src/playlist/lastfm_playlist_plugin.c b/src/playlist/lastfm_playlist_plugin.c new file mode 100644 index 000000000..afb3979d9 --- /dev/null +++ b/src/playlist/lastfm_playlist_plugin.c @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist/lastfm_playlist_plugin.h" +#include "playlist_plugin.h" +#include "playlist_list.h" +#include "conf.h" +#include "uri.h" +#include "song.h" +#include "input_stream.h" +#include "glib_compat.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +struct lastfm_playlist { + struct playlist_provider base; + + struct input_stream *is; + + struct playlist_provider *xspf; +}; + +static struct { + char *user; + char *md5; +} lastfm_config; + +static bool +lastfm_init(const struct config_param *param) +{ + const char *user = config_get_block_string(param, "user", NULL); + const char *passwd = config_get_block_string(param, "password", NULL); + + if (user == NULL || passwd == NULL) { + g_debug("disabling the last.fm playlist plugin " + "because account is not configured"); + return false; + } + + lastfm_config.user = g_uri_escape_string(user, NULL, false); + +#if GLIB_CHECK_VERSION(2,16,0) + if (strlen(passwd) != 32) + lastfm_config.md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5, + passwd, strlen(passwd)); + else +#endif + lastfm_config.md5 = g_strdup(passwd); + + return true; +} + +static void +lastfm_finish(void) +{ + g_free(lastfm_config.user); + g_free(lastfm_config.md5); +} + +/** + * Simple data fetcher. + * @param url path or url of data to fetch. + * @return data fetched, or NULL on error. Must be freed with g_free. + */ +static char * +lastfm_get(const char *url) +{ + struct input_stream *input_stream; + GError *error = NULL; + int ret; + char buffer[4096]; + size_t length = 0, nbytes; + + input_stream = input_stream_open(url, &error); + if (input_stream == NULL) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + + return NULL; + } + + while (!input_stream->ready) { + ret = input_stream_buffer(input_stream, &error); + if (ret < 0) { + input_stream_close(input_stream); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + } + + do { + nbytes = input_stream_read(input_stream, buffer + length, + sizeof(buffer) - length, &error); + if (nbytes == 0) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + + if (input_stream_eof(input_stream)) + break; + + /* I/O error */ + input_stream_close(input_stream); + return NULL; + } + + length += nbytes; + } while (length < sizeof(buffer)); + + input_stream_close(input_stream); + return g_strndup(buffer, length); +} + +/** + * Ini-style value fetcher. + * @param response data through which to search. + * @param name name of value to search for. + * @return value for param name in param reponse or NULL on error. Free with g_free. + */ +static char * +lastfm_find(const char *response, const char *name) +{ + size_t name_length = strlen(name); + + while (true) { + const char *eol = strchr(response, '\n'); + if (eol == NULL) + return NULL; + + if (strncmp(response, name, name_length) == 0 && + response[name_length] == '=') { + response += name_length + 1; + return g_strndup(response, eol - response); + } + + response = eol + 1; + } +} + +static struct playlist_provider * +lastfm_open_uri(const char *uri) +{ + struct lastfm_playlist *playlist; + GError *error = NULL; + char *p, *q, *response, *session; + + /* handshake */ + + p = g_strconcat("http://ws.audioscrobbler.com/radio/handshake.php?" + "version=1.1.1&platform=linux&" + "username=", lastfm_config.user, "&" + "passwordmd5=", lastfm_config.md5, "&" + "debug=0&partner=", NULL); + response = lastfm_get(p); + g_free(p); + if (response == NULL) + return NULL; + + /* extract session id from response */ + + session = lastfm_find(response, "session"); + g_free(response); + if (session == NULL) { + g_warning("last.fm handshake failed"); + return NULL; + } + + q = g_uri_escape_string(session, NULL, false); + g_free(session); + session = q; + + g_debug("session='%s'", session); + + /* "adjust" last.fm radio */ + + if (strlen(uri) > 9) { + char *escaped_uri; + + escaped_uri = g_uri_escape_string(uri, NULL, false); + + p = g_strconcat("http://ws.audioscrobbler.com/radio/adjust.php?" + "session=", session, "&url=", escaped_uri, "&debug=0", + NULL); + g_free(escaped_uri); + + response = lastfm_get(p); + g_free(response); + g_free(p); + + if (response == NULL) { + g_free(session); + return NULL; + } + } + + /* create the playlist object */ + + playlist = g_new(struct lastfm_playlist, 1); + playlist_provider_init(&playlist->base, &lastfm_playlist_plugin); + + /* open the last.fm playlist */ + + p = g_strconcat("http://ws.audioscrobbler.com/radio/xspf.php?" + "sk=", session, "&discovery=0&desktop=1.5.1.31879", + NULL); + g_free(session); + + playlist->is = input_stream_open(p, &error); + g_free(p); + + if (playlist->is == NULL) { + if (error != NULL) { + g_warning("Failed to load XSPF playlist: %s", + error->message); + g_error_free(error); + } else + g_warning("Failed to load XSPF playlist"); + g_free(playlist); + return NULL; + } + + while (!playlist->is->ready) { + int ret = input_stream_buffer(playlist->is, &error); + if (ret < 0) { + input_stream_close(playlist->is); + g_free(playlist); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + + if (ret == 0) + /* nothing was buffered - wait */ + g_usleep(10000); + } + + /* last.fm does not send a MIME type, we have to fake it here + :-( */ + g_free(playlist->is->mime); + playlist->is->mime = g_strdup("application/xspf+xml"); + + /* parse the XSPF playlist */ + + playlist->xspf = playlist_list_open_stream(playlist->is, NULL); + if (playlist->xspf == NULL) { + input_stream_close(playlist->is); + g_free(playlist); + g_warning("Failed to parse XSPF playlist"); + return NULL; + } + + return &playlist->base; +} + +static void +lastfm_close(struct playlist_provider *_playlist) +{ + struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist; + + playlist_plugin_close(playlist->xspf); + input_stream_close(playlist->is); + g_free(playlist); +} + +static struct song * +lastfm_read(struct playlist_provider *_playlist) +{ + struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist; + + return playlist_plugin_read(playlist->xspf); +} + +static const char *const lastfm_schemes[] = { + "lastfm", + NULL +}; + +const struct playlist_plugin lastfm_playlist_plugin = { + .name = "lastfm", + + .init = lastfm_init, + .finish = lastfm_finish, + .open_uri = lastfm_open_uri, + .close = lastfm_close, + .read = lastfm_read, + + .schemes = lastfm_schemes, +}; diff --git a/src/playlist/lastfm_playlist_plugin.h b/src/playlist/lastfm_playlist_plugin.h new file mode 100644 index 000000000..363377c21 --- /dev/null +++ b/src/playlist/lastfm_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_LASTFM_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_LASTFM_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin lastfm_playlist_plugin; + +#endif diff --git a/src/playlist/m3u_playlist_plugin.c b/src/playlist/m3u_playlist_plugin.c new file mode 100644 index 000000000..221c27277 --- /dev/null +++ b/src/playlist/m3u_playlist_plugin.c @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist/m3u_playlist_plugin.h" +#include "playlist_plugin.h" +#include "text_input_stream.h" +#include "uri.h" +#include "song.h" + +#include <glib.h> + +struct m3u_playlist { + struct playlist_provider base; + + struct text_input_stream *tis; +}; + +static struct playlist_provider * +m3u_open_stream(struct input_stream *is) +{ + struct m3u_playlist *playlist = g_new(struct m3u_playlist, 1); + + playlist_provider_init(&playlist->base, &m3u_playlist_plugin); + playlist->tis = text_input_stream_new(is); + + return &playlist->base; +} + +static void +m3u_close(struct playlist_provider *_playlist) +{ + struct m3u_playlist *playlist = (struct m3u_playlist *)_playlist; + + text_input_stream_free(playlist->tis); + g_free(playlist); +} + +static struct song * +m3u_read(struct playlist_provider *_playlist) +{ + struct m3u_playlist *playlist = (struct m3u_playlist *)_playlist; + const char *line; + + do { + line = text_input_stream_read(playlist->tis); + if (line == NULL) + return NULL; + + while (*line != 0 && g_ascii_isspace(*line)) + ++line; + } while (line[0] == '#' || *line == 0); + + return song_remote_new(line); +} + +static const char *const m3u_suffixes[] = { + "m3u", + NULL +}; + +static const char *const m3u_mime_types[] = { + "audio/x-mpegurl", + NULL +}; + +const struct playlist_plugin m3u_playlist_plugin = { + .name = "m3u", + + .open_stream = m3u_open_stream, + .close = m3u_close, + .read = m3u_read, + + .suffixes = m3u_suffixes, + .mime_types = m3u_mime_types, +}; diff --git a/src/playlist/m3u_playlist_plugin.h b/src/playlist/m3u_playlist_plugin.h new file mode 100644 index 000000000..98dcc4729 --- /dev/null +++ b/src/playlist/m3u_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_M3U_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_M3U_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin m3u_playlist_plugin; + +#endif diff --git a/src/playlist/pls_playlist_plugin.c b/src/playlist/pls_playlist_plugin.c new file mode 100644 index 000000000..2a36f12f5 --- /dev/null +++ b/src/playlist/pls_playlist_plugin.c @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist/pls_playlist_plugin.h" +#include "playlist_plugin.h" +#include "input_stream.h" +#include "uri.h" +#include "song.h" +#include "tag.h" +#include <glib.h> + +struct pls_playlist { + struct playlist_provider base; + + GSList *songs; +}; + +static void pls_parser(GKeyFile *keyfile, struct pls_playlist *playlist) +{ + gchar *key; + gchar *value; + int length; + GError *error = NULL; + int num_entries = g_key_file_get_integer(keyfile, "playlist", + "NumberOfEntries", &error); + if (error) { + g_debug("Invalid PLS file: '%s'", error->message); + g_error_free(error); + error = NULL; + + /* Hack to work around shoutcast failure to comform to spec */ + num_entries = g_key_file_get_integer(keyfile, "playlist", + "numberofentries", &error); + if (error) { + g_error_free(error); + error = NULL; + } + } + + while (num_entries > 0) { + struct song *song; + key = g_strdup_printf("File%i", num_entries); + value = g_key_file_get_string(keyfile, "playlist", key, + &error); + if(error) { + g_debug("Invalid PLS entry %s: '%s'",key, error->message); + g_error_free(error); + g_free(key); + return; + } + g_free(key); + + song = song_remote_new(value); + g_free(value); + + key = g_strdup_printf("Title%i", num_entries); + value = g_key_file_get_string(keyfile, "playlist", key, + &error); + g_free(key); + if(error == NULL && value){ + if (song->tag == NULL) + song->tag = tag_new(); + tag_add_item(song->tag,TAG_TITLE, value); + } + /* Ignore errors? Most likely value not present */ + if(error) g_error_free(error); + error = NULL; + g_free(value); + + key = g_strdup_printf("Length%i", num_entries); + length = g_key_file_get_integer(keyfile, "playlist", key, + &error); + g_free(key); + if(error == NULL && length > 0){ + if (song->tag == NULL) + song->tag = tag_new(); + song->tag->time = length; + } + /* Ignore errors? Most likely value not present */ + if(error) g_error_free(error); + error = NULL; + + playlist->songs = g_slist_prepend(playlist->songs, song); + num_entries--; + } + +} + +static struct playlist_provider * +pls_open_stream(struct input_stream *is) +{ + GError *error = NULL; + size_t nbytes; + char buffer[1024]; + bool success; + GKeyFile *keyfile; + struct pls_playlist *playlist; + GString *kf_data = g_string_new(""); + + do { + nbytes = input_stream_read(is, buffer, sizeof(buffer), &error); + if (nbytes == 0) { + if (error != NULL) { + g_string_free(kf_data, TRUE); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + + break; + } + + kf_data = g_string_append_len(kf_data, buffer,nbytes); + /* Limit to 64k */ + } while(kf_data->len < 65536); + + if (kf_data->len == 0) { + g_warning("KeyFile parser failed: No Data"); + g_string_free(kf_data, TRUE); + return NULL; + } + + keyfile = g_key_file_new(); + success = g_key_file_load_from_data(keyfile, + kf_data->str, kf_data->len, + G_KEY_FILE_NONE, &error); + + g_string_free(kf_data, TRUE); + + if (!success) { + g_warning("KeyFile parser failed: %s", error->message); + g_error_free(error); + g_key_file_free(keyfile); + return NULL; + } + + playlist = g_new(struct pls_playlist, 1); + playlist_provider_init(&playlist->base, &pls_playlist_plugin); + playlist->songs = NULL; + + pls_parser(keyfile, playlist); + + g_key_file_free(keyfile); + return &playlist->base; +} + + +static void +song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) +{ + struct song *song = data; + + song_free(song); +} + +static void +pls_close(struct playlist_provider *_playlist) +{ + struct pls_playlist *playlist = (struct pls_playlist *)_playlist; + + g_slist_foreach(playlist->songs, song_free_callback, NULL); + g_slist_free(playlist->songs); + + g_free(playlist); + +} + +static struct song * +pls_read(struct playlist_provider *_playlist) +{ + struct pls_playlist *playlist = (struct pls_playlist *)_playlist; + struct song *song; + + if (playlist->songs == NULL) + return NULL; + + song = playlist->songs->data; + playlist->songs = g_slist_remove(playlist->songs, song); + + return song; +} + +static const char *const pls_suffixes[] = { + "pls", + NULL +}; + +static const char *const pls_mime_types[] = { + "audio/x-scpls", + NULL +}; + +const struct playlist_plugin pls_playlist_plugin = { + .name = "pls", + + .open_stream = pls_open_stream, + .close = pls_close, + .read = pls_read, + + .suffixes = pls_suffixes, + .mime_types = pls_mime_types, +}; diff --git a/src/playlist/pls_playlist_plugin.h b/src/playlist/pls_playlist_plugin.h new file mode 100644 index 000000000..c3bcf3f05 --- /dev/null +++ b/src/playlist/pls_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_PLS_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_PLS_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin pls_playlist_plugin; + +#endif diff --git a/src/playlist/rss_playlist_plugin.c b/src/playlist/rss_playlist_plugin.c new file mode 100644 index 000000000..b5787bb68 --- /dev/null +++ b/src/playlist/rss_playlist_plugin.c @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist/rss_playlist_plugin.h" +#include "playlist_plugin.h" +#include "input_stream.h" +#include "song.h" +#include "tag.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "rss" + +/** + * This is the state object for the GLib XML parser. + */ +struct rss_parser { + /** + * The list of songs (in reverse order because that's faster + * while adding). + */ + GSList *songs; + + /** + * The current position in the XML file. + */ + enum { + ROOT, ITEM, + } state; + + /** + * The current tag within the "entry" element. This is only + * valid if state==ITEM. TAG_NUM_OF_ITEM_TYPES means there + * is no (known) tag. + */ + enum tag_type tag; + + /** + * The current song. It is allocated after the "location" + * element. + */ + struct song *song; +}; + +static const gchar * +get_attribute(const gchar **attribute_names, const gchar **attribute_values, + const gchar *name) +{ + for (unsigned i = 0; attribute_names[i] != NULL; ++i) + if (g_ascii_strcasecmp(attribute_names[i], name) == 0) + return attribute_values[i]; + + return NULL; +} + +static void +rss_start_element(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + struct rss_parser *parser = user_data; + + switch (parser->state) { + case ROOT: + if (g_ascii_strcasecmp(element_name, "item") == 0) { + parser->state = ITEM; + parser->song = song_remote_new("rss:"); + parser->tag = TAG_NUM_OF_ITEM_TYPES; + } + + break; + + case ITEM: + if (g_ascii_strcasecmp(element_name, "enclosure") == 0) { + const gchar *href = get_attribute(attribute_names, + attribute_values, + "url"); + if (href != NULL) { + /* create new song object, and copy + the existing tag over; we cannot + replace the existing song's URI, + because that attribute is + immutable */ + struct song *song = song_remote_new(href); + + if (parser->song != NULL) { + song->tag = parser->song->tag; + parser->song->tag = NULL; + song_free(parser->song); + } + + parser->song = song; + } + } else if (g_ascii_strcasecmp(element_name, "title") == 0) + parser->tag = TAG_TITLE; + else if (g_ascii_strcasecmp(element_name, "itunes:author") == 0) + parser->tag = TAG_ARTIST; + + break; + } +} + +static void +rss_end_element(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + struct rss_parser *parser = user_data; + + switch (parser->state) { + case ROOT: + break; + + case ITEM: + if (g_ascii_strcasecmp(element_name, "item") == 0) { + if (strcmp(parser->song->uri, "rss:") != 0) + parser->songs = g_slist_prepend(parser->songs, + parser->song); + else + song_free(parser->song); + + parser->state = ROOT; + } else + parser->tag = TAG_NUM_OF_ITEM_TYPES; + + break; + } +} + +static void +rss_text(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *text, gsize text_len, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + struct rss_parser *parser = user_data; + + switch (parser->state) { + case ROOT: + break; + + case ITEM: + if (parser->tag != TAG_NUM_OF_ITEM_TYPES) { + if (parser->song->tag == NULL) + parser->song->tag = tag_new(); + tag_add_item_n(parser->song->tag, parser->tag, + text, text_len); + } + + break; + } +} + +static const GMarkupParser rss_parser = { + .start_element = rss_start_element, + .end_element = rss_end_element, + .text = rss_text, +}; + +static void +song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) +{ + struct song *song = data; + + song_free(song); +} + +static void +rss_parser_destroy(gpointer data) +{ + struct rss_parser *parser = data; + + if (parser->state >= ITEM) + song_free(parser->song); + + g_slist_foreach(parser->songs, song_free_callback, NULL); + g_slist_free(parser->songs); +} + +/* + * The playlist object + * + */ + +struct rss_playlist { + struct playlist_provider base; + + GSList *songs; +}; + +static struct playlist_provider * +rss_open_stream(struct input_stream *is) +{ + struct rss_parser parser = { + .songs = NULL, + .state = ROOT, + }; + struct rss_playlist *playlist; + GMarkupParseContext *context; + char buffer[1024]; + size_t nbytes; + bool success; + GError *error = NULL; + + /* parse the RSS XML file */ + + context = g_markup_parse_context_new(&rss_parser, + G_MARKUP_TREAT_CDATA_AS_TEXT, + &parser, rss_parser_destroy); + + while (true) { + nbytes = input_stream_read(is, buffer, sizeof(buffer), &error); + if (nbytes == 0) { + if (error != NULL) { + g_markup_parse_context_free(context); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + + break; + } + + success = g_markup_parse_context_parse(context, buffer, nbytes, + &error); + if (!success) { + g_warning("XML parser failed: %s", error->message); + g_error_free(error); + g_markup_parse_context_free(context); + return NULL; + } + } + + success = g_markup_parse_context_end_parse(context, &error); + if (!success) { + g_warning("XML parser failed: %s", error->message); + g_error_free(error); + g_markup_parse_context_free(context); + return NULL; + } + + /* create a #rss_playlist object from the parsed song list */ + + playlist = g_new(struct rss_playlist, 1); + playlist_provider_init(&playlist->base, &rss_playlist_plugin); + playlist->songs = g_slist_reverse(parser.songs); + parser.songs = NULL; + + g_markup_parse_context_free(context); + + return &playlist->base; +} + +static void +rss_close(struct playlist_provider *_playlist) +{ + struct rss_playlist *playlist = (struct rss_playlist *)_playlist; + + g_slist_foreach(playlist->songs, song_free_callback, NULL); + g_slist_free(playlist->songs); + g_free(playlist); +} + +static struct song * +rss_read(struct playlist_provider *_playlist) +{ + struct rss_playlist *playlist = (struct rss_playlist *)_playlist; + struct song *song; + + if (playlist->songs == NULL) + return NULL; + + song = playlist->songs->data; + playlist->songs = g_slist_remove(playlist->songs, song); + + return song; +} + +static const char *const rss_suffixes[] = { + "rss", + NULL +}; + +static const char *const rss_mime_types[] = { + "application/rss+xml", + "text/xml", + NULL +}; + +const struct playlist_plugin rss_playlist_plugin = { + .name = "rss", + + .open_stream = rss_open_stream, + .close = rss_close, + .read = rss_read, + + .suffixes = rss_suffixes, + .mime_types = rss_mime_types, +}; diff --git a/src/playlist/rss_playlist_plugin.h b/src/playlist/rss_playlist_plugin.h new file mode 100644 index 000000000..d8992f2e5 --- /dev/null +++ b/src/playlist/rss_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_RSS_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_RSS_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin rss_playlist_plugin; + +#endif diff --git a/src/playlist/xspf_playlist_plugin.c b/src/playlist/xspf_playlist_plugin.c new file mode 100644 index 000000000..50f6bd1e7 --- /dev/null +++ b/src/playlist/xspf_playlist_plugin.c @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist/xspf_playlist_plugin.h" +#include "playlist_plugin.h" +#include "input_stream.h" +#include "uri.h" +#include "song.h" +#include "tag.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "xspf" + +/** + * This is the state object for the GLib XML parser. + */ +struct xspf_parser { + /** + * The list of songs (in reverse order because that's faster + * while adding). + */ + GSList *songs; + + /** + * The current position in the XML file. + */ + enum { + ROOT, PLAYLIST, TRACKLIST, TRACK, + LOCATION, + } state; + + /** + * The current tag within the "track" element. This is only + * valid if state==TRACK. TAG_NUM_OF_ITEM_TYPES means there + * is no (known) tag. + */ + enum tag_type tag; + + /** + * The current song. It is allocated after the "location" + * element. + */ + struct song *song; +}; + +static void +xspf_start_element(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *element_name, + G_GNUC_UNUSED const gchar **attribute_names, + G_GNUC_UNUSED const gchar **attribute_values, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + struct xspf_parser *parser = user_data; + + switch (parser->state) { + case ROOT: + if (strcmp(element_name, "playlist") == 0) + parser->state = PLAYLIST; + + break; + + case PLAYLIST: + if (strcmp(element_name, "trackList") == 0) + parser->state = TRACKLIST; + + break; + + case TRACKLIST: + if (strcmp(element_name, "track") == 0) { + parser->state = TRACK; + parser->song = NULL; + parser->tag = TAG_NUM_OF_ITEM_TYPES; + } + + break; + + case TRACK: + if (strcmp(element_name, "location") == 0) + parser->state = LOCATION; + else if (strcmp(element_name, "title") == 0) + parser->tag = TAG_TITLE; + else if (strcmp(element_name, "creator") == 0) + /* TAG_COMPOSER would be more correct + according to the XSPF spec */ + parser->tag = TAG_ARTIST; + else if (strcmp(element_name, "annotation") == 0) + parser->tag = TAG_COMMENT; + else if (strcmp(element_name, "album") == 0) + parser->tag = TAG_ALBUM; + else if (strcmp(element_name, "trackNum") == 0) + parser->tag = TAG_TRACK; + + break; + + case LOCATION: + break; + } +} + +static void +xspf_end_element(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + struct xspf_parser *parser = user_data; + + switch (parser->state) { + case ROOT: + break; + + case PLAYLIST: + if (strcmp(element_name, "playlist") == 0) + parser->state = ROOT; + + break; + + case TRACKLIST: + if (strcmp(element_name, "tracklist") == 0) + parser->state = PLAYLIST; + + break; + + case TRACK: + if (strcmp(element_name, "track") == 0) { + if (parser->song != NULL) + parser->songs = g_slist_prepend(parser->songs, + parser->song); + + parser->state = TRACKLIST; + } else + parser->tag = TAG_NUM_OF_ITEM_TYPES; + + break; + + case LOCATION: + parser->state = TRACK; + break; + } +} + +static void +xspf_text(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *text, gsize text_len, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + struct xspf_parser *parser = user_data; + + switch (parser->state) { + case ROOT: + case PLAYLIST: + case TRACKLIST: + break; + + case TRACK: + if (parser->song != NULL && + parser->tag != TAG_NUM_OF_ITEM_TYPES) { + if (parser->song->tag == NULL) + parser->song->tag = tag_new(); + tag_add_item_n(parser->song->tag, parser->tag, + text, text_len); + } + + break; + + case LOCATION: + if (parser->song == NULL) { + char *uri = g_strndup(text, text_len); + parser->song = song_remote_new(uri); + g_free(uri); + } + + break; + } +} + +static const GMarkupParser xspf_parser = { + .start_element = xspf_start_element, + .end_element = xspf_end_element, + .text = xspf_text, +}; + +static void +song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) +{ + struct song *song = data; + + song_free(song); +} + +static void +xspf_parser_destroy(gpointer data) +{ + struct xspf_parser *parser = data; + + if (parser->state >= TRACK && parser->song != NULL) + song_free(parser->song); + + g_slist_foreach(parser->songs, song_free_callback, NULL); + g_slist_free(parser->songs); +} + +/* + * The playlist object + * + */ + +struct xspf_playlist { + struct playlist_provider base; + + GSList *songs; +}; + +static struct playlist_provider * +xspf_open_stream(struct input_stream *is) +{ + struct xspf_parser parser = { + .songs = NULL, + .state = ROOT, + }; + struct xspf_playlist *playlist; + GMarkupParseContext *context; + char buffer[1024]; + size_t nbytes; + bool success; + GError *error = NULL; + + /* parse the XSPF XML file */ + + context = g_markup_parse_context_new(&xspf_parser, + G_MARKUP_TREAT_CDATA_AS_TEXT, + &parser, xspf_parser_destroy); + + while (true) { + nbytes = input_stream_read(is, buffer, sizeof(buffer), &error); + if (nbytes == 0) { + if (error != NULL) { + g_markup_parse_context_free(context); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + + break; + } + + success = g_markup_parse_context_parse(context, buffer, nbytes, + &error); + if (!success) { + g_warning("XML parser failed: %s", error->message); + g_error_free(error); + g_markup_parse_context_free(context); + return NULL; + } + } + + success = g_markup_parse_context_end_parse(context, &error); + if (!success) { + g_warning("XML parser failed: %s", error->message); + g_error_free(error); + g_markup_parse_context_free(context); + return NULL; + } + + /* create a #xspf_playlist object from the parsed song list */ + + playlist = g_new(struct xspf_playlist, 1); + playlist_provider_init(&playlist->base, &xspf_playlist_plugin); + playlist->songs = g_slist_reverse(parser.songs); + parser.songs = NULL; + + g_markup_parse_context_free(context); + + return &playlist->base; +} + +static void +xspf_close(struct playlist_provider *_playlist) +{ + struct xspf_playlist *playlist = (struct xspf_playlist *)_playlist; + + g_slist_foreach(playlist->songs, song_free_callback, NULL); + g_slist_free(playlist->songs); + g_free(playlist); +} + +static struct song * +xspf_read(struct playlist_provider *_playlist) +{ + struct xspf_playlist *playlist = (struct xspf_playlist *)_playlist; + struct song *song; + + if (playlist->songs == NULL) + return NULL; + + song = playlist->songs->data; + playlist->songs = g_slist_remove(playlist->songs, song); + + return song; +} + +static const char *const xspf_suffixes[] = { + "xspf", + NULL +}; + +static const char *const xspf_mime_types[] = { + "application/xspf+xml", + NULL +}; + +const struct playlist_plugin xspf_playlist_plugin = { + .name = "xspf", + + .open_stream = xspf_open_stream, + .close = xspf_close, + .read = xspf_read, + + .suffixes = xspf_suffixes, + .mime_types = xspf_mime_types, +}; diff --git a/src/playlist/xspf_playlist_plugin.h b/src/playlist/xspf_playlist_plugin.h new file mode 100644 index 000000000..ea832207d --- /dev/null +++ b/src/playlist/xspf_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_XSPF_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_XSPF_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin xspf_playlist_plugin; + +#endif diff --git a/src/playlist_any.c b/src/playlist_any.c new file mode 100644 index 000000000..39e21b178 --- /dev/null +++ b/src/playlist_any.c @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist_any.h" +#include "playlist_list.h" +#include "playlist_mapper.h" +#include "uri.h" +#include "input_stream.h" + +#include <assert.h> + +static struct playlist_provider * +playlist_open_remote(const char *uri, struct input_stream **is_r) +{ + assert(uri_has_scheme(uri)); + + struct playlist_provider *playlist = playlist_list_open_uri(uri); + if (playlist != NULL) { + *is_r = NULL; + return playlist; + } + + GError *error = NULL; + struct input_stream *is = input_stream_open(uri, &error); + if (is == NULL) { + if (error != NULL) { + g_warning("Failed to open %s: %s", + uri, error->message); + g_error_free(error); + } + + return NULL; + } + + playlist = playlist_list_open_stream(is, uri); + if (playlist == NULL) { + input_stream_close(is); + return NULL; + } + + *is_r = is; + return playlist; +} + +struct playlist_provider * +playlist_open_any(const char *uri, struct input_stream **is_r) +{ + return uri_has_scheme(uri) + ? playlist_open_remote(uri, is_r) + : playlist_mapper_open(uri, is_r); +} diff --git a/src/playlist_any.h b/src/playlist_any.h new file mode 100644 index 000000000..6fed97d15 --- /dev/null +++ b/src/playlist_any.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_ANY_H +#define MPD_PLAYLIST_ANY_H + +#include <stdbool.h> + +struct playlist_provider; +struct input_stream; + +/** + * Opens a playlist from the specified URI, which can be either an + * absolute remote URI (with a scheme) or a relative path to the + * music orplaylist directory. + * + * @param is_r on success, an input_stream object may be returned + * here, which must be closed after the playlist_provider object is + * freed + */ +struct playlist_provider * +playlist_open_any(const char *uri, struct input_stream **is_r); + +#endif diff --git a/src/playlist_control.c b/src/playlist_control.c index 4c156f0f5..ce9bc8442 100644 --- a/src/playlist_control.c +++ b/src/playlist_control.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,6 +22,7 @@ * */ +#include "config.h" #include "playlist_internal.h" #include "player_control.h" #include "idle.h" @@ -31,15 +32,7 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "playlist" -enum { - /** - * When the "prev" command is received, rewind the current - * track if this number of seconds has already elapsed. - */ - PLAYLIST_PREV_UNLESS_ELAPSED = 10, -}; - -void stopPlaylist(struct playlist *playlist) +void playlist_stop(struct playlist *playlist) { if (!playlist->playing) return; @@ -47,7 +40,7 @@ void stopPlaylist(struct playlist *playlist) assert(playlist->current >= 0); g_debug("stop"); - playerWait(); + pc_stop(); playlist->queued = -1; playlist->playing = false; @@ -69,11 +62,11 @@ void stopPlaylist(struct playlist *playlist) } } -enum playlist_result playPlaylist(struct playlist *playlist, int song) +enum playlist_result playlist_play(struct playlist *playlist, int song) { unsigned i = song; - clearPlayerError(); + pc_clear_error(); if (song == -1) { /* play any song ("current" song, or the first song */ @@ -84,7 +77,7 @@ enum playlist_result playPlaylist(struct playlist *playlist, int song) if (playlist->playing) { /* already playing: unpause playback, just in case it was paused, and return */ - playerSetPause(0); + pc_set_pause(false); return PLAYLIST_RESULT_SUCCESS; } @@ -116,28 +109,28 @@ enum playlist_result playPlaylist(struct playlist *playlist, int song) playlist->stop_on_error = false; playlist->error_count = 0; - playPlaylistOrderNumber(playlist, i); + playlist_play_order(playlist, i); return PLAYLIST_RESULT_SUCCESS; } enum playlist_result -playPlaylistById(struct playlist *playlist, int id) +playlist_play_id(struct playlist *playlist, int id) { int song; if (id == -1) { - return playPlaylist(playlist, id); + return playlist_play(playlist, id); } song = queue_id_to_position(&playlist->queue, id); if (song < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return playPlaylist(playlist, song); + return playlist_play(playlist, song); } void -nextSongInPlaylist(struct playlist *playlist) +playlist_next(struct playlist *playlist) { int next_order; int current; @@ -155,12 +148,8 @@ nextSongInPlaylist(struct playlist *playlist) next_order = queue_next_order(&playlist->queue, playlist->current); if (next_order < 0) { - /* cancel single */ - playlist->queue.single = false; - idle_add(IDLE_OPTIONS); - /* no song after this one: stop playback */ - stopPlaylist(playlist); + playlist_stop(playlist); /* reset "current song" */ playlist->current = -1; @@ -177,50 +166,42 @@ nextSongInPlaylist(struct playlist *playlist) queue_shuffle_order(&playlist->queue); /* note that playlist->current and playlist->queued are - now invalid, but playPlaylistOrderNumber() will + now invalid, but playlist_play_order() will discard them anyway */ } - playPlaylistOrderNumber(playlist, next_order); + playlist_play_order(playlist, next_order); } /* Consume mode removes each played songs. */ if(playlist->queue.consume) - deleteFromPlaylist(playlist, queue_order_to_position(&playlist->queue, current)); + playlist_delete(playlist, queue_order_to_position(&playlist->queue, current)); } -void previousSongInPlaylist(struct playlist *playlist) +void playlist_previous(struct playlist *playlist) { if (!playlist->playing) return; - if (g_timer_elapsed(playlist->prev_elapsed, NULL) >= 1.0 && - getPlayerElapsedTime() > PLAYLIST_PREV_UNLESS_ELAPSED) { - /* re-start playing the current song (just like the - "prev" button on CD players) */ + assert(queue_length(&playlist->queue) > 0); - playPlaylistOrderNumber(playlist, playlist->current); + if (playlist->current > 0) { + /* play the preceding song */ + playlist_play_order(playlist, + playlist->current - 1); + } else if (playlist->queue.repeat) { + /* play the last song in "repeat" mode */ + playlist_play_order(playlist, + queue_length(&playlist->queue) - 1); } else { - if (playlist->current > 0) { - /* play the preceding song */ - playPlaylistOrderNumber(playlist, - playlist->current - 1); - } else if (playlist->queue.repeat) { - /* play the last song in "repeat" mode */ - playPlaylistOrderNumber(playlist, - queue_length(&playlist->queue) - 1); - } else { - /* re-start playing the current song if it's - the first one */ - playPlaylistOrderNumber(playlist, playlist->current); - } + /* re-start playing the current song if it's + the first one */ + playlist_play_order(playlist, playlist->current); } - - g_timer_start(playlist->prev_elapsed); } enum playlist_result -seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time) +playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time) { const struct song *queued; unsigned i; @@ -236,7 +217,7 @@ seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time) else i = song; - clearPlayerError(); + pc_clear_error(); playlist->stop_on_error = true; playlist->error_count = 0; @@ -244,7 +225,7 @@ seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time) /* seeking is not within the current song - first start playing the new song */ - playPlaylistOrderNumber(playlist, i); + playlist_play_order(playlist, i); queued = NULL; } @@ -262,11 +243,11 @@ seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time) } enum playlist_result -seekSongInPlaylistById(struct playlist *playlist, unsigned id, float seek_time) +playlist_seek_song_id(struct playlist *playlist, unsigned id, float seek_time) { int song = queue_id_to_position(&playlist->queue, id); if (song < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return seekSongInPlaylist(playlist, song, seek_time); + return playlist_seek_song(playlist, song, seek_time); } diff --git a/src/playlist_database.c b/src/playlist_database.c new file mode 100644 index 000000000..0a8a6f139 --- /dev/null +++ b/src/playlist_database.c @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist_database.h" +#include "playlist_vector.h" +#include "text_file.h" + +#include <string.h> +#include <stdlib.h> + +static GQuark +playlist_database_quark(void) +{ + return g_quark_from_static_string("playlist_database"); +} + +void +playlist_vector_save(FILE *fp, const struct playlist_vector *pv) +{ + for (const struct playlist_metadata *pm = pv->head; + pm != NULL; pm = pm->next) + fprintf(fp, PLAYLIST_META_BEGIN "%s\n" + "mtime: %li\n" + "playlist_end\n", + pm->name, (long)pm->mtime); +} + +bool +playlist_metadata_load(FILE *fp, struct playlist_vector *pv, const char *name, + GString *buffer, GError **error_r) +{ + struct playlist_metadata pm = { + .mtime = 0, + }; + char *line, *colon; + const char *value; + + while ((line = read_text_line(fp, buffer)) != NULL && + strcmp(line, "playlist_end") != 0) { + colon = strchr(line, ':'); + if (colon == NULL || colon == line) { + g_set_error(error_r, playlist_database_quark(), 0, + "unknown line in db: %s", line); + return false; + } + + *colon++ = 0; + value = g_strchug(colon); + + if (strcmp(line, "mtime") == 0) + pm.mtime = strtol(value, NULL, 10); + else { + g_set_error(error_r, playlist_database_quark(), 0, + "unknown line in db: %s", line); + return false; + } + } + + playlist_vector_update_or_add(pv, name, pm.mtime); + return true; +} diff --git a/src/playlist_database.h b/src/playlist_database.h new file mode 100644 index 000000000..7e114abdd --- /dev/null +++ b/src/playlist_database.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_DATABASE_H +#define MPD_PLAYLIST_DATABASE_H + +#include "check.h" + +#include <stdbool.h> +#include <stdio.h> +#include <glib.h> + +#define PLAYLIST_META_BEGIN "playlist_begin: " + +struct playlist_vector; + +void +playlist_vector_save(FILE *fp, const struct playlist_vector *pv); + +bool +playlist_metadata_load(FILE *fp, struct playlist_vector *pv, const char *name, + GString *buffer, GError **error_r); + +#endif diff --git a/src/playlist_edit.c b/src/playlist_edit.c index b83dc0933..c54b72750 100644 --- a/src/playlist_edit.c +++ b/src/playlist_edit.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,6 +23,7 @@ * */ +#include "config.h" #include "playlist_internal.h" #include "player_control.h" #include "database.h" @@ -35,16 +36,16 @@ #include <unistd.h> #include <stdlib.h> -static void incrPlaylistVersion(struct playlist *playlist) +static void playlist_increment_version(struct playlist *playlist) { queue_increment_version(&playlist->queue); idle_add(IDLE_PLAYLIST); } -void clearPlaylist(struct playlist *playlist) +void playlist_clear(struct playlist *playlist) { - stopPlaylist(playlist); + playlist_stop(playlist); /* make sure there are no references to allocated songs anymore */ @@ -58,7 +59,7 @@ void clearPlaylist(struct playlist *playlist) playlist->current = -1; - incrPlaylistVersion(playlist); + playlist_increment_version(playlist); } #ifndef WIN32 @@ -86,41 +87,12 @@ playlist_append_file(struct playlist *playlist, const char *path, int uid, if (song == NULL) return PLAYLIST_RESULT_NO_SUCH_SONG; - return addSongToPlaylist(playlist, song, added_id); + return playlist_append_song(playlist, song, added_id); } #endif -static struct song * -song_by_url(const char *url) -{ - struct song *song; - - song = db_get_song(url); - if (song != NULL) - return song; - - if (uri_has_scheme(url)) - return song_remote_new(url); - - return NULL; -} - -enum playlist_result -addToPlaylist(struct playlist *playlist, const char *url, unsigned *added_id) -{ - struct song *song; - - g_debug("add to playlist: %s", url); - - song = song_by_url(url); - if (song == NULL) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return addSongToPlaylist(playlist, song, added_id); -} - enum playlist_result -addSongToPlaylist(struct playlist *playlist, +playlist_append_song(struct playlist *playlist, struct song *song, unsigned *added_id) { const struct song *queued; @@ -147,7 +119,7 @@ addSongToPlaylist(struct playlist *playlist, queue_length(&playlist->queue)); } - incrPlaylistVersion(playlist); + playlist_increment_version(playlist); playlist_update_queued_song(playlist, queued); @@ -157,8 +129,38 @@ addSongToPlaylist(struct playlist *playlist, return PLAYLIST_RESULT_SUCCESS; } +static struct song * +song_by_uri(const char *uri) +{ + struct song *song; + + song = db_get_song(uri); + if (song != NULL) + return song; + + if (uri_has_scheme(uri)) + return song_remote_new(uri); + + return NULL; +} + +enum playlist_result +playlist_append_uri(struct playlist *playlist, const char *uri, + unsigned *added_id) +{ + struct song *song; + + g_debug("add to playlist: %s", uri); + + song = song_by_uri(uri); + if (song == NULL) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return playlist_append_song(playlist, song, added_id); +} + enum playlist_result -swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2) +playlist_swap_songs(struct playlist *playlist, unsigned song1, unsigned song2) { const struct song *queued; @@ -188,7 +190,7 @@ swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2) playlist->current = song1; } - incrPlaylistVersion(playlist); + playlist_increment_version(playlist); playlist_update_queued_song(playlist, queued); @@ -196,7 +198,7 @@ swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2) } enum playlist_result -swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2) +playlist_swap_songs_id(struct playlist *playlist, unsigned id1, unsigned id2) { int song1 = queue_id_to_position(&playlist->queue, id1); int song2 = queue_id_to_position(&playlist->queue, id2); @@ -204,28 +206,25 @@ swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2) if (song1 < 0 || song2 < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return swapSongsInPlaylist(playlist, song1, song2); + return playlist_swap_songs(playlist, song1, song2); } -enum playlist_result -deleteFromPlaylist(struct playlist *playlist, unsigned song) +static void +playlist_delete_internal(struct playlist *playlist, unsigned song, + const struct song **queued_p) { - const struct song *queued; unsigned songOrder; - if (song >= queue_length(&playlist->queue)) - return PLAYLIST_RESULT_BAD_RANGE; - - queued = playlist_get_queued_song(playlist); + assert(song < queue_length(&playlist->queue)); songOrder = queue_position_to_order(&playlist->queue, song); if (playlist->playing && playlist->current == (int)songOrder) { - bool paused = getPlayerState() == PLAYER_STATE_PAUSE; + bool paused = pc_get_state() == PLAYER_STATE_PAUSE; /* the current song is going to be deleted: stop the player */ - playerWait(); + pc_stop(); playlist->playing = false; /* see which song is going to be played instead */ @@ -237,13 +236,13 @@ deleteFromPlaylist(struct playlist *playlist, unsigned song) if (playlist->current >= 0 && !paused) /* play the song after the deleted one */ - playPlaylistOrderNumber(playlist, playlist->current); + playlist_play_order(playlist, playlist->current); else /* no songs left to play, stop playback completely */ - stopPlaylist(playlist); + playlist_stop(playlist); - queued = NULL; + *queued_p = NULL; } else if (playlist->current == (int)songOrder) /* there's a "current song" but we're not playing currently - clear "current" */ @@ -256,41 +255,80 @@ deleteFromPlaylist(struct playlist *playlist, unsigned song) queue_delete(&playlist->queue, song); - incrPlaylistVersion(playlist); - /* update the "current" and "queued" variables */ if (playlist->current > (int)songOrder) { playlist->current--; } +} + +enum playlist_result +playlist_delete(struct playlist *playlist, unsigned song) +{ + const struct song *queued; + + if (song >= queue_length(&playlist->queue)) + return PLAYLIST_RESULT_BAD_RANGE; + + queued = playlist_get_queued_song(playlist); + + playlist_delete_internal(playlist, song, &queued); + playlist_increment_version(playlist); playlist_update_queued_song(playlist, queued); return PLAYLIST_RESULT_SUCCESS; } enum playlist_result -deleteFromPlaylistById(struct playlist *playlist, unsigned id) +playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end) +{ + const struct song *queued; + + if (start >= queue_length(&playlist->queue)) + return PLAYLIST_RESULT_BAD_RANGE; + + if (end > queue_length(&playlist->queue)) + end = queue_length(&playlist->queue); + + if (start >= end) + return PLAYLIST_RESULT_SUCCESS; + + queued = playlist_get_queued_song(playlist); + + do { + playlist_delete_internal(playlist, --end, &queued); + } while (end != start); + + playlist_increment_version(playlist); + playlist_update_queued_song(playlist, queued); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist_delete_id(struct playlist *playlist, unsigned id) { int song = queue_id_to_position(&playlist->queue, id); if (song < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return deleteFromPlaylist(playlist, song); + return playlist_delete(playlist, song); } void -deleteASongFromPlaylist(struct playlist *playlist, const struct song *song) +playlist_delete_song(struct playlist *playlist, const struct song *song) { for (int i = queue_length(&playlist->queue) - 1; i >= 0; --i) if (song == queue_get(&playlist->queue, i)) - deleteFromPlaylist(playlist, i); + playlist_delete(playlist, i); pc_song_deleted(song); } enum playlist_result -moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end, int to) +playlist_move_range(struct playlist *playlist, + unsigned start, unsigned end, int to) { const struct song *queued; int currentSong; @@ -342,7 +380,7 @@ moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end, } } - incrPlaylistVersion(playlist); + playlist_increment_version(playlist); playlist_update_queued_song(playlist, queued); @@ -350,16 +388,17 @@ moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end, } enum playlist_result -moveSongInPlaylistById(struct playlist *playlist, unsigned id1, int to) +playlist_move_id(struct playlist *playlist, unsigned id1, int to) { int song = queue_id_to_position(&playlist->queue, id1); if (song < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return moveSongRangeInPlaylist(playlist, song, song+1, to); + return playlist_move_range(playlist, song, song+1, to); } -void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end) +void +playlist_shuffle(struct playlist *playlist, unsigned start, unsigned end) { const struct song *queued; @@ -399,7 +438,7 @@ void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end) queue_shuffle_range(&playlist->queue, start, end); - incrPlaylistVersion(playlist); + playlist_increment_version(playlist); playlist_update_queued_song(playlist, queued); } diff --git a/src/playlist_global.c b/src/playlist_global.c index fa810bbc3..2833b62ed 100644 --- a/src/playlist_global.c +++ b/src/playlist_global.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,6 +22,7 @@ * */ +#include "config.h" #include "playlist.h" #include "playlist_state.h" #include "event_pipe.h" @@ -37,10 +38,11 @@ playlist_tag_event(void) static void playlist_event(void) { - syncPlayerAndPlaylist(&g_playlist); + playlist_sync(&g_playlist); } -void initPlaylist(void) +void +playlist_global_init(void) { playlist_init(&g_playlist); @@ -48,17 +50,8 @@ void initPlaylist(void) event_pipe_register(PIPE_EVENT_PLAYLIST, playlist_event); } -void finishPlaylist(void) +void +playlist_global_finish(void) { playlist_finish(&g_playlist); } - -void savePlaylistState(FILE *fp) -{ - playlist_state_save(fp, &g_playlist); -} - -void readPlaylistState(FILE *fp) -{ - playlist_state_restore(fp, &g_playlist); -} diff --git a/src/playlist_internal.h b/src/playlist_internal.h index af880691b..9d205188f 100644 --- a/src/playlist_internal.h +++ b/src/playlist_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -47,6 +47,6 @@ playlist_update_queued_song(struct playlist *playlist, const struct song *prev); void -playPlaylistOrderNumber(struct playlist *playlist, int orderNum); +playlist_play_order(struct playlist *playlist, int orderNum); #endif diff --git a/src/playlist_list.c b/src/playlist_list.c new file mode 100644 index 000000000..019654bfc --- /dev/null +++ b/src/playlist_list.c @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist_list.h" +#include "playlist_plugin.h" +#include "playlist/extm3u_playlist_plugin.h" +#include "playlist/m3u_playlist_plugin.h" +#include "playlist/xspf_playlist_plugin.h" +#include "playlist/lastfm_playlist_plugin.h" +#include "playlist/pls_playlist_plugin.h" +#include "playlist/asx_playlist_plugin.h" +#include "playlist/rss_playlist_plugin.h" +#include "playlist/cue_playlist_plugin.h" +#include "playlist/flac_playlist_plugin.h" +#include "input_stream.h" +#include "uri.h" +#include "utils.h" +#include "conf.h" +#include "glib_compat.h" +#include "mpd_error.h" + +#include <assert.h> +#include <string.h> +#include <stdio.h> + +static const struct playlist_plugin *const playlist_plugins[] = { + &extm3u_playlist_plugin, + &m3u_playlist_plugin, + &xspf_playlist_plugin, + &pls_playlist_plugin, + &asx_playlist_plugin, + &rss_playlist_plugin, +#ifdef ENABLE_LASTFM + &lastfm_playlist_plugin, +#endif +#ifdef HAVE_CUE + &cue_playlist_plugin, +#endif +#ifdef HAVE_FLAC + &flac_playlist_plugin, +#endif + NULL +}; + +/** which plugins have been initialized successfully? */ +static bool playlist_plugins_enabled[G_N_ELEMENTS(playlist_plugins)]; + +/** + * Find the "playlist" configuration block for the specified plugin. + * + * @param plugin_name the name of the playlist plugin + * @return the configuration block, or NULL if none was configured + */ +static const struct config_param * +playlist_plugin_config(const char *plugin_name) +{ + const struct config_param *param = NULL; + + assert(plugin_name != NULL); + + while ((param = config_get_next_param(CONF_PLAYLIST_PLUGIN, param)) != NULL) { + const char *name = + config_get_block_string(param, "name", NULL); + if (name == NULL) + MPD_ERROR("playlist configuration without 'plugin' name in line %d", + param->line); + + if (strcmp(name, plugin_name) == 0) + return param; + } + + return NULL; +} + +void +playlist_list_global_init(void) +{ + for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + const struct config_param *param = + playlist_plugin_config(plugin->name); + + if (!config_get_block_bool(param, "enabled", true)) + /* the plugin is disabled in mpd.conf */ + continue; + + playlist_plugins_enabled[i] = + playlist_plugin_init(playlist_plugins[i], param); + } +} + +void +playlist_list_global_finish(void) +{ + for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) + if (playlist_plugins_enabled[i]) + playlist_plugin_finish(playlist_plugins[i]); +} + +static struct playlist_provider * +playlist_list_open_uri_scheme(const char *uri, bool *tried) +{ + char *scheme; + struct playlist_provider *playlist = NULL; + + assert(uri != NULL); + + scheme = g_uri_parse_scheme(uri); + if (scheme == NULL) + return NULL; + + for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + + assert(!tried[i]); + + if (playlist_plugins_enabled[i] && plugin->open_uri != NULL && + plugin->schemes != NULL && + string_array_contains(plugin->schemes, scheme)) { + playlist = playlist_plugin_open_uri(plugin, uri); + if (playlist != NULL) + break; + + tried[i] = true; + } + } + + g_free(scheme); + return playlist; +} + +static struct playlist_provider * +playlist_list_open_uri_suffix(const char *uri, const bool *tried) +{ + const char *suffix; + struct playlist_provider *playlist = NULL; + + assert(uri != NULL); + + suffix = uri_get_suffix(uri); + if (suffix == NULL) + return NULL; + + for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + + if (playlist_plugins_enabled[i] && !tried[i] && + plugin->open_uri != NULL && plugin->suffixes != NULL && + string_array_contains(plugin->suffixes, suffix)) { + playlist = playlist_plugin_open_uri(plugin, uri); + if (playlist != NULL) + break; + } + } + + return playlist; +} + +struct playlist_provider * +playlist_list_open_uri(const char *uri) +{ + struct playlist_provider *playlist; + /** this array tracks which plugins have already been tried by + playlist_list_open_uri_scheme() */ + bool tried[G_N_ELEMENTS(playlist_plugins) - 1]; + + assert(uri != NULL); + + memset(tried, false, sizeof(tried)); + + playlist = playlist_list_open_uri_scheme(uri, tried); + if (playlist == NULL) + playlist = playlist_list_open_uri_suffix(uri, tried); + + return playlist; +} + +static struct playlist_provider * +playlist_list_open_stream_mime2(struct input_stream *is, const char *mime) +{ + struct playlist_provider *playlist; + + assert(is != NULL); + assert(mime != NULL); + + for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + + if (playlist_plugins_enabled[i] && + plugin->open_stream != NULL && + plugin->mime_types != NULL && + string_array_contains(plugin->mime_types, mime)) { + /* rewind the stream, so each plugin gets a + fresh start */ + input_stream_seek(is, 0, SEEK_SET, NULL); + + playlist = playlist_plugin_open_stream(plugin, is); + if (playlist != NULL) + return playlist; + } + } + + return NULL; +} + +static struct playlist_provider * +playlist_list_open_stream_mime(struct input_stream *is) +{ + assert(is->mime != NULL); + + const char *semicolon = strchr(is->mime, ';'); + if (semicolon == NULL) + return playlist_list_open_stream_mime2(is, is->mime); + + if (semicolon == is->mime) + return NULL; + + /* probe only the portion before the semicolon*/ + char *mime = g_strndup(is->mime, semicolon - is->mime); + struct playlist_provider *playlist = + playlist_list_open_stream_mime2(is, mime); + g_free(mime); + return playlist; +} + +static struct playlist_provider * +playlist_list_open_stream_suffix(struct input_stream *is, const char *suffix) +{ + struct playlist_provider *playlist; + + assert(is != NULL); + assert(suffix != NULL); + + for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + + if (playlist_plugins_enabled[i] && + plugin->open_stream != NULL && + plugin->suffixes != NULL && + string_array_contains(plugin->suffixes, suffix)) { + /* rewind the stream, so each plugin gets a + fresh start */ + input_stream_seek(is, 0, SEEK_SET, NULL); + + playlist = playlist_plugin_open_stream(plugin, is); + if (playlist != NULL) + return playlist; + } + } + + return NULL; +} + +struct playlist_provider * +playlist_list_open_stream(struct input_stream *is, const char *uri) +{ + const char *suffix; + struct playlist_provider *playlist; + + GError *error = NULL; + while (!is->ready) { + int ret = input_stream_buffer(is, &error); + if (ret < 0) { + input_stream_close(is); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + } + + if (is->mime != NULL) { + playlist = playlist_list_open_stream_mime(is); + if (playlist != NULL) + return playlist; + } + + suffix = uri != NULL ? uri_get_suffix(uri) : NULL; + if (suffix != NULL) { + playlist = playlist_list_open_stream_suffix(is, suffix); + if (playlist != NULL) + return playlist; + } + + return NULL; +} + +bool +playlist_suffix_supported(const char *suffix) +{ + assert(suffix != NULL); + + for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + + if (playlist_plugins_enabled[i] && plugin->suffixes != NULL && + string_array_contains(plugin->suffixes, suffix)) + return true; + } + + return false; +} + +struct playlist_provider * +playlist_list_open_path(const char *path_fs, struct input_stream **is_r) +{ + GError *error = NULL; + const char *suffix; + struct input_stream *is; + struct playlist_provider *playlist; + + assert(path_fs != NULL); + + suffix = uri_get_suffix(path_fs); + if (suffix == NULL || !playlist_suffix_supported(suffix)) + return NULL; + + is = input_stream_open(path_fs, &error); + if (is == NULL) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + + return NULL; + } + + while (!is->ready) { + int ret = input_stream_buffer(is, &error); + if (ret < 0) { + input_stream_close(is); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + } + + playlist = playlist_list_open_stream_suffix(is, suffix); + if (playlist != NULL) + *is_r = is; + else + input_stream_close(is); + + return playlist; +} diff --git a/src/playlist_list.h b/src/playlist_list.h new file mode 100644 index 000000000..3710589a2 --- /dev/null +++ b/src/playlist_list.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_LIST_H +#define MPD_PLAYLIST_LIST_H + +#include <stdbool.h> + +struct playlist_provider; +struct input_stream; + +/** + * Initializes all playlist plugins. + */ +void +playlist_list_global_init(void); + +/** + * Deinitializes all playlist plugins. + */ +void +playlist_list_global_finish(void); + +/** + * Opens a playlist by its URI. + */ +struct playlist_provider * +playlist_list_open_uri(const char *uri); + +/** + * Opens a playlist from an input stream. + * + * @param is an #input_stream object which is open and ready + * @param uri optional URI which was used to open the stream; may be + * used to select the appropriate playlist plugin + */ +struct playlist_provider * +playlist_list_open_stream(struct input_stream *is, const char *uri); + +/** + * Determines if there is a playlist plugin which can handle the + * specified file name suffix. + */ +bool +playlist_suffix_supported(const char *suffix); + +/** + * Opens a playlist from a local file. + * + * @param path_fs the path of the playlist file + * @param is_r on success, an input_stream object is returned here, + * which must be closed after the playlist_provider object is freed + * @return a playlist, or NULL on error + */ +struct playlist_provider * +playlist_list_open_path(const char *path_fs, struct input_stream **is_r); + +#endif diff --git a/src/playlist_mapper.c b/src/playlist_mapper.c new file mode 100644 index 000000000..99b322073 --- /dev/null +++ b/src/playlist_mapper.c @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist_mapper.h" +#include "playlist_list.h" +#include "stored_playlist.h" +#include "mapper.h" +#include "uri.h" + +#include <assert.h> + +static struct playlist_provider * +playlist_open_path(const char *path_fs, struct input_stream **is_r) +{ + struct playlist_provider *playlist; + + playlist = playlist_list_open_uri(path_fs); + if (playlist != NULL) + *is_r = NULL; + else + playlist = playlist_list_open_path(path_fs, is_r); + + return playlist; +} + +/** + * Load a playlist from the configured playlist directory. + */ +static struct playlist_provider * +playlist_open_in_playlist_dir(const char *uri, struct input_stream **is_r) +{ + char *path_fs; + + assert(spl_valid_name(uri)); + + const char *playlist_directory_fs = map_spl_path(); + if (playlist_directory_fs == NULL) + return NULL; + + path_fs = g_build_filename(playlist_directory_fs, uri, NULL); + + struct playlist_provider *playlist = playlist_open_path(path_fs, is_r); + g_free(path_fs); + + return playlist; +} + +/** + * Load a playlist from the configured music directory. + */ +static struct playlist_provider * +playlist_open_in_music_dir(const char *uri, struct input_stream **is_r) +{ + char *path_fs; + + assert(uri_safe_local(uri)); + + path_fs = map_uri_fs(uri); + if (path_fs == NULL) + return NULL; + + struct playlist_provider *playlist = playlist_open_path(path_fs, is_r); + g_free(path_fs); + + return playlist; +} + +struct playlist_provider * +playlist_mapper_open(const char *uri, struct input_stream **is_r) +{ + struct playlist_provider *playlist; + + if (spl_valid_name(uri)) { + playlist = playlist_open_in_playlist_dir(uri, is_r); + if (playlist != NULL) + return playlist; + } + + if (uri_safe_local(uri)) { + playlist = playlist_open_in_music_dir(uri, is_r); + if (playlist != NULL) + return playlist; + } + + return NULL; +} diff --git a/src/playlist_mapper.h b/src/playlist_mapper.h new file mode 100644 index 000000000..b98af1b13 --- /dev/null +++ b/src/playlist_mapper.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_MAPPER_H +#define MPD_PLAYLIST_MAPPER_H + +struct input_stream; + +/** + * Opens a playlist from an URI relative to the playlist or music + * directory. + * + * @param is_r on success, an input_stream object may be returned + * here, which must be closed after the playlist_provider object is + * freed + */ +struct playlist_provider * +playlist_mapper_open(const char *uri, struct input_stream **is_r); + +#endif diff --git a/src/playlist_plugin.h b/src/playlist_plugin.h new file mode 100644 index 000000000..3d840573e --- /dev/null +++ b/src/playlist_plugin.h @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_PLUGIN_H + +#include <stdbool.h> +#include <stddef.h> + +struct config_param; +struct input_stream; +struct tag; + +/** + * An object which provides the contents of a playlist. + */ +struct playlist_provider { + const struct playlist_plugin *plugin; +}; + +static inline void +playlist_provider_init(struct playlist_provider *playlist, + const struct playlist_plugin *plugin) +{ + playlist->plugin = plugin; +} + +struct playlist_plugin { + const char *name; + + /** + * Initialize the plugin. Optional method. + * + * @param param a configuration block for this plugin, or NULL + * if none is configured + * @return true if the plugin was initialized successfully, + * false if the plugin is not available + */ + bool (*init)(const struct config_param *param); + + /** + * Deinitialize a plugin which was initialized successfully. + * Optional method. + */ + void (*finish)(void); + + /** + * Opens the playlist on the specified URI. This URI has + * either matched one of the schemes or one of the suffixes. + */ + struct playlist_provider *(*open_uri)(const char *uri); + + /** + * Opens the playlist in the specified input stream. It has + * either matched one of the suffixes or one of the MIME + * types. + */ + struct playlist_provider *(*open_stream)(struct input_stream *is); + + void (*close)(struct playlist_provider *playlist); + + struct song *(*read)(struct playlist_provider *playlist); + + const char *const*schemes; + const char *const*suffixes; + const char *const*mime_types; +}; + +/** + * Initialize a plugin. + * + * @param param a configuration block for this plugin, or NULL if none + * is configured + * @return true if the plugin was initialized successfully, false if + * the plugin is not available + */ +static inline bool +playlist_plugin_init(const struct playlist_plugin *plugin, + const struct config_param *param) +{ + return plugin->init != NULL + ? plugin->init(param) + : true; +} + +/** + * Deinitialize a plugin which was initialized successfully. + */ +static inline void +playlist_plugin_finish(const struct playlist_plugin *plugin) +{ + if (plugin->finish != NULL) + plugin->finish(); +} + +static inline struct playlist_provider * +playlist_plugin_open_uri(const struct playlist_plugin *plugin, const char *uri) +{ + return plugin->open_uri(uri); +} + +static inline struct playlist_provider * +playlist_plugin_open_stream(const struct playlist_plugin *plugin, + struct input_stream *is) +{ + return plugin->open_stream(is); +} + +static inline void +playlist_plugin_close(struct playlist_provider *playlist) +{ + playlist->plugin->close(playlist); +} + +static inline struct song * +playlist_plugin_read(struct playlist_provider *playlist) +{ + return playlist->plugin->read(playlist); +} + +#endif diff --git a/src/playlist_print.c b/src/playlist_print.c index fd61ab62c..89ab2e5ab 100644 --- a/src/playlist_print.c +++ b/src/playlist_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,19 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "playlist_print.h" +#include "playlist_list.h" +#include "playlist_plugin.h" +#include "playlist_any.h" +#include "playlist_song.h" #include "queue_print.h" #include "stored_playlist.h" #include "song_print.h" #include "song.h" #include "database.h" #include "client.h" +#include "input_stream.h" void playlist_print_uris(struct client *client, const struct playlist *playlist) @@ -69,7 +75,7 @@ playlist_print_id(struct client *client, const struct playlist *playlist, bool playlist_print_current(struct client *client, const struct playlist *playlist) { - int current_position = getPlaylistCurrentSong(playlist); + int current_position = playlist_get_current_song(playlist); if (current_position < 0) return false; @@ -138,3 +144,41 @@ spl_print(struct client *client, const char *name_utf8, bool detail) spl_free(list); return true; } + +static void +playlist_provider_print(struct client *client, const char *uri, + struct playlist_provider *playlist, bool detail) +{ + struct song *song; + char *base_uri = uri != NULL ? g_path_get_dirname(uri) : NULL; + + while ((song = playlist_plugin_read(playlist)) != NULL) { + song = playlist_check_translate_song(song, base_uri); + if (song == NULL) + continue; + + if (detail) + song_print_info(client, song); + else + song_print_uri(client, song); + } + + g_free(base_uri); +} + +bool +playlist_file_print(struct client *client, const char *uri, bool detail) +{ + struct input_stream *is; + struct playlist_provider *playlist = playlist_open_any(uri, &is); + if (playlist == NULL) + return false; + + playlist_provider_print(client, uri, playlist, detail); + playlist_plugin_close(playlist); + + if (is != NULL) + input_stream_close(is); + + return true; +} diff --git a/src/playlist_print.h b/src/playlist_print.h index 0cfe80776..b3a0446ed 100644 --- a/src/playlist_print.h +++ b/src/playlist_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -101,4 +101,15 @@ playlist_print_changes_position(struct client *client, bool spl_print(struct client *client, const char *name_utf8, bool detail); +/** + * Send the playlist file to the client. + * + * @param client the client which requested the playlist + * @param uri the URI of the playlist file in UTF-8 encoding + * @param detail true if all details should be printed + * @return true on success, false if the playlist does not exist + */ +bool +playlist_file_print(struct client *client, const char *uri, bool detail); + #endif diff --git a/src/playlist_queue.c b/src/playlist_queue.c new file mode 100644 index 000000000..635e23a28 --- /dev/null +++ b/src/playlist_queue.c @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist_queue.h" +#include "playlist_plugin.h" +#include "playlist_any.h" +#include "playlist_song.h" +#include "song.h" +#include "input_stream.h" + +enum playlist_result +playlist_load_into_queue(const char *uri, struct playlist_provider *source, + struct playlist *dest) +{ + enum playlist_result result; + struct song *song; + char *base_uri = uri != NULL ? g_path_get_dirname(uri) : NULL; + + while ((song = playlist_plugin_read(source)) != NULL) { + song = playlist_check_translate_song(song, base_uri); + if (song == NULL) + continue; + + result = playlist_append_song(dest, song, NULL); + if (result != PLAYLIST_RESULT_SUCCESS) { + if (!song_in_database(song)) + song_free(song); + g_free(base_uri); + return result; + } + } + + g_free(base_uri); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist_open_into_queue(const char *uri, struct playlist *dest) +{ + struct input_stream *is; + struct playlist_provider *playlist = playlist_open_any(uri, &is); + if (playlist == NULL) + return PLAYLIST_RESULT_NO_SUCH_LIST; + + enum playlist_result result = + playlist_load_into_queue(uri, playlist, dest); + playlist_plugin_close(playlist); + + if (is != NULL) + input_stream_close(is); + + return result; +} diff --git a/src/playlist_queue.h b/src/playlist_queue.h new file mode 100644 index 000000000..530d4b4be --- /dev/null +++ b/src/playlist_queue.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/*! \file + * \brief Glue between playlist plugin and the play queue + */ + +#ifndef MPD_PLAYLIST_QUEUE_H +#define MPD_PLAYLIST_QUEUE_H + +#include "playlist.h" + +struct playlist_provider; +struct playlist; + +/** + * Loads the contents of a playlist and append it to the specified + * play queue. + * + * @param uri the URI of the playlist, used to resolve relative song + * URIs + */ +enum playlist_result +playlist_load_into_queue(const char *uri, struct playlist_provider *source, + struct playlist *dest); + +/** + * Opens a playlist with a playlist plugin and append to the specified + * play queue. + */ +enum playlist_result +playlist_open_into_queue(const char *uri, struct playlist *dest); + +#endif + diff --git a/src/playlist_save.c b/src/playlist_save.c index 13dbc721d..8ddc93ec9 100644 --- a/src/playlist_save.c +++ b/src/playlist_save.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "playlist_save.h" #include "stored_playlist.h" #include "song.h" @@ -54,7 +55,7 @@ playlist_print_uri(FILE *file, const char *uri) char *s; if (playlist_saveAbsolutePaths && !uri_has_scheme(uri) && - uri[0] != '/') + !g_path_is_absolute(uri)) s = map_uri_fs(uri); else s = utf8_to_fs_charset(uri); @@ -118,7 +119,7 @@ playlist_load_spl(struct playlist *playlist, const char *name_utf8) for (unsigned i = 0; i < list->len; ++i) { const char *temp = g_ptr_array_index(list, i); - if ((addToPlaylist(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { + if ((playlist_append_uri(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { /* for windows compatibility, convert slashes */ char *temp2 = g_strdup(temp); char *p = temp2; @@ -127,7 +128,7 @@ playlist_load_spl(struct playlist *playlist, const char *name_utf8) *p = '/'; p++; } - if ((addToPlaylist(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { + if ((playlist_append_uri(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { g_warning("can't add file \"%s\"", temp2); } g_free(temp2); diff --git a/src/playlist_save.h b/src/playlist_save.h index 8669ca025..a0131cf7f 100644 --- a/src/playlist_save.h +++ b/src/playlist_save.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist_song.c b/src/playlist_song.c new file mode 100644 index 000000000..20fac59d8 --- /dev/null +++ b/src/playlist_song.c @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist_song.h" +#include "database.h" +#include "mapper.h" +#include "song.h" +#include "uri.h" +#include "ls.h" +#include "tag.h" + +#include <assert.h> +#include <string.h> + +static void +merge_song_metadata(struct song *dest, const struct song *base, + const struct song *add) +{ + dest->tag = base->tag != NULL + ? (add->tag != NULL + ? tag_merge(base->tag, add->tag) + : tag_dup(base->tag)) + : (add->tag != NULL + ? tag_dup(add->tag) + : NULL); + + dest->mtime = base->mtime; + dest->start_ms = add->start_ms; + dest->end_ms = add->end_ms; +} + +static struct song * +apply_song_metadata(struct song *dest, const struct song *src) +{ + struct song *tmp; + + assert(dest != NULL); + assert(src != NULL); + + if (src->tag == NULL && src->start_ms == 0 && src->end_ms == 0) + return dest; + + if (song_in_database(dest)) { + char *path_fs = map_song_fs(dest); + if (path_fs == NULL) + return dest; + + tmp = song_file_new(path_fs, NULL); + g_free(path_fs); + + merge_song_metadata(tmp, dest, src); + } else { + tmp = song_file_new(dest->uri, NULL); + merge_song_metadata(tmp, dest, src); + song_free(dest); + } + + return tmp; +} + +struct song * +playlist_check_translate_song(struct song *song, const char *base_uri) +{ + struct song *dest; + + if (song_in_database(song)) + /* already ok */ + return song; + + char *uri = song->uri; + + if (uri_has_scheme(uri)) { + if (uri_supported_scheme(uri)) + /* valid remote song */ + return song; + else { + /* unsupported remote song */ + song_free(song); + return NULL; + } + } + + if (g_path_is_absolute(uri)) { + /* XXX fs_charset vs utf8? */ + char *prefix = base_uri != NULL + ? map_uri_fs(base_uri) + : map_directory_fs(db_get_root()); + + if (prefix == NULL || !g_str_has_prefix(uri, prefix) || + uri[strlen(prefix)] != '/') { + /* local files must be relative to the music + directory */ + g_free(prefix); + song_free(song); + return NULL; + } + + uri += strlen(prefix) + 1; + g_free(prefix); + } + + if (base_uri != NULL) + uri = g_build_filename(base_uri, uri, NULL); + else + uri = g_strdup(uri); + + if (uri_has_scheme(base_uri)) { + dest = song_remote_new(uri); + g_free(uri); + } else { + dest = db_get_song(uri); + g_free(uri); + if (dest == NULL) { + /* not found in database */ + song_free(song); + return dest; + } + } + + dest = apply_song_metadata(dest, song); + song_free(song); + + return dest; +} diff --git a/src/playlist_song.h b/src/playlist_song.h new file mode 100644 index 000000000..5a2e4c2b0 --- /dev/null +++ b/src/playlist_song.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_SONG_H +#define MPD_PLAYLIST_SONG_H + +/** + * Verifies the song, returns NULL if it is unsafe. Translate the + * song to a new song object within the database, if it is a local + * file. The old song object is freed. + */ +struct song * +playlist_check_translate_song(struct song *song, const char *base_uri); + +#endif diff --git a/src/playlist_state.c b/src/playlist_state.c index af0f7982b..bb9897e01 100644 --- a/src/playlist_state.c +++ b/src/playlist_state.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,11 +22,13 @@ * */ +#include "config.h" #include "playlist_state.h" #include "playlist.h" #include "player_control.h" #include "queue_save.h" #include "path.h" +#include "text_file.h" #include <string.h> #include <stdlib.h> @@ -39,6 +41,8 @@ #define PLAYLIST_STATE_FILE_CURRENT "current: " #define PLAYLIST_STATE_FILE_TIME "time: " #define PLAYLIST_STATE_FILE_CROSSFADE "crossfade: " +#define PLAYLIST_STATE_FILE_MIXRAMPDB "mixrampdb: " +#define PLAYLIST_STATE_FILE_MIXRAMPDELAY "mixrampdelay: " #define PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "playlist_begin" #define PLAYLIST_STATE_FILE_PLAYLIST_END "playlist_end" @@ -51,57 +55,65 @@ void playlist_state_save(FILE *fp, const struct playlist *playlist) { - fprintf(fp, "%s", PLAYLIST_STATE_FILE_STATE); + struct player_status player_status; + + pc_get_status(&player_status); + + fputs(PLAYLIST_STATE_FILE_STATE, fp); if (playlist->playing) { - switch (getPlayerState()) { + switch (player_status.state) { case PLAYER_STATE_PAUSE: - fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PAUSE); + fputs(PLAYLIST_STATE_FILE_STATE_PAUSE "\n", fp); break; default: - fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PLAY); + fputs(PLAYLIST_STATE_FILE_STATE_PLAY "\n", fp); } - fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CURRENT, + fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", queue_order_to_position(&playlist->queue, playlist->current)); - fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_TIME, - getPlayerElapsedTime()); - } else - fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_STOP); - - fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_RANDOM, - playlist->queue.random); - fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_REPEAT, - playlist->queue.repeat); - fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_SINGLE, - playlist->queue.single); - fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CONSUME, + fprintf(fp, PLAYLIST_STATE_FILE_TIME "%i\n", + (int)player_status.elapsed_time); + } else { + fputs(PLAYLIST_STATE_FILE_STATE_STOP "\n", fp); + + if (playlist->current >= 0) + fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", + queue_order_to_position(&playlist->queue, + playlist->current)); + } + + fprintf(fp, PLAYLIST_STATE_FILE_RANDOM "%i\n", playlist->queue.random); + fprintf(fp, PLAYLIST_STATE_FILE_REPEAT "%i\n", playlist->queue.repeat); + fprintf(fp, PLAYLIST_STATE_FILE_SINGLE "%i\n", playlist->queue.single); + fprintf(fp, PLAYLIST_STATE_FILE_CONSUME "%i\n", playlist->queue.consume); - fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CROSSFADE, - (int)(getPlayerCrossFade())); - fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_BEGIN); + fprintf(fp, PLAYLIST_STATE_FILE_CROSSFADE "%i\n", + (int)(pc_get_cross_fade())); + fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n", pc_get_mixramp_db()); + fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n", + pc_get_mixramp_delay()); + fputs(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n", fp); queue_save(fp, &playlist->queue); - fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_END); + fputs(PLAYLIST_STATE_FILE_PLAYLIST_END "\n", fp); } static void -playlist_state_load(FILE *fp, struct playlist *playlist, char *buffer) +playlist_state_load(FILE *fp, GString *buffer, struct playlist *playlist) { - int song; - - if (!fgets(buffer, PLAYLIST_BUFFER_SIZE, fp)) { + const char *line = read_text_line(fp, buffer); + if (line == NULL) { g_warning("No playlist in state file"); return; } - while (!g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_PLAYLIST_END)) { - g_strchomp(buffer); - - song = queue_load_song(&playlist->queue, buffer); + while (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_PLAYLIST_END)) { + queue_load_song(fp, buffer, line, &playlist->queue); - if (!fgets(buffer, PLAYLIST_BUFFER_SIZE, fp)) { - g_warning("'%s' not found in state file", - PLAYLIST_STATE_FILE_PLAYLIST_END); + line = read_text_line(fp, buffer); + if (line == NULL) { + g_warning("'" PLAYLIST_STATE_FILE_PLAYLIST_END + "' not found in state file"); break; } } @@ -109,87 +121,116 @@ 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, GString *buffer, + struct playlist *playlist) { int current = -1; int seek_time = 0; int state = PLAYER_STATE_STOP; - char buffer[PLAYLIST_BUFFER_SIZE]; bool random_mode = false; - while (fgets(buffer, sizeof(buffer), fp)) { - g_strchomp(buffer); + if (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_STATE)) + return false; - 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)) { + 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 ((line = read_text_line(fp, buffer)) != NULL) { + if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_TIME)) { seek_time = - atoi(&(buffer[strlen(PLAYLIST_STATE_FILE_TIME)])); - } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_REPEAT)) { + atoi(&(line[strlen(PLAYLIST_STATE_FILE_TIME)])); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_REPEAT)) { if (strcmp - (&(buffer[strlen(PLAYLIST_STATE_FILE_REPEAT)]), + (&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]), "1") == 0) { - setPlaylistRepeatStatus(playlist, true); + playlist_set_repeat(playlist, true); } else - setPlaylistRepeatStatus(playlist, false); - } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_SINGLE)) { + playlist_set_repeat(playlist, false); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_SINGLE)) { if (strcmp - (&(buffer[strlen(PLAYLIST_STATE_FILE_SINGLE)]), + (&(line[strlen(PLAYLIST_STATE_FILE_SINGLE)]), "1") == 0) { - setPlaylistSingleStatus(playlist, true); + playlist_set_single(playlist, true); } else - setPlaylistSingleStatus(playlist, false); - } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CONSUME)) { + playlist_set_single(playlist, false); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CONSUME)) { if (strcmp - (&(buffer[strlen(PLAYLIST_STATE_FILE_CONSUME)]), + (&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]), "1") == 0) { - setPlaylistConsumeStatus(playlist, true); + playlist_set_consume(playlist, true); } else - setPlaylistConsumeStatus(playlist, false); - } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CROSSFADE)) { - setPlayerCrossFade(atoi - (& - (buffer - [strlen - (PLAYLIST_STATE_FILE_CROSSFADE)]))); - } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_RANDOM)) { + playlist_set_consume(playlist, false); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CROSSFADE)) { + pc_set_cross_fade(atoi(line + strlen(PLAYLIST_STATE_FILE_CROSSFADE))); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDB)) { + pc_set_mixramp_db(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDB))); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY)) { + pc_set_mixramp_delay(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY))); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_RANDOM)) { random_mode = - strcmp(buffer + strlen(PLAYLIST_STATE_FILE_RANDOM), + strcmp(line + strlen(PLAYLIST_STATE_FILE_RANDOM), "1") == 0; - } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CURRENT)) { - current = atoi(&(buffer + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CURRENT)) { + current = atoi(&(line [strlen (PLAYLIST_STATE_FILE_CURRENT)])); - } else if (g_str_has_prefix(buffer, + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) { - if (state == PLAYER_STATE_STOP) - current = -1; - playlist_state_load(fp, playlist, buffer); + playlist_state_load(fp, buffer, playlist); } } - setPlaylistRandomStatus(playlist, random_mode); + playlist_set_random(playlist, random_mode); - if (state != PLAYER_STATE_STOP && !queue_is_empty(&playlist->queue)) { + if (!queue_is_empty(&playlist->queue)) { if (!queue_valid_position(&playlist->queue, current)) current = 0; - if (seek_time == 0) - playPlaylist(playlist, current); + /* enable all devices for the first time; this must be + called here, after the audio output states were + restored, before playback begins */ + if (state != PLAYER_STATE_STOP) + pc_update_audio(); + + if (state == PLAYER_STATE_STOP /* && config_option */) + playlist->current = current; + else if (seek_time == 0) + playlist_play(playlist, current); else - seekSongInPlaylist(playlist, current, seek_time); + playlist_seek_song(playlist, current, seek_time); if (state == PLAYER_STATE_PAUSE) - playerPause(); + pc_pause(); } + + return true; +} + +unsigned +playlist_state_get_hash(const struct playlist *playlist) +{ + struct player_status player_status; + + pc_get_status(&player_status); + + return playlist->queue.version ^ + (player_status.state != PLAYER_STATE_STOP + ? ((int)player_status.elapsed_time << 8) + : 0) ^ + (playlist->current >= 0 + ? (queue_order_to_position(&playlist->queue, + playlist->current) << 16) + : 0) ^ + ((int)pc_get_cross_fade() << 20) ^ + (player_status.state << 24) ^ + (playlist->queue.random << 27) ^ + (playlist->queue.repeat << 28) ^ + (playlist->queue.single << 29) ^ + (playlist->queue.consume << 30) ^ + (playlist->queue.random << 31); } diff --git a/src/playlist_state.h b/src/playlist_state.h index 989430264..8ca3657f2 100644 --- a/src/playlist_state.h +++ b/src/playlist_state.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,6 +25,8 @@ #ifndef PLAYLIST_STATE_H #define PLAYLIST_STATE_H +#include <glib.h> +#include <stdbool.h> #include <stdio.h> struct playlist; @@ -32,7 +34,17 @@ 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, GString *buffer, + struct playlist *playlist); + +/** + * Generates a hash number for the current state of the playlist and + * the playback options. This is used by timer_save_state_file() to + * determine whether the state has changed and the state file should + * be saved. + */ +unsigned +playlist_state_get_hash(const struct playlist *playlist); #endif diff --git a/src/playlist_vector.c b/src/playlist_vector.c new file mode 100644 index 000000000..7c1765a98 --- /dev/null +++ b/src/playlist_vector.c @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist_vector.h" + +#include <assert.h> +#include <string.h> +#include <glib.h> + +static struct playlist_metadata * +playlist_metadata_new(const char *name, time_t mtime) +{ + assert(name != NULL); + + struct playlist_metadata *pm = g_slice_new(struct playlist_metadata); + pm->name = g_strdup(name); + pm->mtime = mtime; + return pm; +} + +static void +playlist_metadata_free(struct playlist_metadata *pm) +{ + assert(pm != NULL); + assert(pm->name != NULL); + + g_free(pm->name); + g_slice_free(struct playlist_metadata, pm); +} + +void +playlist_vector_deinit(struct playlist_vector *pv) +{ + assert(pv != NULL); + + while (pv->head != NULL) { + struct playlist_metadata *pm = pv->head; + pv->head = pm->next; + playlist_metadata_free(pm); + } +} + +static struct playlist_metadata ** +playlist_vector_find_p(struct playlist_vector *pv, const char *name) +{ + assert(pv != NULL); + assert(name != NULL); + + struct playlist_metadata **pmp = &pv->head; + + for (;;) { + struct playlist_metadata *pm = *pmp; + if (pm == NULL) + return NULL; + + if (strcmp(pm->name, name) == 0) + return pmp; + + pmp = &pm->next; + } +} + +struct playlist_metadata * +playlist_vector_find(struct playlist_vector *pv, const char *name) +{ + struct playlist_metadata **pmp = playlist_vector_find_p(pv, name); + return pmp != NULL ? *pmp : NULL; +} + +void +playlist_vector_add(struct playlist_vector *pv, + const char *name, time_t mtime) +{ + struct playlist_metadata *pm = playlist_metadata_new(name, mtime); + pm->next = pv->head; + pv->head = pm; +} + +bool +playlist_vector_update_or_add(struct playlist_vector *pv, + const char *name, time_t mtime) +{ + struct playlist_metadata **pmp = playlist_vector_find_p(pv, name); + if (pmp != NULL) { + struct playlist_metadata *pm = *pmp; + if (mtime == pm->mtime) + return false; + + pm->mtime = mtime; + } else + playlist_vector_add(pv, name, mtime); + + return true; +} + +bool +playlist_vector_remove(struct playlist_vector *pv, const char *name) +{ + struct playlist_metadata **pmp = playlist_vector_find_p(pv, name); + if (pmp == NULL) + return false; + + struct playlist_metadata *pm = *pmp; + *pmp = pm->next; + + playlist_metadata_free(pm); + return true; +} diff --git a/src/playlist_vector.h b/src/playlist_vector.h new file mode 100644 index 000000000..566de5f00 --- /dev/null +++ b/src/playlist_vector.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_VECTOR_H +#define MPD_PLAYLIST_VECTOR_H + +#include <stdbool.h> +#include <stddef.h> +#include <sys/time.h> + +/** + * A directory entry pointing to a playlist file. + */ +struct playlist_metadata { + struct playlist_metadata *next; + + /** + * The UTF-8 encoded name of the playlist file. + */ + char *name; + + time_t mtime; +}; + +struct playlist_vector { + struct playlist_metadata *head; +}; + +static inline void +playlist_vector_init(struct playlist_vector *pv) +{ + pv->head = NULL; +} + +void +playlist_vector_deinit(struct playlist_vector *pv); + +struct playlist_metadata * +playlist_vector_find(struct playlist_vector *pv, const char *name); + +void +playlist_vector_add(struct playlist_vector *pv, + const char *name, time_t mtime); + +/** + * @return true if the vector or one of its items was modified + */ +bool +playlist_vector_update_or_add(struct playlist_vector *pv, + const char *name, time_t mtime); + +bool +playlist_vector_remove(struct playlist_vector *pv, const char *name); + +#endif /* SONGVEC_H */ diff --git a/src/poison.h b/src/poison.h index 5919c3cbe..9c7052c91 100644 --- a/src/poison.h +++ b/src/poison.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,8 +20,9 @@ #ifndef MPD_POISON_H #define MPD_POISON_H +#include "check.h" + #ifndef NDEBUG -#include "config.h" #ifdef HAVE_VALGRIND_MEMCHECK_H #include <valgrind/memcheck.h> diff --git a/src/queue.c b/src/queue.c index 16891d0aa..dd0b48cb5 100644 --- a/src/queue.c +++ b/src/queue.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "queue.h" #include "song.h" @@ -33,7 +34,7 @@ queue_generate_id(const struct queue *queue) if (cur >= queue->max_length * QUEUE_HASH_MULT) cur = 0; - } while (queue->idToPosition[cur] != -1); + } while (queue->id_to_position[cur] != -1); return cur; } @@ -43,14 +44,9 @@ queue_next_order(const struct queue *queue, unsigned order) { assert(order < queue->length); - if (queue->single) - { - if (queue->repeat && !queue->consume) - return order; - else - return -1; - } - if (order + 1 < queue->length) + if (queue->single && queue->repeat && !queue->consume) + return order; + else if (order + 1 < queue->length) return order + 1; else if (queue->repeat && (order > 0 || !queue->consume)) /* restart at first song */ @@ -111,7 +107,7 @@ queue_append(struct queue *queue, struct song *song) }; queue->order[queue->length] = queue->length; - queue->idToPosition[id] = queue->length; + queue->id_to_position[id] = queue->length; ++queue->length; @@ -132,8 +128,8 @@ queue_swap(struct queue *queue, unsigned position1, unsigned position2) queue->items[position1].version = queue->version; queue->items[position2].version = queue->version; - queue->idToPosition[id1] = position2; - queue->idToPosition[id2] = position1; + queue->id_to_position[id1] = position2; + queue->id_to_position[id2] = position1; } static void @@ -143,7 +139,7 @@ queue_move_song_to(struct queue *queue, unsigned from, unsigned to) queue->items[to] = queue->items[from]; queue->items[to].version = queue->version; - queue->idToPosition[from_id] = to; + queue->id_to_position[from_id] = to; } void @@ -163,7 +159,7 @@ queue_move(struct queue *queue, unsigned from, unsigned to) /* put song at _to_ */ - queue->idToPosition[item.id] = to; + queue->id_to_position[item.id] = to; queue->items[to] = item; queue->items[to].version = queue->version; @@ -203,7 +199,7 @@ queue_move_range(struct queue *queue, unsigned start, unsigned end, unsigned to) // Copy the original block back in, starting at to. for (unsigned i = start; i< end; i++) { - queue->idToPosition[items[i-start].id] = to + i - start; + queue->id_to_position[items[i-start].id] = to + i - start; queue->items[to + i - start] = items[i-start]; queue->items[to + i - start].version = queue->version; } @@ -243,7 +239,7 @@ queue_delete(struct queue *queue, unsigned position) /* release the song id */ - queue->idToPosition[id] = -1; + queue->id_to_position[id] = -1; /* delete song from songs array */ @@ -271,7 +267,7 @@ queue_clear(struct queue *queue) if (!song_in_database(item->song)) song_free(item->song); - queue->idToPosition[item->id] = -1; + queue->id_to_position[item->id] = -1; } queue->length = 0; @@ -291,11 +287,11 @@ queue_init(struct queue *queue, unsigned max_length) queue->items = g_new(struct queue_item, max_length); queue->order = g_malloc(sizeof(queue->order[0]) * max_length); - queue->idToPosition = g_malloc(sizeof(queue->idToPosition[0]) * + queue->id_to_position = g_malloc(sizeof(queue->id_to_position[0]) * max_length * QUEUE_HASH_MULT); for (unsigned i = 0; i < max_length * QUEUE_HASH_MULT; ++i) - queue->idToPosition[i] = -1; + queue->id_to_position[i] = -1; queue->rand = g_rand_new(); } @@ -307,7 +303,7 @@ queue_finish(struct queue *queue) g_free(queue->items); g_free(queue->order); - g_free(queue->idToPosition); + g_free(queue->id_to_position); g_rand_free(queue->rand); } diff --git a/src/queue.h b/src/queue.h index 9c7228fd8..05eeafa22 100644 --- a/src/queue.h +++ b/src/queue.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -74,8 +74,8 @@ struct queue { /** map order numbers to positions */ unsigned *order; - /** map song ids to posiitons */ - int *idToPosition; + /** map song ids to positions */ + int *id_to_position; /** repeat playback when the end of the queue has been reached? */ @@ -146,10 +146,10 @@ queue_id_to_position(const struct queue *queue, unsigned id) if (id >= queue->max_length * QUEUE_HASH_MULT) return -1; - assert(queue->idToPosition[id] >= -1); - assert(queue->idToPosition[id] < (int)queue->length); + assert(queue->id_to_position[id] >= -1); + assert(queue->id_to_position[id] < (int)queue->length); - return queue->idToPosition[id]; + return queue->id_to_position[id]; } static inline int diff --git a/src/queue_print.c b/src/queue_print.c index 2ca9ccc34..53ddfb689 100644 --- a/src/queue_print.c +++ b/src/queue_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,12 +17,14 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "queue_print.h" #include "queue.h" #include "song.h" #include "song_print.h" #include "locate.h" #include "client.h" +#include "mapper.h" /** * Send detailed information about a range of songs in the queue to a @@ -60,11 +62,8 @@ queue_print_uris(struct client *client, const struct queue *queue, assert(end <= queue_length(queue)); for (unsigned i = start; i < end; ++i) { - const struct song *song = queue_get(queue, i); - char *uri = song_get_uri(song); - - client_printf(client, "%i:%s\n", i, uri); - g_free(uri); + client_printf(client, "%i:", i); + song_print_uri(client, queue_get(queue, i)); } } diff --git a/src/queue_print.h b/src/queue_print.h index 02cbc8b76..d754a9673 100644 --- a/src/queue_print.h +++ b/src/queue_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/queue_save.c b/src/queue_save.c index 9a5a0e30f..afe04ca2d 100644 --- a/src/queue_save.c +++ b/src/queue_save.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,63 +17,90 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "queue_save.h" #include "queue.h" #include "song.h" #include "uri.h" #include "database.h" +#include "song_save.h" #include <stdlib.h> -void -queue_save(FILE *fp, const struct queue *queue) +static void +queue_save_database_song(FILE *fp, int idx, const struct song *song) { - for (unsigned i = 0; i < queue_length(queue); i++) { - const struct song *song = queue_get(queue, i); - char *uri = song_get_uri(song); + char *uri = song_get_uri(song); - fprintf(fp, "%i:%s\n", i, uri); - g_free(uri); - } + fprintf(fp, "%i:%s\n", idx, uri); + g_free(uri); } -static struct song * -get_song(const char *uri) +static void +queue_save_full_song(FILE *fp, const struct song *song) { - struct song *song; + song_save(fp, song); +} - song = db_get_song(uri); - if (song != NULL) - return song; +static void +queue_save_song(FILE *fp, int idx, const struct song *song) +{ + if (song_in_database(song)) + queue_save_database_song(fp, idx, song); + else + queue_save_full_song(fp, song); +} - if (uri_has_scheme(uri)) - return song_remote_new(uri); +void +queue_save(FILE *fp, const struct queue *queue) +{ + for (unsigned i = 0; i < queue_length(queue); i++) + queue_save_song(fp, i, queue_get(queue, i)); +} - return NULL; +static struct song * +get_song(const char *uri) +{ + return uri_has_scheme(uri) + ? song_remote_new(uri) + : db_get_song(uri); } -int -queue_load_song(struct queue *queue, const char *line) +void +queue_load_song(FILE *fp, GString *buffer, const char *line, + struct queue *queue) { - long ret; - char *endptr; struct song *song; if (queue_is_full(queue)) - return -1; + return; - ret = strtol(line, &endptr, 10); - if (ret < 0 || *endptr != ':' || endptr[1] == 0) { - g_warning("Malformed playlist line in state file"); - return -1; - } + if (g_str_has_prefix(line, SONG_BEGIN)) { + const char *uri = line + sizeof(SONG_BEGIN) - 1; + if (!uri_has_scheme(uri) && !g_path_is_absolute(uri)) + return; - line = endptr + 1; + GError *error = NULL; + song = song_load(fp, NULL, uri, buffer, &error); + if (song == NULL) { + g_warning("%s", error->message); + g_error_free(error); + return; + } + } else { + char *endptr; + long ret = strtol(line, &endptr, 10); + if (ret < 0 || *endptr != ':' || endptr[1] == 0) { + g_warning("Malformed playlist line in state file"); + return; + } - song = get_song(line); - if (song == NULL) - return -1; + line = endptr + 1; + + song = get_song(line); + if (song == NULL) + return; + } queue_append(queue, song); - return ret; } diff --git a/src/queue_save.h b/src/queue_save.h index 07209b8d0..287683390 100644 --- a/src/queue_save.h +++ b/src/queue_save.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,6 +25,7 @@ #ifndef QUEUE_SAVE_H #define QUEUE_SAVE_H +#include <glib.h> #include <stdio.h> struct queue; @@ -33,10 +34,10 @@ void queue_save(FILE *fp, const struct queue *queue); /** - * Loads one song from the state file line and returns its number. - * Returns -1 on failure. + * Loads one song from the state file and appends it to the queue. */ -int -queue_load_song(struct queue *queue, const char *line); +void +queue_load_song(FILE *fp, GString *buffer, const char *line, + struct queue *queue); #endif diff --git a/src/refcount.h b/src/refcount.h new file mode 100644 index 000000000..87a2715a4 --- /dev/null +++ b/src/refcount.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file + * + * A very simple reference counting library. + */ + +#ifndef MPD_REFCOUNT_H +#define MPD_REFCOUNT_H + +#include <glib.h> + +struct refcount { + gint n; +}; + +static inline void +refcount_init(struct refcount *r) +{ + r->n = 1; +} + +static inline void +refcount_inc(struct refcount *r) +{ + g_atomic_int_inc(&r->n); +} + +/** + * @return true if the number of references has been dropped to 0 + */ +static inline bool +refcount_dec(struct refcount *r) +{ + return g_atomic_int_dec_and_test(&r->n); +} + +#endif diff --git a/src/replay_gain.c b/src/replay_gain.c deleted file mode 100644 index bcb501e54..000000000 --- a/src/replay_gain.c +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2003-2009 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -/* - * (c)2004 replayGain code by AliasMrJones - */ - -#include "replay_gain.h" -#include "conf.h" -#include "audio_format.h" -#include "pcm_volume.h" - -#include <glib.h> -#include <stdlib.h> -#include <string.h> -#include <math.h> - -static const char *const replay_gain_mode_names[] = { - [REPLAY_GAIN_ALBUM] = "album", - [REPLAY_GAIN_TRACK] = "track", -}; - -enum replay_gain_mode replay_gain_mode = REPLAY_GAIN_OFF; - -static float replay_gain_preamp = 1.0; - -void replay_gain_global_init(void) -{ - const struct config_param *param = config_get_param(CONF_REPLAYGAIN); - - if (!param) - return; - - if (strcmp(param->value, "track") == 0) { - replay_gain_mode = REPLAY_GAIN_TRACK; - } else if (strcmp(param->value, "album") == 0) { - replay_gain_mode = REPLAY_GAIN_ALBUM; - } else { - g_error("replaygain value \"%s\" at line %i is invalid\n", - param->value, param->line); - } - - param = config_get_param(CONF_REPLAYGAIN_PREAMP); - - if (param) { - char *test; - float f = strtod(param->value, &test); - - if (*test != '\0') { - g_error("Replaygain preamp \"%s\" is not a number at " - "line %i\n", param->value, param->line); - } - - if (f < -15 || f > 15) { - g_error("Replaygain preamp \"%s\" is not between -15 and" - "15 at line %i\n", param->value, param->line); - } - - replay_gain_preamp = pow(10, f / 20.0); - } -} - -static float calc_replay_gain_scale(float gain, float peak) -{ - float scale; - - if (gain == 0.0) - return (1); - scale = pow(10.0, gain / 20.0); - scale *= replay_gain_preamp; - if (scale > 15.0) - scale = 15.0; - - if (scale * peak > 1.0) { - scale = 1.0 / peak; - } - return (scale); -} - -struct replay_gain_info *replay_gain_info_new(void) -{ - struct replay_gain_info *ret = g_new(struct replay_gain_info, 1); - - for (unsigned i = 0; i < G_N_ELEMENTS(ret->tuples); ++i) { - ret->tuples[i].gain = 0.0; - ret->tuples[i].peak = 0.0; - } - - /* set to -1 so that we know in replay_gain_apply to compute the scale */ - ret->scale = -1.0; - - return ret; -} - -void replay_gain_info_free(struct replay_gain_info *info) -{ - g_free(info); -} - -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) - return; - - 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); - - info->scale = calc_replay_gain_scale(tuple->gain, tuple->peak); - } - - pcm_volume(buffer, size, format, pcm_float_to_volume(info->scale)); -} diff --git a/src/replay_gain_config.c b/src/replay_gain_config.c new file mode 100644 index 000000000..bbfe127a7 --- /dev/null +++ b/src/replay_gain_config.c @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "replay_gain_config.h" +#include "playlist.h" +#include "conf.h" +#include "idle.h" +#include "mpd_error.h" + +#include <glib.h> + +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> + +static const char *const replay_gain_mode_names[] = { + [REPLAY_GAIN_ALBUM] = "album", + [REPLAY_GAIN_TRACK] = "track", +}; + +enum replay_gain_mode replay_gain_mode = REPLAY_GAIN_OFF; + +const bool DEFAULT_REPLAYGAIN_LIMIT = true; + +float replay_gain_preamp = 1.0; +float replay_gain_missing_preamp = 1.0; +bool replay_gain_limit; + +const char * +replay_gain_get_mode_string(void) +{ + switch (replay_gain_mode) { + case REPLAY_GAIN_AUTO: + return "auto"; + + case REPLAY_GAIN_OFF: + return "off"; + + case REPLAY_GAIN_TRACK: + return "track"; + + case REPLAY_GAIN_ALBUM: + return "album"; + } + + /* unreachable */ + assert(false); + return "off"; +} + +bool +replay_gain_set_mode_string(const char *p) +{ + assert(p != NULL); + + if (strcmp(p, "off") == 0) + replay_gain_mode = REPLAY_GAIN_OFF; + else if (strcmp(p, "track") == 0) + replay_gain_mode = REPLAY_GAIN_TRACK; + else if (strcmp(p, "album") == 0) + replay_gain_mode = REPLAY_GAIN_ALBUM; + else if (strcmp(p, "auto") == 0) + replay_gain_mode = REPLAY_GAIN_AUTO; + else + return false; + + idle_add(IDLE_OPTIONS); + + return true; +} + +void replay_gain_global_init(void) +{ + const struct config_param *param = config_get_param(CONF_REPLAYGAIN); + + if (param != NULL && !replay_gain_set_mode_string(param->value)) { + MPD_ERROR("replaygain value \"%s\" at line %i is invalid\n", + param->value, param->line); + } + + param = config_get_param(CONF_REPLAYGAIN_PREAMP); + + if (param) { + char *test; + float f = strtod(param->value, &test); + + if (*test != '\0') { + MPD_ERROR("Replaygain preamp \"%s\" is not a number at " + "line %i\n", param->value, param->line); + } + + if (f < -15 || f > 15) { + MPD_ERROR("Replaygain preamp \"%s\" is not between -15 and" + "15 at line %i\n", param->value, param->line); + } + + 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') { + MPD_ERROR("Replaygain missing preamp \"%s\" is not a number at " + "line %i\n", param->value, param->line); + } + + if (f < -15 || f > 15) { + MPD_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); + } + + replay_gain_limit = config_get_bool(CONF_REPLAYGAIN_LIMIT, DEFAULT_REPLAYGAIN_LIMIT); +} + +enum replay_gain_mode replay_gain_get_real_mode(void) +{ + enum replay_gain_mode rgm; + + rgm = replay_gain_mode; + + if (rgm == REPLAY_GAIN_AUTO) + rgm = g_playlist.queue.random ? REPLAY_GAIN_TRACK : REPLAY_GAIN_ALBUM; + + return rgm; +} diff --git a/src/replay_gain.h b/src/replay_gain_config.h index aa48f3f14..8fb77a5f6 100644 --- a/src/replay_gain.h +++ b/src/replay_gain_config.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -16,44 +16,40 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -/* - * (c)2004 replayGain code by AliasMrJones - */ -#ifndef MPD_REPLAY_GAIN_H -#define MPD_REPLAY_GAIN_H +#ifndef MPD_REPLAY_GAIN_CONFIG_H +#define MPD_REPLAY_GAIN_CONFIG_H -enum replay_gain_mode { - REPLAY_GAIN_OFF = -1, - REPLAY_GAIN_ALBUM, - REPLAY_GAIN_TRACK, -}; +#include "check.h" +#include "replay_gain_info.h" -struct audio_format; +#include <stdbool.h> extern enum replay_gain_mode replay_gain_mode; - -struct replay_gain_tuple { - float gain; - float peak; -}; - -struct replay_gain_info { - struct replay_gain_tuple tuples[2]; - - /* used internally by mpd, to mess with it */ - float scale; -}; - -struct replay_gain_info * -replay_gain_info_new(void); - -void replay_gain_info_free(struct replay_gain_info *info); +extern float replay_gain_preamp; +extern float replay_gain_missing_preamp; +extern bool replay_gain_limit; void replay_gain_global_init(void); -void -replay_gain_apply(struct replay_gain_info *info, char *buffer, int bufferSize, - const struct audio_format *format); +/** + * Returns the current replay gain mode as a machine-readable string. + */ +const char * +replay_gain_get_mode_string(void); + +/** + * Sets the replay gain mode, parsed from a string. + * + * @return true on success, false if the string could not be parsed + */ +bool +replay_gain_set_mode_string(const char *p); + +/** + * Returns the "real" mode according to the "auto" setting" + */ +enum replay_gain_mode +replay_gain_get_real_mode(void); #endif diff --git a/src/replay_gain_info.c b/src/replay_gain_info.c new file mode 100644 index 000000000..3b4ab4577 --- /dev/null +++ b/src/replay_gain_info.c @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "replay_gain_info.h" + +float +replay_gain_tuple_scale(const struct replay_gain_tuple *tuple, float preamp, float missing_preamp, bool peak_limit) +{ + float scale; + + if (replay_gain_tuple_defined(tuple)) { + scale = pow(10.0, tuple->gain / 20.0); + scale *= preamp; + if (scale > 15.0) + scale = 15.0; + + if (peak_limit && scale * tuple->peak > 1.0) + scale = 1.0 / tuple->peak; + } else + scale = missing_preamp; + + return scale; +} + +void +replay_gain_info_complete(struct replay_gain_info *info) +{ + if (!replay_gain_tuple_defined(&info->tuples[REPLAY_GAIN_ALBUM])) + info->tuples[REPLAY_GAIN_ALBUM] = + info->tuples[REPLAY_GAIN_TRACK]; +} diff --git a/src/replay_gain_info.h b/src/replay_gain_info.h new file mode 100644 index 000000000..83b46df84 --- /dev/null +++ b/src/replay_gain_info.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2010 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_REPLAY_GAIN_INFO_H +#define MPD_REPLAY_GAIN_INFO_H + +#include "check.h" + +#include <stdbool.h> +#include <math.h> + +enum replay_gain_mode { + REPLAY_GAIN_AUTO = -2, + REPLAY_GAIN_OFF, + REPLAY_GAIN_ALBUM, + REPLAY_GAIN_TRACK, +}; + +struct replay_gain_tuple { + float gain; + float peak; +}; + +struct replay_gain_info { + struct replay_gain_tuple tuples[2]; +}; + +static inline void +replay_gain_tuple_init(struct replay_gain_tuple *tuple) +{ + tuple->gain = INFINITY; + tuple->peak = 0.0; +} + +static inline void +replay_gain_info_init(struct replay_gain_info *info) +{ + replay_gain_tuple_init(&info->tuples[REPLAY_GAIN_ALBUM]); + replay_gain_tuple_init(&info->tuples[REPLAY_GAIN_TRACK]); +} + +static inline bool +replay_gain_tuple_defined(const struct replay_gain_tuple *tuple) +{ + return !isinf(tuple->gain); +} + +float +replay_gain_tuple_scale(const struct replay_gain_tuple *tuple, float preamp, float missing_preamp, bool peak_limit); + +/** + * Attempt to auto-complete missing data. In particular, if album + * information is missing, track gain is used. + */ +void +replay_gain_info_complete(struct replay_gain_info *info); + +#endif diff --git a/src/riff.c b/src/riff.c index a8ea9dd42..2e8648ff6 100644 --- a/src/riff.c +++ b/src/riff.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" /* must be first for large file support */ #include "riff.h" #include <glib.h> diff --git a/src/riff.h b/src/riff.h index 470985105..bfcb69a7d 100644 --- a/src/riff.h +++ b/src/riff.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/server_socket.c b/src/server_socket.c new file mode 100644 index 000000000..bb7a6f097 --- /dev/null +++ b/src/server_socket.c @@ -0,0 +1,444 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "server_socket.h" +#include "socket_util.h" +#include "fd_util.h" +#include "glib_compat.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <assert.h> + +#ifdef WIN32 +#define WINVER 0x0501 +#include <ws2tcpip.h> +#include <winsock.h> +#else +#include <netinet/in.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <netdb.h> +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "listen" + +#define DEFAULT_PORT 6600 + +struct one_socket { + struct one_socket *next; + struct server_socket *parent; + + unsigned serial; + + int fd; + guint source_id; + + char *path; + + size_t address_length; + struct sockaddr address; +}; + +struct server_socket { + server_socket_callback_t callback; + void *callback_ctx; + + struct one_socket *sockets, **sockets_tail_r; + unsigned next_serial; +}; + +static GQuark +server_socket_quark(void) +{ + return g_quark_from_static_string("server_socket"); +} + +struct server_socket * +server_socket_new(server_socket_callback_t callback, void *callback_ctx) +{ + struct server_socket *ss = g_new(struct server_socket, 1); + ss->callback = callback; + ss->callback_ctx = callback_ctx; + ss->sockets = NULL; + ss->sockets_tail_r = &ss->sockets; + ss->next_serial = 1; + return ss; +} + +void +server_socket_free(struct server_socket *ss) +{ + server_socket_close(ss); + + while (ss->sockets != NULL) { + struct one_socket *s = ss->sockets; + ss->sockets = s->next; + + assert(s->fd < 0); + + g_free(s->path); + g_free(s); + } + + g_free(ss); +} + +/** + * Wraper for sockaddr_to_string() which never fails. + */ +static char * +one_socket_to_string(const struct one_socket *s) +{ + char *p = sockaddr_to_string(&s->address, s->address_length, NULL); + if (p == NULL) + p = g_strdup("[unknown]"); + return p; +} + +static int +get_remote_uid(int fd) +{ +#ifdef HAVE_STRUCT_UCRED + struct ucred cred; + socklen_t len = sizeof (cred); + + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0) + return 0; + + return cred.uid; +#else +#ifdef HAVE_GETPEEREID + uid_t euid; + gid_t egid; + + if (getpeereid(fd, &euid, &egid) == 0) + return euid; +#else + (void)fd; +#endif + return -1; +#endif +} + +static gboolean +server_socket_in_event(G_GNUC_UNUSED GIOChannel *source, + G_GNUC_UNUSED GIOCondition condition, + gpointer data) +{ + struct one_socket *s = data; + + struct sockaddr_storage address; + size_t address_length = sizeof(address); + int fd = accept_cloexec_nonblock(s->fd, (struct sockaddr*)&address, + &address_length); + if (fd >= 0) + s->parent->callback(fd, (const struct sockaddr*)&address, + address_length, get_remote_uid(fd), + s->parent->callback_ctx); + else + g_warning("accept() failed: %s", g_strerror(errno)); + + return true; +} + +bool +server_socket_open(struct server_socket *ss, GError **error_r) +{ + struct one_socket *good = NULL, *bad = NULL; + GError *last_error = NULL; + + for (struct one_socket *s = ss->sockets; s != NULL; s = s->next) { + assert(s->serial > 0); + assert(good == NULL || s->serial >= good->serial); + assert(s->fd < 0); + + if (bad != NULL && s->serial != bad->serial) { + server_socket_close(ss); + g_propagate_error(error_r, last_error); + return false; + } + + GError *error = NULL; + s->fd = socket_bind_listen(s->address.sa_family, SOCK_STREAM, 0, + &s->address, s->address_length, 5, + &error); + if (s->fd < 0) { + if (good != NULL && good->serial == s->serial) { + char *address_string = one_socket_to_string(s); + char *good_string = one_socket_to_string(good); + g_warning("bind to '%s' failed: %s " + "(continuing anyway, because " + "binding to '%s' succeeded)", + address_string, error->message, + good_string); + g_free(address_string); + g_free(good_string); + g_error_free(error); + } else if (bad == NULL) { + bad = s; + + char *address_string = one_socket_to_string(s); + g_propagate_prefixed_error(&last_error, error, + "Failed to bind to '%s': ", + address_string); + g_free(address_string); + } else + g_error_free(error); + continue; + } + + /* allow everybody to connect */ + + if (s->path != NULL) + chmod(s->path, 0666); + + /* register in the GLib main loop */ + + GIOChannel *channel = g_io_channel_unix_new(s->fd); + s->source_id = g_io_add_watch(channel, G_IO_IN, + server_socket_in_event, s); + g_io_channel_unref(channel); + + /* mark this socket as "good", and clear previous + errors */ + + good = s; + + if (bad != NULL) { + bad = NULL; + g_error_free(last_error); + last_error = NULL; + } + } + + if (bad != NULL) { + server_socket_close(ss); + g_propagate_error(error_r, last_error); + return false; + } + + return true; +} + +void +server_socket_close(struct server_socket *ss) +{ + for (struct one_socket *s = ss->sockets; s != NULL; s = s->next) { + if (s->fd < 0) + continue; + + g_source_remove(s->source_id); + close(s->fd); + s->fd = -1; + } +} + +static struct one_socket * +one_socket_new(unsigned serial, const struct sockaddr *address, + size_t address_length) +{ + assert(address != NULL); + assert(address_length > 0); + + struct one_socket *s = g_malloc(sizeof(*s) - sizeof(s->address) + + address_length); + s->next = NULL; + s->serial = serial; + s->fd = -1; + s->path = NULL; + s->address_length = address_length; + memcpy(&s->address, address, address_length); + + return s; +} + +static struct one_socket * +server_socket_add_address(struct server_socket *ss, + const struct sockaddr *address, + size_t address_length) +{ + assert(ss != NULL); + assert(ss->sockets_tail_r != NULL); + assert(*ss->sockets_tail_r == NULL); + + struct one_socket *s = one_socket_new(ss->next_serial, + address, address_length); + s->parent = ss; + *ss->sockets_tail_r = s; + ss->sockets_tail_r = &s->next; + + return s; +} + +#ifdef HAVE_TCP + +/** + * Add a listener on a port on all IPv4 interfaces. + * + * @param port the TCP port + */ +static void +server_socket_add_port_ipv4(struct server_socket *ss, unsigned port) +{ + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + sin.sin_port = htons(port); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_ANY; + + server_socket_add_address(ss, (const struct sockaddr *)&sin, + sizeof(sin)); +} + +#ifdef HAVE_IPV6 +/** + * Add a listener on a port on all IPv6 interfaces. + * + * @param port the TCP port + */ +static void +server_socket_add_port_ipv6(struct server_socket *ss, unsigned port) +{ + struct sockaddr_in6 sin; + memset(&sin, 0, sizeof(sin)); + sin.sin6_port = htons(port); + sin.sin6_family = AF_INET6; + + server_socket_add_address(ss, (const struct sockaddr *)&sin, + sizeof(sin)); +} +#endif /* HAVE_IPV6 */ + +#endif /* HAVE_TCP */ + +bool +server_socket_add_port(struct server_socket *ss, unsigned port, + GError **error_r) +{ +#ifdef HAVE_TCP + if (port == 0 || port > 0xffff) { + g_set_error(error_r, server_socket_quark(), 0, + "Invalid TCP port"); + return false; + } + +#ifdef HAVE_IPV6 + server_socket_add_port_ipv6(ss, port); +#endif + server_socket_add_port_ipv4(ss, port); + + ++ss->next_serial; + + return true; +#else /* HAVE_TCP */ + (void)ss; + (void)port; + + g_set_error(error_r, server_socket_quark(), 0, + "TCP support is disabled"); + return false; +#endif /* HAVE_TCP */ +} + +bool +server_socket_add_host(struct server_socket *ss, const char *hostname, + unsigned port, GError **error_r) +{ +#ifdef HAVE_TCP + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + char service[20]; + g_snprintf(service, sizeof(service), "%u", port); + + struct addrinfo *ai; + int ret = getaddrinfo(hostname, service, &hints, &ai); + if (ret != 0) { + g_set_error(error_r, server_socket_quark(), ret, + "Failed to look up host \"%s\": %s", + hostname, gai_strerror(ret)); + return false; + } + + for (const struct addrinfo *i = ai; i != NULL; i = i->ai_next) + server_socket_add_address(ss, i->ai_addr, i->ai_addrlen); + + freeaddrinfo(ai); + + ++ss->next_serial; + + return true; +#else /* HAVE_TCP */ + (void)ss; + (void)hostname; + (void)port; + + g_set_error(error_r, server_socket_quark(), 0, + "TCP support is disabled"); + return false; +#endif /* HAVE_TCP */ +} + +bool +server_socket_add_path(struct server_socket *ss, const char *path, + GError **error_r) +{ +#ifdef HAVE_UN + struct sockaddr_un s_un; + + size_t path_length = strlen(path); + if (path_length >= sizeof(s_un.sun_path)) { + g_set_error(error_r, server_socket_quark(), 0, + "UNIX socket path is too long"); + return false; + } + + unlink(path); + + s_un.sun_family = AF_UNIX; + memcpy(s_un.sun_path, path, path_length + 1); + + struct one_socket *s = + server_socket_add_address(ss, (const struct sockaddr *)&s_un, + sizeof(s_un)); + s->path = g_strdup(path); + + return true; +#else /* !HAVE_UN */ + (void)ss; + (void)path; + + g_set_error(error_r, server_socket_quark(), 0, + "UNIX domain socket support is disabled"); + return false; +#endif /* !HAVE_UN */ +} + diff --git a/src/server_socket.h b/src/server_socket.h new file mode 100644 index 000000000..ae0ce0c8d --- /dev/null +++ b/src/server_socket.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2003-2010 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_SERVER_SOCKET_H +#define MPD_SERVER_SOCKET_H + +#include <stdbool.h> + +#include <glib.h> + +struct sockaddr; + +typedef void (*server_socket_callback_t)(int fd, + const struct sockaddr *address, + size_t address_length, int uid, + void *ctx); + +struct server_socket * +server_socket_new(server_socket_callback_t callback, void *callback_ctx); + +void +server_socket_free(struct server_socket *ss); + +bool +server_socket_open(struct server_socket *ss, GError **error_r); + +void +server_socket_close(struct server_socket *ss); + +/** + * Add a listener on a port on all interfaces. + * + * @param port the TCP port + * @param error_r location to store the error occuring, or NULL to + * ignore errors + * @return true on success + */ +bool +server_socket_add_port(struct server_socket *ss, unsigned port, + GError **error_r); + +/** + * Resolves a host name, and adds listeners on all addresses in the + * result set. + * + * @param hostname the host name to be resolved + * @param port the TCP port + * @param error_r location to store the error occuring, or NULL to + * ignore errors + * @return true on success + */ +bool +server_socket_add_host(struct server_socket *ss, const char *hostname, + unsigned port, GError **error_r); + +/** + * Add a listener on a Unix domain socket. + * + * @param path the absolute socket path + * @param error_r location to store the error occuring, or NULL to + * ignore errors + * @return true on success + */ +bool +server_socket_add_path(struct server_socket *ss, const char *path, + GError **error_r); + +#endif diff --git a/src/sig_handlers.c b/src/sig_handlers.c index e70e1a159..8aa85cf88 100644 --- a/src/sig_handlers.c +++ b/src/sig_handlers.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "sig_handlers.h" #ifndef WIN32 @@ -24,6 +25,7 @@ #include "log.h" #include "main.h" #include "event_pipe.h" +#include "mpd_error.h" #include <glib.h> @@ -45,7 +47,7 @@ static void x_sigaction(int signum, const struct sigaction *act) { if (sigaction(signum, act, NULL) < 0) - g_error("sigaction() failed: %s", strerror(errno)); + MPD_ERROR("sigaction() failed: %s", strerror(errno)); } static void @@ -65,7 +67,7 @@ void initSigHandlers(void) sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sa.sa_handler = SIG_IGN; - while (sigaction(SIGPIPE, &sa, NULL) < 0 && errno == EINTR) ; + x_sigaction(SIGPIPE, &sa); sa.sa_handler = exit_signal_handler; x_sigaction(SIGINT, &sa); diff --git a/src/sig_handlers.h b/src/sig_handlers.h index efc7f797c..a578cd243 100644 --- a/src/sig_handlers.h +++ b/src/sig_handlers.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/socket_util.c b/src/socket_util.c index da4e414b6..0909765ba 100644 --- a/src/socket_util.c +++ b/src/socket_util.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "socket_util.h" #include "config.h" +#include "socket_util.h" +#include "fd_util.h" #include <errno.h> #include <unistd.h> @@ -27,6 +28,7 @@ #include <sys/socket.h> #include <netdb.h> #else /* G_OS_WIN32 */ +#define WINVER 0x0501 #include <ws2tcpip.h> #include <winsock.h> #endif /* G_OS_WIN32 */ @@ -102,15 +104,21 @@ socket_bind_listen(int domain, int type, int protocol, int passcred = 1; #endif - fd = socket(domain, type, protocol); + fd = socket_cloexec_nonblock(domain, type, protocol); if (fd < 0) { g_set_error(error, listen_quark(), errno, "Failed to create socket: %s", g_strerror(errno)); return -1; } +#ifdef WIN32 + const char *optval = (const char *)&reuse; +#else + const void *optval = &reuse; +#endif + ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, - &reuse, sizeof(reuse)); + optval, sizeof(reuse)); if (ret < 0) { g_set_error(error, listen_quark(), errno, "setsockopt() failed: %s", g_strerror(errno)); diff --git a/src/socket_util.h b/src/socket_util.h index dc129df40..7ef081362 100644 --- a/src/socket_util.h +++ b/src/socket_util.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/song.c b/src/song.c index 76c25f44f..13fd476b9 100644 --- a/src/song.c +++ b/src/song.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,47 +17,40 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "song.h" #include "uri.h" #include "directory.h" -#include "mapper.h" -#include "decoder_list.h" -#include "decoder_plugin.h" -#include "tag_ape.h" -#include "tag_id3.h" #include "tag.h" #include <glib.h> #include <assert.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <string.h> static struct song * -song_alloc(const char *url, struct directory *parent) +song_alloc(const char *uri, struct directory *parent) { - size_t urllen; + size_t uri_length; struct song *song; - assert(url); - urllen = strlen(url); - assert(urllen); - song = g_malloc(sizeof(*song) - sizeof(song->url) + urllen + 1); + assert(uri); + uri_length = strlen(uri); + assert(uri_length); + song = g_malloc(sizeof(*song) - sizeof(song->uri) + uri_length + 1); song->tag = NULL; - memcpy(song->url, url, urllen + 1); + memcpy(song->uri, uri, uri_length + 1); song->parent = parent; song->mtime = 0; + song->start_ms = song->end_ms = 0; return song; } struct song * -song_remote_new(const char *url) +song_remote_new(const char *uri) { - return song_alloc(url, NULL); + return song_alloc(uri, NULL); } struct song * @@ -68,32 +61,6 @@ song_file_new(const char *path, struct directory *parent) return song_alloc(path, parent); } -struct song * -song_file_load(const char *path, struct directory *parent) -{ - struct song *song; - bool ret; - - assert((parent == NULL) == (*path == '/')); - assert(!uri_has_scheme(path)); - assert(strchr(path, '\n') == NULL); - - song = song_file_new(path, parent); - - //in archive ? - if (parent != NULL && parent->device == DEVICE_INARCHIVE) { - ret = song_file_update_inarchive(song); - } else { - ret = song_file_update(song); - } - if (!ret) { - song_free(song); - return NULL; - } - - return song; -} - void song_free(struct song *song) { @@ -102,127 +69,27 @@ song_free(struct song *song) g_free(song); } -/** - * Attempts to load APE or ID3 tags from the specified file. - */ -static struct tag * -tag_load_fallback(const char *path) -{ - struct tag *tag = tag_ape_load(path); - if (tag == NULL) - tag = tag_id3_load(path); - return tag; -} - -/** - * The decoder plugin failed to load any tags: fall back to the APE or - * ID3 tag loader. - */ -static struct tag * -tag_fallback(const char *path, struct tag *tag) -{ - struct tag *fallback = tag_load_fallback(path); - - if (fallback != NULL) { - /* tag was successfully loaded: copy the song - duration, and destroy the old (empty) tag */ - fallback->time = tag->time; - tag_free(tag); - return fallback; - } else - /* no APE/ID3 tag found: return the empty tag */ - return tag; -} - -bool -song_file_update(struct song *song) -{ - const char *suffix; - char *path_fs; - const struct decoder_plugin *plugin; - struct stat st; - - assert(song_is_file(song)); - - /* check if there's a suffix and a plugin */ - - suffix = uri_get_suffix(song->url); - if (suffix == NULL) - return false; - - plugin = decoder_plugin_from_suffix(suffix, false); - if (plugin == NULL) - return false; - - path_fs = map_song_fs(song); - if (path_fs == NULL) - return false; - - if (song->tag != NULL) { - tag_free(song->tag); - song->tag = NULL; - } - - if (stat(path_fs, &st) < 0 || !S_ISREG(st.st_mode)) { - g_free(path_fs); - return false; - } - - song->mtime = st.st_mtime; - - do { - song->tag = plugin->tag_dup(path_fs); - if (song->tag != NULL) - break; - - plugin = decoder_plugin_from_suffix(suffix, true); - } while (plugin != NULL); - - if (song->tag != NULL && tag_is_empty(song->tag)) - song->tag = tag_fallback(path_fs, song->tag); - - g_free(path_fs); - return song->tag != NULL; -} - -bool -song_file_update_inarchive(struct song *song) -{ - const char *suffix; - const struct decoder_plugin *plugin; - - assert(song_is_file(song)); - - /* check if there's a suffix and a plugin */ - - suffix = uri_get_suffix(song->url); - if (suffix == NULL) - return false; - - plugin = decoder_plugin_from_suffix(suffix, false); - if (plugin == NULL) - return false; - - if (song->tag != NULL) - tag_free(song->tag); - - //accept every file that has music suffix - //because we dont support tag reading throught - //input streams - song->tag = tag_new(); - - return true; -} - char * song_get_uri(const struct song *song) { assert(song != NULL); - assert(*song->url); + assert(*song->uri); if (!song_in_database(song) || directory_is_root(song->parent)) - return g_strdup(song->url); + return g_strdup(song->uri); else return g_strconcat(directory_get_path(song->parent), - "/", song->url, NULL); + "/", song->uri, NULL); +} + +double +song_get_duration(const struct song *song) +{ + if (song->end_ms > 0) + return (song->end_ms - song->start_ms) / 1000.0; + + if (song->tag == NULL) + return 0; + + return song->tag->time - song->start_ms / 1000.0; } diff --git a/src/song.h b/src/song.h index 3044e910f..26a1dc806 100644 --- a/src/song.h +++ b/src/song.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,9 +24,6 @@ #include <stdbool.h> #include <sys/time.h> -#define SONG_BEGIN "songList begin" -#define SONG_END "songList end" - #define SONG_FILE "file: " #define SONG_TIME "Time: " @@ -34,12 +31,24 @@ struct song { struct tag *tag; struct directory *parent; time_t mtime; - char url[sizeof(int)]; + + /** + * Start of this sub-song within the file in milliseconds. + */ + unsigned start_ms; + + /** + * End of this sub-song within the file in milliseconds. + * Unused if zero. + */ + unsigned end_ms; + + char uri[sizeof(int)]; }; /** allocate a new song with a remote URL */ struct song * -song_remote_new(const char *url); +song_remote_new(const char *uri); /** allocate a new song with a local file name */ struct song * @@ -72,6 +81,9 @@ song_file_update_inarchive(struct song *song); char * song_get_uri(const struct song *song); +double +song_get_duration(const struct song *song); + static inline bool song_in_database(const struct song *song) { @@ -81,7 +93,7 @@ song_in_database(const struct song *song) static inline bool song_is_file(const struct song *song) { - return song_in_database(song) || song->url[0] == '/'; + return song_in_database(song) || song->uri[0] == '/'; } #endif diff --git a/src/song_print.c b/src/song_print.c index 64ab9f6b1..16239e03b 100644 --- a/src/song_print.c +++ b/src/song_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "song_print.h" #include "song.h" #include "songvec.h" @@ -24,46 +25,87 @@ #include "tag_print.h" #include "client.h" #include "uri.h" +#include "mapper.h" void -song_print_url(struct client *client, struct song *song) +song_print_uri(struct client *client, struct song *song) { if (song_in_database(song) && !directory_is_root(song->parent)) { client_printf(client, "%s%s/%s\n", SONG_FILE, - directory_get_path(song->parent), song->url); + directory_get_path(song->parent), song->uri); } else { char *allocated; const char *uri; - uri = allocated = uri_remove_auth(song->url); + uri = allocated = uri_remove_auth(song->uri); if (uri == NULL) - uri = song->url; + uri = song->uri; - client_printf(client, "%s%s\n", SONG_FILE, uri); + client_printf(client, "%s%s\n", SONG_FILE, + map_to_relative_path(uri)); g_free(allocated); } } -int +void song_print_info(struct client *client, struct song *song) { - song_print_url(client, song); + song_print_uri(client, song); + + if (song->end_ms > 0) + client_printf(client, "Range: %u.%03u-%u.%03u\n", + song->start_ms / 1000, + song->start_ms % 1000, + song->end_ms / 1000, + song->end_ms % 1000); + else if (song->start_ms > 0) + client_printf(client, "Range: %u.%03u-\n", + song->start_ms / 1000, + song->start_ms % 1000); + + 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), +#ifdef G_OS_WIN32 + "%Y-%m-%dT%H:%M:%SZ", +#else + "%FT%TZ", +#endif + tm2); + client_printf(client, "Last-Modified: %s\n", + timestamp); + } + } if (song->tag) tag_print(client, song->tag); - - return 0; } static int song_print_info_x(struct song *song, void *data) { struct client *client = data; - return song_print_info(client, song); + song_print_info(client, song); + + return 0; } -int songvec_print(struct client *client, const struct songvec *sv) +void +songvec_print(struct client *client, const struct songvec *sv) { - return songvec_for_each(sv, song_print_info_x, client); + songvec_for_each(sv, song_print_info_x, client); } diff --git a/src/song_print.h b/src/song_print.h index 291fd81c8..cb83f4711 100644 --- a/src/song_print.h +++ b/src/song_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,12 +24,13 @@ struct client; struct song; struct songvec; -int +void song_print_info(struct client *client, struct song *song); -int songvec_print(struct client *client, const struct songvec *sv); +void +songvec_print(struct client *client, const struct songvec *sv); void -song_print_url(struct client *client, struct song *song); +song_print_uri(struct client *client, struct song *song); #endif diff --git a/src/song_save.c b/src/song_save.c index 2d6297f3e..a1a573298 100644 --- a/src/song_save.c +++ b/src/song_save.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,11 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "song_save.h" #include "song.h" #include "tag_save.h" #include "directory.h" #include "tag.h" +#include "text_file.h" #include <glib.h> @@ -30,136 +32,107 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "song" -#define SONG_KEY "key: " -#define SONG_MTIME "mtime: " +#define SONG_MTIME "mtime" +#define SONG_END "song_end" -static void -song_save_url(FILE *fp, struct song *song) +static GQuark +song_save_quark(void) { - if (song->parent != NULL && song->parent->path != NULL) - fprintf(fp, SONG_FILE "%s/%s\n", - directory_get_path(song->parent), song->url); - else - fprintf(fp, SONG_FILE "%s\n", - song->url); + return g_quark_from_static_string("song_save"); } -static int -song_save(struct song *song, void *data) +void +song_save(FILE *fp, const struct song *song) { - FILE *fp = data; - - fprintf(fp, SONG_KEY "%s\n", song->url); + fprintf(fp, SONG_BEGIN "%s\n", song->uri); - song_save_url(fp, song); + if (song->end_ms > 0) + fprintf(fp, "Range: %u-%u\n", song->start_ms, song->end_ms); + else if (song->start_ms > 0) + fprintf(fp, "Range: %u-\n", song->start_ms); if (song->tag != NULL) tag_save(fp, song->tag); - fprintf(fp, SONG_MTIME "%li\n", (long)song->mtime); - - return 0; + fprintf(fp, SONG_MTIME ": %li\n", (long)song->mtime); + fprintf(fp, SONG_END "\n"); } -void songvec_save(FILE *fp, struct songvec *sv) +static int +song_save_callback(struct song *song, void *data) { - fprintf(fp, "%s\n", SONG_BEGIN); - songvec_for_each(sv, song_save, fp); - fprintf(fp, "%s\n", SONG_END); + FILE *fp = data; + song_save(fp, song); + return 0; } -static void -insertSongIntoList(struct songvec *sv, struct song *newsong) +void songvec_save(FILE *fp, const struct songvec *sv) { - struct song *existing = songvec_find(sv, newsong->url); - - if (!existing) { - songvec_add(sv, newsong); - if (newsong->tag) - tag_end_add(newsong->tag); - } else { /* prevent dupes, just update the existing song info */ - if (existing->mtime != newsong->mtime) { - if (existing->tag != NULL) - tag_free(existing->tag); - if (newsong->tag) - tag_end_add(newsong->tag); - existing->tag = newsong->tag; - existing->mtime = newsong->mtime; - newsong->tag = NULL; - } - song_free(newsong); - } + songvec_for_each(sv, song_save_callback, fp); } -static char * -matchesAnMpdTagItemKey(char *buffer, enum tag_type *itemType) +struct song * +song_load(FILE *fp, struct directory *parent, const char *uri, + GString *buffer, GError **error_r) { - int i; - - for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) { - size_t len = strlen(tag_item_names[i]); + struct song *song = parent != NULL + ? song_file_new(uri, parent) + : song_remote_new(uri); + char *line, *colon; + enum tag_type type; + const char *value; - if (0 == strncmp(tag_item_names[i], buffer, len) && - buffer[len] == ':') { - *itemType = i; - return g_strchug(buffer + len + 1); + while ((line = read_text_line(fp, buffer)) != NULL && + strcmp(line, SONG_END) != 0) { + colon = strchr(line, ':'); + if (colon == NULL || colon == line) { + if (song->tag != NULL) + tag_end_add(song->tag); + song_free(song); + + g_set_error(error_r, song_save_quark(), 0, + "unknown line in db: %s", line); + return NULL; } - } - return NULL; -} + *colon++ = 0; + value = g_strchug(colon); -void readSongInfoIntoList(FILE *fp, struct songvec *sv, - struct directory *parent) -{ - enum { - buffer_size = 32768, - }; - char *buffer = g_malloc(buffer_size); - struct song *song = NULL; - enum tag_type itemType; - const char *value; - - while (fgets(buffer, buffer_size, fp) && - !g_str_has_prefix(buffer, SONG_END)) { - g_strchomp(buffer); - - if (0 == strncmp(SONG_KEY, buffer, strlen(SONG_KEY))) { - if (song) - insertSongIntoList(sv, song); - - song = song_file_new(buffer + strlen(SONG_KEY), - parent); - } else if (*buffer == 0) { - /* ignore empty lines (starting with '\0') */ - } else if (song == NULL) { - g_error("Problems reading song info"); - } else if (0 == strncmp(SONG_FILE, buffer, strlen(SONG_FILE))) { - /* we don't need this info anymore */ - } else if ((value = matchesAnMpdTagItemKey(buffer, - &itemType)) != NULL) { + if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) { if (!song->tag) { song->tag = tag_new(); tag_begin_add(song->tag); } - tag_add_item(song->tag, itemType, value); - } else if (0 == strncmp(SONG_TIME, buffer, strlen(SONG_TIME))) { + tag_add_item(song->tag, type, value); + } else if (strcmp(line, "Time") == 0) { if (!song->tag) { song->tag = tag_new(); tag_begin_add(song->tag); } - song->tag->time = atoi(&(buffer[strlen(SONG_TIME)])); - } else if (0 == strncmp(SONG_MTIME, buffer, strlen(SONG_MTIME))) { - song->mtime = atoi(&(buffer[strlen(SONG_MTIME)])); + song->tag->time = atoi(value); + } else if (strcmp(line, SONG_MTIME) == 0) { + song->mtime = atoi(value); + } else if (strcmp(line, "Range") == 0) { + char *endptr; + + song->start_ms = strtoul(value, &endptr, 10); + if (*endptr == '-') + song->end_ms = strtoul(endptr + 1, NULL, 10); + } else { + if (song->tag != NULL) + tag_end_add(song->tag); + song_free(song); + + g_set_error(error_r, song_save_quark(), 0, + "unknown line in db: %s", line); + return NULL; } - else - g_error("unknown line in db: %s", buffer); } - g_free(buffer); + if (song->tag != NULL) + tag_end_add(song->tag); - if (song) - insertSongIntoList(sv, song); + return song; } diff --git a/src/song_save.h b/src/song_save.h index 370e42730..03285015a 100644 --- a/src/song_save.h +++ b/src/song_save.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,14 +20,32 @@ #ifndef MPD_SONG_SAVE_H #define MPD_SONG_SAVE_H +#include <glib.h> + #include <stdio.h> +#define SONG_BEGIN "song_begin: " + +struct song; struct songvec; struct directory; -void songvec_save(FILE *fp, struct songvec *sv); +void +song_save(FILE *fp, const struct song *song); + +void +songvec_save(FILE *fp, const struct songvec *sv); -void readSongInfoIntoList(FILE * fp, struct songvec *sv, - struct directory *parent); +/** + * Loads a song from the input file. Reading stops after the + * "song_end" line. + * + * @param error_r location to store the error occuring, or NULL to + * ignore errors + * @return true on success, false on error + */ +struct song * +song_load(FILE *fp, struct directory *parent, const char *uri, + GString *buffer, GError **error_r); #endif diff --git a/src/song_sticker.c b/src/song_sticker.c index 2758ff534..c3c64c8d1 100644 --- a/src/song_sticker.c +++ b/src/song_sticker.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "song_sticker.h" #include "song.h" #include "directory.h" diff --git a/src/song_sticker.h b/src/song_sticker.h index 9652052e0..6318ccf48 100644 --- a/src/song_sticker.h +++ b/src/song_sticker.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/song_update.c b/src/song_update.c new file mode 100644 index 000000000..b418b600e --- /dev/null +++ b/src/song_update.c @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "song.h" +#include "uri.h" +#include "directory.h" +#include "mapper.h" +#include "decoder_list.h" +#include "decoder_plugin.h" +#include "tag_ape.h" +#include "tag_id3.h" +#include "tag.h" +#include "input_stream.h" + +#include <glib.h> + +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <stdio.h> + +struct song * +song_file_load(const char *path, struct directory *parent) +{ + struct song *song; + bool ret; + + assert((parent == NULL) == g_path_is_absolute(path)); + assert(!uri_has_scheme(path)); + assert(strchr(path, '\n') == NULL); + + song = song_file_new(path, parent); + + //in archive ? + if (parent != NULL && parent->device == DEVICE_INARCHIVE) { + ret = song_file_update_inarchive(song); + } else { + ret = song_file_update(song); + } + if (!ret) { + song_free(song); + return NULL; + } + + return song; +} + +/** + * Attempts to load APE or ID3 tags from the specified file. + */ +static struct tag * +tag_load_fallback(const char *path) +{ + struct tag *tag = tag_ape_load(path); + if (tag == NULL) + tag = tag_id3_load(path); + return tag; +} + +/** + * The decoder plugin failed to load any tags: fall back to the APE or + * ID3 tag loader. + */ +static struct tag * +tag_fallback(const char *path, struct tag *tag) +{ + struct tag *fallback = tag_load_fallback(path); + + if (fallback != NULL) { + /* tag was successfully loaded: copy the song + duration, and destroy the old (empty) tag */ + fallback->time = tag->time; + tag_free(tag); + return fallback; + } else + /* no APE/ID3 tag found: return the empty tag */ + return tag; +} + +bool +song_file_update(struct song *song) +{ + const char *suffix; + char *path_fs; + const struct decoder_plugin *plugin; + struct stat st; + struct input_stream *is = NULL; + + assert(song_is_file(song)); + + /* check if there's a suffix and a plugin */ + + suffix = uri_get_suffix(song->uri); + if (suffix == NULL) + return false; + + plugin = decoder_plugin_from_suffix(suffix, NULL); + if (plugin == NULL) + return false; + + path_fs = map_song_fs(song); + if (path_fs == NULL) + return false; + + if (song->tag != NULL) { + tag_free(song->tag); + song->tag = NULL; + } + + if (stat(path_fs, &st) < 0 || !S_ISREG(st.st_mode)) { + g_free(path_fs); + return false; + } + + song->mtime = st.st_mtime; + + do { + /* load file tag */ + song->tag = decoder_plugin_tag_dup(plugin, path_fs); + if (song->tag != NULL) + break; + + /* fall back to stream tag */ + if (plugin->stream_tag != NULL) { + /* open the input_stream (if not already + open) */ + if (is == NULL) + is = input_stream_open(path_fs, NULL); + + /* now try the stream_tag() method */ + if (is != NULL) { + song->tag = decoder_plugin_stream_tag(plugin, + is); + if (song->tag != NULL) + break; + + input_stream_seek(is, 0, SEEK_SET, NULL); + } + } + + plugin = decoder_plugin_from_suffix(suffix, plugin); + } while (plugin != NULL); + + if (is != NULL) + input_stream_close(is); + + if (song->tag != NULL && tag_is_empty(song->tag)) + song->tag = tag_fallback(path_fs, song->tag); + + g_free(path_fs); + return song->tag != NULL; +} + +bool +song_file_update_inarchive(struct song *song) +{ + const char *suffix; + const struct decoder_plugin *plugin; + + assert(song_is_file(song)); + + /* check if there's a suffix and a plugin */ + + suffix = uri_get_suffix(song->uri); + if (suffix == NULL) + return false; + + plugin = decoder_plugin_from_suffix(suffix, false); + if (plugin == NULL) + return false; + + if (song->tag != NULL) + tag_free(song->tag); + + //accept every file that has music suffix + //because we dont support tag reading throught + //input streams + song->tag = tag_new(); + + return true; +} diff --git a/src/songvec.c b/src/songvec.c index efef02216..38bcbac88 100644 --- a/src/songvec.c +++ b/src/songvec.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "songvec.h" #include "song.h" #include "tag.h" @@ -29,6 +30,38 @@ static GMutex *nr_lock = NULL; +static const char * +tag_get_value_checked(const struct tag *tag, enum tag_type type) +{ + return tag != NULL + ? tag_get_value(tag, type) + : NULL; +} + +static int +compare_utf8_string(const char *a, const char *b) +{ + if (a == NULL) + return b == NULL ? 0 : -1; + + if (b == NULL) + return 1; + + return g_utf8_collate(a, b); +} + +/** + * Compare two string tag values, ignoring case. Either one may be + * NULL. + */ +static int +compare_string_tag_item(const struct tag *a, const struct tag *b, + enum tag_type type) +{ + return compare_utf8_string(tag_get_value_checked(a, type), + tag_get_value_checked(b, type)); +} + /** * Compare two tag values which should contain an integer value * (e.g. disc or track number). Either one may be NULL. @@ -51,14 +84,8 @@ compare_number_string(const char *a, const char *b) static int compare_tag_item(const struct tag *a, const struct tag *b, enum tag_type type) { - if (a == NULL) - return b == NULL ? 0 : -1; - - if (b == NULL) - return 1; - - return compare_number_string(tag_get_value(a, type), - tag_get_value(b, type)); + return compare_number_string(tag_get_value_checked(a, type), + tag_get_value_checked(b, type)); } /* Only used for sorting/searchin a songvec, not general purpose compares */ @@ -68,18 +95,23 @@ static int songvec_cmp(const void *s1, const void *s2) const struct song *b = ((const struct song * const *)s2)[0]; int ret; - /* first sort by disc */ - ret = compare_tag_item(a->tag, b->tag, TAG_ITEM_DISC); + /* first sort by album */ + ret = compare_string_tag_item(a->tag, b->tag, TAG_ALBUM); + if (ret != 0) + return ret; + + /* then sort by disc */ + ret = compare_tag_item(a->tag, b->tag, TAG_DISC); if (ret != 0) return ret; /* then by track number */ - ret = compare_tag_item(a->tag, b->tag, TAG_ITEM_TRACK); + ret = compare_tag_item(a->tag, b->tag, TAG_TRACK); if (ret != 0) return ret; /* still no difference? compare file name */ - return g_utf8_collate(a->url, b->url); + return g_utf8_collate(a->uri, b->uri); } static size_t sv_size(const struct songvec *sv) @@ -108,14 +140,14 @@ void songvec_sort(struct songvec *sv) } struct song * -songvec_find(const struct songvec *sv, const char *url) +songvec_find(const struct songvec *sv, const char *uri) { int i; struct song *ret = NULL; g_mutex_lock(nr_lock); for (i = sv->nr; --i >= 0; ) { - if (strcmp(sv->base[i]->url, url)) + if (strcmp(sv->base[i]->uri, uri)) continue; ret = sv->base[i]; break; @@ -182,7 +214,7 @@ songvec_for_each(const struct songvec *sv, struct song *song = sv->base[i]; assert(song); - assert(*song->url); + assert(*song->uri); prev_nr = sv->nr; g_mutex_unlock(nr_lock); /* fn() may block */ diff --git a/src/songvec.h b/src/songvec.h index 0fd207ed0..8a50b974b 100644 --- a/src/songvec.h +++ b/src/songvec.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -34,7 +34,7 @@ void songvec_deinit(void); void songvec_sort(struct songvec *sv); struct song * -songvec_find(const struct songvec *sv, const char *url); +songvec_find(const struct songvec *sv, const char *uri); int songvec_delete(struct songvec *sv, const struct song *del); diff --git a/src/state_file.c b/src/state_file.c index 9c6475cc8..55af25d5c 100644 --- a/src/state_file.c +++ b/src/state_file.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,14 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "state_file.h" #include "output_state.h" #include "playlist.h" +#include "playlist_state.h" #include "volume.h" +#include "text_file.h" +#include "glib_compat.h" #include <glib.h> #include <assert.h> @@ -30,28 +34,26 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "state_file" -static struct _sf_cb { - void (*reader)(FILE *); - void (*writer)(FILE *); -} sf_callbacks [] = { - { read_sw_volume_state, save_sw_volume_state }, - { readAudioDevicesState, saveAudioDevicesState }, - { readPlaylistState, savePlaylistState }, -}; - static char *state_file_path; /** the GLib source id for the save timer */ static guint save_state_source_id; +/** + * These version numbers determine whether we need to save the state + * file. If nothing has changed, we won't let the hard drive spin up. + */ +static unsigned prev_volume_version, prev_output_version, + prev_playlist_version; + static void state_file_write(void) { - unsigned int i; FILE *fp; - if (state_file_path == NULL) - return; + assert(state_file_path != NULL); + + g_debug("Saving state file %s", state_file_path); fp = fopen(state_file_path, "w"); if (G_UNLIKELY(!fp)) { @@ -60,21 +62,26 @@ state_file_write(void) return; } - for (i = 0; i < G_N_ELEMENTS(sf_callbacks); i++) - sf_callbacks[i].writer(fp); + save_sw_volume_state(fp); + audio_output_state_save(fp); + playlist_state_save(fp, &g_playlist); + + fclose(fp); - while(fclose(fp) && errno == EINTR) /* nothing */; + prev_volume_version = sw_volume_state_get_hash(); + prev_output_version = audio_output_state_get_version(); + prev_playlist_version = playlist_state_get_hash(&g_playlist); } static void state_file_read(void) { - unsigned int i; FILE *fp; + bool success; assert(state_file_path != NULL); - g_debug("Saving state file"); + g_debug("Loading state file %s", state_file_path); fp = fopen(state_file_path, "r"); if (G_UNLIKELY(!fp)) { @@ -82,12 +89,25 @@ 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); + + GString *buffer = g_string_sized_new(1024); + const char *line; + while ((line = read_text_line(fp, buffer)) != NULL) { + success = read_sw_volume_state(line) || + audio_output_state_read(line) || + playlist_state_restore(line, fp, buffer, &g_playlist); + if (!success) + g_warning("Unrecognized line in state file: %s", line); } - while(fclose(fp) && errno == EINTR) /* nothing */; + fclose(fp); + + prev_volume_version = sw_volume_state_get_hash(); + prev_output_version = audio_output_state_get_version(); + prev_playlist_version = playlist_state_get_hash(&g_playlist); + + + g_string_free(buffer, true); } /** @@ -97,6 +117,13 @@ state_file_read(void) static gboolean timer_save_state_file(G_GNUC_UNUSED gpointer data) { + if (prev_volume_version == sw_volume_state_get_hash() && + prev_output_version == audio_output_state_get_version() && + prev_playlist_version == playlist_state_get_hash(&g_playlist)) + /* nothing has changed - don't save the state file, + don't spin up the hard disk */ + return true; + state_file_write(); return true; } @@ -112,18 +139,22 @@ state_file_init(const char *path) state_file_path = g_strdup(path); state_file_read(); - save_state_source_id = g_timeout_add(5 * 60 * 1000, - timer_save_state_file, NULL); + save_state_source_id = g_timeout_add_seconds(5 * 60, + timer_save_state_file, + NULL); } void state_file_finish(void) { + if (state_file_path == NULL) + /* no state file configured, no cleanup required */ + return; + if (save_state_source_id != 0) g_source_remove(save_state_source_id); - if (state_file_path != NULL) - state_file_write(); + state_file_write(); g_free(state_file_path); } diff --git a/src/state_file.h b/src/state_file.h index d1e53d005..ec01fcbed 100644 --- a/src/state_file.h +++ b/src/state_file.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/stats.c b/src/stats.c index 01f6761f3..673d531ec 100644 --- a/src/stats.c +++ b/src/stats.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "stats.h" #include "database.h" #include "tag.h" @@ -52,11 +53,11 @@ visit_tag(struct visit_data *data, const struct tag *tag) const struct tag_item *item = tag->items[i]; switch (item->type) { - case TAG_ITEM_ARTIST: + case TAG_ARTIST: strset_add(data->artists, item->value); break; - case TAG_ITEM_ALBUM: + case TAG_ALBUM: strset_add(data->albums, item->value); break; @@ -113,7 +114,7 @@ int stats_print(struct client *client) stats.album_count, stats.song_count, (long)g_timer_elapsed(stats.timer, NULL), - (long)(getPlayerTotalPlayTime() + 0.5), + (long)(pc_get_total_play_time() + 0.5), stats.song_duration, db_get_mtime()); return 0; diff --git a/src/stats.h b/src/stats.h index ee1a3d5d6..fbb2e4a46 100644 --- a/src/stats.h +++ b/src/stats.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/sticker.c b/src/sticker.c index cded09fca..c59cdd078 100644 --- a/src/sticker.c +++ b/src/sticker.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "sticker.h" #include "idle.h" @@ -76,50 +77,69 @@ static const char sticker_sql_create[] = static sqlite3 *sticker_db; static sqlite3_stmt *sticker_stmt[G_N_ELEMENTS(sticker_sql)]; +static GQuark +sticker_quark(void) +{ + return g_quark_from_static_string("sticker"); +} + static sqlite3_stmt * -sticker_prepare(const char *sql) +sticker_prepare(const char *sql, GError **error_r) { int ret; sqlite3_stmt *stmt; ret = sqlite3_prepare_v2(sticker_db, sql, -1, &stmt, NULL); - if (ret != SQLITE_OK) - g_error("sqlite3_prepare_v2() failed: %s", - sqlite3_errmsg(sticker_db)); + if (ret != SQLITE_OK) { + g_set_error(error_r, sticker_quark(), ret, + "sqlite3_prepare_v2() failed: %s", + sqlite3_errmsg(sticker_db)); + return NULL; + } return stmt; } -void -sticker_global_init(const char *path) +bool +sticker_global_init(const char *path, GError **error_r) { int ret; if (path == NULL) /* not configured */ - return; + return true; /* open/create the sqlite database */ ret = sqlite3_open(path, &sticker_db); - if (ret != SQLITE_OK) - g_error("Failed to open sqlite database '%s': %s", - path, sqlite3_errmsg(sticker_db)); + if (ret != SQLITE_OK) { + g_set_error(error_r, sticker_quark(), ret, + "Failed to open sqlite database '%s': %s", + path, sqlite3_errmsg(sticker_db)); + return false; + } /* create the table and index */ ret = sqlite3_exec(sticker_db, sticker_sql_create, NULL, NULL, NULL); - if (ret != SQLITE_OK) - g_error("Failed to create sticker table: %s", - sqlite3_errmsg(sticker_db)); + if (ret != SQLITE_OK) { + g_set_error(error_r, sticker_quark(), ret, + "Failed to create sticker table: %s", + sqlite3_errmsg(sticker_db)); + return false; + } /* prepare the statements we're going to use */ for (unsigned i = 0; i < G_N_ELEMENTS(sticker_sql); ++i) { assert(sticker_sql[i] != NULL); - sticker_stmt[i] = sticker_prepare(sticker_sql[i]); + sticker_stmt[i] = sticker_prepare(sticker_sql[i], error_r); + if (sticker_stmt[i] == NULL) + return false; } + + return true; } void diff --git a/src/sticker.h b/src/sticker.h index 8e6410914..6cc0ebcee 100644 --- a/src/sticker.h +++ b/src/sticker.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -50,9 +50,13 @@ struct sticker; /** * Opens the sticker database (if path is not NULL). + * + * @param error_r location to store the error occuring, or NULL to + * ignore errors + * @return true on success, false on error */ -void -sticker_global_init(const char *path); +bool +sticker_global_init(const char *path, GError **error_r); /** * Close the sticker database. diff --git a/src/sticker_print.c b/src/sticker_print.c index 12dafd3f7..b158c8af3 100644 --- a/src/sticker_print.c +++ b/src/sticker_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "sticker_print.h" #include "sticker.h" #include "client.h" diff --git a/src/sticker_print.h b/src/sticker_print.h index 25f0deae2..ac542709c 100644 --- a/src/sticker_print.h +++ b/src/sticker_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/stored_playlist.c b/src/stored_playlist.c index 5ed7182f6..cd2818522 100644 --- a/src/stored_playlist.c +++ b/src/stored_playlist.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "stored_playlist.h" #include "playlist_save.h" #include "song.h" @@ -159,7 +160,7 @@ spl_save(GPtrArray *list, const char *utf8path) if (path_fs == NULL) return PLAYLIST_RESULT_BAD_NAME; - while (!(file = fopen(path_fs, "w")) && errno == EINTR); + file = fopen(path_fs, "w"); g_free(path_fs); if (file == NULL) return PLAYLIST_RESULT_ERRNO; @@ -169,7 +170,7 @@ spl_save(GPtrArray *list, const char *utf8path) playlist_print_uri(file, uri); } - while (fclose(file) != 0 && errno == EINTR); + fclose(file); return PLAYLIST_RESULT_SUCCESS; } @@ -188,7 +189,7 @@ spl_load(const char *utf8path) if (path_fs == NULL) return NULL; - while (!(file = fopen(path_fs, "r")) && errno == EINTR); + file = fopen(path_fs, "r"); g_free(path_fs); if (file == NULL) return NULL; @@ -226,7 +227,7 @@ spl_load(const char *utf8path) break; } - while (fclose(file) && errno == EINTR); + fclose(file); return list; } @@ -312,12 +313,12 @@ spl_clear(const char *utf8path) if (path_fs == NULL) return PLAYLIST_RESULT_BAD_NAME; - while (!(file = fopen(path_fs, "w")) && errno == EINTR); + file = fopen(path_fs, "w"); g_free(path_fs); if (file == NULL) return PLAYLIST_RESULT_ERRNO; - while (fclose(file) != 0 && errno == EINTR); + fclose(file); idle_add(IDLE_STORED_PLAYLIST); return PLAYLIST_RESULT_SUCCESS; @@ -392,26 +393,26 @@ spl_append_song(const char *utf8path, struct song *song) if (path_fs == NULL) return PLAYLIST_RESULT_BAD_NAME; - while (!(file = fopen(path_fs, "a")) && errno == EINTR); + file = fopen(path_fs, "a"); g_free(path_fs); if (file == NULL) return PLAYLIST_RESULT_ERRNO; if (fstat(fileno(file), &st) < 0) { int save_errno = errno; - while (fclose(file) != 0 && errno == EINTR); + fclose(file); errno = save_errno; return PLAYLIST_RESULT_ERRNO; } if (st.st_size / (MPD_PATH_MAX + 1) >= (off_t)playlist_max_length) { - while (fclose(file) != 0 && errno == EINTR); + fclose(file); return PLAYLIST_RESULT_TOO_LARGE; } playlist_print_song(file, song); - while (fclose(file) != 0 && errno == EINTR); + fclose(file); idle_add(IDLE_STORED_PLAYLIST); return PLAYLIST_RESULT_SUCCESS; diff --git a/src/stored_playlist.h b/src/stored_playlist.h index e78ce293b..3afdbb0f0 100644 --- a/src/stored_playlist.h +++ b/src/stored_playlist.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/strset.c b/src/strset.c index 474dd6642..e071fbc98 100644 --- a/src/strset.c +++ b/src/strset.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "strset.h" #include <assert.h> diff --git a/src/strset.h b/src/strset.h index 7b0c38c1a..9a7aa45e5 100644 --- a/src/strset.h +++ b/src/strset.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,11 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "tag.h" #include "tag_internal.h" #include "tag_pool.h" #include "conf.h" #include "song.h" +#include "mpd_error.h" #include <glib.h> #include <assert.h> @@ -42,18 +44,20 @@ static struct { } bulk; const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = { - "Artist", - "Album", - "AlbumArtist", - "Title", - "Track", - "Name", - "Genre", - "Date", - "Composer", - "Performer", - "Comment", - "Disc", + [TAG_ARTIST] = "Artist", + [TAG_ARTIST_SORT] = "ArtistSort", + [TAG_ALBUM] = "Album", + [TAG_ALBUM_ARTIST] = "AlbumArtist", + [TAG_ALBUM_ARTIST_SORT] = "AlbumArtistSort", + [TAG_TITLE] = "Title", + [TAG_TRACK] = "Track", + [TAG_NAME] = "Name", + [TAG_GENRE] = "Genre", + [TAG_DATE] = "Date", + [TAG_COMPOSER] = "Composer", + [TAG_PERFORMER] = "Performer", + [TAG_COMMENT] = "Comment", + [TAG_DISC] = "Disc", /* MusicBrainz tags from http://musicbrainz.org/doc/MusicBrainzTag */ [TAG_MUSICBRAINZ_ARTISTID] = "MUSICBRAINZ_ARTISTID", @@ -111,7 +115,7 @@ void tag_lib_init(void) /* parse the "metadata_to_use" config parameter below */ /* ignore comments by default */ - ignore_tag_items[TAG_ITEM_COMMENT] = true; + ignore_tag_items[TAG_COMMENT] = true; value = config_get_string(CONF_METADATA_TO_USE, NULL); if (value == NULL) @@ -135,8 +139,8 @@ void tag_lib_init(void) type = tag_name_parse_i(c); if (type == TAG_NUM_OF_ITEM_TYPES) - g_error("error parsing metadata item \"%s\"", - c); + MPD_ERROR("error parsing metadata item \"%s\"", + c); ignore_tag_items[type] = false; @@ -169,7 +173,7 @@ static void tag_delete_item(struct tag *tag, unsigned idx) if (tag->num_items - idx > 0) { memmove(tag->items + idx, tag->items + idx + 1, - tag->num_items - idx); + (tag->num_items - idx) * sizeof(tag->items[0])); } if (tag->num_items > 0) { @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -31,18 +31,20 @@ * Codes for the type of a tag item. */ enum tag_type { - TAG_ITEM_ARTIST, - TAG_ITEM_ALBUM, - TAG_ITEM_ALBUM_ARTIST, - TAG_ITEM_TITLE, - TAG_ITEM_TRACK, - TAG_ITEM_NAME, - TAG_ITEM_GENRE, - TAG_ITEM_DATE, - TAG_ITEM_COMPOSER, - TAG_ITEM_PERFORMER, - TAG_ITEM_COMMENT, - TAG_ITEM_DISC, + TAG_ARTIST, + TAG_ARTIST_SORT, + TAG_ALBUM, + TAG_ALBUM_ARTIST, + TAG_ALBUM_ARTIST_SORT, + TAG_TITLE, + TAG_TRACK, + TAG_NAME, + TAG_GENRE, + TAG_DATE, + TAG_COMPOSER, + TAG_PERFORMER, + TAG_COMMENT, + TAG_DISC, TAG_MUSICBRAINZ_ARTISTID, TAG_MUSICBRAINZ_ALBUMID, diff --git a/src/tag_ape.c b/src/tag_ape.c index babefcda3..4841b3138 100644 --- a/src/tag_ape.c +++ b/src/tag_ape.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "tag_ape.h" #include "tag.h" #include "tag_table.h" @@ -27,8 +28,8 @@ #include <stdio.h> static const char *const ape_tag_names[TAG_NUM_OF_ITEM_TYPES] = { - [TAG_ITEM_ALBUM_ARTIST] = "album artist", - [TAG_ITEM_DATE] = "year" + [TAG_ALBUM_ARTIST] = "album artist", + [TAG_DATE] = "year", }; static enum tag_type @@ -82,7 +83,7 @@ tag_ape_load(const char *file) unsigned char reserved[8]; } footer; - fp = fopen(file, "r"); + fp = fopen(file, "rb"); if (!fp) return NULL; diff --git a/src/tag_ape.h b/src/tag_ape.h index dd06d27cf..150659685 100644 --- a/src/tag_ape.h +++ b/src/tag_ape.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag_id3.c b/src/tag_id3.c index a33ebc00b..c1302ca86 100644 --- a/src/tag_id3.c +++ b/src/tag_id3.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "tag_id3.h" #include "tag.h" #include "riff.h" @@ -34,25 +35,31 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "id3" -# define isId3v1(tag) (id3_tag_options(tag, 0, 0) & ID3_TAG_OPTION_ID3V1) # ifndef ID3_FRAME_COMPOSER # define ID3_FRAME_COMPOSER "TCOM" # endif -# ifndef ID3_FRAME_PERFORMER -# define ID3_FRAME_PERFORMER "TOPE" -# endif # ifndef ID3_FRAME_DISC # define ID3_FRAME_DISC "TPOS" # endif +#ifndef ID3_FRAME_ARTIST_SORT +#define ID3_FRAME_ARTIST_SORT "TSOP" +#endif + #ifndef ID3_FRAME_ALBUM_ARTIST_SORT -#define ID3_FRAME_ALBUM_ARTIST_SORT "TSO2" +#define ID3_FRAME_ALBUM_ARTIST_SORT "TSO2" /* this one is unofficial, introduced by Itunes */ #endif #ifndef ID3_FRAME_ALBUM_ARTIST #define ID3_FRAME_ALBUM_ARTIST "TPE2" #endif +static inline bool +tag_is_id3v1(struct id3_tag *tag) +{ + return (id3_tag_options(tag, 0, 0) & ID3_TAG_OPTION_ID3V1) != 0; +} + static id3_utf8_t * tag_id3_getstring(const struct id3_frame *frame, unsigned i) { @@ -72,14 +79,13 @@ tag_id3_getstring(const struct id3_frame *frame, unsigned i) /* This will try to convert a string to utf-8, */ -static id3_utf8_t * processID3FieldString (int is_id3v1, const id3_ucs4_t *ucs4, int type) +static id3_utf8_t * +import_id3_string(bool is_id3v1, const id3_ucs4_t *ucs4) { id3_utf8_t *utf8, *utf8_stripped; id3_latin1_t *isostr; const char *encoding; - if (type == TAG_ITEM_GENRE) - ucs4 = id3_genre_name(ucs4); /* use encoding field here? */ if (is_id3v1 && (encoding = config_get_string(CONF_ID3V1_ENCODING, NULL)) != NULL) { @@ -112,8 +118,16 @@ static id3_utf8_t * processID3FieldString (int is_id3v1, const id3_ucs4_t *ucs4, return utf8_stripped; } +/** + * Import a "Text information frame" (ID3v2.4.0 section 4.2). It + * contains 2 fields: + * + * - encoding + * - string list + */ static void -getID3Info(struct id3_tag *tag, const char *id, int type, struct tag *mpdTag) +tag_id3_import_text(struct tag *dest, struct id3_tag *tag, const char *id, + enum tag_type type) { struct id3_frame const *frame; id3_ucs4_t const *ucs4; @@ -122,108 +136,77 @@ getID3Info(struct id3_tag *tag, const char *id, int type, struct tag *mpdTag) unsigned int nstrings, i; frame = id3_tag_findframe(tag, id, 0); - /* Check frame */ - if (!frame) - { + if (frame == NULL || frame->nfields != 2) return; - } - /* Check fields in frame */ - if(frame->nfields == 0) - { - g_debug("Frame has no fields"); + + /* check the encoding field */ + + field = id3_frame_field(frame, 0); + if (field == NULL || field->type != ID3_FIELD_TYPE_TEXTENCODING) return; - } - /* Starting with T is a stringlist */ - if (id[0] == 'T') - { - /* This one contains 2 fields: - * 1st: Text encoding - * 2: Stringlist - * Shamefully this isn't the RL case. - * But I am going to enforce it anyway. - */ - if(frame->nfields != 2) - { - g_debug("Invalid number '%i' of fields for TXX frame", - frame->nfields); - return; - } - field = &frame->fields[0]; - /** - * First field is encoding field. - * This is ignored by mpd. - */ - if(field->type != ID3_FIELD_TYPE_TEXTENCODING) - { - g_debug("Expected encoding, found: %i", - field->type); - } - /* Process remaining fields, should be only one */ - field = &frame->fields[1]; - /* Encoding field */ - if(field->type == ID3_FIELD_TYPE_STRINGLIST) { - /* Get the number of strings available */ - nstrings = id3_field_getnstrings(field); - for (i = 0; i < nstrings; i++) { - ucs4 = id3_field_getstrings(field,i); - if(!ucs4) - continue; - utf8 = processID3FieldString(isId3v1(tag),ucs4, type); - if(!utf8) - continue; - - tag_add_item(mpdTag, type, (char *)utf8); - g_free(utf8); - } - } - else { - g_warning("Field type not processed: %i", - (int)id3_field_gettextencoding(field)); - } - } - /* A comment frame */ - else if(!strcmp(ID3_FRAME_COMMENT, id)) - { - /* A comment frame is different... */ - /* 1st: encoding - * 2nd: Language - * 3rd: String - * 4th: FullString. - * The 'value' we want is in the 4th field - */ - if(frame->nfields == 4) - { - /* for now I only read the 4th field, with the fullstring */ - field = &frame->fields[3]; - if(field->type == ID3_FIELD_TYPE_STRINGFULL) - { - ucs4 = id3_field_getfullstring(field); - if(ucs4) - { - utf8 = processID3FieldString(isId3v1(tag),ucs4, type); - if(utf8) - { - tag_add_item(mpdTag, type, (char *)utf8); - g_free(utf8); - } - } - } - else - { - g_debug("4th field in comment frame differs from expected, got '%i': ignoring", - field->type); - } - } - else - { - g_debug("Invalid 'comments' tag, got '%i' fields instead of 4", - frame->nfields); - } + /* process the value(s) */ + + field = id3_frame_field(frame, 1); + if (field == NULL || field->type != ID3_FIELD_TYPE_STRINGLIST) + return; + + /* Get the number of strings available */ + nstrings = id3_field_getnstrings(field); + for (i = 0; i < nstrings; i++) { + ucs4 = id3_field_getstrings(field, i); + if (ucs4 == NULL) + continue; + + if (type == TAG_GENRE) + ucs4 = id3_genre_name(ucs4); + + utf8 = import_id3_string(tag_is_id3v1(tag), ucs4); + if (utf8 == NULL) + continue; + + tag_add_item(dest, type, (char *)utf8); + g_free(utf8); } - /* Unsupported */ - else - g_debug("Unsupported tag type requrested"); +} + +/** + * Import a "Comment frame" (ID3v2.4.0 section 4.10). It + * contains 4 fields: + * + * - encoding + * - language + * - string + * - full string (we use this one) + */ +static void +tag_id3_import_comment(struct tag *dest, struct id3_tag *tag, const char *id, + enum tag_type type) +{ + struct id3_frame const *frame; + id3_ucs4_t const *ucs4; + id3_utf8_t *utf8; + union id3_field const *field; + + frame = id3_tag_findframe(tag, id, 0); + if (frame == NULL || frame->nfields != 4) + return; + + /* for now I only read the 4th field, with the fullstring */ + field = id3_frame_field(frame, 3); + if (field == NULL) + return; + + ucs4 = id3_field_getfullstring(field); + if (ucs4 == NULL) + return; + + utf8 = import_id3_string(tag_is_id3v1(tag), ucs4); + if (utf8 == NULL) + return; + + tag_add_item(dest, type, (char *)utf8); + g_free(utf8); } /** @@ -237,6 +220,7 @@ tag_id3_parse_txxx_name(const char *name) enum tag_type type; const char *name; } musicbrainz_txxx[] = { + { TAG_ALBUM_ARTIST_SORT, "ALBUMARTISTSORT" }, { TAG_MUSICBRAINZ_ARTISTID, "MusicBrainz Artist Id" }, { TAG_MUSICBRAINZ_ALBUMID, "MusicBrainz Album Id" }, { TAG_MUSICBRAINZ_ALBUMARTISTID, @@ -328,20 +312,23 @@ struct tag *tag_id3_import(struct id3_tag * tag) { struct tag *ret = tag_new(); - getID3Info(tag, ID3_FRAME_ARTIST, TAG_ITEM_ARTIST, ret); - getID3Info(tag, ID3_FRAME_ALBUM_ARTIST, - TAG_ITEM_ALBUM_ARTIST, ret); - getID3Info(tag, ID3_FRAME_ALBUM_ARTIST_SORT, - TAG_ITEM_ALBUM_ARTIST, ret); - getID3Info(tag, ID3_FRAME_TITLE, TAG_ITEM_TITLE, ret); - getID3Info(tag, ID3_FRAME_ALBUM, TAG_ITEM_ALBUM, ret); - getID3Info(tag, ID3_FRAME_TRACK, TAG_ITEM_TRACK, ret); - getID3Info(tag, ID3_FRAME_YEAR, TAG_ITEM_DATE, ret); - getID3Info(tag, ID3_FRAME_GENRE, TAG_ITEM_GENRE, ret); - getID3Info(tag, ID3_FRAME_COMPOSER, TAG_ITEM_COMPOSER, ret); - getID3Info(tag, ID3_FRAME_PERFORMER, TAG_ITEM_PERFORMER, ret); - getID3Info(tag, ID3_FRAME_COMMENT, TAG_ITEM_COMMENT, ret); - getID3Info(tag, ID3_FRAME_DISC, TAG_ITEM_DISC, ret); + tag_id3_import_text(ret, tag, ID3_FRAME_ARTIST, TAG_ARTIST); + tag_id3_import_text(ret, tag, ID3_FRAME_ALBUM_ARTIST, + TAG_ALBUM_ARTIST); + tag_id3_import_text(ret, tag, ID3_FRAME_ARTIST_SORT, + TAG_ARTIST_SORT); + tag_id3_import_text(ret, tag, ID3_FRAME_ALBUM_ARTIST_SORT, + TAG_ALBUM_ARTIST_SORT); + tag_id3_import_text(ret, tag, ID3_FRAME_TITLE, TAG_TITLE); + tag_id3_import_text(ret, tag, ID3_FRAME_ALBUM, TAG_ALBUM); + tag_id3_import_text(ret, tag, ID3_FRAME_TRACK, TAG_TRACK); + tag_id3_import_text(ret, tag, ID3_FRAME_YEAR, TAG_DATE); + tag_id3_import_text(ret, tag, ID3_FRAME_GENRE, TAG_GENRE); + tag_id3_import_text(ret, tag, ID3_FRAME_COMPOSER, TAG_COMPOSER); + tag_id3_import_text(ret, tag, "TPE3", TAG_PERFORMER); + tag_id3_import_text(ret, tag, "TPE4", TAG_PERFORMER); + tag_id3_import_comment(ret, tag, ID3_FRAME_COMMENT, TAG_COMMENT); + tag_id3_import_text(ret, tag, ID3_FRAME_DISC, TAG_DISC); tag_id3_import_musicbrainz(ret, tag); tag_id3_import_ufid(ret, tag); @@ -354,69 +341,72 @@ struct tag *tag_id3_import(struct id3_tag * tag) return ret; } -static int fillBuffer(void *buf, size_t size, FILE * stream, - long offset, int whence) +static int +fill_buffer(void *buf, size_t size, FILE *stream, long offset, int whence) { if (fseek(stream, offset, whence) != 0) return 0; return fread(buf, 1, size, stream); } -static int getId3v2FooterSize(FILE * stream, long offset, int whence) +static int +get_id3v2_footer_size(FILE *stream, long offset, int whence) { id3_byte_t buf[ID3_TAG_QUERYSIZE]; int bufsize; - bufsize = fillBuffer(buf, ID3_TAG_QUERYSIZE, stream, offset, whence); + bufsize = fill_buffer(buf, ID3_TAG_QUERYSIZE, stream, offset, whence); if (bufsize <= 0) return 0; return id3_tag_query(buf, bufsize); } -static struct id3_tag *getId3Tag(FILE * stream, long offset, int whence) +static struct id3_tag * +tag_id3_read(FILE *stream, long offset, int whence) { struct id3_tag *tag; - id3_byte_t queryBuf[ID3_TAG_QUERYSIZE]; - id3_byte_t *tagBuf; - int tagSize; - int queryBufSize; - int tagBufSize; + id3_byte_t query_buffer[ID3_TAG_QUERYSIZE]; + id3_byte_t *tag_buffer; + int tag_size; + int query_buffer_size; + int tag_buffer_size; /* It's ok if we get less than we asked for */ - queryBufSize = fillBuffer(queryBuf, ID3_TAG_QUERYSIZE, - stream, offset, whence); - if (queryBufSize <= 0) return NULL; + query_buffer_size = fill_buffer(query_buffer, ID3_TAG_QUERYSIZE, + stream, offset, whence); + if (query_buffer_size <= 0) return NULL; /* Look for a tag header */ - tagSize = id3_tag_query(queryBuf, queryBufSize); - if (tagSize <= 0) return NULL; + tag_size = id3_tag_query(query_buffer, query_buffer_size); + if (tag_size <= 0) return NULL; /* Found a tag. Allocate a buffer and read it in. */ - tagBuf = g_malloc(tagSize); - if (!tagBuf) return NULL; + tag_buffer = g_malloc(tag_size); + if (!tag_buffer) return NULL; - tagBufSize = fillBuffer(tagBuf, tagSize, stream, offset, whence); - if (tagBufSize < tagSize) { - g_free(tagBuf); + tag_buffer_size = fill_buffer(tag_buffer, tag_size, stream, offset, whence); + if (tag_buffer_size < tag_size) { + g_free(tag_buffer); return NULL; } - tag = id3_tag_parse(tagBuf, tagBufSize); + tag = id3_tag_parse(tag_buffer, tag_buffer_size); - g_free(tagBuf); + g_free(tag_buffer); return tag; } -static struct id3_tag *findId3TagFromBeginning(FILE * stream) +static struct id3_tag * +tag_id3_find_from_beginning(FILE *stream) { struct id3_tag *tag; struct id3_tag *seektag; struct id3_frame *frame; int seek; - tag = getId3Tag(stream, 0, SEEK_SET); + tag = tag_id3_read(stream, 0, SEEK_SET); if (!tag) { return NULL; - } else if (isId3v1(tag)) { + } else if (tag_is_id3v1(tag)) { /* id3v1 tags don't belong here */ id3_tag_delete(tag); return NULL; @@ -430,8 +420,8 @@ static struct id3_tag *findId3TagFromBeginning(FILE * stream) break; /* Get the tag specified by the SEEK frame */ - seektag = getId3Tag(stream, seek, SEEK_CUR); - if (!seektag || isId3v1(seektag)) + seektag = tag_id3_read(stream, seek, SEEK_CUR); + if (!seektag || tag_is_id3v1(seektag)) break; /* Replace the old tag with the new one */ @@ -442,22 +432,23 @@ static struct id3_tag *findId3TagFromBeginning(FILE * stream) return tag; } -static struct id3_tag *findId3TagFromEnd(FILE * stream) +static struct id3_tag * +tag_id3_find_from_end(FILE *stream) { struct id3_tag *tag; struct id3_tag *v1tag; int tagsize; /* Get an id3v1 tag from the end of file for later use */ - v1tag = getId3Tag(stream, -128, SEEK_END); + v1tag = tag_id3_read(stream, -128, SEEK_END); /* Get the id3v2 tag size from the footer (located before v1tag) */ - tagsize = getId3v2FooterSize(stream, (v1tag ? -128 : 0) - 10, SEEK_END); + tagsize = get_id3v2_footer_size(stream, (v1tag ? -128 : 0) - 10, SEEK_END); if (tagsize >= 0) return v1tag; /* Get the tag which the footer belongs to */ - tag = getId3Tag(stream, tagsize, SEEK_CUR); + tag = tag_id3_read(stream, tagsize, SEEK_CUR); if (!tag) return v1tag; @@ -504,18 +495,18 @@ struct tag *tag_id3_load(const char *file) struct id3_tag *tag; FILE *stream; - stream = fopen(file, "r"); + stream = fopen(file, "rb"); if (!stream) { g_debug("tag_id3_load: Failed to open file: '%s', %s", file, strerror(errno)); return NULL; } - tag = findId3TagFromBeginning(stream); + tag = tag_id3_find_from_beginning(stream); if (tag == NULL) tag = tag_id3_riff_aiff_load(stream); if (!tag) - tag = findId3TagFromEnd(stream); + tag = tag_id3_find_from_end(stream); fclose(stream); diff --git a/src/tag_id3.h b/src/tag_id3.h index 4f51a70b8..43f9678b4 100644 --- a/src/tag_id3.h +++ b/src/tag_id3.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,7 +20,7 @@ #ifndef MPD_TAG_ID3_H #define MPD_TAG_ID3_H -#include "config.h" +#include "check.h" struct tag; diff --git a/src/tag_internal.h b/src/tag_internal.h index 4c3ef41be..9d76efed1 100644 --- a/src/tag_internal.h +++ b/src/tag_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag_pool.c b/src/tag_pool.c index 6aef12941..6ad1e1f2d 100644 --- a/src/tag_pool.c +++ b/src/tag_pool.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "tag_pool.h" #include <assert.h> diff --git a/src/tag_pool.h b/src/tag_pool.h index 991a32a7e..289d6fe5f 100644 --- a/src/tag_pool.h +++ b/src/tag_pool.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag_print.c b/src/tag_print.c index dddbbbe67..493fa89b5 100644 --- a/src/tag_print.c +++ b/src/tag_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "tag_print.h" #include "tag.h" #include "tag_internal.h" diff --git a/src/tag_print.h b/src/tag_print.h index 24ffbc914..e16e2c441 100644 --- a/src/tag_print.h +++ b/src/tag_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag_rva2.c b/src/tag_rva2.c new file mode 100644 index 000000000..35f12118f --- /dev/null +++ b/src/tag_rva2.c @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "tag_rva2.h" +#include "replay_gain_info.h" + +#include <stdint.h> +#include <glib.h> +#include <id3tag.h> + +enum rva2_channel { + CHANNEL_OTHER = 0x00, + CHANNEL_MASTER_VOLUME = 0x01, + CHANNEL_FRONT_RIGHT = 0x02, + CHANNEL_FRONT_LEFT = 0x03, + CHANNEL_BACK_RIGHT = 0x04, + CHANNEL_BACK_LEFT = 0x05, + CHANNEL_FRONT_CENTRE = 0x06, + CHANNEL_BACK_CENTRE = 0x07, + CHANNEL_SUBWOOFER = 0x08 +}; + +struct rva2_data { + uint8_t type; + uint8_t volume_adjustment[2]; + uint8_t peak_bits; +}; + +static inline id3_length_t +rva2_peak_bytes(const struct rva2_data *data) +{ + return (data->peak_bits + 7) / 8; +} + +static inline int +rva2_fixed_volume_adjustment(const struct rva2_data *data) +{ + signed int voladj_fixed; + voladj_fixed = (data->volume_adjustment[0] << 8) | + data->volume_adjustment[1]; + voladj_fixed |= -(voladj_fixed & 0x8000); + return voladj_fixed; +} + +static inline float +rva2_float_volume_adjustment(const struct rva2_data *data) +{ + /* + * "The volume adjustment is encoded as a fixed point decibel + * value, 16 bit signed integer representing (adjustment*512), + * giving +/- 64 dB with a precision of 0.001953125 dB." + */ + + return (float)rva2_fixed_volume_adjustment(data) / (float)512; +} + +static inline bool +rva2_apply_data(struct replay_gain_info *replay_gain_info, + const struct rva2_data *data) +{ + if (data->type != CHANNEL_MASTER_VOLUME) + return false; + + float volume_adjustment = rva2_float_volume_adjustment(data); + + replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = volume_adjustment; + replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = volume_adjustment; + + return true; +} + +bool +tag_rva2_parse(struct id3_tag *tag, struct replay_gain_info *replay_gain_info) +{ + struct id3_frame const * frame; + + id3_latin1_t const *id; + id3_byte_t const *data; + id3_length_t length; + + /* relative volume adjustment information */ + + frame = id3_tag_findframe(tag, "RVA2", 0); + if (frame == NULL) + return false; + + id = id3_field_getlatin1(id3_frame_field(frame, 0)); + data = id3_field_getbinarydata(id3_frame_field(frame, 1), + &length); + + if (id == NULL || data == NULL) + return false; + + /* + * "The 'identification' string is used to identify the + * situation and/or device where this adjustment should apply. + * The following is then repeated for every channel + * + * Type of channel $xx + * Volume adjustment $xx xx + * Bits representing peak $xx + * Peak volume $xx (xx ...)" + */ + + while (length >= 4) { + const struct rva2_data *d = (const struct rva2_data *)data; + unsigned int peak_bytes = rva2_peak_bytes(d); + if (4 + peak_bytes > length) + break; + + if (rva2_apply_data(replay_gain_info, d)) + return true; + + data += 4 + peak_bytes; + length -= 4 + peak_bytes; + } + + return false; +} diff --git a/src/tag_rva2.h b/src/tag_rva2.h new file mode 100644 index 000000000..a92c97912 --- /dev/null +++ b/src/tag_rva2.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2010 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_TAG_RVA2_H +#define MPD_TAG_RVA2_H + +#include "check.h" + +#include <stdbool.h> + +struct id3_tag; +struct replay_gain_info; + +/** + * Parse the RVA2 tag, and fill the #replay_gain_info struct. This is + * used by decoder plugins with ID3 support. + * + * @return true on success + */ +bool +tag_rva2_parse(struct id3_tag *tag, struct replay_gain_info *replay_gain_info); + +#endif diff --git a/src/tag_save.c b/src/tag_save.c index fac948b9f..9b90d1b92 100644 --- a/src/tag_save.c +++ b/src/tag_save.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "tag_save.h" #include "tag.h" #include "tag_internal.h" diff --git a/src/tag_save.h b/src/tag_save.h index 687c35beb..2e8924c20 100644 --- a/src/tag_save.h +++ b/src/tag_save.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/text_file.c b/src/text_file.c new file mode 100644 index 000000000..355217aba --- /dev/null +++ b/src/text_file.c @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "text_file.h" + +#include <assert.h> +#include <string.h> + +char * +read_text_line(FILE *file, GString *buffer) +{ + enum { + max_length = 512 * 1024, + step = 1024, + }; + + gsize length = 0, i; + char *p; + + assert(file != NULL); + assert(buffer != NULL); + + if (buffer->allocated_len < step) + g_string_set_size(buffer, step); + + while (buffer->len < max_length) { + p = fgets(buffer->str + length, + buffer->allocated_len - length, file); + if (p == NULL) { + if (length == 0 || ferror(file)) + return NULL; + break; + } + + i = strlen(buffer->str + length); + length += i; + if (i < step - 1 || buffer->str[length - 1] == '\n') + break; + + g_string_set_size(buffer, length + step); + } + + /* remove the newline characters */ + if (buffer->str[length - 1] == '\n') + --length; + if (buffer->str[length - 1] == '\r') + --length; + + g_string_set_size(buffer, length); + return buffer->str; +} diff --git a/src/text_file.h b/src/text_file.h new file mode 100644 index 000000000..d016f8f7a --- /dev/null +++ b/src/text_file.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TEXT_FILE_H +#define MPD_TEXT_FILE_H + +#include <glib.h> + +#include <stdio.h> + +/** + * Reads a line from the input file, and strips trailing space. There + * is a reasonable maximum line length, only to prevent denial of + * service. + * + * @param file the source file, opened in text mode + * @param buffer an allocator for the buffer + * @return a pointer to the line, or NULL on end-of-file or error + */ +char * +read_text_line(FILE *file, GString *buffer); + +#endif diff --git a/src/text_input_stream.c b/src/text_input_stream.c new file mode 100644 index 000000000..29fb6dce6 --- /dev/null +++ b/src/text_input_stream.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "text_input_stream.h" +#include "input_stream.h" +#include "fifo_buffer.h" + +#include <glib.h> + +#include <string.h> + +struct text_input_stream { + struct input_stream *is; + + struct fifo_buffer *buffer; + + char *line; +}; + +struct text_input_stream * +text_input_stream_new(struct input_stream *is) +{ + struct text_input_stream *tis = g_new(struct text_input_stream, 1); + + tis->is = is; + tis->buffer = fifo_buffer_new(4096); + tis->line = NULL; + + return tis; +} + +void +text_input_stream_free(struct text_input_stream *tis) +{ + fifo_buffer_free(tis->buffer); + g_free(tis->line); + g_free(tis); +} + +const char * +text_input_stream_read(struct text_input_stream *tis) +{ + GError *error = NULL; + void *dest; + const char *src, *p; + size_t length, nbytes; + + g_free(tis->line); + tis->line = NULL; + + do { + dest = fifo_buffer_write(tis->buffer, &length); + if (dest != NULL) { + nbytes = input_stream_read(tis->is, dest, length, + &error); + if (nbytes > 0) + fifo_buffer_append(tis->buffer, nbytes); + else if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + } + + src = fifo_buffer_read(tis->buffer, &length); + if (src == NULL) + return NULL; + + p = memchr(src, '\n', length); + } while (p == NULL); + + length = p - src + 1; + while (p > src && g_ascii_isspace(p[-1])) + --p; + + tis->line = g_strndup(src, p - src); + fifo_buffer_consume(tis->buffer, length); + return tis->line; +} diff --git a/src/text_input_stream.h b/src/text_input_stream.h new file mode 100644 index 000000000..a1fda065d --- /dev/null +++ b/src/text_input_stream.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TEXT_INPUT_STREAM_H +#define MPD_TEXT_INPUT_STREAM_H + +struct input_stream; +struct text_input_stream; + +/** + * Wraps an existing #input_stream object into a #text_input_stream, + * to read its contents as text lines. + * + * @param is an open #input_stream object + * @return the new #text_input_stream object + */ +struct text_input_stream * +text_input_stream_new(struct input_stream *is); + +/** + * Frees the #text_input_stream object. Does not close or free the + * underlying #input_stream. + */ +void +text_input_stream_free(struct text_input_stream *tis); + +/** + * Reads the next line from the stream. + * + * @return a line (newline character stripped), or NULL on end of file + * or error + */ +const char * +text_input_stream_read(struct text_input_stream *tis); + +#endif diff --git a/src/timer.c b/src/timer.c index d9a143bcc..e125b1009 100644 --- a/src/timer.c +++ b/src/timer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "timer.h" #include "audio_format.h" @@ -70,6 +71,19 @@ void timer_add(Timer *timer, int size) timer->time += ((uint64_t)size * 1000000) / timer->rate; } +unsigned +timer_delay(const Timer *timer) +{ + int64_t delay = (timer->time - now()) / 1000; + if (delay < 0) + return 0; + + if (delay > G_MAXINT) + delay = G_MAXINT; + + return delay / 1000; +} + void timer_sync(Timer *timer) { int64_t sleep_duration; diff --git a/src/timer.h b/src/timer.h index 7225fb5ee..bbd895b31 100644 --- a/src/timer.h +++ b/src/timer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -40,6 +40,12 @@ void timer_reset(Timer *timer); void timer_add(Timer *timer, int size); +/** + * Returns the number of milliseconds to sleep to get back to sync. + */ +unsigned +timer_delay(const Timer *timer); + void timer_sync(Timer *timer); #endif diff --git a/src/tokenizer.c b/src/tokenizer.c new file mode 100644 index 000000000..2b9e05070 --- /dev/null +++ b/src/tokenizer.c @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "tokenizer.h" + +#include <stdbool.h> +#include <assert.h> +#include <string.h> + +G_GNUC_CONST +static GQuark +tokenizer_quark(void) +{ + return g_quark_from_static_string("tokenizer"); +} + +static inline bool +valid_word_first_char(char ch) +{ + return g_ascii_isalpha(ch); +} + +static inline bool +valid_word_char(char ch) +{ + return g_ascii_isalnum(ch) || ch == '_'; +} + +char * +tokenizer_next_word(char **input_p, GError **error_r) +{ + char *word, *input; + + assert(input_p != NULL); + assert(*input_p != NULL); + + word = input = *input_p; + + if (*input == 0) + return NULL; + + /* check the first character */ + + if (!valid_word_first_char(*input)) { + g_set_error(error_r, tokenizer_quark(), 0, + "Letter expected"); + return NULL; + } + + /* now iterate over the other characters until we find a + whitespace or end-of-string */ + + while (*++input != 0) { + if (g_ascii_isspace(*input)) { + /* a whitespace: the word ends here */ + *input = 0; + /* skip all following spaces, too */ + input = g_strchug(input + 1); + break; + } + + if (!valid_word_char(*input)) { + *input_p = input; + g_set_error(error_r, tokenizer_quark(), 0, + "Invalid word character"); + return NULL; + } + } + + /* end of string: the string is already null-terminated + here */ + + *input_p = input; + return word; +} + +static inline bool +valid_unquoted_char(char ch) +{ + return (unsigned char)ch > 0x20 && ch != '"' && ch != '\''; +} + +char * +tokenizer_next_unquoted(char **input_p, GError **error_r) +{ + char *word, *input; + + assert(input_p != NULL); + assert(*input_p != NULL); + + word = input = *input_p; + + if (*input == 0) + return NULL; + + /* check the first character */ + + if (!valid_unquoted_char(*input)) { + g_set_error(error_r, tokenizer_quark(), 0, + "Invalid unquoted character"); + return NULL; + } + + /* now iterate over the other characters until we find a + whitespace or end-of-string */ + + while (*++input != 0) { + if (g_ascii_isspace(*input)) { + /* a whitespace: the word ends here */ + *input = 0; + /* skip all following spaces, too */ + input = g_strchug(input + 1); + break; + } + + if (!valid_unquoted_char(*input)) { + *input_p = input; + g_set_error(error_r, tokenizer_quark(), 0, + "Invalid unquoted character"); + return NULL; + } + } + + /* end of string: the string is already null-terminated + here */ + + *input_p = input; + return word; +} + +char * +tokenizer_next_string(char **input_p, GError **error_r) +{ + char *word, *dest, *input; + + assert(input_p != NULL); + assert(*input_p != NULL); + + word = dest = input = *input_p; + + if (*input == 0) + /* end of line */ + return NULL; + + /* check for the opening " */ + + if (*input != '"') { + g_set_error(error_r, tokenizer_quark(), 0, + "'\"' expected"); + return NULL; + } + + ++input; + + /* copy all characters */ + + while (*input != '"') { + if (*input == '\\') + /* the backslash escapes the following + character */ + ++input; + + if (*input == 0) { + /* return input-1 so the caller can see the + difference between "end of line" and + "error" */ + *input_p = input - 1; + g_set_error(error_r, tokenizer_quark(), 0, + "Missing closing '\"'"); + return NULL; + } + + /* copy one character */ + *dest++ = *input++; + } + + /* the following character must be a whitespace (or end of + line) */ + + ++input; + if (*input != 0 && !g_ascii_isspace(*input)) { + *input_p = input; + g_set_error(error_r, tokenizer_quark(), 0, + "Space expected after closing '\"'"); + return NULL; + } + + /* finish the string and return it */ + + *dest = 0; + *input_p = g_strchug(input); + return word; +} + +char * +tokenizer_next_param(char **input_p, GError **error_r) +{ + assert(input_p != NULL); + assert(*input_p != NULL); + + if (**input_p == '"') + return tokenizer_next_string(input_p, error_r); + else + return tokenizer_next_unquoted(input_p, error_r); +} diff --git a/src/tokenizer.h b/src/tokenizer.h new file mode 100644 index 000000000..61ff398a4 --- /dev/null +++ b/src/tokenizer.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TOKENIZER_H +#define MPD_TOKENIZER_H + +#include <glib.h> + +/** + * Reads the next word from the input string. This function modifies + * the input string. + * + * @param input_p the input string; this function returns a pointer to + * the first non-whitespace character of the following token + * @param error_r if this function returns NULL and **input_p!=0, it + * optionally provides a GError object in this argument + * @return a pointer to the null-terminated word, or NULL on error or + * end of line + */ +char * +tokenizer_next_word(char **input_p, GError **error_r); + +/** + * Reads the next unquoted word from the input string. This function + * modifies the input string. + * + * @param input_p the input string; this function returns a pointer to + * the first non-whitespace character of the following token + * @param error_r if this function returns NULL and **input_p!=0, it + * optionally provides a GError object in this argument + * @return a pointer to the null-terminated word, or NULL on error or + * end of line + */ +char * +tokenizer_next_unquoted(char **input_p, GError **error_r); + +/** + * Reads the next quoted string from the input string. A backslash + * escapes the following character. This function modifies the input + * string. + * + * @param input_p the input string; this function returns a pointer to + * the first non-whitespace character of the following token + * @param error_r if this function returns NULL and **input_p!=0, it + * optionally provides a GError object in this argument + * @return a pointer to the null-terminated string, or NULL on error + * or end of line + */ +char * +tokenizer_next_string(char **input_p, GError **error_r); + +/** + * Reads the next unquoted word or quoted string from the input. This + * is a wrapper for tokenizer_next_unquoted() and + * tokenizer_next_string(). + * + * @param input_p the input string; this function returns a pointer to + * the first non-whitespace character of the following token + * @param error_r if this function returns NULL and **input_p!=0, it + * optionally provides a GError object in this argument + * @return a pointer to the null-terminated string, or NULL on error + * or end of line + */ +char * +tokenizer_next_param(char **input_p, GError **error_r); + +#endif diff --git a/src/update.c b/src/update.c index d5c9779c8..d57fb114d 100644 --- a/src/update.c +++ b/src/update.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,45 +17,22 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" +#include "update_internal.h" #include "update.h" #include "database.h" -#include "directory.h" -#include "song.h" -#include "uri.h" #include "mapper.h" -#include "path.h" -#include "decoder_list.h" -#include "archive_list.h" #include "playlist.h" #include "event_pipe.h" -#include "notify.h" #include "update.h" #include "idle.h" -#include "conf.h" #include "stats.h" #include "main.h" -#include "config.h" - -#ifdef ENABLE_SQLITE -#include "sticker.h" -#include "song_sticker.h" -#endif +#include "mpd_error.h" #include <glib.h> #include <assert.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <dirent.h> -#include <string.h> -#include <stdlib.h> -#include <errno.h> - -#include "decoder_plugin.h" - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "update" static enum update_progress { UPDATE_PROGRESS_IDLE = 0, @@ -65,32 +42,14 @@ static enum update_progress { static bool modified; -/* make this dynamic?, or maybe this is big enough... */ -static char *update_paths[32]; -static size_t update_paths_nr; - static GThread *update_thr; static const unsigned update_task_id_max = 1 << 15; static unsigned update_task_id; -static struct song *delete; - -/** used by the main thread to notify the update thread */ -static struct notify update_notify; - -#ifndef WIN32 - -enum { - DEFAULT_FOLLOW_INSIDE_SYMLINKS = true, - DEFAULT_FOLLOW_OUTSIDE_SYMLINKS = true, -}; - -static bool follow_inside_symlinks; -static bool follow_outside_symlinks; - -#endif +/* XXX this flag is passed to update_task() */ +static bool discard; unsigned isUpdatingDB(void) @@ -98,703 +57,33 @@ isUpdatingDB(void) return (progress != UPDATE_PROGRESS_IDLE) ? update_task_id : 0; } -static void -directory_set_stat(struct directory *dir, const struct stat *st) -{ - dir->inode = st->st_ino; - dir->device = st->st_dev; - dir->stat = 1; -} - -static void -delete_song(struct directory *dir, struct song *del) -{ - /* first, prevent traversers in main task from getting this */ - songvec_delete(&dir->songs, del); - - /* now take it out of the playlist (in the main_task) */ - assert(!delete); - delete = del; - event_pipe_emit(PIPE_EVENT_DELETE); - - do { - notify_wait(&update_notify); - } while (delete != NULL); - - /* finally, all possible references gone, free it */ - song_free(del); -} - -static int -delete_each_song(struct song *song, G_GNUC_UNUSED void *data) -{ - struct directory *directory = data; - assert(song->parent == directory); - delete_song(directory, song); - return 0; -} - -static void -delete_directory(struct directory *directory); - -/** - * Recursively remove all sub directories and songs from a directory, - * leaving an empty directory. - */ -static void -clear_directory(struct directory *directory) -{ - int i; - - for (i = directory->children.nr; --i >= 0;) - delete_directory(directory->children.base[i]); - - assert(directory->children.nr == 0); - - songvec_for_each(&directory->songs, delete_each_song, directory); -} - -/** - * Recursively free a directory and all its contents. - */ -static void -delete_directory(struct directory *directory) -{ - assert(directory->parent != NULL); - - clear_directory(directory); - - dirvec_delete(&directory->parent->children, directory); - directory_free(directory); -} - -static void -delete_name_in(struct directory *parent, const char *name) -{ - struct directory *directory = directory_get_child(parent, name); - struct song *song = songvec_find(&parent->songs, name); - - if (directory != NULL) { - delete_directory(directory); - modified = true; - } - - if (song != NULL) { - delete_song(parent, song); - modified = true; - } -} - -/* passed to songvec_for_each */ -static int -delete_song_if_removed(struct song *song, void *_data) -{ - struct directory *dir = _data; - char *path; - struct stat st; - - if ((path = map_song_fs(song)) == NULL || - stat(path, &st) < 0 || !S_ISREG(st.st_mode)) { - delete_song(dir, song); - modified = true; - } - - g_free(path); - return 0; -} - -static bool -directory_exists(const struct directory *directory) -{ - char *path_fs; - GFileTest test; - bool exists; - - path_fs = map_directory_fs(directory); - if (path_fs == NULL) - /* invalid path: cannot exist */ - return false; - - test = directory->device == DEVICE_INARCHIVE || - directory->device == DEVICE_CONTAINER - ? G_FILE_TEST_IS_REGULAR - : G_FILE_TEST_IS_DIR; - - exists = g_file_test(path_fs, test); - g_free(path_fs); - - return exists; -} - -static void -removeDeletedFromDirectory(struct directory *directory) -{ - int i; - struct dirvec *dv = &directory->children; - - for (i = dv->nr; --i >= 0; ) { - if (directory_exists(dv->base[i])) - continue; - - g_debug("removing directory: %s", dv->base[i]->path); - delete_directory(dv->base[i]); - modified = true; - } - - songvec_for_each(&directory->songs, delete_song_if_removed, directory); -} - -static int -stat_directory(const struct directory *directory, struct stat *st) -{ - char *path_fs; - int ret; - - path_fs = map_directory_fs(directory); - if (path_fs == NULL) - return -1; - ret = stat(path_fs, st); - g_free(path_fs); - return ret; -} - -static int -stat_directory_child(const struct directory *parent, const char *name, - struct stat *st) -{ - char *path_fs; - int ret; - - path_fs = map_directory_child_fs(parent, name); - if (path_fs == NULL) - return -1; - - ret = stat(path_fs, st); - g_free(path_fs); - return ret; -} - -static int -statDirectory(struct directory *dir) -{ - struct stat st; - - if (stat_directory(dir, &st) < 0) - return -1; - - directory_set_stat(dir, &st); - - return 0; -} - -static int -inodeFoundInParent(struct directory *parent, ino_t inode, dev_t device) -{ - while (parent) { - if (!parent->stat && statDirectory(parent) < 0) - return -1; - if (parent->inode == inode && parent->device == device) { - g_debug("recursive directory found"); - return 1; - } - parent = parent->parent; - } - - return 0; -} - -static struct directory * -make_subdir(struct directory *parent, const char *name) -{ - struct directory *directory; - - directory = directory_get_child(parent, name); - if (directory == NULL) { - char *path; - - if (directory_is_root(parent)) - path = NULL; - else - name = path = g_strconcat(directory_get_path(parent), - "/", name, NULL); - - directory = directory_new_child(parent, name); - g_free(path); - } - - return directory; -} - -#ifdef ENABLE_ARCHIVE -static void -update_archive_tree(struct directory *directory, char *name) -{ - struct directory *subdir; - struct song *song; - char *tmp; - - tmp = strchr(name, '/'); - if (tmp) { - *tmp = 0; - //add dir is not there already - if ((subdir = dirvec_find(&directory->children, name)) == NULL) { - //create new directory - subdir = make_subdir(directory, name); - subdir->device = DEVICE_INARCHIVE; - } - //create directories first - update_archive_tree(subdir, tmp+1); - } else { - if (strlen(name) == 0) { - g_warning("archive returned directory only"); - return; - } - //add file - song = songvec_find(&directory->songs, name); - if (song == NULL) { - song = song_file_load(name, directory); - if (song != NULL) { - songvec_add(&directory->songs, song); - modified = true; - g_message("added %s/%s", - directory_get_path(directory), name); - } - } - } -} - -/** - * Updates the file listing from an archive file. - * - * @param parent the parent directory the archive file resides in - * @param name the UTF-8 encoded base name of the archive file - * @param st stat() information on the archive file - * @param plugin the archive plugin which fits this archive type - */ -static void -update_archive_file(struct directory *parent, const char *name, - const struct stat *st, - const struct archive_plugin *plugin) -{ - char *path_fs; - struct archive_file *file; - struct directory *directory; - char *filepath; - - directory = dirvec_find(&parent->children, name); - if (directory != NULL && directory->mtime == st->st_mtime) - /* MPD has already scanned the archive, and it hasn't - changed since - don't consider updating it */ - return; - - path_fs = map_directory_child_fs(parent, name); - - /* open archive */ - file = plugin->open(path_fs); - if (file == NULL) { - g_warning("unable to open archive %s", path_fs); - g_free(path_fs); - return; - } - - g_debug("archive %s opened", path_fs); - g_free(path_fs); - - if (directory == NULL) { - g_debug("creating archive directory: %s", name); - directory = make_subdir(parent, name); - /* mark this directory as archive (we use device for - this) */ - directory->device = DEVICE_INARCHIVE; - } - - directory->mtime = st->st_mtime; - - plugin->scan_reset(file); - - while ((filepath = plugin->scan_next(file)) != NULL) { - /* split name into directory and file */ - g_debug("adding archive file: %s", filepath); - update_archive_tree(directory, filepath); - } - - plugin->close(file); -} -#endif - -static bool -update_container_file( struct directory* directory, - const char* name, - const struct stat* st, - const struct decoder_plugin* plugin) +static void * update_task(void *_path) { - char* vtrack = NULL; - unsigned int tnum = 0; - char* pathname = map_directory_child_fs(directory, name); - struct directory* contdir = dirvec_find(&directory->children, name); - - // directory exists already - if (contdir != NULL) - { - // modification time not eq. file mod. time - if (contdir->mtime != st->st_mtime) - { - g_message("removing container file: %s", pathname); - - delete_directory(contdir); - contdir = NULL; - - modified = true; - } - else { - g_free(pathname); - return true; - } - } - - contdir = make_subdir(directory, name); - contdir->mtime = st->st_mtime; - contdir->device = DEVICE_CONTAINER; - - while ((vtrack = plugin->container_scan(pathname, ++tnum)) != NULL) - { - struct song* song = song_file_new(vtrack, contdir); - char *child_path_fs; - - // shouldn't be necessary but it's there.. - song->mtime = st->st_mtime; - - child_path_fs = map_directory_child_fs(contdir, vtrack); - g_free(vtrack); + const char *path = _path; - song->tag = plugin->tag_dup(child_path_fs); - g_free(child_path_fs); - - songvec_add(&contdir->songs, song); - - modified = true; - } - - g_free(pathname); - - if (tnum == 1) - { - delete_directory(contdir); - return false; - } + if (path != NULL && *path != 0) + g_debug("starting: %s", path); else - return true; -} - -static void -update_regular_file(struct directory *directory, - const char *name, const struct stat *st) -{ - const char *suffix = uri_get_suffix(name); - const struct decoder_plugin* plugin; -#ifdef ENABLE_ARCHIVE - const struct archive_plugin *archive; -#endif - if (suffix == NULL) - return; - - if ((plugin = decoder_plugin_from_suffix(suffix, false)) != NULL) - { - struct song* song = songvec_find(&directory->songs, name); - - if (!(song != NULL && st->st_mtime == song->mtime) && - plugin->container_scan != NULL) - { - if (update_container_file(directory, name, st, plugin)) - { - if (song != NULL) - delete_song(directory, song); - - return; - } - } - - if (song == NULL) { - song = song_file_load(name, directory); - if (song == NULL) - return; - - songvec_add(&directory->songs, song); - modified = true; - g_message("added %s/%s", - directory_get_path(directory), name); - } else if (st->st_mtime != song->mtime) { - g_message("updating %s/%s", - directory_get_path(directory), name); - if (!song_file_update(song)) - delete_song(directory, song); - modified = true; - } -#ifdef ENABLE_ARCHIVE - } else if ((archive = archive_plugin_from_suffix(suffix))) { - update_archive_file(directory, name, st, archive); -#endif - } -} - -static bool -updateDirectory(struct directory *directory, const struct stat *st); - -static void -updateInDirectory(struct directory *directory, - const char *name, const struct stat *st) -{ - assert(strchr(name, '/') == NULL); - - if (S_ISREG(st->st_mode)) { - update_regular_file(directory, name, st); - } else if (S_ISDIR(st->st_mode)) { - struct directory *subdir; - bool ret; - - if (inodeFoundInParent(directory, st->st_ino, st->st_dev)) - return; - - subdir = make_subdir(directory, name); - assert(directory == subdir->parent); - - ret = updateDirectory(subdir, st); - if (!ret) - delete_directory(subdir); - } else { - g_debug("update: %s is not a directory, archive or music", name); - } -} - -/* we don't look at "." / ".." nor files with newlines in their name */ -static bool skip_path(const char *path) -{ - return (path[0] == '.' && path[1] == 0) || - (path[0] == '.' && path[1] == '.' && path[2] == 0) || - strchr(path, '\n') != NULL; -} - -static bool -skip_symlink(const struct directory *directory, const char *utf8_name) -{ -#ifndef WIN32 - char buffer[MPD_PATH_MAX]; - char *path_fs; - const char *p; - ssize_t ret; - - path_fs = map_directory_child_fs(directory, utf8_name); - if (path_fs == NULL) - return true; - - ret = readlink(path_fs, buffer, sizeof(buffer)); - g_free(path_fs); - if (ret < 0) - /* don't skip if this is not a symlink */ - return errno != EINVAL; - - if (!follow_inside_symlinks && !follow_outside_symlinks) { - /* ignore all symlinks */ - return true; - } else if (follow_inside_symlinks && follow_outside_symlinks) { - /* consider all symlinks */ - return false; - } - - if (buffer[0] == '/') - return !follow_outside_symlinks; - - p = buffer; - while (*p == '.') { - if (p[1] == '.' && p[2] == '/') { - /* "../" moves to parent directory */ - directory = directory->parent; - if (directory == NULL) { - /* we have moved outside the music - directory - skip this symlink - if such symlinks are not allowed */ - return !follow_outside_symlinks; - } - p += 3; - } else if (p[1] == '/') - /* eliminate "./" */ - p += 2; - else - break; - } - - /* we are still in the music directory, so this symlink points - to a song which is already in the database - skip according - to the follow_inside_symlinks param*/ - return !follow_inside_symlinks; -#else - /* no symlink checking on WIN32 */ - - (void)directory; - (void)utf8_name; - - return false; -#endif -} - -static bool -updateDirectory(struct directory *directory, const struct stat *st) -{ - DIR *dir; - struct dirent *ent; - char *path_fs; - - assert(S_ISDIR(st->st_mode)); - - directory_set_stat(directory, st); - - path_fs = map_directory_fs(directory); - if (path_fs == NULL) - return false; - - dir = opendir(path_fs); - if (!dir) { - g_warning("Failed to open directory %s: %s", - path_fs, g_strerror(errno)); - g_free(path_fs); - return false; - } - - g_free(path_fs); - - removeDeletedFromDirectory(directory); - - while ((ent = readdir(dir))) { - char *utf8; - struct stat st2; - - if (skip_path(ent->d_name)) - continue; - - utf8 = fs_charset_to_utf8(ent->d_name); - if (utf8 == NULL) - continue; - - if (skip_symlink(directory, utf8)) { - delete_name_in(directory, utf8); - g_free(utf8); - continue; - } - - if (stat_directory_child(directory, utf8, &st2) == 0) - updateInDirectory(directory, utf8, &st2); - else - delete_name_in(directory, utf8); - - g_free(utf8); - } - - closedir(dir); - - directory->mtime = st->st_mtime; + g_debug("starting"); - return true; -} - -static struct directory * -directory_make_child_checked(struct directory *parent, const char *path) -{ - struct directory *directory; - char *base; - struct stat st; - struct song *conflicting; - - directory = directory_get_child(parent, path); - if (directory != NULL) - return directory; - - base = g_path_get_basename(path); - - if (stat_directory_child(parent, base, &st) < 0 || - inodeFoundInParent(parent, st.st_ino, st.st_dev)) { - g_free(base); - return NULL; - } - - /* if we're adding directory paths, make sure to delete filenames - with potentially the same name */ - conflicting = songvec_find(&parent->songs, base); - if (conflicting) - delete_song(parent, conflicting); + modified = update_walk(path, discard); - g_free(base); - - directory = directory_new_child(parent, path); - directory_set_stat(directory, &st); - return directory; -} - -static struct directory * -addParentPathToDB(const char *utf8path) -{ - struct directory *directory = db_get_root(); - char *duplicated = g_strdup(utf8path); - char *slash = duplicated; - - while ((slash = strchr(slash, '/')) != NULL) { - *slash = 0; - - directory = directory_make_child_checked(directory, - duplicated); - if (directory == NULL || slash == NULL) - break; - - *slash++ = '/'; - } - - g_free(duplicated); - return directory; -} - -static void -updatePath(const char *path) -{ - struct directory *parent; - char *name; - struct stat st; - - parent = addParentPathToDB(path); - if (parent == NULL) - return; - - name = g_path_get_basename(path); + if (modified || !db_exists()) + db_save(); - if (stat_directory_child(parent, name, &st) == 0) - updateInDirectory(parent, name, &st); + if (path != NULL && *path != 0) + g_debug("finished: %s", path); else - delete_name_in(parent, name); - - g_free(name); -} - -static void * update_task(void *_path) -{ - if (_path != NULL && !isRootDirectory(_path)) { - updatePath((char *)_path); - } else { - struct directory *directory = db_get_root(); - struct stat st; - - if (stat_directory(directory, &st) == 0) - updateDirectory(directory, &st); - } - + g_debug("finished"); g_free(_path); - if (modified || !db_exists()) - db_save(); - progress = UPDATE_PROGRESS_DONE; event_pipe_emit(PIPE_EVENT_UPDATE); return NULL; } -static void spawn_update_task(char *path) +static void +spawn_update_task(const char *path) { GError *e = NULL; @@ -802,15 +91,18 @@ static void spawn_update_task(char *path) progress = UPDATE_PROGRESS_RUNNING; modified = false; - if (!(update_thr = g_thread_create(update_task, path, TRUE, &e))) - g_error("Failed to spawn update task: %s", e->message); + + update_thr = g_thread_create(update_task, g_strdup(path), TRUE, &e); + if (update_thr == NULL) + MPD_ERROR("Failed to spawn update task: %s", e->message); + if (++update_task_id > update_task_id_max) update_task_id = 1; g_debug("spawned thread for update job id %i", update_task_id); } unsigned -directory_update_init(char *path) +update_enqueue(const char *path, bool _discard) { assert(g_thread_self() == main_task); @@ -818,48 +110,20 @@ directory_update_init(char *path) return 0; if (progress != UPDATE_PROGRESS_IDLE) { - unsigned next_task_id; - - if (update_paths_nr == G_N_ELEMENTS(update_paths)) { - g_free(path); + unsigned next_task_id = + update_queue_push(path, discard, update_task_id); + if (next_task_id == 0) return 0; - } - - assert(update_paths_nr < G_N_ELEMENTS(update_paths)); - update_paths[update_paths_nr++] = path; - next_task_id = update_task_id + update_paths_nr; return next_task_id > update_task_id_max ? 1 : next_task_id; } - spawn_update_task(path); - return update_task_id; -} - -/** - * Safely delete a song from the database. This must be done in the - * main task, to be sure that there is no pointer left to it. - */ -static void song_delete_event(void) -{ - char *uri; - - assert(progress == UPDATE_PROGRESS_RUNNING); - assert(delete != NULL); - uri = song_get_uri(delete); - g_debug("removing: %s", uri); - g_free(uri); - -#ifdef ENABLE_SQLITE - /* if the song has a sticker, delete it */ - if (sticker_enabled()) - sticker_song_delete(delete); -#endif + discard = _discard; + spawn_update_task(path); - deleteASongFromPlaylist(&g_playlist, delete); - delete = NULL; + idle_add(IDLE_UPDATE); - notify_signal(&update_notify); + return update_task_id; } /** @@ -867,22 +131,25 @@ static void song_delete_event(void) */ static void update_finished_event(void) { + char *path; + assert(progress == UPDATE_PROGRESS_DONE); g_thread_join(update_thr); + idle_add(IDLE_UPDATE); + if (modified) { /* send "idle" events */ - playlistVersionChange(&g_playlist); + playlist_increment_version_all(&g_playlist); idle_add(IDLE_DATABASE); } - if (update_paths_nr) { + path = update_queue_shift(&discard); + if (path != NULL) { /* schedule the next path */ - char *path = update_paths[0]; - memmove(&update_paths[0], &update_paths[1], - --update_paths_nr * sizeof(char *)); spawn_update_task(path); + g_free(path); } else { progress = UPDATE_PROGRESS_IDLE; @@ -892,23 +159,14 @@ static void update_finished_event(void) void update_global_init(void) { -#ifndef WIN32 - follow_inside_symlinks = - config_get_bool(CONF_FOLLOW_INSIDE_SYMLINKS, - DEFAULT_FOLLOW_INSIDE_SYMLINKS); - - follow_outside_symlinks = - config_get_bool(CONF_FOLLOW_OUTSIDE_SYMLINKS, - DEFAULT_FOLLOW_OUTSIDE_SYMLINKS); -#endif - - notify_init(&update_notify); - - event_pipe_register(PIPE_EVENT_DELETE, song_delete_event); event_pipe_register(PIPE_EVENT_UPDATE, update_finished_event); + + update_remove_global_init(); + update_walk_global_init(); } void update_global_finish(void) { - notify_deinit(&update_notify); + update_walk_global_finish(); + update_remove_global_finish(); } diff --git a/src/update.h b/src/update.h index 3b7a5a332..3f8a6f6a4 100644 --- a/src/update.h +++ b/src/update.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,8 @@ #ifndef MPD_UPDATE_H #define MPD_UPDATE_H +#include <stdbool.h> + void update_global_init(void); void update_global_finish(void); @@ -27,12 +29,14 @@ void update_global_finish(void); unsigned isUpdatingDB(void); -/* - * returns the positive update job ID on success, - * returns 0 if busy - * @path will be freed by this function and should not be reused +/** + * Add this path to the database update queue. + * + * @param path a path to update; if NULL or an empty string, + * the whole music directory is updated + * @return the job id, or 0 on error */ unsigned -directory_update_init(char *path); +update_enqueue(const char *path, bool discard); #endif diff --git a/src/update_internal.h b/src/update_internal.h new file mode 100644 index 000000000..65744f0d6 --- /dev/null +++ b/src/update_internal.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPDATE_INTERNAL_H +#define MPD_UPDATE_INTERNAL_H + +#include <stdbool.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "update" + +struct stat; +struct song; +struct directory; + +unsigned +update_queue_push(const char *path, bool discard, unsigned base); + +char * +update_queue_shift(bool *discard_r); + +void +update_walk_global_init(void); + +void +update_walk_global_finish(void); + +/** + * Returns true if the database was modified. + */ +bool +update_walk(const char *path, bool discard); + +void +update_remove_global_init(void); + +void +update_remove_global_finish(void); + +/** + * Sends a signal to the main thread which will in turn remove the + * song: from the sticker database and from the playlist. This + * serialized access is implemented to avoid excessive locking. + */ +void +update_remove_song(const struct song *song); + +#endif diff --git a/src/update_queue.c b/src/update_queue.c new file mode 100644 index 000000000..d7b2d4e5f --- /dev/null +++ b/src/update_queue.c @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "update_internal.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +/* make this dynamic?, or maybe this is big enough... */ +static struct { + char *path; + bool discard; +} update_queue[32]; + +static size_t update_queue_length; + +unsigned +update_queue_push(const char *path, bool discard, unsigned base) +{ + assert(update_queue_length <= G_N_ELEMENTS(update_queue)); + + if (update_queue_length == G_N_ELEMENTS(update_queue)) + return 0; + + update_queue[update_queue_length].path = g_strdup(path); + update_queue[update_queue_length].discard = discard; + + ++update_queue_length; + + return base + update_queue_length; +} + +char * +update_queue_shift(bool *discard_r) +{ + char *path; + + if (update_queue_length == 0) + return NULL; + + path = update_queue[0].path; + *discard_r = update_queue[0].discard; + + memmove(&update_queue[0], &update_queue[1], + --update_queue_length * sizeof(update_queue[0])); + return path; +} diff --git a/src/update_remove.c b/src/update_remove.c new file mode 100644 index 000000000..f7c2342a2 --- /dev/null +++ b/src/update_remove.c @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "update_internal.h" +#include "notify.h" +#include "event_pipe.h" +#include "song.h" +#include "playlist.h" + +#ifdef ENABLE_SQLITE +#include "sticker.h" +#include "song_sticker.h" +#endif + +#include <glib.h> + +#include <assert.h> + +static const struct song *removed_song; + +static struct notify remove_notify; + +/** + * Safely remove a song from the database. This must be done in the + * main task, to be sure that there is no pointer left to it. + */ +static void +song_remove_event(void) +{ + char *uri; + + assert(removed_song != NULL); + + uri = song_get_uri(removed_song); + g_debug("removing: %s", uri); + g_free(uri); + +#ifdef ENABLE_SQLITE + /* if the song has a sticker, remove it */ + if (sticker_enabled()) + sticker_song_delete(removed_song); +#endif + + playlist_delete_song(&g_playlist, removed_song); + removed_song = NULL; + + notify_signal(&remove_notify); +} + +void +update_remove_global_init(void) +{ + notify_init(&remove_notify); + + event_pipe_register(PIPE_EVENT_DELETE, song_remove_event); +} + +void +update_remove_global_finish(void) +{ + notify_deinit(&remove_notify); +} + +void +update_remove_song(const struct song *song) +{ + assert(removed_song == NULL); + + removed_song = song; + + event_pipe_emit(PIPE_EVENT_DELETE); + + do { + notify_wait(&remove_notify); + } while (removed_song != NULL); + +} diff --git a/src/update_walk.c b/src/update_walk.c new file mode 100644 index 000000000..95d5854a8 --- /dev/null +++ b/src/update_walk.c @@ -0,0 +1,909 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "update_internal.h" +#include "database.h" +#include "exclude.h" +#include "directory.h" +#include "song.h" +#include "uri.h" +#include "mapper.h" +#include "path.h" +#include "decoder_list.h" +#include "decoder_plugin.h" +#include "playlist_list.h" +#include "conf.h" + +#ifdef ENABLE_ARCHIVE +#include "archive_list.h" +#include "archive_plugin.h" +#endif + +#include <glib.h> + +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <dirent.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> + +static bool walk_discard; +static bool modified; + +#ifndef WIN32 + +enum { + DEFAULT_FOLLOW_INSIDE_SYMLINKS = true, + DEFAULT_FOLLOW_OUTSIDE_SYMLINKS = true, +}; + +static bool follow_inside_symlinks; +static bool follow_outside_symlinks; + +#endif + +void +update_walk_global_init(void) +{ +#ifndef WIN32 + follow_inside_symlinks = + config_get_bool(CONF_FOLLOW_INSIDE_SYMLINKS, + DEFAULT_FOLLOW_INSIDE_SYMLINKS); + + follow_outside_symlinks = + config_get_bool(CONF_FOLLOW_OUTSIDE_SYMLINKS, + DEFAULT_FOLLOW_OUTSIDE_SYMLINKS); +#endif +} + +void +update_walk_global_finish(void) +{ +} + +static void +directory_set_stat(struct directory *dir, const struct stat *st) +{ + dir->inode = st->st_ino; + dir->device = st->st_dev; + dir->stat = 1; +} + +static void +delete_song(struct directory *dir, struct song *del) +{ + /* first, prevent traversers in main task from getting this */ + songvec_delete(&dir->songs, del); + + /* now take it out of the playlist (in the main_task) */ + update_remove_song(del); + + /* finally, all possible references gone, free it */ + song_free(del); +} + +static int +delete_each_song(struct song *song, G_GNUC_UNUSED void *data) +{ + struct directory *directory = data; + assert(song->parent == directory); + delete_song(directory, song); + return 0; +} + +static void +delete_directory(struct directory *directory); + +/** + * Recursively remove all sub directories and songs from a directory, + * leaving an empty directory. + */ +static void +clear_directory(struct directory *directory) +{ + int i; + + for (i = directory->children.nr; --i >= 0;) + delete_directory(directory->children.base[i]); + + assert(directory->children.nr == 0); + + songvec_for_each(&directory->songs, delete_each_song, directory); +} + +/** + * Recursively free a directory and all its contents. + */ +static void +delete_directory(struct directory *directory) +{ + assert(directory->parent != NULL); + + clear_directory(directory); + + dirvec_delete(&directory->parent->children, directory); + directory_free(directory); +} + +static void +delete_name_in(struct directory *parent, const char *name) +{ + struct directory *directory = directory_get_child(parent, name); + struct song *song = songvec_find(&parent->songs, name); + + if (directory != NULL) { + delete_directory(directory); + modified = true; + } + + if (song != NULL) { + delete_song(parent, song); + modified = true; + } + + playlist_vector_remove(&parent->playlists, name); +} + +/* passed to songvec_for_each */ +static int +delete_song_if_excluded(struct song *song, void *_data) +{ + GSList *exclude_list = _data; + char *name_fs; + + assert(song->parent != NULL); + + name_fs = utf8_to_fs_charset(song->uri); + if (exclude_list_check(exclude_list, name_fs)) { + delete_song(song->parent, song); + modified = true; + } + + g_free(name_fs); + return 0; +} + +static void +remove_excluded_from_directory(struct directory *directory, + GSList *exclude_list) +{ + int i; + struct dirvec *dv = &directory->children; + + for (i = dv->nr; --i >= 0; ) { + struct directory *child = dv->base[i]; + char *name_fs = utf8_to_fs_charset(directory_get_name(child)); + + if (exclude_list_check(exclude_list, name_fs)) { + delete_directory(child); + modified = true; + } + + g_free(name_fs); + } + + songvec_for_each(&directory->songs, + delete_song_if_excluded, exclude_list); +} + +/* passed to songvec_for_each */ +static int +delete_song_if_removed(struct song *song, void *_data) +{ + struct directory *dir = _data; + char *path; + struct stat st; + + if ((path = map_song_fs(song)) == NULL || + stat(path, &st) < 0 || !S_ISREG(st.st_mode)) { + delete_song(dir, song); + modified = true; + } + + g_free(path); + return 0; +} + +static bool +directory_exists(const struct directory *directory) +{ + char *path_fs; + GFileTest test; + bool exists; + + path_fs = map_directory_fs(directory); + if (path_fs == NULL) + /* invalid path: cannot exist */ + return false; + + test = directory->device == DEVICE_INARCHIVE || + directory->device == DEVICE_CONTAINER + ? G_FILE_TEST_IS_REGULAR + : G_FILE_TEST_IS_DIR; + + exists = g_file_test(path_fs, test); + g_free(path_fs); + + return exists; +} + +static bool +directory_child_is_regular(const struct directory *directory, + const char *name_utf8) +{ + char *path_fs = map_directory_child_fs(directory, name_utf8); + if (path_fs == NULL) + return false; + + struct stat st; + bool is_regular = stat(path_fs, &st) == 0 && S_ISREG(st.st_mode); + g_free(path_fs); + + return is_regular; +} + +static void +removeDeletedFromDirectory(struct directory *directory) +{ + int i; + struct dirvec *dv = &directory->children; + + for (i = dv->nr; --i >= 0; ) { + if (directory_exists(dv->base[i])) + continue; + + g_debug("removing directory: %s", dv->base[i]->path); + delete_directory(dv->base[i]); + modified = true; + } + + songvec_for_each(&directory->songs, delete_song_if_removed, directory); + + for (const struct playlist_metadata *pm = directory->playlists.head; + pm != NULL;) { + const struct playlist_metadata *next = pm->next; + + if (!directory_child_is_regular(directory, pm->name)) + playlist_vector_remove(&directory->playlists, pm->name); + + pm = next; + } +} + +static int +stat_directory(const struct directory *directory, struct stat *st) +{ + char *path_fs; + int ret; + + path_fs = map_directory_fs(directory); + if (path_fs == NULL) + return -1; + ret = stat(path_fs, st); + g_free(path_fs); + return ret; +} + +static int +stat_directory_child(const struct directory *parent, const char *name, + struct stat *st) +{ + char *path_fs; + int ret; + + path_fs = map_directory_child_fs(parent, name); + if (path_fs == NULL) + return -1; + + ret = stat(path_fs, st); + g_free(path_fs); + return ret; +} + +#ifndef G_OS_WIN32 +static int +statDirectory(struct directory *dir) +{ + struct stat st; + + if (stat_directory(dir, &st) < 0) + return -1; + + directory_set_stat(dir, &st); + + return 0; +} +#endif + +static int +inodeFoundInParent(struct directory *parent, ino_t inode, dev_t device) +{ +#ifndef G_OS_WIN32 + while (parent) { + if (!parent->stat && statDirectory(parent) < 0) + return -1; + if (parent->inode == inode && parent->device == device) { + g_debug("recursive directory found"); + return 1; + } + parent = parent->parent; + } +#else + (void)parent; + (void)inode; + (void)device; +#endif + + return 0; +} + +static struct directory * +make_subdir(struct directory *parent, const char *name) +{ + struct directory *directory; + + directory = directory_get_child(parent, name); + if (directory == NULL) { + char *path; + + if (directory_is_root(parent)) + path = NULL; + else + name = path = g_strconcat(directory_get_path(parent), + "/", name, NULL); + + directory = directory_new_child(parent, name); + g_free(path); + } + + return directory; +} + +#ifdef ENABLE_ARCHIVE +static void +update_archive_tree(struct directory *directory, char *name) +{ + struct directory *subdir; + struct song *song; + char *tmp; + + tmp = strchr(name, '/'); + if (tmp) { + *tmp = 0; + //add dir is not there already + if ((subdir = dirvec_find(&directory->children, name)) == NULL) { + //create new directory + subdir = make_subdir(directory, name); + subdir->device = DEVICE_INARCHIVE; + } + //create directories first + update_archive_tree(subdir, tmp+1); + } else { + if (strlen(name) == 0) { + g_warning("archive returned directory only"); + return; + } + //add file + song = songvec_find(&directory->songs, name); + if (song == NULL) { + song = song_file_load(name, directory); + if (song != NULL) { + songvec_add(&directory->songs, song); + modified = true; + g_message("added %s/%s", + directory_get_path(directory), name); + } + } + } +} + +/** + * Updates the file listing from an archive file. + * + * @param parent the parent directory the archive file resides in + * @param name the UTF-8 encoded base name of the archive file + * @param st stat() information on the archive file + * @param plugin the archive plugin which fits this archive type + */ +static void +update_archive_file(struct directory *parent, const char *name, + const struct stat *st, + const struct archive_plugin *plugin) +{ + GError *error = NULL; + char *path_fs; + struct archive_file *file; + struct directory *directory; + char *filepath; + + directory = dirvec_find(&parent->children, name); + if (directory != NULL && directory->mtime == st->st_mtime && + !walk_discard) + /* MPD has already scanned the archive, and it hasn't + changed since - don't consider updating it */ + return; + + path_fs = map_directory_child_fs(parent, name); + + /* open archive */ + file = archive_file_open(plugin, path_fs, &error); + if (file == NULL) { + g_free(path_fs); + g_warning("%s", error->message); + g_error_free(error); + return; + } + + g_debug("archive %s opened", path_fs); + g_free(path_fs); + + if (directory == NULL) { + g_debug("creating archive directory: %s", name); + directory = make_subdir(parent, name); + /* mark this directory as archive (we use device for + this) */ + directory->device = DEVICE_INARCHIVE; + } + + directory->mtime = st->st_mtime; + + archive_file_scan_reset(file); + + while ((filepath = archive_file_scan_next(file)) != NULL) { + /* split name into directory and file */ + g_debug("adding archive file: %s", filepath); + update_archive_tree(directory, filepath); + } + + archive_file_close(file); +} +#endif + +static bool +update_container_file( struct directory* directory, + const char* name, + const struct stat* st, + const struct decoder_plugin* plugin) +{ + char* vtrack = NULL; + unsigned int tnum = 0; + char* pathname = map_directory_child_fs(directory, name); + struct directory* contdir = dirvec_find(&directory->children, name); + + // directory exists already + if (contdir != NULL) + { + // modification time not eq. file mod. time + if (contdir->mtime != st->st_mtime || walk_discard) + { + g_message("removing container file: %s", pathname); + + delete_directory(contdir); + contdir = NULL; + + modified = true; + } + else { + g_free(pathname); + return true; + } + } + + contdir = make_subdir(directory, name); + contdir->mtime = st->st_mtime; + contdir->device = DEVICE_CONTAINER; + + while ((vtrack = plugin->container_scan(pathname, ++tnum)) != NULL) + { + struct song* song = song_file_new(vtrack, contdir); + char *child_path_fs; + + // shouldn't be necessary but it's there.. + song->mtime = st->st_mtime; + + child_path_fs = map_directory_child_fs(contdir, vtrack); + + song->tag = plugin->tag_dup(child_path_fs); + g_free(child_path_fs); + + songvec_add(&contdir->songs, song); + + modified = true; + + g_message("added %s/%s", + directory_get_path(directory), vtrack); + g_free(vtrack); + } + + g_free(pathname); + + if (tnum == 1) + { + delete_directory(contdir); + return false; + } + else + return true; +} + +/** + * Checks if the given permissions on the mapped file are given. + */ +static bool +directory_child_access(const struct directory *directory, + const char *name, int mode) +{ +#ifdef WIN32 + /* access() is useless on WIN32 */ + (void)directory; + (void)name; + return true; +#else + char *path = map_directory_child_fs(directory, name); + if (path == NULL) + /* something went wrong, but that isn't a permission + problem */ + return true; + + bool success = access(path, mode) == 0 || errno != EACCES; + g_free(path); + return success; +#endif +} + +static void +update_regular_file(struct directory *directory, + const char *name, const struct stat *st) +{ + const char *suffix = uri_get_suffix(name); + const struct decoder_plugin* plugin; +#ifdef ENABLE_ARCHIVE + const struct archive_plugin *archive; +#endif + if (suffix == NULL) + return; + + if ((plugin = decoder_plugin_from_suffix(suffix, false)) != NULL) + { + struct song* song = songvec_find(&directory->songs, name); + + if (!directory_child_access(directory, name, R_OK)) { + g_warning("no read permissions on %s/%s", + directory_get_path(directory), name); + if (song != NULL) + delete_song(directory, song); + return; + } + + if (!(song != NULL && st->st_mtime == song->mtime && + !walk_discard) && + plugin->container_scan != NULL) + { + if (update_container_file(directory, name, st, plugin)) + { + if (song != NULL) + delete_song(directory, song); + + return; + } + } + + if (song == NULL) { + song = song_file_load(name, directory); + if (song == NULL) { + g_debug("ignoring unrecognized file %s/%s", + directory_get_path(directory), name); + return; + } + + songvec_add(&directory->songs, song); + modified = true; + g_message("added %s/%s", + directory_get_path(directory), name); + } else if (st->st_mtime != song->mtime || walk_discard) { + g_message("updating %s/%s", + directory_get_path(directory), name); + if (!song_file_update(song)) { + g_debug("deleting unrecognized file %s/%s", + directory_get_path(directory), name); + delete_song(directory, song); + } + + modified = true; + } +#ifdef ENABLE_ARCHIVE + } else if ((archive = archive_plugin_from_suffix(suffix))) { + update_archive_file(directory, name, st, archive); +#endif + + } else if (playlist_suffix_supported(suffix)) { + if (playlist_vector_update_or_add(&directory->playlists, name, + st->st_mtime)) + modified = true; + } +} + +static bool +updateDirectory(struct directory *directory, const struct stat *st); + +static void +updateInDirectory(struct directory *directory, + const char *name, const struct stat *st) +{ + assert(strchr(name, '/') == NULL); + + if (S_ISREG(st->st_mode)) { + update_regular_file(directory, name, st); + } else if (S_ISDIR(st->st_mode)) { + struct directory *subdir; + bool ret; + + if (inodeFoundInParent(directory, st->st_ino, st->st_dev)) + return; + + subdir = make_subdir(directory, name); + assert(directory == subdir->parent); + + ret = updateDirectory(subdir, st); + if (!ret) + delete_directory(subdir); + } else { + g_debug("update: %s is not a directory, archive or music", name); + } +} + +/* we don't look at "." / ".." nor files with newlines in their name */ +static bool skip_path(const char *path) +{ + return (path[0] == '.' && path[1] == 0) || + (path[0] == '.' && path[1] == '.' && path[2] == 0) || + strchr(path, '\n') != NULL; +} + +static bool +skip_symlink(const struct directory *directory, const char *utf8_name) +{ +#ifndef WIN32 + char buffer[MPD_PATH_MAX]; + char *path_fs; + const char *p; + ssize_t ret; + + path_fs = map_directory_child_fs(directory, utf8_name); + if (path_fs == NULL) + return true; + + ret = readlink(path_fs, buffer, sizeof(buffer)); + g_free(path_fs); + if (ret < 0) + /* don't skip if this is not a symlink */ + return errno != EINVAL; + + if (!follow_inside_symlinks && !follow_outside_symlinks) { + /* ignore all symlinks */ + return true; + } else if (follow_inside_symlinks && follow_outside_symlinks) { + /* consider all symlinks */ + return false; + } + + if (buffer[0] == '/') + return !follow_outside_symlinks; + + p = buffer; + while (*p == '.') { + if (p[1] == '.' && G_IS_DIR_SEPARATOR(p[2])) { + /* "../" moves to parent directory */ + directory = directory->parent; + if (directory == NULL) { + /* we have moved outside the music + directory - skip this symlink + if such symlinks are not allowed */ + return !follow_outside_symlinks; + } + p += 3; + } else if (G_IS_DIR_SEPARATOR(p[1])) + /* eliminate "./" */ + p += 2; + else + break; + } + + /* we are still in the music directory, so this symlink points + to a song which is already in the database - skip according + to the follow_inside_symlinks param*/ + return !follow_inside_symlinks; +#else + /* no symlink checking on WIN32 */ + + (void)directory; + (void)utf8_name; + + return false; +#endif +} + +static bool +updateDirectory(struct directory *directory, const struct stat *st) +{ + DIR *dir; + struct dirent *ent; + char *path_fs, *exclude_path_fs; + GSList *exclude_list; + + assert(S_ISDIR(st->st_mode)); + + directory_set_stat(directory, st); + + path_fs = map_directory_fs(directory); + if (path_fs == NULL) + return false; + + dir = opendir(path_fs); + if (!dir) { + g_warning("Failed to open directory %s: %s", + path_fs, g_strerror(errno)); + g_free(path_fs); + return false; + } + + exclude_path_fs = g_build_filename(path_fs, ".mpdignore", NULL); + exclude_list = exclude_list_load(exclude_path_fs); + g_free(exclude_path_fs); + + g_free(path_fs); + + if (exclude_list != NULL) + remove_excluded_from_directory(directory, exclude_list); + + removeDeletedFromDirectory(directory); + + while ((ent = readdir(dir))) { + char *utf8; + struct stat st2; + + if (skip_path(ent->d_name) || + exclude_list_check(exclude_list, ent->d_name)) + continue; + + utf8 = fs_charset_to_utf8(ent->d_name); + if (utf8 == NULL) + continue; + + if (skip_symlink(directory, utf8)) { + delete_name_in(directory, utf8); + g_free(utf8); + continue; + } + + if (stat_directory_child(directory, utf8, &st2) == 0) + updateInDirectory(directory, utf8, &st2); + else + delete_name_in(directory, utf8); + + g_free(utf8); + } + + exclude_list_free(exclude_list); + + closedir(dir); + + directory->mtime = st->st_mtime; + + return true; +} + +static struct directory * +directory_make_child_checked(struct directory *parent, const char *path) +{ + struct directory *directory; + char *base; + struct stat st; + struct song *conflicting; + + directory = directory_get_child(parent, path); + if (directory != NULL) + return directory; + + base = g_path_get_basename(path); + + if (stat_directory_child(parent, base, &st) < 0 || + inodeFoundInParent(parent, st.st_ino, st.st_dev)) { + g_free(base); + return NULL; + } + + /* if we're adding directory paths, make sure to delete filenames + with potentially the same name */ + conflicting = songvec_find(&parent->songs, base); + if (conflicting) + delete_song(parent, conflicting); + + g_free(base); + + directory = directory_new_child(parent, path); + directory_set_stat(directory, &st); + return directory; +} + +static struct directory * +addParentPathToDB(const char *utf8path) +{ + struct directory *directory = db_get_root(); + char *duplicated = g_strdup(utf8path); + char *slash = duplicated; + + while ((slash = strchr(slash, '/')) != NULL) { + *slash = 0; + + directory = directory_make_child_checked(directory, + duplicated); + if (directory == NULL || slash == NULL) + break; + + *slash++ = '/'; + } + + g_free(duplicated); + return directory; +} + +static void +updatePath(const char *path) +{ + struct directory *parent; + char *name; + struct stat st; + + parent = addParentPathToDB(path); + if (parent == NULL) + return; + + name = g_path_get_basename(path); + + if (stat_directory_child(parent, name, &st) == 0) + updateInDirectory(parent, name, &st); + else + delete_name_in(parent, name); + + g_free(name); +} + +bool +update_walk(const char *path, bool discard) +{ + walk_discard = discard; + modified = false; + + if (path != NULL && !isRootDirectory(path)) { + updatePath(path); + } else { + struct directory *directory = db_get_root(); + struct stat st; + + if (stat_directory(directory, &st) == 0) + updateDirectory(directory, &st); + } + + return modified; +} @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "uri.h" #include <glib.h> +#include <assert.h> #include <string.h> bool uri_has_scheme(const char *uri) @@ -32,9 +34,51 @@ bool uri_has_scheme(const char *uri) const char * uri_get_suffix(const char *uri) { - const char *dot = strrchr(g_basename(uri), '.'); + const char *suffix = strrchr(g_basename(uri), '.'); + if (suffix == NULL) + return NULL; + + ++suffix; + + if (strchr(suffix, '/') != NULL) + return NULL; + + return suffix; +} + +static const char * +verify_uri_segment(const char *p) +{ + const char *q; + + unsigned dots = 0; + while (*p == '.') { + ++p; + ++dots; + } + + if (dots <= 2 && (*p == 0 || *p == '/')) + return NULL; + + q = strchr(p + 1, '/'); + return q != NULL ? q : ""; +} + +bool +uri_safe_local(const char *uri) +{ + while (true) { + uri = verify_uri_segment(uri); + if (uri == NULL) + return false; + + if (*uri == 0) + return true; + + assert(*uri == '/'); - return dot != NULL ? dot + 1 : NULL; + ++uri; + } } char * @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,23 +20,40 @@ #ifndef MPD_URI_H #define MPD_URI_H +#include <glib.h> + #include <stdbool.h> /** * Checks whether the specified URI has a schema in the form * "scheme://". */ +G_GNUC_PURE bool uri_has_scheme(const char *uri); +G_GNUC_PURE const char * uri_get_suffix(const char *uri); /** + * Returns true if this is a safe "local" URI: + * + * - non-empty + * - does not begin or end with a slash + * - no double slashes + * - no path component begins with a dot + */ +G_GNUC_PURE +bool +uri_safe_local(const char *uri); + +/** * Removes HTTP username and password from the URI. This may be * useful for displaying an URI without disclosing secrets. Returns * NULL if nothing needs to be removed, or if the URI is not * recognized. */ +G_GNUC_MALLOC char * uri_remove_auth(const char *uri); diff --git a/src/utils.c b/src/utils.c index fc27b13c9..53494cc5d 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,9 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "utils.h" #include "conf.h" -#include "config.h" #include <glib.h> @@ -44,7 +44,7 @@ char *parsePath(char *path) { #ifndef WIN32 - if (path[0] != '/' && path[0] != '~') { + if (!g_path_is_absolute(path) && path[0] != '~') { g_warning("\"%s\" is not an absolute path", path); return NULL; } else if (path[0] == '~') { @@ -102,43 +102,15 @@ char *parsePath(char *path) #endif } -int set_nonblocking(int fd) +bool +string_array_contains(const char *const* haystack, const char *needle) { -#ifdef WIN32 - u_long val = 1; - int retval; - int lasterr = 0; - retval = ioctlsocket(fd, FIONBIO, &val); - if(retval == SOCKET_ERROR) - g_error("Error: ioctlsocket could not set FIONBIO;" - " Error %d on socket %d", lasterr = WSAGetLastError(), fd); - if(lasterr == 10038) - g_debug("Code-up error! Attempt to set non-blocking I/O on " - "something that is not a Winsock2 socket. This can't " - "be done on Windows!\n"); - return retval; -#else - int ret, flags; - - assert(fd >= 0); - - while ((flags = fcntl(fd, F_GETFL)) < 0 && errno == EINTR) ; - if (flags < 0) - return flags; - - flags |= O_NONBLOCK; - while ((ret = fcntl(fd, F_SETFL, flags)) < 0 && errno == EINTR) ; - return ret; -#endif -} + assert(haystack != NULL); + assert(needle != NULL); -int stringFoundInStringArray(const char *const*array, const char *suffix) -{ - while (array && *array) { - if (g_ascii_strcasecmp(*array, suffix) == 0) - return 1; - array++; - } + for (; *haystack != NULL; ++haystack) + if (g_ascii_strcasecmp(*haystack, needle) == 0) + return true; - return 0; + return false; } diff --git a/src/utils.h b/src/utils.h index d114003be..629056637 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,8 @@ #ifndef MPD_UTILS_H #define MPD_UTILS_H +#include <stdbool.h> + #ifndef assert_static /* Compile time assertion developed by Ralf Holly */ /* http://pera-software.com/articles/compile-time-assertions.pdf */ @@ -31,8 +33,15 @@ char *parsePath(char *path); -int set_nonblocking(int fd); - -int stringFoundInStringArray(const char *const*array, const char *suffix); +/** + * Checks whether a string array contains the specified string. + * + * @param haystack a NULL terminated list of strings + * @param needle the string to search for; the comparison is + * case-insensitive for ASCII characters + * @return true if found + */ +bool +string_array_contains(const char *const* haystack, const char *needle); #endif diff --git a/src/volume.c b/src/volume.c index e7fa20a62..d7b72dd56 100644 --- a/src/volume.c +++ b/src/volume.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,15 +17,17 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "volume.h" #include "conf.h" #include "player_control.h" #include "idle.h" #include "pcm_volume.h" -#include "config.h" #include "output_all.h" #include "mixer_control.h" #include "mixer_all.h" +#include "mixer_type.h" +#include "event_pipe.h" #include <glib.h> @@ -39,132 +41,39 @@ #define SW_VOLUME_STATE "sw_volume: " -static enum { - VOLUME_MIXER_TYPE_SOFTWARE, - VOLUME_MIXER_TYPE_HARDWARE, - VOLUME_MIXER_TYPE_DISABLED, -} volume_mixer_type = VOLUME_MIXER_TYPE_HARDWARE; - -static int volume_software_set = 100; +static unsigned volume_software_set = 100; /** the cached hardware mixer value; invalid if negative */ static int last_hardware_volume = -1; /** the age of #last_hardware_volume */ static GTimer *hardware_volume_timer; -void volume_finish(void) -{ - if (volume_mixer_type == VOLUME_MIXER_TYPE_HARDWARE) - g_timer_destroy(hardware_volume_timer); -} - /** - * Finds the first audio_output configuration section with the - * specified type. - */ -static struct config_param * -find_output_config(const char *type) -{ - struct config_param *param = NULL; - - while ((param = config_get_next_param(CONF_AUDIO_OUTPUT, - param)) != NULL) { - const char *param_type = - config_get_block_string(param, "type", NULL); - if (param_type != NULL && strcmp(param_type, type) == 0) - return param; - } - - return NULL; -} - -/** - * Copy a (top-level) legacy mixer configuration parameter to the - * audio_output section. + * Handler for #PIPE_EVENT_MIXER. */ static void -mixer_copy_legacy_param(const char *type, const char *name) +mixer_event_callback(void) { - const struct config_param *param; - struct config_param *output; - const struct block_param *bp; - - /* see if the deprecated configuration exists */ - - param = config_get_param(name); - if (param == NULL) - return; - - g_warning("deprecated option '%s' found, moving to '%s' audio output", - name, type); - - /* determine the configuration section */ - - output = find_output_config(type); - if (output == NULL) { - /* if there is no output configuration at all, create - a new and empty configuration section for the - legacy mixer */ - - if (config_get_next_param(CONF_AUDIO_OUTPUT, NULL) != NULL) - /* there is an audio_output configuration, but - it does not match the mixer_type setting */ - g_error("no '%s' audio output found", type); - - output = config_new_param(NULL, param->line); - config_add_block_param(output, "type", type, param->line); - config_add_block_param(output, "name", type, param->line); - config_add_param(CONF_AUDIO_OUTPUT, output); - } - - bp = config_get_block_param(output, name); - if (bp != NULL) - g_error("the '%s' audio output already has a '%s' setting", - type, name); - - /* duplicate the parameter in the configuration section */ + /* flush the hardware volume cache */ + last_hardware_volume = -1; - config_add_block_param(output, name, param->value, param->line); + /* notify clients */ + idle_add(IDLE_MIXER); } -static void -mixer_reconfigure(const char *type) +void volume_finish(void) { - mixer_copy_legacy_param(type, CONF_MIXER_DEVICE); - mixer_copy_legacy_param(type, CONF_MIXER_CONTROL); + g_timer_destroy(hardware_volume_timer); } void volume_init(void) { - const struct config_param *param = config_get_param(CONF_MIXER_TYPE); - //hw mixing is by default - if (param) { - if (strcmp(param->value, VOLUME_MIXER_SOFTWARE) == 0) { - volume_mixer_type = VOLUME_MIXER_TYPE_SOFTWARE; - mixer_disable_all(); - } else if (strcmp(param->value, VOLUME_MIXER_DISABLED) == 0) { - volume_mixer_type = VOLUME_MIXER_TYPE_DISABLED; - mixer_disable_all(); - } else if (strcmp(param->value, VOLUME_MIXER_HARDWARE) == 0) { - //nothing to do - } else { - //fallback to old config behaviour - if (strcmp(param->value, VOLUME_MIXER_OSS) == 0) { - mixer_reconfigure(param->value); - } else if (strcmp(param->value, VOLUME_MIXER_ALSA) == 0) { - mixer_reconfigure(param->value); - } else { - g_error("unknown mixer type %s at line %i\n", - param->value, param->line); - } - } - } + hardware_volume_timer = g_timer_new(); - if (volume_mixer_type == VOLUME_MIXER_TYPE_HARDWARE) - hardware_volume_timer = g_timer_new(); + event_pipe_register(PIPE_EVENT_MIXER, mixer_event_callback); } -static int hardware_volume_get(void) +int volume_level_get(void) { assert(hardware_volume_timer != NULL); @@ -178,101 +87,60 @@ static int hardware_volume_get(void) return last_hardware_volume; } -static int software_volume_get(void) +static bool software_volume_change(unsigned volume) { - return volume_software_set; -} - -int volume_level_get(void) -{ - switch (volume_mixer_type) { - case VOLUME_MIXER_TYPE_SOFTWARE: - return software_volume_get(); - case VOLUME_MIXER_TYPE_HARDWARE: - return hardware_volume_get(); - case VOLUME_MIXER_TYPE_DISABLED: - return -1; - } - - /* unreachable */ - assert(false); - return -1; -} - -static bool software_volume_change(int change, bool rel) -{ - int new = change; - - if (rel) - new += volume_software_set; + assert(volume <= 100); - if (new > 100) - new = 100; - else if (new < 0) - new = 0; - - volume_software_set = new; - - /*new = 100.0*(exp(new/50.0)-1)/(M_E*M_E-1)+0.5; */ - if (new >= 100) - new = PCM_VOLUME_1; - else if (new <= 0) - new = 0; - else - new = pcm_float_to_volume((exp(new / 25.0) - 1) / - (54.5981500331F - 1)); - - setPlayerSoftwareVolume(new); + volume_software_set = volume; + mixer_all_set_software_volume(volume); return true; } -static bool hardware_volume_change(int change, bool rel) +static bool hardware_volume_change(unsigned volume) { /* reset the cache */ last_hardware_volume = -1; - return mixer_all_set_volume(change, rel); + return mixer_all_set_volume(volume); } -bool volume_level_change(int change, bool rel) +bool volume_level_change(unsigned volume) { + assert(volume <= 100); + + volume_software_set = volume; + idle_add(IDLE_MIXER); - switch (volume_mixer_type) { - case VOLUME_MIXER_TYPE_HARDWARE: - return hardware_volume_change(change, rel); - case VOLUME_MIXER_TYPE_SOFTWARE: - return software_volume_change(change, rel); - default: - return true; - } + return hardware_volume_change(volume); } -void read_sw_volume_state(FILE *fp) +bool +read_sw_volume_state(const char *line) { - char buf[sizeof(SW_VOLUME_STATE) + sizeof("100") - 1]; char *end = NULL; long int sv; - if (volume_mixer_type != VOLUME_MIXER_TYPE_SOFTWARE) - return; - while (fgets(buf, sizeof(buf), fp)) { - if (!g_str_has_prefix(buf, SW_VOLUME_STATE)) - continue; + if (!g_str_has_prefix(line, SW_VOLUME_STATE)) + return false; - g_strchomp(buf); - sv = strtol(buf + strlen(SW_VOLUME_STATE), &end, 10); - if (G_LIKELY(!*end)) - software_volume_change(sv, 0); - else - g_warning("Can't parse software volume: %s\n", buf); - return; - } + line += sizeof(SW_VOLUME_STATE) - 1; + sv = strtol(line, &end, 10); + if (*end == 0 && sv >= 0 && sv <= 100) + software_volume_change(sv); + else + g_warning("Can't parse software volume: %s\n", line); + return true; } void save_sw_volume_state(FILE *fp) { - if (volume_mixer_type == VOLUME_MIXER_TYPE_SOFTWARE) - fprintf(fp, SW_VOLUME_STATE "%d\n", volume_software_set); + fprintf(fp, SW_VOLUME_STATE "%u\n", volume_software_set); +} + +unsigned +sw_volume_state_get_hash(void) +{ + return volume_software_set; } diff --git a/src/volume.h b/src/volume.h index 99d31da4e..db266fec9 100644 --- a/src/volume.h +++ b/src/volume.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,22 +23,26 @@ #include <stdbool.h> #include <stdio.h> -#define VOLUME_MIXER_OSS "oss" -#define VOLUME_MIXER_ALSA "alsa" -#define VOLUME_MIXER_SOFTWARE "software" -#define VOLUME_MIXER_HARDWARE "hardware" -#define VOLUME_MIXER_DISABLED "disabled" - void volume_init(void); void volume_finish(void); int volume_level_get(void); -bool volume_level_change(int change, bool rel); +bool volume_level_change(unsigned volume); -void read_sw_volume_state(FILE *fp); +bool +read_sw_volume_state(const char *line); void save_sw_volume_state(FILE *fp); +/** + * Generates a hash number for the current state of the software + * volume control. This is used by timer_save_state_file() to + * determine whether the state has changed and the state file should + * be saved. + */ +unsigned +sw_volume_state_get_hash(void); + #endif diff --git a/src/zeroconf-avahi.c b/src/zeroconf-avahi.c index 648f36e03..518a7a481 100644 --- a/src/zeroconf-avahi.c +++ b/src/zeroconf-avahi.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "zeroconf-internal.h" #include "listen.h" +#include "mpd_error.h" #include <glib.h> @@ -217,7 +219,7 @@ void init_avahi(const char *serviceName) g_debug("Initializing interface"); if (!avahi_is_valid_service_name(serviceName)) - g_error("Invalid zeroconf_name \"%s\"", serviceName); + MPD_ERROR("Invalid zeroconf_name \"%s\"", serviceName); avahiName = avahi_strdup(serviceName); diff --git a/src/zeroconf-bonjour.c b/src/zeroconf-bonjour.c index 4e06319e7..84f777c50 100644 --- a/src/zeroconf-bonjour.c +++ b/src/zeroconf-bonjour.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "zeroconf-internal.h" #include "listen.h" @@ -62,7 +63,7 @@ void init_zeroconf_osx(const char *serviceName) DNSServiceErrorType error = DNSServiceRegister(&dnsReference, 0, 0, serviceName, SERVICE_TYPE, NULL, NULL, - htons(listen_port), 0, + g_htons(listen_port), 0, NULL, dnsRegisterCallback, NULL); diff --git a/src/zeroconf-internal.h b/src/zeroconf-internal.h index f5aacebd8..7cb962431 100644 --- a/src/zeroconf-internal.h +++ b/src/zeroconf-internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/zeroconf.c b/src/zeroconf.c index 42e995c45..7b00789b6 100644 --- a/src/zeroconf.c +++ b/src/zeroconf.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "zeroconf.h" #include "zeroconf-internal.h" #include "conf.h" -#include "config.h" #include <glib.h> diff --git a/src/zeroconf.h b/src/zeroconf.h index 6a5934ed5..23354f87d 100644 --- a/src/zeroconf.h +++ b/src/zeroconf.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,7 +20,7 @@ #ifndef MPD_ZEROCONF_H #define MPD_ZEROCONF_H -#include "config.h" +#include "check.h" #ifdef HAVE_ZEROCONF diff --git a/test/dump_playlist.c b/test/dump_playlist.c new file mode 100644 index 000000000..a8cb4d750 --- /dev/null +++ b/test/dump_playlist.c @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "input_init.h" +#include "input_stream.h" +#include "tag_pool.h" +#include "tag_save.h" +#include "conf.h" +#include "song.h" +#include "playlist_list.h" +#include "playlist_plugin.h" + +#include <glib.h> + +#include <unistd.h> + +static void +my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, + const gchar *message, G_GNUC_UNUSED gpointer user_data) +{ + if (log_domain != NULL) + g_printerr("%s: %s\n", log_domain, message); + else + g_printerr("%s\n", message); +} + +int main(int argc, char **argv) +{ + const char *uri; + struct input_stream *is = NULL; + bool success; + GError *error = NULL; + struct playlist_provider *playlist; + struct song *song; + + if (argc != 3) { + g_printerr("Usage: dump_playlist CONFIG URI\n"); + return 1; + } + + uri = argv[2]; + + /* initialize GLib */ + + g_thread_init(NULL); + g_log_set_default_handler(my_log_func, NULL); + + /* initialize MPD */ + + tag_pool_init(); + config_global_init(); + success = config_read_file(argv[1], &error); + if (!success) { + g_printerr("%s:", error->message); + g_error_free(error); + return 1; + } + + if (!input_stream_global_init(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return 2; + } + + playlist_list_global_init(); + + /* open the playlist */ + + playlist = playlist_list_open_uri(uri); + if (playlist == NULL) { + /* open the stream and wait until it becomes ready */ + + is = input_stream_open(uri, &error); + if (is == NULL) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } else + g_printerr("input_stream_open() failed\n"); + return 2; + } + + while (!is->ready) { + int ret = input_stream_buffer(is, &error); + if (ret < 0) { + /* error */ + g_warning("%s", error->message); + g_error_free(error); + return 2; + } + + if (ret == 0) + /* nothing was buffered - wait */ + g_usleep(10000); + } + + /* open the playlist */ + + playlist = playlist_list_open_stream(is, uri); + if (playlist == NULL) { + input_stream_close(is); + g_printerr("Failed to open playlist\n"); + return 2; + } + } + + /* dump the playlist */ + + while ((song = playlist_plugin_read(playlist)) != NULL) { + g_print("%s\n", song->uri); + + if (song->end_ms > 0) + g_print("range: %u:%02u..%u:%02u\n", + song->start_ms / 60000, + (song->start_ms / 1000) % 60, + song->end_ms / 60000, + (song->end_ms / 1000) % 60); + else if (song->start_ms > 0) + g_print("range: %u:%02u..\n", + song->start_ms / 60000, + (song->start_ms / 1000) % 60); + + if (song->tag != NULL) + tag_save(stdout, song->tag); + + song_free(song); + } + + /* deinitialize everything */ + + playlist_plugin_close(playlist); + if (is != NULL) + input_stream_close(is); + playlist_list_global_finish(); + input_stream_global_finish(); + config_global_finish(); + tag_pool_deinit(); + + return 0; +} diff --git a/test/read_conf.c b/test/read_conf.c index 286ec2c77..4f43e6a2e 100644 --- a/test/read_conf.c +++ b/test/read_conf.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "conf.h" #include <glib.h> @@ -37,6 +38,8 @@ my_log_func(G_GNUC_UNUSED const gchar *log_domain, int main(int argc, char **argv) { const char *path, *name, *value; + GError *error = NULL; + bool success; int ret; if (argc != 3) { @@ -50,7 +53,12 @@ int main(int argc, char **argv) g_log_set_default_handler(my_log_func, NULL); config_global_init(); - config_read_file(path); + success = config_read_file(path, &error); + if (!success) { + g_printerr("%s:", error->message); + g_error_free(error); + return 1; + } value = config_get_string(name, NULL); if (value != NULL) { diff --git a/test/read_mixer.c b/test/read_mixer.c index be6864ba1..1b5b093a3 100644 --- a/test/read_mixer.c +++ b/test/read_mixer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "mixer_control.h" #include "mixer_list.h" +#include "filter_registry.h" +#include "pcm_volume.h" +#include "event_pipe.h" #include <glib.h> @@ -26,8 +30,55 @@ #include <string.h> #include <unistd.h> +#ifdef HAVE_PULSE +#include "output/pulse_output_plugin.h" + +void +pulse_output_set_mixer(G_GNUC_UNUSED struct pulse_output *po, + G_GNUC_UNUSED struct pulse_mixer *pm) +{ +} + +void +pulse_output_clear_mixer(G_GNUC_UNUSED struct pulse_output *po, + G_GNUC_UNUSED struct pulse_mixer *pm) +{ +} + +bool +pulse_output_set_volume(G_GNUC_UNUSED struct pulse_output *po, + G_GNUC_UNUSED const struct pa_cvolume *volume, + G_GNUC_UNUSED GError **error_r) +{ + return false; +} + +#endif + +void +event_pipe_emit(G_GNUC_UNUSED enum pipe_event event) +{ +} + +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) { + GError *error = NULL; struct mixer *mixer; bool success; int volume; @@ -39,27 +90,34 @@ int main(int argc, G_GNUC_UNUSED char **argv) g_thread_init(NULL); - mixer = mixer_new(&alsa_mixer, NULL); + mixer = mixer_new(&alsa_mixer_plugin, NULL, NULL, &error); if (mixer == NULL) { - g_printerr("mixer_new() failed\n"); + g_printerr("mixer_new() failed: %s\n", error->message); + g_error_free(error); return 2; } - success = mixer_open(mixer); + success = mixer_open(mixer, &error); if (!success) { mixer_free(mixer); - g_printerr("failed to open the mixer\n"); + g_printerr("failed to open the mixer: %s\n", error->message); + g_error_free(error); return 2; } - volume = mixer_get_volume(mixer); + volume = mixer_get_volume(mixer, &error); mixer_close(mixer); mixer_free(mixer); assert(volume >= -1 && volume <= 100); if (volume < 0) { - g_printerr("failed to read volume\n"); + if (error != NULL) { + g_printerr("failed to read volume: %s\n", + error->message); + g_error_free(error); + } else + g_printerr("failed to read volume\n"); return 2; } diff --git a/test/read_tags.c b/test/read_tags.c index e7958abdb..3e5e523bf 100644 --- a/test/read_tags.c +++ b/test/read_tags.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,19 +17,34 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "decoder_list.h" #include "decoder_api.h" +#include "input_init.h" #include "input_stream.h" #include "audio_format.h" #include "pcm_volume.h" #include "tag_ape.h" #include "tag_id3.h" +#include "idle.h" #include <glib.h> #include <assert.h> #include <unistd.h> +#ifdef HAVE_LOCALE_H +#include <locale.h> +#endif + +/** + * No-op dummy. + */ +void +idle_add(G_GNUC_UNUSED unsigned flags) +{ +} + /** * No-op dummy. */ @@ -49,11 +64,6 @@ decoder_initialized(G_GNUC_UNUSED struct decoder *decoder, { } -char *decoder_get_uri(G_GNUC_UNUSED struct decoder *decoder) -{ - return NULL; -} - enum decoder_command decoder_get_command(G_GNUC_UNUSED struct decoder *decoder) { @@ -78,15 +88,20 @@ decoder_read(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is, void *buffer, size_t length) { - return input_stream_read(is, buffer, length); + return input_stream_read(is, buffer, length, NULL); +} + +void +decoder_timestamp(G_GNUC_UNUSED struct decoder *decoder, + G_GNUC_UNUSED double t) +{ } enum decoder_command decoder_data(G_GNUC_UNUSED struct decoder *decoder, G_GNUC_UNUSED struct input_stream *is, const void *data, size_t datalen, - G_GNUC_UNUSED float data_time, G_GNUC_UNUSED uint16_t bit_rate, - G_GNUC_UNUSED struct replay_gain_info *replay_gain_info) + G_GNUC_UNUSED uint16_t bit_rate) { write(1, data, datalen); return DECODE_COMMAND_NONE; @@ -100,6 +115,22 @@ decoder_tag(G_GNUC_UNUSED struct decoder *decoder, return DECODE_COMMAND_NONE; } +float +decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder, + G_GNUC_UNUSED const struct replay_gain_info *replay_gain_info) +{ + return 0.0; +} + +void +decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder, + G_GNUC_UNUSED float replay_gain_db, + char *mixramp_start, char *mixramp_end) +{ + g_free(mixramp_start); + g_free(mixramp_end); +} + static void print_tag(const struct tag *tag) { @@ -114,11 +145,17 @@ print_tag(const struct tag *tag) int main(int argc, char **argv) { + GError *error = NULL; const char *decoder_name, *path; const struct decoder_plugin *plugin; struct tag *tag; bool empty; +#ifdef HAVE_LOCALE_H + /* initialize locale */ + setlocale(LC_CTYPE,""); +#endif + if (argc != 3) { g_printerr("Usage: read_tags DECODER FILE\n"); return 1; @@ -127,7 +164,12 @@ int main(int argc, char **argv) decoder_name = argv[1]; path = argv[2]; - input_stream_global_init(); + if (!input_stream_global_init(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return 2; + } + decoder_plugin_init_all(); plugin = decoder_plugin_from_name(decoder_name); @@ -137,6 +179,20 @@ int main(int argc, char **argv) } tag = decoder_plugin_tag_dup(plugin, path); + if (tag == NULL && plugin->stream_tag != NULL) { + struct input_stream *is = input_stream_open(path, &error); + + if (is == NULL) { + g_printerr("Failed to open %s: %s\n", + path, error->message); + g_error_free(error); + return 1; + } + + tag = decoder_plugin_stream_tag(plugin, is); + input_stream_close(is); + } + decoder_plugin_deinit_all(); input_stream_global_finish(); if (tag == NULL) { diff --git a/test/run_convert.c b/test/run_convert.c new file mode 100644 index 000000000..415d7535c --- /dev/null +++ b/test/run_convert.c @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2003-2010 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. + */ + +/* + * This program is a command line interface to MPD's PCM conversion + * library (pcm_convert.c). + * + */ + +#include "config.h" +#include "audio_parser.h" +#include "audio_format.h" +#include "pcm_convert.h" +#include "conf.h" +#include "fifo_buffer.h" +#include "stdbin.h" + +#include <glib.h> + +#include <assert.h> +#include <stddef.h> +#include <unistd.h> + +static void +my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, + const gchar *message, G_GNUC_UNUSED gpointer user_data) +{ + if (log_domain != NULL) + g_printerr("%s: %s\n", log_domain, message); + else + g_printerr("%s\n", message); +} + +const char * +config_get_string(G_GNUC_UNUSED const char *name, const char *default_value) +{ + return default_value; +} + +int main(int argc, char **argv) +{ + GError *error = NULL; + struct audio_format in_audio_format, out_audio_format; + struct pcm_convert_state state; + const void *output; + ssize_t nbytes; + size_t length; + + if (argc != 3) { + g_printerr("Usage: run_convert IN_FORMAT OUT_FORMAT <IN >OUT\n"); + return 1; + } + + g_log_set_default_handler(my_log_func, NULL); + + if (!audio_format_parse(&in_audio_format, argv[1], + false, &error)) { + g_printerr("Failed to parse audio format: %s\n", + error->message); + return 1; + } + + if (!audio_format_parse(&out_audio_format, argv[2], + false, &error)) { + g_printerr("Failed to parse audio format: %s\n", + error->message); + return 1; + } + + const size_t in_frame_size = audio_format_frame_size(&in_audio_format); + + pcm_convert_init(&state); + + struct fifo_buffer *buffer = fifo_buffer_new(4096); + + while (true) { + void *p = fifo_buffer_write(buffer, &length); + assert(p != NULL); + + nbytes = read(0, p, length); + if (nbytes <= 0) + break; + + fifo_buffer_append(buffer, nbytes); + + const void *src = fifo_buffer_read(buffer, &length); + assert(src != NULL); + + length -= length % in_frame_size; + if (length == 0) + continue; + + fifo_buffer_consume(buffer, length); + + output = pcm_convert(&state, &in_audio_format, src, length, + &out_audio_format, &length, &error); + if (output == NULL) { + g_printerr("Failed to convert: %s\n", error->message); + return 2; + } + + write(1, output, length); + } + + pcm_convert_deinit(&state); +} diff --git a/test/run_decoder.c b/test/run_decoder.c index 861720cdb..c997ebf8f 100644 --- a/test/run_decoder.c +++ b/test/run_decoder.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,17 +17,39 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "decoder_list.h" #include "decoder_api.h" +#include "input_init.h" #include "input_stream.h" #include "audio_format.h" #include "pcm_volume.h" +#include "idle.h" +#include "stdbin.h" #include <glib.h> #include <assert.h> #include <unistd.h> +static void +my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, + const gchar *message, G_GNUC_UNUSED gpointer user_data) +{ + if (log_domain != NULL) + g_printerr("%s: %s\n", log_domain, message); + else + g_printerr("%s\n", message); +} + +/** + * No-op dummy. + */ +void +idle_add(G_GNUC_UNUSED unsigned flags) +{ +} + /** * No-op dummy. */ @@ -53,20 +75,17 @@ decoder_initialized(struct decoder *decoder, G_GNUC_UNUSED bool seekable, G_GNUC_UNUSED float total_time) { + struct audio_format_string af_string; + assert(!decoder->initialized); assert(audio_format_valid(audio_format)); - g_printerr("audio_format=%u:%u:%u\n", audio_format->sample_rate, - audio_format->bits, audio_format->channels); + g_printerr("audio_format=%s\n", + audio_format_to_string(audio_format, &af_string)); decoder->initialized = true; } -char *decoder_get_uri(struct decoder *decoder) -{ - return g_strdup(decoder->uri); -} - enum decoder_command decoder_get_command(G_GNUC_UNUSED struct decoder *decoder) { @@ -91,15 +110,20 @@ decoder_read(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is, void *buffer, size_t length) { - return input_stream_read(is, buffer, length); + return input_stream_read(is, buffer, length, NULL); +} + +void +decoder_timestamp(G_GNUC_UNUSED struct decoder *decoder, + G_GNUC_UNUSED double t) +{ } enum decoder_command decoder_data(G_GNUC_UNUSED struct decoder *decoder, G_GNUC_UNUSED struct input_stream *is, const void *data, size_t datalen, - G_GNUC_UNUSED float data_time, G_GNUC_UNUSED uint16_t bit_rate, - G_GNUC_UNUSED struct replay_gain_info *replay_gain_info) + G_GNUC_UNUSED uint16_t kbit_rate) { write(1, data, datalen); return DECODE_COMMAND_NONE; @@ -113,9 +137,36 @@ decoder_tag(G_GNUC_UNUSED struct decoder *decoder, return DECODE_COMMAND_NONE; } +float +decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder, + const struct replay_gain_info *replay_gain_info) +{ + const struct replay_gain_tuple *tuple = + &replay_gain_info->tuples[REPLAY_GAIN_ALBUM]; + if (replay_gain_tuple_defined(tuple)) + g_printerr("replay_gain[album]: gain=%f peak=%f\n", + tuple->gain, tuple->peak); + + tuple = &replay_gain_info->tuples[REPLAY_GAIN_TRACK]; + if (replay_gain_tuple_defined(tuple)) + g_printerr("replay_gain[track]: gain=%f peak=%f\n", + tuple->gain, tuple->peak); + + return 0.0; +} + +void +decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder, + G_GNUC_UNUSED float replay_gain_db, + char *mixramp_start, char *mixramp_end) +{ + g_free(mixramp_start); + g_free(mixramp_end); +} + int main(int argc, char **argv) { - bool ret; + GError *error = NULL; const char *decoder_name; struct decoder decoder; @@ -127,7 +178,14 @@ int main(int argc, char **argv) decoder_name = argv[1]; decoder.uri = argv[2]; - input_stream_global_init(); + g_log_set_default_handler(my_log_func, NULL); + + if (!input_stream_global_init(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return 2; + } + decoder_plugin_init_all(); decoder.plugin = decoder_plugin_from_name(decoder_name); @@ -142,15 +200,21 @@ int main(int argc, char **argv) decoder_plugin_file_decode(decoder.plugin, &decoder, decoder.uri); } else if (decoder.plugin->stream_decode != NULL) { - struct input_stream is; + struct input_stream *is = + input_stream_open(decoder.uri, &error); + if (is == NULL) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } else + g_printerr("input_stream_open() failed\n"); - ret = input_stream_open(&is, decoder.uri); - if (!ret) return 1; + } - decoder_plugin_stream_decode(decoder.plugin, &decoder, &is); + decoder_plugin_stream_decode(decoder.plugin, &decoder, is); - input_stream_close(&is); + input_stream_close(is); } else { g_printerr("Decoder plugin is not usable\n"); return 1; diff --git a/test/run_encoder.c b/test/run_encoder.c index 8cb1c6d1d..4b512d46a 100644 --- a/test/run_encoder.c +++ b/test/run_encoder.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,11 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "encoder_list.h" #include "encoder_plugin.h" #include "audio_format.h" #include "audio_parser.h" #include "conf.h" +#include "stdbin.h" #include <glib.h> @@ -41,11 +43,7 @@ encoder_to_stdout(struct encoder *encoder) int main(int argc, char **argv) { GError *error = NULL; - struct audio_format audio_format = { - .sample_rate = 44100, - .bits = 16, - .channels = 2, - }; + struct audio_format audio_format; bool ret; const char *encoder_name; const struct encoder_plugin *plugin; @@ -66,6 +64,8 @@ int main(int argc, char **argv) else encoder_name = "vorbis"; + audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2); + /* create the encoder */ plugin = encoder_plugin_get(encoder_name); @@ -75,7 +75,7 @@ int main(int argc, char **argv) } param = config_new_param(NULL, -1); - config_add_block_param(param, "quality", "5.0", -1); + config_add_block_param(param, "quality", "5.0", -1, NULL); encoder = encoder_init(plugin, param, &error); if (encoder == NULL) { @@ -88,7 +88,8 @@ int main(int argc, char **argv) /* open the encoder */ if (argc > 2) { - ret = audio_format_parse(&audio_format, argv[2], &error); + ret = audio_format_parse(&audio_format, argv[2], + false, &error); if (!ret) { g_printerr("Failed to parse audio format: %s\n", error->message); diff --git a/test/run_filter.c b/test/run_filter.c new file mode 100644 index 000000000..ee2445eac --- /dev/null +++ b/test/run_filter.c @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "conf.h" +#include "audio_parser.h" +#include "audio_format.h" +#include "filter_plugin.h" +#include "pcm_volume.h" +#include "idle.h" +#include "mixer_control.h" +#include "playlist.h" +#include "stdbin.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> + +struct playlist g_playlist; + +void +idle_add(G_GNUC_UNUSED unsigned flags) +{ +} + +bool +mixer_set_volume(G_GNUC_UNUSED struct mixer *mixer, + G_GNUC_UNUSED unsigned volume, G_GNUC_UNUSED GError **error_r) +{ + return true; +} + +static void +my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, + const gchar *message, G_GNUC_UNUSED gpointer user_data) +{ + if (log_domain != NULL) + g_printerr("%s: %s\n", log_domain, message); + else + g_printerr("%s\n", message); +} + +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; + struct audio_format_string af_string; + 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; + } + + audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2); + + /* initialize GLib */ + + g_thread_init(NULL); + g_log_set_default_handler(my_log_func, NULL); + + /* read configuration file (mpd.conf) */ + + config_global_init(); + success = config_read_file(argv[1], &error); + if (!success) { + g_printerr("%s:", error->message); + g_error_free(error); + return 1; + } + + /* parse the audio format */ + + if (argc > 3) { + success = audio_format_parse(&audio_format, argv[3], + false, &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=%s\n", + audio_format_to_string(out_audio_format, &af_string)); + + 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_inotify.c b/test/run_inotify.c new file mode 100644 index 000000000..9f3c30b8c --- /dev/null +++ b/test/run_inotify.c @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "inotify_source.h" + +#include <stdbool.h> +#include <sys/inotify.h> +#include <signal.h> + +static GMainLoop *main_loop; + +static void +exit_signal_handler(G_GNUC_UNUSED int signum) +{ + g_main_loop_quit(main_loop); +} + +enum { + IN_MASK = IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF + |IN_MOVE|IN_MOVE_SELF +#ifdef IN_ONLYDIR + |IN_ONLYDIR +#endif +}; + +static void +my_inotify_callback(G_GNUC_UNUSED int wd, unsigned mask, + const char *name, G_GNUC_UNUSED void *ctx) +{ + g_print("mask=0x%x name='%s'\n", mask, name); +} + +int main(int argc, char **argv) +{ + GError *error = NULL; + const char *path; + + if (argc != 2) { + g_printerr("Usage: run_inotify PATH\n"); + return 1; + } + + path = argv[1]; + + struct mpd_inotify_source *source = + mpd_inotify_source_new(my_inotify_callback, NULL, + &error); + if (source == NULL) { + g_warning("%s", error->message); + g_error_free(error); + return 2; + } + + int descriptor = mpd_inotify_source_add(source, path, + IN_MASK, &error); + if (descriptor < 0) { + mpd_inotify_source_free(source); + g_warning("%s", error->message); + g_error_free(error); + return 2; + } + + main_loop = g_main_loop_new(NULL, false); + + struct sigaction sa; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = exit_signal_handler; + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + + g_main_loop_run(main_loop); + g_main_loop_unref(main_loop); + + mpd_inotify_source_free(source); +} diff --git a/test/run_input.c b/test/run_input.c index 5d74473e3..a50cd70ab 100644 --- a/test/run_input.c +++ b/test/run_input.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,10 +18,12 @@ */ #include "config.h" +#include "input_init.h" #include "input_stream.h" #include "tag_pool.h" #include "tag_save.h" #include "conf.h" +#include "stdbin.h" #ifdef ENABLE_ARCHIVE #include "archive_list.h" @@ -44,6 +46,7 @@ my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, static int dump_input_stream(struct input_stream *is) { + GError *error = NULL; char buffer[4096]; size_t num_read; ssize_t num_written; @@ -51,10 +54,13 @@ dump_input_stream(struct input_stream *is) /* wait until the stream becomes ready */ while (!is->ready) { - int ret = input_stream_buffer(is); - if (ret < 0) + int ret = input_stream_buffer(is, &error); + if (ret < 0) { /* error */ + g_warning("%s", error->message); + g_error_free(error); return 2; + } if (ret == 0) /* nothing was buffered - wait */ @@ -76,9 +82,16 @@ dump_input_stream(struct input_stream *is) tag_free(tag); } - num_read = input_stream_read(is, buffer, sizeof(buffer)); - if (num_read == 0) + num_read = input_stream_read(is, buffer, sizeof(buffer), + &error); + if (num_read == 0) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + break; + } num_written = write(1, buffer, num_read); if (num_written <= 0) @@ -90,7 +103,8 @@ dump_input_stream(struct input_stream *is) int main(int argc, char **argv) { - struct input_stream is; + GError *error = NULL; + struct input_stream *is; int ret; if (argc != 2) { @@ -112,15 +126,24 @@ int main(int argc, char **argv) archive_plugin_init_all(); #endif - input_stream_global_init(); + if (!input_stream_global_init(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return 2; + } /* open the stream and dump it */ - if (input_stream_open(&is, argv[1])) { - ret = dump_input_stream(&is); - input_stream_close(&is); + is = input_stream_open(argv[1], &error); + if (is != NULL) { + ret = dump_input_stream(is); + input_stream_close(is); } else { - g_printerr("input_stream_open() failed\n"); + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } else + g_printerr("input_stream_open() failed\n"); ret = 2; } diff --git a/test/run_normalize.c b/test/run_normalize.c new file mode 100644 index 000000000..dd1140782 --- /dev/null +++ b/test/run_normalize.c @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2003-2010 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. + */ + +/* + * This program is a command line interface to MPD's normalize library + * (based on AudioCompress). + * + */ + +#include "config.h" +#include "AudioCompress/compress.h" +#include "audio_parser.h" +#include "audio_format.h" +#include "stdbin.h" + +#include <glib.h> + +#include <stddef.h> +#include <unistd.h> +#include <string.h> + +int main(int argc, char **argv) +{ + GError *error = NULL; + struct audio_format audio_format; + bool ret; + struct Compressor *compressor; + static char buffer[4096]; + ssize_t nbytes; + + if (argc > 2) { + g_printerr("Usage: run_normalize [FORMAT] <IN >OUT\n"); + return 1; + } + + if (argc > 1) { + ret = audio_format_parse(&audio_format, argv[1], + false, &error); + if (!ret) { + g_printerr("Failed to parse audio format: %s\n", + error->message); + return 1; + } + } else + audio_format_init(&audio_format, 48000, 16, 2); + + compressor = Compressor_new(0); + + while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) { + Compressor_Process_int16(compressor, + (int16_t *)buffer, nbytes / 2); + write(1, buffer, nbytes); + } + + Compressor_delete(compressor); +} diff --git a/test/run_output.c b/test/run_output.c index 1a171198d..5028068ff 100644 --- a/test/run_output.c +++ b/test/run_output.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,11 +17,18 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "output_plugin.h" #include "output_internal.h" #include "output_control.h" #include "conf.h" #include "audio_parser.h" +#include "filter_registry.h" +#include "pcm_convert.h" +#include "event_pipe.h" +#include "idle.h" +#include "playlist.h" +#include "stdbin.h" #include <glib.h> @@ -29,12 +36,44 @@ #include <string.h> #include <unistd.h> +struct playlist g_playlist; + +void +idle_add(G_GNUC_UNUSED unsigned flags) +{ +} + +void +event_pipe_emit(G_GNUC_UNUSED enum pipe_event event) +{ +} + void pcm_convert_init(G_GNUC_UNUSED struct pcm_convert_state *state) { } -void notify_init(G_GNUC_UNUSED struct notify *notify) +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, + GError **error_r) { + g_set_error(error_r, pcm_convert_quark(), 0, + "Not implemented"); + return NULL; +} + +const struct filter_plugin * +filter_plugin_by_name(G_GNUC_UNUSED const char *name) +{ + assert(false); + return NULL; } static const struct config_param * @@ -77,11 +116,8 @@ load_audio_output(struct audio_output *ao, const char *name) int main(int argc, char **argv) { struct audio_output ao; - struct audio_format audio_format = { - .sample_rate = 44100, - .bits = 16, - .channels = 2, - }; + struct audio_format audio_format; + struct audio_format_string af_string; bool success; GError *error = NULL; char buffer[4096]; @@ -93,12 +129,19 @@ int main(int argc, char **argv) return 1; } + audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2); + g_thread_init(NULL); /* read configuration file (mpd.conf) */ config_global_init(); - config_read_file(argv[1]); + success = config_read_file(argv[1], &error); + if (!success) { + g_printerr("%s:", error->message); + g_error_free(error); + return 1; + } /* initialize the audio output */ @@ -108,7 +151,8 @@ int main(int argc, char **argv) /* parse the audio format */ if (argc > 3) { - success = audio_format_parse(&audio_format, argv[3], &error); + success = audio_format_parse(&audio_format, argv[3], + false, &error); if (!success) { g_printerr("Failed to parse audio format: %s\n", error->message); @@ -127,8 +171,8 @@ int main(int argc, char **argv) return 1; } - g_printerr("audio_format=%u:%u:%u\n", audio_format.sample_rate, - audio_format.bits, audio_format.channels); + g_printerr("audio_format=%s\n", + audio_format_to_string(&audio_format, &af_string)); frame_size = audio_format_frame_size(&audio_format); diff --git a/test/software_volume.c b/test/software_volume.c index 9a9fd56f6..c4de69328 100644 --- a/test/software_volume.c +++ b/test/software_volume.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,9 +23,11 @@ * */ +#include "config.h" #include "pcm_volume.h" #include "audio_parser.h" #include "audio_format.h" +#include "stdbin.h" #include <glib.h> @@ -35,31 +37,34 @@ int main(int argc, char **argv) { GError *error = NULL; - struct audio_format audio_format = { - .sample_rate = 48000, - .bits = 16, - .channels = 2, - }; + struct audio_format audio_format; bool ret; static char buffer[4096]; ssize_t nbytes; if (argc > 2) { - g_printerr("Usage: software_voluem [FORMAT] <IN >OUT\n"); + g_printerr("Usage: software_volume [FORMAT] <IN >OUT\n"); return 1; } if (argc > 1) { - ret = audio_format_parse(&audio_format, argv[1], &error); + ret = audio_format_parse(&audio_format, argv[1], + false, &error); if (!ret) { g_printerr("Failed to parse audio format: %s\n", error->message); return 1; } - } + } else + audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, 2); while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) { - pcm_volume(buffer, nbytes, &audio_format, PCM_VOLUME_1 / 2); + if (!pcm_volume(buffer, nbytes, &audio_format, + PCM_VOLUME_1 / 2)) { + g_printerr("pcm_volume() has failed\n"); + return 2; + } + write(1, buffer, nbytes); } } diff --git a/test/stdbin.h b/test/stdbin.h new file mode 100644 index 000000000..362605ad9 --- /dev/null +++ b/test/stdbin.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2003-2010 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_STDBIN_H +#define MPD_STDBIN_H + +#ifdef WIN32 +#include <fcntl.h> +/** set binary mode on stdin/stdout */ +int _CRT_fmode = _O_BINARY; +#endif + +#endif diff --git a/test/test_archive_bzip2.sh b/test/test_archive_bzip2.sh new file mode 100755 index 000000000..2c45076aa --- /dev/null +++ b/test/test_archive_bzip2.sh @@ -0,0 +1,10 @@ +#!/bin/sh -e + +SRC_BASE=configure +SRC="$(dirname $0)/../${SRC_BASE}" +DST="$(pwd)/test/tmp/${SRC_BASE}.bz2" + +mkdir -p test/tmp +rm -f "$DST" +bzip2 -c "$SRC" >"$DST" +./test/run_input "$DST/${SRC_BASE}" |diff "$SRC" - diff --git a/test/test_archive_iso9660.sh b/test/test_archive_iso9660.sh new file mode 100755 index 000000000..070d2da0a --- /dev/null +++ b/test/test_archive_iso9660.sh @@ -0,0 +1,10 @@ +#!/bin/sh -e + +SRC_BASE=configure +SRC="$(dirname $0)/../${SRC_BASE}" +DST="$(pwd)/test/tmp/${SRC_BASE}.iso" + +mkdir -p test/tmp +rm -f "$DST" +mkisofs -quiet -l -o "$DST" "$SRC" +./test/run_input "$DST/${SRC_BASE}" |diff "$SRC" - diff --git a/test/test_archive_zzip.sh b/test/test_archive_zzip.sh new file mode 100755 index 000000000..a8d23f6d7 --- /dev/null +++ b/test/test_archive_zzip.sh @@ -0,0 +1,10 @@ +#!/bin/sh -e + +SRC_BASE=configure +SRC="$(dirname $0)/../${SRC_BASE}" +DST="$(pwd)/test/tmp/${SRC_BASE}.zip" + +mkdir -p test/tmp +rm -f "$DST" +zip --quiet --junk-paths "$DST" "$SRC" +./test/run_input "$DST/${SRC_BASE}" |diff "$SRC" - diff --git a/valgrind.suppressions b/valgrind.suppressions index 7b9f80327..8d687f7b8 100644 --- a/valgrind.suppressions +++ b/valgrind.suppressions @@ -57,13 +57,23 @@ } { + g_main_context_default + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + fun:g_slist_prepend + fun:g_main_context_new + fun:g_main_context_default +} + +{ g_main_loop_run Memcheck:Leak fun:malloc fun:g_malloc fun:g_main_context_iterate fun:g_main_loop_run - fun:main } { @@ -101,6 +111,17 @@ { g_get_language_names Memcheck:Leak + fun:memalign + fun:posix_memalign + fun:slab_allocator_alloc_chunk + fun:g_slice_alloc + fun:g_hash_table_new_full + fun:g_get_language_names +} + +{ + g_get_language_names + Memcheck:Leak fun:calloc fun:g_malloc0 fun:g_get_language_names @@ -157,12 +178,33 @@ { g_get_language_names Memcheck:Leak + fun:calloc + fun:g_malloc0 + fun:g_hash_table_resize + fun:g_hash_table_insert_internal + fun:g_get_language_names +} + +{ + g_get_language_names + Memcheck:Leak fun:malloc fun:g_malloc fun:g_slice_alloc fun:g_hash_table_new_full fun:g_get_language_names - fun:main +} + +{ + g_get_language_names + Memcheck:Leak + fun:memalign + fun:posix_memalign + fun:slab_allocator_alloc_chunk + fun:g_slice_alloc + fun:g_slist_prepend + fun:g_strsplit + fun:g_get_language_names } { @@ -175,6 +217,15 @@ } { + g_set_application_name + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_strdup + fun:g_set_application_name +} + +{ g_thread_init_glib Memcheck:Leak fun:malloc @@ -278,7 +329,7 @@ } { - g_get_filename_charsets2 + g_get_filename_charsets Memcheck:Leak fun:calloc fun:g_malloc0 @@ -286,6 +337,29 @@ } { + g_get_filename_charsets + Memcheck:Leak + fun:memalign + fun:posix_memalign + fun:slab_allocator_alloc_chunk + fun:g_slice_alloc + fun:g_array_sized_new + fun:g_static_private_set + fun:g_get_filename_charsets +} + +{ + g_get_filename_charsets + Memcheck:Leak + fun:calloc + fun:g_malloc0 + fun:g_slice_alloc + fun:g_array_sized_new + fun:g_static_private_set + fun:g_get_filename_charsets +} + +{ g_static_private_set Memcheck:Leak fun:malloc @@ -314,7 +388,7 @@ } { - g_get_charset2 + g_get_charset Memcheck:Leak fun:calloc fun:g_malloc0 @@ -322,6 +396,29 @@ } { + g_get_charset + Memcheck:Leak + fun:calloc + fun:g_malloc0 + fun:g_slice_alloc + fun:g_array_sized_new + fun:g_static_private_set + fun:g_get_charset +} + +{ + g_get_charset + Memcheck:Leak + fun:memalign + fun:posix_memalign + fun:slab_allocator_alloc_chunk + fun:g_slice_alloc + fun:g_array_sized_new + fun:g_static_private_set + fun:g_get_charset +} + +{ openssl Memcheck:Leak fun:malloc @@ -373,6 +470,31 @@ } { + <insert_a_suppression_name_here> + Memcheck:Leak + fun:malloc + fun:CRYPTO_malloc + fun:ERR_get_state + fun:ERR_clear_error + fun:Curl_ossl_init + fun:curl_global_init +} + +{ + openssl + Memcheck:Leak + fun:malloc + fun:CRYPTO_malloc + fun:lh_new + obj:/usr/lib/libcrypto.so.0.9.8 + obj:/usr/lib/libcrypto.so.0.9.8 + fun:ERR_get_state + fun:ERR_clear_error + fun:Curl_ossl_init + fun:curl_global_init +} + +{ openssl Memcheck:Leak fun:malloc @@ -399,6 +521,100 @@ } { + <insert_a_suppression_name_here> + Memcheck:Leak + fun:calloc + fun:_dl_new_object + fun:_dl_map_object_from_fd + fun:_dl_map_object + fun:dl_open_worker + fun:_dl_catch_error + fun:_dl_open +} + +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:malloc + fun:_dl_new_object + fun:_dl_map_object_from_fd + fun:_dl_map_object + fun:dl_open_worker + fun:_dl_catch_error + fun:_dl_open +} + +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:malloc + fun:local_strdup + fun:_dl_map_object + fun:dl_open_worker + fun:_dl_catch_error + fun:_dl_open +} + +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:calloc + fun:_dl_check_map_versions + fun:dl_open_worker + fun:_dl_catch_error + fun:_dl_open +} + +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:malloc + fun:_dl_map_object_deps + fun:dl_open_worker + fun:_dl_catch_error + fun:_dl_open +} + +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:malloc + fun:_dl_map_object_deps + fun:dl_open_worker + fun:_dl_catch_error + fun:_dl_open +} + +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:malloc + fun:_dl_new_object + fun:_dl_map_object_from_fd + fun:_dl_map_object + fun:openaux + fun:_dl_catch_error + fun:_dl_map_object_deps + fun:dl_open_worker + fun:_dl_catch_error + fun:_dl_open +} + +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:malloc + fun:local_strdup + fun:_dl_map_object + fun:openaux + fun:_dl_catch_error + fun:_dl_map_object_deps + fun:dl_open_worker + fun:_dl_catch_error + fun:_dl_open +} + +{ dlopen Memcheck:Leak fun:calloc @@ -542,6 +758,16 @@ } { + g_quark_from_string + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + fun:g_hash_table_new_full + fun:g_quark_from_string +} + +{ g_get_any_init_do Memcheck:Leak fun:malloc |