diff options
586 files changed, 26217 insertions, 8741 deletions
diff --git a/.gitignore b/.gitignore index 83a1bf623..67b261032 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ tags *~ .#* .stgit* +src/dsd2pcm/dsd2pcm doc/doxygen.conf doc/protocol.html doc/protocol @@ -61,3 +62,8 @@ test/dump_playlist test/run_normalize test/tmp test/run_inotify +test/test_queue_priority +test/run_ntp_server +test/run_resolver +test/run_tcp_connect +test/test_pcm @@ -84,11 +84,6 @@ For Ogg Vorbis support. You will need libogg and libvorbis. FLAC - http://flac.sourceforge.net/ For FLAC support. You will need version 1.1.0 or higher of libflac. -OggFLAC - http://www.xiph.org/ogg/vorbis/ and http://flac.sourceforge.net/ -For OggFLAC support. You will need liboggflac, which can be built from the -FLAC sources if libogg is already installed. Versions of flac 1.1.3 and -greater will automatically detect and use OggFLAC if it's available. - Audio File - http://www.68k.org/~michael/audiofile/ For WAVE, AIFF, and AU support. You will need libaudiofile. @@ -101,7 +96,7 @@ For Musepack support. MikMod - http://mikmod.raphnet.net/ For MOD support. You will need libmikmod. -libavcodec, libavformat (ffmpeg) - http://ffmpeg.mplayerhq.hu/ +libavcodec, libavformat (ffmpeg or libav) - http://ffmpeg.mplayerhq.hu/ http://libav.org/ Multi-codec library. libsidplay2 - http://sidplay2.sourceforge.net/ @@ -119,6 +114,9 @@ WAVE, AIFF, and many others. libwavpack - http://www.wavpack.com/ For WavPack playback. +despotify - https://github.com/SimonKagstrom/despotify +For Spotify playback. + Optional Miscellaneous Dependencies ----------------------------------- @@ -138,8 +136,11 @@ For playing MMS streams. SQLite - http://www.sqlite.org/ For the sticker database. -libcue - http://libcue.sourceforge.net/ -For CUE sheet support. +libcdio - http://www.gnu.org/software/libcdio/ +For playing audio CDs. + +libsystemd-daemon - http://freedesktop.org/wiki/Software/systemd/ +For systemd activation. pkg-config diff --git a/Makefile.am b/Makefile.am index ca7d8c717..38d08f098 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,5 +1,5 @@ ACLOCAL_AMFLAGS = -I m4 -AUTOMAKE_OPTIONS = foreign 1.10 dist-bzip2 subdir-objects +AUTOMAKE_OPTIONS = foreign 1.11 dist-bzip2 subdir-objects AM_CPPFLAGS += -I$(srcdir)/src $(GLIB_CFLAGS) @@ -7,31 +7,36 @@ AM_CPPFLAGS += -DSYSTEM_CONFIG_FILE_LOCATION='"$(sysconfdir)/mpd.conf"' bin_PROGRAMS = src/mpd -noinst_LIBRARIES = +noinst_LIBRARIES = \ + libutil.a \ + libpcm.a \ + libtag.a \ + libinput.a \ + libplaylist_plugins.a \ + libdecoder_plugins.a \ + libfilter_plugins.a \ + libmixer_plugins.a \ + liboutput_plugins.a -src_mpd_CFLAGS = $(AM_CFLAGS) $(MPD_CFLAGS) src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \ $(AVAHI_CFLAGS) \ $(LIBWRAP_CFLAGS) \ - $(SQLITE_CFLAGS) \ - $(ARCHIVE_CFLAGS) \ - $(INPUT_CFLAGS) \ - $(TAG_CFLAGS) \ - $(DECODER_CFLAGS) \ - $(ENCODER_CFLAGS) \ - $(FILTER_CFLAGS) \ - $(OUTPUT_CFLAGS) -src_mpd_LDADD = $(MPD_LIBS) \ + $(SQLITE_CFLAGS) +src_mpd_LDADD = \ + $(PLAYLIST_LIBS) \ $(AVAHI_LIBS) \ $(LIBWRAP_LDFLAGS) \ $(SQLITE_LIBS) \ - $(ARCHIVE_LIBS) \ + $(DECODER_LIBS) \ $(INPUT_LIBS) \ + $(ARCHIVE_LIBS) \ $(TAG_LIBS) \ - $(DECODER_LIBS) \ - $(ENCODER_LIBS) \ $(OUTPUT_LIBS) \ $(FILTER_LIBS) \ + $(ENCODER_LIBS) \ + $(MIXER_LIBS) \ + libutil.a \ + $(SYSTEMD_DAEMON_LIBS) \ $(GLIB_LIBS) mpd_headers = \ @@ -39,13 +44,11 @@ mpd_headers = \ src/notify.h \ src/ack.h \ src/ape.h \ - src/audio.h \ src/audio_format.h \ src/audio_check.h \ src/audio_parser.h \ src/output_internal.h \ src/output_api.h \ - src/output_plugin.h \ src/output_list.h \ src/output_all.h \ src/output_thread.h \ @@ -78,7 +81,6 @@ mpd_headers = \ src/decoder_internal.h \ src/directory.h \ src/directory_save.h \ - src/directory_print.h \ src/database.h \ src/encoder_plugin.h \ src/encoder_list.h \ @@ -91,7 +93,6 @@ mpd_headers = \ 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 \ @@ -100,6 +101,7 @@ mpd_headers = \ src/decoder/flac_pcm.h \ src/decoder/_flac_common.h \ src/decoder/_ogg_common.h \ + src/decoder/pcm_decoder_plugin.h \ src/input_init.h \ src/input_plugin.h \ src/input_registry.h \ @@ -109,6 +111,9 @@ mpd_headers = \ src/input/curl_input_plugin.h \ src/input/rewind_input_plugin.h \ src/input/mms_input_plugin.h \ + src/input/despotify_input_plugin.h \ + src/input/cdio_paranoia_input_plugin.h \ + src/despotify_utils.h \ src/text_file.h \ src/text_input_stream.h \ src/icy_server.h \ @@ -140,25 +145,12 @@ mpd_headers = \ 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_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 \ src/player_control.h \ src/playlist.h \ + src/playlist_error.h \ src/playlist_internal.h \ src/playlist_print.h \ src/playlist_save.h \ @@ -178,8 +170,8 @@ mpd_headers = \ src/playlist/asx_playlist_plugin.h \ src/playlist/rss_playlist_plugin.h \ src/playlist/lastfm_playlist_plugin.h \ + src/playlist/despotify_playlist_plugin.h \ src/playlist/cue_playlist_plugin.h \ - src/playlist/flac_playlist_plugin.h \ src/poison.h \ src/riff.h \ src/aiff.h \ @@ -195,7 +187,7 @@ mpd_headers = \ src/song_print.h \ src/song_save.h \ src/song_sticker.h \ - src/songvec.h \ + src/song_sort.c src/song_sort.h \ src/socket_util.h \ src/state_file.h \ src/stats.h \ @@ -214,6 +206,7 @@ mpd_headers = \ src/strset.h \ src/uri.h \ src/utils.h \ + src/string_util.h \ src/volume.h \ src/zeroconf.h src/zeroconf-internal.h \ src/locate.h \ @@ -227,31 +220,27 @@ mpd_headers = \ src/archive/iso9660_archive_plugin.h \ src/archive/zzip_archive_plugin.h \ src/input/archive_input_plugin.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) \ + $(OUTPUT_API_SRC) \ + $(MIXER_API_SRC) \ src/glib_socket.h \ src/notify.c \ - src/audio.c \ + src/audio_config.c src/audio_config.h \ src/audio_check.c \ src/audio_format.c \ src/audio_parser.c \ + src/protocol/argparser.c src/protocol/argparser.h \ + src/protocol/result.c src/protocol/result.h \ src/command.c \ src/idle.c \ src/cmdline.c \ src/conf.c \ src/crossfade.c \ + src/cue/cue_parser.c src/cue/cue_parser.h \ src/dbUtils.c \ src/decoder_thread.c \ src/decoder_control.c \ @@ -260,9 +249,15 @@ src_mpd_SOURCES = \ src/decoder_print.c \ src/directory.c \ src/directory_save.c \ - src/directory_print.c \ src/database.c \ - src/dirvec.c \ + src/db_error.h \ + src/db_lock.c src/db_lock.h \ + src/db_save.c src/db_save.h \ + src/db_print.c src/db_print.h \ + src/db_plugin.h \ + src/db_visitor.h \ + src/db_selection.h \ + src/db/simple_db_plugin.c src/db/simple_db_plugin.h \ src/exclude.c \ src/fd_util.c \ src/fifo_buffer.c src/fifo_buffer.h \ @@ -272,22 +267,34 @@ src_mpd_SOURCES = \ src/filter_registry.c \ src/update.c \ src/update_queue.c \ + src/update_io.c src/update_io.h \ + src/update_db.c src/update_db.h \ 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.h \ 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/client_message.h \ + src/client_message.c \ + src/client_subscribe.h \ + src/client_subscribe.c \ + src/client_file.c src/client_file.h \ + src/tcp_connect.c src/tcp_connect.h \ + src/tcp_socket.c src/tcp_socket.h \ + src/udp_server.c src/udp_server.h \ src/server_socket.c \ src/listen.c \ src/log.c \ src/ls.c \ + src/io_thread.c src/io_thread.h \ src/main.c \ src/main_win32.c \ src/event_pipe.c \ @@ -299,17 +306,6 @@ src_mpd_SOURCES = \ src/path.c \ src/mapper.c \ src/page.c \ - src/pcm_buffer.c src/pcm_buffer.h \ - 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 \ - src/pcm_dither.c \ src/permission.c \ src/player_thread.c \ src/player_control.c \ @@ -336,7 +332,7 @@ src_mpd_SOURCES = \ src/song_update.c \ src/song_print.c \ src/song_save.c \ - src/songvec.c \ + src/resolver.c src/resolver.h \ src/socket_util.c \ src/state_file.c \ src/stats.c \ @@ -344,17 +340,39 @@ src_mpd_SOURCES = \ src/tag_pool.c \ src/tag_print.c \ src/tag_save.c \ + src/tag_handler.c src/tag_handler.h \ + src/tag_file.c src/tag_file.h \ src/tokenizer.c \ src/text_file.c \ src/text_input_stream.c \ src/strset.c \ src/uri.c \ src/utils.c \ + src/string_util.c \ src/volume.c \ src/locate.c \ src/stored_playlist.c \ src/timer.c +# +# Windows resource file +# + +src/win/mpd_win32_rc.$(OBJEXT): src/win/mpd_win32_rc.rc + $(WINDRES) -i $< -o $@ + +if HAVE_WINDOWS +noinst_DATA = src/win/mpd_win32_rc.rc + +src_mpd_DEPENDENCIES = src/win/mpd_win32_rc.$(OBJEXT) +src_mpd_LDFLAGS = -Wl,src/win/mpd_win32_rc.$(OBJEXT) +endif + +if ENABLE_DESPOTIFY +src_mpd_SOURCES += \ + src/despotify_utils.c +endif + if ENABLE_INOTIFY src_mpd_SOURCES += \ src/inotify_source.c \ @@ -369,69 +387,114 @@ src_mpd_SOURCES += \ src/song_sticker.c endif -FILTER_CFLAGS = \ +# Generic utility library + +libutil_a_SOURCES = \ + src/util/list.h \ + src/util/list_sort.c src/util/list_sort.h \ + src/util/byte_reverse.c src/util/byte_reverse.h \ + src/util/bit_reverse.c src/util/bit_reverse.h + +# PCM library + +libpcm_a_SOURCES = \ + src/pcm_buffer.c src/pcm_buffer.h \ + src/pcm_export.c src/pcm_export.h \ + src/pcm_convert.c src/pcm_convert.h \ + src/dsd2pcm/dsd2pcm.c src/dsd2pcm/dsd2pcm.h \ + src/pcm_dsd.c src/pcm_dsd.h \ + src/pcm_dsd_usb.c src/pcm_dsd_usb.h \ + src/pcm_volume.c src/pcm_volume.h \ + src/pcm_mix.c src/pcm_mix.h \ + src/pcm_channels.c src/pcm_channels.h \ + src/pcm_pack.c src/pcm_pack.h \ + src/pcm_format.c src/pcm_format.h \ + src/pcm_resample.c src/pcm_resample.h \ + src/pcm_resample_fallback.c \ + src/pcm_resample_internal.h \ + src/pcm_dither.c src/pcm_dither.h \ + src/pcm_prng.h \ + src/pcm_utils.h +libpcm_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(SAMPLERATE_CFLAGS) -FILTER_LIBS = \ + +PCM_LIBS = \ + libpcm.a \ $(SAMPLERATE_LIBS) if HAVE_LIBSAMPLERATE -src_mpd_SOURCES += src/pcm_resample_libsamplerate.c +libpcm_a_SOURCES += src/pcm_resample_libsamplerate.c endif # archive plugins -ARCHIVE_CFLAGS = \ +if ENABLE_ARCHIVE + +noinst_LIBRARIES += libarchive.a + +libarchive_a_SOURCES = \ + src/archive_api.c \ + src/archive_list.c \ + src/archive_plugin.c \ + src/input/archive_input_plugin.c +libarchive_a_CPPFLAGS = $(AM_CPPFLAGS) \ + $(BZ2_CFLAGS) \ $(ISO9660_CFLAGS) \ $(ZZIP_CFLAGS) ARCHIVE_LIBS = \ + libarchive.a \ + $(BZ2_LIBS) \ $(ISO9660_LIBS) \ $(ZZIP_LIBS) -ARCHIVE_SRC = - if HAVE_BZ2 -ARCHIVE_SRC += src/archive/bz2_archive_plugin.c +libarchive_a_SOURCES += src/archive/bz2_archive_plugin.c endif if HAVE_ZZIP -ARCHIVE_SRC += src/archive/zzip_archive_plugin.c +libarchive_a_SOURCES += src/archive/zzip_archive_plugin.c endif if HAVE_ISO9660 -ARCHIVE_SRC += src/archive/iso9660_archive_plugin.c +libarchive_a_SOURCES += 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 +else +ARCHIVE_LIBS = endif # tag plugins -TAG_CFLAGS = \ +libtag_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(ID3TAG_CFLAGS) TAG_LIBS = \ + libtag.a \ $(ID3TAG_LIBS) -TAG_SRC = \ +libtag_a_SOURCES =\ src/ape.c \ src/replay_gain_ape.c \ src/tag_ape.c if HAVE_ID3TAG -TAG_SRC += src/tag_id3.c \ +libtag_a_SOURCES += \ + src/tag_id3.c \ src/tag_rva2.c \ src/riff.c src/aiff.c endif # decoder plugins -DECODER_CFLAGS = \ +libdecoder_plugins_a_SOURCES = \ + src/decoder/pcm_decoder_plugin.c \ + src/decoder/dsdiff_decoder_plugin.c \ + src/decoder/dsdiff_decoder_plugin.h \ + src/decoder_buffer.c \ + src/decoder_plugin.c \ + src/decoder_list.c +libdecoder_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(VORBIS_CFLAGS) $(TREMOR_CFLAGS) \ $(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \ $(SNDFILE_CFLAGS) \ @@ -445,9 +508,11 @@ DECODER_CFLAGS = \ $(MAD_CFLAGS) \ $(MPG123_CFLAGS) \ $(FFMPEG_CFLAGS) \ - $(CUE_CFLAGS) + $(MPCDEC_CFLAGS) \ + $(FAAD_CFLAGS) DECODER_LIBS = \ + libdecoder_plugins.a \ $(VORBIS_LIBS) $(TREMOR_LIBS) \ $(FLAC_LIBS) \ $(SNDFILE_LIBS) \ @@ -461,66 +526,63 @@ DECODER_LIBS = \ $(MPG123_LIBS) \ $(MP4FF_LIBS) \ $(FFMPEG_LIBS) \ - $(CUE_LIBS) + $(MPCDEC_LIBS) \ + $(FAAD_LIBS) -DECODER_SRC = \ - src/decoder_buffer.c \ - src/decoder_plugin.c \ - src/decoder_list.c +DECODER_SRC = if HAVE_MAD -DECODER_SRC += src/decoder/mad_decoder_plugin.c +libdecoder_plugins_a_SOURCES += src/decoder/mad_decoder_plugin.c endif if HAVE_MPG123 -DECODER_SRC += src/decoder/mpg123_decoder_plugin.c +libdecoder_plugins_a_SOURCES += src/decoder/mpg123_decoder_plugin.c endif if HAVE_MPCDEC -DECODER_SRC += src/decoder/mpcdec_decoder_plugin.c +libdecoder_plugins_a_SOURCES += src/decoder/mpcdec_decoder_plugin.c endif if HAVE_WAVPACK -DECODER_SRC += src/decoder/wavpack_decoder_plugin.c +libdecoder_plugins_a_SOURCES += src/decoder/wavpack_decoder_plugin.c endif if HAVE_FAAD -DECODER_SRC += src/decoder/faad_decoder_plugin.c +libdecoder_plugins_a_SOURCES += src/decoder/faad_decoder_plugin.c endif if HAVE_MP4 -DECODER_SRC += src/decoder/mp4ff_decoder_plugin.c +libdecoder_plugins_a_SOURCES += src/decoder/mp4ff_decoder_plugin.c endif if HAVE_OGG_COMMON -DECODER_SRC += src/decoder/_ogg_common.c +libdecoder_plugins_a_SOURCES += src/decoder/_ogg_common.c endif if HAVE_FLAC_COMMON -DECODER_SRC += \ +libdecoder_plugins_a_SOURCES += \ 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_decoder_plugin.c +libdecoder_plugins_a_SOURCES += \ + src/decoder/vorbis_comments.c \ + src/decoder/vorbis_comments.h \ + src/decoder/vorbis_decoder_plugin.c endif if HAVE_FLAC -DECODER_SRC += src/decoder/flac_decoder_plugin.c -endif - -if HAVE_OGGFLAC -DECODER_SRC += src/decoder/oggflac_decoder_plugin.c +libdecoder_plugins_a_SOURCES += src/decoder/flac_decoder_plugin.c endif if HAVE_AUDIOFILE -DECODER_SRC += src/decoder/audiofile_decoder_plugin.c +libdecoder_plugins_a_SOURCES += src/decoder/audiofile_decoder_plugin.c endif if ENABLE_MIKMOD_DECODER -DECODER_SRC += src/decoder/mikmod_decoder_plugin.c +libdecoder_plugins_a_SOURCES += src/decoder/mikmod_decoder_plugin.c endif if HAVE_MODPLUG @@ -532,68 +594,79 @@ DECODER_LIBS += libmodplug_decoder_plugin.a $(MODPLUG_LIBS) endif if ENABLE_SIDPLAY -DECODER_SRC += src/decoder/sidplay_decoder_plugin.cxx +libdecoder_plugins_a_SOURCES += src/decoder/sidplay_decoder_plugin.cxx +DECODER_SRC += src/dummy.cxx endif if ENABLE_FLUIDSYNTH -DECODER_SRC += src/decoder/fluidsynth_decoder_plugin.c +libdecoder_plugins_a_SOURCES += src/decoder/fluidsynth_decoder_plugin.c endif if ENABLE_WILDMIDI -DECODER_SRC += src/decoder/wildmidi_decoder_plugin.c +libdecoder_plugins_a_SOURCES += src/decoder/wildmidi_decoder_plugin.c endif if HAVE_FFMPEG -DECODER_SRC += src/decoder/ffmpeg_decoder_plugin.c +libdecoder_plugins_a_SOURCES += \ + src/decoder/ffmpeg_metadata.c \ + src/decoder/ffmpeg_metadata.h \ + src/decoder/ffmpeg_decoder_plugin.c endif if ENABLE_SNDFILE -DECODER_SRC += src/decoder/sndfile_decoder_plugin.c +libdecoder_plugins_a_SOURCES += src/decoder/sndfile_decoder_plugin.c endif if HAVE_GME -DECODER_SRC += src/decoder/gme_decoder_plugin.c +libdecoder_plugins_a_SOURCES += src/decoder/gme_decoder_plugin.c endif # encoder plugins -ENCODER_CFLAGS = \ +if ENABLE_ENCODER + +noinst_LIBRARIES += libencoder_plugins.a + +libencoder_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(LAME_CFLAGS) \ $(TWOLAME_CFLAGS) \ $(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \ $(VORBISENC_CFLAGS) ENCODER_LIBS = \ + libencoder_plugins.a \ $(LAME_LIBS) \ $(TWOLAME_LIBS) \ $(FLAC_LIBS) \ $(VORBISENC_LIBS) -ENCODER_SRC = +libencoder_plugins_a_SOURCES = -if ENABLE_ENCODER -ENCODER_SRC += src/encoder_list.c -ENCODER_SRC += src/encoder/null_encoder.c +libencoder_plugins_a_SOURCES += src/encoder_list.c +libencoder_plugins_a_SOURCES += src/encoder/null_encoder.c if ENABLE_WAVE_ENCODER -ENCODER_SRC += src/encoder/wave_encoder.c +libencoder_plugins_a_SOURCES += src/encoder/wave_encoder.c endif if ENABLE_VORBIS_ENCODER -ENCODER_SRC += src/encoder/vorbis_encoder.c +libencoder_plugins_a_SOURCES += src/encoder/vorbis_encoder.c endif if ENABLE_LAME_ENCODER -ENCODER_SRC += src/encoder/lame_encoder.c +libencoder_plugins_a_SOURCES += src/encoder/lame_encoder.c endif if ENABLE_TWOLAME_ENCODER -ENCODER_SRC += src/encoder/twolame_encoder.c +libencoder_plugins_a_SOURCES += src/encoder/twolame_encoder.c endif if ENABLE_FLAC_ENCODER -ENCODER_SRC += src/encoder/flac_encoder.c +libencoder_plugins_a_SOURCES += src/encoder/flac_encoder.c endif + +else +ENCODER_LIBS = endif @@ -609,58 +682,79 @@ src_mpd_SOURCES += src/zeroconf-bonjour.c endif endif -if HAVE_CUE -DECODER_SRC += src/cue/cue_tag.c -endif - # # input plugins # -INPUT_CFLAGS = \ +libinput_a_SOURCES = \ + src/input_init.c \ + src/input_registry.c \ + src/input_stream.c \ + src/input_internal.c src/input_internal.h \ + src/input/rewind_input_plugin.c \ + src/input/file_input_plugin.c + +libinput_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(CURL_CFLAGS) \ + $(SOUP_CFLAGS) \ + $(CDIO_PARANOIA_CFLAGS) \ $(FFMPEG_CFLAGS) \ + $(DESPOTIFY_CFLAGS) \ $(MMS_CFLAGS) INPUT_LIBS = \ + libinput.a \ $(CURL_LIBS) \ + $(SOUP_LIBS) \ + $(CDIO_PARANOIA_LIBS) \ $(FFMPEG_LIBS) \ + $(DESPOTIFY_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 ENABLE_CURL -INPUT_SRC += src/input/curl_input_plugin.c \ +libinput_a_SOURCES += src/input/curl_input_plugin.c \ src/icy_metadata.c endif +if ENABLE_SOUP +libinput_a_SOURCES += \ + src/input/soup_input_plugin.c \ + src/input/soup_input_plugin.h +endif + +if ENABLE_CDIO_PARANOIA +libinput_a_SOURCES += src/input/cdio_paranoia_input_plugin.c +endif + if HAVE_FFMPEG -INPUT_SRC += src/input/ffmpeg_input_plugin.c +libinput_a_SOURCES += src/input/ffmpeg_input_plugin.c endif if ENABLE_MMS -INPUT_SRC += src/input/mms_input_plugin.c +libinput_a_SOURCES += src/input/mms_input_plugin.c +endif + +if ENABLE_DESPOTIFY +libinput_a_SOURCES += src/input/despotify_input_plugin.c endif -OUTPUT_CFLAGS = \ +liboutput_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(AO_CFLAGS) \ $(ALSA_CFLAGS) \ $(FFADO_CFLAGS) \ $(JACK_CFLAGS) \ $(OPENAL_CFLAGS) \ + $(OPENSSL_CFLAGS) \ $(PULSE_CFLAGS) \ $(SHOUT_CFLAGS) OUTPUT_LIBS = \ + liboutput_plugins.a \ $(LIBWRAP_LDFLAGS) \ $(AO_LIBS) \ $(ALSA_LIBS) \ + $(ROAR_LIBS) \ $(FFADO_LIBS) \ $(JACK_LIBS) \ $(OPENAL_LIBS) \ @@ -675,10 +769,16 @@ OUTPUT_API_SRC = \ src/output_state.c \ src/output_print.c \ src/output_command.c \ + src/output_plugin.c src/output_plugin.h \ + src/output_finish.c \ src/output_init.c -OUTPUT_SRC = \ - src/output/null_plugin.c +liboutput_plugins_a_SOURCES = \ + src/output/null_output_plugin.c + +MIXER_LIBS = \ + libmixer_plugins.a \ + $(PULSE_LIBS) MIXER_API_SRC = \ src/mixer_control.c \ @@ -686,78 +786,99 @@ MIXER_API_SRC = \ src/mixer_all.c \ src/mixer_api.c -MIXER_SRC = \ +libmixer_plugins_a_SOURCES = \ src/mixer/software_mixer_plugin.c +libmixer_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ + $(ALSA_CFLAGS) \ + $(PULSE_CFLAGS) if HAVE_ALSA -OUTPUT_SRC += src/output/alsa_plugin.c -MIXER_SRC += src/mixer/alsa_mixer_plugin.c +liboutput_plugins_a_SOURCES += \ + src/output/alsa_output_plugin.c src/output/alsa_output_plugin.h +libmixer_plugins_a_SOURCES += src/mixer/alsa_mixer_plugin.c +endif + +if HAVE_ROAR +liboutput_plugins_a_SOURCES += \ + src/output/roar_output_plugin.c src/output/roar_output_plugin.h +libmixer_plugins_a_SOURCES += src/mixer/roar_mixer_plugin.c endif if ENABLE_FFADO_OUTPUT -OUTPUT_SRC += src/output/ffado_output_plugin.c +liboutput_plugins_a_SOURCES += src/output/ffado_output_plugin.c endif if HAVE_AO -OUTPUT_SRC += src/output/ao_plugin.c +liboutput_plugins_a_SOURCES += src/output/ao_output_plugin.c endif if HAVE_FIFO -OUTPUT_SRC += src/output/fifo_output_plugin.c +liboutput_plugins_a_SOURCES += src/output/fifo_output_plugin.c endif if ENABLE_PIPE_OUTPUT -OUTPUT_SRC += src/output/pipe_output_plugin.c +liboutput_plugins_a_SOURCES += src/output/pipe_output_plugin.c endif if HAVE_JACK -OUTPUT_SRC += src/output/jack_output_plugin.c +liboutput_plugins_a_SOURCES += src/output/jack_output_plugin.c endif if HAVE_MVP -OUTPUT_SRC += src/output/mvp_plugin.c +liboutput_plugins_a_SOURCES += src/output/mvp_output_plugin.c endif if HAVE_OSS -OUTPUT_SRC += src/output/oss_plugin.c -MIXER_SRC += src/mixer/oss_mixer_plugin.c +liboutput_plugins_a_SOURCES += src/output/oss_output_plugin.c +libmixer_plugins_a_SOURCES += src/mixer/oss_mixer_plugin.c endif if HAVE_OPENAL -OUTPUT_SRC += src/output/openal_plugin.c +liboutput_plugins_a_SOURCES += src/output/openal_output_plugin.c endif if HAVE_OSX -OUTPUT_SRC += src/output/osx_plugin.c +liboutput_plugins_a_SOURCES += src/output/osx_output_plugin.c +endif + +if ENABLE_RAOP_OUTPUT +liboutput_plugins_a_SOURCES += \ + src/ntp_server.c src/ntp_server.h \ + src/rtsp_client.c src/rtsp_client.h \ + src/output/raop_output_plugin.c +libmixer_plugins_a_SOURCES += src/mixer/raop_mixer_plugin.c +OUTPUT_LIBS += $(OPENSSL_LIBS) endif if HAVE_PULSE -OUTPUT_SRC += src/output/pulse_output_plugin.c -MIXER_SRC += src/mixer/pulse_mixer_plugin.c +liboutput_plugins_a_SOURCES += \ + src/output/pulse_output_plugin.c src/output/pulse_output_plugin.h +libmixer_plugins_a_SOURCES += src/mixer/pulse_mixer_plugin.c endif if HAVE_SHOUT -OUTPUT_SRC += src/output/shout_plugin.c +liboutput_plugins_a_SOURCES += src/output/shout_output_plugin.c endif if ENABLE_RECORDER_OUTPUT -OUTPUT_SRC += src/output/recorder_output_plugin.c +liboutput_plugins_a_SOURCES += src/output/recorder_output_plugin.c endif if ENABLE_HTTPD_OUTPUT -OUTPUT_SRC += \ +liboutput_plugins_a_SOURCES += \ src/icy_server.c \ src/output/httpd_client.c \ src/output/httpd_output_plugin.c endif if ENABLE_SOLARIS_OUTPUT -OUTPUT_SRC += src/output/solaris_output_plugin.c +liboutput_plugins_a_SOURCES += 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 +liboutput_plugins_a_SOURCES += \ + src/output/winmm_output_plugin.c src/output/winmm_output_plugin.h +libmixer_plugins_a_SOURCES += src/mixer/winmm_mixer_plugin.c endif @@ -765,33 +886,45 @@ endif # Playlist plugins # -PLAYLIST_SRC = \ +libplaylist_plugins_a_SOURCES = \ 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/cue_playlist_plugin.c \ + src/playlist/embcue_playlist_plugin.c \ + src/playlist/embcue_playlist_plugin.h \ src/playlist_list.c +libplaylist_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ + $(YAJL_CFLAGS) \ + $(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) + +PLAYLIST_LIBS = \ + libplaylist_plugins.a \ + $(FLAC_LIBS) if ENABLE_LASTFM -PLAYLIST_SRC += src/playlist/lastfm_playlist_plugin.c +libplaylist_plugins_a_SOURCES += src/playlist/lastfm_playlist_plugin.c endif -if HAVE_CUE -PLAYLIST_SRC += src/playlist/cue_playlist_plugin.c +if ENABLE_DESPOTIFY +libplaylist_plugins_a_SOURCES += src/playlist/despotify_playlist_plugin.c endif -if HAVE_FLAC -PLAYLIST_SRC += src/playlist/flac_playlist_plugin.c +if ENABLE_SOUNDCLOUD +libplaylist_plugins_a_SOURCES += \ + src/playlist/soundcloud_playlist_plugin.h \ + src/playlist/soundcloud_playlist_plugin.c +PLAYLIST_LIBS += $(YAJL_LIBS) endif - # # Filter plugins # -FILTER_SRC = \ +libfilter_plugins_a_SOURCES = \ src/filter/null_filter_plugin.c \ src/filter/chain_filter_plugin.c \ src/filter/autoconvert_filter_plugin.c \ @@ -801,6 +934,10 @@ FILTER_SRC = \ src/filter/replay_gain_filter_plugin.c \ src/filter/volume_filter_plugin.c +FILTER_LIBS = \ + libfilter_plugins.a \ + $(PCM_LIBS) + # # systemd unit @@ -825,8 +962,9 @@ SPARSE_CPPFLAGS = $(DEFAULT_INCLUDES) \ -I$(shell $(CC) -print-file-name=include-fixed) SPARSE_CPPFLAGS += -D__SCHAR_MAX__=127 -D__SHRT_MAX__=32767 \ -D__INT_MAX__=2147483647 -D__LONG_MAX__=2147483647 +SPARSE_SRC = $(addprefix $(top_srcdir)/,$(filter %.c,$(src_mpd_SOURCES))) sparse-check: - $(SPARSE) -I. $(src_mpd_CFLAGS) $(src_mpd_CPPFLAGS) $(SPARSE_FLAGS) $(SPARSE_CPPFLAGS) $(filter-out %.cxx,$(src_mpd_SOURCES)) + $(SPARSE) -I. $(src_mpd_CFLAGS) $(src_mpd_CPPFLAGS) $(SPARSE_FLAGS) $(SPARSE_CPPFLAGS) $(SPARSE_SRC) .PHONY: sparse-check @@ -837,14 +975,24 @@ sparse-check: if ENABLE_TEST -TESTS = +C_TESTS = \ + test/test_byte_reverse \ + test/test_pcm \ + test/test_queue_priority + +TESTS = $(C_TESTS) noinst_PROGRAMS = \ + $(C_TESTS) \ test/read_conf \ + test/run_resolver \ + test/run_tcp_connect \ test/run_input \ + test/dump_text_file \ test/dump_playlist \ test/run_decoder \ test/read_tags \ + test/run_ntp_server \ test/run_filter \ test/run_output \ test/run_convert \ @@ -856,52 +1004,68 @@ if HAVE_ALSA noinst_PROGRAMS += test/read_mixer endif -test_read_conf_CPPFLAGS = $(AM_CPPFLAGS) \ - $(GLIB_CFLAGS) -test_read_conf_LDADD = $(MPD_LIBS) \ +test_read_conf_LDADD = \ $(GLIB_LIBS) test_read_conf_SOURCES = test/read_conf.c \ - src/conf.c src/tokenizer.c src/utils.c + src/conf.c src/tokenizer.c src/utils.c src/string_util.c -test_run_input_CPPFLAGS = $(AM_CPPFLAGS) \ - $(ARCHIVE_CFLAGS) \ - $(INPUT_CFLAGS) -test_run_input_LDADD = $(MPD_LIBS) \ - $(ARCHIVE_LIBS) \ +test_run_resolver_LDADD = \ + $(GLIB_LIBS) +test_run_resolver_SOURCES = test/run_resolver.c \ + src/resolver.c + +test_run_tcp_connect_LDADD = \ + $(GLIB_LIBS) +test_run_tcp_connect_SOURCES = test/run_tcp_connect.c \ + src/io_thread.c src/io_thread.h \ + src/fd_util.c \ + src/resolver.c \ + src/tcp_connect.c + +test_run_input_LDADD = \ $(INPUT_LIBS) \ + $(ARCHIVE_LIBS) \ $(GLIB_LIBS) test_run_input_SOURCES = test/run_input.c \ test/stdbin.h \ - src/conf.c src/tokenizer.c src/utils.c \ + src/io_thread.c src/io_thread.h \ + src/conf.c src/tokenizer.c src/utils.c src/string_util.c\ src/tag.c src/tag_pool.c src/tag_save.c \ - src/fd_util.c \ - $(ARCHIVE_SRC) \ - $(INPUT_SRC) + src/fd_util.c -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) \ +test_dump_text_file_LDADD = \ + $(INPUT_LIBS) \ $(ARCHIVE_LIBS) \ + $(GLIB_LIBS) +test_dump_text_file_SOURCES = test/dump_text_file.c \ + test/stdbin.h \ + src/io_thread.c src/io_thread.h \ + src/conf.c src/tokenizer.c src/utils.c src/string_util.c\ + src/tag.c src/tag_pool.c \ + src/text_input_stream.c src/fifo_buffer.c \ + src/fd_util.c + +test_dump_playlist_LDADD = \ + $(PLAYLIST_LIBS) \ + $(FLAC_LIBS) \ $(INPUT_LIBS) \ + $(ARCHIVE_LIBS) \ + $(DECODER_LIBS) \ + $(TAG_LIBS) \ + libutil.a \ $(GLIB_LIBS) test_dump_playlist_SOURCES = test/dump_playlist.c \ - src/conf.c src/tokenizer.c src/utils.c \ + $(DECODER_SRC) \ + src/io_thread.c src/io_thread.h \ + src/conf.c src/tokenizer.c src/utils.c src/string_util.c\ src/uri.c \ src/song.c src/tag.c src/tag_pool.c src/tag_save.c \ + src/tag_handler.c src/tag_file.c \ + src/audio_check.c src/pcm_buffer.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 + src/cue/cue_parser.c src/cue/cue_parser.h \ + src/timer.c \ + src/fd_util.c if HAVE_FLAC test_dump_playlist_SOURCES += \ @@ -909,97 +1073,100 @@ test_dump_playlist_SOURCES += \ src/decoder/flac_metadata.c endif -test_run_decoder_CPPFLAGS = $(AM_CPPFLAGS) \ - $(TAG_CFLAGS) \ - $(ARCHIVE_CFLAGS) \ - $(INPUT_CFLAGS) $(DECODER_CFLAGS) -test_run_decoder_LDADD = $(MPD_LIBS) \ - $(TAG_LIBS) \ +test_run_decoder_LDADD = \ + $(DECODER_LIBS) \ + libpcm.a \ + $(INPUT_LIBS) \ $(ARCHIVE_LIBS) \ - $(INPUT_LIBS) $(DECODER_LIBS) \ + $(TAG_LIBS) \ + libutil.a \ $(GLIB_LIBS) test_run_decoder_SOURCES = test/run_decoder.c \ test/stdbin.h \ - src/conf.c src/tokenizer.c src/utils.c src/log.c \ - src/tag.c src/tag_pool.c \ + src/io_thread.c src/io_thread.h \ + src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \ + src/tag.c src/tag_pool.c src/tag_handler.c \ src/replay_gain_info.c \ src/uri.c \ src/fd_util.c \ src/audio_check.c \ src/audio_format.c \ src/timer.c \ - src/pcm_buffer.c \ $(ARCHIVE_SRC) \ $(INPUT_SRC) \ $(TAG_SRC) \ $(DECODER_SRC) -test_read_tags_CPPFLAGS = $(AM_CPPFLAGS) \ - $(TAG_CFLAGS) \ - $(ARCHIVE_CFLAGS) \ - $(INPUT_CFLAGS) $(DECODER_CFLAGS) -test_read_tags_LDADD = $(MPD_LIBS) \ - $(TAG_LIBS) \ +test_read_tags_LDADD = \ + $(DECODER_LIBS) \ + libpcm.a \ + $(INPUT_LIBS) \ $(ARCHIVE_LIBS) \ - $(INPUT_LIBS) $(DECODER_LIBS) \ + $(TAG_LIBS) \ + libutil.a \ $(GLIB_LIBS) test_read_tags_SOURCES = test/read_tags.c \ - src/conf.c src/tokenizer.c src/utils.c src/log.c \ - src/tag.c src/tag_pool.c \ + src/io_thread.c src/io_thread.h \ + src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \ + src/tag.c src/tag_pool.c src/tag_handler.c \ src/replay_gain_info.c \ src/uri.c \ src/fd_util.c \ src/audio_check.c \ src/timer.c \ - src/pcm_buffer.c \ - $(ARCHIVE_SRC) \ - $(INPUT_SRC) \ - $(TAG_SRC) \ $(DECODER_SRC) -test_run_filter_CPPFLAGS = $(AM_CPPFLAGS) -test_run_filter_LDADD = $(MPD_LIBS) \ - $(SAMPLERATE_LIBS) \ +test_run_ntp_server_LDADD = \ + $(GLIB_LIBS) +test_run_ntp_server_SOURCES = test/run_ntp_server.c \ + test/signals.c test/signals.h \ + src/io_thread.c src/io_thread.h \ + src/udp_server.c src/udp_server.h \ + src/ntp_server.c src/ntp_server.h + +test_run_filter_LDADD = \ + $(FILTER_LIBS) \ $(GLIB_LIBS) test_run_filter_SOURCES = test/run_filter.c \ test/stdbin.h \ 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/pcm_buffer.c \ + src/conf.c src/tokenizer.c src/utils.c src/string_util.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) + src/AudioCompress/compress.c -if HAVE_LIBSAMPLERATE -test_run_filter_SOURCES += src/pcm_resample_libsamplerate.c +if ENABLE_DESPOTIFY +test_read_tags_SOURCES += \ + src/despotify_utils.c +test_run_input_SOURCES += \ + src/despotify_utils.c +test_dump_text_file_SOURCES += \ + src/despotify_utils.c +test_dump_playlist_SOURCES += \ + src/despotify_utils.c +test_run_decoder_SOURCES += \ + src/despotify_utils.c endif if ENABLE_ENCODER noinst_PROGRAMS += test/run_encoder test_run_encoder_SOURCES = test/run_encoder.c \ test/stdbin.h \ + src/fifo_buffer.c src/growing_fifo.c \ src/conf.c src/tokenizer.c \ - src/utils.c \ + src/utils.c src/string_util.c \ src/tag.c src/tag_pool.c \ src/audio_check.c \ src/audio_format.c \ - src/audio_parser.c \ - src/pcm_buffer.c \ - src/fifo_buffer.c src/growing_fifo.c \ - $(ENCODER_SRC) -test_run_encoder_CPPFLAGS = $(AM_CPPFLAGS) \ - $(ENCODER_CFLAGS) -test_run_encoder_LDADD = $(MPD_LIBS) \ + src/audio_parser.c +test_run_encoder_LDADD = \ $(ENCODER_LIBS) \ + libpcm.a \ + $(TAG_LIBS) \ $(GLIB_LIBS) endif @@ -1009,6 +1176,7 @@ test_test_vorbis_encoder_SOURCES = test/test_vorbis_encoder.c \ test/stdbin.h \ src/conf.c src/tokenizer.c \ src/utils.c \ + src/string_util.c \ src/tag.c src/tag_pool.c \ src/audio_check.c \ src/audio_format.c \ @@ -1026,9 +1194,9 @@ endif test_software_volume_SOURCES = test/software_volume.c \ test/stdbin.h \ src/audio_check.c \ - src/audio_parser.c \ - src/pcm_volume.c + src/audio_parser.c test_software_volume_LDADD = \ + $(PCM_LIBS) \ $(GLIB_LIBS) test_run_normalize_SOURCES = test/run_normalize.c \ @@ -1040,39 +1208,29 @@ test_run_normalize_LDADD = \ $(GLIB_LIBS) test_run_convert_SOURCES = test/run_convert.c \ + src/dsd2pcm/dsd2pcm.c \ src/fifo_buffer.c \ src/audio_format.c \ src/audio_check.c \ - src/audio_parser.c \ - src/pcm_buffer.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) + src/audio_parser.c test_run_convert_LDADD = \ - $(SAMPLERATE_LIBS) \ + $(PCM_LIBS) \ + libutil.a \ $(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) test_run_output_LDADD = $(MPD_LIBS) \ - $(ENCODER_LIBS) \ $(OUTPUT_LIBS) \ + $(ENCODER_LIBS) \ + libmixer_plugins.a \ + $(FILTER_LIBS) \ + libutil.a \ $(GLIB_LIBS) test_run_output_SOURCES = test/run_output.c \ test/stdbin.h \ - src/conf.c src/tokenizer.c src/utils.c src/log.c \ + src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \ + src/io_thread.c src/io_thread.h \ + src/udp_server.c src/udp_server.h \ + src/tcp_socket.c src/tcp_socket.h \ src/audio_check.c \ src/audio_format.c \ src/audio_parser.c \ @@ -1081,41 +1239,31 @@ test_run_output_SOURCES = test/run_output.c \ src/fifo_buffer.c src/growing_fifo.c \ src/page.c \ src/socket_util.c \ - src/output_init.c src/output_list.c \ - $(ENCODER_SRC) \ + src/resolver.c \ + src/output_init.c src/output_finish.c src/output_list.c \ + src/output_plugin.c \ 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_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/pcm_buffer.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) + src/server_socket.c -test_read_mixer_CPPFLAGS = $(AM_CPPFLAGS) \ - $(OUTPUT_CFLAGS) -test_read_mixer_LDADD = $(MPD_LIBS) \ +test_read_mixer_LDADD = \ + libpcm.a \ + libmixer_plugins.a \ $(OUTPUT_LIBS) \ $(GLIB_LIBS) test_read_mixer_SOURCES = test/read_mixer.c \ - src/conf.c src/tokenizer.c src/utils.c src/log.c \ + src/conf.c src/tokenizer.c src/utils.c src/string_util.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 \ - src/pcm_buffer.c \ - $(MIXER_SRC) + src/fd_util.c if ENABLE_BZIP2_TEST TESTS += test/test_archive_bzip2.sh @@ -1138,6 +1286,41 @@ test_run_inotify_SOURCES = test/run_inotify.c \ test_run_inotify_LDADD = $(GLIB_LIBS) endif +test_test_byte_reverse_SOURCES = \ + test/test_glib_compat.h \ + test/test_byte_reverse.c +test_test_byte_reverse_LDADD = \ + libutil.a \ + $(GLIB_LIBS) + +test_test_pcm_SOURCES = \ + test/test_glib_compat.h \ + test/test_pcm_dither.c \ + test/test_pcm_pack.c \ + test/test_pcm_channels.c \ + test/test_pcm_all.h \ + test/test_pcm_main.c +test_test_pcm_LDADD = \ + $(PCM_LIBS) \ + libutil.a \ + $(GLIB_LIBS) + +test_test_queue_priority_SOURCES = \ + src/queue.c \ + test/test_queue_priority.c +test_test_queue_priority_LDADD = \ + $(GLIB_LIBS) + +if HAVE_CXX +noinst_PROGRAMS += src/dsd2pcm/dsd2pcm + +src_dsd2pcm_dsd2pcm_SOURCES = \ + src/dsd2pcm/dsd2pcm.c src/dsd2pcm/dsd2pcm.h \ + src/dsd2pcm/noiseshape.c src/dsd2pcm/noiseshape.h \ + src/dsd2pcm/main.cpp +src_dsd2pcm_dsd2pcm_LDADD = libutil.a +endif + endif @@ -1175,8 +1358,7 @@ endif doc/api/html/index.html: doc/doxygen.conf @mkdir -p $(@D) - [ "$(srcdir)" = "." ] || sed '/INPUT *=/ s/\([^ ]\+\/\)/$(subst /,\/,$(srcdir))\/\1/g' $(srcdir)/doc/doxygen.conf >doc/doxygen.conf - $(DOXYGEN) doc/doxygen.conf + $(DOXYGEN) $< all-local: $(DOCBOOK_HTML) doc/api/html/index.html @@ -1210,4 +1392,5 @@ endif EXTRA_DIST = $(doc_DATA) autogen.sh \ $(wildcard scripts/*.sh) \ - $(man_MANS) $(DOCBOOK_FILES) doc/mpdconf.example doc/doxygen.conf + $(man_MANS) $(DOCBOOK_FILES) doc/mpdconf.example doc/doxygen.conf \ + src/win/mpd_win32_rc.rc.in @@ -1,3 +1,44 @@ +ver 0.17 (2011/??/??) +* protocol: + - support client-to-client communication + - "update" and "rescan" need only "CONTROL" permission + - new command "seekcur" for simpler seeking within current song + - new command "config" dumps location of music directory + - add range parameter to command "load" + - print extra "playlist" object for embedded CUE sheets +* input: + - cdio_paranoia: new input plugin to play audio CDs + - curl: enable CURLOPT_NETRC + - curl: non-blocking I/O + - soup: new input plugin based on libsoup +* decoder: + - mpg123: implement seeking + - ffmpeg: drop support for pre-0.5 ffmpeg + - oggflac: delete this obsolete plugin + - dsdiff: new decoder plugin +* output: + - alsa: support DSD-over-USB (dCS suggested standard) + - httpd: support for streaming to a DLNA client + - openal: improve buffer cancellation + - osx: allow user to specify other audio devices + - osx: implement 32 bit playback + - raop: new output plugin + - shout: add possibility to set url + - roar: new output plugin for RoarAudio + - winmm: fail if wrong device specified instead of using default device +* mixer: + - alsa: listen for external volume changes +* playlist: + - allow references to songs outside the music directory + - new CUE parser, without libcue + - soundcloud: new plugin for accessing soundcloud.com +* state_file: add option "restore_paused" +* cue: show CUE track numbers +* allow port specification in "bind_to_address" settings +* support floating point samples +* systemd socket activation + + ver 0.16.8 (2012/??/??) * fix for libsamplerate assertion failure * decoder: diff --git a/autogen.sh b/autogen.sh index 28a89c674..f163e35a7 100755 --- a/autogen.sh +++ b/autogen.sh @@ -16,7 +16,7 @@ if test -n "$AM_FORCE_VERSION" then AM_VERSIONS="$AM_FORCE_VERSION" else - AM_VERSIONS='1.11 1.10' + AM_VERSIONS='1.11' fi if test -n "$AC_FORCE_VERSION" then diff --git a/configure.ac b/configure.ac index bbea25621..a84d6f698 100644 --- a/configure.ac +++ b/configure.ac @@ -1,11 +1,19 @@ AC_PREREQ(2.60) -AC_INIT(mpd, 0.16.8~git, musicpd-dev-team@lists.sourceforge.net) + +AC_INIT(mpd, 0.17~git, musicpd-dev-team@lists.sourceforge.net) + +VERSION_MAJOR=0 +VERSION_MINOR=17 +VERSION_REVISION=0 +VERSION_EXTRA=0 + AC_CONFIG_SRCDIR([src/main.c]) -AM_INIT_AUTOMAKE([foreign 1.10 dist-bzip2 subdir-objects]) -AM_CONFIG_HEADER(config.h) +AM_INIT_AUTOMAKE([foreign 1.11 dist-bzip2 subdir-objects]) +AM_SILENT_RULES +AC_CONFIG_HEADERS(config.h) AC_CONFIG_MACRO_DIR([m4]) -AC_DEFINE(PROTOCOL_VERSION, "0.16.0", [The MPD protocol version]) +AC_DEFINE(PROTOCOL_VERSION, "0.17.0", [The MPD protocol version]) dnl --------------------------------------------------------------------------- @@ -29,6 +37,7 @@ if test x$CXX = xg++; then HAVE_CXX=no fi fi +AM_CONDITIONAL(HAVE_CXX, test x$HAVE_CXX = xyes) AC_PROG_INSTALL AC_PROG_MAKE_SET @@ -56,10 +65,11 @@ AC_SUBST(AM_CPPFLAGS,"") AC_SUBST(AM_CFLAGS,"") AC_SUBST(AM_CXXFLAGS,"") -AC_SUBST(MPD_LIBS) -AC_SUBST(MPD_CFLAGS) -MPD_LIBS="" -MPD_CFLAGS="" +## Used for the windows resource file +AC_SUBST(VERSION_MAJOR) +AC_SUBST(VERSION_MINOR) +AC_SUBST(VERSION_REVISION) +AC_SUBST(VERSION_EXTRA) dnl --------------------------------------------------------------------------- dnl OS Specific Defaults @@ -68,10 +78,16 @@ AC_CANONICAL_HOST case "$host_os" in mingw32* | windows*) + AC_CONFIG_FILES([ + src/win/mpd_win32_rc.rc + ]) + AC_CHECK_TOOL(WINDRES, windres) AM_CPPFLAGS="$AM_CPPFLAGS -DWINVER=0x0501" - MPD_LIBS="$MPD_LIBS -lws2_32" + LIBS="$LIBS -lws2_32" + HAVE_WINDOWS=1 ;; esac +AM_CONDITIONAL([HAVE_WINDOWS], [test $HAVE_WINDOWS -eq 1]) if test -z "$prefix" || test "x$prefix" = xNONE; then local_lib= @@ -112,22 +128,18 @@ fi 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 - AC_CHECK_LIB($lib, syslog, - [AC_DEFINE(HAVE_SYSLOG) - LIBS="$LIBS -l$lib"; break]) - done -fi +AC_CHECK_FUNCS(daemon fork) -AC_CHECK_LIB(socket,socket,MPD_LIBS="$MPD_LIBS -lsocket",) -AC_CHECK_LIB(nsl,gethostbyname,MPD_LIBS="$MPD_LIBS -lnsl",) +AC_SEARCH_LIBS([syslog], [bsd socket inet], + [AC_DEFINE(HAVE_SYSLOG, 1, [Define if syslog() is available])]) + +AC_SEARCH_LIBS([socket], [socket]) +AC_SEARCH_LIBS([gethostbyname], [nsl]) AC_CHECK_FUNCS(pipe2 accept4) -AC_CHECK_LIB(m,exp,MPD_LIBS="$MPD_LIBS -lm",) +AC_SEARCH_LIBS([exp], [m],, + [AC_MSG_ERROR([exp() not found])]) AC_CHECK_HEADERS(locale.h) AC_CHECK_HEADERS(valgrind/memcheck.h) @@ -139,6 +151,11 @@ AC_ARG_ENABLE(alsa, AS_HELP_STRING([--enable-alsa], [enable ALSA support]),, [enable_alsa=auto]) +AC_ARG_ENABLE(roar, + AS_HELP_STRING([--enable-roar], + [enable support for RoarAudio]),, + [enable_roar=auto]) + AC_ARG_ENABLE(ao, AS_HELP_STRING([--enable-ao], [enable support for libao]),, @@ -154,16 +171,21 @@ AC_ARG_ENABLE(bzip2, [enable bzip2 archive support (default: disabled)]),, enable_bzip2=no) -AC_ARG_ENABLE(cue, - AS_HELP_STRING([--enable-cue], - [enable support for libcue support]),, - enable_cue=auto) +AC_ARG_ENABLE(cdio-paranoia, + AS_HELP_STRING([--enable-cdio-paranoia], + [enable support for audio CD support]),, + enable_cdio_paranoia=auto) AC_ARG_ENABLE(curl, AS_HELP_STRING([--enable-curl], [enable support for libcurl HTTP streaming (default: auto)]),, [enable_curl=auto]) +AC_ARG_ENABLE(soup, + AS_HELP_STRING([--enable-soup], + [enable support for libsoup HTTP streaming (default: auto)]),, + [enable_soup=auto]) + AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug], [enable debugging (default: disabled)]),, @@ -203,16 +225,16 @@ AC_ARG_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(raop-output, + AS_HELP_STRING([--enable-raop-output], + [enables the RAOP output]),, + [enable_raop_output=auto]) + AC_ARG_ENABLE(id3, AS_HELP_STRING([--enable-id3], [enable id3 support]),, @@ -245,6 +267,16 @@ AC_ARG_ENABLE(lastfm, [enable support for last.fm radio (default: disable)]),, [enable_lastfm=no]) +AC_ARG_ENABLE(despotify, + AS_HELP_STRING([--enable-despotify], + [enable support for despotify (default: disable)]),, + [enable_despotify=no]) + +AC_ARG_ENABLE(soundcloud, + AS_HELP_STRING([--enable-soundcloud], + [enable support for soundcloud.com]),, + [enable_soundcloud=auto]) + AC_ARG_ENABLE(lame-encoder, AS_HELP_STRING([--enable-lame-encoder], [enable the LAME mp3 encoder]),, @@ -294,11 +326,6 @@ AC_ARG_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)]),, @@ -350,6 +377,11 @@ AC_ARG_ENABLE(sqlite, [enable support for the SQLite database]),, [enable_sqlite=auto]) +AC_ARG_ENABLE(systemd-daemon, + AS_HELP_STRING([--enable-systemd-daemon], + [use the systemd daemon library (default=auto)]),, + [enable_systemd_daemon=auto]) + AC_ARG_ENABLE(tcp, AS_HELP_STRING([--disable-tcp], [disable support for clients connecting via TCP (default: enable)]),, @@ -492,6 +524,13 @@ if AC_MSG_ERROR([No client interfaces configured!]) fi +MPD_AUTO_PKG(systemd_daemon, SYSTEMD_DAEMON, libsystemd-daemon, + [systemd activation], [libsystemd-daemon not found]) +AM_CONDITIONAL(ENABLE_SYSTEMD_DAEMON, test x$enable_systemd_daemon = xyes) +if test x$enable_systemd_daemon = xyes; then + AC_DEFINE([ENABLE_SYSTEMD_DAEMON], 1, [Define to use the systemd daemon library]) +fi + dnl --------------------------------------------------------------------------- dnl LIBC Features dnl --------------------------------------------------------------------------- @@ -531,16 +570,6 @@ 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 - AC_DEFINE([HAVE_CUE], 1, - [Define to enable libcue support]) -fi - -AM_CONDITIONAL(HAVE_CUE, test x$enable_cue = xyes) - dnl -------------------------------- libid3tag -------------------------------- MPD_AUTO_PKG_LIB(id3, ID3TAG, id3tag, id3tag, id3_file_open, [-lid3tag -lz], [], [id3tag], [libid3tag not found]) @@ -585,8 +614,7 @@ if test x$with_zeroconf != xno; then if test x$with_zeroconf = xbonjour || test x$with_zeroconf = xauto; then AC_CHECK_HEADER(dns_sd.h, [enable_bonjour=yes;AC_DEFINE([HAVE_BONJOUR], 1, [Define to enable Bonjour Zeroconf support])]) - AC_CHECK_LIB(dns_sd, DNSServiceRegister, - MPD_LIBS="$MPD_LIBS -ldns_sd") + AC_CHECK_LIB([dns_sd], [DNSServiceRegister]) fi if test x$enable_bonjour = xyes; then @@ -653,6 +681,14 @@ if test x$enable_curl = xyes; then fi AM_CONDITIONAL(ENABLE_CURL, test x$enable_curl = xyes) +dnl ----------------------------------- SOUP ---------------------------------- +MPD_AUTO_PKG(soup, SOUP, [libsoup-2.4], + [libsoup HTTP streaming], [libsoup not found]) +if test x$enable_soup = xyes; then + AC_DEFINE(ENABLE_SOUP, 1, [Define when libsoup is used for HTTP streaming]) +fi +AM_CONDITIONAL(ENABLE_SOUP, test x$enable_soup = xyes) + dnl --------------------------------- Last.FM --------------------------------- if test x$enable_lastfm = xyes; then if test x$enable_curl != xyes; then @@ -663,6 +699,39 @@ if test x$enable_lastfm = xyes; then fi AM_CONDITIONAL(ENABLE_LASTFM, test x$enable_lastfm = xyes) +dnl --------------------------------- Despotify --------------------------------- +MPD_AUTO_PKG(despotify, DESPOTIFY, [despotify], + [Despotify support], [despotify not found]) +if test x$enable_despotify = xyes; then + AC_DEFINE(ENABLE_DESPOTIFY, 1, [Define when despotify is enabled]) +fi +AM_CONDITIONAL(ENABLE_DESPOTIFY, test x$enable_despotify = xyes) + +dnl --------------------------------- Soundcloud ------------------------------ +if test x$enable_soundcloud != xno; then + PKG_CHECK_MODULES([YAJL], [yajl >= 2.0], + [found_soundcloud=yes], + AC_CHECK_LIB([yajl], [yajl_alloc], + [found_soundcloud=yes YAJL_CFLAGS=-DHAVE_YAJL1 YAJL_LIBS=-lyajl], + [found_soundcloud=no])) +fi +MPD_AUTO_RESULT([soundcloud], [soundcloud.com support], [libyajl not found]) +if test x$enable_soundcloud = xyes; then + AC_DEFINE(ENABLE_SOUNDCLOUD, 1, [Define when soundcloud is enabled]) +fi +AM_CONDITIONAL(ENABLE_SOUNDCLOUD, test x$enable_soundcloud = xyes) +AC_SUBST(YAJL_LIBS) + +dnl ---------------------------------- cdio --------------------------------- +MPD_AUTO_PKG(cdio_paranoia, CDIO_PARANOIA, [libcdio_paranoia], + [libcdio_paranoia audio CD library], [libcdio_paranoia not found]) +if test x$enable_cdio_paranoia = xyes; then + AC_DEFINE([ENABLE_CDIO_PARANOIA], 1, + [Define to enable libcdio_paranoia support]) +fi + +AM_CONDITIONAL(ENABLE_CDIO_PARANOIA, test x$enable_cdio_paranoia = xyes) + dnl ---------------------------------- libmms --------------------------------- MPD_AUTO_PKG(mms, MMS, [libmms >= 0.4], [libmms mms:// protocol support], [libmms not found]) @@ -694,9 +763,10 @@ 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"], + [BZ2_LIBS="-lbz2"], [AC_MSG_ERROR([libbz2 not found])]) fi +AC_SUBST(BZ2_LIBS) AM_CONDITIONAL(HAVE_BZ2, test x$enable_bzip2 = xyes) if test x$enable_bzip2 = xyes; then @@ -756,21 +826,10 @@ AM_CONDITIONAL(HAVE_FAAD, test x$enable_aac = xyes) AM_CONDITIONAL(HAVE_MP4, test x$enable_mp4 = xyes) dnl ---------------------------------- ffmpeg --------------------------------- -MPD_AUTO_PKG(ffmpeg, FFMPEG, [libavformat >= 52 libavcodec >= 51 libavutil >= 49], +MPD_AUTO_PKG(ffmpeg, FFMPEG, [libavformat >= 52.31 libavcodec >= 52.20 libavutil >= 49.15], [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 @@ -783,25 +842,6 @@ MPD_AUTO_PKG(flac, FLAC, [flac >= 1.1], if test x$enable_flac = xyes; then AC_DEFINE(HAVE_FLAC, 1, [Define for FLAC support]) - - 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_oggflac = xflac; then - PKG_CHECK_MODULES(OGG, [ogg], - [FLAC_LIBS="${FLAC_LIBS} ${OGG_LIBS}" FLAC_CFLAGS="${FLAC_CFLAGS} ${OGG_CFLAGS}"], - [enable_oggflac=yes; - AC_MSG_WARN("FLAC has the ogg API built in, but couldn't find ogg. Disabling oggflac.")]) - fi fi AM_CONDITIONAL(HAVE_FLAC, test x$enable_flac = xyes) @@ -887,52 +927,32 @@ AM_CONDITIONAL(ENABLE_SNDFILE, test x$enable_sndfile = xyes) dnl --------------------------------- musepack -------------------------------- if test x$enable_mpc = xyes; then - if test "x$mpcdec_libraries" != "x" ; then - MPCDEC_LIBS="-L$mpcdec_libraries" - elif test "x$mpcdec_prefix" != "x" ; then - MPCDEC_LIBS="-L$mpcdec_prefix/lib" - fi - - MPCDEC_LIBS="$MPCDEC_LIBS -lmpcdec" - - if test "x$mpcdec_includes" != "x" ; then - MPCDEC_CFLAGS="-I$mpcdec_includes" - elif test "x$mpcdec_prefix" != "x" ; then - MPCDEC_CFLAGS="-I$mpcdec_prefix/include" - fi - oldcflags=$CFLAGS oldlibs=$LIBS oldcppflags=$CPPFLAGS - CFLAGS="$CFLAGS $MPD_CFLAGS $MPCDEC_CFLAGS -I." - LIBS="$LIBS $MPD_LIBS $MPCDEC_LIBS" - CPPFLAGS=$CFLAGS - AC_CHECK_HEADER(mpc/mpcdec.h, - old_mpcdec=no, - [AC_CHECK_HEADER(mpcdec/mpcdec.h, - old_mpcdec=yes, - enable_mpc=no)]) - if test x$enable_mpc = xyes; then - AC_CHECK_LIB(mpcdec,main, - [MPD_LIBS="$MPD_LIBS $MPCDEC_LIBS"; - MPD_CFLAGS="$MPD_CFLAGS $MPCDEC_CFLAGS";], + AC_CHECK_LIB(mpcdec,main, + MPCDEC_LIBS="$MPCDEC_LIBS -lmpcdec", enable_mpc=no) - fi + CFLAGS=$oldcflags + LIBS=$oldlibs + CPPFLAGS=$oldcppflags + if test x$enable_mpc = xyes; then - AC_DEFINE(HAVE_MPCDEC,1, - [Define to use libmpcdec for MPC decoding]) - if test x$old_mpcdec = xyes; then - AC_DEFINE(MPC_IS_OLD_API, 1, - [Define if an old pre-SV8 libmpcdec is used]) - fi + AC_CHECK_HEADER([mpc/mpcdec.h], + [AC_DEFINE(HAVE_MPCDEC,1, + [Define to use libmpcdec for MPC decoding])], + [AC_CHECK_HEADER(mpcdec/mpcdec.h, + [AC_DEFINE(MPC_IS_OLD_API, 1, + [Define if an old pre-SV8 libmpcdec is used])] + )] + ) else AC_MSG_WARN([mpcdec lib needed for MPC support -- disabling MPC support]) fi - CFLAGS=$oldcflags - LIBS=$oldlibs - CPPFLAGS=$oldcppflags fi +AC_SUBST(MPCDEC_LIBS) +AC_SUBST(MPCDEC_CFLAGS) AM_CONDITIONAL(HAVE_MPCDEC, test x$enable_mpc = xyes) dnl -------------------------------- Ogg Tremor ------------------------------- @@ -977,25 +997,6 @@ fi AC_SUBST(TREMOR_CFLAGS) AC_SUBST(TREMOR_LIBS) -dnl --------------------------------- OggFLAC --------------------------------- -dnl OggFLAC must go after Ogg Tremor - -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 - -if test x$enable_oggflac = xyes; then - 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) - dnl -------------------------------- Ogg Vorbis ------------------------------- if test x$enable_tremor = xyes; then @@ -1020,19 +1021,18 @@ 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]) + AC_CHECK_LIB([sidplay2],[main],[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, + 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]) + AC_CHECK_LIB([sidutils],[main],[],[found_sidplay=no],[]) fi MPD_AUTO_RESULT(sidplay, [sidplay decoder plugin], @@ -1095,7 +1095,6 @@ if 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 && @@ -1106,10 +1105,10 @@ if fi AM_CONDITIONAL(HAVE_OGG_COMMON, - test x$enable_vorbis = xyes || test x$enable_tremor = xyes || test x$enable_oggflac = xyes || test x$enable_flac = xyes) + test x$enable_vorbis = xyes || test x$enable_tremor = xyes || test x$enable_flac = xyes) AM_CONDITIONAL(HAVE_FLAC_COMMON, - test x$enable_flac = xyes || test x$enable_oggflac = xyes) + test x$enable_flac = xyes) dnl --------------------------------------------------------------------------- dnl Encoders for Streaming Audio Output Plugins @@ -1232,6 +1231,16 @@ fi AM_CONDITIONAL(HAVE_ALSA, test x$enable_alsa = xyes) +dnl ----------------------------------- ROAR ---------------------------------- +MPD_AUTO_PKG(roar, ROAR, [libroar >= 0.4.0], + [ROAR output plugin], [libroar not found]) + +if test x$enable_roar = xyes; then + AC_DEFINE(HAVE_ROAR, 1, [Define to enable ROAR support]) +fi + +AM_CONDITIONAL(HAVE_ROAR, test x$enable_roar = xyes) + dnl ----------------------------------- FFADO --------------------------------- MPD_AUTO_PKG(ffado, FFADO, [libffado], @@ -1342,7 +1351,7 @@ 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" + LIBS="$LIBS -framework AudioUnit -framework CoreAudio -framework CoreServices" enable_osx=yes ;; esac @@ -1419,13 +1428,24 @@ fi AM_CONDITIONAL(ENABLE_SOLARIS_OUTPUT, test x$enable_solaris_output = xyes) +dnl --------------------------------- RAOP ------------------------------------ + +MPD_AUTO_PKG(raop_output, OPENSSL, [openssl], + [RAOP output], [OpenSSL not found]) + +if test x$enable_raop_output = xyes; then + AC_DEFINE(ENABLE_RAOP_OUTPUT, 1, [Define for compiling RAOP support]) +fi + +AM_CONDITIONAL(ENABLE_RAOP_OUTPUT, test x$enable_raop_output = xyes) + dnl --------------------------------- WinMM --------------------------------- 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" + LIBS="$LIBS -lwinmm" ;; *) @@ -1438,6 +1458,7 @@ 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_roar = xno && test x$enable_ao = xno && test x$enable_ffado = xno && test x$enable_fifo = xno && @@ -1447,6 +1468,7 @@ if test x$enable_openal = xno && test x$enable_oss = xno && test x$enable_osx = xno && + test x$enable_raop_output = xno && test x$enable_pipe_output = xno && test x$enable_pulse = xno && test x$enable_recorder_output = xno && @@ -1505,12 +1527,6 @@ then MPD_CHECK_FLAG([-pedantic]) fi -dnl ------------------------------ gprof profiler ----------------------------- -if test "x$enable_gprof" = xyes; then - MPD_CFLAGS="$MPD_CFLAGS -pg" - MPD_LIBS="$MPD_LIBS -pg" -fi - dnl ---------------------------- warnings as errors --------------------------- if test "x$enable_werror" = xyes; then AM_CFLAGS="$AM_CFLAGS -Werror -pedantic-errors" @@ -1554,7 +1570,6 @@ results(mad, [MAD]) results(mpg123, [MPG123]) results(mp4, [MP4]) results(mpc, [Musepack]) -results(oggflac, [OggFLAC], flac) printf '\n\t' results(tremor, [OggTremor]) results(vorbis, [OggVorbis]) @@ -1568,7 +1583,6 @@ results(inotify, [inotify]) results(sqlite, [SQLite]) printf '\nMetadata support:\n\t' -results(cue,[cue]) results(id3,[ID3]) printf '\nPlayback support:\n\t' @@ -1578,16 +1592,18 @@ results(fifo,FIFO) results(recorder_output,[File Recorder]) results(httpd_output,[HTTP Daemon]) results(jack,[JACK]) +printf '\n\t' results(ao,[libao]) +results(mvp, [Media MVP]) results(oss,[OSS]) -printf '\n\t' results(openal,[OpenAL]) results(osx, [OS X]) results(pipe_output, [Pipeline]) +printf '\n\t' results(pulse, [PulseAudio]) -results(mvp, [Media MVP]) +results(raop_output, [RAOP]) +results(roar,[ROAR]) results(shout, [SHOUTcast]) -printf '\n\t' results(solaris_output, [Solaris]) results(winmm_output, [WinMM]) @@ -1604,9 +1620,14 @@ if fi printf '\nStreaming support:\n\t' +results(cdio_paranoia, [CDIO_PARANOIA]) results(curl,[CURL]) +results(despotify,[Despotify]) results(lastfm,[Last.FM]) +results(soundcloud,[Soundcloud]) +printf '\n\t' results(mms,[MMS]) +results(soup, [SOUP]) printf '\n\n##########################################\n\n' @@ -1615,7 +1636,9 @@ echo 'Generating files needed for compilation' dnl --------------------------------------------------------------------------- dnl Generate files dnl --------------------------------------------------------------------------- -AC_OUTPUT(Makefile) -AC_OUTPUT(mpd.service) +AC_CONFIG_FILES(Makefile) +AC_CONFIG_FILES(doc/doxygen.conf) +AC_CONFIG_FILES(mpd.service) +AC_OUTPUT echo 'MPD is ready for compilation, type "make" to begin.' diff --git a/doc/doxygen.conf b/doc/doxygen.conf.in index ddece77e8..95dca9a3c 100644 --- a/doc/doxygen.conf +++ b/doc/doxygen.conf.in @@ -31,7 +31,7 @@ PROJECT_NAME = MPD # This could be handy for archiving the generated documentation or # if some version control system is used. -PROJECT_NUMBER = +PROJECT_NUMBER = @VERSION@ # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. @@ -481,7 +481,7 @@ FILE_VERSION_FILTER = # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. -QUIET = NO +QUIET = YES # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank @@ -534,7 +534,7 @@ WARN_LOGFILE = # directories like "/usr/src/myproject". Separate the files or directories # with spaces. -INPUT = src/ +INPUT = @abs_top_srcdir@/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 diff --git a/doc/mpd.conf.5 b/doc/mpd.conf.5 index 4cddd7ba9..bd890228b 100644 --- a/doc/mpd.conf.5 +++ b/doc/mpd.conf.5 @@ -69,6 +69,9 @@ mpd will be saved to this file when mpd is terminated by a TERM signal or by the "kill" command. When mpd is restarted, it will read the state file and restore the state of mpd (including the playlist). .TP +.B restore_paused <yes or no> +Put MPD into pause mode instead of starting playback after startup. +.TP .B user <username> This specifies the user that MPD will run as, if set. MPD should never run as root, and you may use this option to make MPD change its @@ -80,6 +83,10 @@ This specifies which address mpd binds to and listens on. Multiple bind_to_address parameters may be specified. The default is "any", which binds to all available addresses. +You can set a port that is different from the global port setting, +e.g. "localhost:6602". IPv6 addresses must be enclosed in square +brackets if you want to configure a port, e.g. "[::1]:6602". + To bind to a Unix domain socket, specify an absolute path. For a system-wide MPD, we suggest the path "\fB/var/run/mpd/socket\fP". .TP @@ -259,6 +266,17 @@ of database. .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. +.TP +.B despotify_user <name> +This specifies the user to use when logging in to Spotify using the despotify plugins. +.TP +.B despotify_password <name> +This specifies the password to use when logging in to Spotify using the despotify plugins. +.TP +.B despotify_high_bitrate <yes or no> +This specifies if the requested bitrate for Spotify should be high or not. Higher sounds +better but requires more processing and higher bandwidth. Default is yes. +.TP .SH REQUIRED AUDIO OUTPUT PARAMETERS .TP .B type <type> @@ -464,6 +482,9 @@ connect to the icecast server. The default is 2 seconds. .B description <description> This specifies a description of the stream. .TP +.B url <url> +This specifies a URL associated with the stream. +.TP .B genre <genre> This specifies the genre(s) of the stream. .SH FILES diff --git a/doc/mpdconf.example b/doc/mpdconf.example index b14337c76..1aa9cf1dc 100644 --- a/doc/mpdconf.example +++ b/doc/mpdconf.example @@ -103,6 +103,11 @@ # #gapless_mp3_playback "yes" # +# Setting "restore_paused" to "yes" puts MPD into pause mode instead +# of starting playback after startup. +# +#restore_paused "no" +# # This setting enables MPD to create playlists in a format usable by other # music players. # @@ -235,6 +240,7 @@ input { ## protocol "icecast2" # optional ## user "source" # optional ## description "My Stream Description" # optional +## url "http://example.com" # optional ## genre "jazz" # optional ## public "no" # optional ## timeout "2" # optional diff --git a/doc/protocol.xml b/doc/protocol.xml index 0b4f0d175..56ff33b1e 100644 --- a/doc/protocol.xml +++ b/doc/protocol.xml @@ -59,7 +59,7 @@ <para> All data between the client and the server is encoded in UTF-8. (Note: In UTF-8 all standard ansi characters, 0-127 are - the same as a standard ansi encoding. Also, no ansi character + the same as in standard ansi encoding. Also, no ansi character appears in any multi-byte characters. So, you can use standard C functions like <function>strlen</function>, and <function>strcpy</function> just fine with UTF-8 encoded @@ -204,6 +204,47 @@ </chapter> <chapter> + <title>Recipes</title> + + <section> + <title>Queuing</title> + + <para> + Often, users run MPD with "<link + linkend="command_random">random</link>" enabled, but want to + be able to insert songs "before" the rest of the playlist. + That is commonly called "queuing". + </para> + + <para> + MPD implements this by allowing the client to specify a + "priority" for each song in the playlist (commands <link + linkend="command_prio"><command>prio</command></link> and + <link + linkend="command_prioid"><command>prioid</command></link>). A + higher priority means that the song is going to be played + before the other songs. + </para> + + <para> + In "random" mode, MPD maintains an internal randomized + sequence of songs. In this sequence, songs with a higher + priority come first, and all songs with the same priority are + shuffled (by default, all songs are shuffled, because all have + the same priority "0"). When you increase the priority of a + song, it is moved to the front of the sequence according to + its new priority, but always after the current one. A song + that has been played already (it's "before" the current song + in that sequence) will only be scheduled for repeated playback + if its priority has become bigger than the priority of the + current song. Decreasing the priority of a song will moved it + farther to the end of the sequence. Changing the priority of + the current song has no effect on the sequence. + </para> + </section> + </chapter> + + <chapter> <title>Command reference</title> <note> @@ -318,6 +359,25 @@ <option>crossfade</option>, replay gain </para> </listitem> + <listitem> + <para> + <returnvalue>sticker</returnvalue>: the sticker database + has been modified. + </para> + </listitem> + <listitem> + <para> + <returnvalue>subscription</returnvalue>: a client + has subscribed or unsubscribed to a channel + </para> + </listitem> + <listitem> + <para> + <returnvalue>message</returnvalue>: a message was + received on a channel this client is subscribed to; + this event is only emitted when the queue is empty + </para> + </listitem> </itemizedlist> <para> While a client is waiting for <command>idle</command> @@ -677,7 +737,11 @@ Sets the replay gain mode. One of <parameter>off</parameter>, <parameter>track</parameter>, - <parameter>album</parameter>. + <parameter>album</parameter>, + <parameter>auto</parameter><footnote + id="replay_gain_auto_since_0_16"> + <simpara>added in MPD 0.16</simpara> + </footnote>. </para> <para> Changing the mode during playback may take several @@ -814,6 +878,23 @@ </para> </listitem> </varlistentry> + + <varlistentry id="command_seekcur"> + <term> + <cmdsynopsis> + <command>seekcur</command> + <arg choice="req"><replaceable>TIME</replaceable></arg> + </cmdsynopsis> + </term> + <listitem> + <para> + Seeks to the position <varname>TIME</varname> within the + current song. If prefixed by '+' or '-', then the time + is relative to the current playing position. + </para> + </listitem> + </varlistentry> + <varlistentry id="command_stop"> <term> <cmdsynopsis> @@ -1073,6 +1154,46 @@ OK </para> </listitem> </varlistentry> + + <varlistentry id="command_prio"> + <term> + <cmdsynopsis> + <command>prio</command> + <arg choice="req"><replaceable>PRIORITY</replaceable></arg> + <arg choice="req" rep="repeat"><replaceable>START:END</replaceable></arg> + </cmdsynopsis> + </term> + <listitem> + <para> + Set the priority of the specified songs. A higher + priority means that it will be played first when + "random" mode is enabled. + </para> + + <para> + A priority is an integer between 0 and 255. The default + priority of new songs is 0. + </para> + </listitem> + </varlistentry> + + <varlistentry id="command_prioid"> + <term> + <cmdsynopsis> + <command>prioid</command> + <arg choice="req"><replaceable>PRIORITY</replaceable></arg> + <arg choice="req" rep="repeat"><replaceable>ID</replaceable></arg> + </cmdsynopsis> + </term> + <listitem> + <para> + Same as <link + linkend="command_prio"><command>prio</command></link>, + but address the songs with their id. + </para> + </listitem> + </varlistentry> + <varlistentry id="command_shuffle"> <term> <cmdsynopsis> @@ -1191,12 +1312,14 @@ OK <cmdsynopsis> <command>load</command> <arg choice="req"><replaceable>NAME</replaceable></arg> + <arg choice="opt"><replaceable>START:END</replaceable></arg> </cmdsynopsis> </term> <listitem> <para> Loads the playlist into the current queue. Playlist - plugins are supported. + plugins are supported. A range may be specified to load + only a part of the playlist. </para> </listitem> </varlistentry> @@ -1433,6 +1556,11 @@ OK the list of stored playlists. This behavior is deprecated; use "listplaylists" instead. </para> + <para> + Clients that are connected via UNIX domain socket may + use this command to read the tags of an arbitrary local + file (URI beginning with "file:///"). + </para> </listitem> </varlistentry> <varlistentry id="command_search"> @@ -1681,6 +1809,7 @@ OK <term> <cmdsynopsis> <command>disableoutput</command> + <arg choice="req"><replaceable>ID</replaceable></arg> </cmdsynopsis> </term> <listitem> @@ -1693,6 +1822,7 @@ OK <term> <cmdsynopsis> <command>enableoutput</command> + <arg choice="req"><replaceable>ID</replaceable></arg> </cmdsynopsis> </term> <listitem> @@ -1720,6 +1850,47 @@ OK <title>Reflection</title> <variablelist> + <varlistentry id="command_config"> + <term> + <cmdsynopsis> + <command>config</command> + </cmdsynopsis> + </term> + <listitem> + <para> + Dumps configuration values that may be interesting for + the client. This command is only permitted to "local" + clients (connected via UNIX domain socket). + </para> + <para> + The following response attributes are available: + </para> + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry> + Name + </entry> + <entry> + Description + </entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>music_directory</varname> + </entry> + <entry> + The absolute path of the music directory. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </listitem> + </varlistentry> <varlistentry id="command_commands"> <term> <cmdsynopsis> @@ -1790,5 +1961,105 @@ suffix: mpc</programlisting> </varlistentry> </variablelist> </section> + + <section> + <title>Client to client</title> + + <para> + Clients can communicate with each others over "channels". A + channel is created by a client subscribing to it. More than + one client can be subscribed to a channel at a time; all of + them will receive the messages which get sent to it. + </para> + + <para> + Each time a client subscribes or unsubscribes, the global idle + event <varname>subscription</varname> is generated. In + conjunction with the <command>channels</command> command, this + may be used to auto-detect clients providing additional + services. + </para> + + <para> + New messages are indicated by the <varname>message</varname> + idle event. + </para> + + <variablelist> + <varlistentry id="command_subscribe"> + <term> + <cmdsynopsis> + <command>subscribe</command> + <arg choice="req"><replaceable>NAME</replaceable></arg> + </cmdsynopsis> + </term> + <listitem> + <para> + Subscribe to a channel. The channel is created if it + does not exist already. The name may consist of + alphanumeric ASCII characters plus underscore, dash, dot + and colon. + </para> + </listitem> + </varlistentry> + + <varlistentry id="command_unsubscribe"> + <term> + <cmdsynopsis> + <command>unsubscribe</command> + <arg choice="req"><replaceable>NAME</replaceable></arg> + </cmdsynopsis> + </term> + <listitem> + <para> + Unsubscribe from a channel. + </para> + </listitem> + </varlistentry> + + <varlistentry id="command_channels"> + <term> + <cmdsynopsis> + <command>channels</command> + </cmdsynopsis> + </term> + <listitem> + <para> + Obtain a list of all channels. The response is a list + of "channel:" lines. + </para> + </listitem> + </varlistentry> + + <varlistentry id="command_readmessages"> + <term> + <cmdsynopsis> + <command>readmessages</command> + </cmdsynopsis> + </term> + <listitem> + <para> + Reads messages for this client. The response is a list + of "channel:" and "message:" lines. + </para> + </listitem> + </varlistentry> + + <varlistentry id="command_sendmessage"> + <term> + <cmdsynopsis> + <command>sendmessage</command> + <arg choice="req"><replaceable>CHANNEL</replaceable></arg> + <arg choice="req"><replaceable>TEXT</replaceable></arg> + </cmdsynopsis> + </term> + <listitem> + <para> + Send a message to the specified channel. + </para> + </listitem> + </varlistentry> + </variablelist> + </section> </chapter> </book> diff --git a/doc/user.xml b/doc/user.xml index 6a9871007..cd36528d5 100644 --- a/doc/user.xml +++ b/doc/user.xml @@ -99,6 +99,47 @@ cd mpd-version</programlisting> <programlisting>make install</programlisting> </section> + + <section> + <title><filename>systemd</filename> socket activation</title> + + <para> + Using <filename>systemd</filename>, you can launch + <filename>mpd</filename> on demand when the first client + attempts to connect. Create two files in + <filename>/etc/systemd/system/</filename>; first + <filename>mpd.socket</filename>: + </para> + + <programlisting>[Socket] +ListenStream=/run/mpd.socket +ListenStream=6600 +[Install] +WantedBy=sockets.target</programlisting> + + <para> + Now create <filename>mpd.service</filename>: + </para> + + <programlisting>[Unit] +Description=Music Player Daemon +After=sound.target +[Service] +ExecStart=/usr/bin/mpd --stdout --no-daemon</programlisting> + + <para> + Start the socket: + </para> + + <programlisting>systemctl enable mpd.socket +systemctl start mpd.socket</programlisting> + + <para> + In this configuration, <filename>mpd</filename> will ignore + the <varname>bind_to_address</varname> and + <varname>port</varname> settings. + </para> + </section> </chapter> <chapter> @@ -236,6 +277,16 @@ cd mpd-version</programlisting> </section> <section> + <title>Configuring encoder plugins</title> + + <para> + Encoders are used by some of the output plugins (such as + <varname>shout</varname>). The encoder settings are included + in the <varname>audio_output</varname> section. + </para> + </section> + + <section> <title>Configuring audio outputs</title> <para> @@ -322,7 +373,8 @@ cd mpd-version</programlisting> <varname>24_3</varname> (signed 24 bit integer samples, no padding, 3 bytes per sample), <varname>32</varname> (signed 32 bit integer - samples). + samples), <varname>f</varname> (32 bit floating + point, -1.0 to 1.0). </para> </entry> </row> @@ -346,7 +398,7 @@ cd mpd-version</programlisting> 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. + listeners even when playback is accidentally stopped. </entry> </row> <row> @@ -621,12 +673,143 @@ cd mpd-version</programlisting> Plays streams with the MMS protocol. </para> </section> + + <section> + <title><varname>cdio_paranoia</varname></title> + + <para> + Plays audio CDs. The URI has the form: + "<filename>cdda://[DEVICE][/TRACK]</filename>". The + simplest form <filename>cdda://</filename> plays the whole + disc in the default drive. + </para> + </section> + + <section> + <title><varname>despotify</varname></title> + + <para> + Plays <ulink url="http://www.spotify.com">Spotify</ulink> tracks using the despotify + library. The despotify plugin uses a <filename>spt://</filename> URI and a Spotify + URL. So for example, you can add a song with: + </para> + + <para> + <filename>mpc add spt://spotify:track:5qENVY0YEdZ7fiuOax70x1</filename> + </para> + + <para> + You need a Spotify premium account to use this plugin, and you need + to setup username and password in the configuration file. The + configuration settings are global since the despotify playlist plugin + use the same settings. + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>despotify_user</varname> + </entry> + <entry> + Sets up the Spotify username (required) + </entry> + </row> + <row> + <entry> + <varname>despotify_password</varname> + </entry> + <entry> + Sets up the Spotify password (required) + </entry> + </row> + <row> + <entry> + <varname>despotify_high_bitrate</varname> + </entry> + <entry> + Set up if high bitrate should be used for Spotify tunes. + High bitrate sounds better but slow systems can have problems + with playback (default yes). + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> + + <section> + <title><varname>soup</varname></title> + + <para> + Opens remote files or streams over HTTP. + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>proxy</varname> + </entry> + <entry> + Sets the address of the HTTP proxy server. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> </section> <section> <title>Decoder plugins</title> <section> + <title><varname>dsdiff</varname></title> + + <para> + Decodes DFF files containing DSDIFF data (e.g. SACD rips). + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>lsbitfirst</varname> + <parameter>yes|no</parameter> + </entry> + <entry> + Decode the least significant bit first. Default is + "no". + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> + + <section> <title><varname>mikmod</varname></title> <para> @@ -658,6 +841,178 @@ cd mpd-version</programlisting> </section> <section> + <title>Encoder plugins</title> + + <section> + <title><varname>flac</varname></title> + + <para> + Encodes into FLAC (lossless). + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>compression</varname> + </entry> + <entry> + Sets the <filename>libFLAC</filename> compression + level. The levels range from 0 (fastest, least + compression) to 8 (slowest, most compression). + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> + + <section> + <title><varname>lame</varname></title> + + <para> + Encodes into MP3 using the LAME library. + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>quality</varname> + </entry> + <entry> + Sets the quality for VBR. 0 is the highest quality, + 9 is the lowest quality. Cannot be used with + <varname>bitrate</varname>. + </entry> + </row> + <row> + <entry> + <varname>bitrate</varname> + </entry> + <entry> + Sets the bit rate in kilobit per second. Cannot be + used with <varname>quality</varname>. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> + + <section> + <title><varname>null</varname></title> + + <para> + Does not encode anything, passes the input PCM data as-is. + </para> + </section> + + <section> + <title><varname>twolame</varname></title> + + <para> + Encodes into MP2 using the <filename>twolame</filename> + library. + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>quality</varname> + </entry> + <entry> + Sets the quality for VBR. 0 is the highest quality, + 9 is the lowest quality. Cannot be used with + <varname>bitrate</varname>. + </entry> + </row> + <row> + <entry> + <varname>bitrate</varname> + </entry> + <entry> + Sets the bit rate in kilobit per second. Cannot be + used with <varname>quality</varname>. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> + + <section> + <title><varname>vorbis</varname></title> + + <para> + Encodes into Ogg Vorbis. + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>quality</varname> + </entry> + <entry> + Sets the quality for VBR. -1 is the lowest quality, + 10 is the highest quality. Cannot be used with + <varname>bitrate</varname>. + </entry> + </row> + <row> + <entry> + <varname>bitrate</varname> + </entry> + <entry> + Sets the bit rate in kilobit per second. Cannot be + used with <varname>quality</varname>. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> + + <section> + <title><varname>wave</varname></title> + + <para> + Encodes into WAV (lossless). + </para> + </section> + </section> + + <section> <title>Output plugins</title> <section> @@ -764,6 +1119,23 @@ cd mpd-version</programlisting> bit, floating point, ...). </entry> </row> + <row> + <entry> + <varname>dsd_usb</varname> + <parameter>yes|no</parameter> + </entry> + <entry> + If set to <parameter>yes</parameter>, then DSD over + USB according to the <ulink + url="http://www.sonore.us/DoP_openStandard_1v1.pdf">pro + posed standard by dCS and others</ulink> is enabled. This wraps + DSD samples in fake 24 bit PCM, and is understood by + some DSD capable products, but may be harmful to + other hardware. Therefore, the default is + <parameter>no</parameter> and you can enable the + option at your own risk. + </entry> + </row> </tbody> </tgroup> </informaltable> @@ -1366,6 +1738,15 @@ cd mpd-version</programlisting> </row> <row> <entry> + <varname>url</varname> + <parameter>URL</parameter> + </entry> + <entry> + Sets a URL associated with the stream (optional). + </entry> + </row> + <row> + <entry> <varname>public</varname> <parameter>yes|no</parameter> </entry> @@ -1467,6 +1848,14 @@ cd mpd-version</programlisting> </section> <section> + <title><varname>embcue</varname></title> + + <para> + Reads CUE sheets from the "CUESHEET" tag of song files. + </para> + </section> + + <section> <title><varname>m3u</varname></title> <para> @@ -1498,6 +1887,27 @@ cd mpd-version</programlisting> playlist files. </para> </section> + + <section> + <title><varname>despotify</varname></title> + + <para> + Adds <ulink url="http://www.spotify.com/">Spotify</ulink> + playlists. Spotify playlists use the <filename>spt://</filename> URI, + and a Spotify playlist URL. So for example, you can load a playlist + with + </para> + + <para> + <filename>mpc load spt://spotify:user:simon.kagstrom:playlist:3SUwkOe5VbVHysZcidEZtH</filename> + </para> + + <para> + See the despotify input plugin for configuration options (username + and password needs to be setup) + </para> + </section> + </section> </chapter> </book> diff --git a/m4/faad.m4 b/m4/faad.m4 index 007787557..1048c566c 100644 --- a/m4/faad.m4 +++ b/m4/faad.m4 @@ -39,8 +39,8 @@ if test x$enable_aac = xyes; then oldcflags=$CFLAGS oldlibs=$LIBS oldcppflags=$CPPFLAGS - CFLAGS="$CFLAGS $MPD_CFLAGS $FAAD_CFLAGS -I." - LIBS="$LIBS $MPD_LIBS $FAAD_LIBS" + CFLAGS="$CFLAGS $FAAD_CFLAGS -I." + LIBS="$LIBS $FAAD_LIBS" CPPFLAGS=$CFLAGS AC_CHECK_HEADER(faad.h,,enable_aac=no) if test x$enable_aac = xyes; then @@ -50,10 +50,10 @@ if test x$enable_aac = xyes; then AC_CHECK_DECL(faacDecInit2,,enable_aac=no,[#include <faad.h>]) fi if test x$enable_aac = xyes; then - AC_CHECK_LIB(faad,faacDecInit2,[MPD_LIBS="$MPD_LIBS $FAAD_LIBS";MPD_CFLAGS="$MPD_CFLAGS $FAAD_CFLAGS"],enable_aac=no) + AC_CHECK_LIB(faad,faacDecInit2,,enable_aac=no) if test x$enable_aac = xno; then enable_aac=yes - AC_CHECK_LIB(faad,NeAACDecInit2,[MPD_LIBS="$MPD_LIBS $FAAD_LIBS";MPD_CFLAGS="$MPD_CFLAGS $FAAD_CFLAGS"],enable_aac=no) + AC_CHECK_LIB(faad,NeAACDecInit2,,enable_aac=no) fi fi if test x$enable_aac = xyes; then @@ -131,8 +131,8 @@ if test x$enable_aac = xyes; then oldcflags=$CFLAGS oldlibs=$LIBS oldcppflags=$CPPFLAGS - CFLAGS="$CFLAGS $MPD_CFLAGS $FAAD_CFLAGS -Werror" - LIBS="$LIBS $MPD_LIBS $FAAD_LIBS" + CFLAGS="$CFLAGS $FAAD_CFLAGS -Werror" + LIBS="$LIBS $FAAD_LIBS" CPPFLAGS=$CFLAGS AC_MSG_CHECKING(for broken libfaad headers) @@ -188,5 +188,11 @@ if test x$enable_aac = xyes; then CPPFLAGS=$oldcppflags else enable_mp4=no + FAAD_CFLAGS="" + FAAD_LIBS="" fi + +AC_SUBST(FAAD_CFLAGS) +AC_SUBST(FAAD_LIBS) + ]) diff --git a/m4/ucred.m4 b/m4/ucred.m4 index 3a8245245..cdc6ea3b3 100644 --- a/m4/ucred.m4 +++ b/m4/ucred.m4 @@ -20,7 +20,7 @@ AC_DEFUN([STRUCT_UCRED],[ mpd_cv_have_struct_ucred=no) if test x$mpd_cv_have_struct_ucred = xyes; then # :( - MPD_CFLAGS="$MPD_CFLAGS -D_GNU_SOURCE" + CFLAGS="$CFLAGS -D_GNU_SOURCE" fi fi ]) @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_ACK_H #define MPD_ACK_H +#include <glib.h> + enum ack { ACK_ERROR_NOT_LIST = 1, ACK_ERROR_ARG = 2, @@ -36,4 +38,14 @@ enum ack { ACK_ERROR_EXIST = 56, }; +/** + * Quark for GError.domain; the code is an enum #ack. + */ +G_GNUC_CONST +static inline GQuark +ack_quark(void) +{ + return g_quark_from_static_string("ack"); +} + #endif diff --git a/src/aiff.c b/src/aiff.c index e2ca0dfe4..06de9ffe7 100644 --- a/src/aiff.c +++ b/src/aiff.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -57,7 +57,7 @@ aiff_seek_id3(FILE *file) ret = fstat(fileno(file), &st); if (ret < 0) { g_warning("Failed to stat file descriptor: %s", - strerror(errno)); + g_strerror(errno)); return 0; } diff --git a/src/aiff.h b/src/aiff.h index 52c0a73ec..a0ae2d41a 100644 --- a/src/aiff.h +++ b/src/aiff.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 index 2414eb519..e2420048b 100644 --- a/src/archive/bz2_archive_plugin.c +++ b/src/archive/bz2_archive_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,6 +24,7 @@ #include "config.h" #include "archive/bz2_archive_plugin.h" #include "archive_api.h" +#include "input_internal.h" #include "input_plugin.h" #include "refcount.h" @@ -102,6 +103,11 @@ bz2_destroy(struct bz2_input_stream *data) /* archive open && listing routine */ +#if GCC_CHECK_VERSION(4, 2) +/* workaround for a warning caused by G_STATIC_MUTEX_INIT */ +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + static struct archive_file * bz2_open(const char *pathname, GError **error_r) { @@ -113,7 +119,11 @@ bz2_open(const char *pathname, GError **error_r) refcount_init(&context->ref); //open archive - context->istream = input_stream_open(pathname, error_r); + static GStaticMutex mutex = G_STATIC_MUTEX_INIT; + context->istream = input_stream_open(pathname, + g_static_mutex_get_mutex(&mutex), + NULL, + error_r); if (context->istream == NULL) { g_free(context); return NULL; @@ -168,12 +178,15 @@ bz2_close(struct archive_file *file) /* single archive handling */ static struct input_stream * -bz2_open_stream(struct archive_file *file, const char *path, GError **error_r) +bz2_open_stream(struct archive_file *file, const char *path, + GMutex *mutex, GCond *cond, + 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); + input_stream_init(&bis->base, &bz2_inputplugin, path, + mutex, cond); bis->archive = context; diff --git a/src/archive/bz2_archive_plugin.h b/src/archive/bz2_archive_plugin.h index 199049008..46c69a66c 100644 --- a/src/archive/bz2_archive_plugin.h +++ b/src/archive/bz2_archive_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/iso9660_archive_plugin.c b/src/archive/iso9660_archive_plugin.c index 142fa10e0..bb6cb9588 100644 --- a/src/archive/iso9660_archive_plugin.c +++ b/src/archive/iso9660_archive_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,6 +24,7 @@ #include "config.h" #include "archive/iso9660_archive_plugin.h" #include "archive_api.h" +#include "input_internal.h" #include "input_plugin.h" #include "refcount.h" @@ -172,15 +173,17 @@ struct iso9660_input_stream { }; static struct input_stream * -iso9660_archive_open_stream(struct archive_file *file, - const char *pathname, GError **error_r) +iso9660_archive_open_stream(struct archive_file *file, const char *pathname, + GMutex *mutex, GCond *cond, + 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); + input_stream_init(&iis->base, &iso9660_input_plugin, pathname, + mutex, cond); iis->archive = context; iis->statbuf = iso9660_ifs_stat_translate(context->iso, pathname); diff --git a/src/archive/iso9660_archive_plugin.h b/src/archive/iso9660_archive_plugin.h index 2a3864cee..47dc6e474 100644 --- a/src/archive/iso9660_archive_plugin.h +++ b/src/archive/iso9660_archive_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/zzip_archive_plugin.c b/src/archive/zzip_archive_plugin.c index 3c2b80318..ad96b5f89 100644 --- a/src/archive/zzip_archive_plugin.c +++ b/src/archive/zzip_archive_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 @@ #include "archive/zzip_archive_plugin.h" #include "archive_api.h" #include "archive_api.h" +#include "input_internal.h" #include "input_plugin.h" #include "refcount.h" @@ -134,14 +135,17 @@ struct zzip_input_stream { static struct input_stream * zzip_archive_open_stream(struct archive_file *file, - const char *pathname, GError **error_r) + const char *pathname, + GMutex *mutex, GCond *cond, + 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); + input_stream_init(&zis->base, &zzip_input_plugin, pathname, + mutex, cond); zis->archive = context; zis->file = zzip_file_open(context->dir, pathname, 0); diff --git a/src/archive/zzip_archive_plugin.h b/src/archive/zzip_archive_plugin.h index 6d5037eef..2b2c01e5a 100644 --- a/src/archive/zzip_archive_plugin.h +++ b/src/archive/zzip_archive_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_api.c b/src/archive_api.c index b15810f1b..be3c35f7e 100644 --- a/src/archive_api.c +++ b/src/archive_api.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_api.h b/src/archive_api.h index f08960c72..4e0f603f5 100644 --- a/src/archive_api.h +++ b/src/archive_api.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_internal.h b/src/archive_internal.h index 03439e826..0d885e91c 100644 --- a/src/archive_internal.h +++ b/src/archive_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_list.c b/src/archive_list.c index 2656726b5..24aa060c9 100644 --- a/src/archive_list.c +++ b/src/archive_list.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 @@ #include "config.h" #include "archive_list.h" #include "archive_plugin.h" -#include "utils.h" +#include "string_util.h" #include "archive/bz2_archive_plugin.h" #include "archive/iso9660_archive_plugin.h" #include "archive/zzip_archive_plugin.h" diff --git a/src/archive_list.h b/src/archive_list.h index b65245ce9..24e4063bf 100644 --- a/src/archive_list.h +++ b/src/archive_list.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_plugin.c b/src/archive_plugin.c index 60da4d283..cf23e6393 100644 --- a/src/archive_plugin.c +++ b/src/archive_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -81,12 +81,14 @@ 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) +archive_file_open_stream(struct archive_file *file, const char *path, + GMutex *mutex, GCond *cond, + 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); + return file->plugin->open_stream(file, path, mutex, cond, + error_r); } diff --git a/src/archive_plugin.h b/src/archive_plugin.h index b08c93389..b7b92446d 100644 --- a/src/archive_plugin.h +++ b/src/archive_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -68,11 +68,12 @@ struct archive_plugin { * 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 + * @param error_r location to store the error occurring, or * NULL to ignore errors */ struct input_stream *(*open_stream)(struct archive_file *af, const char *path, + GMutex *mutex, GCond *cond, GError **error_r); /** @@ -101,7 +102,8 @@ 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); +archive_file_open_stream(struct archive_file *file, const char *path, + GMutex *mutex, GCond *cond, + GError **error_r); #endif diff --git a/src/audio_check.c b/src/audio_check.c index 61d2c5833..a9aa2dd82 100644 --- a/src/audio_check.c +++ b/src/audio_check.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/audio_check.h b/src/audio_check.h index 4862e7f15..9f71cf9c0 100644 --- a/src/audio_check.h +++ b/src/audio_check.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,6 +28,7 @@ /** * The GLib quark used for errors reported by this library. */ +G_GNUC_CONST static inline GQuark audio_format_quark(void) { diff --git a/src/audio.c b/src/audio_config.c index f9894cf3c..72869c384 100644 --- a/src/audio.c +++ b/src/audio_config.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,7 +18,7 @@ */ #include "config.h" -#include "audio.h" +#include "audio_config.h" #include "audio_format.h" #include "audio_parser.h" #include "output_internal.h" diff --git a/src/audio.h b/src/audio_config.h index cb3ab7bbe..85143247f 100644 --- a/src/audio.h +++ b/src/audio_config.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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. */ -#ifndef MPD_AUDIO_H -#define MPD_AUDIO_H +#ifndef MPD_AUDIO_CONFIG_H +#define MPD_AUDIO_CONFIG_H #include <stdbool.h> diff --git a/src/audio_format.c b/src/audio_format.c index 13403fbc1..45d94a853 100644 --- a/src/audio_format.c +++ b/src/audio_format.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,11 +22,24 @@ #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 +void +audio_format_mask_apply(struct audio_format *af, + const struct audio_format *mask) +{ + assert(audio_format_valid(af)); + assert(audio_format_mask_valid(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; + + assert(audio_format_valid(af)); +} const char * sample_format_to_string(enum sample_format format) @@ -41,14 +54,17 @@ sample_format_to_string(enum sample_format format) 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"; + + case SAMPLE_FORMAT_FLOAT: + return "f"; + + case SAMPLE_FORMAT_DSD: + return "dsd"; } /* unreachable */ @@ -63,9 +79,8 @@ audio_format_to_string(const struct audio_format *af, assert(af != NULL); assert(s != NULL); - snprintf(s->buffer, sizeof(s->buffer), "%u:%s%s:%u", + snprintf(s->buffer, sizeof(s->buffer), "%u:%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 a4450ad71..bf77add3b 100644 --- a/src/audio_format.h +++ b/src/audio_format.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ #ifndef MPD_AUDIO_FORMAT_H #define MPD_AUDIO_FORMAT_H +#include <glib.h> #include <stdint.h> #include <stdbool.h> #include <assert.h> @@ -31,19 +32,28 @@ enum sample_format { 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, + + /** + * 32 bit floating point samples in the host's format. The + * range is -1.0f to +1.0f. + */ + SAMPLE_FORMAT_FLOAT, + + /** + * Direct Stream Digital. 1-bit samples; each frame has one + * byte (8 samples) per channel. + */ + SAMPLE_FORMAT_DSD, }; +static const unsigned MAX_CHANNELS = 8; + /** * This structure describes the format of a raw PCM stream. */ @@ -66,13 +76,6 @@ struct audio_format { * 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; }; /** @@ -91,7 +94,6 @@ static inline void audio_format_clear(struct audio_format *af) af->sample_rate = 0; af->format = SAMPLE_FORMAT_UNDEFINED; af->channels = 0; - af->reverse_endian = 0; } /** @@ -105,7 +107,6 @@ static inline void audio_format_init(struct audio_format *af, af->sample_rate = sample_rate; af->format = (uint8_t)format; af->channels = channels; - af->reverse_endian = 0; } /** @@ -162,9 +163,10 @@ audio_valid_sample_format(enum sample_format format) switch (format) { case SAMPLE_FORMAT_S8: case SAMPLE_FORMAT_S16: - case SAMPLE_FORMAT_S24: case SAMPLE_FORMAT_S24_P32: case SAMPLE_FORMAT_S32: + case SAMPLE_FORMAT_FLOAT: + case SAMPLE_FORMAT_DSD: return true; case SAMPLE_FORMAT_UNDEFINED: @@ -180,13 +182,14 @@ audio_valid_sample_format(enum sample_format format) static inline bool audio_valid_channel_count(unsigned channels) { - return channels >= 1 && channels <= 8; + return channels >= 1 && channels <= MAX_CHANNELS; } /** * Returns false if the format is not valid for playback with MPD. * This function performs some basic validity checks. */ +G_GNUC_PURE static inline bool audio_format_valid(const struct audio_format *af) { return audio_valid_sample_rate(af->sample_rate) && @@ -198,6 +201,7 @@ static inline bool audio_format_valid(const struct audio_format *af) * Returns false if the format mask is not valid for playback with * MPD. This function performs some basic validity checks. */ +G_GNUC_PURE static inline bool audio_format_mask_valid(const struct audio_format *af) { return (af->sample_rate == 0 || @@ -212,58 +216,54 @@ static inline bool audio_format_equals(const struct audio_format *a, { return a->sample_rate == b->sample_rate && a->format == b->format && - a->channels == b->channels && - a->reverse_endian == b->reverse_endian; + a->channels == b->channels; } -static inline void +void audio_format_mask_apply(struct audio_format *af, - const struct audio_format *mask) -{ - assert(audio_format_valid(af)); - assert(audio_format_mask_valid(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; - - assert(audio_format_valid(af)); -} + const struct audio_format *mask); -/** - * Returns the size of each (mono) sample in bytes. - */ -static inline unsigned audio_format_sample_size(const struct audio_format *af) +G_GNUC_CONST +static inline unsigned +sample_format_size(enum sample_format format) { - switch (af->format) { + switch (format) { case SAMPLE_FORMAT_S8: return 1; case SAMPLE_FORMAT_S16: return 2; - case SAMPLE_FORMAT_S24: - return 3; - case SAMPLE_FORMAT_S24_P32: case SAMPLE_FORMAT_S32: + case SAMPLE_FORMAT_FLOAT: return 4; + case SAMPLE_FORMAT_DSD: + /* each frame has 8 samples per channel */ + return 1; + case SAMPLE_FORMAT_UNDEFINED: - break; + return 0; } + assert(false); return 0; } /** + * Returns the size of each (mono) sample in bytes. + */ +G_GNUC_PURE +static inline unsigned audio_format_sample_size(const struct audio_format *af) +{ + return sample_format_size((enum sample_format)af->format); +} + +/** * Returns the size of each full frame in bytes. */ +G_GNUC_PURE static inline unsigned audio_format_frame_size(const struct audio_format *af) { @@ -274,6 +274,7 @@ audio_format_frame_size(const struct audio_format *af) * Returns the floating point factor which converts a time span to a * storage size in bytes. */ +G_GNUC_PURE static inline double audio_format_time_to_size(const struct audio_format *af) { return af->sample_rate * audio_format_frame_size(af); @@ -286,6 +287,7 @@ static inline double audio_format_time_to_size(const struct audio_format *af) * @param format a #sample_format enum value * @return the string */ +G_GNUC_PURE G_GNUC_MALLOC const char * sample_format_to_string(enum sample_format format); @@ -297,6 +299,7 @@ sample_format_to_string(enum sample_format format); * @param s a buffer to print into * @return the string, or NULL if the #audio_format object is invalid */ +G_GNUC_PURE G_GNUC_MALLOC const char * audio_format_to_string(const struct audio_format *af, struct audio_format_string *s); diff --git a/src/audio_parser.c b/src/audio_parser.c index 139cf1c04..b1be8887a 100644 --- a/src/audio_parser.c +++ b/src/audio_parser.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -81,6 +81,18 @@ parse_sample_format(const char *src, bool mask, return true; } + if (*src == 'f') { + *sample_format_r = SAMPLE_FORMAT_FLOAT; + *endptr_r = src + 1; + return true; + } + + if (memcmp(src, "dsd", 3) == 0) { + *sample_format_r = SAMPLE_FORMAT_DSD; + *endptr_r = src + 3; + return true; + } + value = strtoul(src, &endptr, 10); if (endptr == src) { g_set_error(error_r, audio_parser_quark(), 0, @@ -98,11 +110,11 @@ parse_sample_format(const char *src, bool mask, break; case 24: - if (memcmp(endptr, "_3", 2) == 0) { - sample_format = SAMPLE_FORMAT_S24; + if (memcmp(endptr, "_3", 2) == 0) + /* for backwards compatibility */ endptr += 2; - } else - sample_format = SAMPLE_FORMAT_S24_P32; + + sample_format = SAMPLE_FORMAT_S24_P32; break; case 32: diff --git a/src/audio_parser.h b/src/audio_parser.h index 214ec5eb1..a963eb467 100644 --- a/src/audio_parser.h +++ b/src/audio_parser.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -38,7 +38,7 @@ struct audio_format; * @param dest the destination #audio_format struct * @param src the input string * @param mask if true, then "*" is allowed for any number of items - * @param error_r location to store the error occuring, or NULL to + * @param error_r location to store the error occurring, or NULL to * ignore errors * @return true on success */ diff --git a/src/buffer.c b/src/buffer.c index bee871700..559f39a9a 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/buffer.h b/src/buffer.h index 75e5bc6e6..f860231e7 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/check.h b/src/check.h index 56061621f..0642a4b91 100644 --- a/src/check.h +++ b/src/check.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/chunk.c b/src/chunk.c index 79597506d..1eb96f4b9 100644 --- a/src/chunk.c +++ b/src/chunk.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/chunk.h b/src/chunk.h index 02e7b3650..a06a203eb 100644 --- a/src/chunk.h +++ b/src/chunk.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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.c b/src/client.c index 9668c9249..3fa2c9be4 100644 --- a/src/client.c +++ b/src/client.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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.h b/src/client.h index d46747b4f..0302a2e0a 100644 --- a/src/client.h +++ b/src/client.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,20 +27,36 @@ struct client; struct sockaddr; +struct player_control; void client_manager_init(void); void client_manager_deinit(void); -void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid); +void client_new(struct player_control *player_control, + int fd, const struct sockaddr *sa, size_t sa_length, int uid); +G_GNUC_PURE bool client_is_expired(const struct client *client); /** * returns the uid of the client process, or a negative value if the * uid is unknown */ +G_GNUC_PURE int client_get_uid(const struct client *client); +/** + * Is this client running on the same machine, connected with a local + * (UNIX domain) socket? + */ +G_GNUC_PURE +static inline bool +client_is_local(const struct client *client) +{ + return client_get_uid(client) > 0; +} + +G_GNUC_PURE unsigned client_get_permission(const struct client *client); void client_set_permission(struct client *client, unsigned permission); @@ -60,17 +76,4 @@ void client_vprintf(struct client *client, const char *fmt, va_list args); */ G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...); -/** - * Adds the specified idle flags to all clients and immediately sends - * notifications to all waiting clients. - */ -void client_manager_idle_add(unsigned flags); - -/** - * Checks whether the client has pending idle flags. If yes, they are - * sent immediately and "true" is returned". If no, it puts the - * client into waiting mode and returns false. - */ -bool client_idle_wait(struct client *client, unsigned flags); - #endif diff --git a/src/client_event.c b/src/client_event.c index 93f5a9df7..4f54ae0a7 100644 --- a/src/client_event.c +++ b/src/client_event.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_expire.c b/src/client_expire.c index a5b0be047..1ca32ebcc 100644 --- a/src/client_expire.c +++ b/src/client_expire.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_file.c b/src/client_file.c new file mode 100644 index 000000000..2ee433308 --- /dev/null +++ b/src/client_file.c @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "client_file.h" +#include "client.h" +#include "ack.h" + +#include <sys/stat.h> +#include <sys/types.h> +#include <errno.h> +#include <unistd.h> + +bool +client_allow_file(const struct client *client, const char *path_fs, + GError **error_r) +{ +#ifdef WIN32 + (void)client; + (void)path_fs; + + g_set_error(error_r, ack_quark(), ACK_ERROR_PERMISSION, + "Access denied"); + return false; +#else + const int uid = client_get_uid(client); + if (uid >= 0 && (uid_t)uid == geteuid()) + /* always allow access if user runs his own MPD + instance */ + return true; + + if (uid <= 0) { + /* unauthenticated client */ + g_set_error(error_r, ack_quark(), ACK_ERROR_PERMISSION, + "Access denied"); + return false; + } + + struct stat st; + if (stat(path_fs, &st) < 0) { + g_set_error(error_r, g_file_error_quark(), errno, + "%s", g_strerror(errno)); + return false; + } + + if (st.st_uid != (uid_t)uid && (st.st_mode & 0444) != 0444) { + /* client is not owner */ + g_set_error(error_r, ack_quark(), ACK_ERROR_PERMISSION, + "Access denied"); + return false; + } + + return true; +#endif +} diff --git a/src/client_file.h b/src/client_file.h new file mode 100644 index 000000000..3dcbe7500 --- /dev/null +++ b/src/client_file.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_FILE_H +#define MPD_CLIENT_FILE_H + +#include <glib.h> +#include <stdbool.h> + +struct client; + +/** + * Is this client allowed to use the specified local file? + * + * Note that this function is vulnerable to timing/symlink attacks. + * We cannot fix this as long as there are plugins that open a file by + * its name, and not by file descriptor / callbacks. + * + * @param path_fs the absolute path name in filesystem encoding + * @return true if access is allowed + */ +G_GNUC_PURE +bool +client_allow_file(const struct client *client, const char *path_fs, + GError **error_r); + +#endif diff --git a/src/client_global.c b/src/client_global.c index fc5adedba..adf3b2f9e 100644 --- a/src/client_global.c +++ b/src/client_global.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_idle.c b/src/client_idle.c index 10be4d430..930911d6e 100644 --- a/src/client_idle.c +++ b/src/client_idle.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "client_idle.h" #include "client_internal.h" #include "idle.h" @@ -50,12 +51,9 @@ client_idle_notify(struct client *client) g_timer_start(client->last_activity); } -static void -client_idle_callback(gpointer data, gpointer user_data) +void +client_idle_add(struct client *client, unsigned flags) { - struct client *client = data; - unsigned flags = GPOINTER_TO_UINT(user_data); - if (client_is_expired(client)) return; @@ -67,6 +65,15 @@ client_idle_callback(gpointer data, gpointer user_data) } } +static void +client_idle_callback(gpointer data, gpointer user_data) +{ + struct client *client = data; + unsigned flags = GPOINTER_TO_UINT(user_data); + + client_idle_add(client, flags); +} + void client_manager_idle_add(unsigned flags) { assert(flags != 0); diff --git a/src/client_idle.h b/src/client_idle.h new file mode 100644 index 000000000..c56fd014c --- /dev/null +++ b/src/client_idle.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_IDLE_H +#define MPD_CLIENT_IDLE_H + +#include <stdbool.h> + +struct client; + +void +client_idle_add(struct client *client, unsigned flags); + +/** + * Adds the specified idle flags to all clients and immediately sends + * notifications to all waiting clients. + */ +void +client_manager_idle_add(unsigned flags); + +/** + * Checks whether the client has pending idle flags. If yes, they are + * sent immediately and "true" is returned". If no, it puts the + * client into waiting mode and returns false. + */ +bool +client_idle_wait(struct client *client, unsigned flags); + +#endif diff --git a/src/client_internal.h b/src/client_internal.h index 2b1b92433..ba97e4b8f 100644 --- a/src/client_internal.h +++ b/src/client_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,17 +21,25 @@ #define MPD_CLIENT_INTERNAL_H #include "client.h" +#include "client_message.h" #include "command.h" #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "client" +enum { + CLIENT_MAX_SUBSCRIPTIONS = 16, + CLIENT_MAX_MESSAGES = 64, +}; + struct deferred_buffer { size_t size; char data[sizeof(long)]; }; struct client { + struct player_control *player_control; + GIOChannel *channel; guint source_id; @@ -67,6 +75,28 @@ struct client { /** idle flags that the client wants to receive */ unsigned idle_subscriptions; + + /** + * A list of channel names this client is subscribed to. + */ + GSList *subscriptions; + + /** + * The number of subscriptions in #subscriptions. Used to + * limit the number of subscriptions. + */ + unsigned num_subscriptions; + + /** + * A list of messages this client has received in reverse + * order (latest first). + */ + GSList *messages; + + /** + * The number of messages in #messages. + */ + unsigned num_messages; }; extern unsigned int client_max_connections; diff --git a/src/client_list.c b/src/client_list.c index 5332ed65f..2c7f37aff 100644 --- a/src/client_list.c +++ b/src/client_list.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_message.c b/src/client_message.c new file mode 100644 index 000000000..b681b4e7f --- /dev/null +++ b/src/client_message.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "client_message.h" + +#include <assert.h> +#include <glib.h> + +G_GNUC_PURE +static bool +valid_channel_char(const char ch) +{ + return g_ascii_isalnum(ch) || + ch == '_' || ch == '-' || ch == '.' || ch == ':'; +} + +bool +client_message_valid_channel_name(const char *name) +{ + do { + if (!valid_channel_char(*name)) + return false; + } while (*++name != 0); + + return true; +} + +void +client_message_init_null(struct client_message *msg) +{ + assert(msg != NULL); + + msg->channel = NULL; + msg->message = NULL; +} + +void +client_message_init(struct client_message *msg, + const char *channel, const char *message) +{ + assert(msg != NULL); + + msg->channel = g_strdup(channel); + msg->message = g_strdup(message); +} + +void +client_message_copy(struct client_message *dest, + const struct client_message *src) +{ + assert(dest != NULL); + assert(src != NULL); + assert(client_message_defined(src)); + + client_message_init(dest, src->channel, src->message); +} + +struct client_message * +client_message_dup(const struct client_message *src) +{ + struct client_message *dest = g_slice_new(struct client_message); + client_message_copy(dest, src); + return dest; +} + +void +client_message_deinit(struct client_message *msg) +{ + assert(msg != NULL); + + g_free(msg->channel); + g_free(msg->message); +} + +void +client_message_free(struct client_message *msg) +{ + client_message_deinit(msg); + g_slice_free(struct client_message, msg); +} diff --git a/src/client_message.h b/src/client_message.h new file mode 100644 index 000000000..38c2e7615 --- /dev/null +++ b/src/client_message.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_MESSAGE_H +#define MPD_CLIENT_MESSAGE_H + +#include <assert.h> +#include <stdbool.h> +#include <stddef.h> +#include <glib.h> + +/** + * A client-to-client message. + */ +struct client_message { + char *channel; + + char *message; +}; + +G_GNUC_PURE +bool +client_message_valid_channel_name(const char *name); + +G_GNUC_PURE +static inline bool +client_message_defined(const struct client_message *msg) +{ + assert(msg != NULL); + assert((msg->channel == NULL) == (msg->message == NULL)); + + return msg->channel != NULL; +} + +void +client_message_init_null(struct client_message *msg); + +void +client_message_init(struct client_message *msg, + const char *channel, const char *message); + +void +client_message_copy(struct client_message *dest, + const struct client_message *src); + +G_GNUC_MALLOC +struct client_message * +client_message_dup(const struct client_message *src); + +void +client_message_deinit(struct client_message *msg); + +void +client_message_free(struct client_message *msg); + +#endif diff --git a/src/client_new.c b/src/client_new.c index e764a6550..cf28c43c5 100644 --- a/src/client_new.c +++ b/src/client_new.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,7 +21,7 @@ #include "client_internal.h" #include "fd_util.h" #include "fifo_buffer.h" -#include "socket_util.h" +#include "resolver.h" #include "permission.h" #include "glib_socket.h" @@ -43,12 +43,15 @@ static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n"; -void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid) +void +client_new(struct player_control *player_control, + int fd, const struct sockaddr *sa, size_t sa_length, int uid) { static unsigned int next_client_num; struct client *client; char *remote; + assert(player_control != NULL); assert(fd >= 0); #ifdef HAVE_LIBWRAP @@ -83,6 +86,7 @@ void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid) } client = g_new0(struct client, 1); + client->player_control = player_control; client->channel = g_io_channel_new_socket(fd); /* GLib is responsible for closing the file descriptor */ @@ -115,6 +119,10 @@ void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid) client->send_buf_used = 0; + client->subscriptions = NULL; + client->messages = NULL; + client->num_messages = 0; + (void)send(fd, GREETING, sizeof(GREETING) - 1, 0); client_list_add(client); diff --git a/src/client_process.c b/src/client_process.c index aeb75bb57..57a8a7824 100644 --- a/src/client_process.c +++ b/src/client_process.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_read.c b/src/client_read.c index 7a6bd3d5e..26ade264e 100644 --- a/src/client_read.c +++ b/src/client_read.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_subscribe.c b/src/client_subscribe.c new file mode 100644 index 000000000..c65a7ed31 --- /dev/null +++ b/src/client_subscribe.c @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "client_subscribe.h" +#include "client_internal.h" +#include "client_idle.h" +#include "idle.h" + +#include <string.h> + +G_GNUC_PURE +static GSList * +client_find_subscription(const struct client *client, const char *channel) +{ + for (GSList *i = client->subscriptions; i != NULL; i = g_slist_next(i)) + if (strcmp((const char *)i->data, channel) == 0) + return i; + + return NULL; +} + +enum client_subscribe_result +client_subscribe(struct client *client, const char *channel) +{ + assert(client != NULL); + assert(channel != NULL); + + if (!client_message_valid_channel_name(channel)) + return CLIENT_SUBSCRIBE_INVALID; + + if (client_find_subscription(client, channel) != NULL) + return CLIENT_SUBSCRIBE_ALREADY; + + if (client->num_subscriptions >= CLIENT_MAX_SUBSCRIPTIONS) + return CLIENT_SUBSCRIBE_FULL; + + client->subscriptions = g_slist_prepend(client->subscriptions, + g_strdup(channel)); + ++client->num_subscriptions; + + idle_add(IDLE_SUBSCRIPTION); + + return CLIENT_SUBSCRIBE_OK; +} + +bool +client_unsubscribe(struct client *client, const char *channel) +{ + GSList *i = client_find_subscription(client, channel); + if (i == NULL) + return false; + + assert(client->num_subscriptions > 0); + + client->subscriptions = g_slist_remove(client->subscriptions, i->data); + --client->num_subscriptions; + + idle_add(IDLE_SUBSCRIPTION); + + assert((client->num_subscriptions == 0) == + (client->subscriptions == NULL)); + + return true; +} + +void +client_unsubscribe_all(struct client *client) +{ + for (GSList *i = client->subscriptions; i != NULL; i = g_slist_next(i)) + g_free(i->data); + + g_slist_free(client->subscriptions); + client->subscriptions = NULL; + client->num_subscriptions = 0; +} + +bool +client_push_message(struct client *client, const struct client_message *msg) +{ + assert(client != NULL); + assert(msg != NULL); + assert(client_message_defined(msg)); + + if (client->num_messages >= CLIENT_MAX_MESSAGES || + client_find_subscription(client, msg->channel) == NULL) + return false; + + if (client->messages == NULL) + client_idle_add(client, IDLE_MESSAGE); + + client->messages = g_slist_prepend(client->messages, + client_message_dup(msg)); + ++client->num_messages; + + return true; +} + +GSList * +client_read_messages(struct client *client) +{ + GSList *messages = g_slist_reverse(client->messages); + + client->messages = NULL; + client->num_messages = 0; + + return messages; +} diff --git a/src/client_subscribe.h b/src/client_subscribe.h new file mode 100644 index 000000000..09f864417 --- /dev/null +++ b/src/client_subscribe.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SUBSCRIBE_H +#define MPD_CLIENT_SUBSCRIBE_H + +#include <stdbool.h> +#include <glib.h> + +struct client; +struct client_message; + +enum client_subscribe_result { + /** success */ + CLIENT_SUBSCRIBE_OK, + + /** invalid channel name */ + CLIENT_SUBSCRIBE_INVALID, + + /** already subscribed to this channel */ + CLIENT_SUBSCRIBE_ALREADY, + + /** too many subscriptions */ + CLIENT_SUBSCRIBE_FULL, +}; + +enum client_subscribe_result +client_subscribe(struct client *client, const char *channel); + +bool +client_unsubscribe(struct client *client, const char *channel); + +void +client_unsubscribe_all(struct client *client); + +bool +client_push_message(struct client *client, const struct client_message *msg); + +G_GNUC_MALLOC +GSList * +client_read_messages(struct client *client); + +#endif diff --git a/src/client_write.c b/src/client_write.c index 543cdbb6c..78cfca8a1 100644 --- a/src/client_write.c +++ b/src/client_write.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/cmdline.c b/src/cmdline.c index d986c8eb8..8ab317730 100644 --- a/src/cmdline.c +++ b/src/cmdline.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -79,7 +79,7 @@ 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-2010 Max Kellermann <max@duempel.org>\n" + "Copyright (C) 2008-2011 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" diff --git a/src/cmdline.h b/src/cmdline.h index b7af63c5a..68f625a6c 100644 --- a/src/cmdline.h +++ b/src/cmdline.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/command.c b/src/command.c index 64f161805..7d4403399 100644 --- a/src/command.c +++ b/src/command.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,17 +19,19 @@ #include "config.h" #include "command.h" +#include "protocol/argparser.h" +#include "protocol/result.h" #include "player_control.h" #include "playlist.h" #include "playlist_print.h" #include "playlist_save.h" #include "playlist_queue.h" +#include "playlist_error.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" #include "update.h" #include "volume.h" @@ -42,18 +44,28 @@ #include "output_print.h" #include "locate.h" #include "dbUtils.h" +#include "db_error.h" +#include "db_print.h" +#include "db_selection.h" +#include "db_lock.h" #include "tag.h" #include "client.h" +#include "client_idle.h" +#include "client_internal.h" +#include "client_subscribe.h" +#include "client_file.h" #include "tag_print.h" #include "path.h" #include "replay_gain_config.h" #include "idle.h" +#include "mapper.h" +#include "song.h" +#include "song_print.h" #ifdef ENABLE_SQLITE #include "sticker.h" #include "sticker_print.h" #include "song_sticker.h" -#include "song_print.h" #endif #include <assert.h> @@ -98,221 +110,6 @@ struct command { enum command_return (*handler)(struct client *client, int argc, char **argv); }; -/* this should really be "need a non-negative integer": */ -static const char need_positive[] = "need a positive integer"; /* no-op */ -static const char need_range[] = "need a range"; - -/* FIXME: redundant error messages */ -static const char check_integer[] = "\"%s\" is not a integer"; -static const char need_integer[] = "need an integer"; - -static const char *current_command; -static int command_list_num; - -void command_success(struct client *client) -{ - client_puts(client, "OK\n"); -} - -static void command_error_v(struct client *client, enum ack error, - const char *fmt, va_list args) -{ - assert(client != NULL); - assert(current_command != NULL); - - client_printf(client, "ACK [%i@%i] {%s} ", - (int)error, command_list_num, current_command); - client_vprintf(client, fmt, args); - client_puts(client, "\n"); - - current_command = NULL; -} - -G_GNUC_PRINTF(3, 4) static void command_error(struct client *client, enum ack error, - const char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - command_error_v(client, error, fmt, args); - va_end(args); -} - -static bool G_GNUC_PRINTF(4, 5) -check_uint32(struct client *client, uint32_t *dst, - const char *s, const char *fmt, ...) -{ - char *test; - - *dst = strtoul(s, &test, 10); - if (*test != '\0') { - va_list args; - va_start(args, fmt); - command_error_v(client, ACK_ERROR_ARG, fmt, args); - va_end(args); - return false; - } - return true; -} - -static bool G_GNUC_PRINTF(4, 5) -check_int(struct client *client, int *value_r, - const char *s, const char *fmt, ...) -{ - char *test; - long value; - - value = strtol(s, &test, 10); - if (*test != '\0') { - va_list args; - va_start(args, fmt); - command_error_v(client, ACK_ERROR_ARG, fmt, args); - va_end(args); - return false; - } - -#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; - } -#endif - - *value_r = (int)value; - return true; -} - -static bool G_GNUC_PRINTF(5, 6) -check_range(struct client *client, unsigned *value_r1, unsigned *value_r2, - const char *s, const char *fmt, ...) -{ - char *test, *test2; - long value; - - value = strtol(s, &test, 10); - if (*test != '\0' && *test != ':') { - va_list args; - va_start(args, fmt); - command_error_v(client, ACK_ERROR_ARG, fmt, args); - va_end(args); - return false; - } - - if (value == -1 && *test == 0) { - /* compatibility with older MPD versions: specifying - "-1" makes MPD display the whole list */ - *value_r1 = 0; - *value_r2 = G_MAXUINT; - return true; - } - - if (value < 0) { - command_error(client, ACK_ERROR_ARG, - "Number is negative: %s", s); - return false; - } - -#if G_MAXLONG > G_MAXUINT - if (value > G_MAXUINT) { - command_error(client, ACK_ERROR_ARG, - "Number too large: %s", s); - return false; - } -#endif - - *value_r1 = (unsigned)value; - - if (*test == ':') { - value = strtol(++test, &test2, 10); - if (*test2 != '\0') { - va_list args; - va_start(args, fmt); - command_error_v(client, ACK_ERROR_ARG, fmt, args); - va_end(args); - 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 G_MAXLONG > G_MAXUINT - if (value > G_MAXUINT) { - command_error(client, ACK_ERROR_ARG, - "Number too large: %s", s); - return false; - } -#endif - *value_r2 = (unsigned)value; - } else { - *value_r2 = (unsigned)value + 1; - } - - return true; -} - -static bool -check_unsigned(struct client *client, unsigned *value_r, const char *s) -{ - unsigned long value; - char *endptr; - - value = strtoul(s, &endptr, 10); - if (*endptr != 0) { - command_error(client, ACK_ERROR_ARG, - "Integer expected: %s", s); - return false; - } - - if (value > G_MAXUINT) { - command_error(client, ACK_ERROR_ARG, - "Number too large: %s", s); - return false; - } - - *value_r = (unsigned)value; - return true; -} - -static bool -check_bool(struct client *client, bool *value_r, const char *s) -{ - long value; - char *endptr; - - value = strtol(s, &endptr, 10); - if (*endptr != 0 || (value != 0 && value != 1)) { - command_error(client, ACK_ERROR_ARG, - "Boolean (0/1) expected: %s", s); - return false; - } - - *value_r = !!value; - 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) @@ -322,11 +119,12 @@ print_playlist_result(struct client *client, return COMMAND_RETURN_OK; case PLAYLIST_RESULT_ERRNO: - command_error(client, ACK_ERROR_SYSTEM, "%s", strerror(errno)); + command_error(client, ACK_ERROR_SYSTEM, "%s", + g_strerror(errno)); return COMMAND_RETURN_ERROR; case PLAYLIST_RESULT_DENIED: - command_error(client, ACK_ERROR_NO_EXIST, "Access denied"); + command_error(client, ACK_ERROR_PERMISSION, "Access denied"); return COMMAND_RETURN_ERROR; case PLAYLIST_RESULT_NO_SUCH_SONG: @@ -372,6 +170,50 @@ print_playlist_result(struct client *client, return COMMAND_RETURN_ERROR; } +/** + * Send the GError to the client and free the GError. + */ +static enum command_return +print_error(struct client *client, GError *error) +{ + assert(client != NULL); + assert(error != NULL); + + g_warning("%s", error->message); + + if (error->domain == playlist_quark()) { + enum playlist_result result = error->code; + g_error_free(error); + return print_playlist_result(client, result); + } else if (error->domain == ack_quark()) { + command_error(client, error->code, "%s", error->message); + g_error_free(error); + return COMMAND_RETURN_ERROR; + } else if (error->domain == db_quark()) { + switch ((enum db_error)error->code) { + case DB_DISABLED: + command_error(client, ACK_ERROR_NO_EXIST, "%s", + error->message); + g_error_free(error); + return COMMAND_RETURN_ERROR; + + case DB_NOT_FOUND: + g_error_free(error); + command_error(client, ACK_ERROR_NO_EXIST, "Not found"); + return COMMAND_RETURN_ERROR; + } + } else if (error->domain == g_file_error_quark()) { + command_error(client, ACK_ERROR_SYSTEM, "%s", + g_strerror(error->code)); + g_error_free(error); + return COMMAND_RETURN_ERROR; + } + + g_error_free(error); + command_error(client, ACK_ERROR_UNKNOWN, "error"); + return COMMAND_RETURN_ERROR; +} + static void print_spl_list(struct client *client, GPtrArray *list) { @@ -404,7 +246,7 @@ static enum command_return handle_urlhandlers(struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - if (client_get_uid(client) > 0) + if (client_is_local(client)) client_puts(client, "handler: file://\n"); print_supported_uri_schemes(client); return COMMAND_RETURN_OK; @@ -432,9 +274,9 @@ handle_play(struct client *client, int argc, char *argv[]) int song = -1; enum playlist_result result; - if (argc == 2 && !check_int(client, &song, argv[1], need_positive)) + if (argc == 2 && !check_int(client, &song, argv[1])) return COMMAND_RETURN_ERROR; - result = playlist_play(&g_playlist, song); + result = playlist_play(&g_playlist, client->player_control, song); return print_playlist_result(client, result); } @@ -444,10 +286,10 @@ handle_playid(struct client *client, int argc, char *argv[]) int id = -1; enum playlist_result result; - if (argc == 2 && !check_int(client, &id, argv[1], need_positive)) + if (argc == 2 && !check_int(client, &id, argv[1])) return COMMAND_RETURN_ERROR; - result = playlist_play_id(&g_playlist, id); + result = playlist_play_id(&g_playlist, client->player_control, id); return print_playlist_result(client, result); } @@ -455,7 +297,7 @@ static enum command_return handle_stop(G_GNUC_UNUSED struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - playlist_stop(&g_playlist); + playlist_stop(&g_playlist, client->player_control); return COMMAND_RETURN_OK; } @@ -476,9 +318,9 @@ handle_pause(struct client *client, if (!check_bool(client, &pause_flag, argv[1])) return COMMAND_RETURN_ERROR; - pc_set_pause(pause_flag); + pc_set_pause(client->player_control, pause_flag); } else - pc_pause(); + pc_pause(client->player_control); return COMMAND_RETURN_OK; } @@ -493,7 +335,7 @@ handle_status(struct client *client, char *error; int song; - pc_get_status(&player_status); + pc_get_status(client->player_control, &player_status); switch (player_status.state) { case PLAYER_STATE_STOP: @@ -526,9 +368,9 @@ handle_status(struct client *client, 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(), + (int)(pc_get_cross_fade(client->player_control) + 0.5), + pc_get_mixramp_db(client->player_control), + pc_get_mixramp_delay(client->player_control), state); song = playlist_get_current_song(&g_playlist); @@ -561,7 +403,7 @@ handle_status(struct client *client, updateJobId); } - error = pc_get_error_message(); + error = pc_get_error_message(client->player_control); if (error != NULL) { client_printf(client, COMMAND_STATUS_ERROR ": %s\n", @@ -601,13 +443,16 @@ handle_add(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) enum playlist_result result; if (strncmp(uri, "file:///", 8) == 0) { -#ifdef WIN32 - result = PLAYLIST_RESULT_DENIED; -#else + const char *path = uri + 7; + + GError *error = NULL; + if (!client_allow_file(client, path, &error)) + return print_error(client, error); + result = playlist_append_file(&g_playlist, - uri + 7, client_get_uid(client), + client->player_control, + path, NULL); -#endif return print_playlist_result(client, result); } @@ -618,18 +463,16 @@ handle_add(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - result = playlist_append_uri(&g_playlist, uri, NULL); + result = playlist_append_uri(&g_playlist, + client->player_control, + uri, NULL); return print_playlist_result(client, result); } - result = addAllIn(uri); - if (result == (enum playlist_result)-1) { - command_error(client, ACK_ERROR_NO_EXIST, - "directory or file not found"); - return COMMAND_RETURN_ERROR; - } - - return print_playlist_result(client, result); + GError *error = NULL; + return addAllIn(client->player_control, uri, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); } static enum command_return @@ -640,13 +483,16 @@ handle_addid(struct client *client, int argc, char *argv[]) enum playlist_result result; if (strncmp(uri, "file:///", 8) == 0) { -#ifdef WIN32 - result = PLAYLIST_RESULT_DENIED; -#else - result = playlist_append_file(&g_playlist, uri + 7, - client_get_uid(client), + const char *path = uri + 7; + + GError *error = NULL; + if (!client_allow_file(client, path, &error)) + return print_error(client, error); + + result = playlist_append_file(&g_playlist, + client->player_control, + path, &added_id); -#endif } else { if (uri_has_scheme(uri) && !uri_supported_scheme(uri)) { command_error(client, ACK_ERROR_NO_EXIST, @@ -654,21 +500,25 @@ handle_addid(struct client *client, int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - result = playlist_append_uri(&g_playlist, uri, &added_id); + result = playlist_append_uri(&g_playlist, + client->player_control, + uri, &added_id); } if (result != PLAYLIST_RESULT_SUCCESS) return print_playlist_result(client, result); if (argc == 3) { - int to; - if (!check_int(client, &to, argv[2], check_integer, argv[2])) + unsigned to; + if (!check_unsigned(client, &to, argv[2])) return COMMAND_RETURN_ERROR; - result = playlist_move_id(&g_playlist, added_id, to); + result = playlist_move_id(&g_playlist, client->player_control, + added_id, to); if (result != PLAYLIST_RESULT_SUCCESS) { enum command_return ret = print_playlist_result(client, result); - playlist_delete_id(&g_playlist, added_id); + playlist_delete_id(&g_playlist, client->player_control, + added_id); return ret; } } @@ -683,23 +533,24 @@ handle_delete(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) unsigned start, end; enum playlist_result result; - if (!check_range(client, &start, &end, argv[1], need_range)) + if (!check_range(client, &start, &end, argv[1])) return COMMAND_RETURN_ERROR; - result = playlist_delete_range(&g_playlist, start, end); + result = playlist_delete_range(&g_playlist, client->player_control, + start, end); return print_playlist_result(client, result); } static enum command_return handle_deleteid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - int id; + unsigned id; enum playlist_result result; - if (!check_int(client, &id, argv[1], need_positive)) + if (!check_unsigned(client, &id, argv[1])) return COMMAND_RETURN_ERROR; - result = playlist_delete_id(&g_playlist, id); + result = playlist_delete_id(&g_playlist, client->player_control, id); return print_playlist_result(client, result); } @@ -716,11 +567,10 @@ handle_shuffle(G_GNUC_UNUSED struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { unsigned start = 0, end = queue_length(&g_playlist.queue); - if (argc == 2 && !check_range(client, &start, &end, - argv[1], need_range)) + if (argc == 2 && !check_range(client, &start, &end, argv[1])) return COMMAND_RETURN_ERROR; - playlist_shuffle(&g_playlist, start, end); + playlist_shuffle(&g_playlist, client->player_control, start, end); return COMMAND_RETURN_OK; } @@ -728,7 +578,7 @@ static enum command_return handle_clear(G_GNUC_UNUSED struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - playlist_clear(&g_playlist); + playlist_clear(&g_playlist, client->player_control); return COMMAND_RETURN_OK; } @@ -743,16 +593,40 @@ handle_save(struct client *client, } static enum command_return -handle_load(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +handle_load(struct client *client, int argc, char *argv[]) { + unsigned start_index, end_index; + + if (argc < 3) { + start_index = 0; + end_index = G_MAXUINT; + } else if (!check_range(client, &start_index, &end_index, argv[2])) + return COMMAND_RETURN_ERROR; + enum playlist_result result; - result = playlist_open_into_queue(argv[1], &g_playlist); + result = playlist_open_into_queue(argv[1], + start_index, end_index, + &g_playlist, + client->player_control, true); if (result != PLAYLIST_RESULT_NO_SUCH_LIST) return print_playlist_result(client, result); - result = playlist_load_spl(&g_playlist, argv[1]); - return print_playlist_result(client, result); + GError *error = NULL; + if (playlist_load_spl(&g_playlist, client->player_control, + argv[1], start_index, end_index, + &error)) + return COMMAND_RETURN_OK; + + if (error->domain == playlist_quark() && + error->code == PLAYLIST_RESULT_BAD_NAME) + /* the message for BAD_NAME is confusing when the + client wants to load a playlist file from the music + directory; patch the GError object to show "no such + playlist" instead */ + error->code = PLAYLIST_RESULT_NO_SUCH_LIST; + + return print_error(client, error); } static enum command_return @@ -761,15 +635,10 @@ 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); - if (!ret) { - command_error(client, ACK_ERROR_NO_EXIST, "No such playlist"); - return COMMAND_RETURN_ERROR; - } - - return COMMAND_RETURN_OK; + GError *error = NULL; + return spl_print(client, argv[1], false, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); } static enum command_return @@ -779,22 +648,16 @@ handle_listplaylistinfo(struct client *client, if (playlist_file_print(client, argv[1], true)) return COMMAND_RETURN_OK; - bool ret; - - ret = spl_print(client, argv[1], true); - if (!ret) { - command_error(client, ACK_ERROR_NO_EXIST, "No such playlist"); - return COMMAND_RETURN_ERROR; - } - - return COMMAND_RETURN_OK; + GError *error = NULL; + return spl_print(client, argv[1], true, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); } static enum command_return handle_lsinfo(struct client *client, int argc, char *argv[]) { const char *uri; - const struct directory *directory; if (argc == 2) uri = argv[1]; @@ -802,17 +665,35 @@ handle_lsinfo(struct client *client, int argc, char *argv[]) /* default is root directory */ uri = ""; - directory = db_get_directory(uri); - if (directory == NULL) { - command_error(client, ACK_ERROR_NO_EXIST, - "directory not found"); - return COMMAND_RETURN_ERROR; + if (strncmp(uri, "file:///", 8) == 0) { + /* print information about an arbitrary local file */ + const char *path = uri + 7; + + GError *error = NULL; + if (!client_allow_file(client, path, &error)) + return print_error(client, error); + + struct song *song = song_file_load(path, NULL); + if (song == NULL) { + command_error(client, ACK_ERROR_NO_EXIST, + "No such file"); + return COMMAND_RETURN_ERROR; + } + + song_print_info(client, song); + song_free(song); + return COMMAND_RETURN_OK; } - directory_print(client, directory); + struct db_selection selection; + db_selection_init(&selection, uri, false); + + GError *error = NULL; + if (!db_selection_print(client, &selection, true, &error)) + return print_error(client, error); if (isRootDirectory(uri)) { - GPtrArray *list = spl_list(); + GPtrArray *list = spl_list(NULL); if (list != NULL) { print_spl_list(client, list); spl_list_free(list); @@ -825,19 +706,19 @@ handle_lsinfo(struct client *client, int argc, char *argv[]) static enum command_return handle_rm(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - enum playlist_result result; - - result = spl_delete(argv[1]); - return print_playlist_result(client, result); + GError *error = NULL; + return spl_delete(argv[1], &error) + ? COMMAND_RETURN_OK + : print_error(client, error); } static enum command_return handle_rename(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - enum playlist_result result; - - result = spl_rename(argv[1], argv[2]); - return print_playlist_result(client, result); + GError *error = NULL; + return spl_rename(argv[1], argv[2], &error) + ? COMMAND_RETURN_OK + : print_error(client, error); } static enum command_return @@ -845,7 +726,7 @@ handle_plchanges(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { uint32_t version; - if (!check_uint32(client, &version, argv[1], need_positive)) + if (!check_uint32(client, &version, argv[1])) return COMMAND_RETURN_ERROR; playlist_print_changes_info(client, &g_playlist, version); @@ -857,7 +738,7 @@ handle_plchangesposid(struct client *client, G_GNUC_UNUSED int argc, char *argv[ { uint32_t version; - if (!check_uint32(client, &version, argv[1], need_positive)) + if (!check_uint32(client, &version, argv[1])) return COMMAND_RETURN_ERROR; playlist_print_changes_position(client, &g_playlist, version); @@ -870,8 +751,7 @@ handle_playlistinfo(struct client *client, int argc, char *argv[]) unsigned start = 0, end = G_MAXUINT; bool ret; - if (argc == 2 && !check_range(client, &start, &end, - argv[1], need_range)) + if (argc == 2 && !check_range(client, &start, &end, argv[1])) return COMMAND_RETURN_ERROR; ret = playlist_print_info(client, &g_playlist, start, end); @@ -885,12 +765,11 @@ handle_playlistinfo(struct client *client, int argc, char *argv[]) static enum command_return handle_playlistid(struct client *client, int argc, char *argv[]) { - int id = -1; - - if (argc == 2 && !check_int(client, &id, argv[1], need_positive)) - return COMMAND_RETURN_ERROR; + if (argc >= 2) { + unsigned id; + if (!check_unsigned(client, &id, argv[1])) + return COMMAND_RETURN_ERROR; - if (id >= 0) { bool ret = playlist_print_id(client, &g_playlist, id); if (!ret) return print_playlist_result(client, @@ -905,7 +784,6 @@ handle_playlistid(struct client *client, int argc, char *argv[]) static enum command_return handle_find(struct client *client, int argc, char *argv[]) { - int ret; struct locate_item_list *list = locate_item_list_parse(argv + 1, argc - 1); @@ -917,10 +795,10 @@ handle_find(struct client *client, int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - ret = findSongsIn(client, NULL, list); - if (ret == -1) - command_error(client, ACK_ERROR_NO_EXIST, - "directory or file not found"); + GError *error = NULL; + enum command_return ret = findSongsIn(client, "", list, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); locate_item_list_free(list); @@ -930,7 +808,6 @@ 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) { @@ -941,10 +818,11 @@ handle_findadd(struct client *client, int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - ret = findAddIn(client, NULL, list); - if (ret == -1) - command_error(client, ACK_ERROR_NO_EXIST, - "directory or file not found"); + GError *error = NULL; + enum command_return ret = + findAddIn(client->player_control, "", list, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); locate_item_list_free(list); @@ -954,7 +832,6 @@ handle_findadd(struct client *client, int argc, char *argv[]) static enum command_return handle_search(struct client *client, int argc, char *argv[]) { - int ret; struct locate_item_list *list = locate_item_list_parse(argv + 1, argc - 1); @@ -966,10 +843,10 @@ handle_search(struct client *client, int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - ret = searchForSongsIn(client, NULL, list); - if (ret == -1) - command_error(client, ACK_ERROR_NO_EXIST, - "directory or file not found"); + GError *error = NULL; + enum command_return ret = searchForSongsIn(client, "", list, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); locate_item_list_free(list); @@ -979,7 +856,6 @@ handle_search(struct client *client, int argc, char *argv[]) static enum command_return handle_count(struct client *client, int argc, char *argv[]) { - int ret; struct locate_item_list *list = locate_item_list_parse(argv + 1, argc - 1); @@ -991,10 +867,11 @@ handle_count(struct client *client, int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - ret = searchStatsForSongsIn(client, NULL, list); - if (ret == -1) - command_error(client, ACK_ERROR_NO_EXIST, - "directory or file not found"); + GError *error = NULL; + enum command_return ret = + searchStatsForSongsIn(client, "", list, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); locate_item_list_free(list); @@ -1047,30 +924,32 @@ static enum command_return handle_playlistdelete(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { char *playlist = argv[1]; - int from; - enum playlist_result result; + unsigned from; - if (!check_int(client, &from, argv[2], check_integer, argv[2])) + if (!check_unsigned(client, &from, argv[2])) return COMMAND_RETURN_ERROR; - result = spl_remove_index(playlist, from); - return print_playlist_result(client, result); + GError *error = NULL; + return spl_remove_index(playlist, from, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); } static enum command_return handle_playlistmove(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { char *playlist = argv[1]; - int from, to; - enum playlist_result result; + unsigned from, to; - if (!check_int(client, &from, argv[2], check_integer, argv[2])) + if (!check_unsigned(client, &from, argv[2])) return COMMAND_RETURN_ERROR; - if (!check_int(client, &to, argv[3], check_integer, argv[3])) + if (!check_unsigned(client, &to, argv[3])) return COMMAND_RETURN_ERROR; - result = spl_move_index(playlist, from, to); - return print_playlist_result(client, result); + GError *error = NULL; + return spl_move_index(playlist, from, to, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); } static enum command_return @@ -1141,7 +1020,7 @@ handle_next(G_GNUC_UNUSED struct client *client, int single = g_playlist.queue.single; g_playlist.queue.single = false; - playlist_next(&g_playlist); + playlist_next(&g_playlist, client->player_control); g_playlist.queue.single = single; return COMMAND_RETURN_OK; @@ -1151,37 +1030,96 @@ static enum command_return handle_previous(G_GNUC_UNUSED struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - playlist_previous(&g_playlist); + playlist_previous(&g_playlist, client->player_control); + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_prio(struct client *client, int argc, char *argv[]) +{ + unsigned priority; + + if (!check_unsigned(client, &priority, argv[1])) + return COMMAND_RETURN_ERROR; + + if (priority > 0xff) { + command_error(client, ACK_ERROR_ARG, + "Priority out of range: %s", argv[1]); + return COMMAND_RETURN_ERROR; + } + + for (int i = 2; i < argc; ++i) { + unsigned start_position, end_position; + if (!check_range(client, &start_position, &end_position, + argv[i])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + playlist_set_priority(&g_playlist, + client->player_control, + start_position, end_position, + priority); + if (result != PLAYLIST_RESULT_SUCCESS) + return print_playlist_result(client, result); + } + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_prioid(struct client *client, int argc, char *argv[]) +{ + unsigned priority; + + if (!check_unsigned(client, &priority, argv[1])) + return COMMAND_RETURN_ERROR; + + if (priority > 0xff) { + command_error(client, ACK_ERROR_ARG, + "Priority out of range: %s", argv[1]); + return COMMAND_RETURN_ERROR; + } + + for (int i = 2; i < argc; ++i) { + unsigned song_id; + if (!check_unsigned(client, &song_id, argv[i])) + return COMMAND_RETURN_ERROR; + + enum playlist_result result = + playlist_set_priority_id(&g_playlist, + client->player_control, + song_id, priority); + if (result != PLAYLIST_RESULT_SUCCESS) + return print_playlist_result(client, result); + } + return COMMAND_RETURN_OK; } static enum command_return handle_listall(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - char *directory = NULL; - int ret; + const char *directory = ""; if (argc == 2) directory = argv[1]; - ret = printAllIn(client, directory); - if (ret == -1) - command_error(client, ACK_ERROR_NO_EXIST, - "directory or file not found"); - - return ret; + GError *error = NULL; + return printAllIn(client, directory, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); } static enum command_return handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - int level; + unsigned level; bool success; - if (!check_int(client, &level, argv[1], need_integer)) + if (!check_unsigned(client, &level, argv[1])) return COMMAND_RETURN_ERROR; - if (level < 0 || level > 100) { + if (level > 100) { command_error(client, ACK_ERROR_ARG, "Invalid volume value"); return COMMAND_RETURN_ERROR; } @@ -1199,52 +1137,31 @@ handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) static enum command_return handle_repeat(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - int status; - - if (!check_int(client, &status, argv[1], need_integer)) - return COMMAND_RETURN_ERROR; - - if (status != 0 && status != 1) { - command_error(client, ACK_ERROR_ARG, - "\"%i\" is not 0 or 1", status); + bool status; + if (!check_bool(client, &status, argv[1])) return COMMAND_RETURN_ERROR; - } - playlist_set_repeat(&g_playlist, status); + playlist_set_repeat(&g_playlist, client->player_control, status); return COMMAND_RETURN_OK; } static enum command_return handle_single(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - int status; - - if (!check_int(client, &status, argv[1], need_integer)) + bool status; + if (!check_bool(client, &status, argv[1])) return COMMAND_RETURN_ERROR; - if (status != 0 && status != 1) { - command_error(client, ACK_ERROR_ARG, - "\"%i\" is not 0 or 1", status); - return COMMAND_RETURN_ERROR; - } - - playlist_set_single(&g_playlist, status); + playlist_set_single(&g_playlist, client->player_control, status); return COMMAND_RETURN_OK; } static enum command_return handle_consume(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - int status; - - if (!check_int(client, &status, argv[1], need_integer)) - return COMMAND_RETURN_ERROR; - - if (status != 0 && status != 1) { - command_error(client, ACK_ERROR_ARG, - "\"%i\" is not 0 or 1", status); + bool status; + if (!check_bool(client, &status, argv[1])) return COMMAND_RETURN_ERROR; - } playlist_set_consume(&g_playlist, status); return COMMAND_RETURN_OK; @@ -1253,18 +1170,11 @@ handle_consume(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) static enum command_return handle_random(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - int status; - - if (!check_int(client, &status, argv[1], need_integer)) + bool status; + if (!check_bool(client, &status, argv[1])) return COMMAND_RETURN_ERROR; - if (status != 0 && status != 1) { - command_error(client, ACK_ERROR_ARG, - "\"%i\" is not 0 or 1", status); - return COMMAND_RETURN_ERROR; - } - - playlist_set_random(&g_playlist, status); + playlist_set_random(&g_playlist, client->player_control, status); return COMMAND_RETURN_OK; } @@ -1279,7 +1189,7 @@ static enum command_return handle_clearerror(G_GNUC_UNUSED struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - pc_clear_error(); + pc_clear_error(client->player_control); return COMMAND_RETURN_OK; } @@ -1288,7 +1198,6 @@ handle_list(struct client *client, int argc, char *argv[]) { struct locate_item_list *conditionals; int tagType = locate_parse_type(argv[1]); - int ret; if (tagType < 0) { command_error(client, ACK_ERROR_ARG, "\"%s\" is not known", argv[1]); @@ -1325,14 +1234,14 @@ handle_list(struct client *client, int argc, char *argv[]) } } - ret = listAllUniqueTags(client, tagType, conditionals); + GError *error = NULL; + enum command_return ret = + listAllUniqueTags(client, tagType, conditionals, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); locate_item_list_free(conditionals); - if (ret == -1) - command_error(client, ACK_ERROR_NO_EXIST, - "directory or file not found"); - return ret; } @@ -1343,102 +1252,120 @@ handle_move(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) int to; enum playlist_result result; - if (!check_range(client, &start, &end, - argv[1], need_range)) + if (!check_range(client, &start, &end, argv[1])) return COMMAND_RETURN_ERROR; - if (!check_int(client, &to, argv[2], check_integer, argv[2])) + if (!check_int(client, &to, argv[2])) return COMMAND_RETURN_ERROR; - result = playlist_move_range(&g_playlist, start, end, to); + result = playlist_move_range(&g_playlist, client->player_control, + start, end, to); return print_playlist_result(client, result); } static enum command_return handle_moveid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - int id, to; + unsigned id; + int to; enum playlist_result result; - if (!check_int(client, &id, argv[1], check_integer, argv[1])) + if (!check_unsigned(client, &id, argv[1])) return COMMAND_RETURN_ERROR; - if (!check_int(client, &to, argv[2], check_integer, argv[2])) + if (!check_int(client, &to, argv[2])) return COMMAND_RETURN_ERROR; - result = playlist_move_id(&g_playlist, id, to); + result = playlist_move_id(&g_playlist, client->player_control, + id, to); return print_playlist_result(client, result); } static enum command_return handle_swap(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - int song1, song2; + unsigned song1, song2; enum playlist_result result; - if (!check_int(client, &song1, argv[1], check_integer, argv[1])) + if (!check_unsigned(client, &song1, argv[1])) return COMMAND_RETURN_ERROR; - if (!check_int(client, &song2, argv[2], check_integer, argv[2])) + if (!check_unsigned(client, &song2, argv[2])) return COMMAND_RETURN_ERROR; - result = playlist_swap_songs(&g_playlist, song1, song2); + result = playlist_swap_songs(&g_playlist, client->player_control, + song1, song2); return print_playlist_result(client, result); } static enum command_return handle_swapid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - int id1, id2; + unsigned id1, id2; enum playlist_result result; - if (!check_int(client, &id1, argv[1], check_integer, argv[1])) + if (!check_unsigned(client, &id1, argv[1])) return COMMAND_RETURN_ERROR; - if (!check_int(client, &id2, argv[2], check_integer, argv[2])) + if (!check_unsigned(client, &id2, argv[2])) return COMMAND_RETURN_ERROR; - result = playlist_swap_songs_id(&g_playlist, id1, id2); + result = playlist_swap_songs_id(&g_playlist, client->player_control, + id1, id2); return print_playlist_result(client, result); } static enum command_return handle_seek(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - int song, seek_time; + unsigned song, seek_time; enum playlist_result result; - if (!check_int(client, &song, argv[1], check_integer, argv[1])) + if (!check_unsigned(client, &song, argv[1])) return COMMAND_RETURN_ERROR; - if (!check_int(client, &seek_time, argv[2], check_integer, argv[2])) + if (!check_unsigned(client, &seek_time, argv[2])) return COMMAND_RETURN_ERROR; - result = playlist_seek_song(&g_playlist, song, seek_time); + result = playlist_seek_song(&g_playlist, client->player_control, + song, seek_time); return print_playlist_result(client, result); } static enum command_return handle_seekid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - int id, seek_time; + unsigned id, seek_time; enum playlist_result result; - if (!check_int(client, &id, argv[1], check_integer, argv[1])) + if (!check_unsigned(client, &id, argv[1])) return COMMAND_RETURN_ERROR; - if (!check_int(client, &seek_time, argv[2], check_integer, argv[2])) + if (!check_unsigned(client, &seek_time, argv[2])) + return COMMAND_RETURN_ERROR; + + result = playlist_seek_song_id(&g_playlist, client->player_control, + id, seek_time); + return print_playlist_result(client, result); +} + +static enum command_return +handle_seekcur(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + const char *p = argv[1]; + bool relative = *p == '+' || *p == '-'; + int seek_time; + if (!check_int(client, &seek_time, p)) return COMMAND_RETURN_ERROR; - result = playlist_seek_song_id(&g_playlist, id, seek_time); + enum playlist_result result = + playlist_seek_current(&g_playlist, client->player_control, + seek_time, relative); return print_playlist_result(client, result); } static enum command_return handle_listallinfo(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - char *directory = NULL; - int ret; + const char *directory = ""; if (argc == 2) directory = argv[1]; - ret = printInfoForAllIn(client, directory); - if (ret == -1) - command_error(client, ACK_ERROR_NO_EXIST, - "directory or file not found"); - - return ret; + GError *error = NULL; + return printInfoForAllIn(client, directory, &error) + ? COMMAND_RETURN_OK + : print_error(client, error); } static enum command_return @@ -1470,7 +1397,7 @@ handle_crossfade(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (!check_unsigned(client, &xfade_time, argv[1])) return COMMAND_RETURN_ERROR; - pc_set_cross_fade(xfade_time); + pc_set_cross_fade(client->player_control, xfade_time); return COMMAND_RETURN_OK; } @@ -1482,7 +1409,7 @@ handle_mixrampdb(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (!check_float(client, &db, argv[1])) return COMMAND_RETURN_ERROR; - pc_set_mixramp_db(db); + pc_set_mixramp_db(client->player_control, db); return COMMAND_RETURN_OK; } @@ -1494,7 +1421,7 @@ handle_mixrampdelay(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (!check_float(client, &delay_secs, argv[1])) return COMMAND_RETURN_ERROR; - pc_set_mixramp_delay(delay_secs); + pc_set_mixramp_delay(client->player_control, delay_secs); return COMMAND_RETURN_OK; } @@ -1556,12 +1483,29 @@ handle_not_commands(struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]); static enum command_return -handle_playlistclear(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +handle_config(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - enum playlist_result result; + if (!client_is_local(client)) { + command_error(client, ACK_ERROR_PERMISSION, + "Command only permitted to local clients"); + return COMMAND_RETURN_ERROR; + } - result = spl_clear(argv[1]); - return print_playlist_result(client, result); + const char *path = mapper_get_music_directory(); + if (path != NULL) + client_printf(client, "music_directory: %s\n", path); + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_playlistclear(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + GError *error = NULL; + return spl_clear(argv[1], &error) + ? COMMAND_RETURN_OK + : print_error(client, error); } static enum command_return @@ -1569,8 +1513,9 @@ handle_playlistadd(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { char *playlist = argv[1]; char *uri = argv[2]; - enum playlist_result result; + bool success; + GError *error = NULL; if (uri_has_scheme(uri)) { if (!uri_supported_scheme(uri)) { command_error(client, ACK_ERROR_NO_EXIST, @@ -1578,29 +1523,27 @@ handle_playlistadd(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - result = spl_append_uri(uri, playlist); + success = spl_append_uri(argv[1], playlist, &error); } else - result = addAllInToStoredPlaylist(uri, playlist); + success = addAllInToStoredPlaylist(uri, playlist, &error); - if (result == (enum playlist_result)-1) { + if (!success && error == NULL) { command_error(client, ACK_ERROR_NO_EXIST, "directory or file not found"); return COMMAND_RETURN_ERROR; } - return print_playlist_result(client, result); + return success ? COMMAND_RETURN_OK : print_error(client, error); } static enum command_return handle_listplaylists(struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - GPtrArray *list = spl_list(); - if (list == NULL) { - command_error(client, ACK_ERROR_SYSTEM, - "failed to get list of stored playlists"); - return COMMAND_RETURN_ERROR; - } + GError *error = NULL; + GPtrArray *list = spl_list(&error); + if (list == NULL) + return print_error(client, error); print_spl_list(client, list); spl_list_free(list); @@ -1774,8 +1717,10 @@ handle_sticker_song(struct client *client, int argc, char *argv[]) .name = argv[4], }; + db_lock(); directory = db_get_directory(argv[3]); if (directory == NULL) { + db_unlock(); command_error(client, ACK_ERROR_NO_EXIST, "no such directory"); return COMMAND_RETURN_ERROR; @@ -1783,6 +1728,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[]) success = sticker_song_find(directory, data.name, sticker_song_find_print_cb, &data); + db_unlock(); if (!success) { command_error(client, ACK_ERROR_SYSTEM, "failed to set search sticker database"); @@ -1817,6 +1763,172 @@ handle_sticker(struct client *client, int argc, char *argv[]) } #endif +static enum command_return +handle_subscribe(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + assert(argc == 2); + + switch (client_subscribe(client, argv[1])) { + case CLIENT_SUBSCRIBE_OK: + return COMMAND_RETURN_OK; + + case CLIENT_SUBSCRIBE_INVALID: + command_error(client, ACK_ERROR_ARG, + "invalid channel name"); + return COMMAND_RETURN_ERROR; + + case CLIENT_SUBSCRIBE_ALREADY: + command_error(client, ACK_ERROR_EXIST, + "already subscribed to this channel"); + return COMMAND_RETURN_ERROR; + + case CLIENT_SUBSCRIBE_FULL: + command_error(client, ACK_ERROR_EXIST, + "subscription list is full"); + return COMMAND_RETURN_ERROR; + } + + /* unreachable */ + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_unsubscribe(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + assert(argc == 2); + + if (client_unsubscribe(client, argv[1])) + return COMMAND_RETURN_OK; + else { + command_error(client, ACK_ERROR_NO_EXIST, + "not subscribed to this channel"); + return COMMAND_RETURN_ERROR; + } +} + +struct channels_context { + GStringChunk *chunk; + + GHashTable *channels; +}; + +static void +collect_channels(gpointer data, gpointer user_data) +{ + struct channels_context *context = user_data; + const struct client *client = data; + + for (GSList *i = client->subscriptions; i != NULL; + i = g_slist_next(i)) { + const char *channel = i->data; + + if (g_hash_table_lookup(context->channels, channel) == NULL) { + char *channel2 = g_string_chunk_insert(context->chunk, + channel); + g_hash_table_insert(context->channels, channel2, + context); + } + } +} + +static void +print_channel(gpointer key, G_GNUC_UNUSED gpointer value, gpointer user_data) +{ + struct client *client = user_data; + const char *channel = key; + + client_printf(client, "channel: %s\n", channel); +} + +static enum command_return +handle_channels(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + assert(argc == 1); + + struct channels_context context = { + .chunk = g_string_chunk_new(1024), + .channels = g_hash_table_new(g_str_hash, g_str_equal), + }; + + client_list_foreach(collect_channels, &context); + + g_hash_table_foreach(context.channels, print_channel, client); + + g_hash_table_destroy(context.channels); + g_string_chunk_free(context.chunk); + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_read_messages(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + assert(argc == 1); + + GSList *messages = client_read_messages(client); + + for (GSList *i = messages; i != NULL; i = g_slist_next(i)) { + struct client_message *msg = i->data; + + client_printf(client, "channel: %s\nmessage: %s\n", + msg->channel, msg->message); + client_message_free(msg); + } + + g_slist_free(messages); + + return COMMAND_RETURN_OK; +} + +struct send_message_context { + struct client_message msg; + + bool sent; +}; + +static void +send_message(gpointer data, gpointer user_data) +{ + struct send_message_context *context = user_data; + struct client *client = data; + + if (client_push_message(client, &context->msg)) + context->sent = true; +} + +static enum command_return +handle_send_message(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + assert(argc == 3); + + if (!client_message_valid_channel_name(argv[1])) { + command_error(client, ACK_ERROR_ARG, + "invalid channel name"); + return COMMAND_RETURN_ERROR; + } + + struct send_message_context context = { + .sent = false, + }; + + client_message_init(&context.msg, argv[1], argv[2]); + + client_list_foreach(send_message, &context); + + client_message_deinit(&context.msg); + + if (context.sent) + return COMMAND_RETURN_OK; + else { + command_error(client, ACK_ERROR_NO_EXIST, + "nobody is subscribed to this channel"); + return COMMAND_RETURN_ERROR; + } +} + /** * The command registry. * @@ -1825,10 +1937,12 @@ handle_sticker(struct client *client, int argc, char *argv[]) static const struct command commands[] = { { "add", PERMISSION_ADD, 1, 1, handle_add }, { "addid", PERMISSION_ADD, 1, 2, handle_addid }, + { "channels", PERMISSION_READ, 0, 0, handle_channels }, { "clear", PERMISSION_CONTROL, 0, 0, handle_clear }, { "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror }, { "close", PERMISSION_NONE, -1, -1, handle_close }, { "commands", PERMISSION_NONE, 0, 0, handle_commands }, + { "config", PERMISSION_ADMIN, 0, 0, handle_config }, { "consume", PERMISSION_CONTROL, 1, 1, handle_consume }, { "count", PERMISSION_READ, 2, -1, handle_count }, { "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade }, @@ -1848,7 +1962,7 @@ static const struct command commands[] = { { "listplaylist", PERMISSION_READ, 1, 1, handle_listplaylist }, { "listplaylistinfo", PERMISSION_READ, 1, 1, handle_listplaylistinfo }, { "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists }, - { "load", PERMISSION_ADD, 1, 1, handle_load }, + { "load", PERMISSION_ADD, 1, 2, handle_load }, { "lsinfo", PERMISSION_READ, 0, 1, handle_lsinfo }, { "mixrampdb", PERMISSION_CONTROL, 1, 1, handle_mixrampdb }, { "mixrampdelay", PERMISSION_CONTROL, 1, 1, handle_mixrampdelay }, @@ -1874,19 +1988,24 @@ static const struct command commands[] = { { "plchanges", PERMISSION_READ, 1, 1, handle_plchanges }, { "plchangesposid", PERMISSION_READ, 1, 1, handle_plchangesposid }, { "previous", PERMISSION_CONTROL, 0, 0, handle_previous }, + { "prio", PERMISSION_CONTROL, 2, -1, handle_prio }, + { "prioid", PERMISSION_CONTROL, 2, -1, handle_prioid }, { "random", PERMISSION_CONTROL, 1, 1, handle_random }, + { "readmessages", PERMISSION_READ, 0, 0, handle_read_messages }, { "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 }, + { "rescan", PERMISSION_CONTROL, 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 }, { "seek", PERMISSION_CONTROL, 2, 2, handle_seek }, + { "seekcur", PERMISSION_CONTROL, 1, 1, handle_seekcur }, { "seekid", PERMISSION_CONTROL, 2, 2, handle_seekid }, + { "sendmessage", PERMISSION_CONTROL, 2, 2, handle_send_message }, { "setvol", PERMISSION_CONTROL, 1, 1, handle_setvol }, { "shuffle", PERMISSION_CONTROL, 0, 1, handle_shuffle }, { "single", PERMISSION_CONTROL, 1, 1, handle_single }, @@ -1896,10 +2015,12 @@ static const struct command commands[] = { { "sticker", PERMISSION_ADMIN, 3, -1, handle_sticker }, #endif { "stop", PERMISSION_CONTROL, 0, 0, handle_stop }, + { "subscribe", PERMISSION_READ, 1, 1, handle_subscribe }, { "swap", PERMISSION_CONTROL, 2, 2, handle_swap }, { "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid }, { "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes }, - { "update", PERMISSION_ADMIN, 0, 1, handle_update }, + { "unsubscribe", PERMISSION_READ, 1, 1, handle_unsubscribe }, + { "update", PERMISSION_CONTROL, 0, 1, handle_update }, { "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers }, }; @@ -2029,10 +2150,9 @@ static const struct command * command_checked_lookup(struct client *client, unsigned permission, int argc, char *argv[]) { - static char unknown[] = ""; const struct command *cmd; - current_command = unknown; + current_command = ""; if (argc == 0) return NULL; diff --git a/src/command.h b/src/command.h index 39389385d..68d1f95e4 100644 --- a/src/command.h +++ b/src/command.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/conf.c b/src/conf.c index 14dac93a6..549ff51ef 100644 --- a/src/conf.c +++ b/src/conf.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ #include "config.h" #include "conf.h" #include "utils.h" +#include "string_util.h" #include "tokenizer.h" #include "path.h" #include "glib_compat.h" @@ -58,6 +59,7 @@ static struct config_entry config_entries[] = { { .name = CONF_LOG_FILE, false, false }, { .name = CONF_PID_FILE, false, false }, { .name = CONF_STATE_FILE, false, false }, + { .name = "restore_paused", false, false }, { .name = CONF_USER, false, false }, { .name = CONF_GROUP, false, false }, { .name = CONF_BIND_TO_ADDRESS, true, false }, @@ -97,6 +99,9 @@ static struct config_entry config_entries[] = { { .name = CONF_PLAYLIST_PLUGIN, true, true }, { .name = CONF_AUTO_UPDATE, false, false }, { .name = CONF_AUTO_UPDATE_DEPTH, false, false }, + { .name = CONF_DESPOTIFY_USER, false, false }, + { .name = CONF_DESPOTIFY_PASSWORD, false, false}, + { .name = CONF_DESPOTIFY_HIGH_BITRATE, false, false }, { .name = "filter", true, true }, }; @@ -138,7 +143,7 @@ config_new_param(const char *value, int line) return ret; } -static void +void config_param_free(struct config_param *param) { g_free(param->value); @@ -218,20 +223,13 @@ void config_global_check(void) } } -bool +void config_add_block_param(struct config_param * param, const char *name, - const char *value, int line, GError **error_r) + const char *value, int line) { 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; - } + assert(config_get_block_param(param, name) == NULL); param->num_block_params++; @@ -245,7 +243,46 @@ config_add_block_param(struct config_param * param, const char *name, bp->value = g_strdup(value); bp->line = line; bp->used = false; +} +static bool +config_read_name_value(struct config_param *param, char *input, unsigned line, + GError **error_r) +{ + const char *name = tokenizer_next_word(&input, error_r); + if (name == NULL) { + assert(*input != 0); + return false; + } + + const char *value = tokenizer_next_string(&input, error_r); + if (value == NULL) { + if (*input == 0) { + assert(error_r == NULL || *error_r == NULL); + g_set_error(error_r, config_quark(), 0, + "Value missing"); + } else { + assert(error_r == NULL || *error_r != NULL); + } + + return false; + } + + if (*input != 0 && *input != CONF_COMMENT) { + g_set_error(error_r, config_quark(), 0, + "Unknown tokens after value"); + return false; + } + + const struct block_param *bp = config_get_block_param(param, name); + if (bp != NULL) { + g_set_error(error_r, config_quark(), 0, + "\"%s\" is duplicate, first defined on line %i", + name, bp->line); + return false; + } + + config_add_block_param(param, name, value, line); return true; } @@ -254,11 +291,9 @@ config_read_block(FILE *fp, int *count, char *string, GError **error_r) { struct config_param *ret = config_new_param(NULL, *count); GError *error = NULL; - bool success; while (true) { char *line; - const char *name, *value; line = fgets(string, MAX_STRING_SIZE, fp); if (line == NULL) { @@ -269,7 +304,7 @@ config_read_block(FILE *fp, int *count, char *string, GError **error_r) } (*count)++; - line = g_strchug(line); + line = strchug_fast(line); if (*line == 0 || *line == CONF_COMMENT) continue; @@ -277,7 +312,7 @@ config_read_block(FILE *fp, int *count, char *string, GError **error_r) /* end of this block; return from the function (and from this "while" loop) */ - line = g_strchug(line + 1); + line = strchug_fast(line + 1); if (*line != 0 && *line != CONF_COMMENT) { config_param_free(ret); g_set_error(error_r, config_quark(), 0, @@ -291,42 +326,13 @@ config_read_block(FILE *fp, int *count, char *string, GError **error_r) /* parse name and value */ - name = tokenizer_next_word(&line, &error); - if (name == NULL) { + if (!config_read_name_value(ret, line, *count, &error)) { assert(*line != 0); config_param_free(ret); g_propagate_prefixed_error(error_r, error, "line %i: ", *count); return NULL; } - - 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 (*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; - } - - success = config_add_block_param(ret, name, value, *count, - error_r); - if (!success) { - config_param_free(ret); - return false; - } } } @@ -344,7 +350,7 @@ config_read_file(const char *file, GError **error_r) if (!(fp = fopen(file, "r"))) { g_set_error(error_r, config_quark(), errno, "Failed to open %s: %s", - file, strerror(errno)); + file, g_strerror(errno)); return false; } @@ -355,7 +361,7 @@ config_read_file(const char *file, GError **error_r) count++; - line = g_strchug(string); + line = strchug_fast(string); if (*line == 0 || *line == CONF_COMMENT) continue; @@ -405,7 +411,7 @@ config_read_file(const char *file, GError **error_r) return false; } - line = g_strchug(line + 1); + line = strchug_fast(line + 1); if (*line != 0 && *line != CONF_COMMENT) { g_set_error(error_r, config_quark(), 0, "line %i: Unknown tokens after '{'", @@ -457,7 +463,7 @@ config_read_file(const char *file, GError **error_r) return true; } -struct config_param * +const struct config_param * config_get_next_param(const char *name, const struct config_param * last) { struct config_entry *entry; @@ -497,22 +503,23 @@ config_get_string(const char *name, const char *default_value) return param->value; } -const char * -config_get_path(const char *name) +char * +config_dup_path(const char *name, GError **error_r) { - struct config_param *param = config_get_param(name); - char *path; + assert(error_r != NULL); + assert(*error_r == NULL); + const struct config_param *param = config_get_param(name); if (param == NULL) return NULL; - path = parsePath(param->value); - if (path == NULL) - MPD_ERROR("error parsing \"%s\" at line %i\n", - name, param->line); + char *path = parsePath(param->value, error_r); + if (G_UNLIKELY(path == NULL)) + g_prefix_error(error_r, + "Invalid path in \"%s\" at line %i: ", + name, param->line); - g_free(param->value); - return param->value = path; + return path; } unsigned @@ -553,7 +560,7 @@ config_get_positive(const char *name, unsigned default_value) return (unsigned)value; } -struct block_param * +const struct block_param * config_get_block_param(const struct config_param * param, const char *name) { if (param == NULL) @@ -591,7 +598,7 @@ const char * config_get_block_string(const struct config_param *param, const char *name, const char *default_value) { - struct block_param *bp = config_get_block_param(param, name); + const struct block_param *bp = config_get_block_param(param, name); if (bp == NULL) return default_value; @@ -599,11 +606,31 @@ config_get_block_string(const struct config_param *param, const char *name, return bp->value; } +char * +config_dup_block_path(const struct config_param *param, const char *name, + GError **error_r) +{ + assert(error_r != NULL); + assert(*error_r == NULL); + + const struct block_param *bp = config_get_block_param(param, name); + if (bp == NULL) + return NULL; + + char *path = parsePath(bp->value, error_r); + if (G_UNLIKELY(path == NULL)) + g_prefix_error(error_r, + "Invalid path in \"%s\" at line %i: ", + name, bp->line); + + return path; +} + unsigned config_get_block_unsigned(const struct config_param *param, const char *name, unsigned default_value) { - struct block_param *bp = config_get_block_param(param, name); + const struct block_param *bp = config_get_block_param(param, name); long value; char *endptr; @@ -624,7 +651,7 @@ bool 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); + const struct block_param *bp = config_get_block_param(param, name); bool success, value; if (bp == NULL) diff --git a/src/conf.h b/src/conf.h index 8a0678312..815c739b1 100644 --- a/src/conf.h +++ b/src/conf.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -72,6 +72,9 @@ #define CONF_PLAYLIST_PLUGIN "playlist_plugin" #define CONF_AUTO_UPDATE "auto_update" #define CONF_AUTO_UPDATE_DEPTH "auto_update_depth" +#define CONF_DESPOTIFY_USER "despotify_user" +#define CONF_DESPOTIFY_PASSWORD "despotify_password" +#define CONF_DESPOTIFY_HIGH_BITRATE "despotify_high_bitrate" #define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16) #define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false @@ -108,6 +111,7 @@ struct config_param { * A GQuark for GError instances, resulting from malformed * configuration. */ +G_GNUC_CONST static inline GQuark config_quark(void) { @@ -129,11 +133,11 @@ 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 * +const struct config_param * config_get_next_param(const char *name, const struct config_param *last); G_GNUC_PURE -static inline struct config_param * +static inline const struct config_param * config_get_param(const char *name) { return config_get_next_param(name, NULL); @@ -152,17 +156,15 @@ config_get_string(const char *name, const char *default_value); /** * Returns an optional configuration variable which contains an - * absolute path. If there is a tilde prefix, it is expanded. Aborts - * MPD if the path is not a valid absolute path. + * absolute path. If there is a tilde prefix, it is expanded. + * Returns NULL if the value is not present. If the path could not be + * parsed, returns NULL and sets the error. + * + * The return value must be freed with g_free(). */ -/* 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_MALLOC +char * +config_dup_path(const char *name, GError **error_r); G_GNUC_PURE unsigned @@ -173,7 +175,7 @@ unsigned config_get_positive(const char *name, unsigned default_value); G_GNUC_PURE -struct block_param * +const struct block_param * config_get_block_param(const struct config_param *param, const char *name); G_GNUC_PURE @@ -184,6 +186,7 @@ const char * config_get_block_string(const struct config_param *param, const char *name, const char *default_value); +G_GNUC_MALLOC static inline char * config_dup_block_string(const struct config_param *param, const char *name, const char *default_value) @@ -191,6 +194,15 @@ config_dup_block_string(const struct config_param *param, const char *name, return g_strdup(config_get_block_string(param, name, default_value)); } +/** + * Same as config_dup_path(), but looks up the setting in the + * specified block. + */ +G_GNUC_MALLOC +char * +config_dup_block_path(const struct config_param *param, const char *name, + GError **error_r); + G_GNUC_PURE unsigned config_get_block_unsigned(const struct config_param *param, const char *name, @@ -201,11 +213,15 @@ bool config_get_block_bool(const struct config_param *param, const char *name, bool default_value); +G_GNUC_MALLOC struct config_param * config_new_param(const char *value, int line); -bool +void +config_param_free(struct config_param *param); + +void config_add_block_param(struct config_param * param, const char *name, - const char *value, int line, GError **error_r); + const char *value, int line); #endif diff --git a/src/crossfade.c b/src/crossfade.c index cdfd82879..46a0dff30 100644 --- a/src/crossfade.c +++ b/src/crossfade.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,7 +19,6 @@ #include "config.h" #include "crossfade.h" -#include "pcm_mix.h" #include "chunk.h" #include "audio_format.h" #include "tag.h" diff --git a/src/crossfade.h b/src/crossfade.h index 096a62020..d581dbfe0 100644 --- a/src/crossfade.h +++ b/src/crossfade.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/cue/cue_parser.c b/src/cue/cue_parser.c new file mode 100644 index 000000000..034d4a1f9 --- /dev/null +++ b/src/cue/cue_parser.c @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "cue_parser.h" +#include "string_util.h" +#include "song.h" +#include "tag.h" + +#include <assert.h> +#include <stdlib.h> + +struct cue_parser { + enum { + /** + * Parsing the CUE header. + */ + HEADER, + + /** + * Parsing a "FILE ... WAVE". + */ + WAVE, + + /** + * Ignore everything until the next "FILE". + */ + IGNORE_FILE, + + /** + * Parsing a "TRACK ... AUDIO". + */ + TRACK, + + /** + * Ignore everything until the next "TRACK". + */ + IGNORE_TRACK, + } state; + + struct tag *tag; + + char *filename; + + struct song *current, *previous, *finished; + + bool last_updated; +}; + +struct cue_parser * +cue_parser_new(void) +{ + struct cue_parser *parser = g_new(struct cue_parser, 1); + parser->state = HEADER; + parser->tag = tag_new(); + parser->filename = NULL; + parser->current = NULL; + parser->previous = NULL; + parser->finished = NULL; + return parser; +} + +void +cue_parser_free(struct cue_parser *parser) +{ + tag_free(parser->tag); + g_free(parser->filename); + + if (parser->current != NULL) + song_free(parser->current); + + if (parser->finished != NULL) + song_free(parser->finished); + + g_free(parser); +} + +static const char * +cue_next_word(char *p, char **pp) +{ + assert(p >= *pp); + assert(!g_ascii_isspace(*p)); + + const char *word = p; + while (*p != 0 && !g_ascii_isspace(*p)) + ++p; + + *p = 0; + *pp = p + 1; + return word; +} + +static const char * +cue_next_quoted(char *p, char **pp) +{ + assert(p >= *pp); + assert(p[-1] == '"'); + + char *end = strchr(p, '"'); + if (end == NULL) { + /* syntax error - ignore it silently */ + *pp = p + strlen(p); + return p; + } + + *end = 0; + *pp = end + 1; + + return p; +} + +static const char * +cue_next_token(char **pp) +{ + char *p = strchug_fast(*pp); + if (*p == 0) + return NULL; + + return cue_next_word(p, pp); +} + +static const char * +cue_next_value(char **pp) +{ + char *p = strchug_fast(*pp); + if (*p == 0) + return NULL; + + if (*p == '"') + return cue_next_quoted(p + 1, pp); + else + return cue_next_word(p, pp); +} + +static void +cue_add_tag(struct tag *tag, enum tag_type type, char *p) +{ + const char *value = cue_next_value(&p); + if (value != NULL) + tag_add_item(tag, type, value); + +} + +static void +cue_parse_rem(char *p, struct tag *tag) +{ + const char *type = cue_next_token(&p); + if (type == NULL) + return; + + enum tag_type type2 = tag_name_parse_i(type); + if (type2 != TAG_NUM_OF_ITEM_TYPES) + cue_add_tag(tag, type2, p); +} + +static struct tag * +cue_current_tag(struct cue_parser *parser) +{ + if (parser->state == HEADER) + return parser->tag; + else if (parser->state == TRACK) + return parser->current->tag; + else + return NULL; +} + +static int +cue_parse_position(const char *p) +{ + char *endptr; + unsigned long minutes = strtoul(p, &endptr, 10); + if (endptr == p || *endptr != ':') + return -1; + + p = endptr + 1; + unsigned long seconds = strtoul(p, &endptr, 10); + if (endptr == p || *endptr != ':') + return -1; + + p = endptr + 1; + unsigned long frames = strtoul(p, &endptr, 10); + if (endptr == p || *endptr != 0) + return -1; + + return minutes * 60000 + seconds * 1000 + frames * 1000 / 75; +} + +static void +cue_parser_feed2(struct cue_parser *parser, char *p) +{ + assert(parser != NULL); + assert(p != NULL); + + const char *command = cue_next_token(&p); + if (command == NULL) + return; + + if (strcmp(command, "REM") == 0) { + struct tag *tag = cue_current_tag(parser); + if (tag != NULL) + cue_parse_rem(p, tag); + } else if (strcmp(command, "PERFORMER") == 0) { + struct tag *tag = cue_current_tag(parser); + if (tag != NULL) + cue_add_tag(tag, TAG_PERFORMER, p); + } else if (strcmp(command, "TITLE") == 0) { + if (parser->state == HEADER) + cue_add_tag(parser->tag, TAG_ALBUM, p); + else if (parser->state == TRACK) + cue_add_tag(parser->current->tag, TAG_TITLE, p); + } else if (strcmp(command, "FILE") == 0) { + cue_parser_finish(parser); + + const char *filename = cue_next_value(&p); + if (filename == NULL) + return; + + const char *type = cue_next_token(&p); + if (type == NULL) + return; + + if (strcmp(type, "WAVE") != 0) { + parser->state = IGNORE_FILE; + return; + } + + parser->state = WAVE; + g_free(parser->filename); + parser->filename = g_strdup(filename); + } else if (parser->state == IGNORE_FILE) { + return; + } else if (strcmp(command, "TRACK") == 0) { + cue_parser_finish(parser); + + const char *nr = cue_next_token(&p); + if (nr == NULL) + return; + + const char *type = cue_next_token(&p); + if (type == NULL) + return; + + if (strcmp(type, "AUDIO") != 0) { + parser->state = IGNORE_TRACK; + return; + } + + parser->state = TRACK; + parser->current = song_remote_new(parser->filename); + assert(parser->current->tag == NULL); + parser->current->tag = tag_dup(parser->tag); + tag_add_item(parser->current->tag, TAG_TRACK, nr); + parser->last_updated = false; + } else if (parser->state == IGNORE_TRACK) { + return; + } else if (parser->state == TRACK && strcmp(command, "INDEX") == 0) { + const char *nr = cue_next_token(&p); + if (nr == NULL) + return; + + const char *position = cue_next_token(&p); + if (position == NULL) + return; + + int position_ms = cue_parse_position(position); + if (position_ms < 0) + return; + + if (!parser->last_updated && parser->previous != NULL && + parser->previous->start_ms < (unsigned)position_ms) { + parser->last_updated = true; + parser->previous->end_ms = position_ms; + parser->previous->tag->time = + (parser->previous->end_ms - parser->previous->start_ms + 500) / 1000; + } + + parser->current->start_ms = position_ms; + } +} + +void +cue_parser_feed(struct cue_parser *parser, const char *line) +{ + assert(parser != NULL); + assert(line != NULL); + + char *allocated = g_strdup(line); + cue_parser_feed2(parser, allocated); + g_free(allocated); +} + +void +cue_parser_finish(struct cue_parser *parser) +{ + if (parser->finished != NULL) + song_free(parser->finished); + + parser->finished = parser->previous; + parser->previous = parser->current; + parser->current = NULL; +} + +struct song * +cue_parser_get(struct cue_parser *parser) +{ + assert(parser != NULL); + + struct song *song = parser->finished; + parser->finished = NULL; + return song; +} diff --git a/src/cue/cue_parser.h b/src/cue/cue_parser.h new file mode 100644 index 000000000..d8d695739 --- /dev/null +++ b/src/cue/cue_parser.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_CUE_PARSER_H +#define MPD_CUE_PARSER_H + +#include "check.h" + +#include <stdbool.h> + +struct cue_parser * +cue_parser_new(void); + +void +cue_parser_free(struct cue_parser *parser); + +/** + * Feed a text line from the CUE file into the parser. Call + * cue_parser_get() after this to see if a song has been finished. + */ +void +cue_parser_feed(struct cue_parser *parser, const char *line); + +/** + * Tell the parser that the end of the file has been reached. Call + * cue_parser_get() after this to see if a song has been finished. + * This procedure must be done twice! + */ +void +cue_parser_finish(struct cue_parser *parser); + +/** + * Check if a song was finished by the last cue_parser_feed() or + * cue_parser_finish() call. + * + * @return a song object that must be freed by the caller, or NULL if + * no song was finished at this time + */ +struct song * +cue_parser_get(struct cue_parser *parser); + +#endif diff --git a/src/cue/cue_tag.c b/src/cue/cue_tag.c deleted file mode 100644 index ba1172559..000000000 --- a/src/cue/cue_tag.c +++ /dev/null @@ -1,235 +0,0 @@ -#include "config.h" -#include "cue_tag.h" -#include "tag.h" - -#include <libcue/libcue.h> -#include <assert.h> - -static struct tag * -cue_tag_cd(struct Cdtext *cdtext, struct Rem *rem) -{ - struct tag *tag; - char *tmp; - - assert(cdtext != NULL); - - tag = tag_new(); - - tag_begin_add(tag); - - /* TAG_ALBUM_ARTIST */ - if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL) - tag_add_item(tag, TAG_ALBUM_ARTIST, tmp); - - else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL) - tag_add_item(tag, TAG_ALBUM_ARTIST, tmp); - - else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL) - tag_add_item(tag, TAG_ALBUM_ARTIST, tmp); - - else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL) - tag_add_item(tag, TAG_ALBUM_ARTIST, tmp); - - /* TAG_ARTIST */ - if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL) - tag_add_item(tag, TAG_ARTIST, tmp); - - else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL) - tag_add_item(tag, TAG_ARTIST, tmp); - - else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL) - tag_add_item(tag, TAG_ARTIST, tmp); - - else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL) - tag_add_item(tag, TAG_ARTIST, tmp); - - /* TAG_PERFORMER */ - if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL) - tag_add_item(tag, TAG_PERFORMER, tmp); - - /* TAG_COMPOSER */ - if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL) - tag_add_item(tag, TAG_COMPOSER, tmp); - - /* TAG_ALBUM */ - if ((tmp = cdtext_get(PTI_TITLE, cdtext)) != NULL) - tag_add_item(tag, TAG_ALBUM, tmp); - - /* TAG_GENRE */ - if ((tmp = cdtext_get(PTI_GENRE, cdtext)) != NULL) - tag_add_item(tag, TAG_GENRE, tmp); - - /* TAG_DATE */ - if ((tmp = rem_get(REM_DATE, rem)) != NULL) - tag_add_item(tag, TAG_DATE, tmp); - - /* TAG_COMMENT */ - if ((tmp = cdtext_get(PTI_MESSAGE, cdtext)) != NULL) - tag_add_item(tag, TAG_COMMENT, tmp); - - /* TAG_DISC */ - if ((tmp = cdtext_get(PTI_DISC_ID, cdtext)) != NULL) - tag_add_item(tag, TAG_DISC, tmp); - - /* stream name, usually empty - * tag_add_item(tag, TAG_NAME,); - */ - - /* REM MUSICBRAINZ entry? - tag_add_item(tag, TAG_MUSICBRAINZ_ARTISTID,); - tag_add_item(tag, TAG_MUSICBRAINZ_ALBUMID,); - tag_add_item(tag, TAG_MUSICBRAINZ_ALBUMARTISTID,); - tag_add_item(tag, TAG_MUSICBRAINZ_TRACKID,); - */ - - tag_end_add(tag); - - if (tag_is_empty(tag)) { - tag_free(tag); - return NULL; - } - - return tag; -} - -static struct tag * -cue_tag_track(struct Cdtext *cdtext, struct Rem *rem) -{ - struct tag *tag; - char *tmp; - - assert(cdtext != NULL); - - tag = tag_new(); - - tag_begin_add(tag); - - /* TAG_ARTIST */ - if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL) - tag_add_item(tag, TAG_ARTIST, tmp); - - else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL) - tag_add_item(tag, TAG_ARTIST, tmp); - - else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL) - tag_add_item(tag, TAG_ARTIST, tmp); - - else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL) - tag_add_item(tag, TAG_ARTIST, tmp); - - /* TAG_TITLE */ - if ((tmp = cdtext_get(PTI_TITLE, cdtext)) != NULL) - tag_add_item(tag, TAG_TITLE, tmp); - - /* TAG_GENRE */ - if ((tmp = cdtext_get(PTI_GENRE, cdtext)) != NULL) - tag_add_item(tag, TAG_GENRE, tmp); - - /* TAG_DATE */ - if ((tmp = rem_get(REM_DATE, rem)) != NULL) - tag_add_item(tag, TAG_DATE, tmp); - - /* TAG_COMPOSER */ - if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL) - tag_add_item(tag, TAG_COMPOSER, tmp); - - /* TAG_PERFORMER */ - if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL) - tag_add_item(tag, TAG_PERFORMER, tmp); - - /* TAG_COMMENT */ - if ((tmp = cdtext_get(PTI_MESSAGE, cdtext)) != NULL) - tag_add_item(tag, TAG_COMMENT, tmp); - - /* TAG_DISC */ - if ((tmp = cdtext_get(PTI_DISC_ID, cdtext)) != NULL) - tag_add_item(tag, TAG_DISC, tmp); - - tag_end_add(tag); - - if (tag_is_empty(tag)) { - tag_free(tag); - return NULL; - } - - return tag; -} - -struct tag * -cue_tag(struct Cd *cd, unsigned tnum) -{ - struct tag *cd_tag, *track_tag, *tag; - struct Track *track; - - assert(cd != NULL); - - track = cd_get_track(cd, tnum); - if (track == NULL) - return NULL; - - /* 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(track), - track_get_rem(track)); - - tag = tag_merge_replace(cd_tag, track_tag); - if (tag == NULL) - return NULL; - - 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_file(FILE *fp, unsigned tnum) -{ - struct Cd *cd; - struct tag *tag; - - assert(fp != NULL); - - if (tnum > 256) - return NULL; - - cd = cue_parse_file(fp); - if (cd == NULL) - return NULL; - - tag = cue_tag(cd, tnum); - cd_delete(cd); - - return tag; -} - -struct tag * -cue_tag_string(const char *str, unsigned tnum) -{ - struct Cd *cd; - struct tag *tag; - - assert(str != NULL); - - if (tnum > 256) - return NULL; - - 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 deleted file mode 100644 index 1ddaa59c8..000000000 --- a/src/cue/cue_tag.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef MPD_CUE_TAG_H -#define MPD_CUE_TAG_H - -#include "check.h" - -#ifdef HAVE_CUE /* libcue */ - -#include <stdio.h> - -struct tag; -struct Cd; - -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 852541375..8bca9095a 100644 --- a/src/daemon.c +++ b/src/daemon.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -80,7 +80,7 @@ daemonize_kill(void) ret = kill(pid, SIGTERM); if (ret < 0) - MPD_ERROR("unable to kill proccess %i: %s", + MPD_ERROR("unable to kill process %i: %s", pid, g_strerror(errno)); exit(EXIT_SUCCESS); diff --git a/src/daemon.h b/src/daemon.h index 71039543c..c43a74cfd 100644 --- a/src/daemon.h +++ b/src/daemon.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/database.c b/src/database.c index 9f29f95e1..8c903bb45 100644 --- a/src/database.c +++ b/src/database.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,14 +19,16 @@ #include "config.h" #include "database.h" +#include "db_error.h" +#include "db_save.h" +#include "db_selection.h" +#include "db_visitor.h" +#include "db_plugin.h" +#include "db/simple_db_plugin.h" #include "directory.h" -#include "directory_save.h" -#include "song.h" -#include "path.h" #include "stats.h" -#include "text_file.h" -#include "tag.h" -#include "tag_internal.h" +#include "conf.h" +#include "glib_compat.h" #include <glib.h> @@ -35,85 +37,64 @@ #include <unistd.h> #include <assert.h> #include <string.h> -#include <stdlib.h> #include <errno.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "database" -#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: " +static struct db *db; +static bool db_is_open; -enum { - DB_FORMAT = 1, -}; - -static char *database_path; - -static struct directory *music_root; - -static time_t database_mtime; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -db_quark(void) +bool +db_init(const struct config_param *path, GError **error_r) { - return g_quark_from_static_string("database"); -} + assert(db == NULL); + assert(!db_is_open); -void -db_init(const char *path) -{ - database_path = g_strdup(path); + if (path == NULL) + return true; - if (path != NULL) - music_root = directory_new("", NULL); -} + struct config_param *param = config_new_param("database", path->line); + config_add_block_param(param, "path", path->value, path->line); -void -db_finish(void) -{ - assert((database_path == NULL) == (music_root == NULL)); + db = db_plugin_new(&simple_db_plugin, param, error_r); - if (music_root != NULL) - directory_free(music_root); + config_param_free(param); - g_free(database_path); + return db != NULL; } void -db_clear(void) +db_finish(void) { - assert(music_root != NULL); + if (db_is_open) + db_plugin_close(db); - directory_free(music_root); - music_root = directory_new("", NULL); + if (db != NULL) + db_plugin_free(db); } struct directory * db_get_root(void) { - assert(music_root != NULL); + assert(db != NULL); - return music_root; + return simple_db_get_root(db); } struct directory * db_get_directory(const char *name) { - if (music_root == NULL) + if (db == NULL) return NULL; + struct directory *music_root = db_get_root(); if (name == NULL) return music_root; - return directory_lookup_directory(music_root, name); + struct directory *directory = + directory_lookup_directory(music_root, name); + return directory; } struct song * @@ -123,281 +104,67 @@ db_get_song(const char *file) g_debug("get song: %s", file); - if (music_root == NULL) + if (db == NULL) return NULL; - return directory_lookup_song(music_root, file); + return db_plugin_get_song(db, file, NULL); } -int -db_walk(const char *name, - int (*forEachSong)(struct song *, void *), - int (*forEachDir)(struct directory *, void *), void *data) +bool +db_visit(const struct db_selection *selection, + const struct db_visitor *visitor, void *ctx, + GError **error_r) { - struct directory *directory; - - if (music_root == NULL) - return -1; - - if ((directory = db_get_directory(name)) == NULL) { - struct song *song; - if ((song = db_get_song(name)) && forEachSong) { - return forEachSong(song, data); - } - return -1; + if (db == NULL) { + g_set_error_literal(error_r, db_quark(), DB_DISABLED, + "No database"); + return false; } - return directory_walk(directory, forEachSong, forEachDir, data); + return db_plugin_visit(db, selection, visitor, ctx, error_r); } bool -db_check(void) +db_walk(const char *uri, + const struct db_visitor *visitor, void *ctx, + GError **error_r) { - struct stat st; - - assert(database_path != NULL); - - /* Check if the file exists */ - if (access(database_path, F_OK)) { - /* If the file doesn't exist, we can't check if we can write - * it, so we are going to try to get the directory path, and - * see if we can write a file in that */ - char *dirPath = g_path_get_dirname(database_path); - - /* Check that the parent part of the path is a directory */ - if (stat(dirPath, &st) < 0) { - g_free(dirPath); - g_warning("Couldn't stat parent directory of db file " - "\"%s\": %s", database_path, strerror(errno)); - return false; - } - - if (!S_ISDIR(st.st_mode)) { - g_free(dirPath); - g_warning("Couldn't create db file \"%s\" because the " - "parent path is not a directory", database_path); - return false; - } - - /* Check if we can write to the directory */ - if (access(dirPath, X_OK | W_OK)) { - g_warning("Can't create db file in \"%s\": %s", - dirPath, strerror(errno)); - g_free(dirPath); - return false; - } - - g_free(dirPath); - - return true; - } - - /* Path exists, now check if it's a regular file */ - if (stat(database_path, &st) < 0) { - g_warning("Couldn't stat db file \"%s\": %s", - database_path, strerror(errno)); - return false; - } + struct db_selection selection; + db_selection_init(&selection, uri, true); - if (!S_ISREG(st.st_mode)) { - g_warning("db file \"%s\" is not a regular file", database_path); - return false; - } - - /* And check that we can write to it */ - if (access(database_path, R_OK | W_OK)) { - g_warning("Can't open db file \"%s\" for reading/writing: %s", - database_path, strerror(errno)); - return false; - } - - return true; + return db_visit(&selection, visitor, ctx, error_r); } bool -db_save(void) +db_save(GError **error_r) { - FILE *fp; - struct stat st; - - assert(database_path != NULL); - assert(music_root != NULL); + assert(db != NULL); + assert(db_is_open); - g_debug("removing empty directories from DB"); - directory_prune_empty(music_root); - - g_debug("sorting DB"); - - directory_sort(music_root); - - g_debug("writing DB"); - - fp = fopen(database_path, "w"); - if (!fp) { - g_warning("unable to write to db file \"%s\": %s", - database_path, strerror(errno)); - return false; - } - - 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); - - directory_save(fp, music_root); - - if (ferror(fp)) { - g_warning("Failed to write to database file: %s", - strerror(errno)); - fclose(fp); - return false; - } - - fclose(fp); - - if (stat(database_path, &st) == 0) - database_mtime = st.st_mtime; - - return true; + return simple_db_save(db, error_r); } bool db_load(GError **error) { - FILE *fp = NULL; - struct stat st; - 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); - - 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 */ - 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; - } - - memset(tags, false, sizeof(tags)); - - 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(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 = line + sizeof(DIRECTORY_FS_CHARSET) - 1; - old_charset = path_get_fs_charset(); - if (old_charset != NULL - && strcmp(new_charset, old_charset)) { - fclose(fp); - g_set_error(error, db_quark(), 0, - "Existing database has charset " - "\"%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", line); - g_string_free(buffer, true); - return false; - } - } + assert(db != NULL); + assert(!db_is_open); - if (format != DB_FORMAT) { - g_set_error(error, db_quark(), 0, - "Database format mismatch, " - "discarding database file"); + if (!db_plugin_open(db, error)) 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, buffer, error); - g_string_free(buffer, true); - fclose(fp); - - if (!success) - return false; + db_is_open = true; stats_update(); - if (stat(database_path, &st) == 0) - database_mtime = st.st_mtime; - return true; } time_t db_get_mtime(void) { - return database_mtime; + assert(db != NULL); + assert(db_is_open); + + return simple_db_get_mtime(db); } diff --git a/src/database.h b/src/database.h index 67149b20b..f877b74d3 100644 --- a/src/database.h +++ b/src/database.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,62 +20,76 @@ #ifndef MPD_DATABASE_H #define MPD_DATABASE_H +#include "gcc.h" + #include <glib.h> #include <sys/time.h> #include <stdbool.h> +struct config_param; struct directory; +struct db_selection; +struct db_visitor; /** * Initialize the database library. * * @param path the absolute path of the database file */ -void -db_init(const char *path); +bool +db_init(const struct config_param *path, GError **error_r); void db_finish(void); /** - * Clear the database. - */ -void -db_clear(void); - -/** * Returns the root directory object. Returns NULL if there is no * configured music directory. */ +G_GNUC_PURE struct directory * db_get_root(void); +/** + * Caller must lock the #db_mutex. + */ +gcc_nonnull(1) +G_GNUC_PURE struct directory * db_get_directory(const char *name); +gcc_nonnull(1) +G_GNUC_PURE struct song * db_get_song(const char *file); -int db_walk(const char *name, - int (*forEachSong)(struct song *, void *), - int (*forEachDir)(struct directory *, void *), void *data); +gcc_nonnull(1,2) +bool +db_visit(const struct db_selection *selection, + const struct db_visitor *visitor, void *ctx, + GError **error_r); +gcc_nonnull(1,2) bool -db_check(void); +db_walk(const char *uri, + const struct db_visitor *visitor, void *ctx, + GError **error_r); bool -db_save(void); +db_save(GError **error_r); bool db_load(GError **error); +G_GNUC_PURE time_t db_get_mtime(void); /** * Returns true if there is a valid database file on the disk. */ +G_GNUC_PURE static inline bool db_exists(void) { diff --git a/src/db/simple_db_plugin.c b/src/db/simple_db_plugin.c new file mode 100644 index 000000000..f11090828 --- /dev/null +++ b/src/db/simple_db_plugin.c @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "simple_db_plugin.h" +#include "db_internal.h" +#include "db_error.h" +#include "db_selection.h" +#include "db_visitor.h" +#include "db_save.h" +#include "db_lock.h" +#include "conf.h" +#include "glib_compat.h" +#include "directory.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> + +struct simple_db { + struct db base; + + char *path; + + struct directory *root; + + time_t mtime; +}; + +G_GNUC_CONST +static inline GQuark +simple_db_quark(void) +{ + return g_quark_from_static_string("simple_db"); +} + +G_GNUC_PURE +static const struct directory * +simple_db_lookup_directory(const struct simple_db *db, const char *uri) +{ + assert(db != NULL); + assert(db->root != NULL); + assert(uri != NULL); + + db_lock(); + struct directory *directory = + directory_lookup_directory(db->root, uri); + db_unlock(); + return directory; +} + +static struct db * +simple_db_init(const struct config_param *param, GError **error_r) +{ + struct simple_db *db = g_malloc(sizeof(*db)); + db_base_init(&db->base, &simple_db_plugin); + + GError *error = NULL; + db->path = config_dup_block_path(param, "path", &error); + if (db->path == NULL) { + g_free(db); + if (error != NULL) + g_propagate_error(error_r, error); + else + g_set_error(error_r, simple_db_quark(), 0, + "No \"path\" parameter specified"); + return NULL; + } + + return &db->base; +} + +static void +simple_db_finish(struct db *_db) +{ + struct simple_db *db = (struct simple_db *)_db; + + g_free(db->path); + g_free(db); +} + +static bool +simple_db_check(struct simple_db *db, GError **error_r) +{ + assert(db != NULL); + assert(db->path != NULL); + + /* Check if the file exists */ + if (access(db->path, F_OK)) { + /* If the file doesn't exist, we can't check if we can write + * it, so we are going to try to get the directory path, and + * see if we can write a file in that */ + char *dirPath = g_path_get_dirname(db->path); + + /* Check that the parent part of the path is a directory */ + struct stat st; + if (stat(dirPath, &st) < 0) { + g_free(dirPath); + g_set_error(error_r, simple_db_quark(), errno, + "Couldn't stat parent directory of db file " + "\"%s\": %s", + db->path, g_strerror(errno)); + return false; + } + + if (!S_ISDIR(st.st_mode)) { + g_free(dirPath); + g_set_error(error_r, simple_db_quark(), 0, + "Couldn't create db file \"%s\" because the " + "parent path is not a directory", + db->path); + return false; + } + + /* Check if we can write to the directory */ + if (access(dirPath, X_OK | W_OK)) { + g_set_error(error_r, simple_db_quark(), errno, + "Can't create db file in \"%s\": %s", + dirPath, g_strerror(errno)); + g_free(dirPath); + return false; + } + + g_free(dirPath); + + return true; + } + + /* Path exists, now check if it's a regular file */ + struct stat st; + if (stat(db->path, &st) < 0) { + g_set_error(error_r, simple_db_quark(), errno, + "Couldn't stat db file \"%s\": %s", + db->path, g_strerror(errno)); + return false; + } + + if (!S_ISREG(st.st_mode)) { + g_set_error(error_r, simple_db_quark(), 0, + "db file \"%s\" is not a regular file", + db->path); + return false; + } + + /* And check that we can write to it */ + if (access(db->path, R_OK | W_OK)) { + g_set_error(error_r, simple_db_quark(), errno, + "Can't open db file \"%s\" for reading/writing: %s", + db->path, g_strerror(errno)); + return false; + } + + return true; +} + +static bool +simple_db_load(struct simple_db *db, GError **error_r) +{ + assert(db != NULL); + assert(db->path != NULL); + assert(db->root != NULL); + + FILE *fp = fopen(db->path, "r"); + if (fp == NULL) { + g_set_error(error_r, simple_db_quark(), errno, + "Failed to open database file \"%s\": %s", + db->path, g_strerror(errno)); + return false; + } + + if (!db_load_internal(fp, db->root, error_r)) { + fclose(fp); + return false; + } + + fclose(fp); + + struct stat st; + if (stat(db->path, &st) == 0) + db->mtime = st.st_mtime; + + return true; +} + +static bool +simple_db_open(struct db *_db, G_GNUC_UNUSED GError **error_r) +{ + struct simple_db *db = (struct simple_db *)_db; + + db->root = directory_new_root(); + db->mtime = 0; + + GError *error = NULL; + if (!simple_db_load(db, &error)) { + directory_free(db->root); + + g_warning("Failed to load database: %s", error->message); + g_error_free(error); + + if (!simple_db_check(db, error_r)) + return false; + + db->root = directory_new_root(); + } + + return true; +} + +static void +simple_db_close(struct db *_db) +{ + struct simple_db *db = (struct simple_db *)_db; + + assert(db->root != NULL); + + directory_free(db->root); +} + +static struct song * +simple_db_get_song(struct db *_db, const char *uri, GError **error_r) +{ + struct simple_db *db = (struct simple_db *)_db; + + assert(db->root != NULL); + + db_lock(); + struct song *song = directory_lookup_song(db->root, uri); + db_unlock(); + if (song == NULL) + g_set_error(error_r, db_quark(), DB_NOT_FOUND, + "No such song: %s", uri); + + return song; +} + +static bool +simple_db_visit(struct db *_db, const struct db_selection *selection, + const struct db_visitor *visitor, void *ctx, + GError **error_r) +{ + const struct simple_db *db = (const struct simple_db *)_db; + const struct directory *directory = + simple_db_lookup_directory(db, selection->uri); + if (directory == NULL) { + struct song *song; + if (visitor->song != NULL && + (song = simple_db_get_song(_db, selection->uri, NULL)) != NULL) + return visitor->song(song, ctx, error_r); + + g_set_error(error_r, db_quark(), DB_NOT_FOUND, + "No such directory"); + return false; + } + + if (selection->recursive && visitor->directory != NULL && + !visitor->directory(directory, ctx, error_r)) + return false; + + db_lock(); + bool ret = directory_walk(directory, selection->recursive, + visitor, ctx, error_r); + db_unlock(); + return ret; +} + +const struct db_plugin simple_db_plugin = { + .name = "simple", + .init = simple_db_init, + .finish = simple_db_finish, + .open = simple_db_open, + .close = simple_db_close, + .get_song = simple_db_get_song, + .visit = simple_db_visit, +}; + +struct directory * +simple_db_get_root(struct db *_db) +{ + struct simple_db *db = (struct simple_db *)_db; + + assert(db != NULL); + assert(db->root != NULL); + + return db->root; +} + +bool +simple_db_save(struct db *_db, GError **error_r) +{ + struct simple_db *db = (struct simple_db *)_db; + struct directory *music_root = db->root; + + db_lock(); + + g_debug("removing empty directories from DB"); + directory_prune_empty(music_root); + + g_debug("sorting DB"); + directory_sort(music_root); + + db_unlock(); + + g_debug("writing DB"); + + FILE *fp = fopen(db->path, "w"); + if (!fp) { + g_set_error(error_r, simple_db_quark(), errno, + "unable to write to db file \"%s\": %s", + db->path, g_strerror(errno)); + return false; + } + + db_save_internal(fp, music_root); + + if (ferror(fp)) { + g_set_error(error_r, simple_db_quark(), errno, + "Failed to write to database file: %s", + g_strerror(errno)); + fclose(fp); + return false; + } + + fclose(fp); + + struct stat st; + if (stat(db->path, &st) == 0) + db->mtime = st.st_mtime; + + return true; +} + +time_t +simple_db_get_mtime(const struct db *_db) +{ + const struct simple_db *db = (const struct simple_db *)_db; + + assert(db != NULL); + assert(db->root != NULL); + + return db->mtime; +} diff --git a/src/db/simple_db_plugin.h b/src/db/simple_db_plugin.h new file mode 100644 index 000000000..511505846 --- /dev/null +++ b/src/db/simple_db_plugin.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SIMPLE_DB_PLUGIN_H +#define MPD_SIMPLE_DB_PLUGIN_H + +#include <glib.h> +#include <stdbool.h> +#include <time.h> + +extern const struct db_plugin simple_db_plugin; + +struct db; + +G_GNUC_PURE +struct directory * +simple_db_get_root(struct db *db); + +bool +simple_db_save(struct db *db, GError **error_r); + +G_GNUC_PURE +time_t +simple_db_get_mtime(const struct db *db); + +#endif diff --git a/src/dbUtils.c b/src/dbUtils.c index f950d42cc..827d0a0c1 100644 --- a/src/dbUtils.c +++ b/src/dbUtils.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,311 +20,105 @@ #include "config.h" #include "dbUtils.h" #include "locate.h" -#include "directory.h" #include "database.h" -#include "client.h" +#include "db_visitor.h" #include "playlist.h" -#include "song.h" -#include "song_print.h" -#include "tag.h" -#include "strset.h" #include "stored_playlist.h" #include <glib.h> -#include <stdlib.h> - -typedef struct _ListCommandItem { - int8_t tagType; - const struct locate_item_list *criteria; -} ListCommandItem; - -typedef struct _SearchStats { - const struct locate_item_list *criteria; - int numberOfSongs; - unsigned long playTime; -} SearchStats; - -static int -printDirectoryInDirectory(struct directory *directory, void *data) -{ - struct client *client = data; - - if (!directory_is_root(directory)) - client_printf(client, "directory: %s\n", directory_get_path(directory)); - - return 0; -} - -static int -printSongInDirectory(struct song *song, G_GNUC_UNUSED void *data) -{ - struct client *client = data; - song_print_uri(client, song); - return 0; -} - -struct search_data { - struct client *client; - const struct locate_item_list *criteria; -}; - -static int -searchInDirectory(struct song *song, void *_data) -{ - struct search_data *data = _data; - - if (locate_song_search(song, data->criteria)) - song_print_info(data->client, song); - - return 0; -} - -int -searchForSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria) -{ - int ret; - struct locate_item_list *new_list - = locate_item_list_casefold(criteria); - struct search_data data; - - data.client = client; - data.criteria = new_list; - - ret = db_walk(name, searchInDirectory, NULL, &data); - - locate_item_list_free(new_list); - - return ret; -} - -static int -findInDirectory(struct song *song, void *_data) +static bool +add_to_queue_song(struct song *song, void *ctx, GError **error_r) { - struct search_data *data = _data; - - if (locate_song_match(song, data->criteria)) - song_print_info(data->client, song); + struct player_control *pc = ctx; - return 0; -} - -int -findSongsIn(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, findInDirectory, NULL, &data); -} - -static void printSearchStats(struct client *client, SearchStats *stats) -{ - client_printf(client, "songs: %i\n", stats->numberOfSongs); - client_printf(client, "playtime: %li\n", stats->playTime); -} - -static int -searchStatsInDirectory(struct song *song, void *data) -{ - SearchStats *stats = data; - - if (locate_song_match(song, stats->criteria)) { - stats->numberOfSongs++; - stats->playTime += song_get_duration(song); + enum playlist_result result = + playlist_append_song(&g_playlist, pc, song, NULL); + if (result != PLAYLIST_RESULT_SUCCESS) { + g_set_error(error_r, playlist_quark(), result, + "Playlist error"); + return false; } - return 0; -} - -int -searchStatsForSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria) -{ - SearchStats stats; - int ret; - - stats.criteria = criteria; - stats.numberOfSongs = 0; - stats.playTime = 0; - - ret = db_walk(name, searchStatsInDirectory, NULL, &stats); - if (ret == 0) - printSearchStats(client, &stats); - - return ret; + return true; } -int printAllIn(struct client *client, const char *name) -{ - return db_walk(name, printSongInDirectory, - printDirectoryInDirectory, client); -} +static const struct db_visitor add_to_queue_visitor = { + .song = add_to_queue_song, +}; -static int -directoryAddSongToPlaylist(struct song *song, G_GNUC_UNUSED void *data) +bool +addAllIn(struct player_control *pc, const char *uri, GError **error_r) { - return playlist_append_song(&g_playlist, song, NULL); + return db_walk(uri, &add_to_queue_visitor, pc, error_r); } struct add_data { const char *path; }; -static int -directoryAddSongToStoredPlaylist(struct song *song, void *_data) +static bool +add_to_spl_song(struct song *song, void *ctx, GError **error_r) { - struct add_data *data = _data; + struct add_data *data = ctx; - if (spl_append_song(data->path, song) != 0) - return -1; - return 0; -} + if (!spl_append_song(data->path, song, error_r)) + return false; -int addAllIn(const char *name) -{ - return db_walk(name, directoryAddSongToPlaylist, NULL, NULL); + return true; } -int addAllInToStoredPlaylist(const char *name, const char *utf8file) +static const struct db_visitor add_to_spl_visitor = { + .song = add_to_spl_song, +}; + +bool +addAllInToStoredPlaylist(const char *uri_utf8, const char *path_utf8, + GError **error_r) { struct add_data data = { - .path = utf8file, + .path = path_utf8, }; - return db_walk(name, directoryAddSongToStoredPlaylist, NULL, &data); + return db_walk(uri_utf8, &add_to_spl_visitor, &data, error_r); } -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; - song_print_info(client, song); - return 0; -} - -int printInfoForAllIn(struct client *client, const char *name) -{ - return db_walk(name, directoryPrintSongInfo, - printDirectoryInDirectory, client); -} - -static ListCommandItem * -newListCommandItem(int tagType, const struct locate_item_list *criteria) -{ - ListCommandItem *item = g_new(ListCommandItem, 1); - - item->tagType = tagType; - item->criteria = criteria; - - return item; -} - -static void freeListCommandItem(ListCommandItem * item) -{ - g_free(item); -} +struct find_add_data { + struct player_control *pc; + const struct locate_item_list *criteria; +}; -static void -visitTag(struct client *client, struct strset *set, - struct song *song, enum tag_type tagType) +static bool +find_add_song(struct song *song, void *ctx, GError **error_r) { - struct tag *tag = song->tag; - bool found = false; + struct find_add_data *data = ctx; - if (tagType == LOCATE_TAG_FILE_TYPE) { - song_print_uri(client, song); - return; - } - - if (!tag) - return; + if (!locate_song_match(song, data->criteria)) + return true; - for (unsigned i = 0; i < tag->num_items; i++) { - if (tag->items[i]->type == tagType) { - strset_add(set, tag->items[i]->value); - found = true; - } + enum playlist_result result = + playlist_append_song(&g_playlist, data->pc, + song, NULL); + if (result != PLAYLIST_RESULT_SUCCESS) { + g_set_error(error_r, playlist_quark(), result, + "Playlist error"); + return false; } - if (!found) - strset_add(set, ""); + return true; } -struct list_tags_data { - struct client *client; - ListCommandItem *item; - struct strset *set; +static const struct db_visitor find_add_visitor = { + .song = find_add_song, }; -static int -listUniqueTagsInDirectory(struct song *song, void *_data) -{ - struct list_tags_data *data = _data; - ListCommandItem *item = data->item; - - if (locate_song_match(song, item->criteria)) - visitTag(data->client, data->set, song, item->tagType); - - return 0; -} - -int listAllUniqueTags(struct client *client, int type, - const struct locate_item_list *criteria) +bool +findAddIn(struct player_control *pc, const char *name, + const struct locate_item_list *criteria, GError **error_r) { - int ret; - ListCommandItem *item = newListCommandItem(type, criteria); - struct list_tags_data data = { - .client = client, - .item = item, - }; - - if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) { - data.set = strset_new(); - } - - ret = db_walk(NULL, listUniqueTagsInDirectory, NULL, &data); - - if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) { - const char *value; - - strset_rewind(data.set); - - while ((value = strset_next(data.set)) != NULL) - client_printf(client, "%s: %s\n", - tag_item_names[type], - value); - - strset_free(data.set); - } - - freeListCommandItem(item); + struct find_add_data data; + data.pc = pc; + data.criteria = criteria; - return ret; + return db_walk(name, &find_add_visitor, &data, error_r); } diff --git a/src/dbUtils.h b/src/dbUtils.h index bba253154..40594652b 100644 --- a/src/dbUtils.h +++ b/src/dbUtils.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,37 +20,26 @@ #ifndef MPD_DB_UTILS_H #define MPD_DB_UTILS_H -struct client; -struct locate_item_list; - -int printAllIn(struct client *client, const char *name); - -int addAllIn(const char *name); - -int addAllInToStoredPlaylist(const char *name, const char *utf8file); +#include "gcc.h" -int printInfoForAllIn(struct client *client, const char *name); +#include <glib.h> +#include <stdbool.h> -int -searchForSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria); - -int -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); +struct locate_item_list; +struct player_control; -int -searchStatsForSongsIn(struct client *client, const char *name, - const struct locate_item_list *criteria); +gcc_nonnull(1,2) +bool +addAllIn(struct player_control *pc, const char *uri, GError **error_r); -unsigned long sumSongTimesIn(const char *name); +gcc_nonnull(1,2) +bool +addAllInToStoredPlaylist(const char *uri_utf8, const char *path_utf8, + GError **error_r); -int -listAllUniqueTags(struct client *client, int type, - const struct locate_item_list *criteria); +gcc_nonnull(1,2,3) +bool +findAddIn(struct player_control *pc, const char *name, + const struct locate_item_list *criteria, GError **error_r); #endif diff --git a/src/db_error.h b/src/db_error.h new file mode 100644 index 000000000..d3be582cf --- /dev/null +++ b/src/db_error.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_DB_ERROR_H +#define MPD_DB_ERROR_H + +#include <glib.h> + +enum db_error { + /** + * The database is disabled, i.e. none is configured in this + * MPD instance. + */ + DB_DISABLED, + + DB_NOT_FOUND, +}; + +/** + * Quark for GError.domain; the code is an enum #db_error. + */ +G_GNUC_CONST +static inline GQuark +db_quark(void) +{ + return g_quark_from_static_string("db"); +} + +#endif diff --git a/src/db_internal.h b/src/db_internal.h new file mode 100644 index 000000000..a33351524 --- /dev/null +++ b/src/db_internal.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_DB_INTERNAL_H +#define MPD_DB_INTERNAL_H + +#include "db_plugin.h" + +#include <assert.h> + +static inline void +db_base_init(struct db *db, const struct db_plugin *plugin) +{ + assert(plugin != NULL); + + db->plugin = plugin; +} + +#endif diff --git a/src/db_lock.c b/src/db_lock.c new file mode 100644 index 000000000..53759673d --- /dev/null +++ b/src/db_lock.c @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "db_lock.h" +#include "gcc.h" + +#if GCC_CHECK_VERSION(4, 2) +/* workaround for a warning caused by G_STATIC_MUTEX_INIT */ +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + +GStaticMutex db_mutex = G_STATIC_MUTEX_INIT; + +#ifndef NDEBUG +GThread *db_mutex_holder; +#endif diff --git a/src/db_lock.h b/src/db_lock.h new file mode 100644 index 000000000..4640502f3 --- /dev/null +++ b/src/db_lock.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * Support for locking data structures from the database, for safe + * multi-threading. + */ + +#ifndef MPD_DB_LOCK_H +#define MPD_DB_LOCK_H + +#include "check.h" + +#include <glib.h> +#include <assert.h> +#include <stdbool.h> + +extern GStaticMutex db_mutex; + +#ifndef NDEBUG + +extern GThread *db_mutex_holder; + +/** + * Does the current thread hold the database lock? + */ +G_GNUC_PURE +static inline bool +holding_db_lock(void) +{ + return db_mutex_holder == g_thread_self(); +} + +#endif + +/** + * Obtain the global database lock. This is needed before + * dereferencing a #song or #directory. It is not recursive. + */ +static inline void +db_lock(void) +{ + assert(!holding_db_lock()); + + g_static_mutex_lock(&db_mutex); + + assert(db_mutex_holder == NULL); +#ifndef NDEBUG + db_mutex_holder = g_thread_self(); +#endif +} + +/** + * Release the global database lock. + */ +static inline void +db_unlock(void) +{ + assert(holding_db_lock()); +#ifndef NDEBUG + db_mutex_holder = NULL; +#endif + + g_static_mutex_unlock(&db_mutex); +} + +#endif diff --git a/src/db_plugin.h b/src/db_plugin.h new file mode 100644 index 000000000..1c7e14ede --- /dev/null +++ b/src/db_plugin.h @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 db_plugin class. It describes a + * plugin API for databases of song metadata. + */ + +#ifndef MPD_DB_PLUGIN_H +#define MPD_DB_PLUGIN_H + +#include <glib.h> +#include <assert.h> +#include <stdbool.h> + +struct config_param; +struct db_selection; +struct db_visitor; + +struct db { + const struct db_plugin *plugin; +}; + +struct db_plugin { + const char *name; + + /** + * Allocates and configures a database. + */ + struct db *(*init)(const struct config_param *param, GError **error_r); + + /** + * Free instance data. + */ + void (*finish)(struct db *db); + + /** + * Open the database. Read it into memory if applicable. + */ + bool (*open)(struct db *db, GError **error_r); + + /** + * Close the database, free allocated memory. + */ + void (*close)(struct db *db); + + /** + * Look up a song (including tag data) in the database. + * + * @param the URI of the song within the music directory + * (UTF-8) + */ + struct song *(*get_song)(struct db *db, const char *uri, + GError **error_r); + + /** + * Visit the selected entities. + */ + bool (*visit)(struct db *db, const struct db_selection *selection, + const struct db_visitor *visitor, void *ctx, + GError **error_r); +}; + +G_GNUC_MALLOC +static inline struct db * +db_plugin_new(const struct db_plugin *plugin, const struct config_param *param, + GError **error_r) +{ + assert(plugin != NULL); + assert(plugin->init != NULL); + assert(plugin->finish != NULL); + assert(plugin->get_song != NULL); + assert(plugin->visit != NULL); + assert(error_r == NULL || *error_r == NULL); + + struct db *db = plugin->init(param, error_r); + assert(db == NULL || db->plugin == plugin); + assert(db != NULL || error_r == NULL || *error_r != NULL); + + return db; +} + +static inline void +db_plugin_free(struct db *db) +{ + assert(db != NULL); + assert(db->plugin != NULL); + assert(db->plugin->finish != NULL); + + db->plugin->finish(db); +} + +static inline bool +db_plugin_open(struct db *db, GError **error_r) +{ + assert(db != NULL); + assert(db->plugin != NULL); + + return db->plugin->open != NULL + ? db->plugin->open(db, error_r) + : true; +} + +static inline void +db_plugin_close(struct db *db) +{ + assert(db != NULL); + assert(db->plugin != NULL); + + if (db->plugin->close != NULL) + db->plugin->close(db); +} + +static inline struct song * +db_plugin_get_song(struct db *db, const char *uri, GError **error_r) +{ + assert(db != NULL); + assert(db->plugin != NULL); + assert(db->plugin->get_song != NULL); + assert(uri != NULL); + + return db->plugin->get_song(db, uri, error_r); +} + +static inline bool +db_plugin_visit(struct db *db, const struct db_selection *selection, + const struct db_visitor *visitor, void *ctx, + GError **error_r) +{ + assert(db != NULL); + assert(db->plugin != NULL); + assert(selection != NULL); + assert(visitor != NULL); + assert(error_r == NULL || *error_r == NULL); + + return db->plugin->visit(db, selection, visitor, ctx, error_r); +} + +#endif diff --git a/src/db_print.c b/src/db_print.c new file mode 100644 index 000000000..4d7e3f5ef --- /dev/null +++ b/src/db_print.c @@ -0,0 +1,393 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "db_print.h" +#include "db_selection.h" +#include "db_visitor.h" +#include "locate.h" +#include "directory.h" +#include "database.h" +#include "client.h" +#include "song.h" +#include "song_print.h" +#include "playlist_vector.h" +#include "tag.h" +#include "strset.h" + +#include <glib.h> + +typedef struct _ListCommandItem { + int8_t tagType; + const struct locate_item_list *criteria; +} ListCommandItem; + +typedef struct _SearchStats { + const struct locate_item_list *criteria; + int numberOfSongs; + unsigned long playTime; +} SearchStats; + +static bool +print_visitor_directory(const struct directory *directory, void *data, + G_GNUC_UNUSED GError **error_r) +{ + struct client *client = data; + + if (!directory_is_root(directory)) + client_printf(client, "directory: %s\n", directory_get_path(directory)); + + return true; +} + +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); +} + +static bool +print_visitor_song(struct song *song, void *data, + G_GNUC_UNUSED GError **error_r) +{ + assert(song != NULL); + assert(song->parent != NULL); + + struct client *client = data; + song_print_uri(client, song); + + if (song->tag != NULL && song->tag->has_playlist) + /* this song file has an embedded CUE sheet */ + print_playlist_in_directory(client, song->parent, + song->uri); + + return true; +} + +static bool +print_visitor_song_info(struct song *song, void *data, + G_GNUC_UNUSED GError **error_r) +{ + assert(song != NULL); + assert(song->parent != NULL); + + struct client *client = data; + song_print_info(client, song); + + if (song->tag != NULL && song->tag->has_playlist) + /* this song file has an embedded CUE sheet */ + print_playlist_in_directory(client, song->parent, + song->uri); + + return true; +} + +static bool +print_visitor_playlist(const struct playlist_metadata *playlist, + const struct directory *directory, void *ctx, + G_GNUC_UNUSED GError **error_r) +{ + struct client *client = ctx; + print_playlist_in_directory(client, directory, playlist->name); + return true; +} + +static bool +print_visitor_playlist_info(const struct playlist_metadata *playlist, + const struct directory *directory, + void *ctx, G_GNUC_UNUSED GError **error_r) +{ + struct client *client = ctx; + print_playlist_in_directory(client, directory, playlist->name); + +#ifndef G_OS_WIN32 + struct tm tm; +#endif + char timestamp[32]; + time_t t = playlist->mtime; + strftime(timestamp, sizeof(timestamp), +#ifdef G_OS_WIN32 + "%Y-%m-%dT%H:%M:%SZ", + gmtime(&t) +#else + "%FT%TZ", + gmtime_r(&t, &tm) +#endif + ); + client_printf(client, "Last-Modified: %s\n", timestamp); + + return true; +} + +static const struct db_visitor print_visitor = { + .directory = print_visitor_directory, + .song = print_visitor_song, + .playlist = print_visitor_playlist, +}; + +static const struct db_visitor print_info_visitor = { + .directory = print_visitor_directory, + .song = print_visitor_song_info, + .playlist = print_visitor_playlist_info, +}; + +bool +db_selection_print(struct client *client, const struct db_selection *selection, + bool full, GError **error_r) +{ + return db_visit(selection, full ? &print_info_visitor : &print_visitor, + client, error_r); +} + +struct search_data { + struct client *client; + const struct locate_item_list *criteria; +}; + +static bool +search_visitor_song(struct song *song, void *_data, + G_GNUC_UNUSED GError **error_r) +{ + struct search_data *data = _data; + + if (locate_song_search(song, data->criteria)) + song_print_info(data->client, song); + + return true; +} + +static const struct db_visitor search_visitor = { + .song = search_visitor_song, +}; + +bool +searchForSongsIn(struct client *client, const char *name, + const struct locate_item_list *criteria, + GError **error_r) +{ + struct locate_item_list *new_list + = locate_item_list_casefold(criteria); + struct search_data data; + + data.client = client; + data.criteria = new_list; + + bool success = db_walk(name, &search_visitor, &data, error_r); + + locate_item_list_free(new_list); + + return success; +} + +static bool +find_visitor_song(struct song *song, void *_data, + G_GNUC_UNUSED GError **error_r) +{ + struct search_data *data = _data; + + if (locate_song_match(song, data->criteria)) + song_print_info(data->client, song); + + return true; +} + +static const struct db_visitor find_visitor = { + .song = find_visitor_song, +}; + +bool +findSongsIn(struct client *client, const char *name, + const struct locate_item_list *criteria, + GError **error_r) +{ + struct search_data data; + + data.client = client; + data.criteria = criteria; + + return db_walk(name, &find_visitor, &data, error_r); +} + +static void printSearchStats(struct client *client, SearchStats *stats) +{ + client_printf(client, "songs: %i\n", stats->numberOfSongs); + client_printf(client, "playtime: %li\n", stats->playTime); +} + +static bool +stats_visitor_song(struct song *song, void *data, + G_GNUC_UNUSED GError **error_r) +{ + SearchStats *stats = data; + + if (locate_song_match(song, stats->criteria)) { + stats->numberOfSongs++; + stats->playTime += song_get_duration(song); + } + + return true; +} + +static const struct db_visitor stats_visitor = { + .song = stats_visitor_song, +}; + +bool +searchStatsForSongsIn(struct client *client, const char *name, + const struct locate_item_list *criteria, + GError **error_r) +{ + SearchStats stats; + + stats.criteria = criteria; + stats.numberOfSongs = 0; + stats.playTime = 0; + + if (!db_walk(name, &stats_visitor, &stats, error_r)) + return false; + + printSearchStats(client, &stats); + return true; +} + +bool +printAllIn(struct client *client, const char *uri_utf8, GError **error_r) +{ + struct db_selection selection; + db_selection_init(&selection, uri_utf8, true); + return db_selection_print(client, &selection, false, error_r); +} + +bool +printInfoForAllIn(struct client *client, const char *uri_utf8, + GError **error_r) +{ + struct db_selection selection; + db_selection_init(&selection, uri_utf8, true); + return db_selection_print(client, &selection, true, error_r); +} + +static ListCommandItem * +newListCommandItem(int tagType, const struct locate_item_list *criteria) +{ + ListCommandItem *item = g_new(ListCommandItem, 1); + + item->tagType = tagType; + item->criteria = criteria; + + return item; +} + +static void freeListCommandItem(ListCommandItem * item) +{ + g_free(item); +} + +static void +visitTag(struct client *client, struct strset *set, + struct song *song, enum tag_type tagType) +{ + struct tag *tag = song->tag; + bool found = false; + + if (tagType == LOCATE_TAG_FILE_TYPE) { + song_print_uri(client, song); + return; + } + + if (!tag) + return; + + for (unsigned i = 0; i < tag->num_items; i++) { + if (tag->items[i]->type == tagType) { + strset_add(set, tag->items[i]->value); + found = true; + } + } + + if (!found) + strset_add(set, ""); +} + +struct list_tags_data { + struct client *client; + ListCommandItem *item; + struct strset *set; +}; + +static bool +unique_tags_visitor_song(struct song *song, void *_data, + G_GNUC_UNUSED GError **error_r) +{ + struct list_tags_data *data = _data; + ListCommandItem *item = data->item; + + if (locate_song_match(song, item->criteria)) + visitTag(data->client, data->set, song, item->tagType); + + return true; +} + +static const struct db_visitor unique_tags_visitor = { + .song = unique_tags_visitor_song, +}; + +bool +listAllUniqueTags(struct client *client, int type, + const struct locate_item_list *criteria, + GError **error_r) +{ + ListCommandItem *item = newListCommandItem(type, criteria); + struct list_tags_data data = { + .client = client, + .item = item, + }; + + if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) { + data.set = strset_new(); + } + + if (!db_walk("", &unique_tags_visitor, &data, error_r)) { + freeListCommandItem(item); + return false; + } + + if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) { + const char *value; + + strset_rewind(data.set); + + while ((value = strset_next(data.set)) != NULL) + client_printf(client, "%s: %s\n", + tag_item_names[type], + value); + + strset_free(data.set); + } + + freeListCommandItem(item); + + return true; +} diff --git a/src/db_print.h b/src/db_print.h new file mode 100644 index 000000000..1b957da18 --- /dev/null +++ b/src/db_print.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_DB_PRINT_H +#define MPD_DB_PRINT_H + +#include "gcc.h" + +#include <glib.h> +#include <stdbool.h> + +struct client; +struct locate_item_list; +struct db_selection; +struct db_visitor; + +gcc_nonnull(1,2) +bool +db_selection_print(struct client *client, const struct db_selection *selection, + bool full, GError **error_r); + +gcc_nonnull(1,2) +bool +printAllIn(struct client *client, const char *uri_utf8, GError **error_r); + +gcc_nonnull(1,2) +bool +printInfoForAllIn(struct client *client, const char *uri_utf8, + GError **error_r); + +gcc_nonnull(1,2,3) +bool +searchForSongsIn(struct client *client, const char *name, + const struct locate_item_list *criteria, + GError **error_r); + +gcc_nonnull(1,2,3) +bool +findSongsIn(struct client *client, const char *name, + const struct locate_item_list *criteria, + GError **error_r); + +gcc_nonnull(1,2,3) +bool +searchStatsForSongsIn(struct client *client, const char *name, + const struct locate_item_list *criteria, + GError **error_r); + +gcc_nonnull(1,3) +bool +listAllUniqueTags(struct client *client, int type, + const struct locate_item_list *criteria, + GError **error_r); + +#endif diff --git a/src/db_save.c b/src/db_save.c new file mode 100644 index 000000000..4af9d58b8 --- /dev/null +++ b/src/db_save.c @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "db_save.h" +#include "db_lock.h" +#include "directory.h" +#include "directory_save.h" +#include "song.h" +#include "path.h" +#include "text_file.h" +#include "tag.h" +#include "tag_internal.h" + +#include <glib.h> + +#include <assert.h> +#include <stdlib.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "database" + +#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, +}; + +G_GNUC_CONST +static GQuark +db_quark(void) +{ + return g_quark_from_static_string("database"); +} + +void +db_save_internal(FILE *fp, const struct directory *music_root) +{ + assert(music_root != NULL); + + 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); + + directory_save(fp, music_root); +} + +bool +db_load_internal(FILE *fp, struct directory *music_root, GError **error) +{ + 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(music_root != NULL); + + /* get initial info */ + line = read_text_line(fp, buffer); + if (line == NULL || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) { + g_set_error(error, db_quark(), 0, "Database corrupted"); + g_string_free(buffer, true); + return false; + } + + memset(tags, false, sizeof(tags)); + + 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) { + 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(line, DIRECTORY_FS_CHARSET)) { + const char *new_charset, *old_charset; + + if (found_charset) { + g_set_error(error, db_quark(), 0, + "Duplicate charset line"); + g_string_free(buffer, true); + return false; + } + + found_charset = true; + + new_charset = line + sizeof(DIRECTORY_FS_CHARSET) - 1; + old_charset = path_get_fs_charset(); + if (old_charset != NULL + && strcmp(new_charset, old_charset)) { + g_set_error(error, db_quark(), 0, + "Existing database has charset " + "\"%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 { + g_set_error(error, db_quark(), 0, + "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"); + + db_lock(); + success = directory_load(fp, music_root, buffer, error); + db_unlock(); + g_string_free(buffer, true); + + return success; +} diff --git a/src/db_save.h b/src/db_save.h new file mode 100644 index 000000000..e760ec881 --- /dev/null +++ b/src/db_save.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_DB_SAVE_H +#define MPD_DB_SAVE_H + +#include <glib.h> +#include <stdbool.h> +#include <stdio.h> + +struct directory; + +void +db_save_internal(FILE *file, const struct directory *root); + +bool +db_load_internal(FILE *file, struct directory *root, GError **error); + +#endif diff --git a/src/db_selection.h b/src/db_selection.h new file mode 100644 index 000000000..2cebb4907 --- /dev/null +++ b/src/db_selection.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_DB_SELECTION_H +#define MPD_DB_SELECTION_H + +#include "gcc.h" + +#include <assert.h> + +struct directory; +struct song; + +struct db_selection { + /** + * The base URI of the search (UTF-8). Must not begin or end + * with a slash. NULL or an empty string searches the whole + * database. + */ + const char *uri; + + /** + * Recursively search all sub directories? + */ + bool recursive; +}; + +gcc_nonnull(1,2) +static inline void +db_selection_init(struct db_selection *selection, + const char *uri, bool recursive) +{ + assert(selection != NULL); + assert(uri != NULL); + + selection->uri = uri; + selection->recursive = recursive; +} + +#endif diff --git a/src/db_visitor.h b/src/db_visitor.h new file mode 100644 index 000000000..6b90c1868 --- /dev/null +++ b/src/db_visitor.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_DB_VISITOR_H +#define MPD_DB_VISITOR_H + +struct directory; +struct song; +struct playlist_metadata; + +struct db_visitor { + /** + * Visit a directory. Optional method. + * + * @return true to continue the operation, false on error (set error_r) + */ + bool (*directory)(const struct directory *directory, void *ctx, + GError **error_r); + + /** + * Visit a song. Optional method. + * + * @return true to continue the operation, false on error (set error_r) + */ + bool (*song)(struct song *song, void *ctx, GError **error_r); + + /** + * Visit a playlist. Optional method. + * + * @param directory the directory the playlist resides in + * @return true to continue the operation, false on error (set error_r) + */ + bool (*playlist)(const struct playlist_metadata *playlist, + const struct directory *directory, void *ctx, + GError **error_r); +}; + +#endif diff --git a/src/decoder/_flac_common.c b/src/decoder/_flac_common.c index 8dd22a253..bab3995f0 100644 --- a/src/decoder/_flac_common.c +++ b/src/decoder/_flac_common.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -139,26 +139,13 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block, } } -void flac_error_common_cb(const char *plugin, - const FLAC__StreamDecoderErrorStatus status, +void flac_error_common_cb(const FLAC__StreamDecoderErrorStatus status, struct flac_data *data) { if (decoder_get_command(data->decoder) == DECODE_COMMAND_STOP) return; - switch (status) { - case FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC: - g_warning("%s lost sync\n", plugin); - break; - case FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER: - g_warning("bad %s header\n", plugin); - break; - case FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH: - g_warning("%s crc mismatch\n", plugin); - break; - default: - g_warning("unknown %s error\n", plugin); - } + g_warning("%s", FLAC__StreamDecoderErrorStatusString[status]); } /** diff --git a/src/decoder/_flac_common.h b/src/decoder/_flac_common.h index 5c59ee123..0d90ba656 100644 --- a/src/decoder/_flac_common.h +++ b/src/decoder/_flac_common.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -94,8 +94,7 @@ flac_data_deinit(struct flac_data *data); void flac_metadata_common_cb(const FLAC__StreamMetadata * block, struct flac_data *data); -void flac_error_common_cb(const char *plugin, - FLAC__StreamDecoderErrorStatus status, +void flac_error_common_cb(FLAC__StreamDecoderErrorStatus status, struct flac_data *data); FLAC__StreamDecoderWriteStatus diff --git a/src/decoder/_ogg_common.c b/src/decoder/_ogg_common.c index bd0650ac4..bedd3de61 100644 --- a/src/decoder/_ogg_common.c +++ b/src/decoder/_ogg_common.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/_ogg_common.h b/src/decoder/_ogg_common.h index f8446c69c..85e4ebba6 100644 --- a/src/decoder/_ogg_common.h +++ b/src/decoder/_ogg_common.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/audiofile_decoder_plugin.c b/src/decoder/audiofile_decoder_plugin.c index de236a6e2..b344795e7 100644 --- a/src/decoder/audiofile_decoder_plugin.c +++ b/src/decoder/audiofile_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ #include "config.h" #include "decoder_api.h" #include "audio_check.h" +#include "tag_handler.h" #include <audiofile.h> #include <af_vfs.h> @@ -54,7 +55,7 @@ audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length) GError *error = NULL; size_t nbytes; - nbytes = input_stream_read(is, data, length, &error); + nbytes = input_stream_lock_read(is, data, length, &error); if (nbytes == 0 && error != NULL) { g_warning("%s", error->message); g_error_free(error); @@ -91,7 +92,7 @@ audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset 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, NULL)) { + if (input_stream_lock_seek(is, offset, whence, NULL)) { return is->offset; } else { return -1; @@ -222,20 +223,20 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is) afCloseFile(af_fp); } -static struct tag *audiofile_tag_dup(const char *file) +static bool +audiofile_scan_file(const char *file, + const struct tag_handler *handler, void *handler_ctx) { - struct tag *ret = NULL; int total_time = audiofile_get_duration(file); - if (total_time >= 0) { - ret = tag_new(); - ret->time = total_time; - } else { + if (total_time < 0) { g_debug("Failed to get total song time from: %s\n", file); + return false; } - return ret; + tag_handler_invoke_duration(handler, handler_ctx, total_time); + return true; } static const char *const audiofile_suffixes[] = { @@ -251,7 +252,7 @@ static const char *const audiofile_mime_types[] = { const struct decoder_plugin audiofile_decoder_plugin = { .name = "audiofile", .stream_decode = audiofile_stream_decode, - .tag_dup = audiofile_tag_dup, + .scan_file = audiofile_scan_file, .suffixes = audiofile_suffixes, .mime_types = audiofile_mime_types, }; diff --git a/src/decoder/dsdiff_decoder_plugin.c b/src/decoder/dsdiff_decoder_plugin.c new file mode 100644 index 000000000..ae42002dd --- /dev/null +++ b/src/decoder/dsdiff_decoder_plugin.c @@ -0,0 +1,458 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 plugin decodes DSDIFF data (SACD) embedded in DFF files. It + * was modeled after the specification found here: + * http://www.sonicstudio.com/pdf/dsd/DSDIFF_1.5_Spec.pdf + */ + +#include "config.h" +#include "dsdiff_decoder_plugin.h" +#include "decoder_api.h" +#include "audio_check.h" +#include "util/bit_reverse.h" + +#include <unistd.h> +#include <stdio.h> /* for SEEK_SET, SEEK_CUR */ + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "dsdiff" + +struct dsdiff_id { + char value[4]; +}; + +struct dsdiff_header { + struct dsdiff_id id; + uint32_t size_high, size_low; + struct dsdiff_id format; +}; + +struct dsdiff_chunk_header { + struct dsdiff_id id; + uint32_t size_high, size_low; +}; + +struct dsdiff_metadata { + unsigned sample_rate, channels; +}; + +static bool lsbitfirst; + +static bool +dsdiff_init(const struct config_param *param) +{ + lsbitfirst = config_get_block_bool(param, "lsbitfirst", false); + return true; +} + +static bool +dsdiff_id_equals(const struct dsdiff_id *id, const char *s) +{ + assert(id != NULL); + assert(s != NULL); + assert(strlen(s) == sizeof(id->value)); + + return memcmp(id->value, s, sizeof(id->value)) == 0; +} + +/** + * Read the "size" attribute from the specified header, converting it + * to the host byte order if needed. + */ +G_GNUC_CONST +static uint64_t +dsdiff_chunk_size(const struct dsdiff_chunk_header *header) +{ + return (((uint64_t)GUINT32_FROM_BE(header->size_high)) << 32) | + ((uint64_t)GUINT32_FROM_BE(header->size_low)); +} + +static bool +dsdiff_read(struct decoder *decoder, struct input_stream *is, + void *data, size_t length) +{ + size_t nbytes = decoder_read(decoder, is, data, length); + return nbytes == length; +} + +static bool +dsdiff_read_id(struct decoder *decoder, struct input_stream *is, + struct dsdiff_id *id) +{ + return dsdiff_read(decoder, is, id, sizeof(*id)); +} + +static bool +dsdiff_read_chunk_header(struct decoder *decoder, struct input_stream *is, + struct dsdiff_chunk_header *header) +{ + return dsdiff_read(decoder, is, header, sizeof(*header)); +} + +static bool +dsdiff_read_payload(struct decoder *decoder, struct input_stream *is, + const struct dsdiff_chunk_header *header, + void *data, size_t length) +{ + uint64_t size = dsdiff_chunk_size(header); + if (size != (uint64_t)length) + return false; + + size_t nbytes = decoder_read(decoder, is, data, length); + return nbytes == length; +} + +/** + * Skip the #input_stream to the specified offset. + */ +static bool +dsdiff_skip_to(struct decoder *decoder, struct input_stream *is, + goffset offset) +{ + if (is->seekable) + return input_stream_seek(is, offset, SEEK_SET, NULL); + + if (is->offset > offset) + return false; + + char buffer[8192]; + while (is->offset < offset) { + size_t length = sizeof(buffer); + if (offset - is->offset < (goffset)length) + length = offset - is->offset; + + size_t nbytes = decoder_read(decoder, is, buffer, length); + if (nbytes == 0) + return false; + } + + assert(is->offset == offset); + return true; +} + +/** + * Skip some bytes from the #input_stream. + */ +static bool +dsdiff_skip(struct decoder *decoder, struct input_stream *is, + goffset delta) +{ + assert(delta >= 0); + + if (delta == 0) + return true; + + if (is->seekable) + return input_stream_seek(is, delta, SEEK_CUR, NULL); + + char buffer[8192]; + while (delta > 0) { + size_t length = sizeof(buffer); + if ((goffset)length > delta) + length = delta; + + size_t nbytes = decoder_read(decoder, is, buffer, length); + if (nbytes == 0) + return false; + + delta -= nbytes; + } + + return true; +} + +/** + * Read and parse a "SND" chunk inside "PROP". + */ +static bool +dsdiff_read_prop_snd(struct decoder *decoder, struct input_stream *is, + struct dsdiff_metadata *metadata, + goffset end_offset) +{ + struct dsdiff_chunk_header header; + while ((goffset)(is->offset + sizeof(header)) <= end_offset) { + if (!dsdiff_read_chunk_header(decoder, is, &header)) + return false; + + goffset chunk_end_offset = + is->offset + dsdiff_chunk_size(&header); + if (chunk_end_offset > end_offset) + return false; + + if (dsdiff_id_equals(&header.id, "FS ")) { + uint32_t sample_rate; + if (!dsdiff_read_payload(decoder, is, &header, + &sample_rate, + sizeof(sample_rate))) + return false; + + metadata->sample_rate = GUINT32_FROM_BE(sample_rate); + } else if (dsdiff_id_equals(&header.id, "CHNL")) { + uint16_t channels; + if (dsdiff_chunk_size(&header) < sizeof(channels) || + !dsdiff_read(decoder, is, + &channels, sizeof(channels)) || + !dsdiff_skip_to(decoder, is, chunk_end_offset)) + return false; + + metadata->channels = GUINT16_FROM_BE(channels); + } else if (dsdiff_id_equals(&header.id, "CMPR")) { + struct dsdiff_id type; + if (dsdiff_chunk_size(&header) < sizeof(type) || + !dsdiff_read(decoder, is, + &type, sizeof(type)) || + !dsdiff_skip_to(decoder, is, chunk_end_offset)) + return false; + + if (!dsdiff_id_equals(&type, "DSD ")) + /* only uincompressed DSD audio data + is implemented */ + return false; + } else { + /* ignore unknown chunk */ + + if (!dsdiff_skip_to(decoder, is, chunk_end_offset)) + return false; + } + } + + return is->offset == end_offset; +} + +/** + * Read and parse a "PROP" chunk. + */ +static bool +dsdiff_read_prop(struct decoder *decoder, struct input_stream *is, + struct dsdiff_metadata *metadata, + const struct dsdiff_chunk_header *prop_header) +{ + uint64_t prop_size = dsdiff_chunk_size(prop_header); + goffset end_offset = is->offset + prop_size; + + struct dsdiff_id prop_id; + if (prop_size < sizeof(prop_id) || + !dsdiff_read_id(decoder, is, &prop_id)) + return false; + + if (dsdiff_id_equals(&prop_id, "SND ")) + return dsdiff_read_prop_snd(decoder, is, metadata, end_offset); + else + /* ignore unknown PROP chunk */ + return dsdiff_skip_to(decoder, is, end_offset); +} + +/** + * Read and parse all metadata chunks at the beginning. Stop when the + * first "DSD" chunk is seen, and return its header in the + * "chunk_header" parameter. + */ +static bool +dsdiff_read_metadata(struct decoder *decoder, struct input_stream *is, + struct dsdiff_metadata *metadata, + struct dsdiff_chunk_header *chunk_header) +{ + struct dsdiff_header header; + if (!dsdiff_read(decoder, is, &header, sizeof(header)) || + !dsdiff_id_equals(&header.id, "FRM8") || + !dsdiff_id_equals(&header.format, "DSD ")) + return false; + + while (true) { + if (!dsdiff_read_chunk_header(decoder, is, chunk_header)) + return false; + + if (dsdiff_id_equals(&chunk_header->id, "PROP")) { + if (!dsdiff_read_prop(decoder, is, metadata, + chunk_header)) + return false; + } else if (dsdiff_id_equals(&chunk_header->id, "DSD ")) { + /* done with metadata */ + return true; + } else { + /* ignore unknown chunk */ + + uint64_t chunk_size = dsdiff_chunk_size(chunk_header); + goffset chunk_end_offset = is->offset + chunk_size; + + if (!dsdiff_skip_to(decoder, is, chunk_end_offset)) + return false; + } + } +} + +static void +bit_reverse_buffer(uint8_t *p, uint8_t *end) +{ + for (; p < end; ++p) + *p = bit_reverse(*p); +} + +/** + * Decode one "DSD" chunk. + */ +static bool +dsdiff_decode_chunk(struct decoder *decoder, struct input_stream *is, + unsigned channels, + uint64_t chunk_size) +{ + uint8_t buffer[8192]; + const size_t sample_size = sizeof(buffer[0]); + const size_t frame_size = channels * sample_size; + const unsigned buffer_frames = sizeof(buffer) / frame_size; + const unsigned buffer_samples = buffer_frames * frame_size; + const size_t buffer_size = buffer_samples * sample_size; + + while (chunk_size > 0) { + /* see how much aligned data from the remaining chunk + fits into the local buffer */ + unsigned now_frames = buffer_frames; + size_t now_size = buffer_size; + if (chunk_size < (uint64_t)now_size) { + now_frames = (unsigned)chunk_size / frame_size; + now_size = now_frames * frame_size; + } + + size_t nbytes = decoder_read(decoder, is, buffer, now_size); + if (nbytes != now_size) + return false; + + chunk_size -= nbytes; + + if (lsbitfirst) + bit_reverse_buffer(buffer, buffer + nbytes); + + enum decoder_command cmd = + decoder_data(decoder, is, buffer, nbytes, 0); + switch (cmd) { + case DECODE_COMMAND_NONE: + break; + + case DECODE_COMMAND_START: + case DECODE_COMMAND_STOP: + return false; + + case DECODE_COMMAND_SEEK: + /* not implemented yet */ + decoder_seek_error(decoder); + break; + } + } + + return dsdiff_skip(decoder, is, chunk_size); +} + +static void +dsdiff_stream_decode(struct decoder *decoder, struct input_stream *is) +{ + struct dsdiff_metadata metadata = { + .sample_rate = 0, + .channels = 0, + }; + + struct dsdiff_chunk_header chunk_header; + if (!dsdiff_read_metadata(decoder, is, &metadata, &chunk_header)) + return; + + GError *error = NULL; + struct audio_format audio_format; + if (!audio_format_init_checked(&audio_format, metadata.sample_rate / 8, + SAMPLE_FORMAT_DSD, + metadata.channels, &error)) { + g_warning("%s", error->message); + g_error_free(error); + return; + } + + /* success: file was recognized */ + + decoder_initialized(decoder, &audio_format, false, -1); + + /* every iteration of the following loop decodes one "DSD" + chunk */ + + while (true) { + uint64_t chunk_size = dsdiff_chunk_size(&chunk_header); + + if (dsdiff_id_equals(&chunk_header.id, "DSD ")) { + if (!dsdiff_decode_chunk(decoder, is, + metadata.channels, + chunk_size)) + break; + } else { + /* ignore other chunks */ + + if (!dsdiff_skip(decoder, is, chunk_size)) + break; + } + + /* read next chunk header; the first one was read by + dsdiff_read_metadata() */ + + if (!dsdiff_read_chunk_header(decoder, is, &chunk_header)) + break; + } +} + +static bool +dsdiff_scan_stream(struct input_stream *is, + G_GNUC_UNUSED const struct tag_handler *handler, + G_GNUC_UNUSED void *handler_ctx) +{ + struct dsdiff_metadata metadata = { + .sample_rate = 0, + .channels = 0, + }; + + struct dsdiff_chunk_header chunk_header; + if (!dsdiff_read_metadata(NULL, is, &metadata, &chunk_header)) + return false; + + struct audio_format audio_format; + if (!audio_format_init_checked(&audio_format, metadata.sample_rate / 8, + SAMPLE_FORMAT_DSD, + metadata.channels, NULL)) + /* refuse to parse files which we cannot play anyway */ + return false; + + /* no total time estimate, no tags implemented yet */ + return true; +} + +static const char *const dsdiff_suffixes[] = { + "dff", + NULL +}; + +static const char *const dsdiff_mime_types[] = { + "application/x-dff", + NULL +}; + +const struct decoder_plugin dsdiff_decoder_plugin = { + .name = "dsdiff", + .init = dsdiff_init, + .stream_decode = dsdiff_stream_decode, + .scan_stream = dsdiff_scan_stream, + .suffixes = dsdiff_suffixes, + .mime_types = dsdiff_mime_types, +}; diff --git a/src/playlist/flac_playlist_plugin.h b/src/decoder/dsdiff_decoder_plugin.h index 7b141264f..34e1438de 100644 --- a/src/playlist/flac_playlist_plugin.h +++ b/src/decoder/dsdiff_decoder_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 MPD_PLAYLIST_FLAC_PLAYLIST_PLUGIN_H -#define MPD_PLAYLIST_FLAC_PLAYLIST_PLUGIN_H +#ifndef MPD_DECODER_DSDIFF_H +#define MPD_DECODER_DSDIFF_H -extern const struct playlist_plugin flac_playlist_plugin; +extern const struct decoder_plugin dsdiff_decoder_plugin; #endif diff --git a/src/decoder/faad_decoder_plugin.c b/src/decoder/faad_decoder_plugin.c index 8f932ad58..911f033b8 100644 --- a/src/decoder/faad_decoder_plugin.c +++ b/src/decoder/faad_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,6 +21,7 @@ #include "decoder_api.h" #include "decoder_buffer.h" #include "audio_check.h" +#include "tag_handler.h" #define AAC_MAX_CHANNELS 6 @@ -205,7 +206,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, NULL); + input_stream_lock_seek(is, tagsize, SEEK_SET, NULL); data = decoder_buffer_read(buffer, &length); if (data != NULL) @@ -406,7 +407,7 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is) faacDecSetConfiguration(decoder, config); while (!decoder_buffer_is_full(buffer) && - !input_stream_eof(is) && + !input_stream_lock_eof(is) && decoder_get_command(mpd_decoder) == DECODE_COMMAND_NONE) { adts_find_frame(buffer); decoder_buffer_fill(buffer); @@ -487,18 +488,17 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is) faacDecClose(decoder); } -static struct tag * -faad_stream_tag(struct input_stream *is) +static bool +faad_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) { int file_time = faad_get_file_time(is); - struct tag *tag; if (file_time < 0) - return NULL; + return false; - tag = tag_new(); - tag->time = file_time; - return tag; + tag_handler_invoke_duration(handler, handler_ctx, file_time); + return true; } static const char *const faad_suffixes[] = { "aac", NULL }; @@ -509,7 +509,7 @@ static const char *const faad_mime_types[] = { const struct decoder_plugin faad_decoder_plugin = { .name = "faad", .stream_decode = faad_stream_decode, - .stream_tag = faad_stream_tag, + .scan_stream = faad_scan_stream, .suffixes = faad_suffixes, .mime_types = faad_mime_types, }; diff --git a/src/decoder/ffmpeg_decoder_plugin.c b/src/decoder/ffmpeg_decoder_plugin.c index 2929d316a..6ad10026a 100644 --- a/src/decoder/ffmpeg_decoder_plugin.c +++ b/src/decoder/ffmpeg_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 @@ #include "config.h" #include "decoder_api.h" #include "audio_check.h" +#include "ffmpeg_metadata.h" +#include "tag_handler.h" #include <glib.h> @@ -32,11 +34,6 @@ #include <sys/stat.h> #include <unistd.h> -#ifdef OLD_FFMPEG_INCLUDES -#include <avcodec.h> -#include <avformat.h> -#include <avio.h> -#else #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavformat/avio.h> @@ -46,13 +43,10 @@ #if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0) #include <libavutil/dict.h> #endif -#endif #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "ffmpeg" -#ifndef OLD_FFMPEG_INCLUDES - static GLogLevelFlags level_ffmpeg_to_glib(int level) { @@ -84,12 +78,6 @@ mpd_ffmpeg_log_callback(G_GNUC_UNUSED void *ptr, int level, } } -#endif /* !OLD_FFMPEG_INCLUDES */ - -#ifndef AV_VERSION_INT -#define AV_VERSION_INT(a, b, c) (a<<16 | b<<8 | c) -#endif - struct mpd_ffmpeg_stream { struct decoder *decoder; struct input_stream *input; @@ -119,7 +107,7 @@ mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence) if (whence == AVSEEK_SIZE) return stream->input->size; - if (!input_stream_seek(stream->input, pos, whence, NULL)) + if (!input_stream_lock_seek(stream->input, pos, whence, NULL)) return -1; return stream->input->offset; @@ -189,9 +177,7 @@ 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; @@ -369,7 +355,6 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is, static enum sample_format ffmpeg_sample_format(G_GNUC_UNUSED const AVCodecContext *codec_context) { -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(51, 41, 0) switch (codec_context->sample_fmt) { #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 94, 1) case AV_SAMPLE_FMT_S16: @@ -390,10 +375,6 @@ ffmpeg_sample_format(G_GNUC_UNUSED const AVCodecContext *codec_context) 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 * @@ -406,7 +387,8 @@ 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, NULL)) { + if (nbytes <= PADDING || + !input_stream_lock_seek(is, 0, SEEK_SET, NULL)) { g_free(buffer); return NULL; } @@ -588,72 +570,24 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input) mpd_ffmpeg_stream_close(stream); } -#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0) -typedef struct ffmpeg_tag_map { - enum tag_type type; - const char *name; -} ffmpeg_tag_map; - -static const ffmpeg_tag_map ffmpeg_tag_maps[] = { -#if LIBAVFORMAT_VERSION_INT < ((52<<16)+(50<<8)) - { TAG_ARTIST, "author" }, -#endif - { TAG_DATE, "year" }, - { TAG_ARTIST_SORT, "author-sort" }, - { TAG_ALBUM_ARTIST, "album_artist" }, - { TAG_ALBUM_ARTIST_SORT, "album_artist-sort" }, - - /* sentinel */ - { TAG_NUM_OF_ITEM_TYPES, NULL } -}; - -#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53,1,0) -#define AVDictionary AVMetadata -#define AVDictionaryEntry AVMetadataTag -#define av_dict_get av_metadata_get -#endif - -static void -ffmpeg_copy_metadata(struct tag *tag, enum tag_type type, - AVDictionary *m, const char *name) -{ - AVDictionaryEntry *mt = NULL; - - while ((mt = av_dict_get(m, name, mt, 0)) != NULL) - tag_add_item(tag, type, mt->value); -} - -static void -ffmpeg_copy_dictionary(struct tag *tag, AVDictionary *dict) -{ - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - ffmpeg_copy_metadata(tag, i, - dict, tag_item_names[i]); - - for (const struct ffmpeg_tag_map *i = ffmpeg_tag_maps; - i->name != NULL; ++i) - ffmpeg_copy_metadata(tag, i->type, dict, i->name); -} - -#endif - //no tag reading in ffmpeg, check if playable -static struct tag * -ffmpeg_stream_tag(struct input_stream *is) +static bool +ffmpeg_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) { AVInputFormat *input_format = ffmpeg_probe(NULL, is); if (input_format == NULL) - return NULL; + return false; struct mpd_ffmpeg_stream *stream = mpd_ffmpeg_stream_open(NULL, is); if (stream == NULL) - return NULL; + return false; AVFormatContext *f = NULL; if (mpd_ffmpeg_open_input(&f, stream->io, is->uri, input_format) != 0) { mpd_ffmpeg_stream_close(stream); - return NULL; + return false; } #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,6,0) @@ -669,49 +603,22 @@ ffmpeg_stream_tag(struct input_stream *is) av_close_input_stream(f); #endif mpd_ffmpeg_stream_close(stream); - return NULL; + return false; } - struct tag *tag = tag_new(); - - tag->time = f->duration != (int64_t)AV_NOPTS_VALUE - ? f->duration / AV_TIME_BASE - : 0; + if (f->duration != (int64_t)AV_NOPTS_VALUE) + tag_handler_invoke_duration(handler, handler_ctx, + f->duration / AV_TIME_BASE); -#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0) #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52,101,0) av_metadata_conv(f, NULL, f->iformat->metadata_conv); #endif - ffmpeg_copy_dictionary(tag, f->metadata); + ffmpeg_scan_dictionary(f->metadata, handler, handler_ctx); int idx = ffmpeg_find_audio_stream(f); if (idx >= 0) - ffmpeg_copy_dictionary(tag, f->streams[idx]->metadata); -#else - if (f->author[0]) - tag_add_item(tag, TAG_ARTIST, f->author); - if (f->title[0]) - tag_add_item(tag, TAG_TITLE, f->title); - if (f->album[0]) - 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_TRACK, buffer); - } - - if (f->comment[0]) - tag_add_item(tag, TAG_COMMENT, f->comment); - if (f->genre[0]) - 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_DATE, buffer); - } - -#endif + ffmpeg_scan_dictionary(f->streams[idx]->metadata, + handler, handler_ctx); #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0) avformat_close_input(&f); @@ -720,7 +627,7 @@ ffmpeg_stream_tag(struct input_stream *is) #endif mpd_ffmpeg_stream_close(stream); - return tag; + return true; } /** @@ -839,7 +746,7 @@ const struct decoder_plugin ffmpeg_decoder_plugin = { .name = "ffmpeg", .init = ffmpeg_init, .stream_decode = ffmpeg_decode, - .stream_tag = ffmpeg_stream_tag, + .scan_stream = ffmpeg_scan_stream, .suffixes = ffmpeg_suffixes, .mime_types = ffmpeg_mime_types }; diff --git a/src/decoder/ffmpeg_metadata.c b/src/decoder/ffmpeg_metadata.c new file mode 100644 index 000000000..3ef774f63 --- /dev/null +++ b/src/decoder/ffmpeg_metadata.c @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ffmpeg_metadata.h" +#include "tag_table.h" +#include "tag_handler.h" + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "ffmpeg" + +static const struct tag_table ffmpeg_tags[] = { +#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52,50,0) + { "author", TAG_ARTIST }, +#endif + { "year", TAG_DATE }, + { "author-sort", TAG_ARTIST_SORT }, + { "album_artist", TAG_ALBUM_ARTIST }, + { "album_artist-sort", TAG_ALBUM_ARTIST_SORT }, + + /* sentinel */ + { NULL, TAG_NUM_OF_ITEM_TYPES } +}; + +static void +ffmpeg_copy_metadata(enum tag_type type, + AVDictionary *m, const char *name, + const struct tag_handler *handler, void *handler_ctx) +{ + AVDictionaryEntry *mt = NULL; + + while ((mt = av_dict_get(m, name, mt, 0)) != NULL) + tag_handler_invoke_tag(handler, handler_ctx, + type, mt->value); +} + +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0) + +static void +ffmpeg_scan_pairs(AVDictionary *dict, + const struct tag_handler *handler, void *handler_ctx) +{ + AVDictionaryEntry *i = NULL; + + while ((i = av_dict_get(dict, "", i, AV_DICT_IGNORE_SUFFIX)) != NULL) + tag_handler_invoke_pair(handler, handler_ctx, + i->key, i->value); +} + +#endif + +void +ffmpeg_scan_dictionary(AVDictionary *dict, + const struct tag_handler *handler, void *handler_ctx) +{ + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + ffmpeg_copy_metadata(i, dict, tag_item_names[i], + handler, handler_ctx); + + for (const struct tag_table *i = ffmpeg_tags; + i->name != NULL; ++i) + ffmpeg_copy_metadata(i->type, dict, i->name, + handler, handler_ctx); + +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0) + if (handler->pair != NULL) + ffmpeg_scan_pairs(dict, handler, handler_ctx); +#endif +} diff --git a/src/decoder/ffmpeg_metadata.h b/src/decoder/ffmpeg_metadata.h new file mode 100644 index 000000000..60658f479 --- /dev/null +++ b/src/decoder/ffmpeg_metadata.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_METADATA_H +#define MPD_FFMPEG_METADATA_H + +#include <libavformat/avformat.h> +#include <libavutil/avutil.h> +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0) +#include <libavutil/dict.h> +#endif + +#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53,1,0) +#define AVDictionary AVMetadata +#define AVDictionaryEntry AVMetadataTag +#define av_dict_get av_metadata_get +#endif + +struct tag_handler; + +void +ffmpeg_scan_dictionary(AVDictionary *dict, + const struct tag_handler *handler, void *handler_ctx); + +#endif diff --git a/src/decoder/flac_compat.h b/src/decoder/flac_compat.h index d597690a0..9a30acc26 100644 --- a/src/decoder/flac_compat.h +++ b/src/decoder/flac_compat.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/flac_decoder_plugin.c b/src/decoder/flac_decoder_plugin.c index 9d980b79d..fb0b3502d 100644 --- a/src/decoder/flac_decoder_plugin.c +++ b/src/decoder/flac_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -50,7 +50,7 @@ flac_read_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, if (r == 0) { if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE || - input_stream_eof(data->input_stream)) + input_stream_lock_eof(data->input_stream)) return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; else return FLAC__STREAM_DECODER_READ_STATUS_ABORT; @@ -68,7 +68,8 @@ flac_seek_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, if (!data->input_stream->seekable) return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; - if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL)) + if (!input_stream_lock_seek(data->input_stream, offset, SEEK_SET, + NULL)) return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; return FLAC__STREAM_DECODER_SEEK_STATUS_OK; @@ -109,53 +110,40 @@ flac_eof_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, void *fdata) return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE && decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) || - input_stream_eof(data->input_stream); + input_stream_lock_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); + flac_error_common_cb(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"; + break; } - g_warning("%s\n", str); + g_warning("%s\n", FLAC__SeekableStreamDecoderStateString[state]); } #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: @@ -163,23 +151,16 @@ static void flacPrintErroredState(FLAC__StreamDecoderState state) 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"; + break; } - g_warning("%s\n", str); + g_warning("%s\n", FLAC__StreamDecoderStateString[state]); } #endif /* FLAC_API_VERSION_CURRENT >= 7 */ @@ -210,10 +191,11 @@ flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame, return flac_common_write(data, frame, buf, nbytes); } -static struct tag * -flac_tag_dup(const char *file) +static bool +flac_scan_file(const char *file, + const struct tag_handler *handler, void *handler_ctx) { - return flac_tag_load(file, NULL); + return flac_scan_file2(file, NULL, handler, handler_ctx); } /** @@ -305,17 +287,56 @@ flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec, /* 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 (!FLAC__stream_decoder_process_single(flac_dec) && + decoder_get_command(decoder) == DECODE_COMMAND_NONE) { + /* a failure that was not triggered by a + decoder command */ + flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec)); + break; } } +} - if (cmd != DECODE_COMMAND_STOP) { - flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec)); - FLAC__stream_decoder_finish(flac_dec); - } +static FLAC__StreamDecoderInitStatus +stream_init_oggflac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) +{ +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + return 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, + data); +#else + (void)flac_dec; + (void)data; + + return FLAC__STREAM_DECODER_INIT_STATUS_ERROR; +#endif +} + +static FLAC__StreamDecoderInitStatus +stream_init_flac(FLAC__StreamDecoder *flac_dec, struct flac_data *data) +{ + return 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, + data); +} + +static FLAC__StreamDecoderInitStatus +stream_init(FLAC__StreamDecoder *flac_dec, struct flac_data *data, bool is_ogg) +{ + return is_ogg + ? stream_init_oggflac(flac_dec, data) + : stream_init_flac(flac_dec, data); } static void @@ -325,7 +346,6 @@ flac_decode_internal(struct decoder * decoder, { FLAC__StreamDecoder *flac_dec; struct flac_data data; - const char *err = NULL; flac_dec = flac_decoder_new(); if (flac_dec == NULL) @@ -334,58 +354,30 @@ flac_decode_internal(struct decoder * decoder, flac_data_init(&data, decoder, input_stream); data.tag = tag_new(); - if (is_ogg) { + FLAC__StreamDecoderInitStatus status = + stream_init(flac_dec, &data, is_ogg); + if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { + flac_data_deinit(&data); + FLAC__stream_decoder_delete(flac_dec); #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; + g_warning("%s", FLAC__StreamDecoderInitStatusString[status]); #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; - } + return; } if (!flac_decoder_initialize(&data, flac_dec, 0)) { flac_data_deinit(&data); + FLAC__stream_decoder_finish(flac_dec); 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); + FLAC__stream_decoder_finish(flac_dec); + FLAC__stream_decoder_delete(flac_dec); } static void @@ -409,36 +401,33 @@ oggflac_init(G_GNUC_UNUSED const struct config_param *param) #if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 -static struct tag * -oggflac_tag_dup(const char *file) +static bool +oggflac_scan_file(const char *file, + const struct tag_handler *handler, void *handler_ctx) { - 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; + if (!(FLAC__metadata_chain_read_ogg(chain, file))) { + FLAC__metadata_chain_delete(chain); + return false; + } + 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); + flac_scan_metadata(NULL, block, + handler, handler_ctx); } 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; + return true; } static void @@ -449,7 +438,7 @@ oggflac_decode(struct decoder *decoder, struct input_stream *input_stream) /* rewind the stream, because ogg_stream_type_detect() has moved it */ - input_stream_seek(input_stream, 0, SEEK_SET, NULL); + input_stream_lock_seek(input_stream, 0, SEEK_SET, NULL); flac_decode_internal(decoder, input_stream, true); } @@ -471,7 +460,7 @@ const struct decoder_plugin oggflac_decoder_plugin = { .init = oggflac_init, #if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 .stream_decode = oggflac_decode, - .tag_dup = oggflac_tag_dup, + .scan_file = oggflac_scan_file, .suffixes = oggflac_suffixes, .mime_types = oggflac_mime_types #endif @@ -491,7 +480,7 @@ static const char *const flac_mime_types[] = { const struct decoder_plugin flac_decoder_plugin = { .name = "flac", .stream_decode = flac_decode, - .tag_dup = flac_tag_dup, + .scan_file = flac_scan_file, .suffixes = flac_suffixes, .mime_types = flac_mime_types, }; diff --git a/src/decoder/flac_metadata.c b/src/decoder/flac_metadata.c index 5b94fd426..bd1eaf323 100644 --- a/src/decoder/flac_metadata.c +++ b/src/decoder/flac_metadata.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,6 +21,8 @@ #include "flac_metadata.h" #include "replay_gain_info.h" #include "tag.h" +#include "tag_handler.h" +#include "tag_table.h" #include <glib.h> @@ -163,69 +165,87 @@ flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, * the comment value into the tag. */ static bool -flac_copy_comment(struct tag *tag, - const FLAC__StreamMetadata_VorbisComment_Entry *entry, +flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, const char *name, enum tag_type tag_type, - const char *char_tnum) + const char *char_tnum, + const struct tag_handler *handler, void *handler_ctx) { 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); + char *p = g_strndup(value, value_length); + tag_handler_invoke_tag(handler, handler_ctx, tag_type, p); + g_free(p); 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 const struct tag_table flac_tags[] = { + { "tracknumber", TAG_TRACK }, + { "discnumber", TAG_DISC }, + { "album artist", TAG_ALBUM_ARTIST }, + { NULL, TAG_NUM_OF_ITEM_TYPES } +}; static void -flac_parse_comment(struct tag *tag, const char *char_tnum, - const FLAC__StreamMetadata_VorbisComment_Entry *entry) +flac_scan_comment(const char *char_tnum, + const FLAC__StreamMetadata_VorbisComment_Entry *entry, + const struct tag_handler *handler, void *handler_ctx) { - assert(tag != NULL); + if (handler->pair != NULL) { + char *name = g_strdup((const char*)entry->entry); + char *value = strchr(name, '='); + + if (value != NULL && value > name) { + *value++ = 0; + tag_handler_invoke_pair(handler, handler_ctx, + name, value); + } - 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; + g_free(name); + } + + for (const struct tag_table *i = flac_tags; i->name != NULL; ++i) + if (flac_copy_comment(entry, i->name, i->type, char_tnum, + handler, handler_ctx)) + 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)) + if (flac_copy_comment(entry, + tag_item_names[i], i, char_tnum, + handler, handler_ctx)) return; } -void -flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum, - const FLAC__StreamMetadata_VorbisComment *comment) +static void +flac_scan_comments(const char *char_tnum, + const FLAC__StreamMetadata_VorbisComment *comment, + const struct tag_handler *handler, void *handler_ctx) { for (unsigned i = 0; i < comment->num_comments; ++i) - flac_parse_comment(tag, char_tnum, &comment->comments[i]); + flac_scan_comment(char_tnum, &comment->comments[i], + handler, handler_ctx); } void -flac_tag_apply_metadata(struct tag *tag, const char *track, - const FLAC__StreamMetadata *block) +flac_scan_metadata(const char *track, + const FLAC__StreamMetadata *block, + const struct tag_handler *handler, void *handler_ctx) { switch (block->type) { case FLAC__METADATA_TYPE_VORBIS_COMMENT: - flac_vorbis_comments_to_tag(tag, track, - &block->data.vorbis_comment); + flac_scan_comments(track, &block->data.vorbis_comment, + handler, handler_ctx); break; case FLAC__METADATA_TYPE_STREAMINFO: if (block->data.stream_info.sample_rate > 0) - tag->time = flac_duration(&block->data.stream_info); + tag_handler_invoke_duration(handler, handler_ctx, + flac_duration(&block->data.stream_info)); break; default: @@ -233,10 +253,18 @@ flac_tag_apply_metadata(struct tag *tag, const char *track, } } -struct tag * -flac_tag_load(const char *file, const char *char_tnum) +void +flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum, + const FLAC__StreamMetadata_VorbisComment *comment) +{ + flac_scan_comments(char_tnum, comment, + &add_tag_handler, tag); +} + +bool +flac_scan_file2(const char *file, const char *char_tnum, + const struct tag_handler *handler, void *handler_ctx) { - struct tag *tag; FLAC__Metadata_SimpleIterator *it; FLAC__StreamMetadata *block = NULL; @@ -263,22 +291,30 @@ flac_tag_load(const char *file, const char *char_tnum) g_debug("Reading '%s' metadata gave the following error: %s\n", file, err); FLAC__metadata_simple_iterator_delete(it); - return NULL; + return false; } - tag = tag_new(); do { block = FLAC__metadata_simple_iterator_get_block(it); if (!block) break; - flac_tag_apply_metadata(tag, char_tnum, block); + flac_scan_metadata(char_tnum, block, handler, handler_ctx); FLAC__metadata_object_delete(block); } while (FLAC__metadata_simple_iterator_next(it)); FLAC__metadata_simple_iterator_delete(it); - if (!tag_is_defined(tag)) { + return true; +} + +struct tag * +flac_tag_load(const char *file, const char *char_tnum) +{ + struct tag *tag = tag_new(); + + if (!flac_scan_file2(file, char_tnum, &add_tag_handler, tag) || + tag_is_empty(tag)) { tag_free(tag); tag = NULL; } diff --git a/src/decoder/flac_metadata.h b/src/decoder/flac_metadata.h index e52b0fb82..3c463d5d6 100644 --- a/src/decoder/flac_metadata.h +++ b/src/decoder/flac_metadata.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,6 +24,7 @@ #include <stdbool.h> #include <FLAC/metadata.h> +struct tag_handler; struct tag; struct replay_gain_info; @@ -49,8 +50,13 @@ 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); +flac_scan_metadata(const char *track, + const FLAC__StreamMetadata *block, + const struct tag_handler *handler, void *handler_ctx); + +bool +flac_scan_file2(const char *file, const char *char_tnum, + const struct tag_handler *handler, void *handler_ctx); struct tag * flac_tag_load(const char *file, const char *char_tnum); diff --git a/src/decoder/flac_pcm.c b/src/decoder/flac_pcm.c index bf6e2612c..6964d8ac6 100644 --- a/src/decoder/flac_pcm.c +++ b/src/decoder/flac_pcm.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -101,7 +101,8 @@ flac_convert(void *dest, position, end); break; - case SAMPLE_FORMAT_S24: + case SAMPLE_FORMAT_FLOAT: + case SAMPLE_FORMAT_DSD: case SAMPLE_FORMAT_UNDEFINED: /* unreachable */ assert(false); diff --git a/src/decoder/flac_pcm.h b/src/decoder/flac_pcm.h index bccfc645c..a931998c1 100644 --- a/src/decoder/flac_pcm.h +++ b/src/decoder/flac_pcm.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/fluidsynth_decoder_plugin.c b/src/decoder/fluidsynth_decoder_plugin.c index b9a2d0d99..085f84f14 100644 --- a/src/decoder/fluidsynth_decoder_plugin.c +++ b/src/decoder/fluidsynth_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -102,7 +102,7 @@ fluidsynth_file_decode(struct decoder *decoder, const char *path_fs) fluid_player_t *player; char *path_dup; int ret; - Timer *timer; + struct timer *timer; enum decoder_command cmd; soundfont_path = @@ -219,15 +219,15 @@ fluidsynth_file_decode(struct decoder *decoder, const char *path_fs) delete_fluid_settings(settings); } -static struct tag * -fluidsynth_tag_dup(const char *file) +static bool +fluidsynth_scan_file(const char *file, + G_GNUC_UNUSED const struct tag_handler *handler, + G_GNUC_UNUSED void *handler_ctx) { - struct tag *tag = tag_new(); - /* to be implemented */ (void)file; - return tag; + return true; } static const char *const fluidsynth_suffixes[] = { @@ -239,6 +239,6 @@ const struct decoder_plugin fluidsynth_decoder_plugin = { .name = "fluidsynth", .init = fluidsynth_init, .file_decode = fluidsynth_file_decode, - .tag_dup = fluidsynth_tag_dup, + .scan_file = fluidsynth_scan_file, .suffixes = fluidsynth_suffixes, }; diff --git a/src/decoder/gme_decoder_plugin.c b/src/decoder/gme_decoder_plugin.c index e14a52d32..237a1deb1 100644 --- a/src/decoder/gme_decoder_plugin.c +++ b/src/decoder/gme_decoder_plugin.c @@ -2,6 +2,7 @@ #include "../decoder_api.h" #include "audio_check.h" #include "uri.h" +#include "tag_handler.h" #include <glib.h> #include <assert.h> @@ -180,8 +181,9 @@ gme_file_decode(struct decoder *decoder, const char *path_fs) gme_delete(emu); } -static struct tag * -gme_tag_dup(const char *path_fs) +static bool +gme_scan_file(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) { Music_Emu *emu; gme_info_t *ti; @@ -194,41 +196,49 @@ gme_tag_dup(const char *path_fs) g_free(path_container); if (gme_err != NULL) { g_warning("%s", gme_err); - return NULL; + return false; } if((gme_err = gme_track_info(emu, &ti, song_num)) != NULL){ g_warning("%s", gme_err); gme_delete(emu); - return NULL; + return false; } - 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); + assert(ti != NULL); + + if(ti->length > 0) + tag_handler_invoke_duration(handler, handler_ctx, + ti->length / 100); + + 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_handler_invoke_tag(handler, handler_ctx, + TAG_TITLE, tag_title); + g_free(tag_title); + }else + tag_handler_invoke_tag(handler, handler_ctx, + TAG_TITLE, ti->song); } + if(ti->author != NULL) + tag_handler_invoke_tag(handler, handler_ctx, + TAG_ARTIST, ti->author); + if(ti->game != NULL) + tag_handler_invoke_tag(handler, handler_ctx, + TAG_ALBUM, ti->game); + if(ti->comment != NULL) + tag_handler_invoke_tag(handler, handler_ctx, + TAG_COMMENT, ti->comment); + if(ti->copyright != NULL) + tag_handler_invoke_tag(handler, handler_ctx, + TAG_DATE, ti->copyright); gme_free_info(ti); gme_delete(emu); - return tag; + + return true; } static const char *const gme_suffixes[] = { @@ -241,7 +251,7 @@ 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, + .scan_file = gme_scan_file, .suffixes = gme_suffixes, .container_scan = gme_container_scan, }; diff --git a/src/decoder/mad_decoder_plugin.c b/src/decoder/mad_decoder_plugin.c index 2c2906c5c..a69284be5 100644 --- a/src/decoder/mad_decoder_plugin.c +++ b/src/decoder/mad_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 "conf.h" #include "tag_id3.h" #include "tag_rva2.h" +#include "tag_handler.h" #include "audio_check.h" #include <assert.h> @@ -168,7 +169,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, NULL)) + if (!input_stream_lock_seek(data->input_stream, offset, SEEK_SET, NULL)) return false; mad_stream_buffer(&data->stream, data->input_buffer, 0); @@ -1176,19 +1177,18 @@ mp3_decode(struct decoder *decoder, struct input_stream *input_stream) mp3_data_finish(&data); } -static struct tag * -mad_decoder_stream_tag(struct input_stream *is) +static bool +mad_decoder_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) { - struct tag *tag; int total_time; total_time = mad_decoder_total_file_time(is); if (total_time < 0) - return NULL; + return false; - tag = tag_new(); - tag->time = total_time; - return tag; + tag_handler_invoke_duration(handler, handler_ctx, total_time); + return true; } static const char *const mp3_suffixes[] = { "mp3", "mp2", NULL }; @@ -1198,7 +1198,7 @@ const struct decoder_plugin mad_decoder_plugin = { .name = "mad", .init = mp3_plugin_init, .stream_decode = mp3_decode, - .stream_tag = mad_decoder_stream_tag, + .scan_stream = mad_decoder_scan_stream, .suffixes = mp3_suffixes, .mime_types = mp3_mime_types }; diff --git a/src/decoder/mikmod_decoder_plugin.c b/src/decoder/mikmod_decoder_plugin.c index 91478e86f..5681a7a57 100644 --- a/src/decoder/mikmod_decoder_plugin.c +++ b/src/decoder/mikmod_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ #include "config.h" #include "decoder_api.h" #include "mpd_error.h" +#include "tag_handler.h" #include <glib.h> #include <mikmod.h> @@ -177,8 +178,9 @@ mikmod_decoder_file_decode(struct decoder *decoder, const char *path_fs) Player_Free(handle); } -static struct tag * -mikmod_decoder_tag_dup(const char *path_fs) +static bool +mikmod_decoder_scan_file(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) { char *path2 = g_strdup(path_fs); MODULE *handle = Player_Load(path2, 128, 0); @@ -186,25 +188,22 @@ mikmod_decoder_tag_dup(const char *path_fs) if (handle == NULL) { g_free(path2); g_debug("Failed to open file: %s", path_fs); - return NULL; + return false; } Player_Free(handle); - struct tag *tag = tag_new(); - - tag->time = 0; - char *title = Player_LoadTitle(path2); g_free(path2); if (title != NULL) { - tag_add_item(tag, TAG_TITLE, title); + tag_handler_invoke_tag(handler, handler_ctx, + TAG_TITLE, title); free(title); } - return tag; + return true; } static const char *const mikmod_decoder_suffixes[] = { @@ -231,6 +230,6 @@ const struct decoder_plugin mikmod_decoder_plugin = { .init = mikmod_decoder_init, .finish = mikmod_decoder_finish, .file_decode = mikmod_decoder_file_decode, - .tag_dup = mikmod_decoder_tag_dup, + .scan_file = mikmod_decoder_scan_file, .suffixes = mikmod_decoder_suffixes, }; diff --git a/src/decoder/modplug_decoder_plugin.c b/src/decoder/modplug_decoder_plugin.c index 037c2fd74..21ee79e7e 100644 --- a/src/decoder/modplug_decoder_plugin.c +++ b/src/decoder/modplug_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,6 +19,7 @@ #include "config.h" #include "decoder_api.h" +#include "tag_handler.h" #include <glib.h> #include <modplug.h> @@ -62,7 +63,7 @@ static GByteArray *mod_loadfile(struct decoder *decoder, struct input_stream *is while (true) { ret = decoder_read(decoder, is, data, MODPLUG_READ_BLOCK); if (ret == 0) { - if (input_stream_eof(is)) + if (input_stream_lock_eof(is)) /* end of file */ break; @@ -149,34 +150,33 @@ mod_decode(struct decoder *decoder, struct input_stream *is) ModPlug_Unload(f); } -static struct tag * -modplug_stream_tag(struct input_stream *is) +static bool +modplug_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) { ModPlugFile *f; - struct tag *ret = NULL; GByteArray *bdatas; - char *title; bdatas = mod_loadfile(NULL, is); if (!bdatas) - return NULL; + return false; f = ModPlug_Load(bdatas->data, bdatas->len); g_byte_array_free(bdatas, TRUE); if (f == NULL) - return NULL; + return false; - ret = tag_new(); - ret->time = ModPlug_GetLength(f) / 1000; + tag_handler_invoke_duration(handler, handler_ctx, + ModPlug_GetLength(f) / 1000); - title = g_strdup(ModPlug_GetName(f)); - if (title) - tag_add_item(ret, TAG_TITLE, title); - g_free(title); + const char *title = ModPlug_GetName(f); + if (title != NULL) + tag_handler_invoke_tag(handler, handler_ctx, + TAG_TITLE, title); ModPlug_Unload(f); - return ret; + return true; } static const char *const mod_suffixes[] = { @@ -189,6 +189,6 @@ static const char *const mod_suffixes[] = { const struct decoder_plugin modplug_decoder_plugin = { .name = "modplug", .stream_decode = mod_decode, - .stream_tag = modplug_stream_tag, + .scan_stream = modplug_scan_stream, .suffixes = mod_suffixes, }; diff --git a/src/decoder/mp4ff_decoder_plugin.c b/src/decoder/mp4ff_decoder_plugin.c index cd85778f8..ca78a22d0 100644 --- a/src/decoder/mp4ff_decoder_plugin.c +++ b/src/decoder/mp4ff_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,6 +21,7 @@ #include "decoder_api.h" #include "audio_check.h" #include "tag_table.h" +#include "tag_handler.h" #include <glib.h> @@ -108,7 +109,8 @@ mp4_seek(void *user_data, uint64_t position) { struct mp4ff_input_stream *mis = user_data; - return input_stream_seek(mis->input_stream, position, SEEK_SET, NULL) + return input_stream_lock_seek(mis->input_stream, position, SEEK_SET, + NULL) ? 0 : -1; } @@ -355,16 +357,17 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream) mp4ff_close(mp4fh); } -static const char *const mp4ff_tag_names[TAG_NUM_OF_ITEM_TYPES] = { - [TAG_ALBUM_ARTIST] = "album artist", - [TAG_COMPOSER] = "writer", - [TAG_PERFORMER] = "band", +static const struct tag_table mp4ff_tags[] = { + { "album artist", TAG_ALBUM_ARTIST }, + { "writer", TAG_COMPOSER }, + { "band", TAG_PERFORMER }, + { NULL, TAG_NUM_OF_ITEM_TYPES } }; static enum tag_type mp4ff_tag_name_parse(const char *name) { - enum tag_type type = tag_table_lookup(mp4ff_tag_names, name); + enum tag_type type = tag_table_lookup_i(mp4ff_tags, name); if (type == TAG_NUM_OF_ITEM_TYPES) type = tag_name_parse_i(name); @@ -375,8 +378,9 @@ mp4ff_tag_name_parse(const char *name) return type; } -static struct tag * -mp4_stream_tag(struct input_stream *is) +static bool +mp4ff_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) { struct mp4ff_input_stream mis; int32_t track; @@ -386,23 +390,23 @@ mp4_stream_tag(struct input_stream *is) mp4ff_t *mp4fh = mp4ff_input_stream_open(&mis, NULL, is); if (mp4fh == NULL) - return NULL; + return false; track = mp4_get_aac_track(mp4fh, NULL, NULL, NULL); if (track < 0) { mp4ff_close(mp4fh); - return NULL; + return false; } file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track); scale = mp4ff_time_scale(mp4fh, track); if (scale < 0) { mp4ff_close(mp4fh); - return NULL; + return false; } - struct tag *tag = tag_new(); - tag->time = ((float)file_time) / scale + 0.5; + tag_handler_invoke_duration(handler, handler_ctx, + ((float)file_time) / scale + 0.5); for (i = 0; i < mp4ff_meta_get_num_items(mp4fh); i++) { char *item; @@ -410,9 +414,12 @@ mp4_stream_tag(struct input_stream *is) mp4ff_meta_get_by_index(mp4fh, i, &item, &value); + tag_handler_invoke_pair(handler, handler_ctx, item, value); + enum tag_type type = mp4ff_tag_name_parse(item); if (type != TAG_NUM_OF_ITEM_TYPES) - tag_add_item(tag, type, value); + tag_handler_invoke_tag(handler, handler_ctx, + type, value); free(item); free(value); @@ -420,7 +427,7 @@ mp4_stream_tag(struct input_stream *is) mp4ff_close(mp4fh); - return tag; + return true; } static const char *const mp4_suffixes[] = { @@ -435,7 +442,7 @@ static const char *const mp4_mime_types[] = { "audio/mp4", "audio/m4a", NULL }; const struct decoder_plugin mp4ff_decoder_plugin = { .name = "mp4ff", .stream_decode = mp4_decode, - .stream_tag = mp4_stream_tag, + .scan_stream = mp4ff_scan_stream, .suffixes = mp4_suffixes, .mime_types = mp4_mime_types, }; diff --git a/src/decoder/mpcdec_decoder_plugin.c b/src/decoder/mpcdec_decoder_plugin.c index eaf470a40..d4768b35b 100644 --- a/src/decoder/mpcdec_decoder_plugin.c +++ b/src/decoder/mpcdec_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ #include "config.h" #include "decoder_api.h" #include "audio_check.h" +#include "tag_handler.h" #ifdef MPC_IS_OLD_API #include <mpcdec/mpcdec.h> @@ -61,7 +62,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, NULL); + return input_stream_lock_seek(data->is, offset, SEEK_SET, NULL); } static mpc_int32_t @@ -323,18 +324,17 @@ mpcdec_get_file_duration(struct input_stream *is) return total_time; } -static struct tag * -mpcdec_stream_tag(struct input_stream *is) +static bool +mpcdec_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) { float total_time = mpcdec_get_file_duration(is); - struct tag *tag; if (total_time < 0) - return NULL; + return false; - tag = tag_new(); - tag->time = total_time; - return tag; + tag_handler_invoke_duration(handler, handler_ctx, total_time); + return true; } static const char *const mpcdec_suffixes[] = { "mpc", NULL }; @@ -342,6 +342,6 @@ static const char *const mpcdec_suffixes[] = { "mpc", NULL }; const struct decoder_plugin mpcdec_decoder_plugin = { .name = "mpcdec", .stream_decode = mpcdec_decode, - .stream_tag = mpcdec_stream_tag, + .scan_stream = mpcdec_scan_stream, .suffixes = mpcdec_suffixes, }; diff --git a/src/decoder/mpg123_decoder_plugin.c b/src/decoder/mpg123_decoder_plugin.c index 7b48ebfaf..657a9c889 100644 --- a/src/decoder/mpg123_decoder_plugin.c +++ b/src/decoder/mpg123_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,10 +20,12 @@ #include "config.h" /* must be first for large file support */ #include "decoder_api.h" #include "audio_check.h" +#include "tag_handler.h" #include <glib.h> #include <mpg123.h> +#include <stdio.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "mpg123" @@ -105,6 +107,7 @@ mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs) int error; off_t num_samples; enum decoder_command cmd; + struct mpg123_frameinfo info; /* open the file */ @@ -124,10 +127,25 @@ mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs) /* tell MPD core we're ready */ - decoder_initialized(decoder, &audio_format, false, + decoder_initialized(decoder, &audio_format, true, (float)num_samples / (float)audio_format.sample_rate); + if (mpg123_info(handle, &info) != MPG123_OK) { + info.vbr = MPG123_CBR; + info.bitrate = 0; + } + + switch (info.vbr) { + case MPG123_ABR: + info.bitrate = info.abr_rate; + break; + case MPG123_CBR: + break; + default: + info.bitrate = 0; + } + /* the decoder main loop */ do { @@ -144,11 +162,30 @@ mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs) break; } + /* update bitrate for ABR/VBR */ + if (info.vbr != MPG123_CBR) { + /* FIXME: maybe skip, as too expensive? */ + /* FIXME: maybe, (info.vbr == MPG123_VBR) ? */ + if (mpg123_info (handle, &info) != MPG123_OK) + info.bitrate = 0; + } + /* send to MPD */ - cmd = decoder_data(decoder, NULL, buffer, nbytes, 0); + cmd = decoder_data(decoder, NULL, buffer, nbytes, info.bitrate); - /* seeking not yet implemented */ + if (cmd == DECODE_COMMAND_SEEK) { + off_t c = decoder_seek_where(decoder)*audio_format.sample_rate; + c = mpg123_seek(handle, c, SEEK_SET); + if (c < 0) + decoder_seek_error(decoder); + else { + decoder_command_finished(decoder); + decoder_timestamp(decoder, c/(double)audio_format.sample_rate); + } + + cmd = DECODE_COMMAND_NONE; + } } while (cmd == DECODE_COMMAND_NONE); /* cleanup */ @@ -156,41 +193,40 @@ mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs) mpg123_delete(handle); } -static struct tag * -mpd_mpg123_tag_dup(const char *path_fs) +static bool +mpd_mpg123_scan_file(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) { 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; + return false; } if (!mpd_mpg123_open(handle, path_fs, &audio_format)) { mpg123_delete(handle); - return NULL; + return false; } num_samples = mpg123_length(handle); if (num_samples <= 0) { mpg123_delete(handle); - return NULL; + return false; } - tag = tag_new(); - - tag->time = num_samples / audio_format.sample_rate; - /* ID3 tag support not yet implemented */ mpg123_delete(handle); - return tag; + + tag_handler_invoke_duration(handler, handler_ctx, + num_samples / audio_format.sample_rate); + return true; } static const char *const mpg123_suffixes[] = { @@ -204,6 +240,6 @@ const struct decoder_plugin mpg123_decoder_plugin = { .finish = mpd_mpg123_finish, .file_decode = mpd_mpg123_file_decode, /* streaming not yet implemented */ - .tag_dup = mpd_mpg123_tag_dup, + .scan_file = mpd_mpg123_scan_file, .suffixes = mpg123_suffixes, }; diff --git a/src/decoder/oggflac_decoder_plugin.c b/src/decoder/oggflac_decoder_plugin.c deleted file mode 100644 index 7e5f48318..000000000 --- a/src/decoder/oggflac_decoder_plugin.c +++ /dev/null @@ -1,354 +0,0 @@ -/* - * Copyright (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. - */ - -/* - * 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(OggFLAC__SeekableStreamDecoder * decoder) -{ - if (decoder) - OggFLAC__seekable_stream_decoder_delete(decoder); -} - -static OggFLAC__SeekableStreamDecoderReadStatus of_read_cb(G_GNUC_UNUSED const - OggFLAC__SeekableStreamDecoder - * decoder, - FLAC__byte buf[], - unsigned *bytes, - void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - size_t r; - - r = decoder_read(data->decoder, data->input_stream, - (void *)buf, *bytes); - *bytes = r; - - if (r == 0 && !input_stream_eof(data->input_stream) && - decoder_get_command(data->decoder) == DECODE_COMMAND_NONE) - return OggFLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR; - - return OggFLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK; -} - -static OggFLAC__SeekableStreamDecoderSeekStatus of_seek_cb(G_GNUC_UNUSED const - OggFLAC__SeekableStreamDecoder - * decoder, - FLAC__uint64 offset, - void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - 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; -} - -static OggFLAC__SeekableStreamDecoderTellStatus of_tell_cb(G_GNUC_UNUSED const - OggFLAC__SeekableStreamDecoder - * decoder, - FLAC__uint64 * - offset, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - *offset = (long)(data->input_stream->offset); - - return OggFLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK; -} - -static OggFLAC__SeekableStreamDecoderLengthStatus of_length_cb(G_GNUC_UNUSED const - OggFLAC__SeekableStreamDecoder - * decoder, - FLAC__uint64 * - length, - void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - if (data->input_stream->size < 0) - return OggFLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR; - - *length = (size_t) (data->input_stream->size); - - return OggFLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK; -} - -static FLAC__bool of_EOF_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecoder * decoder, - 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 of_error_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecoder * decoder, - FLAC__StreamDecoderErrorStatus status, void *fdata) -{ - flac_error_common_cb("oggflac", status, (struct flac_data *) fdata); -} - -static void oggflacPrintErroredState(OggFLAC__SeekableStreamDecoderState state) -{ - switch (state) { - case OggFLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR: - g_warning("oggflac allocation error\n"); - break; - case OggFLAC__SEEKABLE_STREAM_DECODER_READ_ERROR: - g_warning("oggflac read error\n"); - break; - case OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR: - g_warning("oggflac seek error\n"); - break; - case OggFLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR: - g_warning("oggflac seekable stream error\n"); - break; - case OggFLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED: - g_warning("oggflac decoder already initialized\n"); - break; - case OggFLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK: - g_warning("invalid oggflac callback\n"); - break; - case OggFLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED: - g_warning("oggflac decoder uninitialized\n"); - break; - case OggFLAC__SEEKABLE_STREAM_DECODER_OK: - case OggFLAC__SEEKABLE_STREAM_DECODER_SEEKING: - case OggFLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM: - break; - } -} - -static FLAC__StreamDecoderWriteStatus -oggflac_write_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecoder *decoder, - const FLAC__Frame *frame, const FLAC__int32 *const buf[], - void *vdata) -{ - struct flac_data *data = (struct flac_data *) vdata; - - return flac_common_write(data, frame, buf, 0); -} - -/* used by TagDup */ -static void of_metadata_dup_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecoder * decoder, - const FLAC__StreamMetadata * block, void *vdata) -{ - struct flac_data *data = (struct flac_data *) vdata; - - assert(data->tag != NULL); - - flac_tag_apply_metadata(data->tag, NULL, block); -} - -/* used by decode */ -static void of_metadata_decode_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecoder * dec, - const FLAC__StreamMetadata * block, - void *vdata) -{ - flac_metadata_common_cb(block, (struct flac_data *) vdata); -} - -static OggFLAC__SeekableStreamDecoder * -full_decoder_init_and_read_metadata(struct flac_data *data, - unsigned int metadata_only) -{ - OggFLAC__SeekableStreamDecoder *decoder = NULL; - unsigned int s = 1; - - if (!(decoder = OggFLAC__seekable_stream_decoder_new())) - return NULL; - - if (metadata_only) { - s &= OggFLAC__seekable_stream_decoder_set_metadata_callback - (decoder, of_metadata_dup_cb); - s &= OggFLAC__seekable_stream_decoder_set_metadata_respond - (decoder, FLAC__METADATA_TYPE_STREAMINFO); - } else { - s &= OggFLAC__seekable_stream_decoder_set_metadata_callback - (decoder, of_metadata_decode_cb); - } - - s &= OggFLAC__seekable_stream_decoder_set_read_callback(decoder, - of_read_cb); - s &= OggFLAC__seekable_stream_decoder_set_seek_callback(decoder, - of_seek_cb); - s &= OggFLAC__seekable_stream_decoder_set_tell_callback(decoder, - of_tell_cb); - s &= OggFLAC__seekable_stream_decoder_set_length_callback(decoder, - of_length_cb); - s &= OggFLAC__seekable_stream_decoder_set_eof_callback(decoder, - of_EOF_cb); - s &= OggFLAC__seekable_stream_decoder_set_write_callback(decoder, - oggflac_write_cb); - s &= OggFLAC__seekable_stream_decoder_set_metadata_respond(decoder, - FLAC__METADATA_TYPE_VORBIS_COMMENT); - s &= OggFLAC__seekable_stream_decoder_set_error_callback(decoder, - of_error_cb); - s &= OggFLAC__seekable_stream_decoder_set_client_data(decoder, - (void *)data); - - if (!s) { - g_warning("oggflac problem before init()\n"); - goto fail; - } - if (OggFLAC__seekable_stream_decoder_init(decoder) != - OggFLAC__SEEKABLE_STREAM_DECODER_OK) { - g_warning("oggflac problem doing init()\n"); - goto fail; - } - if (!OggFLAC__seekable_stream_decoder_process_until_end_of_metadata - (decoder)) { - g_warning("oggflac problem reading metadata\n"); - goto fail; - } - - return decoder; - -fail: - oggflacPrintErroredState(OggFLAC__seekable_stream_decoder_get_state - (decoder)); - OggFLAC__seekable_stream_decoder_delete(decoder); - return NULL; -} - -/* public functions: */ -static struct tag * -oggflac_stream_tag(struct input_stream *is) -{ - OggFLAC__SeekableStreamDecoder *decoder; - struct flac_data data; - struct tag *tag; - - if (ogg_stream_type_detect(is) != FLAC) - return NULL; - - /* rewind the stream, because ogg_stream_type_detect() has - moved it */ - input_stream_seek(is, 0, SEEK_SET, NULL); - - flac_data_init(&data, NULL, is); - - data.tag = tag_new(); - - /* errors here won't matter, - * data.tag will be set or unset, that's all we care about */ - decoder = full_decoder_init_and_read_metadata(&data, 1); - - oggflac_cleanup(decoder); - - if (tag_is_defined(data.tag)) { - tag = data.tag; - data.tag = NULL; - } else - tag = NULL; - - flac_data_deinit(&data); - - return tag; -} - -static void -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, NULL); - - flac_data_init(&data, mpd_decoder, input_stream); - - if (!(decoder = full_decoder_init_and_read_metadata(&data, 0))) { - goto fail; - } - - if (!data.initialized) - goto fail; - - 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); - if (OggFLAC__seekable_stream_decoder_get_state(decoder) != - OggFLAC__SEEKABLE_STREAM_DECODER_OK) { - break; - } - if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) { - FLAC__uint64 seek_sample = decoder_seek_where(mpd_decoder) * - data.audio_format.sample_rate; - if (OggFLAC__seekable_stream_decoder_seek_absolute - (decoder, seek_sample)) { - data.next_frame = seek_sample; - data.position = 0; - decoder_command_finished(mpd_decoder); - } else - decoder_seek_error(mpd_decoder); - } - } - - if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_NONE) { - oggflacPrintErroredState - (OggFLAC__seekable_stream_decoder_get_state(decoder)); - OggFLAC__seekable_stream_decoder_finish(decoder); - } - -fail: - oggflac_cleanup(decoder); - flac_data_deinit(&data); -} - -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-ogg", - "audio/x-flac+ogg", - NULL -}; - -const struct decoder_plugin oggflac_decoder_plugin = { - .name = "oggflac", - .stream_decode = oggflac_decode, - .stream_tag = oggflac_stream_tag, - .suffixes = oggflac_suffixes, - .mime_types = oggflac_mime_types -}; diff --git a/src/decoder/pcm_decoder_plugin.c b/src/decoder/pcm_decoder_plugin.c new file mode 100644 index 000000000..fc7dffc05 --- /dev/null +++ b/src/decoder/pcm_decoder_plugin.c @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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/pcm_decoder_plugin.h" +#include "decoder_api.h" +#include "util/byte_reverse.h" + +#include <glib.h> +#include <unistd.h> +#include <stdio.h> /* for SEEK_SET */ + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "pcm" + +static void +pcm_stream_decode(struct decoder *decoder, struct input_stream *is) +{ + static const struct audio_format audio_format = { + .sample_rate = 44100, + .format = SAMPLE_FORMAT_S16, + .channels = 2, + }; + + const bool reverse_endian = is->mime != NULL && + strcmp(is->mime, "audio/x-mpd-cdda-pcm-reverse") == 0; + + GError *error = NULL; + enum decoder_command cmd; + + double time_to_size = audio_format_time_to_size(&audio_format); + + float total_time = -1; + if (is->size >= 0) + total_time = is->size / time_to_size; + + decoder_initialized(decoder, &audio_format, is->seekable, total_time); + + do { + char buffer[4096]; + + size_t nbytes = decoder_read(decoder, is, + buffer, sizeof(buffer)); + + if (nbytes == 0 && input_stream_lock_eof(is)) + break; + + if (reverse_endian) + /* make sure we deliver samples in host byte order */ + reverse_bytes_16((uint16_t *)buffer, + (uint16_t *)buffer, + (uint16_t *)(buffer + nbytes)); + + cmd = nbytes > 0 + ? decoder_data(decoder, is, + buffer, nbytes, 0) + : decoder_get_command(decoder); + if (cmd == DECODE_COMMAND_SEEK) { + goffset offset = (goffset)(time_to_size * + decoder_seek_where(decoder)); + if (input_stream_lock_seek(is, offset, SEEK_SET, + &error)) { + decoder_command_finished(decoder); + } else { + g_warning("seeking failed: %s", error->message); + g_error_free(error); + decoder_seek_error(decoder); + } + + cmd = DECODE_COMMAND_NONE; + } + } while (cmd == DECODE_COMMAND_NONE); +} + +static const char *const pcm_mime_types[] = { + /* for streams obtained by the cdio_paranoia input plugin */ + "audio/x-mpd-cdda-pcm", + + /* same as above, but with reverse byte order */ + "audio/x-mpd-cdda-pcm-reverse", + + NULL +}; + +const struct decoder_plugin pcm_decoder_plugin = { + .name = "pcm", + .stream_decode = pcm_stream_decode, + .mime_types = pcm_mime_types, +}; diff --git a/src/decoder/pcm_decoder_plugin.h b/src/decoder/pcm_decoder_plugin.h new file mode 100644 index 000000000..11df80155 --- /dev/null +++ b/src/decoder/pcm_decoder_plugin.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * Not really a decoder; this plugin forwards its input data "as-is". + * + * It was written only to support the "cdio_paranoia" input plugin, + * which does not need a decoder. + */ + +#ifndef MPD_DECODER_PCM_H +#define MPD_DECODER_PCM_H + +extern const struct decoder_plugin pcm_decoder_plugin; + +#endif diff --git a/src/decoder/sidplay_decoder_plugin.cxx b/src/decoder/sidplay_decoder_plugin.cxx index 6fceeb30f..5d162f179 100644 --- a/src/decoder/sidplay_decoder_plugin.cxx +++ b/src/decoder/sidplay_decoder_plugin.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,6 +21,7 @@ extern "C" { #include "../decoder_api.h" +#include "tag_handler.h" } #include <errno.h> @@ -200,7 +201,6 @@ get_song_length(const char *path_fs) static void sidplay_file_decode(struct decoder *decoder, const char *path_fs) { - int ret; int channels; /* load the tune */ @@ -336,8 +336,9 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs) } while (cmd != DECODE_COMMAND_STOP); } -static struct tag * -sidplay_tag_dup(const char *path_fs) +static bool +sidplay_scan_file(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) { int song_num=get_song_num(path_fs); char *path_container=get_container_name(path_fs); @@ -345,10 +346,9 @@ sidplay_tag_dup(const char *path_fs) SidTune tune(path_container, NULL, true); g_free(path_container); if (!tune) - return NULL; + return false; const SidTuneInfo &info = tune.getInfo(); - struct tag *tag = tag_new(); /* title */ const char *title; @@ -360,25 +360,28 @@ sidplay_tag_dup(const char *path_fs) 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); + tag_handler_invoke_tag(handler, handler_ctx, + TAG_TITLE, tag_title); g_free(tag_title); } else - tag_add_item(tag, TAG_TITLE, title); + tag_handler_invoke_tag(handler, handler_ctx, TAG_TITLE, title); /* artist */ if (info.numberOfInfoStrings > 1 && info.infoString[1] != NULL) - tag_add_item(tag, TAG_ARTIST, info.infoString[1]); + tag_handler_invoke_tag(handler, handler_ctx, TAG_ARTIST, + info.infoString[1]); /* track */ char *track=g_strdup_printf("%d", song_num); - tag_add_item(tag, TAG_TRACK, track); + tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track); g_free(track); /* time */ int song_len=get_song_length(path_fs); - if(song_len!=-1) tag->time=song_len; + if (song_len >= 0) + tag_handler_invoke_duration(handler, handler_ctx, song_len); - return tag; + return true; } static char * @@ -421,7 +424,7 @@ const struct decoder_plugin sidplay_decoder_plugin = { sidplay_finish, NULL, /* stream_decode() */ sidplay_file_decode, - sidplay_tag_dup, + sidplay_scan_file, NULL, /* stream_tag() */ sidplay_container_scan, sidplay_suffixes, diff --git a/src/decoder/sndfile_decoder_plugin.c b/src/decoder/sndfile_decoder_plugin.c index af68f117d..8dd98236f 100644 --- a/src/decoder/sndfile_decoder_plugin.c +++ b/src/decoder/sndfile_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ #include "config.h" #include "decoder_api.h" #include "audio_check.h" +#include "tag_handler.h" #include <sndfile.h> @@ -40,7 +41,7 @@ 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); + success = input_stream_lock_seek(is, offset, whence, NULL); if (!success) return -1; @@ -54,7 +55,7 @@ sndfile_vio_read(void *ptr, sf_count_t count, void *user_data) GError *error = NULL; size_t nbytes; - nbytes = input_stream_read(is, ptr, count, &error); + nbytes = input_stream_lock_read(is, ptr, count, &error); if (nbytes == 0 && error != NULL) { g_warning("%s", error->message); g_error_free(error); @@ -172,44 +173,47 @@ sndfile_stream_decode(struct decoder *decoder, struct input_stream *is) sf_close(sf); } -static struct tag * -sndfile_tag_dup(const char *path_fs) +static bool +sndfile_scan_file(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) { 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; + return false; if (!audio_valid_sample_rate(info.samplerate)) { sf_close(sf); g_warning("Invalid sample rate in %s\n", path_fs); - return NULL; + return false; } - tag = tag_new(); - tag->time = info.frames / info.samplerate; + tag_handler_invoke_duration(handler, handler_ctx, + info.frames / info.samplerate); p = sf_get_string(sf, SF_STR_TITLE); if (p != NULL) - tag_add_item(tag, TAG_TITLE, p); + tag_handler_invoke_tag(handler, handler_ctx, + TAG_TITLE, p); p = sf_get_string(sf, SF_STR_ARTIST); if (p != NULL) - tag_add_item(tag, TAG_ARTIST, p); + tag_handler_invoke_tag(handler, handler_ctx, + TAG_ARTIST, p); p = sf_get_string(sf, SF_STR_DATE); if (p != NULL) - tag_add_item(tag, TAG_DATE, p); + tag_handler_invoke_tag(handler, handler_ctx, + TAG_DATE, p); sf_close(sf); - return tag; + return true; } static const char *const sndfile_suffixes[] = { @@ -245,7 +249,7 @@ static const char *const sndfile_mime_types[] = { const struct decoder_plugin sndfile_decoder_plugin = { .name = "sndfile", .stream_decode = sndfile_stream_decode, - .tag_dup = sndfile_tag_dup, + .scan_file = sndfile_scan_file, .suffixes = sndfile_suffixes, .mime_types = sndfile_mime_types, }; diff --git a/src/decoder/vorbis_comments.c b/src/decoder/vorbis_comments.c new file mode 100644 index 000000000..6c2d57b72 --- /dev/null +++ b/src/decoder/vorbis_comments.c @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "vorbis_comments.h" +#include "tag.h" +#include "tag_table.h" +#include "tag_handler.h" +#include "replay_gain_info.h" + +#include <glib.h> +#include <assert.h> +#include <stddef.h> +#include <string.h> +#include <stdlib.h> + +static const char * +vorbis_comment_value(const char *comment, const char *needle) +{ + size_t len = strlen(needle); + + if (g_ascii_strncasecmp(comment, needle, len) == 0 && + comment[len] == '=') + return comment + len + 1; + + return NULL; +} + +bool +vorbis_comments_to_replay_gain(struct replay_gain_info *rgi, char **comments) +{ + const char *temp; + bool found = false; + + replay_gain_info_init(rgi); + + while (*comments) { + if ((temp = + vorbis_comment_value(*comments, "replaygain_track_gain"))) { + rgi->tuples[REPLAY_GAIN_TRACK].gain = atof(temp); + found = true; + } else if ((temp = vorbis_comment_value(*comments, + "replaygain_album_gain"))) { + rgi->tuples[REPLAY_GAIN_ALBUM].gain = atof(temp); + found = true; + } else if ((temp = vorbis_comment_value(*comments, + "replaygain_track_peak"))) { + rgi->tuples[REPLAY_GAIN_TRACK].peak = atof(temp); + found = true; + } else if ((temp = vorbis_comment_value(*comments, + "replaygain_album_peak"))) { + rgi->tuples[REPLAY_GAIN_ALBUM].peak = atof(temp); + found = true; + } + + comments++; + } + + return found; +} + +/** + * Check if the comment's name equals the passed name, and if so, copy + * the comment value into the tag. + */ +static bool +vorbis_copy_comment(const char *comment, + const char *name, enum tag_type tag_type, + const struct tag_handler *handler, void *handler_ctx) +{ + const char *value; + + value = vorbis_comment_value(comment, name); + if (value != NULL) { + tag_handler_invoke_tag(handler, handler_ctx, tag_type, value); + return true; + } + + return false; +} + +static const struct tag_table vorbis_tags[] = { + { "tracknumber", TAG_TRACK }, + { "discnumber", TAG_DISC }, + { "album artist", TAG_ALBUM_ARTIST }, + { NULL, TAG_NUM_OF_ITEM_TYPES } +}; + +static void +vorbis_scan_comment(const char *comment, + const struct tag_handler *handler, void *handler_ctx) +{ + if (handler->pair != NULL) { + char *name = g_strdup((const char*)comment); + char *value = strchr(name, '='); + + if (value != NULL && value > name) { + *value++ = 0; + tag_handler_invoke_pair(handler, handler_ctx, + name, value); + } + + g_free(name); + } + + for (const struct tag_table *i = vorbis_tags; i->name != NULL; ++i) + if (vorbis_copy_comment(comment, i->name, i->type, + handler, handler_ctx)) + return; + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + if (vorbis_copy_comment(comment, + tag_item_names[i], i, + handler, handler_ctx)) + return; +} + +void +vorbis_comments_scan(char **comments, + const struct tag_handler *handler, void *handler_ctx) +{ + while (*comments) + vorbis_scan_comment(*comments++, + handler, handler_ctx); + +} + +struct tag * +vorbis_comments_to_tag(char **comments) +{ + struct tag *tag = tag_new(); + vorbis_comments_scan(comments, &add_tag_handler, tag); + + if (tag_is_empty(tag)) { + tag_free(tag); + tag = NULL; + } + + return tag; +} diff --git a/src/decoder/vorbis_comments.h b/src/decoder/vorbis_comments.h new file mode 100644 index 000000000..c15096930 --- /dev/null +++ b/src/decoder/vorbis_comments.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_VORBIS_COMMENTS_H +#define MPD_VORBIS_COMMENTS_H + +#include "check.h" + +#include <stdbool.h> + +struct replay_gain_info; +struct tag_handler; + +bool +vorbis_comments_to_replay_gain(struct replay_gain_info *rgi, char **comments); + +void +vorbis_comments_scan(char **comments, + const struct tag_handler *handler, void *handler_ctx); + +struct tag * +vorbis_comments_to_tag(char **comments); + +#endif diff --git a/src/decoder/vorbis_decoder_plugin.c b/src/decoder/vorbis_decoder_plugin.c index 0a3944ad6..15cdc0ca9 100644 --- a/src/decoder/vorbis_decoder_plugin.c +++ b/src/decoder/vorbis_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,9 +18,11 @@ */ #include "config.h" +#include "vorbis_comments.h" #include "_ogg_common.h" #include "audio_check.h" #include "uri.h" +#include "tag_handler.h" #ifndef HAVE_TREMOR #define OV_EXCLUDE_STATIC_CALLBACKS @@ -42,7 +44,6 @@ #include <assert.h> #include <errno.h> -#include <stdlib.h> #include <unistd.h> #undef G_LOG_DOMAIN @@ -80,7 +81,7 @@ static int ogg_seek_cb(void *data, ogg_int64_t offset, int whence) return vis->seekable && (!vis->decoder || decoder_get_command(vis->decoder) != DECODE_COMMAND_STOP) && - input_stream_seek(vis->input_stream, offset, whence, NULL) + input_stream_lock_seek(vis->input_stream, offset, whence, NULL) ? 0 : -1; } @@ -150,108 +151,6 @@ vorbis_is_open(struct vorbis_input_stream *vis, OggVorbis_File *vf, return true; } -static const char * -vorbis_comment_value(const char *comment, const char *needle) -{ - size_t len = strlen(needle); - - if (g_ascii_strncasecmp(comment, needle, len) == 0 && - comment[len] == '=') - return comment + len + 1; - - return NULL; -} - -static bool -vorbis_comments_to_replay_gain(struct replay_gain_info *rgi, char **comments) -{ - const char *temp; - bool found = false; - - replay_gain_info_init(rgi); - - while (*comments) { - if ((temp = - vorbis_comment_value(*comments, "replaygain_track_gain"))) { - rgi->tuples[REPLAY_GAIN_TRACK].gain = atof(temp); - found = true; - } else if ((temp = vorbis_comment_value(*comments, - "replaygain_album_gain"))) { - rgi->tuples[REPLAY_GAIN_ALBUM].gain = atof(temp); - found = true; - } else if ((temp = vorbis_comment_value(*comments, - "replaygain_track_peak"))) { - rgi->tuples[REPLAY_GAIN_TRACK].peak = atof(temp); - found = true; - } else if ((temp = vorbis_comment_value(*comments, - "replaygain_album_peak"))) { - rgi->tuples[REPLAY_GAIN_ALBUM].peak = atof(temp); - found = true; - } - - comments++; - } - - return found; -} - -static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber"; -static const char *VORBIS_COMMENT_DISC_KEY = "discnumber"; - -/** - * Check if the comment's name equals the passed name, and if so, copy - * the comment value into the tag. - */ -static bool -vorbis_copy_comment(struct tag *tag, const char *comment, - const char *name, enum tag_type tag_type) -{ - const char *value; - - value = vorbis_comment_value(comment, name); - if (value != NULL) { - tag_add_item(tag, tag_type, value); - return true; - } - - return false; -} - -static void -vorbis_parse_comment(struct tag *tag, const char *comment) -{ - assert(tag != NULL); - - if (vorbis_copy_comment(tag, comment, VORBIS_COMMENT_TRACK_KEY, - TAG_TRACK) || - vorbis_copy_comment(tag, comment, VORBIS_COMMENT_DISC_KEY, - TAG_DISC) || - vorbis_copy_comment(tag, comment, "album artist", - TAG_ALBUM_ARTIST)) - return; - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - if (vorbis_copy_comment(tag, comment, - tag_item_names[i], i)) - return; -} - -static struct tag * -vorbis_comments_to_tag(char **comments) -{ - struct tag *tag = tag_new(); - - while (*comments) - vorbis_parse_comment(tag, *comments++); - - if (tag_is_empty(tag)) { - tag_free(tag); - tag = NULL; - } - - return tag; -} - static void vorbis_send_comments(struct decoder *decoder, struct input_stream *is, char **comments) @@ -290,7 +189,7 @@ 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, NULL); + input_stream_lock_seek(input_stream, 0, SEEK_SET, NULL); if (!vorbis_is_open(&vis, &vf, decoder, input_stream)) return; @@ -370,24 +269,24 @@ vorbis_stream_decode(struct decoder *decoder, ov_clear(&vf); } -static struct tag * -vorbis_stream_tag(struct input_stream *is) +static bool +vorbis_scan_stream(struct input_stream *is, + const struct tag_handler *handler, void *handler_ctx) { struct vorbis_input_stream vis; OggVorbis_File vf; if (!vorbis_is_open(&vis, &vf, NULL, is)) - return NULL; + return false; - struct tag *tag = vorbis_comments_to_tag(ov_comment(&vf, -1)->user_comments); + tag_handler_invoke_duration(handler, handler_ctx, + (int)(ov_time_total(&vf, -1) + 0.5)); - if (tag == NULL) - tag = tag_new(); - tag->time = (int)(ov_time_total(&vf, -1) + 0.5); + vorbis_comments_scan(ov_comment(&vf, -1)->user_comments, + handler, handler_ctx); ov_clear(&vf); - - return tag; + return true; } static const char *const vorbis_suffixes[] = { @@ -409,7 +308,7 @@ static const char *const vorbis_mime_types[] = { const struct decoder_plugin vorbis_decoder_plugin = { .name = "vorbis", .stream_decode = vorbis_stream_decode, - .stream_tag = vorbis_stream_tag, + .scan_stream = vorbis_scan_stream, .suffixes = vorbis_suffixes, .mime_types = vorbis_mime_types }; diff --git a/src/decoder/wavpack_decoder_plugin.c b/src/decoder/wavpack_decoder_plugin.c index 61026842b..ae85b0e27 100644 --- a/src/decoder/wavpack_decoder_plugin.c +++ b/src/decoder/wavpack_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,6 +22,8 @@ #include "audio_check.h" #include "path.h" #include "utils.h" +#include "tag_table.h" +#include "tag_handler.h" #include <wavpack/wavpack.h> #include <glib.h> @@ -36,10 +38,7 @@ #define ERRORLEN 80 -static struct { - const char *name; - enum tag_type type; -} tagtypes[] = { +static const struct tag_table wavpack_tags[] = { { "artist", TAG_ARTIST }, { "album", TAG_ALBUM }, { "title", TAG_TITLE }, @@ -51,6 +50,7 @@ static struct { { "performer", TAG_PERFORMER }, { "comment", TAG_COMMENT }, { "disc", TAG_DISC }, + { NULL, TAG_NUM_OF_ITEM_TYPES } }; /** A pointer type for format converter function. */ @@ -111,12 +111,11 @@ static void format_samples_float(G_GNUC_UNUSED int bytes_per_sample, void *buffer, uint32_t count) { - int32_t *dst = buffer; - float *src = buffer; - assert_static(sizeof(*dst) <= sizeof(*src)); + float *p = buffer; while (count--) { - *dst++ = (int32_t)(*src++ + 0.5f); + *p /= (1 << 23); + ++p; } } @@ -127,7 +126,7 @@ static enum sample_format wavpack_bits_to_sample_format(bool is_float, int bytes_per_sample) { if (is_float) - return SAMPLE_FORMAT_S24_P32; + return SAMPLE_FORMAT_FLOAT; switch (bytes_per_sample) { case 1: @@ -273,17 +272,41 @@ wavpack_replaygain(struct replay_gain_info *replay_gain_info, return found; } +static void +wavpack_scan_tag_item(WavpackContext *wpc, const char *name, + enum tag_type type, + const struct tag_handler *handler, void *handler_ctx) +{ + char buffer[1024]; + int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer)); + if (len <= 0 || (unsigned)len >= sizeof(buffer)) + return; + + tag_handler_invoke_tag(handler, handler_ctx, type, buffer); + +} + +static void +wavpack_scan_pair(WavpackContext *wpc, const char *name, + const struct tag_handler *handler, void *handler_ctx) +{ + char buffer[8192]; + int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer)); + if (len <= 0 || (unsigned)len >= sizeof(buffer)) + return; + + tag_handler_invoke_pair(handler, handler_ctx, name, buffer); +} + /* * Reads metainfo from the specified file. */ -static struct tag * -wavpack_tagdup(const char *fname) +static bool +wavpack_scan_file(const char *fname, + const struct tag_handler *handler, void *handler_ctx) { WavpackContext *wpc; - struct tag *tag; char error[ERRORLEN]; - char *s; - int size, allocated_size; wpc = WavpackOpenFileInput(fname, error, OPEN_TAGS, 0); if (wpc == NULL) { @@ -291,40 +314,34 @@ wavpack_tagdup(const char *fname) "failed to open WavPack file \"%s\": %s\n", fname, error ); - return NULL; + return false; } - tag = tag_new(); - tag->time = WavpackGetNumSamples(wpc); - tag->time /= WavpackGetSampleRate(wpc); - - allocated_size = 0; - s = NULL; - - for (unsigned i = 0; i < G_N_ELEMENTS(tagtypes); ++i) { - size = WavpackGetTagItem(wpc, tagtypes[i].name, NULL, 0); - if (size > 0) { - ++size; /* EOS */ - - if (s == NULL) { - s = g_malloc(size); - allocated_size = size; - } else if (size > allocated_size) { - char *t = (char *)g_realloc(s, size); - allocated_size = size; - s = t; - } + tag_handler_invoke_duration(handler, handler_ctx, + WavpackGetNumSamples(wpc) / + WavpackGetSampleRate(wpc)); - WavpackGetTagItem(wpc, tagtypes[i].name, s, size); - tag_add_item(tag, tagtypes[i].type, s); + for (const struct tag_table *i = wavpack_tags; i->name != NULL; ++i) + wavpack_scan_tag_item(wpc, i->name, i->type, + handler, handler_ctx); + + if (handler->pair != NULL) { + char name[64]; + + for (int i = 0, n = WavpackGetNumTagItems(wpc); + i < n; ++i) { + int len = WavpackGetTagItemIndexed(wpc, i, name, + sizeof(name)); + if (len <= 0 || (unsigned)len >= sizeof(name)) + continue; + + wavpack_scan_pair(wpc, name, handler, handler_ctx); } } - g_free(s); - WavpackCloseFile(wpc); - return tag; + return true; } /* @@ -390,13 +407,15 @@ 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, NULL) ? 0 : -1; + return input_stream_lock_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, NULL) ? 0 : -1; + return input_stream_lock_seek(wpin(id)->is, delta, mode, NULL) + ? 0 : -1; } static int @@ -447,6 +466,7 @@ wavpack_input_init(struct wavpack_input *isp, struct decoder *decoder, static struct input_stream * wavpack_open_wvc(struct decoder *decoder, const char *uri, + GMutex *mutex, GCond *cond, struct wavpack_input *wpi) { struct input_stream *is_wvc; @@ -462,7 +482,7 @@ wavpack_open_wvc(struct decoder *decoder, const char *uri, return false; wvc_url = g_strconcat(uri, "c", NULL); - is_wvc = input_stream_open(wvc_url, NULL); + is_wvc = input_stream_open(wvc_url, mutex, cond, NULL); g_free(wvc_url); if (is_wvc == NULL) @@ -499,7 +519,8 @@ wavpack_streamdecode(struct decoder * decoder, struct input_stream *is) struct wavpack_input isp, isp_wvc; bool can_seek = is->seekable; - is_wvc = wavpack_open_wvc(decoder, is->uri, &isp_wvc); + is_wvc = wavpack_open_wvc(decoder, is->uri, is->mutex, is->cond, + &isp_wvc); if (is_wvc != NULL) { open_flags |= OPEN_WVC; can_seek &= is_wvc->seekable; @@ -573,7 +594,7 @@ const struct decoder_plugin wavpack_decoder_plugin = { .name = "wavpack", .stream_decode = wavpack_streamdecode, .file_decode = wavpack_filedecode, - .tag_dup = wavpack_tagdup, + .scan_file = wavpack_scan_file, .suffixes = wavpack_suffixes, .mime_types = wavpack_mime_types }; diff --git a/src/decoder/wildmidi_decoder_plugin.c b/src/decoder/wildmidi_decoder_plugin.c index 66e6c61cf..a2224940d 100644 --- a/src/decoder/wildmidi_decoder_plugin.c +++ b/src/decoder/wildmidi_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,6 +19,7 @@ #include "config.h" #include "decoder_api.h" +#include "tag_handler.h" #include <glib.h> @@ -111,25 +112,26 @@ wildmidi_file_decode(struct decoder *decoder, const char *path_fs) WildMidi_Close(wm); } -static struct tag * -wildmidi_tag_dup(const char *path_fs) +static bool +wildmidi_scan_file(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) { midi *wm = WildMidi_Open(path_fs); if (wm == NULL) - return NULL; + return false; const struct _WM_Info *info = WildMidi_GetInfo(wm); if (info == NULL) { WildMidi_Close(wm); - return NULL; + return false; } - struct tag *tag = tag_new(); - tag->time = info->approx_total_samples / WILDMIDI_SAMPLE_RATE; + int duration = info->approx_total_samples / WILDMIDI_SAMPLE_RATE; + tag_handler_invoke_duration(handler, handler_ctx, duration); WildMidi_Close(wm); - return tag; + return true; } static const char *const wildmidi_suffixes[] = { @@ -142,6 +144,6 @@ const struct decoder_plugin wildmidi_decoder_plugin = { .init = wildmidi_init, .finish = wildmidi_finish, .file_decode = wildmidi_file_decode, - .tag_dup = wildmidi_tag_dup, + .scan_file = wildmidi_scan_file, .suffixes = wildmidi_suffixes, }; diff --git a/src/decoder_api.c b/src/decoder_api.c index 19de47855..a45d0f1e6 100644 --- a/src/decoder_api.c +++ b/src/decoder_api.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,8 +21,7 @@ #include "decoder_api.h" #include "decoder_internal.h" #include "decoder_control.h" -#include "player_control.h" -#include "audio.h" +#include "audio_config.h" #include "song.h" #include "buffer.h" #include "pipe.h" @@ -63,10 +62,9 @@ decoder_initialized(struct decoder *decoder, decoder_lock(dc); dc->state = DECODE_STATE_DECODE; + g_cond_signal(dc->client_cond); decoder_unlock(dc); - player_lock_signal(); - g_debug("audio_format=%s, seekable=%s", audio_format_to_string(&dc->in_audio_format, &af_string), seekable ? "true" : "false"); @@ -189,9 +187,8 @@ decoder_command_finished(struct decoder *decoder) } dc->command = DECODE_COMMAND_NONE; + g_cond_signal(dc->client_cond); decoder_unlock(dc); - - player_lock_signal(); } double decoder_seek_where(G_GNUC_UNUSED struct decoder * decoder) @@ -231,50 +228,73 @@ void decoder_seek_error(struct decoder * decoder) decoder_command_finished(decoder); } +/** + * Should be read operation be cancelled? That is the case when the + * player thread has sent a command such as "STOP". + */ +G_GNUC_PURE +static inline bool +decoder_check_cancel_read(const struct decoder *decoder) +{ + if (decoder == NULL) + return false; + + const struct decoder_control *dc = decoder->dc; + if (dc->command == DECODE_COMMAND_NONE) + return false; + + /* ignore the SEEK command during initialization, the plugin + should handle that after it has initialized successfully */ + if (dc->command == DECODE_COMMAND_SEEK && + (dc->state == DECODE_STATE_START || decoder->seeking)) + return false; + + return true; +} + 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; + /* XXX don't allow decoder==NULL */ GError *error = NULL; size_t nbytes; assert(decoder == NULL || - dc->state == DECODE_STATE_START || - dc->state == DECODE_STATE_DECODE); + decoder->dc->state == DECODE_STATE_START || + decoder->dc->state == DECODE_STATE_DECODE); assert(is != NULL); assert(buffer != NULL); if (length == 0) return 0; + input_stream_lock(is); + while (true) { - /* XXX don't allow decoder==NULL */ - if (decoder != NULL && - /* 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) + if (decoder_check_cancel_read(decoder)) { + input_stream_unlock(is); return 0; + } - nbytes = input_stream_read(is, buffer, length, &error); + if (input_stream_available(is)) + break; - if (G_UNLIKELY(nbytes == 0 && error != NULL)) { - g_warning("%s", error->message); - g_error_free(error); - return 0; - } + g_cond_wait(is->cond, is->mutex); + } - if (nbytes > 0 || input_stream_eof(is)) - return nbytes; + nbytes = input_stream_read(is, buffer, length, &error); + assert(nbytes == 0 || error == NULL); + assert(nbytes > 0 || error != NULL || input_stream_eof(is)); - /* sleep for a fraction of a second! */ - /* XXX don't sleep, wait for an event instead */ - g_usleep(10000); + if (G_UNLIKELY(nbytes == 0 && error != NULL)) { + g_warning("%s", error->message); + g_error_free(error); } + + input_stream_unlock(is); + + return nbytes; } void @@ -291,8 +311,7 @@ decoder_timestamp(struct decoder *decoder, double t) * (decoder.chunk) if there is one. */ static enum decoder_command -do_send_tag(struct decoder *decoder, struct input_stream *is, - const struct tag *tag) +do_send_tag(struct decoder *decoder, const struct tag *tag) { struct music_chunk *chunk; @@ -300,12 +319,12 @@ 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); - player_lock_signal(); + g_cond_signal(decoder->dc->client_cond); } assert(decoder->chunk == NULL); - chunk = decoder_get_chunk(decoder, is); + chunk = decoder_get_chunk(decoder); if (chunk == NULL) { assert(decoder->dc->command != DECODE_COMMAND_NONE); return decoder->dc->command; @@ -321,7 +340,7 @@ update_stream_tag(struct decoder *decoder, struct input_stream *is) struct tag *tag; tag = is != NULL - ? input_stream_tag(is) + ? input_stream_lock_tag(is) : NULL; if (tag == NULL) { tag = decoder->song_tag; @@ -372,11 +391,11 @@ decoder_data(struct decoder *decoder, tag = tag_merge(decoder->decoder_tag, decoder->stream_tag); - cmd = do_send_tag(decoder, is, tag); + cmd = do_send_tag(decoder, tag); tag_free(tag); } else /* send only the stream tag */ - cmd = do_send_tag(decoder, is, decoder->stream_tag); + cmd = do_send_tag(decoder, decoder->stream_tag); if (cmd != DECODE_COMMAND_NONE) return cmd; @@ -402,7 +421,7 @@ decoder_data(struct decoder *decoder, size_t nbytes; bool full; - chunk = decoder_get_chunk(decoder, is); + chunk = decoder_get_chunk(decoder); if (chunk == NULL) { assert(dc->command != DECODE_COMMAND_NONE); return dc->command; @@ -415,7 +434,7 @@ decoder_data(struct decoder *decoder, if (dest == NULL) { /* the chunk is full, flush it */ decoder_flush_chunk(decoder); - player_lock_signal(); + g_cond_signal(dc->client_cond); continue; } @@ -434,7 +453,7 @@ decoder_data(struct decoder *decoder, if (full) { /* the chunk is full, flush it */ decoder_flush_chunk(decoder); - player_lock_signal(); + g_cond_signal(dc->client_cond); } data += nbytes; @@ -489,11 +508,11 @@ decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is, struct tag *merged; merged = tag_merge(decoder->stream_tag, decoder->decoder_tag); - cmd = do_send_tag(decoder, is, merged); + cmd = do_send_tag(decoder, merged); tag_free(merged); } else /* send only the decoder tag */ - cmd = do_send_tag(decoder, is, tag); + cmd = do_send_tag(decoder, tag); return cmd; } @@ -526,7 +545,7 @@ decoder_replay_gain(struct decoder *decoder, replay gain values affect the following samples */ decoder_flush_chunk(decoder); - player_lock_signal(); + g_cond_signal(decoder->dc->client_cond); } } else decoder->replay_gain_serial = 0; diff --git a/src/decoder_api.h b/src/decoder_api.h index 8b5f3d82b..6e011c395 100644 --- a/src/decoder_api.h +++ b/src/decoder_api.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -83,7 +83,7 @@ double decoder_seek_where(struct decoder *decoder); /** - * Call this right before decoder_command_finished() when seeking has + * Call this instead of decoder_command_finished() when seeking has * failed. * * @param decoder the decoder object diff --git a/src/decoder_buffer.c b/src/decoder_buffer.c index 8f8eb8545..fcb135976 100644 --- a/src/decoder_buffer.c +++ b/src/decoder_buffer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_buffer.h b/src/decoder_buffer.h index b6051e122..77eff5dd1 100644 --- a/src/decoder_buffer.h +++ b/src/decoder_buffer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 4a2e49f3e..795e13fb2 100644 --- a/src/decoder_command.h +++ b/src/decoder_command.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 85c2e5ba8..70f34b331 100644 --- a/src/decoder_control.c +++ b/src/decoder_control.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,7 +19,6 @@ #include "config.h" #include "decoder_control.h" -#include "player_control.h" #include "pipe.h" #include <assert.h> @@ -27,13 +26,16 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "decoder_control" -void -dc_init(struct decoder_control *dc) +struct decoder_control * +dc_new(GCond *client_cond) { + struct decoder_control *dc = g_new(struct decoder_control, 1); + dc->thread = NULL; dc->mutex = g_mutex_new(); dc->cond = g_cond_new(); + dc->client_cond = client_cond; dc->state = DECODE_STATE_STOP; dc->command = DECODE_COMMAND_NONE; @@ -43,34 +45,26 @@ dc_init(struct decoder_control *dc) dc->mixramp_start = NULL; dc->mixramp_end = NULL; dc->mixramp_prev_end = NULL; + + return dc; } void -dc_deinit(struct decoder_control *dc) +dc_free(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; + g_free(dc); } static void dc_command_wait_locked(struct decoder_control *dc) { while (dc->command != DECODE_COMMAND_NONE) - player_wait_decoder(dc); -} - -void -dc_command_wait(struct decoder_control *dc) -{ - decoder_lock(dc); - dc_command_wait_locked(dc); - decoder_unlock(dc); + g_cond_wait(dc->client_cond, dc->mutex); } static void diff --git a/src/decoder_control.h b/src/decoder_control.h index 64c7c302e..566b153ee 100644 --- a/src/decoder_control.h +++ b/src/decoder_control.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -58,6 +58,12 @@ struct decoder_control { */ GCond *cond; + /** + * The trigger of this object's client. It is signalled + * whenever an event occurs. + */ + GCond *client_cond; + enum decoder_state state; enum decoder_command command; @@ -114,11 +120,12 @@ struct decoder_control { char *mixramp_prev_end; }; -void -dc_init(struct decoder_control *dc); +G_GNUC_MALLOC +struct decoder_control * +dc_new(GCond *client_cond); void -dc_deinit(struct decoder_control *dc); +dc_free(struct decoder_control *dc); /** * Locks the #decoder_control object. @@ -234,9 +241,6 @@ decoder_current_song(const struct decoder_control *dc) return NULL; } -void -dc_command_wait(struct decoder_control *dc); - /** * Start the decoder. * diff --git a/src/decoder_internal.c b/src/decoder_internal.c index 990d728e9..bc349f2ff 100644 --- a/src/decoder_internal.c +++ b/src/decoder_internal.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,7 +20,6 @@ #include "config.h" #include "decoder_internal.h" #include "decoder_control.h" -#include "player_control.h" #include "pipe.h" #include "input_stream.h" #include "buffer.h" @@ -29,43 +28,19 @@ #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 decoder_control *dc, struct input_stream *is, bool do_wait) +need_chunks(struct decoder_control *dc, bool do_wait) { if (dc->command == DECODE_COMMAND_STOP || dc->command == DECODE_COMMAND_SEEK) return dc->command; - if ((is == NULL || !decoder_input_buffer(dc, is)) && do_wait) { + if (do_wait) { decoder_wait(dc); - player_signal(); + g_cond_signal(dc->client_cond); return dc->command; } @@ -74,7 +49,7 @@ need_chunks(struct decoder_control *dc, struct input_stream *is, bool do_wait) } struct music_chunk * -decoder_get_chunk(struct decoder *decoder, struct input_stream *is) +decoder_get_chunk(struct decoder *decoder) { struct decoder_control *dc = decoder->dc; enum decoder_command cmd; @@ -97,7 +72,7 @@ decoder_get_chunk(struct decoder *decoder, struct input_stream *is) } decoder_lock(dc); - cmd = need_chunks(dc, is, true); + cmd = need_chunks(dc, true); decoder_unlock(dc); } while (cmd == DECODE_COMMAND_NONE); diff --git a/src/decoder_internal.h b/src/decoder_internal.h index 5818632e5..d89e68cfc 100644 --- a/src/decoder_internal.h +++ b/src/decoder_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -89,7 +89,7 @@ struct decoder { * @return the chunk, or NULL if we have received a decoder command */ struct music_chunk * -decoder_get_chunk(struct decoder *decoder, struct input_stream *is); +decoder_get_chunk(struct decoder *decoder); /** * Flushes the current chunk. diff --git a/src/decoder_list.c b/src/decoder_list.c index d76050023..2029b3e62 100644 --- a/src/decoder_list.c +++ b/src/decoder_list.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,6 +23,8 @@ #include "utils.h" #include "conf.h" #include "mpd_error.h" +#include "decoder/pcm_decoder_plugin.h" +#include "decoder/dsdiff_decoder_plugin.h" #include <glib.h> @@ -57,7 +59,7 @@ const struct decoder_plugin *const decoder_plugins[] = { #ifdef ENABLE_VORBIS_DECODER &vorbis_decoder_plugin, #endif -#if defined(HAVE_FLAC) || defined(HAVE_OGGFLAC) +#if defined(HAVE_FLAC) &oggflac_decoder_plugin, #endif #ifdef HAVE_FLAC @@ -69,6 +71,7 @@ const struct decoder_plugin *const decoder_plugins[] = { #ifdef HAVE_AUDIOFILE &audiofile_decoder_plugin, #endif + &dsdiff_decoder_plugin, #ifdef HAVE_FAAD &faad_decoder_plugin, #endif @@ -102,6 +105,7 @@ const struct decoder_plugin *const decoder_plugins[] = { #ifdef HAVE_GME &gme_decoder_plugin, #endif + &pcm_decoder_plugin, NULL }; diff --git a/src/decoder_list.h b/src/decoder_list.h index 7041db0c9..d259cb195 100644 --- a/src/decoder_list.h +++ b/src/decoder_list.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_plugin.c b/src/decoder_plugin.c index 062dad364..d32043f0e 100644 --- a/src/decoder_plugin.c +++ b/src/decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,7 +19,7 @@ #include "config.h" #include "decoder_plugin.h" -#include "utils.h" +#include "string_util.h" #include <assert.h> diff --git a/src/decoder_plugin.h b/src/decoder_plugin.h index d8371ddb8..933ba6751 100644 --- a/src/decoder_plugin.h +++ b/src/decoder_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,6 +26,7 @@ struct config_param; struct input_stream; struct tag; +struct tag_handler; /** * Opaque handle which the decoder plugin passes to the functions in @@ -70,18 +71,22 @@ struct decoder_plugin { void (*file_decode)(struct decoder *decoder, const char *path_fs); /** - * Read the tags of a local file. + * Scan metadata of a file. * - * @return NULL if the operation has failed + * @return false if the operation has failed */ - struct tag *(*tag_dup)(const char *path_fs); + bool (*scan_file)(const char *path_fs, + const struct tag_handler *handler, + void *handler_ctx); /** - * Read the tags of a stream. + * Scan metadata of a file. * - * @return NULL if the operation has failed + * @return false if the operation has failed */ - struct tag *(*stream_tag)(struct input_stream *is); + bool (*scan_stream)(struct input_stream *is, + const struct tag_handler *handler, + void *handler_ctx); /** * @brief Return a "virtual" filename for subtracks in @@ -150,25 +155,28 @@ decoder_plugin_file_decode(const struct decoder_plugin *plugin, /** * Read the tag of a file. */ -static inline struct tag * -decoder_plugin_tag_dup(const struct decoder_plugin *plugin, - const char *path_fs) +static inline bool +decoder_plugin_scan_file(const struct decoder_plugin *plugin, + const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) { - return plugin->tag_dup != NULL - ? plugin->tag_dup(path_fs) - : NULL; + return plugin->scan_file != NULL + ? plugin->scan_file(path_fs, handler, handler_ctx) + : false; } /** * Read the tag of a stream. */ -static inline struct tag * -decoder_plugin_stream_tag(const struct decoder_plugin *plugin, - struct input_stream *is) +static inline bool +decoder_plugin_scan_stream(const struct decoder_plugin *plugin, + struct input_stream *is, + const struct tag_handler *handler, + void *handler_ctx) { - return plugin->stream_tag != NULL - ? plugin->stream_tag(is) - : NULL; + return plugin->scan_stream != NULL + ? plugin->scan_stream(is, handler, handler_ctx) + : false; } /** diff --git a/src/decoder_print.c b/src/decoder_print.c index a1c2da2e5..72c40ac75 100644 --- a/src/decoder_print.c +++ b/src/decoder_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_print.h b/src/decoder_print.h index 520438871..31713d5d8 100644 --- a/src/decoder_print.h +++ b/src/decoder_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_thread.c b/src/decoder_thread.c index bb3ef9348..421efd32a 100644 --- a/src/decoder_thread.c +++ b/src/decoder_thread.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,7 +26,6 @@ #include "decoder_api.h" #include "replay_gain_ape.h" #include "input_stream.h" -#include "player_control.h" #include "pipe.h" #include "song.h" #include "tag.h" @@ -43,16 +42,20 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "decoder_thread" -static enum decoder_command -decoder_lock_get_command(struct decoder_control *dc) +/** + * Marks the current decoder command as "finished" and notifies the + * player thread. + * + * @param dc the #decoder_control object; must be locked + */ +static void +decoder_command_finished_locked(struct decoder_control *dc) { - enum decoder_command command; + assert(dc->command != DECODE_COMMAND_NONE); - decoder_lock(dc); - command = dc->command; - decoder_unlock(dc); + dc->command = DECODE_COMMAND_NONE; - return command; + g_cond_signal(dc->client_cond); } /** @@ -72,7 +75,7 @@ decoder_input_stream_open(struct decoder_control *dc, const char *uri) GError *error = NULL; struct input_stream *is; - is = input_stream_open(uri, &error); + is = input_stream_open(uri, dc->mutex, dc->cond, &error); if (is == NULL) { if (error != NULL) { g_warning("%s", error->message); @@ -85,19 +88,27 @@ decoder_input_stream_open(struct decoder_control *dc, const char *uri) /* wait for the input stream to become ready; its metadata will be available then */ + decoder_lock(dc); + + input_stream_update(is); while (!is->ready && - decoder_lock_get_command(dc) != DECODE_COMMAND_STOP) { - int ret; + dc->command != DECODE_COMMAND_STOP) { + decoder_wait(dc); - ret = input_stream_buffer(is, &error); - if (ret < 0) { - input_stream_close(is); - g_warning("%s", error->message); - g_error_free(error); - return NULL; - } + input_stream_update(is); } + if (!input_stream_check(is, &error)) { + decoder_unlock(dc); + + g_warning("%s", error->message); + g_error_free(error); + + return NULL; + } + + decoder_unlock(dc); + return is; } @@ -118,11 +129,11 @@ decoder_stream_decode(const struct decoder_plugin *plugin, 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, NULL); + decoder_unlock(decoder->dc); + decoder_plugin_stream_decode(plugin, decoder, input_stream); decoder_lock(decoder->dc); @@ -384,9 +395,8 @@ decoder_run_song(struct decoder_control *dc, decoder.chunk = NULL; dc->state = DECODE_STATE_START; - dc->command = DECODE_COMMAND_NONE; - player_signal(); + decoder_command_finished_locked(dc); pcm_convert_init(&decoder.conv_state); @@ -432,6 +442,7 @@ decoder_run(struct decoder_control *dc) if (uri == NULL) { dc->state = DECODE_STATE_ERROR; + decoder_command_finished_locked(dc); return; } @@ -464,16 +475,10 @@ decoder_task(gpointer arg) case DECODE_COMMAND_SEEK: decoder_run(dc); - - dc->command = DECODE_COMMAND_NONE; - - player_signal(); break; case DECODE_COMMAND_STOP: - dc->command = DECODE_COMMAND_NONE; - - player_signal(); + decoder_command_finished_locked(dc); break; case DECODE_COMMAND_NONE: diff --git a/src/decoder_thread.h b/src/decoder_thread.h index 28042d7f8..78f12a54a 100644 --- a/src/decoder_thread.h +++ b/src/decoder_thread.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/despotify_utils.c b/src/despotify_utils.c new file mode 100644 index 000000000..7555d105d --- /dev/null +++ b/src/despotify_utils.c @@ -0,0 +1,121 @@ +#include <glib.h> +#include <despotify.h> + +#include "tag.h" +#include "conf.h" +#include "despotify_utils.h" + +static struct despotify_session *g_session; +static void (*registered_callbacks[8])(struct despotify_session *, + int, void *, void *); +static void *registered_callback_data[8]; + +static void callback(struct despotify_session* ds, int sig, + void* data, G_GNUC_UNUSED void* callback_data) +{ + size_t i; + + for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { + void (*cb)(struct despotify_session *, int, void *, void *) = registered_callbacks[i]; + void *cb_data = registered_callback_data[i]; + + if (cb) + cb(ds, sig, data, cb_data); + } +} + +bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *), + void *cb_data) +{ + size_t i; + + for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { + + if (!registered_callbacks[i]) { + registered_callbacks[i] = cb; + registered_callback_data[i] = cb_data; + + return true; + } + } + + return false; +} + +void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *)) +{ + size_t i; + + for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { + + if (registered_callbacks[i] == cb) { + registered_callbacks[i] = NULL; + } + } +} + + +struct tag *mpd_despotify_tag_from_track(struct ds_track *track) +{ + char tracknum[20]; + char comment[80]; + char date[20]; + struct tag *tag; + + tag = tag_new(); + + if (!track->has_meta_data) + return tag; + + g_snprintf(tracknum, sizeof(tracknum), "%d", track->tracknumber); + g_snprintf(date, sizeof(date), "%d", track->year); + g_snprintf(comment, sizeof(comment), "Bitrate %d Kbps, %sgeo restricted", + track->file_bitrate / 1000, track->geo_restricted ? "" : "not "); + tag_add_item(tag, TAG_TITLE, track->title); + tag_add_item(tag, TAG_ARTIST, track->artist->name); + tag_add_item(tag, TAG_TRACK, tracknum); + tag_add_item(tag, TAG_ALBUM, track->album); + tag_add_item(tag, TAG_DATE, date); + tag_add_item(tag, TAG_COMMENT, comment); + tag->time = track->length / 1000; + + return tag; +} + +struct despotify_session *mpd_despotify_get_session(void) +{ + const char *user; + const char *passwd; + bool high_bitrate; + + if (g_session) + return g_session; + + user = config_get_string(CONF_DESPOTIFY_USER, NULL); + passwd = config_get_string(CONF_DESPOTIFY_PASSWORD, NULL); + high_bitrate = config_get_bool(CONF_DESPOTIFY_HIGH_BITRATE, true); + + if (user == NULL || passwd == NULL) { + g_debug("disabling despotify because account is not configured"); + return NULL; + } + if (!despotify_init()) { + g_debug("Can't initialize despotify\n"); + return false; + } + + g_session = despotify_init_client(callback, NULL, + high_bitrate, true); + if (!g_session) { + g_debug("Can't initialize despotify client\n"); + return false; + } + + if (!despotify_authenticate(g_session, user, passwd)) { + g_debug("Can't authenticate despotify session\n"); + despotify_exit(g_session); + return false; + } + + return g_session; +} diff --git a/src/despotify_utils.h b/src/despotify_utils.h new file mode 100644 index 000000000..7e35edc3c --- /dev/null +++ b/src/despotify_utils.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_DESPOTIFY_H +#define MPD_DESPOTIFY_H + +struct despotify_session; +struct ds_track; + +/** + * Return the current despotify session. + * + * If the session isn't initialized, this function will initialize + * it and connect to Spotify. + * + * @return a pointer to the despotify session, or NULL if it can't + * be initialized (e.g., if the configuration isn't supplied) + */ +struct despotify_session *mpd_despotify_get_session(void); + +/** + * Create a MPD tags structure from a spotify track + * + * @param track the track to convert + * + * @return a pointer to the filled in tags structure + */ +struct tag *mpd_despotify_tag_from_track(struct ds_track *track); + +/** + * Register a despotify callback. + * + * Despotify calls this e.g., when a track ends. + * + * @param cb the callback + * @param cb_data the data to pass to the callback + * + * @return true if the callback could be registered + */ +bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *), + void *cb_data); + +/** + * Unregister a despotify callback. + * + * @param cb the callback to unregister. + */ +void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *)); + +#endif + diff --git a/src/directory.c b/src/directory.c index 1e3a4cf28..930881129 100644 --- a/src/directory.c +++ b/src/directory.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,7 +20,12 @@ #include "config.h" #include "directory.h" #include "song.h" +#include "song_sort.h" +#include "playlist_vector.h" #include "path.h" +#include "util/list_sort.h" +#include "db_visitor.h" +#include "db_lock.h" #include <glib.h> @@ -39,11 +44,13 @@ directory_new(const char *path, struct directory *parent) directory = g_malloc0(sizeof(*directory) - sizeof(directory->path) + pathlen + 1); + INIT_LIST_HEAD(&directory->children); + INIT_LIST_HEAD(&directory->songs); + INIT_LIST_HEAD(&directory->playlists); + directory->parent = parent; memcpy(directory->path, path, pathlen + 1); - playlist_vector_init(&directory->playlists); - return directory; } @@ -52,19 +59,30 @@ 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]); + struct song *song, *ns; + directory_for_each_song_safe(song, ns, directory) + song_free(song); - for (unsigned i = 0; i < directory->children.nr; ++i) - directory_free(directory->children.base[i]); + struct directory *child, *n; + directory_for_each_child_safe(child, n, directory) + directory_free(child); - dirvec_destroy(&directory->children); - songvec_destroy(&directory->songs); g_free(directory); /* this resets last dir returned */ /*directory_get_path(NULL); */ } +void +directory_delete(struct directory *directory) +{ + assert(holding_db_lock()); + assert(directory != NULL); + assert(directory->parent != NULL); + + list_del(&directory->siblings); + directory_free(directory); +} + const char * directory_get_name(const struct directory *directory) { @@ -79,65 +97,137 @@ directory_get_name(const struct directory *directory) : directory->path; } +struct directory * +directory_new_child(struct directory *parent, const char *name_utf8) +{ + assert(holding_db_lock()); + assert(parent != NULL); + assert(name_utf8 != NULL); + assert(*name_utf8 != 0); + + char *allocated; + const char *path_utf8; + if (directory_is_root(parent)) { + allocated = NULL; + path_utf8 = name_utf8; + } else { + allocated = g_strconcat(directory_get_path(parent), + "/", name_utf8, NULL); + path_utf8 = allocated; + } + + struct directory *directory = directory_new(path_utf8, parent); + g_free(allocated); + + list_add_tail(&directory->siblings, &parent->children); + return directory; +} + +struct directory * +directory_get_child(const struct directory *directory, const char *name) +{ + assert(holding_db_lock()); + + struct directory *child; + directory_for_each_child(child, directory) + if (strcmp(directory_get_name(child), name) == 0) + return child; + + return NULL; +} + void directory_prune_empty(struct directory *directory) { - int i; - struct dirvec *dv = &directory->children; - - for (i = dv->nr; --i >= 0; ) { - struct directory *child = dv->base[i]; + assert(holding_db_lock()); + struct directory *child, *n; + directory_for_each_child_safe(child, n, directory) { directory_prune_empty(child); - if (directory_is_empty(child)) { - dirvec_delete(dv, child); - directory_free(child); - } + if (directory_is_empty(child)) + directory_delete(child); } - if (!dv->nr) - dirvec_destroy(dv); } struct directory * directory_lookup_directory(struct directory *directory, const char *uri) { - struct directory *cur = directory; - struct directory *found = NULL; - char *duplicated; - char *locate; - + assert(holding_db_lock()); assert(uri != NULL); if (isRootDirectory(uri)) return directory; - duplicated = g_strdup(uri); - locate = strchr(duplicated, '/'); + char *duplicated = g_strdup(uri), *name = duplicated; + while (1) { - if (locate) - *locate = '\0'; - if (!(found = directory_get_child(cur, duplicated))) + char *slash = strchr(name, '/'); + if (slash == name) { + directory = NULL; break; - assert(cur == found->parent); - cur = found; - if (!locate) + } + + if (slash != NULL) + *slash = '\0'; + + directory = directory_get_child(directory, name); + if (directory == NULL || slash == NULL) break; - *locate = '/'; - locate = strchr(locate + 1, '/'); + + name = slash + 1; } g_free(duplicated); - return found; + return directory; +} + +void +directory_add_song(struct directory *directory, struct song *song) +{ + assert(directory != NULL); + assert(song != NULL); + assert(song->parent == directory); + + list_add_tail(&song->siblings, &directory->songs); +} + +void +directory_remove_song(G_GNUC_UNUSED struct directory *directory, + struct song *song) +{ + assert(directory != NULL); + assert(song != NULL); + assert(song->parent == directory); + + list_del(&song->siblings); +} + +struct song * +directory_get_song(const struct directory *directory, const char *name_utf8) +{ + assert(holding_db_lock()); + assert(directory != NULL); + assert(name_utf8 != NULL); + + struct song *song; + directory_for_each_song(song, directory) { + assert(song->parent == directory); + + if (strcmp(song->uri, name_utf8) == 0) + return song; + } + + return NULL; } struct song * directory_lookup_song(struct directory *directory, const char *uri) { char *duplicated, *base; - struct song *song; + assert(holding_db_lock()); assert(directory != NULL); assert(uri != NULL); @@ -154,7 +244,7 @@ directory_lookup_song(struct directory *directory, const char *uri) } else base = duplicated; - song = songvec_find(&directory->songs, base); + struct song *song = directory_get_song(directory, base); assert(song == NULL || song->parent == directory); g_free(duplicated); @@ -162,41 +252,61 @@ directory_lookup_song(struct directory *directory, const char *uri) } +static int +directory_cmp(G_GNUC_UNUSED void *priv, + struct list_head *_a, struct list_head *_b) +{ + const struct directory *a = (const struct directory *)_a; + const struct directory *b = (const struct directory *)_b; + return g_utf8_collate(a->path, b->path); +} + void directory_sort(struct directory *directory) { - int i; - struct dirvec *dv = &directory->children; + assert(holding_db_lock()); - dirvec_sort(dv); - songvec_sort(&directory->songs); + list_sort(NULL, &directory->children, directory_cmp); + song_list_sort(&directory->songs); - for (i = dv->nr; --i >= 0; ) - directory_sort(dv->base[i]); + struct directory *child; + directory_for_each_child(child, directory) + directory_sort(child); } -int -directory_walk(struct directory *directory, - int (*forEachSong)(struct song *, void *), - int (*forEachDir)(struct directory *, void *), - void *data) +bool +directory_walk(const struct directory *directory, bool recursive, + const struct db_visitor *visitor, void *ctx, + GError **error_r) { - struct dirvec *dv = &directory->children; - int err = 0; - size_t j; - - if (forEachDir && (err = forEachDir(directory, data)) < 0) - return err; + assert(directory != NULL); + assert(visitor != NULL); + assert(error_r == NULL || *error_r == NULL); + + if (visitor->song != NULL) { + struct song *song; + directory_for_each_song(song, directory) + if (!visitor->song(song, ctx, error_r)) + return false; + } - if (forEachSong) { - err = songvec_for_each(&directory->songs, forEachSong, data); - if (err < 0) - return err; + if (visitor->playlist != NULL) { + struct playlist_metadata *i; + directory_for_each_playlist(i, directory) + if (!visitor->playlist(i, directory, ctx, error_r)) + return false; } - for (j = 0; err >= 0 && j < dv->nr; ++j) - err = directory_walk(dv->base[j], forEachSong, - forEachDir, data); + struct directory *child; + directory_for_each_child(child, directory) { + if (visitor->directory != NULL && + !visitor->directory(child, ctx, error_r)) + return false; + + if (recursive && + !directory_walk(child, recursive, visitor, ctx, error_r)) + return false; + } - return err; + return true; } diff --git a/src/directory.h b/src/directory.h index 9c0a9b567..b3cd9c8c9 100644 --- a/src/directory.h +++ b/src/directory.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,23 +21,64 @@ #define MPD_DIRECTORY_H #include "check.h" -#include "dirvec.h" -#include "songvec.h" -#include "playlist_vector.h" +#include "util/list.h" +#include <glib.h> #include <stdbool.h> #include <sys/types.h> -#define DIRECTORY_DIR "directory: " - #define DEVICE_INARCHIVE (dev_t)(-1) #define DEVICE_CONTAINER (dev_t)(-2) +#define directory_for_each_child(pos, directory) \ + list_for_each_entry(pos, &directory->children, siblings) + +#define directory_for_each_child_safe(pos, n, directory) \ + list_for_each_entry_safe(pos, n, &directory->children, siblings) + +#define directory_for_each_song(pos, directory) \ + list_for_each_entry(pos, &directory->songs, siblings) + +#define directory_for_each_song_safe(pos, n, directory) \ + list_for_each_entry_safe(pos, n, &directory->songs, siblings) + +#define directory_for_each_playlist(pos, directory) \ + list_for_each_entry(pos, &directory->playlists, siblings) + +#define directory_for_each_playlist_safe(pos, n, directory) \ + list_for_each_entry_safe(pos, n, &directory->playlists, siblings) + +struct song; +struct db_visitor; + struct directory { - struct dirvec children; - struct songvec songs; + /** + * Pointers to the siblings of this directory within the + * parent directory. It is unused (undefined) in the root + * directory. + * + * This attribute is protected with the global #db_mutex. + * Read access in the update thread does not need protection. + */ + struct list_head siblings; - struct playlist_vector playlists; + /** + * A doubly linked list of child directories. + * + * This attribute is protected with the global #db_mutex. + * Read access in the update thread does not need protection. + */ + struct list_head children; + + /** + * A doubly linked list of songs within this directory. + * + * This attribute is protected with the global #db_mutex. + * Read access in the update thread does not need protection. + */ + struct list_head songs; + + struct list_head playlists; struct directory *parent; time_t mtime; @@ -53,17 +94,45 @@ isRootDirectory(const char *name) return name[0] == 0 || (name[0] == '/' && name[1] == 0); } +/** + * Generic constructor for #directory object. + */ +G_GNUC_MALLOC struct directory * directory_new(const char *dirname, struct directory *parent); +/** + * Create a new root #directory object. + */ +G_GNUC_MALLOC +static inline struct directory * +directory_new_root(void) +{ + return directory_new("", NULL); +} + +/** + * Free this #directory object (and the whole object tree within it), + * assuming it was already removed from the parent. + */ void directory_free(struct directory *directory); +/** + * Remove this #directory object from its parent and free it. This + * must not be called with the root directory. + * + * Caller must lock the #db_mutex. + */ +void +directory_delete(struct directory *directory); + static inline bool directory_is_empty(const struct directory *directory) { - return directory->children.nr == 0 && directory->songs.nr == 0 && - playlist_vector_is_empty(&directory->playlists); + return list_empty(&directory->children) && + list_empty(&directory->songs) && + list_empty(&directory->playlists); } static inline const char * @@ -84,23 +153,47 @@ directory_is_root(const struct directory *directory) /** * Returns the base name of the directory. */ +G_GNUC_PURE const char * directory_get_name(const struct directory *directory); -static inline struct directory * -directory_get_child(const struct directory *directory, const char *name) -{ - return dirvec_find(&directory->children, name); -} +/** + * Caller must lock the #db_mutex. + */ +G_GNUC_PURE +struct directory * +directory_get_child(const struct directory *directory, const char *name); +/** + * Create a new #directory object as a child of the given one. + * + * Caller must lock the #db_mutex. + * + * @param parent the parent directory the new one will be added to + * @param name_utf8 the UTF-8 encoded name of the new sub directory + */ +G_GNUC_MALLOC +struct directory * +directory_new_child(struct directory *parent, const char *name_utf8); + +/** + * Look up a sub directory, and create the object if it does not + * exist. + * + * Caller must lock the #db_mutex. + */ static inline struct directory * -directory_new_child(struct directory *directory, const char *name) +directory_make_child(struct directory *directory, const char *name_utf8) { - struct directory *subdir = directory_new(name, directory); - dirvec_add(&directory->children, subdir); - return subdir; + struct directory *child = directory_get_child(directory, name_utf8); + if (child == NULL) + child = directory_new_child(directory, name_utf8); + return child; } +/** + * Caller must lock the #db_mutex. + */ void directory_prune_empty(struct directory *directory); @@ -115,8 +208,34 @@ struct directory * directory_lookup_directory(struct directory *directory, const char *uri); /** + * Add a song object to this directory. Its "parent" attribute must + * be set already. + */ +void +directory_add_song(struct directory *directory, struct song *song); + +/** + * Remove a song object from this directory (which effectively + * invalidates the song object, because the "parent" attribute becomes + * stale), but does not free it. + */ +void +directory_remove_song(struct directory *directory, struct song *song); + +/** + * Look up a song in this directory by its name. + * + * Caller must lock the #db_mutex. + */ +G_GNUC_PURE +struct song * +directory_get_song(const struct directory *directory, const char *name_utf8); + +/** * Looks up a song by its relative URI. * + * Caller must lock the #db_mutex. + * * @param directory the parent (or grandparent, ...) directory * @param uri the relative URI * @return the song, or NULL if none was found @@ -124,13 +243,20 @@ directory_lookup_directory(struct directory *directory, const char *uri); struct song * directory_lookup_song(struct directory *directory, const char *uri); +/** + * Sort all directory entries recursively. + * + * Caller must lock the #db_mutex. + */ void directory_sort(struct directory *directory); -int -directory_walk(struct directory *directory, - int (*forEachSong)(struct song *, void *), - int (*forEachDir)(struct directory *, void *), - void *data); +/** + * Caller must lock #db_mutex. + */ +bool +directory_walk(const struct directory *directory, bool recursive, + const struct db_visitor *visitor, void *ctx, + GError **error_r); #endif diff --git a/src/directory_print.c b/src/directory_print.c deleted file mode 100644 index 74ff26cb3..000000000 --- a/src/directory_print.c +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (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 "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) -{ - size_t i; - - for (i = 0; i < dv->nr; ++i) - client_printf(client, DIRECTORY_DIR "%s\n", - 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_save.c b/src/directory_save.c index 55896c289..de1df050a 100644 --- a/src/directory_save.c +++ b/src/directory_save.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,6 +28,7 @@ #include <assert.h> #include <string.h> +#define DIRECTORY_DIR "directory: " #define DIRECTORY_MTIME "mtime: " #define DIRECTORY_BEGIN "begin: " #define DIRECTORY_END "end: " @@ -42,11 +43,8 @@ directory_quark(void) } void -directory_save(FILE *fp, struct directory *directory) +directory_save(FILE *fp, const struct directory *directory) { - struct dirvec *children = &directory->children; - size_t i; - if (!directory_is_root(directory)) { fprintf(fp, DIRECTORY_MTIME "%lu\n", (unsigned long)directory->mtime); @@ -55,8 +53,8 @@ directory_save(FILE *fp, struct directory *directory) directory_get_path(directory)); } - for (i = 0; i < children->nr; ++i) { - struct directory *cur = children->base[i]; + struct directory *cur; + directory_for_each_child(cur, directory) { char *base = g_path_get_basename(cur->path); fprintf(fp, DIRECTORY_DIR "%s\n", base); @@ -68,7 +66,9 @@ directory_save(FILE *fp, struct directory *directory) return; } - songvec_save(fp, &directory->songs); + struct song *song; + directory_for_each_song(song, directory) + song_save(fp, song); playlist_vector_save(fp, &directory->playlists); @@ -81,7 +81,6 @@ static struct directory * directory_load_subdir(FILE *fp, struct directory *parent, const char *name, GString *buffer, GError **error_r) { - struct directory *directory; const char *line; bool success; @@ -91,20 +90,13 @@ directory_load_subdir(FILE *fp, struct directory *parent, const char *name, return NULL; } - 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); - } + struct directory *directory = directory_new_child(parent, name); line = read_text_line(fp, buffer); if (line == NULL) { g_set_error(error_r, directory_quark(), 0, "Unexpected end of file"); - directory_free(directory); + directory_delete(directory); return NULL; } @@ -117,7 +109,7 @@ directory_load_subdir(FILE *fp, struct directory *parent, const char *name, if (line == NULL) { g_set_error(error_r, directory_quark(), 0, "Unexpected end of file"); - directory_free(directory); + directory_delete(directory); return NULL; } } @@ -125,13 +117,13 @@ directory_load_subdir(FILE *fp, struct directory *parent, const char *name, if (!g_str_has_prefix(line, DIRECTORY_BEGIN)) { g_set_error(error_r, directory_quark(), 0, "Malformed line: %s", line); - directory_free(directory); + directory_delete(directory); return NULL; } success = directory_load(fp, directory, buffer, error_r); if (!success) { - directory_free(directory); + directory_delete(directory); return NULL; } @@ -153,13 +145,11 @@ directory_load(FILE *fp, struct directory *directory, buffer, error); if (subdir == NULL) return false; - - 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) { + if (directory_get_song(directory, name) != NULL) { g_set_error(error, directory_quark(), 0, "Duplicate song '%s'", name); return NULL; @@ -170,7 +160,7 @@ directory_load(FILE *fp, struct directory *directory, if (song == NULL) return false; - songvec_add(&directory->songs, song); + directory_add_song(directory, song); } else if (g_str_has_prefix(line, PLAYLIST_META_BEGIN)) { /* duplicate the name, because playlist_metadata_load() will overwrite the diff --git a/src/directory_save.h b/src/directory_save.h index 9193b6c41..2d0056830 100644 --- a/src/directory_save.h +++ b/src/directory_save.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,7 +28,7 @@ struct directory; void -directory_save(FILE *fp, struct directory *directory); +directory_save(FILE *fp, const struct directory *directory); bool directory_load(FILE *fp, struct directory *directory, diff --git a/src/dirvec.c b/src/dirvec.c deleted file mode 100644 index 89b32a4f4..000000000 --- a/src/dirvec.c +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (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 "dirvec.h" -#include "directory.h" - -#include <glib.h> - -#include <assert.h> -#include <string.h> -#include <stdlib.h> - -static GMutex *nr_lock = NULL; - -static size_t dv_size(const struct dirvec *dv) -{ - return dv->nr * sizeof(struct directory *); -} - -/* Only used for sorting/searching a dirvec, not general purpose compares */ -static int dirvec_cmp(const void *d1, const void *d2) -{ - const struct directory *a = ((const struct directory * const *)d1)[0]; - const struct directory *b = ((const struct directory * const *)d2)[0]; - return g_utf8_collate(a->path, b->path); -} - -void dirvec_init(void) -{ - g_assert(nr_lock == NULL); - nr_lock = g_mutex_new(); -} - -void dirvec_deinit(void) -{ - g_assert(nr_lock != NULL); - g_mutex_free(nr_lock); - nr_lock = NULL; -} - -void dirvec_sort(struct dirvec *dv) -{ - g_mutex_lock(nr_lock); - qsort(dv->base, dv->nr, sizeof(struct directory *), dirvec_cmp); - g_mutex_unlock(nr_lock); -} - -struct directory *dirvec_find(const struct dirvec *dv, const char *path) -{ - char *base; - int i; - struct directory *ret = NULL; - - base = g_path_get_basename(path); - - g_mutex_lock(nr_lock); - for (i = dv->nr; --i >= 0; ) - if (!strcmp(directory_get_name(dv->base[i]), base)) { - ret = dv->base[i]; - break; - } - g_mutex_unlock(nr_lock); - - g_free(base); - return ret; -} - -int dirvec_delete(struct dirvec *dv, struct directory *del) -{ - size_t i; - - g_mutex_lock(nr_lock); - for (i = 0; i < dv->nr; ++i) { - if (dv->base[i] != del) - continue; - /* we _don't_ call directory_free() here */ - if (!--dv->nr) { - g_mutex_unlock(nr_lock); - g_free(dv->base); - dv->base = NULL; - return i; - } else { - memmove(&dv->base[i], &dv->base[i + 1], - (dv->nr - i) * sizeof(struct directory *)); - dv->base = g_realloc(dv->base, dv_size(dv)); - } - break; - } - g_mutex_unlock(nr_lock); - - return i; -} - -void dirvec_add(struct dirvec *dv, struct directory *add) -{ - g_mutex_lock(nr_lock); - ++dv->nr; - dv->base = g_realloc(dv->base, dv_size(dv)); - dv->base[dv->nr - 1] = add; - g_mutex_unlock(nr_lock); -} - -void dirvec_destroy(struct dirvec *dv) -{ - g_mutex_lock(nr_lock); - dv->nr = 0; - g_mutex_unlock(nr_lock); - if (dv->base) { - g_free(dv->base); - dv->base = NULL; - } -} - -int dirvec_for_each(const struct dirvec *dv, - int (*fn)(struct directory *, void *), void *arg) -{ - size_t i; - size_t prev_nr; - - g_mutex_lock(nr_lock); - for (i = 0; i < dv->nr; ) { - struct directory *dir = dv->base[i]; - - assert(dir); - prev_nr = dv->nr; - g_mutex_unlock(nr_lock); - if (fn(dir, arg) < 0) - return -1; - g_mutex_lock(nr_lock); /* dv->nr may change in fn() */ - if (prev_nr == dv->nr) - ++i; - } - g_mutex_unlock(nr_lock); - - return 0; -} diff --git a/src/dsd2pcm/dsd2pcm.c b/src/dsd2pcm/dsd2pcm.c new file mode 100644 index 000000000..4c7640853 --- /dev/null +++ b/src/dsd2pcm/dsd2pcm.c @@ -0,0 +1,184 @@ +#include "util/bit_reverse.h" + +#include <stdlib.h> +#include <string.h> + +#include "dsd2pcm.h" + +#define HTAPS 48 /* number of FIR constants */ +#define FIFOSIZE 16 /* must be a power of two */ +#define FIFOMASK (FIFOSIZE-1) /* bit mask for FIFO offsets */ +#define CTABLES ((HTAPS+7)/8) /* number of "8 MACs" lookup tables */ + +#if FIFOSIZE*8 < HTAPS*2 +#error "FIFOSIZE too small" +#endif + +/* + * Properties of this 96-tap lowpass filter when applied on a signal + * with sampling rate of 44100*64 Hz: + * + * () has a delay of 17 microseconds. + * + * () flat response up to 48 kHz + * + * () if you downsample afterwards by a factor of 8, the + * spectrum below 70 kHz is practically alias-free. + * + * () stopband rejection is about 160 dB + * + * The coefficient tables ("ctables") take only 6 Kibi Bytes and + * should fit into a modern processor's fast cache. + */ + +/* + * The 2nd half (48 coeffs) of a 96-tap symmetric lowpass filter + */ +static const double htaps[HTAPS] = { + 0.09950731974056658, + 0.09562845727714668, + 0.08819647126516944, + 0.07782552527068175, + 0.06534876523171299, + 0.05172629311427257, + 0.0379429484910187, + 0.02490921351762261, + 0.0133774746265897, + 0.003883043418804416, + -0.003284703416210726, + -0.008080250212687497, + -0.01067241812471033, + -0.01139427235000863, + -0.0106813877974587, + -0.009007905078766049, + -0.006828859761015335, + -0.004535184322001496, + -0.002425035959059578, + -0.0006922187080790708, + 0.0005700762133516592, + 0.001353838005269448, + 0.001713709169690937, + 0.001742046839472948, + 0.001545601648013235, + 0.001226696225277855, + 0.0008704322683580222, + 0.0005381636200535649, + 0.000266446345425276, + 7.002968738383528e-05, + -5.279407053811266e-05, + -0.0001140625650874684, + -0.0001304796361231895, + -0.0001189970287491285, + -9.396247155265073e-05, + -6.577634378272832e-05, + -4.07492895872535e-05, + -2.17407957554587e-05, + -9.163058931391722e-06, + -2.017460145032201e-06, + 1.249721855219005e-06, + 2.166655190537392e-06, + 1.930520892991082e-06, + 1.319400334374195e-06, + 7.410039764949091e-07, + 3.423230509967409e-07, + 1.244182214744588e-07, + 3.130441005359396e-08 +}; + +static float ctables[CTABLES][256]; +static int precalculated = 0; + +static void precalc(void) +{ + int t, e, m, k; + double acc; + if (precalculated) return; + for (t=0; t<CTABLES; ++t) { + k = HTAPS - t*8; + if (k>8) k=8; + for (e=0; e<256; ++e) { + acc = 0.0; + for (m=0; m<k; ++m) { + acc += (((e >> (7-m)) & 1)*2-1) * htaps[t*8+m]; + } + ctables[CTABLES-1-t][e] = (float)acc; + } + } + precalculated = 1; +} + +struct dsd2pcm_ctx_s +{ + unsigned char fifo[FIFOSIZE]; + unsigned fifopos; +}; + +extern dsd2pcm_ctx* dsd2pcm_init(void) +{ + dsd2pcm_ctx* ptr; + if (!precalculated) precalc(); + ptr = (dsd2pcm_ctx*) malloc(sizeof(dsd2pcm_ctx)); + if (ptr) dsd2pcm_reset(ptr); + return ptr; +} + +extern void dsd2pcm_destroy(dsd2pcm_ctx* ptr) +{ + free(ptr); +} + +extern dsd2pcm_ctx* dsd2pcm_clone(dsd2pcm_ctx* ptr) +{ + dsd2pcm_ctx* p2; + p2 = (dsd2pcm_ctx*) malloc(sizeof(dsd2pcm_ctx)); + if (p2) { + memcpy(p2,ptr,sizeof(dsd2pcm_ctx)); + } + return p2; +} + +extern void dsd2pcm_reset(dsd2pcm_ctx* ptr) +{ + int i; + for (i=0; i<FIFOSIZE; ++i) + ptr->fifo[i] = 0x69; /* my favorite silence pattern */ + ptr->fifopos = 0; + /* 0x69 = 01101001 + * This pattern "on repeat" makes a low energy 352.8 kHz tone + * and a high energy 1.0584 MHz tone which should be filtered + * out completely by any playback system --> silence + */ +} + +extern void dsd2pcm_translate( + dsd2pcm_ctx* ptr, + size_t samples, + const unsigned char *src, ptrdiff_t src_stride, + int lsbf, + float *dst, ptrdiff_t dst_stride) +{ + unsigned ffp; + unsigned i; + unsigned bite1, bite2; + unsigned char* p; + double acc; + ffp = ptr->fifopos; + lsbf = lsbf ? 1 : 0; + while (samples-- > 0) { + bite1 = *src & 0xFFu; + if (lsbf) bite1 = bit_reverse(bite1); + ptr->fifo[ffp] = bite1; src += src_stride; + p = ptr->fifo + ((ffp-CTABLES) & FIFOMASK); + *p = bit_reverse(*p); + acc = 0; + for (i=0; i<CTABLES; ++i) { + bite1 = ptr->fifo[(ffp -i) & FIFOMASK] & 0xFF; + bite2 = ptr->fifo[(ffp-(CTABLES*2-1)+i) & FIFOMASK] & 0xFF; + acc += ctables[i][bite1] + ctables[i][bite2]; + } + *dst = (float)acc; dst += dst_stride; + ffp = (ffp + 1) & FIFOMASK; + } + ptr->fifopos = ffp; +} + diff --git a/src/dsd2pcm/dsd2pcm.h b/src/dsd2pcm/dsd2pcm.h new file mode 100644 index 000000000..80e8ce0cc --- /dev/null +++ b/src/dsd2pcm/dsd2pcm.h @@ -0,0 +1,64 @@ +#ifndef DSD2PCM_H_INCLUDED +#define DSD2PCM_H_INCLUDED + +#include <stddef.h> +#include <string.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct dsd2pcm_ctx_s; + +typedef struct dsd2pcm_ctx_s dsd2pcm_ctx; + +/** + * initializes a "dsd2pcm engine" for one channel + * (precomputes tables and allocates memory) + * + * This is the only function that is not thread-safe in terms of the + * POSIX thread-safety definition because it modifies global state + * (lookup tables are computed during the first call) + */ +extern dsd2pcm_ctx* dsd2pcm_init(void); + +/** + * deinitializes a "dsd2pcm engine" + * (releases memory, don't forget!) + */ +extern void dsd2pcm_destroy(dsd2pcm_ctx *ctx); + +/** + * clones the context and returns a pointer to the + * newly allocated copy + */ +extern dsd2pcm_ctx* dsd2pcm_clone(dsd2pcm_ctx *ctx); + +/** + * resets the internal state for a fresh new stream + */ +extern void dsd2pcm_reset(dsd2pcm_ctx *ctx); + +/** + * "translates" a stream of octets to a stream of floats + * (8:1 decimation) + * @param ctx -- pointer to abstract context (buffers) + * @param samples -- number of octets/samples to "translate" + * @param src -- pointer to first octet (input) + * @param src_stride -- src pointer increment + * @param lsbitfirst -- bitorder, 0=msb first, 1=lsbfirst + * @param dst -- pointer to first float (output) + * @param dst_stride -- dst pointer increment + */ +extern void dsd2pcm_translate(dsd2pcm_ctx *ctx, + size_t samples, + const unsigned char *src, ptrdiff_t src_stride, + int lsbitfirst, + float *dst, ptrdiff_t dst_stride); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* include guard DSD2PCM_H_INCLUDED */ + diff --git a/src/dsd2pcm/dsd2pcm.hpp b/src/dsd2pcm/dsd2pcm.hpp new file mode 100644 index 000000000..b1b2ae1c5 --- /dev/null +++ b/src/dsd2pcm/dsd2pcm.hpp @@ -0,0 +1,41 @@ +#ifndef DSD2PCM_HXX_INCLUDED +#define DSD2PCM_HXX_INCLUDED + +#include <algorithm> +#include <stdexcept> +#include "dsd2pcm.h" + +/** + * C++ PImpl Wrapper for the dsd2pcm C library + */ + +class dxd +{ + dsd2pcm_ctx *handle; +public: + dxd() : handle(dsd2pcm_init()) + { if (!handle) throw std::runtime_error("wtf?!"); } + + dxd(dxd const& x) : handle(dsd2pcm_clone(x.handle)) + { if (!handle) throw std::runtime_error("wtf?!"); } + + ~dxd() { dsd2pcm_destroy(handle); } + + friend void swap(dxd & a, dxd & b) + { std::swap(a.handle,b.handle); } + + dxd& operator=(dxd x) + { swap(*this,x); return *this; } + + void translate(size_t samples, + const unsigned char *src, ptrdiff_t src_stride, + bool lsbitfirst, + float *dst, ptrdiff_t dst_stride) + { + dsd2pcm_translate(handle,samples,src,src_stride, + lsbitfirst,dst,dst_stride); + } +}; + +#endif // DSD2PCM_HXX_INCLUDED + diff --git a/src/dsd2pcm/info.txt b/src/dsd2pcm/info.txt new file mode 100644 index 000000000..15ff29245 --- /dev/null +++ b/src/dsd2pcm/info.txt @@ -0,0 +1,38 @@ +You downloaded the source code for "dsd2pcm" which is a simple little +"filter" program, that takes a DSD data stream on stdin and converts +it to a PCM stream (352.8 kHz, either 16 or 24 bits) and writes it to +stdout. The code is split into three modules: + + (1) dsd2pcm + + This is where the 8:1 decimation magic happens. It's an + implementation of a symmetric 96-taps FIR lowpass filter + optimized for DSD inputs. If you feed this converter with + DSD64 you get a PCM stream at 352.8 kHz and floating point + samples. This module is independent and can be reused. + + (2) noiseshape + + A module for applying generic noise shaping filters. It's + used for the 16-bit output mode in "main" to preserve the + dynamic range. This module is independent and can be reused. + + (3) main.cpp (file contains the main function and handles I/O) + +The first two modules are pure C for maximum portability. In addition, +there are C++ wrapper headers for convenient use of these modules in +C++. The main application is a C++ application and makes use of the +C++ headers to access the functionality of the first two modules. + + +Under Linux this program is easily compiled by typing + + g++ *.c *.cpp -O3 -o dsd2pcm + +provided you have GCC installed. That's why I didn't bother writing +any makefiles. :-p + + +Cheers! +SG + diff --git a/src/dsd2pcm/main.cpp b/src/dsd2pcm/main.cpp new file mode 100644 index 000000000..0b58888a8 --- /dev/null +++ b/src/dsd2pcm/main.cpp @@ -0,0 +1,120 @@ +#include <iostream> +#include <vector> +#include <cstring> + +#include "dsd2pcm.hpp" +#include "noiseshape.hpp" + +namespace { + +const float my_ns_coeffs[] = { +// b1 b2 a1 a2 + -1.62666423, 0.79410094, 0.61367127, 0.23311013, // section 1 + -1.44870017, 0.54196219, 0.03373857, 0.70316556 // section 2 +}; + +const int my_ns_soscount = sizeof(my_ns_coeffs)/(sizeof(my_ns_coeffs[0])*4); + +inline long myround(float x) +{ + return static_cast<long>(x + (x>=0 ? 0.5f : -0.5f)); +} + +template<typename T> +struct id { typedef T type; }; + +template<typename T> +inline T clip( + typename id<T>::type min, + T v, + typename id<T>::type max) +{ + if (v<min) return min; + if (v>max) return max; + return v; +} + +inline void write_intel16(unsigned char * ptr, unsigned word) +{ + ptr[0] = word & 0xFF; + ptr[1] = (word >> 8) & 0xFF; +} + +inline void write_intel24(unsigned char * ptr, unsigned long word) +{ + ptr[0] = word & 0xFF; + ptr[1] = (word >> 8) & 0xFF; + ptr[2] = (word >> 16) & 0xFF; +} + +} // anonymous namespace + +using std::vector; +using std::cin; +using std::cout; +using std::cerr; + +int main(int argc, char *argv[]) +{ + const int block = 16384; + int channels = -1; + int lsbitfirst = -1; + int bits = -1; + if (argc==4) { + if ('1'<=argv[1][0] && argv[1][0]<='9') channels = 1 + (argv[1][0]-'1'); + if (argv[2][0]=='m' || argv[2][0]=='M') lsbitfirst=0; + if (argv[2][0]=='l' || argv[2][0]=='L') lsbitfirst=1; + if (!strcmp(argv[3],"16")) bits = 16; + if (!strcmp(argv[3],"24")) bits = 24; + } + if (channels<1 || lsbitfirst<0 || bits<0) { + cerr << "\n" + "DSD2PCM filter (raw DSD64 --> 352 kHz raw PCM)\n" + "(c) 2009 Sebastian Gesemann\n\n" + "(filter as in \"reads data from stdin and writes to stdout\")\n\n" + "Syntax: dsd2pcm <channels> <bitorder> <bitdepth>\n" + "channels = 1,2,3,...,9 (number of channels in DSD stream)\n" + "bitorder = L (lsb first), M (msb first) (DSD stream option)\n" + "bitdepth = 16 or 24 (intel byte order, output option)\n\n" + "Note: At 16 bits/sample a noise shaper kicks in that can preserve\n" + "a dynamic range of 135 dB below 30 kHz.\n\n"; + return 1; + } + int bytespersample = bits/8; + vector<dxd> dxds (channels); + vector<noise_shaper> ns; + if (bits==16) { + ns.resize(channels, noise_shaper(my_ns_soscount, my_ns_coeffs) ); + } + vector<unsigned char> dsd_data (block * channels); + vector<float> float_data (block); + vector<unsigned char> pcm_data (block * channels * bytespersample); + char * const dsd_in = reinterpret_cast<char*>(&dsd_data[0]); + char * const pcm_out = reinterpret_cast<char*>(&pcm_data[0]); + while (cin.read(dsd_in,block * channels)) { + for (int c=0; c<channels; ++c) { + dxds[c].translate(block,&dsd_data[0]+c,channels, + lsbitfirst, + &float_data[0],1); + unsigned char * out = &pcm_data[0] + c*bytespersample; + if (bits==16) { + for (int s=0; s<block; ++s) { + float r = float_data[s]*32768 + ns[c].get(); + long smp = clip(-32768,myround(r),32767); + ns[c].update( clip(-1,smp-r,1) ); + write_intel16(out,smp); + out += channels*bytespersample; + } + } else { + for (int s=0; s<block; ++s) { + float r = float_data[s]*8388608; + long smp = clip(-8388608,myround(r),8388607); + write_intel24(out,smp); + out += channels*bytespersample; + } + } + } + cout.write(pcm_out,block*channels*bytespersample); + } +} + diff --git a/src/dsd2pcm/noiseshape.c b/src/dsd2pcm/noiseshape.c new file mode 100644 index 000000000..ecd2f251d --- /dev/null +++ b/src/dsd2pcm/noiseshape.c @@ -0,0 +1,83 @@ +#include <stdlib.h> +#include <string.h> + +#include "noiseshape.h" + +extern int noise_shape_init( + noise_shape_ctx *ctx, + int sos_count, + const float *coeffs) +{ + int i; + ctx->sos_count = sos_count; + ctx->bbaa = coeffs; + ctx->t1 = (float*) malloc(sizeof(float)*sos_count); + if (!ctx->t1) goto escape1; + ctx->t2 = (float*) malloc(sizeof(float)*sos_count); + if (!ctx->t2) goto escape2; + for (i=0; i<sos_count; ++i) { + ctx->t1[i] = 0.f; + ctx->t2[i] = 0.f; + } + return 0; +escape2: + free(ctx->t1); +escape1: + return -1; +} + +extern void noise_shape_destroy( + noise_shape_ctx *ctx) +{ + free(ctx->t1); + free(ctx->t2); +} + +extern int noise_shape_clone( + const noise_shape_ctx *from, + noise_shape_ctx *to) +{ + to->sos_count = from->sos_count; + to->bbaa = from->bbaa; + to->t1 = (float*) malloc(sizeof(float)*to->sos_count); + if (!to->t1) goto error1; + to->t2 = (float*) malloc(sizeof(float)*to->sos_count); + if (!to->t2) goto error2; + memcpy(to->t1,from->t1,sizeof(float)*to->sos_count); + memcpy(to->t2,from->t2,sizeof(float)*to->sos_count); + return 0; +error2: + free(to->t1); +error1: + return -1; +} + +extern float noise_shape_get(noise_shape_ctx *ctx) +{ + int i; + float acc; + const float *c; + acc = 0.0; + c = ctx->bbaa; + for (i=0; i<ctx->sos_count; ++i) { + float t1i = ctx->t1[i]; + float t2i = ctx->t2[i]; + ctx->t2[i] = acc -= t1i * c[2] + t2i * c[3]; + acc += t1i * c[0] + t2i * c[1]; + c += 4; + } + return acc; +} + +extern void noise_shape_update(noise_shape_ctx *ctx, float qerror) +{ + float *p; + int i; + for (i=0; i<ctx->sos_count; ++i) { + ctx->t2[i] += qerror; + } + p = ctx->t1; + ctx->t1 = ctx->t2; + ctx->t2 = p; +} + diff --git a/src/dsd2pcm/noiseshape.h b/src/dsd2pcm/noiseshape.h new file mode 100644 index 000000000..6075f0d88 --- /dev/null +++ b/src/dsd2pcm/noiseshape.h @@ -0,0 +1,57 @@ +#ifndef NOISE_SHAPE_H_INCLUDED +#define NOISE_SHAPE_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct noise_shape_ctx_s { + int sos_count; /* number of second order sections */ + const float *bbaa; /* filter coefficients, owned by user */ + float *t1, *t2; /* filter state, owned by ns library */ +} noise_shape_ctx; + +/** + * initializes a noise_shaper context + * returns an error code or 0 + */ +extern int noise_shape_init( + noise_shape_ctx *ctx, + int sos_count, + const float *coeffs); + +/** + * destroys a noise_shaper context + */ +extern void noise_shape_destroy( + noise_shape_ctx *ctx); + +/** + * initializes a noise_shaper context so that its state + * is a copy of a given context + * returns an error code or 0 + */ +extern int noise_shape_clone( + const noise_shape_ctx *from, noise_shape_ctx *to); + +/** + * computes the next "noise shaping sample". Note: This call + * alters the internal state. xxx_get and xxx_update must be + * called in an alternating manner. + */ +extern float noise_shape_get( + noise_shape_ctx *ctx); + +/** + * updates the noise shaper's state with the + * last quantization error + */ +extern void noise_shape_update( + noise_shape_ctx *ctx, float qerror); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* NOISE_SHAPE_H_INCLUDED */ + diff --git a/src/dsd2pcm/noiseshape.hpp b/src/dsd2pcm/noiseshape.hpp new file mode 100644 index 000000000..726272f91 --- /dev/null +++ b/src/dsd2pcm/noiseshape.hpp @@ -0,0 +1,46 @@ +#ifndef NOISE_SHAPE_HXX_INCLUDED +#define NOISE_SHAPE_HXX_INCLUDED + +#include <stdexcept> +#include "noiseshape.h" + +/** + * C++ wrapper for the noiseshape C library + */ + +class noise_shaper +{ + noise_shape_ctx ctx; +public: + noise_shaper(int sos_count, const float *bbaa) + { + if (noise_shape_init(&ctx,sos_count,bbaa)) + throw std::runtime_error("noise shaper initialization failed"); + } + + noise_shaper(noise_shaper const& x) + { + if (noise_shape_clone(&x.ctx,&ctx)) + throw std::runtime_error("noise shaper initialization failed"); + } + + ~noise_shaper() + { noise_shape_destroy(&ctx); } + + noise_shaper& operator=(noise_shaper const& x) + { + if (this != &x) { + noise_shape_destroy(&ctx); + if (noise_shape_clone(&x.ctx,&ctx)) + throw std::runtime_error("noise shaper initialization failed"); + } + return *this; + } + + float get() { return noise_shape_get(&ctx); } + + void update(float error) { noise_shape_update(&ctx,error); } +}; + +#endif /* NOISE_SHAPE_HXX_INCLUDED */ + diff --git a/src/dummy.cxx b/src/dummy.cxx new file mode 100644 index 000000000..b21555d06 --- /dev/null +++ b/src/dummy.cxx @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Just a dummy C++ file that is linked to work around an automake + * bug: automake uses CXXLD when at least one source is C++, but when + * you link a static library with a C++ source, it uses CCLD. This + * causes linker problems (undefined reference to 'operator + * delete(void*)'), because CCLD does not link with libstdc++. + * + * By linking with this empty C++ source, automake decides to use + * CXXLD. + * + */ diff --git a/src/encoder/flac_encoder.c b/src/encoder/flac_encoder.c index e2c455e3a..e32588e29 100644 --- a/src/encoder/flac_encoder.c +++ b/src/encoder/flac_encoder.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -207,7 +207,7 @@ flac_encoder_open(struct encoder *_encoder, struct audio_format *audio_format, encoder->output_buffer = growing_fifo_new(); - /* this immediatelly outputs data throught callback */ + /* this immediately outputs data through callback */ #if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 { diff --git a/src/encoder/lame_encoder.c b/src/encoder/lame_encoder.c index df843471b..3bb99ea28 100644 --- a/src/encoder/lame_encoder.c +++ b/src/encoder/lame_encoder.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/null_encoder.c b/src/encoder/null_encoder.c index 4e45f4345..48cdf139b 100644 --- a/src/encoder/null_encoder.c +++ b/src/encoder/null_encoder.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/twolame_encoder.c b/src/encoder/twolame_encoder.c index 073c3128f..934b2ab24 100644 --- a/src/encoder/twolame_encoder.c +++ b/src/encoder/twolame_encoder.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/vorbis_encoder.c b/src/encoder/vorbis_encoder.c index 9f09b2ac7..fcf2b5135 100644 --- a/src/encoder/vorbis_encoder.c +++ b/src/encoder/vorbis_encoder.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/wave_encoder.c b/src/encoder/wave_encoder.c index 3a64c5197..9eeb4d513 100644 --- a/src/encoder/wave_encoder.c +++ b/src/encoder/wave_encoder.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -122,11 +122,6 @@ wave_encoder_open(struct encoder *_encoder, encoder->bits = 16; break; - case SAMPLE_FORMAT_S24: - audio_format->format = SAMPLE_FORMAT_S24_P32; - encoder->bits = 24; - break; - case SAMPLE_FORMAT_S24_P32: encoder->bits = 24; break; diff --git a/src/encoder_api.h b/src/encoder_api.h index 5df486ebd..46c8d10c8 100644 --- a/src/encoder_api.h +++ b/src/encoder_api.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 f49ad48f7..d98e617b4 100644 --- a/src/encoder_list.c +++ b/src/encoder_list.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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.h b/src/encoder_list.h index 95f853004..6316d5d2f 100644 --- a/src/encoder_list.h +++ b/src/encoder_list.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_plugin.h b/src/encoder_plugin.h index 70eee51a2..33a379115 100644 --- a/src/encoder_plugin.h +++ b/src/encoder_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -91,7 +91,7 @@ encoder_struct_init(struct encoder *encoder, * * @param plugin the encoder plugin * @param param optional configuration - * @param error location to store the error occuring, or NULL to ignore errors. + * @param error location to store the error occurring, or NULL to ignore errors. * @return an encoder object on success, NULL on failure */ static inline struct encoder * @@ -122,7 +122,7 @@ encoder_finish(struct encoder *encoder) * @param encoder the encoder * @param audio_format the encoder's input audio format; the plugin * may modify the struct to adapt it to its abilities - * @param error location to store the error occuring, or NULL to ignore errors. + * @param error location to store the error occurring, or NULL to ignore errors. * @return true on success */ static inline bool @@ -192,7 +192,7 @@ encoder_end(struct encoder *encoder, GError **error) * buffered available by encoder_read(). * * @param encoder the encoder - * @param error location to store the error occuring, or NULL to ignore errors. + * @param error location to store the error occurring, or NULL to ignore errors. * @return true on success */ static inline bool @@ -246,7 +246,7 @@ encoder_pre_tag(struct encoder *encoder, GError **error) * * @param encoder the encoder * @param tag the tag object - * @param error location to store the error occuring, or NULL to ignore errors. + * @param error location to store the error occurring, or NULL to ignore errors. * @return true on success */ static inline bool @@ -273,7 +273,7 @@ encoder_tag(struct encoder *encoder, const struct tag *tag, GError **error) * @param encoder the encoder * @param data the buffer containing PCM samples * @param length the length of the buffer in bytes - * @param error location to store the error occuring, or NULL to ignore errors. + * @param error location to store the error occurring, or NULL to ignore errors. * @return true on success */ static inline bool diff --git a/src/event_pipe.c b/src/event_pipe.c index 484b7a625..d5c3b9564 100644 --- a/src/event_pipe.c +++ b/src/event_pipe.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/event_pipe.h b/src/event_pipe.h index 923544bf4..3734bb86c 100644 --- a/src/event_pipe.h +++ b/src/event_pipe.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/exclude.c b/src/exclude.c index dd46b58c7..438039d30 100644 --- a/src/exclude.c +++ b/src/exclude.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/exclude.h b/src/exclude.h index fd7cf8795..5b1229e29 100644 --- a/src/exclude.h +++ b/src/exclude.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/fd_util.c b/src/fd_util.c index 4636c19e4..882b4c7d5 100644 --- a/src/fd_util.c +++ b/src/fd_util.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * Redistribution and use in source and binary forms, with or without @@ -206,6 +206,29 @@ socketpair_cloexec(int domain, int type, int protocol, int sv[2]) return ret; } +int +socketpair_cloexec_nonblock(int domain, int type, int protocol, int sv[2]) +{ + int ret; + +#if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) + ret = socketpair(domain, type | SOCK_CLOEXEC | SOCK_NONBLOCK, 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_nonblock(sv[0]); + fd_set_cloexec(sv[1], true); + fd_set_nonblock(sv[1]); + } + + return ret; +} + #endif int diff --git a/src/fd_util.h b/src/fd_util.h index d74bb30d8..dd4df7a13 100644 --- a/src/fd_util.h +++ b/src/fd_util.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * Redistribution and use in source and binary forms, with or without @@ -91,6 +91,13 @@ pipe_cloexec_nonblock(int fd[2]); int socketpair_cloexec(int domain, int type, int protocol, int sv[2]); +/** + * Wrapper for socketpair(), which sets the flags CLOEXEC and NONBLOCK + * (atomically if supported by the OS). + */ +int +socketpair_cloexec_nonblock(int domain, int type, int protocol, int sv[2]); + #endif /** diff --git a/src/filter/autoconvert_filter_plugin.c b/src/filter/autoconvert_filter_plugin.c index 9e197a5f6..3826a0fb3 100644 --- a/src/filter/autoconvert_filter_plugin.c +++ b/src/filter/autoconvert_filter_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/filter/autoconvert_filter_plugin.h b/src/filter/autoconvert_filter_plugin.h index 730db197d..def08ab7e 100644 --- a/src/filter/autoconvert_filter_plugin.h +++ b/src/filter/autoconvert_filter_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/filter/chain_filter_plugin.c b/src/filter/chain_filter_plugin.c index 06d4d0e6b..2c785a36f 100644 --- a/src/filter/chain_filter_plugin.c +++ b/src/filter/chain_filter_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/filter/chain_filter_plugin.h b/src/filter/chain_filter_plugin.h index 42c6a9b78..1dba46667 100644 --- a/src/filter/chain_filter_plugin.h +++ b/src/filter/chain_filter_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/filter/convert_filter_plugin.c b/src/filter/convert_filter_plugin.c index cb9e0940a..c55b69af2 100644 --- a/src/filter/convert_filter_plugin.c +++ b/src/filter/convert_filter_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -141,7 +141,6 @@ convert_filter_set(struct filter *_filter, 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 index ba9180e64..156adf8e3 100644 --- a/src/filter/convert_filter_plugin.h +++ b/src/filter/convert_filter_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/filter/normalize_filter_plugin.c b/src/filter/normalize_filter_plugin.c index 63bbb6e4f..2151482e4 100644 --- a/src/filter/normalize_filter_plugin.c +++ b/src/filter/normalize_filter_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -67,7 +67,6 @@ normalize_filter_open(struct filter *_filter, struct normalize_filter *filter = (struct normalize_filter *)_filter; audio_format->format = SAMPLE_FORMAT_S16; - audio_format->reverse_endian = false; filter->compressor = Compressor_new(0); diff --git a/src/filter/null_filter_plugin.c b/src/filter/null_filter_plugin.c index 650f95bc4..e7c998827 100644 --- a/src/filter/null_filter_plugin.c +++ b/src/filter/null_filter_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/filter/replay_gain_filter_plugin.c b/src/filter/replay_gain_filter_plugin.c index 3a0af66ff..583a09f90 100644 --- a/src/filter/replay_gain_filter_plugin.c +++ b/src/filter/replay_gain_filter_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -140,8 +140,6 @@ replay_gain_filter_open(struct filter *_filter, 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); @@ -195,7 +193,7 @@ replay_gain_filter_filter(struct filter *_filter, memcpy(dest, src, src_size); - success = pcm_volume(dest, src_size, &filter->audio_format, + success = pcm_volume(dest, src_size, filter->audio_format.format, filter->volume); if (!success) { g_set_error(error_r, replay_gain_quark(), 0, diff --git a/src/filter/replay_gain_filter_plugin.h b/src/filter/replay_gain_filter_plugin.h index 348b4f50c..45b738e40 100644 --- a/src/filter/replay_gain_filter_plugin.h +++ b/src/filter/replay_gain_filter_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/filter/route_filter_plugin.c b/src/filter/route_filter_plugin.c index 6b9aa2a2f..3bf8677e5 100644 --- a/src/filter/route_filter_plugin.c +++ b/src/filter/route_filter_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/filter/volume_filter_plugin.c b/src/filter/volume_filter_plugin.c index 42311ca5e..e52c0a463 100644 --- a/src/filter/volume_filter_plugin.c +++ b/src/filter/volume_filter_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -74,8 +74,6 @@ volume_filter_open(struct filter *_filter, struct audio_format *audio_format, { struct volume_filter *filter = (struct volume_filter *)_filter; - audio_format->reverse_endian = false; - filter->audio_format = *audio_format; pcm_buffer_init(&filter->buffer); @@ -116,7 +114,7 @@ volume_filter_filter(struct filter *_filter, const void *src, size_t src_size, memcpy(dest, src, src_size); - success = pcm_volume(dest, src_size, &filter->audio_format, + success = pcm_volume(dest, src_size, filter->audio_format.format, filter->volume); if (!success) { g_set_error(error_r, volume_quark(), 0, diff --git a/src/filter/volume_filter_plugin.h b/src/filter/volume_filter_plugin.h index ad3b2c6f1..5b16f7e57 100644 --- a/src/filter/volume_filter_plugin.h +++ b/src/filter/volume_filter_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/filter_config.c b/src/filter_config.c index 90de199b7..ab9bdb0c5 100644 --- a/src/filter_config.c +++ b/src/filter_config.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/filter_config.h b/src/filter_config.h index 9ed4d204b..920cbc07c 100644 --- a/src/filter_config.h +++ b/src/filter_config.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/filter_internal.h b/src/filter_internal.h index 8dd6da491..4e94599a2 100644 --- a/src/filter_internal.h +++ b/src/filter_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/filter_plugin.c b/src/filter_plugin.c index 492d703ac..7173134b3 100644 --- a/src/filter_plugin.c +++ b/src/filter_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/filter_plugin.h b/src/filter_plugin.h index ac6b34522..58e34dfb2 100644 --- a/src/filter_plugin.h +++ b/src/filter_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -79,7 +79,7 @@ struct filter_plugin { * * @param plugin the filter plugin * @param param optional configuration section - * @param error location to store the error occuring, or NULL to + * @param error location to store the error occurring, or NULL to * ignore errors. * @return a new filter object, or NULL on error */ @@ -92,7 +92,7 @@ filter_new(const struct filter_plugin *plugin, * the specified configuration section. * * @param param the configuration section - * @param error location to store the error occuring, or NULL to + * @param error location to store the error occurring, or NULL to * ignore errors. * @return a new filter object, or NULL on error */ @@ -114,7 +114,7 @@ filter_free(struct 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 + * @param error location to store the error occurring, or NULL to * ignore errors. * @return the format of outgoing data */ @@ -137,7 +137,7 @@ filter_close(struct filter *filter); * @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 + * @param error location to store the error occurring, or NULL to * ignore errors. * @return the destination buffer on success (will be invalidated by * filter_close() or filter_filter()), NULL on error diff --git a/src/filter_registry.c b/src/filter_registry.c index 150043cc5..dc1889398 100644 --- a/src/filter_registry.c +++ b/src/filter_registry.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/filter_registry.h b/src/filter_registry.h index 551a7afa1..d3949c7c4 100644 --- a/src/filter_registry.h +++ b/src/filter_registry.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,35 +20,44 @@ #ifndef MPD_GCC_H #define MPD_GCC_H +#define GCC_CHECK_VERSION(major, minor) \ + (defined(__GNUC__) && \ + (__GNUC__ > (major) || \ + (__GNUC__ == (major) && __GNUC_MINOR__ >= (minor)))) + /* this allows us to take advantage of special gcc features while still * allowing other compilers to compile: * * example taken from: http://rlove.org/log/2005102601 */ -#if defined(__GNUC__) && (__GNUC__ >= 3) -# define mpd_must_check __attribute__ ((warn_unused_result)) -# define mpd_packed __attribute__ ((packed)) +#if GCC_CHECK_VERSION(3,0) +# define gcc_must_check __attribute__ ((warn_unused_result)) +# define gcc_packed __attribute__ ((packed)) /* these are very useful for type checking */ -# define mpd_printf __attribute__ ((format(printf,1,2))) -# define mpd_fprintf __attribute__ ((format(printf,2,3))) -# define mpd_fprintf_ __attribute__ ((format(printf,3,4))) -# define mpd_fprintf__ __attribute__ ((format(printf,4,5))) -# define mpd_scanf __attribute__ ((format(scanf,1,2))) -# define mpd_used __attribute__ ((used)) +# define gcc_printf __attribute__ ((format(printf,1,2))) +# define gcc_fprintf __attribute__ ((format(printf,2,3))) +# define gcc_fprintf_ __attribute__ ((format(printf,3,4))) +# define gcc_fprintf__ __attribute__ ((format(printf,4,5))) +# define gcc_scanf __attribute__ ((format(scanf,1,2))) +# define gcc_used __attribute__ ((used)) /* # define inline inline __attribute__ ((always_inline)) */ -# define mpd_noinline __attribute__ ((noinline)) +# define gcc_noinline __attribute__ ((noinline)) +# define gcc_nonnull(...) __attribute__((nonnull(__VA_ARGS__))) +# define gcc_nonnull_all __attribute__((nonnull)) #else -# define mpd_must_check -# define mpd_packed -# define mpd_printf -# define mpd_fprintf -# define mpd_fprintf_ -# define mpd_fprintf__ -# define mpd_scanf -# define mpd_used +# define gcc_must_check +# define gcc_packed +# define gcc_printf +# define gcc_fprintf +# define gcc_fprintf_ +# define gcc_fprintf__ +# define gcc_scanf +# define gcc_used /* # define inline */ -# define mpd_noinline +# define gcc_noinline +# define gcc_nonnull(...) +# define gcc_nonnull_all #endif #endif /* MPD_GCC_H */ diff --git a/src/glib_compat.h b/src/glib_compat.h index 4d0e7040d..330c9e779 100644 --- a/src/glib_compat.h +++ b/src/glib_compat.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -32,6 +32,12 @@ #define g_queue_clear(q) do { g_queue_free(q); q = g_queue_new(); } while (0) +static inline GSource * +g_timeout_source_new_seconds(guint interval) +{ + return g_timeout_source_new(interval * 1000); +} + static inline guint g_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data) { @@ -43,6 +49,12 @@ g_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data) #if !GLIB_CHECK_VERSION(2,16,0) static inline void +g_prefix_error(G_GNUC_UNUSED GError **error_r, + G_GNUC_UNUSED const gchar *format, ...) +{ +} + +static inline void g_propagate_prefixed_error(GError **dest_r, GError *src, G_GNUC_UNUSED const gchar *format, ...) { @@ -74,4 +86,27 @@ g_uri_parse_scheme(const char *uri) #endif +#if !GLIB_CHECK_VERSION(2,18,0) + +static inline void +g_set_error_literal(GError **err, GQuark domain, gint code, + const gchar *message) +{ + g_set_error(err, domain, code, "%s", message); +} + +#endif + +#if !GLIB_CHECK_VERSION(2,28,0) + +static inline gint64 +g_source_get_time(GSource *source) +{ + GTimeVal tv; + g_source_get_current_time(source, &tv); + return tv.tv_sec * 1000000 + tv.tv_usec; +} + +#endif + #endif diff --git a/src/icy_metadata.c b/src/icy_metadata.c index 6a79121cf..32953e69f 100644 --- a/src/icy_metadata.c +++ b/src/icy_metadata.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_metadata.h b/src/icy_metadata.h index 4a51b4cf0..9797122ca 100644 --- a/src/icy_metadata.h +++ b/src/icy_metadata.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 62a2c67af..b6c89eaf6 100644 --- a/src/icy_server.c +++ b/src/icy_server.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -70,7 +70,7 @@ icy_server_metadata_string(const char *stream_title, const char* stream_url) meta_length = strlen(icy_metadata); - meta_length--; // substract placeholder + meta_length--; // subtract placeholder meta_length = ((int)meta_length / 16) + 1; diff --git a/src/icy_server.h b/src/icy_server.h index 3ce4ab635..04f21d2ad 100644 --- a/src/icy_server.h +++ b/src/icy_server.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 eccb62322..2d174d78a 100644 --- a/src/idle.c +++ b/src/idle.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -42,6 +42,8 @@ static const char *const idle_names[] = { "options", "sticker", "update", + "subscription", + "message", NULL }; diff --git a/src/idle.h b/src/idle.h index 7caeb4a8c..0156933c0 100644 --- a/src/idle.h +++ b/src/idle.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -53,6 +53,12 @@ enum { /** a database update has started or finished. */ IDLE_UPDATE = 0x100, + + /** a client has subscribed or unsubscribed to/from a channel */ + IDLE_SUBSCRIPTION = 0x200, + + /** a message on the subscribed channel was receivedd */ + IDLE_MESSAGE = 0x400, }; /** diff --git a/src/inotify_queue.c b/src/inotify_queue.c index 5391a1715..d5e2228c3 100644 --- a/src/inotify_queue.c +++ b/src/inotify_queue.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/inotify_queue.h b/src/inotify_queue.h index 2e43d2f25..cfc28ebfe 100644 --- a/src/inotify_queue.h +++ b/src/inotify_queue.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/inotify_source.c b/src/inotify_source.c index 3a986cbad..e415f5e72 100644 --- a/src/inotify_source.c +++ b/src/inotify_source.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/inotify_source.h b/src/inotify_source.h index e78b92c0f..f92e18e39 100644 --- a/src/inotify_source.h +++ b/src/inotify_source.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/inotify_update.c b/src/inotify_update.c index 8d9657961..02e55ee0b 100644 --- a/src/inotify_update.c +++ b/src/inotify_update.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -266,18 +266,19 @@ mpd_inotify_callback(int wd, unsigned mask, (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 + const char *root = mapper_get_music_directory(); + const char *path_fs; + char *allocated = NULL; + + if (uri_fs != NULL) + path_fs = allocated = + g_strconcat(root, "/", uri_fs, NULL); + else path_fs = root; recursive_watch_subdirectories(directory, path_fs, watch_directory_depth(directory)); - g_free(path_fs); + g_free(allocated); } if ((mask & (IN_CLOSE_WRITE|IN_MOVE|IN_DELETE)) != 0 || @@ -303,21 +304,13 @@ mpd_inotify_callback(int wd, unsigned mask, 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); + const char *path = mapper_get_music_directory(); if (path == NULL) { - g_warning("mapper has failed"); + g_debug("no music directory configured"); return; } @@ -326,13 +319,12 @@ mpd_inotify_init(unsigned max_depth) 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.name = g_strdup(path); inotify_root.descriptor = mpd_inotify_source_add(inotify_source, path, IN_MASK, &error); if (inotify_root.descriptor < 0) { @@ -340,7 +332,6 @@ mpd_inotify_init(unsigned max_depth) g_error_free(error); mpd_inotify_source_free(inotify_source); inotify_source = NULL; - g_free(path); return; } diff --git a/src/inotify_update.h b/src/inotify_update.h index 92b4e0cc6..ca75c0f45 100644 --- a/src/inotify_update.h +++ b/src/inotify_update.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/archive_input_plugin.c b/src/input/archive_input_plugin.c index 97e4836ff..4a038b9e2 100644 --- a/src/input/archive_input_plugin.c +++ b/src/input/archive_input_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -34,7 +34,9 @@ * plugin and gzip fetches file from disk */ static struct input_stream * -input_archive_open(const char *pathname, GError **error_r) +input_archive_open(const char *pathname, + GMutex *mutex, GCond *cond, + GError **error_r) { const struct archive_plugin *arplug; struct archive_file *file; @@ -65,7 +67,8 @@ input_archive_open(const char *pathname, GError **error_r) return NULL; //setup fileops - is = archive_file_open_stream(file, filename, error_r); + is = archive_file_open_stream(file, filename, mutex, cond, + error_r); archive_file_close(file); g_free(pname); diff --git a/src/input/archive_input_plugin.h b/src/input/archive_input_plugin.h index 20568cfbe..51095f37f 100644 --- a/src/input/archive_input_plugin.h +++ b/src/input/archive_input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/cdio_paranoia_input_plugin.c b/src/input/cdio_paranoia_input_plugin.c new file mode 100644 index 000000000..1de7623a1 --- /dev/null +++ b/src/input/cdio_paranoia_input_plugin.c @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** + * CD-Audio handling (requires libcdio_paranoia) + */ + +#include "config.h" +#include "input/cdio_paranoia_input_plugin.h" +#include "input_internal.h" +#include "input_plugin.h" +#include "refcount.h" + +#include <stdio.h> +#include <stdint.h> +#include <stddef.h> +#include <string.h> +#include <stdlib.h> +#include <glib.h> +#include <assert.h> + +#include <cdio/paranoia.h> +#include <cdio/cd_types.h> + +struct input_cdio_paranoia { + struct input_stream base; + + cdrom_drive_t *drv; + CdIo_t *cdio; + cdrom_paranoia_t *para; + + lsn_t lsn_from, lsn_to; + int lsn_relofs; + + int trackno; + + char buffer[CDIO_CD_FRAMESIZE_RAW]; + int buffer_lsn; +}; + +static inline GQuark +cdio_quark(void) +{ + return g_quark_from_static_string("cdio"); +} + +static void +input_cdio_close(struct input_stream *is) +{ + struct input_cdio_paranoia *i = (struct input_cdio_paranoia *)is; + + if (i->para) + cdio_paranoia_free(i->para); + if (i->drv) + cdio_cddap_close_no_free_cdio( i->drv); + if (i->cdio) + cdio_destroy( i->cdio ); + + input_stream_deinit(&i->base); + g_free(i); +} + +struct cdio_uri { + char device[64]; + int track; +}; + +static bool +parse_cdio_uri(struct cdio_uri *dest, const char *src, GError **error_r) +{ + if (!g_str_has_prefix(src, "cdda://")) + return false; + + src += 7; + + if (*src == 0) { + /* play the whole CD in the default drive */ + dest->device[0] = 0; + dest->track = -1; + return true; + } + + const char *slash = strrchr(src, '/'); + if (slash == NULL) { + /* play the whole CD in the specified drive */ + g_strlcpy(dest->device, src, sizeof(dest->device)); + dest->track = -1; + return true; + } + + size_t device_length = slash - src; + if (device_length >= sizeof(dest->device)) + device_length = sizeof(dest->device) - 1; + + memcpy(dest->device, src, device_length); + dest->device[device_length] = 0; + + const char *track = slash + 1; + + char *endptr; + dest->track = strtoul(track, &endptr, 10); + if (*endptr != 0) { + g_set_error(error_r, cdio_quark(), 0, + "Malformed track number"); + return false; + } + + if (endptr == track) + /* play the whole CD */ + dest->track = -1; + + return true; +} + +static char * +cdio_detect_device(void) +{ + char **devices = cdio_get_devices_with_cap(NULL, CDIO_FS_AUDIO, false); + if (devices == NULL) + return NULL; + + char *device = g_strdup(devices[0]); + cdio_free_device_list(devices); + + return device; +} + +static struct input_stream * +input_cdio_open(const char *uri, + GMutex *mutex, GCond *cond, + GError **error_r) +{ + struct input_cdio_paranoia *i; + + struct cdio_uri parsed_uri; + if (!parse_cdio_uri(&parsed_uri, uri, error_r)) + return NULL; + + i = g_new(struct input_cdio_paranoia, 1); + input_stream_init(&i->base, &input_plugin_cdio_paranoia, uri, + mutex, cond); + + /* initialize everything (should be already) */ + i->drv = NULL; + i->cdio = NULL; + i->para = NULL; + i->trackno = parsed_uri.track; + + /* get list of CD's supporting CD-DA */ + char *device = parsed_uri.device[0] != 0 + ? g_strdup(parsed_uri.device) + : cdio_detect_device(); + if (device == NULL) { + g_set_error(error_r, cdio_quark(), 0, + "Unable find or access a CD-ROM drive with an audio CD in it."); + input_cdio_close(&i->base); + return NULL; + } + + /* Found such a CD-ROM with a CD-DA loaded. Use the first drive in the list. */ + i->cdio = cdio_open(device, DRIVER_UNKNOWN); + g_free(device); + + i->drv = cdio_cddap_identify_cdio(i->cdio, 1, NULL); + + if ( !i->drv ) { + g_set_error(error_r, cdio_quark(), 0, + "Unable to identify audio CD disc."); + input_cdio_close(&i->base); + return NULL; + } + + cdda_verbose_set(i->drv, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT); + + if ( 0 != cdio_cddap_open(i->drv) ) { + g_set_error(error_r, cdio_quark(), 0, "Unable to open disc."); + input_cdio_close(&i->base); + return NULL; + } + + bool reverse_endian; + switch (data_bigendianp(i->drv)) { + case -1: + g_debug("cdda: drive returns unknown audio data"); + reverse_endian = false; + break; + case 0: + g_debug("cdda: drive returns audio data Little Endian."); + reverse_endian = G_BYTE_ORDER == G_BIG_ENDIAN; + break; + case 1: + g_debug("cdda: drive returns audio data Big Endian."); + reverse_endian = G_BYTE_ORDER == G_LITTLE_ENDIAN; + break; + default: + g_set_error(error_r, cdio_quark(), 0, + "Drive returns unknown data type %d", + data_bigendianp(i->drv)); + input_cdio_close(&i->base); + return NULL; + } + + i->lsn_relofs = 0; + + if (i->trackno >= 0) { + i->lsn_from = cdio_get_track_lsn(i->cdio, i->trackno); + i->lsn_to = cdio_get_track_last_lsn(i->cdio, i->trackno); + } else { + i->lsn_from = 0; + i->lsn_to = cdio_get_disc_last_lsn(i->cdio); + } + + i->para = cdio_paranoia_init(i->drv); + + /* Set reading mode for full paranoia, but allow skipping sectors. */ + paranoia_modeset(i->para, PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP); + + /* seek to beginning of the track */ + cdio_paranoia_seek(i->para, i->lsn_from, SEEK_SET); + + i->base.ready = true; + i->base.seekable = true; + i->base.size = (i->lsn_to - i->lsn_from + 1) * CDIO_CD_FRAMESIZE_RAW; + + /* hack to make MPD select the "pcm" decoder plugin */ + i->base.mime = g_strdup(reverse_endian + ? "audio/x-mpd-cdda-pcm-reverse" + : "audio/x-mpd-cdda-pcm"); + + return &i->base; +} + +static bool +input_cdio_seek(struct input_stream *is, + goffset offset, int whence, GError **error_r) +{ + struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is; + + /* calculate absolute offset */ + switch (whence) { + case SEEK_SET: + break; + case SEEK_CUR: + offset += cis->base.offset; + break; + case SEEK_END: + offset += cis->base.size; + break; + } + + if (offset < 0 || offset > cis->base.size) { + g_set_error(error_r, cdio_quark(), 0, + "Invalid offset to seek %ld (%ld)", + (long int)offset, (long int)cis->base.size); + return false; + } + + /* simple case */ + if (offset == cis->base.offset) + return true; + + /* calculate current LSN */ + cis->lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW; + cis->base.offset = offset; + + cdio_paranoia_seek(cis->para, cis->lsn_from + cis->lsn_relofs, SEEK_SET); + + return true; +} + +static size_t +input_cdio_read(struct input_stream *is, void *ptr, size_t length, + GError **error_r) +{ + struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is; + size_t nbytes = 0; + int diff; + size_t len, maxwrite; + int16_t *rbuf; + char *s_err, *s_mess; + char *wptr = (char *) ptr; + + while (length > 0) { + + + /* end of track ? */ + if (cis->lsn_from + cis->lsn_relofs > cis->lsn_to) + break; + + //current sector was changed ? + if (cis->lsn_relofs != cis->buffer_lsn) { + rbuf = cdio_paranoia_read(cis->para, NULL); + + s_err = cdda_errors(cis->drv); + if (s_err) { + g_warning("paranoia_read: %s", s_err ); + free(s_err); + } + s_mess = cdda_messages(cis->drv); + if (s_mess) { + free(s_mess); + } + if (!rbuf) { + g_set_error(error_r, cdio_quark(), 0, + "paranoia read error. Stopping."); + return 0; + } + //store current buffer + memcpy(cis->buffer, rbuf, CDIO_CD_FRAMESIZE_RAW); + cis->buffer_lsn = cis->lsn_relofs; + } else { + //use cached sector + rbuf = (int16_t*) cis->buffer; + } + + //correct offset + diff = cis->base.offset - cis->lsn_relofs * CDIO_CD_FRAMESIZE_RAW; + + assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW); + + maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer + len = (length < maxwrite? length : maxwrite); + + //skip diff bytes from this lsn + memcpy(wptr, ((char*)rbuf) + diff, len); + //update pointer + wptr += len; + nbytes += len; + + //update offset + cis->base.offset += len; + cis->lsn_relofs = cis->base.offset / CDIO_CD_FRAMESIZE_RAW; + //update length + length -= len; + } + + return nbytes; +} + +static bool +input_cdio_eof(struct input_stream *is) +{ + struct input_cdio_paranoia *cis = (struct input_cdio_paranoia *)is; + + return (cis->lsn_from + cis->lsn_relofs > cis->lsn_to); +} + +const struct input_plugin input_plugin_cdio_paranoia = { + .name = "cdio_paranoia", + .open = input_cdio_open, + .close = input_cdio_close, + .seek = input_cdio_seek, + .read = input_cdio_read, + .eof = input_cdio_eof +}; diff --git a/src/input/cdio_paranoia_input_plugin.h b/src/input/cdio_paranoia_input_plugin.h new file mode 100644 index 000000000..71c5cbe8d --- /dev/null +++ b/src/input/cdio_paranoia_input_plugin.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_CDIO_PARANOIA_INPUT_PLUGIN_H +#define MPD_CDIO_PARANOIA_INPUT_PLUGIN_H + +/** + * An input plugin based on libcdio_paranoia library. + */ +extern const struct input_plugin input_plugin_cdio_paranoia; + +#endif diff --git a/src/input/curl_input_plugin.c b/src/input/curl_input_plugin.c index 604965dd1..3f191141e 100644 --- a/src/input/curl_input_plugin.c +++ b/src/input/curl_input_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,10 +19,12 @@ #include "config.h" #include "input/curl_input_plugin.h" +#include "input_internal.h" #include "input_plugin.h" #include "conf.h" #include "tag.h" #include "icy_metadata.h" +#include "io_thread.h" #include "glib_compat.h" #include <assert.h> @@ -50,6 +52,11 @@ static const size_t CURL_MAX_BUFFERED = 512 * 1024; /** + * Resume the stream at this number of bytes after it has been paused. + */ +static const size_t CURL_RESUME_AT = 384 * 1024; + +/** * Buffers created by input_curl_writefunction(). */ struct buffer { @@ -73,17 +80,29 @@ struct input_curl { /** the curl handles */ CURL *easy; - CURLM *multi; + + /** the GMainLoop source used to poll all CURL file + descriptors */ + GSource *source; + + /** the source id of #source */ + guint source_id; + + /** a linked list of all registered GPollFD objects */ + GSList *fds; /** list of buffers, where input_curl_writefunction() appends to, and input_curl_read() reads from them */ GQueue *buffers; - /** has something been added to the buffers list? */ - bool buffered; - - /** did libcurl tell us the we're at the end of the response body? */ - bool eof; +#if LIBCURL_VERSION_NUM >= 0x071200 + /** + * Is the connection currently paused? That happens when the + * buffer was getting too large. It will be unpaused when the + * buffer is below the threshold again. + */ + bool paused; +#endif /** error message provided by libcurl */ char error[CURL_ERROR_SIZE]; @@ -97,6 +116,8 @@ struct input_curl { /** the tag object ready to be requested via input_stream_tag() */ struct tag *tag; + + GError *postponed_error; }; /** libcurl should accept "ICY 200 OK" */ @@ -106,20 +127,520 @@ static struct curl_slist *http_200_aliases; static const char *proxy, *proxy_user, *proxy_password; static unsigned proxy_port; +static struct { + CURLM *multi; + + /** + * A linked list of all active HTTP requests. An active + * request is one that doesn't have the "eof" flag set. + */ + GSList *requests; + + /** + * The GMainLoop source used to poll all CURL file + * descriptors. + */ + GSource *source; + + /** + * The source id of #source. + */ + guint source_id; + + GSList *fds; + +#if LIBCURL_VERSION_NUM >= 0x070f04 + /** + * Did CURL give us a timeout? If yes, then we need to call + * curl_multi_perform(), even if there was no event on any + * file descriptor. + */ + bool timeout; + + /** + * The absolute time stamp when the timeout expires. This is + * used in the GSource method check(). + */ + gint64 absolute_timeout; +#endif +} curl; + static inline GQuark curl_quark(void) { return g_quark_from_static_string("curl"); } +/** + * Find a request by its CURL "easy" handle. + * + * Runs in the I/O thread. No lock needed. + */ +static struct input_curl * +input_curl_find_request(CURL *easy) +{ + assert(io_thread_inside()); + + for (GSList *i = curl.requests; i != NULL; i = g_slist_next(i)) { + struct input_curl *c = i->data; + if (c->easy == easy) + return c; + } + + return NULL; +} + +#if LIBCURL_VERSION_NUM >= 0x071200 + +static gpointer +input_curl_resume(gpointer data) +{ + assert(io_thread_inside()); + + struct input_curl *c = data; + + if (c->paused) { + c->paused = false; + curl_easy_pause(c->easy, CURLPAUSE_CONT); + } + + return NULL; +} + +#endif + +/** + * Calculates the GLib event bit mask for one file descriptor, + * obtained from three #fd_set objects filled by curl_multi_fdset(). + */ +static gushort +input_curl_fd_events(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds) +{ + gushort events = 0; + + if (FD_ISSET(fd, rfds)) { + events |= G_IO_IN | G_IO_HUP | G_IO_ERR; + FD_CLR(fd, rfds); + } + + if (FD_ISSET(fd, wfds)) { + events |= G_IO_OUT | G_IO_ERR; + FD_CLR(fd, wfds); + } + + if (FD_ISSET(fd, efds)) { + events |= G_IO_HUP | G_IO_ERR; + FD_CLR(fd, efds); + } + + return events; +} + +/** + * Updates all registered GPollFD objects, unregisters old ones, + * registers new ones. + * + * Runs in the I/O thread. No lock needed. + */ +static void +curl_update_fds(void) +{ + assert(io_thread_inside()); + + fd_set rfds, wfds, efds; + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&efds); + + int max_fd; + CURLMcode mcode = curl_multi_fdset(curl.multi, &rfds, &wfds, + &efds, &max_fd); + if (mcode != CURLM_OK) { + g_warning("curl_multi_fdset() failed: %s\n", + curl_multi_strerror(mcode)); + return; + } + + GSList *fds = curl.fds; + curl.fds = NULL; + + while (fds != NULL) { + GPollFD *poll_fd = fds->data; + gushort events = input_curl_fd_events(poll_fd->fd, &rfds, + &wfds, &efds); + + assert(poll_fd->events != 0); + + fds = g_slist_remove(fds, poll_fd); + + if (events != poll_fd->events) + g_source_remove_poll(curl.source, poll_fd); + + if (events != 0) { + if (events != poll_fd->events) { + poll_fd->events = events; + g_source_add_poll(curl.source, poll_fd); + } + + curl.fds = g_slist_prepend(curl.fds, poll_fd); + } else { + g_free(poll_fd); + } + } + + for (int fd = 0; fd <= max_fd; ++fd) { + gushort events = input_curl_fd_events(fd, &rfds, &wfds, &efds); + if (events != 0) { + GPollFD *poll_fd = g_new(GPollFD, 1); + poll_fd->fd = fd; + poll_fd->events = events; + g_source_add_poll(curl.source, poll_fd); + curl.fds = g_slist_prepend(curl.fds, poll_fd); + } + } +} + +/** + * Runs in the I/O thread. No lock needed. + */ +static bool +input_curl_easy_add(struct input_curl *c, GError **error_r) +{ + assert(io_thread_inside()); + assert(c != NULL); + assert(c->easy != NULL); + assert(input_curl_find_request(c->easy) == NULL); + + curl.requests = g_slist_prepend(curl.requests, c); + + CURLMcode mcode = curl_multi_add_handle(curl.multi, c->easy); + 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_update_fds(); + + return true; +} + +struct easy_add_params { + struct input_curl *c; + GError **error_r; +}; + +static gpointer +input_curl_easy_add_callback(gpointer data) +{ + const struct easy_add_params *params = data; + + bool success = input_curl_easy_add(params->c, params->error_r); + return GUINT_TO_POINTER(success); +} + +/** + * Call input_curl_easy_add() in the I/O thread. May be called from + * any thread. Caller must not hold a mutex. + */ +static bool +input_curl_easy_add_indirect(struct input_curl *c, GError **error_r) +{ + assert(c != NULL); + assert(c->easy != NULL); + + struct easy_add_params params = { + .c = c, + .error_r = error_r, + }; + + gpointer result = + io_thread_call(input_curl_easy_add_callback, ¶ms); + return GPOINTER_TO_UINT(result); +} + +/** + * Frees the current "libcurl easy" handle, and everything associated + * with it. + * + * Runs in the I/O thread. + */ +static void +input_curl_easy_free(struct input_curl *c) +{ + assert(io_thread_inside()); + assert(c != NULL); + + if (c->easy == NULL) + return; + + curl.requests = g_slist_remove(curl.requests, c); + + curl_multi_remove_handle(curl.multi, c->easy); + curl_easy_cleanup(c->easy); + c->easy = NULL; + + curl_slist_free_all(c->request_headers); + c->request_headers = NULL; + + g_free(c->range); + c->range = NULL; +} + +static gpointer +input_curl_easy_free_callback(gpointer data) +{ + struct input_curl *c = data; + + input_curl_easy_free(c); + curl_update_fds(); + + return NULL; +} + +/** + * Frees the current "libcurl easy" handle, and everything associated + * with it. + * + * The mutex must not be locked. + */ +static void +input_curl_easy_free_indirect(struct input_curl *c) +{ + io_thread_call(input_curl_easy_free_callback, c); + assert(c->easy == NULL); +} + +/** + * Abort and free all HTTP requests. + * + * Runs in the I/O thread. The caller must not hold locks. + */ +static void +input_curl_abort_all_requests(GError *error) +{ + assert(io_thread_inside()); + assert(error != NULL); + + while (curl.requests != NULL) { + struct input_curl *c = curl.requests->data; + assert(c->postponed_error == NULL); + + input_curl_easy_free(c); + + g_mutex_lock(c->base.mutex); + c->postponed_error = g_error_copy(error); + c->base.ready = true; + g_cond_broadcast(c->base.cond); + g_mutex_unlock(c->base.mutex); + } + + g_error_free(error); + +} + +/** + * A HTTP request is finished. + * + * Runs in the I/O thread. The caller must not hold locks. + */ +static void +input_curl_request_done(struct input_curl *c, CURLcode result, long status) +{ + assert(io_thread_inside()); + assert(c != NULL); + assert(c->easy == NULL); + assert(c->postponed_error == NULL); + + g_mutex_lock(c->base.mutex); + + if (result != CURLE_OK) { + c->postponed_error = g_error_new(curl_quark(), result, + "curl failed: %s", + c->error); + } else if (status < 200 || status >= 300) { + c->postponed_error = g_error_new(curl_quark(), 0, + "got HTTP status %ld", + status); + } + + c->base.ready = true; + g_cond_broadcast(c->base.cond); + g_mutex_unlock(c->base.mutex); +} + +static void +input_curl_handle_done(CURL *easy_handle, CURLcode result) +{ + struct input_curl *c = input_curl_find_request(easy_handle); + assert(c != NULL); + + long status = 0; + curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status); + + input_curl_easy_free(c); + input_curl_request_done(c, result, status); +} + +/** + * Check for finished HTTP responses. + * + * Runs in the I/O thread. The caller must not hold locks. + */ +static void +input_curl_info_read(void) +{ + assert(io_thread_inside()); + + CURLMsg *msg; + int msgs_in_queue; + + while ((msg = curl_multi_info_read(curl.multi, + &msgs_in_queue)) != NULL) { + if (msg->msg == CURLMSG_DONE) + input_curl_handle_done(msg->easy_handle, msg->data.result); + } +} + +/** + * Give control to CURL. + * + * Runs in the I/O thread. The caller must not hold locks. + */ +static bool +input_curl_perform(void) +{ + assert(io_thread_inside()); + + CURLMcode mcode; + + do { + int running_handles; + mcode = curl_multi_perform(curl.multi, &running_handles); + } while (mcode == CURLM_CALL_MULTI_PERFORM); + + if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) { + GError *error = g_error_new(curl_quark(), mcode, + "curl_multi_perform() failed: %s", + curl_multi_strerror(mcode)); + input_curl_abort_all_requests(error); + return false; + } + + return true; +} + +/* + * GSource methods + * + */ + +/** + * The GSource prepare() method implementation. + */ +static gboolean +input_curl_source_prepare(G_GNUC_UNUSED GSource *source, gint *timeout_r) +{ + curl_update_fds(); + +#if LIBCURL_VERSION_NUM >= 0x070f04 + curl.timeout = false; + + long timeout2; + CURLMcode mcode = curl_multi_timeout(curl.multi, &timeout2); + if (mcode == CURLM_OK) { + if (timeout2 >= 0) + curl.absolute_timeout = g_source_get_time(source) + + timeout2 * 1000; + + if (timeout2 >= 0 && timeout2 < 10) + /* CURL 7.21.1 likes to report "timeout=0", + which means we're running in a busy loop. + Quite a bad idea to waste so much CPU. + Let's use a lower limit of 10ms. */ + timeout2 = 10; + + *timeout_r = timeout2; + + curl.timeout = timeout2 >= 0; + } else + g_warning("curl_multi_timeout() failed: %s\n", + curl_multi_strerror(mcode)); +#else + (void)timeout_r; +#endif + + return false; +} + +/** + * The GSource check() method implementation. + */ +static gboolean +input_curl_source_check(G_GNUC_UNUSED GSource *source) +{ +#if LIBCURL_VERSION_NUM >= 0x070f04 + if (curl.timeout) { + /* when a timeout has expired, we need to call + curl_multi_perform(), even if there was no file + descriptor event */ + + if (g_source_get_time(source) >= curl.absolute_timeout) + return true; + } +#endif + + for (GSList *i = curl.fds; i != NULL; i = i->next) { + GPollFD *poll_fd = i->data; + if (poll_fd->revents != 0) + return true; + } + + return false; +} + +/** + * The GSource dispatch() method implementation. The callback isn't + * used, because we're handling all events directly. + */ +static gboolean +input_curl_source_dispatch(G_GNUC_UNUSED GSource *source, + G_GNUC_UNUSED GSourceFunc callback, + G_GNUC_UNUSED gpointer user_data) +{ + if (input_curl_perform()) + input_curl_info_read(); + + return true; +} + +/** + * The vtable for our GSource implementation. Unfortunately, we + * cannot declare it "const", because g_source_new() takes a non-const + * pointer, for whatever reason. + */ +static GSourceFuncs curl_source_funcs = { + .prepare = input_curl_source_prepare, + .check = input_curl_source_check, + .dispatch = input_curl_source_dispatch, +}; + +/* + * input_plugin methods + * + */ + static bool 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) { - g_warning("curl_global_init() failed: %s\n", - curl_easy_strerror(code)); + g_set_error(error_r, curl_quark(), code, + "curl_global_init() failed: %s\n", + curl_easy_strerror(code)); return false; } @@ -140,20 +661,48 @@ input_curl_init(const struct config_param *param, ""); } + curl.multi = curl_multi_init(); + if (curl.multi == NULL) { + g_set_error(error_r, curl_quark(), 0, + "curl_multi_init() failed"); + return false; + } + + curl.source = g_source_new(&curl_source_funcs, sizeof(*curl.source)); + curl.source_id = g_source_attach(curl.source, io_thread_context()); + return true; } +static gpointer +curl_destroy_sources(G_GNUC_UNUSED gpointer data) +{ + g_source_destroy(curl.source); + + return NULL; +} + static void input_curl_finish(void) { + assert(curl.requests == NULL); + + io_thread_call(curl_destroy_sources, NULL); + + curl_multi_cleanup(curl.multi); + curl_slist_free_all(http_200_aliases); curl_global_cleanup(); } +#if LIBCURL_VERSION_NUM >= 0x071200 + /** * Determine the total sizes of all buffers, including portions that * have already been consumed. + * + * The caller must lock the mutex. */ G_GNUC_PURE static size_t @@ -170,6 +719,8 @@ curl_total_buffer_size(const struct input_curl *c) return total; } +#endif + static void buffer_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) { @@ -180,31 +731,15 @@ buffer_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) g_free(buffer); } -/** - * Frees the current "libcurl easy" handle, and everything associated - * with it. - */ static void -input_curl_easy_free(struct input_curl *c) +input_curl_flush_buffers(struct input_curl *c) { - if (c->easy != NULL) { - curl_multi_remove_handle(c->multi, c->easy); - curl_easy_cleanup(c->easy); - c->easy = NULL; - } - - curl_slist_free_all(c->request_headers); - c->request_headers = NULL; - - g_free(c->range); - c->range = NULL; - g_queue_foreach(c->buffers, buffer_free_callback, NULL); g_queue_clear(c->buffers); } /** - * Frees this stream (but not the input_stream struct itself). + * Frees this stream, including the input_stream struct. */ static void input_curl_free(struct input_curl *c) @@ -213,142 +748,53 @@ input_curl_free(struct input_curl *c) tag_free(c->tag); g_free(c->meta_name); - input_curl_easy_free(c); - - if (c->multi != NULL) - curl_multi_cleanup(c->multi); + input_curl_easy_free_indirect(c); + input_curl_flush_buffers(c); g_queue_free(c->buffers); + if (c->postponed_error != NULL) + g_error_free(c->postponed_error); + 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 = (struct input_curl *)is; - struct tag *tag = c->tag; - - c->tag = NULL; - return tag; -} - static bool -input_curl_multi_info_read(struct input_curl *c, GError **error_r) +input_curl_check(struct input_stream *is, GError **error_r) { - CURLMsg *msg; - int msgs_in_queue; + struct input_curl *c = (struct input_curl *)is; - while ((msg = curl_multi_info_read(c->multi, - &msgs_in_queue)) != NULL) { - if (msg->msg == CURLMSG_DONE) { - c->eof = true; - c->base.ready = true; - - if (msg->data.result != CURLE_OK) { - g_set_error(error_r, curl_quark(), - msg->data.result, - "curl failed: %s", c->error); - return false; - } - } + bool success = c->postponed_error == NULL; + if (!success) { + g_propagate_error(error_r, c->postponed_error); + c->postponed_error = NULL; } - return true; + return success; } -/** - * Wait for the libcurl socket. - * - * @return -1 on error, 0 if no data is available yet, 1 if data is - * available - */ -static int -input_curl_select(struct input_curl *c, GError **error_r) +static struct tag * +input_curl_tag(struct input_stream *is) { - fd_set rfds, wfds, efds; - int max_fd, ret; - CURLMcode mcode; - struct timeval timeout = { - .tv_sec = 1, - .tv_usec = 0, - }; - - assert(!c->eof); - - FD_ZERO(&rfds); - FD_ZERO(&wfds); - FD_ZERO(&efds); - - mcode = curl_multi_fdset(c->multi, &rfds, &wfds, &efds, &max_fd); - if (mcode != CURLM_OK) { - g_set_error(error_r, curl_quark(), mcode, - "curl_multi_fdset() failed: %s", - curl_multi_strerror(mcode)); - return -1; - } - -#if LIBCURL_VERSION_NUM >= 0x070f04 - long timeout2; - mcode = curl_multi_timeout(c->multi, &timeout2); - if (mcode != CURLM_OK) { - g_warning("curl_multi_timeout() failed: %s\n", - curl_multi_strerror(mcode)); - return -1; - } - - if (timeout2 >= 0) { - if (timeout2 > 10000) - timeout2 = 10000; - - timeout.tv_sec = timeout2 / 1000; - timeout.tv_usec = (timeout2 % 1000) * 1000; - } -#endif - - ret = select(max_fd + 1, &rfds, &wfds, &efds, &timeout); - if (ret < 0) - g_set_error(error_r, g_quark_from_static_string("errno"), - errno, - "select() failed: %s\n", g_strerror(errno)); + struct input_curl *c = (struct input_curl *)is; + struct tag *tag = c->tag; - return ret; + c->tag = NULL; + return tag; } static bool -fill_buffer(struct input_stream *is, GError **error_r) +fill_buffer(struct input_curl *c, GError **error_r) { - struct input_curl *c = (struct input_curl *)is; - CURLMcode mcode = CURLM_CALL_MULTI_PERFORM; - - while (!c->eof && g_queue_is_empty(c->buffers)) { - int running_handles; - bool bret; - - 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, error_r); - if (ret <= 0) - /* no data yet or error */ - return false; - } - - mcode = curl_multi_perform(c->multi, &running_handles); - if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) { - 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; - } + while (c->easy != NULL && g_queue_is_empty(c->buffers)) + g_cond_wait(c->base.cond, c->base.mutex); - bret = input_curl_multi_info_read(c, error_r); - if (!bret) - return false; + if (c->postponed_error != NULL) { + g_propagate_error(error_r, c->postponed_error); + c->postponed_error = NULL; + return false; } return !g_queue_is_empty(c->buffers); @@ -444,6 +890,15 @@ copy_icy_tag(struct input_curl *c) c->tag = tag; } +static bool +input_curl_available(struct input_stream *is) +{ + struct input_curl *c = (struct input_curl *)is; + + return c->postponed_error != NULL || c->easy == NULL || + !g_queue_is_empty(c->buffers); +} + static size_t input_curl_read(struct input_stream *is, void *ptr, size_t size, GError **error_r) @@ -456,7 +911,7 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size, do { /* fill the buffer */ - success = fill_buffer(is, error_r); + success = fill_buffer(c, error_r); if (!success) return 0; @@ -476,6 +931,14 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size, is->offset += (goffset)nbytes; +#if LIBCURL_VERSION_NUM >= 0x071200 + if (c->paused && curl_total_buffer_size(c) < CURL_RESUME_AT) { + g_mutex_unlock(c->base.mutex); + io_thread_call(input_curl_resume, c); + g_mutex_lock(c->base.mutex); + } +#endif + return nbytes; } @@ -492,49 +955,7 @@ input_curl_eof(G_GNUC_UNUSED struct input_stream *is) { 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, GError **error_r) -{ - struct input_curl *c = (struct input_curl *)is; - - if (curl_total_buffer_size(c) >= CURL_MAX_BUFFERED) - return 0; - - CURLMcode mcode; - int running_handles; - bool ret; - - c->buffered = false; - - if (!is->ready && !c->eof) - /* not ready yet means the caller is waiting in a busy - loop; relax that by calling select() on the - socket */ - if (input_curl_select(c, error_r) < 0) - return -1; - - do { - mcode = curl_multi_perform(c->multi, &running_handles); - } while (mcode == CURLM_CALL_MULTI_PERFORM && - g_queue_is_empty(c->buffers)); - - if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) { - 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(c, error_r); - if (!ret) - return -1; - - return c->buffered; + return c->easy == NULL && g_queue_is_empty(c->buffers); } /** called by curl when new data is available */ @@ -632,15 +1053,27 @@ input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream) if (size == 0) return 0; + g_mutex_lock(c->base.mutex); + +#if LIBCURL_VERSION_NUM >= 0x071200 + if (curl_total_buffer_size(c) + size >= CURL_MAX_BUFFERED) { + c->paused = true; + g_mutex_unlock(c->base.mutex); + return CURL_WRITEFUNC_PAUSE; + } +#endif + buffer = g_malloc(sizeof(*buffer) - sizeof(buffer->data) + size); buffer->size = size; buffer->consumed = 0; memcpy(buffer->data, ptr, size); - g_queue_push_tail(c->buffers, buffer); - c->buffered = true; + g_queue_push_tail(c->buffers, buffer); c->base.ready = true; + g_cond_broadcast(c->base.cond); + g_mutex_unlock(c->base.mutex); + return size; } @@ -648,9 +1081,6 @@ static bool input_curl_easy_init(struct input_curl *c, GError **error_r) { CURLcode code; - CURLMcode mcode; - - c->eof = false; c->easy = curl_easy_init(); if (c->easy == NULL) { @@ -659,14 +1089,6 @@ input_curl_easy_init(struct input_curl *c, GError **error_r) return false; } - mcode = curl_multi_add_handle(c->multi, c->easy); - 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, @@ -677,6 +1099,7 @@ input_curl_easy_init(struct input_curl *c, GError **error_r) 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_NETRC, 1); curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5); curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, true); curl_easy_setopt(c->easy, CURLOPT_ERRORBUFFER, c->error); @@ -713,38 +1136,6 @@ input_curl_easy_init(struct input_curl *c, GError **error_r) return true; } -void -input_curl_reinit(struct input_stream *is) -{ - struct input_curl *c = (struct input_curl *)is; - - assert(c->base.plugin == &input_plugin_curl); - assert(c->easy != NULL); - - curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, is); - curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, is); -} - -static bool -input_curl_send_request(struct input_curl *c, GError **error_r) -{ - CURLMcode mcode; - int running_handles; - - do { - mcode = curl_multi_perform(c->multi, &running_handles); - } while (mcode == CURLM_CALL_MULTI_PERFORM); - - if (mcode != CURLM_OK) { - g_set_error(error_r, curl_quark(), mcode, - "curl_multi_perform() failed: %s", - curl_multi_strerror(mcode)); - return false; - } - - return true; -} - static bool input_curl_seek(struct input_stream *is, goffset offset, int whence, GError **error_r) @@ -810,14 +1201,16 @@ input_curl_seek(struct input_stream *is, goffset offset, int whence, /* close the old connection and open a new one */ - input_curl_easy_free(c); + g_mutex_unlock(c->base.mutex); + + input_curl_easy_free_indirect(c); + input_curl_flush_buffers(c); is->offset = offset; if (is->offset == is->size) { /* seek to EOF: simulate empty result; avoid triggering a "416 Requested Range Not Satisfiable" response */ - c->eof = true; return true; } @@ -832,53 +1225,59 @@ input_curl_seek(struct input_stream *is, goffset offset, int whence, curl_easy_setopt(c->easy, CURLOPT_RANGE, c->range); } - ret = input_curl_send_request(c, error_r); - if (!ret) + c->base.ready = false; + + if (!input_curl_easy_add_indirect(c, error_r)) return false; - return input_curl_multi_info_read(c, error_r); + g_mutex_lock(c->base.mutex); + + while (!c->base.ready) + g_cond_wait(c->base.cond, c->base.mutex); + + if (c->postponed_error != NULL) { + g_propagate_error(error_r, c->postponed_error); + c->postponed_error = NULL; + return false; + } + + return true; } static struct input_stream * -input_curl_open(const char *url, GError **error_r) +input_curl_open(const char *url, GMutex *mutex, GCond *cond, + GError **error_r) { + assert(mutex != NULL); + assert(cond != NULL); + struct input_curl *c; - bool ret; if (strncmp(url, "http://", 7) != 0) return NULL; c = g_new0(struct input_curl, 1); - input_stream_init(&c->base, &input_plugin_curl, url); + input_stream_init(&c->base, &input_plugin_curl, url, + mutex, cond); c->url = g_strdup(url); c->buffers = g_queue_new(); - c->multi = curl_multi_init(); - if (c->multi == NULL) { - 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(c, error_r); - if (!ret) { - input_curl_free(c); - return NULL; - } + c->postponed_error = NULL; + +#if LIBCURL_VERSION_NUM >= 0x071200 + c->paused = false; +#endif - ret = input_curl_send_request(c, error_r); - if (!ret) { + if (!input_curl_easy_init(c, error_r)) { input_curl_free(c); return NULL; } - ret = input_curl_multi_info_read(c, error_r); - if (!ret) { + if (!input_curl_easy_add_indirect(c, error_r)) { input_curl_free(c); return NULL; } @@ -893,8 +1292,9 @@ const struct input_plugin input_plugin_curl = { .open = input_curl_open, .close = input_curl_close, + .check = input_curl_check, .tag = input_curl_tag, - .buffer = input_curl_buffer, + .available = input_curl_available, .read = input_curl_read, .eof = input_curl_eof, .seek = input_curl_seek, diff --git a/src/input/curl_input_plugin.h b/src/input/curl_input_plugin.h index be7db4e26..c6e71bf40 100644 --- a/src/input/curl_input_plugin.h +++ b/src/input/curl_input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,12 +24,4 @@ struct input_stream; extern const struct input_plugin input_plugin_curl; -/** - * This is a workaround for an input_stream API deficiency; after - * exchanging the input_stream pointer in input_rewind_open(), this - * function is called to reinitialize CURL's data pointers. - */ -void -input_curl_reinit(struct input_stream *is); - #endif diff --git a/src/input/despotify_input_plugin.c b/src/input/despotify_input_plugin.c new file mode 100644 index 000000000..200a0afd6 --- /dev/null +++ b/src/input/despotify_input_plugin.c @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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/despotify_input_plugin.h" +#include "input_internal.h" +#include "input_plugin.h" +#include "tag.h" +#include "despotify_utils.h" + +#include <glib.h> + +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <despotify.h> + +#include <stdio.h> + +struct input_despotify { + struct input_stream base; + + struct despotify_session *session; + struct ds_track *track; + struct tag *tag; + struct ds_pcm_data pcm; + size_t len_available; + bool eof; +}; + + +static void +refill_buffer(struct input_despotify *ctx) +{ + /* Wait until there is data */ + while (1) { + int rc = despotify_get_pcm(ctx->session, &ctx->pcm); + + if (rc == 0 && ctx->pcm.len) { + ctx->len_available = ctx->pcm.len; + break; + } + if (ctx->eof == true) + break; + + if (rc < 0) { + g_debug("despotify_get_pcm error\n"); + ctx->eof = true; + break; + } + + /* Wait a while until next iteration */ + usleep(50 * 1000); + } +} + +static void callback(G_GNUC_UNUSED struct despotify_session* ds, + int sig, G_GNUC_UNUSED void* data, void* callback_data) +{ + struct input_despotify *ctx = (struct input_despotify *)callback_data; + + switch (sig) { + case DESPOTIFY_NEW_TRACK: + break; + + case DESPOTIFY_TIME_TELL: + break; + + case DESPOTIFY_TRACK_PLAY_ERROR: + g_debug("Track play error\n"); + ctx->eof = true; + ctx->len_available = 0; + break; + + case DESPOTIFY_END_OF_PLAYLIST: + ctx->eof = true; + g_debug("End of playlist: %d\n", ctx->eof); + break; + } +} + + +static struct input_stream * +input_despotify_open(const char *url, + GMutex *mutex, GCond *cond, + G_GNUC_UNUSED GError **error_r) +{ + struct input_despotify *ctx; + struct despotify_session *session; + struct ds_link *ds_link; + struct ds_track *track; + + if (!g_str_has_prefix(url, "spt://")) + return NULL; + + session = mpd_despotify_get_session(); + if (!session) + return NULL; + + ds_link = despotify_link_from_uri(url + 6); + if (!ds_link) { + g_debug("Can't find %s\n", url); + return NULL; + } + if (ds_link->type != LINK_TYPE_TRACK) { + despotify_free_link(ds_link); + return NULL; + } + + ctx = g_new(struct input_despotify, 1); + memset(ctx, 0, sizeof(*ctx)); + + track = despotify_link_get_track(session, ds_link); + despotify_free_link(ds_link); + if (!track) { + g_free(ctx); + return NULL; + } + + input_stream_init(&ctx->base, &input_plugin_despotify, url, + mutex, cond); + ctx->session = session; + ctx->track = track; + ctx->tag = mpd_despotify_tag_from_track(track); + ctx->eof = false; + /* Despotify outputs pcm data */ + ctx->base.mime = g_strdup("audio/x-mpd-cdda-pcm"); + ctx->base.ready = true; + + if (!mpd_despotify_register_callback(callback, ctx)) { + despotify_free_link(ds_link); + + return NULL; + } + + if (despotify_play(ctx->session, ctx->track, false) == false) { + despotify_free_track(ctx->track); + g_free(ctx); + return NULL; + } + + return &ctx->base; +} + +static size_t +input_despotify_read(struct input_stream *is, void *ptr, size_t size, + G_GNUC_UNUSED GError **error_r) +{ + struct input_despotify *ctx = (struct input_despotify *)is; + size_t to_cpy = size; + + if (ctx->len_available == 0) + refill_buffer(ctx); + + if (ctx->len_available < size) + to_cpy = ctx->len_available; + memcpy(ptr, ctx->pcm.buf, to_cpy); + ctx->len_available -= to_cpy; + + is->offset += to_cpy; + + return to_cpy; +} + +static void +input_despotify_close(struct input_stream *is) +{ + struct input_despotify *ctx = (struct input_despotify *)is; + + if (ctx->tag != NULL) + tag_free(ctx->tag); + + mpd_despotify_unregister_callback(callback); + despotify_free_track(ctx->track); + input_stream_deinit(&ctx->base); + g_free(ctx); +} + +static bool +input_despotify_eof(struct input_stream *is) +{ + struct input_despotify *ctx = (struct input_despotify *)is; + + return ctx->eof; +} + +static bool +input_despotify_seek(G_GNUC_UNUSED struct input_stream *is, + G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence, + G_GNUC_UNUSED GError **error_r) +{ + return false; +} + +static struct tag * +input_despotify_tag(struct input_stream *is) +{ + struct input_despotify *ctx = (struct input_despotify *)is; + struct tag *tag = ctx->tag; + + ctx->tag = NULL; + + return tag; +} + +const struct input_plugin input_plugin_despotify = { + .name = "spt", + .open = input_despotify_open, + .close = input_despotify_close, + .read = input_despotify_read, + .eof = input_despotify_eof, + .seek = input_despotify_seek, + .tag = input_despotify_tag, +}; diff --git a/src/input/despotify_input_plugin.h b/src/input/despotify_input_plugin.h new file mode 100644 index 000000000..4c070d882 --- /dev/null +++ b/src/input/despotify_input_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 INPUT_DESPOTIFY_H +#define INPUT_DESPOTIFY_H + +extern const struct input_plugin input_plugin_despotify; + +#endif diff --git a/src/input/ffmpeg_input_plugin.c b/src/input/ffmpeg_input_plugin.c index 8ff66c5b4..d71b3d4c0 100644 --- a/src/input/ffmpeg_input_plugin.c +++ b/src/input/ffmpeg_input_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,15 +19,11 @@ #include "config.h" #include "input/ffmpeg_input_plugin.h" +#include "input_internal.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" @@ -71,20 +67,20 @@ input_ffmpeg_init(G_GNUC_UNUSED const struct config_param *param, { av_register_all(); -#if LIBAVFORMAT_VERSION_MAJOR >= 52 /* disable this plugin if there's no registered protocol */ if (!input_ffmpeg_supported()) { 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) +input_ffmpeg_open(const char *uri, + GMutex *mutex, GCond *cond, + GError **error_r) { struct input_ffmpeg *i; @@ -97,10 +93,13 @@ input_ffmpeg_open(const char *uri, GError **error_r) return NULL; i = g_new(struct input_ffmpeg, 1); - input_stream_init(&i->base, &input_plugin_ffmpeg, uri); + input_stream_init(&i->base, &input_plugin_ffmpeg, uri, + mutex, cond); -#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,1,0) int ret = avio_open(&i->h, uri, AVIO_FLAG_READ); +#elif LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,0,0) + int ret = avio_open(&i->h, uri, AVIO_RDONLY); #else int ret = url_open(&i->h, uri, URL_RDONLY); #endif diff --git a/src/input/ffmpeg_input_plugin.h b/src/input/ffmpeg_input_plugin.h index ff87064be..393836ca5 100644 --- a/src/input/ffmpeg_input_plugin.h +++ b/src/input/ffmpeg_input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/file_input_plugin.c b/src/input/file_input_plugin.c index 3646c656e..5ee3f200b 100644 --- a/src/input/file_input_plugin.c +++ b/src/input/file_input_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,6 +19,7 @@ #include "config.h" /* must be first for large file support */ #include "input/file_input_plugin.h" +#include "input_internal.h" #include "input_plugin.h" #include "fd_util.h" #include "open.h" @@ -45,14 +46,16 @@ file_quark(void) } static struct input_stream * -input_file_open(const char *filename, GError **error_r) +input_file_open(const char *filename, + GMutex *mutex, GCond *cond, + GError **error_r) { int fd, ret; struct stat st; struct file_input_stream *fis; if (!g_path_is_absolute(filename)) - return false; + return NULL; fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0); if (fd < 0) { @@ -60,7 +63,7 @@ input_file_open(const char *filename, GError **error_r) g_set_error(error_r, file_quark(), errno, "Failed to open \"%s\": %s", filename, g_strerror(errno)); - return false; + return NULL; } ret = fstat(fd, &st); @@ -69,14 +72,14 @@ input_file_open(const char *filename, GError **error_r) "Failed to stat \"%s\": %s", filename, g_strerror(errno)); close(fd); - return false; + return NULL; } if (!S_ISREG(st.st_mode)) { g_set_error(error_r, file_quark(), 0, "Not a regular file: %s", filename); close(fd); - return false; + return NULL; } #ifdef POSIX_FADV_SEQUENTIAL @@ -84,7 +87,8 @@ input_file_open(const char *filename, GError **error_r) #endif fis = g_new(struct file_input_stream, 1); - input_stream_init(&fis->base, &input_plugin_file, filename); + input_stream_init(&fis->base, &input_plugin_file, filename, + mutex, cond); fis->base.size = st.st_size; fis->base.seekable = true; diff --git a/src/input/file_input_plugin.h b/src/input/file_input_plugin.h index 40340e8bd..f24769d57 100644 --- a/src/input/file_input_plugin.h +++ b/src/input/file_input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/mms_input_plugin.c b/src/input/mms_input_plugin.c index 834d111b8..cff15125b 100644 --- a/src/input/mms_input_plugin.c +++ b/src/input/mms_input_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,6 +19,7 @@ #include "config.h" #include "input/mms_input_plugin.h" +#include "input_internal.h" #include "input_plugin.h" #include <glib.h> @@ -45,7 +46,9 @@ mms_quark(void) } static struct input_stream * -input_mms_open(const char *url, GError **error_r) +input_mms_open(const char *url, + GMutex *mutex, GCond *cond, + GError **error_r) { struct input_mms *m; @@ -56,7 +59,8 @@ input_mms_open(const char *url, GError **error_r) return NULL; m = g_new(struct input_mms, 1); - input_stream_init(&m->base, &input_plugin_mms, url); + input_stream_init(&m->base, &input_plugin_mms, url, + mutex, cond); m->mms = mmsx_connect(NULL, NULL, url, 128 * 1024); if (m->mms == NULL) { diff --git a/src/input/mms_input_plugin.h b/src/input/mms_input_plugin.h index 2e10cfbb9..d6aa593f2 100644 --- a/src/input/mms_input_plugin.h +++ b/src/input/mms_input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 f0d533bc8..cf06fc57b 100644 --- a/src/input/rewind_input_plugin.c +++ b/src/input/rewind_input_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,7 +19,7 @@ #include "config.h" #include "input/rewind_input_plugin.h" -#include "input/curl_input_plugin.h" +#include "input_internal.h" #include "input_plugin.h" #include "tag.h" @@ -107,6 +107,23 @@ input_rewind_close(struct input_stream *is) g_free(r); } +static bool +input_rewind_check(struct input_stream *is, GError **error_r) +{ + struct input_rewind *r = (struct input_rewind *)is; + + return input_stream_check(r->input, error_r); +} + +static void +input_rewind_update(struct input_stream *is) +{ + struct input_rewind *r = (struct input_rewind *)is; + + if (!reading_from_buffer(r)) + copy_attributes(r); +} + static struct tag * input_rewind_tag(struct input_stream *is) { @@ -115,16 +132,12 @@ input_rewind_tag(struct input_stream *is) return input_stream_tag(r->input); } -static int -input_rewind_buffer(struct input_stream *is, GError **error_r) +static bool +input_rewind_available(struct input_stream *is) { struct input_rewind *r = (struct input_rewind *)is; - int ret = input_stream_buffer(r->input, error_r); - if (ret < 0 || !reading_from_buffer(r)) - copy_attributes(r); - - return ret; + return input_stream_available(r->input); } static size_t @@ -212,8 +225,10 @@ input_rewind_seek(struct input_stream *is, goffset offset, int whence, static const struct input_plugin rewind_input_plugin = { .close = input_rewind_close, + .check = input_rewind_check, + .update = input_rewind_update, .tag = input_rewind_tag, - .buffer = input_rewind_buffer, + .available = input_rewind_available, .read = input_rewind_read, .eof = input_rewind_eof, .seek = input_rewind_seek, @@ -232,7 +247,8 @@ input_rewind_open(struct input_stream *is) return is; c = g_new(struct input_rewind, 1); - input_stream_init(&c->base, &rewind_input_plugin, is->uri); + input_stream_init(&c->base, &rewind_input_plugin, is->uri, + is->mutex, is->cond); c->tail = 0; c->input = is; diff --git a/src/input/rewind_input_plugin.h b/src/input/rewind_input_plugin.h index 23d25d94d..83abe257a 100644 --- a/src/input/rewind_input_plugin.h +++ b/src/input/rewind_input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/soup_input_plugin.c b/src/input/soup_input_plugin.c new file mode 100644 index 000000000..fc903b48c --- /dev/null +++ b/src/input/soup_input_plugin.c @@ -0,0 +1,473 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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/soup_input_plugin.h" +#include "input_internal.h" +#include "input_plugin.h" +#include "io_thread.h" +#include "conf.h" + +#include <libsoup/soup-uri.h> +#include <libsoup/soup-session-async.h> + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "input_soup" + +/** + * Do not buffer more than this number of bytes. It should be a + * reasonable limit that doesn't make low-end machines suffer too + * much, but doesn't cause stuttering on high-latency lines. + */ +static const size_t SOUP_MAX_BUFFERED = 512 * 1024; + +/** + * Resume the stream at this number of bytes after it has been paused. + */ +static const size_t SOUP_RESUME_AT = 384 * 1024; + +static SoupURI *soup_proxy; +static SoupSession *soup_session; + +struct input_soup { + struct input_stream base; + + SoupMessage *msg; + + GQueue *buffers; + + size_t current_consumed; + + size_t total_buffered; + + bool alive, pause, eof; + + /** + * Set when the session callback has been invoked, when it is + * safe to free this object. + */ + bool completed; + + GError *postponed_error; +}; + +static inline GQuark +soup_quark(void) +{ + return g_quark_from_static_string("soup"); +} + +static bool +input_soup_init(const struct config_param *param, GError **error_r) +{ + assert(soup_proxy == NULL); + assert(soup_session == NULL); + + g_type_init(); + + const char *proxy = config_get_block_string(param, "proxy", NULL); + + if (proxy != NULL) { + soup_proxy = soup_uri_new(proxy); + if (soup_proxy == NULL) { + g_set_error(error_r, soup_quark(), 0, + "failed to parse proxy setting"); + return false; + } + } + + soup_session = + soup_session_async_new_with_options(SOUP_SESSION_PROXY_URI, + soup_proxy, + SOUP_SESSION_ASYNC_CONTEXT, + io_thread_context(), + NULL); + + return true; +} + +static void +input_soup_finish(void) +{ + assert(soup_session != NULL); + + soup_session_abort(soup_session); + g_object_unref(G_OBJECT(soup_session)); + + if (soup_proxy != NULL) + soup_uri_free(soup_proxy); +} + +/** + * Copy the error from the SoupMessage object to + * input_soup::postponed_error. + * + * @return true if there was no error + */ +static bool +input_soup_copy_error(struct input_soup *s, const SoupMessage *msg) +{ + if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) + return true; + + if (msg->status_code == SOUP_STATUS_CANCELLED) + /* failure, but don't generate a GError, because this + status was caused by _close() */ + return false; + + if (s->postponed_error != NULL) + /* there's already a GError, don't overwrite it */ + return false; + + if (SOUP_STATUS_IS_TRANSPORT_ERROR(msg->status_code)) + s->postponed_error = + g_error_new(soup_quark(), msg->status_code, + "HTTP client error: %s", + msg->reason_phrase); + else + s->postponed_error = + g_error_new(soup_quark(), msg->status_code, + "got HTTP status: %d %s", + msg->status_code, msg->reason_phrase); + + return false; +} + +static void +input_soup_session_callback(G_GNUC_UNUSED SoupSession *session, + SoupMessage *msg, gpointer user_data) +{ + struct input_soup *s = user_data; + + assert(msg == s->msg); + assert(!s->completed); + + g_mutex_lock(s->base.mutex); + + if (!s->base.ready) + input_soup_copy_error(s, msg); + + s->base.ready = true; + s->alive = false; + s->completed = true; + + g_cond_broadcast(s->base.cond); + g_mutex_unlock(s->base.mutex); +} + +static void +input_soup_got_headers(SoupMessage *msg, gpointer user_data) +{ + struct input_soup *s = user_data; + + g_mutex_lock(s->base.mutex); + + if (!input_soup_copy_error(s, msg)) { + g_mutex_unlock(s->base.mutex); + + soup_session_cancel_message(soup_session, msg, + SOUP_STATUS_CANCELLED); + return; + } + + s->base.ready = true; + g_cond_broadcast(s->base.cond); + g_mutex_unlock(s->base.mutex); + + soup_message_body_set_accumulate(msg->response_body, false); +} + +static void +input_soup_got_chunk(SoupMessage *msg, SoupBuffer *chunk, gpointer user_data) +{ + struct input_soup *s = user_data; + + assert(msg == s->msg); + + g_mutex_lock(s->base.mutex); + + g_queue_push_tail(s->buffers, soup_buffer_copy(chunk)); + s->total_buffered += chunk->length; + + if (s->total_buffered >= SOUP_MAX_BUFFERED && !s->pause) { + s->pause = true; + soup_session_pause_message(soup_session, msg); + } + + g_cond_broadcast(s->base.cond); + g_mutex_unlock(s->base.mutex); +} + +static void +input_soup_got_body(G_GNUC_UNUSED SoupMessage *msg, gpointer user_data) +{ + struct input_soup *s = user_data; + + assert(msg == s->msg); + + g_mutex_lock(s->base.mutex); + + s->base.ready = true; + s->eof = true; + s->alive = false; + + g_cond_broadcast(s->base.cond); + g_mutex_unlock(s->base.mutex); +} + +static bool +input_soup_wait_data(struct input_soup *s) +{ + while (true) { + if (s->eof) + return true; + + if (!s->alive) + return false; + + if (!g_queue_is_empty(s->buffers)) + return true; + + assert(s->current_consumed == 0); + + g_cond_wait(s->base.cond, s->base.mutex); + } +} + +static gpointer +input_soup_queue(gpointer data) +{ + struct input_soup *s = data; + + soup_session_queue_message(soup_session, s->msg, + input_soup_session_callback, s); + + return NULL; +} + +static struct input_stream * +input_soup_open(const char *uri, + GMutex *mutex, GCond *cond, + G_GNUC_UNUSED GError **error_r) +{ + if (strncmp(uri, "http://", 7) != 0) + return NULL; + + struct input_soup *s = g_new(struct input_soup, 1); + input_stream_init(&s->base, &input_plugin_soup, uri, + mutex, cond); + + s->buffers = g_queue_new(); + s->current_consumed = 0; + s->total_buffered = 0; + +#if GCC_CHECK_VERSION(4,6) +#pragma GCC diagnostic push + /* the libsoup macro SOUP_METHOD_GET discards the "const" + attribute of the g_intern_static_string() return value; + don't make the gcc warning fatal: */ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + + s->msg = soup_message_new(SOUP_METHOD_GET, uri); + +#if GCC_CHECK_VERSION(4,6) +#pragma GCC diagnostic pop +#endif + + soup_message_set_flags(s->msg, SOUP_MESSAGE_NO_REDIRECT); + + soup_message_headers_append(s->msg->request_headers, "User-Agent", + "Music Player Daemon " VERSION); + + g_signal_connect(s->msg, "got-headers", + G_CALLBACK(input_soup_got_headers), s); + g_signal_connect(s->msg, "got-chunk", + G_CALLBACK(input_soup_got_chunk), s); + g_signal_connect(s->msg, "got-body", + G_CALLBACK(input_soup_got_body), s); + + s->alive = true; + s->pause = false; + s->eof = false; + s->completed = false; + s->postponed_error = NULL; + + io_thread_call(input_soup_queue, s); + + return &s->base; +} + +static gpointer +input_soup_cancel(gpointer data) +{ + struct input_soup *s = data; + + if (!s->completed) + soup_session_cancel_message(soup_session, s->msg, + SOUP_STATUS_CANCELLED); + + return NULL; +} + +static void +input_soup_close(struct input_stream *is) +{ + struct input_soup *s = (struct input_soup *)is; + + g_mutex_lock(s->base.mutex); + + if (!s->completed) { + /* the messages's session callback hasn't been invoked + yet; cancel it and wait for completion */ + + g_mutex_unlock(s->base.mutex); + + io_thread_call(input_soup_cancel, s); + + g_mutex_lock(s->base.mutex); + while (!s->completed) + g_cond_wait(s->base.cond, s->base.mutex); + } + + g_mutex_unlock(s->base.mutex); + + SoupBuffer *buffer; + while ((buffer = g_queue_pop_head(s->buffers)) != NULL) + soup_buffer_free(buffer); + g_queue_free(s->buffers); + + if (s->postponed_error != NULL) + g_error_free(s->postponed_error); + + input_stream_deinit(&s->base); + g_free(s); +} + +static bool +input_soup_check(struct input_stream *is, GError **error_r) +{ + struct input_soup *s = (struct input_soup *)is; + + bool success = s->postponed_error == NULL; + if (!success) { + g_propagate_error(error_r, s->postponed_error); + s->postponed_error = NULL; + } + + return success; +} + +static bool +input_soup_available(struct input_stream *is) +{ + struct input_soup *s = (struct input_soup *)is; + + return s->eof || !s->alive || !g_queue_is_empty(s->buffers); +} + +static size_t +input_soup_read(struct input_stream *is, void *ptr, size_t size, + G_GNUC_UNUSED GError **error_r) +{ + struct input_soup *s = (struct input_soup *)is; + + if (!input_soup_wait_data(s)) { + assert(!s->alive); + + if (s->postponed_error != NULL) { + g_propagate_error(error_r, s->postponed_error); + s->postponed_error = NULL; + } else + g_set_error_literal(error_r, soup_quark(), 0, + "HTTP failure"); + return 0; + } + + char *p0 = ptr, *p = p0, *p_end = p0 + size; + + while (p < p_end) { + SoupBuffer *buffer = g_queue_pop_head(s->buffers); + if (buffer == NULL) { + assert(s->current_consumed == 0); + break; + } + + assert(s->current_consumed < buffer->length); + assert(s->total_buffered >= buffer->length); + + const char *q = buffer->data; + q += s->current_consumed; + + size_t remaining = buffer->length - s->current_consumed; + size_t nbytes = p_end - p; + if (nbytes > remaining) + nbytes = remaining; + + memcpy(p, q, nbytes); + p += nbytes; + + s->current_consumed += remaining; + if (s->current_consumed >= buffer->length) { + /* done with this buffer */ + s->total_buffered -= buffer->length; + soup_buffer_free(buffer); + s->current_consumed = 0; + } else { + /* partial read */ + assert(p == p_end); + + g_queue_push_head(s->buffers, buffer); + } + } + + if (s->pause && s->total_buffered < SOUP_RESUME_AT) { + s->pause = false; + soup_session_unpause_message(soup_session, s->msg); + } + + size_t nbytes = p - p0; + s->base.offset += nbytes; + + return nbytes; +} + +static bool +input_soup_eof(G_GNUC_UNUSED struct input_stream *is) +{ + struct input_soup *s = (struct input_soup *)is; + + return !s->alive && g_queue_is_empty(s->buffers); +} + +const struct input_plugin input_plugin_soup = { + .name = "soup", + .init = input_soup_init, + .finish = input_soup_finish, + + .open = input_soup_open, + .close = input_soup_close, + .check = input_soup_check, + .available = input_soup_available, + .read = input_soup_read, + .eof = input_soup_eof, +}; diff --git a/src/input/soup_input_plugin.h b/src/input/soup_input_plugin.h new file mode 100644 index 000000000..689b2d971 --- /dev/null +++ b/src/input/soup_input_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SOUP_H +#define MPD_INPUT_SOUP_H + +extern const struct input_plugin input_plugin_soup; + +#endif diff --git a/src/input_init.c b/src/input_init.c index 1438c3e52..cf5affb4e 100644 --- a/src/input_init.c +++ b/src/input_init.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,6 +24,7 @@ #include "conf.h" #include "glib_compat.h" +#include <assert.h> #include <string.h> static inline GQuark @@ -67,6 +68,11 @@ input_stream_global_init(GError **error_r) for (unsigned i = 0; input_plugins[i] != NULL; ++i) { const struct input_plugin *plugin = input_plugins[i]; + + assert(plugin->name != NULL); + assert(*plugin->name != 0); + assert(plugin->open != NULL); + const struct config_param *param = input_plugin_config(plugin->name, &error); if (param == NULL && error != NULL) { diff --git a/src/input_init.h b/src/input_init.h index eded15fa9..ad92cda08 100644 --- a/src/input_init.h +++ b/src/input_init.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,7 +28,7 @@ /** * Initializes this library and all input_stream implementations. * - * @param error_r location to store the error occuring, or NULL to + * @param error_r location to store the error occurring, or NULL to * ignore errors */ bool diff --git a/src/input_internal.c b/src/input_internal.c new file mode 100644 index 000000000..92a71856e --- /dev/null +++ b/src/input_internal.c @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_internal.h" +#include "input_stream.h" + +#include <assert.h> + +void +input_stream_init(struct input_stream *is, const struct input_plugin *plugin, + const char *uri, GMutex *mutex, GCond *cond) +{ + assert(is != NULL); + assert(plugin != NULL); + assert(uri != NULL); + + is->plugin = plugin; + is->uri = g_strdup(uri); + is->mutex = mutex; + is->cond = cond; + is->ready = false; + is->seekable = false; + is->size = -1; + is->offset = 0; + is->mime = NULL; +} + +void +input_stream_deinit(struct input_stream *is) +{ + assert(is != NULL); + assert(is->plugin != NULL); + + g_free(is->uri); + g_free(is->mime); +} + +void +input_stream_signal_client(struct input_stream *is) +{ + if (is->cond != NULL) + g_cond_broadcast(is->cond); +} + +void +input_stream_set_ready(struct input_stream *is) +{ + g_mutex_lock(is->mutex); + + if (!is->ready) { + is->ready = true; + input_stream_signal_client(is); + } + + g_mutex_unlock(is->mutex); +} diff --git a/src/input_internal.h b/src/input_internal.h new file mode 100644 index 000000000..d95142e46 --- /dev/null +++ b/src/input_internal.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_INTERNAL_H +#define MPD_INPUT_INTERNAL_H + +#include "check.h" + +#include <glib.h> + +struct input_stream; +struct input_plugin; + +void +input_stream_init(struct input_stream *is, const struct input_plugin *plugin, + const char *uri, GMutex *mutex, GCond *cond); + +void +input_stream_deinit(struct input_stream *is); + +void +input_stream_signal_client(struct input_stream *is); + +void +input_stream_set_ready(struct input_stream *is); + +#endif diff --git a/src/input_plugin.h b/src/input_plugin.h index 10be48dbb..6b0c77c85 100644 --- a/src/input_plugin.h +++ b/src/input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -35,7 +35,7 @@ struct input_plugin { /** * Global initialization. This method is called when MPD starts. * - * @param error_r location to store the error occuring, or + * @param error_r location to store the error occurring, or * NULL to ignore errors * @return true on success, false if the plugin should be * disabled @@ -48,11 +48,37 @@ struct input_plugin { */ void (*finish)(void); - struct input_stream *(*open)(const char *uri, GError **error_r); + struct input_stream *(*open)(const char *uri, + GMutex *mutex, GCond *cond, + GError **error_r); void (*close)(struct input_stream *is); + /** + * Check for errors that may have occurred in the I/O thread. + * May be unimplemented for synchronous plugins. + * + * @return false on error + */ + bool (*check)(struct input_stream *is, GError **error_r); + + /** + * Update the public attributes. Call before access. Can be + * NULL if the plugin always keeps its attributes up to date. + */ + void (*update)(struct input_stream *is); + struct tag *(*tag)(struct input_stream *is); - int (*buffer)(struct input_stream *is, GError **error_r); + + /** + * Returns true if the next read operation will not block: + * either data is available, or end-of-stream has been + * reached, or an error has occurred. + * + * If this method is unimplemented, then it is assumed that + * reading will never block. + */ + bool (*available)(struct input_stream *is); + size_t (*read)(struct input_stream *is, void *ptr, size_t size, GError **error_r); bool (*eof)(struct input_stream *is); diff --git a/src/input_registry.c b/src/input_registry.c index 0b9b47d10..5987d5da2 100644 --- a/src/input_registry.c +++ b/src/input_registry.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -29,6 +29,10 @@ #include "input/curl_input_plugin.h" #endif +#ifdef ENABLE_SOUP +#include "input/soup_input_plugin.h" +#endif + #ifdef HAVE_FFMPEG #include "input/ffmpeg_input_plugin.h" #endif @@ -37,6 +41,14 @@ #include "input/mms_input_plugin.h" #endif +#ifdef ENABLE_CDIO_PARANOIA +#include "input/cdio_paranoia_input_plugin.h" +#endif + +#ifdef ENABLE_DESPOTIFY +#include "input/despotify_input_plugin.h" +#endif + #include <glib.h> const struct input_plugin *const input_plugins[] = { @@ -47,12 +59,21 @@ const struct input_plugin *const input_plugins[] = { #ifdef ENABLE_CURL &input_plugin_curl, #endif +#ifdef ENABLE_SOUP + &input_plugin_soup, +#endif #ifdef HAVE_FFMPEG &input_plugin_ffmpeg, #endif #ifdef ENABLE_MMS &input_plugin_mms, #endif +#ifdef ENABLE_CDIO_PARANOIA + &input_plugin_cdio_paranoia, +#endif +#ifdef ENABLE_DESPOTIFY + &input_plugin_despotify, +#endif NULL }; diff --git a/src/input_registry.h b/src/input_registry.h index e85d6be8e..a1b057469 100644 --- a/src/input_registry.h +++ b/src/input_registry.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_stream.c b/src/input_stream.c index e769adb92..60a1559ba 100644 --- a/src/input_stream.c +++ b/src/input_stream.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -33,10 +33,13 @@ input_quark(void) } struct input_stream * -input_stream_open(const char *url, GError **error_r) +input_stream_open(const char *url, + GMutex *mutex, GCond *cond, + GError **error_r) { GError *error = NULL; + assert(mutex != NULL); assert(error_r == NULL || *error_r == NULL); for (unsigned i = 0; input_plugins[i] != NULL; ++i) { @@ -46,7 +49,7 @@ input_stream_open(const char *url, GError **error_r) if (!input_plugins_enabled[i]) continue; - is = plugin->open(url, &error); + is = plugin->open(url, mutex, cond, &error); if (is != NULL) { assert(is->plugin != NULL); assert(is->plugin->close != NULL); @@ -64,29 +67,131 @@ input_stream_open(const char *url, GError **error_r) } g_set_error(error_r, input_quark(), 0, "Unrecognized URI"); - return false; + return NULL; +} + +bool +input_stream_check(struct input_stream *is, GError **error_r) +{ + assert(is != NULL); + assert(is->plugin != NULL); + + return is->plugin->check == NULL || + is->plugin->check(is, error_r); +} + +void +input_stream_update(struct input_stream *is) +{ + assert(is != NULL); + assert(is->plugin != NULL); + + if (is->plugin->update != NULL) + is->plugin->update(is); +} + +void +input_stream_wait_ready(struct input_stream *is) +{ + assert(is != NULL); + assert(is->mutex != NULL); + assert(is->cond != NULL); + + while (true) { + input_stream_update(is); + if (is->ready) + break; + + g_cond_wait(is->cond, is->mutex); + } +} + +void +input_stream_lock_wait_ready(struct input_stream *is) +{ + assert(is != NULL); + assert(is->mutex != NULL); + assert(is->cond != NULL); + + g_mutex_lock(is->mutex); + input_stream_wait_ready(is); + g_mutex_unlock(is->mutex); } bool input_stream_seek(struct input_stream *is, goffset offset, int whence, GError **error_r) { + assert(is != NULL); + assert(is->plugin != NULL); + if (is->plugin->seek == NULL) return false; return is->plugin->seek(is, offset, whence, error_r); } +bool +input_stream_lock_seek(struct input_stream *is, goffset offset, int whence, + GError **error_r) +{ + assert(is != NULL); + assert(is->plugin != NULL); + + if (is->plugin->seek == NULL) + return false; + + if (is->mutex == NULL) + /* no locking */ + return input_stream_seek(is, offset, whence, error_r); + + g_mutex_lock(is->mutex); + bool success = input_stream_seek(is, offset, whence, error_r); + g_mutex_unlock(is->mutex); + return success; +} + struct tag * input_stream_tag(struct input_stream *is) { assert(is != NULL); + assert(is->plugin != NULL); return is->plugin->tag != NULL ? is->plugin->tag(is) : NULL; } +struct tag * +input_stream_lock_tag(struct input_stream *is) +{ + assert(is != NULL); + assert(is->plugin != NULL); + + if (is->plugin->tag == NULL) + return false; + + if (is->mutex == NULL) + /* no locking */ + return input_stream_tag(is); + + g_mutex_lock(is->mutex); + struct tag *tag = input_stream_tag(is); + g_mutex_unlock(is->mutex); + return tag; +} + +bool +input_stream_available(struct input_stream *is) +{ + assert(is != NULL); + assert(is->plugin != NULL); + + return is->plugin->available != NULL + ? is->plugin->available(is) + : true; +} + size_t input_stream_read(struct input_stream *is, void *ptr, size_t size, GError **error_r) @@ -97,6 +202,23 @@ input_stream_read(struct input_stream *is, void *ptr, size_t size, return is->plugin->read(is, ptr, size, error_r); } +size_t +input_stream_lock_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) +{ + assert(ptr != NULL); + assert(size > 0); + + if (is->mutex == NULL) + /* no locking */ + return input_stream_read(is, ptr, size, error_r); + + g_mutex_lock(is->mutex); + size_t nbytes = input_stream_read(is, ptr, size, error_r); + g_mutex_unlock(is->mutex); + return nbytes; +} + void input_stream_close(struct input_stream *is) { is->plugin->close(is); @@ -107,11 +229,19 @@ bool input_stream_eof(struct input_stream *is) return is->plugin->eof(is); } -int -input_stream_buffer(struct input_stream *is, GError **error_r) +bool +input_stream_lock_eof(struct input_stream *is) { - if (is->plugin->buffer == NULL) - return 0; + assert(is != NULL); + assert(is->plugin != NULL); + + if (is->mutex == NULL) + /* no locking */ + return input_stream_eof(is); - return is->plugin->buffer(is, error_r); + g_mutex_lock(is->mutex); + bool eof = input_stream_eof(is); + g_mutex_unlock(is->mutex); + return eof; } + diff --git a/src/input_stream.h b/src/input_stream.h index 056d008a7..6a10831d2 100644 --- a/src/input_stream.h +++ b/src/input_stream.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,6 +21,7 @@ #define MPD_INPUT_STREAM_H #include "check.h" +#include "gcc.h" #include <glib.h> @@ -45,6 +46,26 @@ struct input_stream { char *uri; /** + * A mutex that protects the mutable attributes of this object + * and its implementation. It must be locked before calling + * any of the public methods. + * + * This object is allocated by the client, and the client is + * responsible for freeing it. + */ + GMutex *mutex; + + /** + * A cond that gets signalled when the state of this object + * changes from the I/O thread. The client of this object may + * wait on it. Optional, may be NULL. + * + * This object is allocated by the client, and the client is + * responsible for freeing it. + */ + GCond *cond; + + /** * indicates whether the stream is ready for reading and * whether the other attributes in this struct are valid */ @@ -71,88 +92,180 @@ struct input_stream { char *mime; }; +/** + * Opens a new input stream. You may not access it until the "ready" + * flag is set. + * + * @param mutex a mutex that is used to protect this object; must be + * locked before calling any of the public methods + * @param cond a cond that gets signalled when the state of + * this object changes; may be NULL if the caller doesn't want to get + * notifications + * @return an #input_stream object on success, NULL on error + */ +gcc_nonnull(1, 2) +G_GNUC_MALLOC +struct input_stream * +input_stream_open(const char *uri, + GMutex *mutex, GCond *cond, + GError **error_r); + +/** + * Close the input stream and free resources. + * + * The caller must not lock the mutex. + */ +gcc_nonnull(1) +void +input_stream_close(struct input_stream *is); + +gcc_nonnull(1) static inline void -input_stream_init(struct input_stream *is, const struct input_plugin *plugin, - const char *uri) +input_stream_lock(struct input_stream *is) { - is->plugin = plugin; - is->uri = g_strdup(uri); - is->ready = false; - is->seekable = false; - is->size = -1; - is->offset = 0; - is->mime = NULL; + g_mutex_lock(is->mutex); } +gcc_nonnull(1) static inline void -input_stream_deinit(struct input_stream *is) +input_stream_unlock(struct input_stream *is) { - g_free(is->uri); - g_free(is->mime); + g_mutex_unlock(is->mutex); } /** - * Opens a new input stream. You may not access it until the "ready" - * flag is set. + * Check for errors that may have occurred in the I/O thread. * - * @return an #input_stream object on success, NULL on error + * @return false on error */ -struct input_stream * -input_stream_open(const char *uri, GError **error_r); +gcc_nonnull(1) +bool +input_stream_check(struct input_stream *is, GError **error_r); /** - * Close the input stream and free resources. + * Update the public attributes. Call before accessing attributes + * such as "ready" or "offset". */ +gcc_nonnull(1) void -input_stream_close(struct input_stream *is); +input_stream_update(struct input_stream *is); + +/** + * Wait until the stream becomes ready. + * + * The caller must lock the mutex. + */ +gcc_nonnull(1) +void +input_stream_wait_ready(struct input_stream *is); + +/** + * Wrapper for input_stream_wait_locked() which locks and unlocks the + * mutex; the caller must not be holding it already. + */ +gcc_nonnull(1) +void +input_stream_lock_wait_ready(struct input_stream *is); /** * Seeks to the specified position in the stream. This will most * likely fail if the "seekable" flag is false. * + * The caller must lock the mutex. + * * @param is the input_stream object * @param offset the relative offset * @param whence the base of the seek, one of SEEK_SET, SEEK_CUR, SEEK_END */ +gcc_nonnull(1) bool input_stream_seek(struct input_stream *is, goffset offset, int whence, GError **error_r); /** + * Wrapper for input_stream_seek() which locks and unlocks the + * mutex; the caller must not be holding it already. + */ +gcc_nonnull(1) +bool +input_stream_lock_seek(struct input_stream *is, goffset offset, int whence, + GError **error_r); + +/** * Returns true if the stream has reached end-of-file. + * + * The caller must lock the mutex. */ +gcc_nonnull(1) +G_GNUC_PURE bool input_stream_eof(struct input_stream *is); /** + * Wrapper for input_stream_eof() which locks and unlocks the mutex; + * the caller must not be holding it already. + */ +gcc_nonnull(1) +G_GNUC_PURE +bool +input_stream_lock_eof(struct input_stream *is); + +/** * Reads the tag from the stream. * + * The caller must lock the mutex. + * * @return a tag object which must be freed with tag_free(), or NULL * if the tag has not changed since the last call */ +gcc_nonnull(1) +G_GNUC_MALLOC struct tag * input_stream_tag(struct input_stream *is); /** - * Reads some of the stream into its buffer. The following return - * codes are defined: -1 = error, 1 = something was buffered, 0 = - * nothing was buffered. + * Wrapper for input_stream_tag() which locks and unlocks the + * mutex; the caller must not be holding it already. + */ +gcc_nonnull(1) +G_GNUC_MALLOC +struct tag * +input_stream_lock_tag(struct input_stream *is); + +/** + * Returns true if the next read operation will not block: either data + * is available, or end-of-stream has been reached, or an error has + * occurred. * - * The semantics of this function are not well-defined, and it will - * eventually be removed. + * The caller must lock the mutex. */ -int input_stream_buffer(struct input_stream *is, GError **error_r); +gcc_nonnull(1) +G_GNUC_PURE +bool +input_stream_available(struct input_stream *is); /** * Reads data from the stream into the caller-supplied buffer. * Returns 0 on error or eof (check with input_stream_eof()). * + * The caller must lock the mutex. + * * @param is the input_stream object * @param ptr the buffer to read into * @param size the maximum number of bytes to read * @return the number of bytes read */ +gcc_nonnull(1, 2) size_t input_stream_read(struct input_stream *is, void *ptr, size_t size, GError **error_r); +/** + * Wrapper for input_stream_tag() which locks and unlocks the + * mutex; the caller must not be holding it already. + */ +gcc_nonnull(1, 2) +size_t +input_stream_lock_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r); + #endif diff --git a/src/io_thread.c b/src/io_thread.c new file mode 100644 index 000000000..2091ecd05 --- /dev/null +++ b/src/io_thread.c @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "io_thread.h" +#include "glib_compat.h" + +#include <assert.h> + +static struct { + GMutex *mutex; + GCond *cond; + + GMainContext *context; + GMainLoop *loop; + GThread *thread; +} io; + +void +io_thread_run(void) +{ + assert(io_thread_inside()); + assert(io.context != NULL); + assert(io.loop != NULL); + + g_main_loop_run(io.loop); +} + +static gpointer +io_thread_func(G_GNUC_UNUSED gpointer arg) +{ + /* lock+unlock to synchronize with io_thread_start(), to be + sure that io.thread is set */ + g_mutex_lock(io.mutex); + g_mutex_unlock(io.mutex); + + io_thread_run(); + return NULL; +} + +void +io_thread_init(void) +{ + assert(io.context == NULL); + assert(io.loop == NULL); + assert(io.thread == NULL); + + io.mutex = g_mutex_new(); + io.cond = g_cond_new(); + io.context = g_main_context_new(); + io.loop = g_main_loop_new(io.context, false); +} + +bool +io_thread_start(GError **error_r) +{ + assert(io.context != NULL); + assert(io.loop != NULL); + assert(io.thread == NULL); + + g_mutex_lock(io.mutex); + io.thread = g_thread_create(io_thread_func, NULL, true, error_r); + g_mutex_unlock(io.mutex); + if (io.thread == NULL) + return false; + + return true; +} + +void +io_thread_quit(void) +{ + assert(io.loop != NULL); + + g_main_loop_quit(io.loop); +} + +void +io_thread_deinit(void) +{ + if (io.thread != NULL) { + io_thread_quit(); + + g_thread_join(io.thread); + } + + if (io.loop != NULL) + g_main_loop_unref(io.loop); + + if (io.context != NULL) + g_main_context_unref(io.context); + + g_cond_free(io.cond); + g_mutex_free(io.mutex); +} + +GMainContext * +io_thread_context(void) +{ + return io.context; +} + +bool +io_thread_inside(void) +{ + return io.thread != NULL && g_thread_self() == io.thread; +} + +guint +io_thread_idle_add(GSourceFunc function, gpointer data) +{ + GSource *source = g_idle_source_new(); + g_source_set_callback(source, function, data, NULL); + guint id = g_source_attach(source, io.context); + g_source_unref(source); + return id; +} + +GSource * +io_thread_timeout_add(guint interval_ms, GSourceFunc function, gpointer data) +{ + GSource *source = g_timeout_source_new(interval_ms); + g_source_set_callback(source, function, data, NULL); + g_source_attach(source, io.context); + return source; +} + +GSource * +io_thread_timeout_add_seconds(guint interval, + GSourceFunc function, gpointer data) +{ + GSource *source = g_timeout_source_new_seconds(interval); + g_source_set_callback(source, function, data, NULL); + g_source_attach(source, io.context); + return source; +} + +struct call_data { + GThreadFunc function; + gpointer data; + bool done; + gpointer result; +}; + +static gboolean +io_thread_call_func(gpointer _data) +{ + struct call_data *data = _data; + + gpointer result = data->function(data->data); + + g_mutex_lock(io.mutex); + data->done = true; + data->result = result; + g_cond_broadcast(io.cond); + g_mutex_unlock(io.mutex); + + return false; +} + +gpointer +io_thread_call(GThreadFunc function, gpointer _data) +{ + assert(io.thread != NULL); + + if (io_thread_inside()) + /* we're already in the I/O thread - no + synchronization needed */ + return function(_data); + + struct call_data data = { + .function = function, + .data = _data, + .done = false, + }; + + io_thread_idle_add(io_thread_call_func, &data); + + g_mutex_lock(io.mutex); + while (!data.done) + g_cond_wait(io.cond, io.mutex); + g_mutex_unlock(io.mutex); + + return data.result; +} diff --git a/src/io_thread.h b/src/io_thread.h new file mode 100644 index 000000000..8ff5a71e5 --- /dev/null +++ b/src/io_thread.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_IO_THREAD_H +#define MPD_IO_THREAD_H + +#include <glib.h> +#include <stdbool.h> + +void +io_thread_init(void); + +bool +io_thread_start(GError **error_r); + +/** + * Run the I/O event loop synchronously in the current thread. This + * can be called instead of io_thread_start(). For testing purposes + * only. + */ +void +io_thread_run(void); + +/** + * Ask the I/O thread to quit, but does not wait for it. Usually, you + * don't need to call this function, because io_thread_deinit() + * includes this. + */ +void +io_thread_quit(void); + +void +io_thread_deinit(void); + +G_GNUC_PURE +GMainContext * +io_thread_context(void); + +/** + * Is the current thread the I/O thread? + */ +G_GNUC_PURE +bool +io_thread_inside(void); + +guint +io_thread_idle_add(GSourceFunc function, gpointer data); + +G_GNUC_MALLOC +GSource * +io_thread_timeout_add(guint interval_ms, GSourceFunc function, gpointer data); + +G_GNUC_MALLOC +GSource * +io_thread_timeout_add_seconds(guint interval, + GSourceFunc function, gpointer data); + +/** + * Call a function synchronously in the I/O thread. + */ +gpointer +io_thread_call(GThreadFunc function, gpointer data); + +#endif diff --git a/src/listen.c b/src/listen.c index da2e79909..e2a40e93f 100644 --- a/src/listen.c +++ b/src/listen.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,10 +23,15 @@ #include "client.h" #include "conf.h" #include "glib_compat.h" +#include "main.h" #include <string.h> #include <assert.h> +#ifdef ENABLE_SYSTEMD_DAEMON +#include <systemd/sd-daemon.h> +#endif + #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "listen" @@ -39,7 +44,7 @@ static void listen_callback(int fd, const struct sockaddr *address, size_t address_length, int uid, G_GNUC_UNUSED void *ctx) { - client_new(fd, address, address_length, uid); + client_new(global_player_control, fd, address, address_length, uid); } static bool @@ -60,6 +65,30 @@ listen_add_config_param(unsigned int port, } } +static bool +listen_systemd_activation(GError **error_r) +{ +#ifdef ENABLE_SYSTEMD_DAEMON + int n = sd_listen_fds(true); + if (n <= 0) { + if (n < 0) + g_warning("sd_listen_fds() failed: %s", + g_strerror(-n)); + return false; + } + + for (int i = SD_LISTEN_FDS_START, end = SD_LISTEN_FDS_START + n; + i != end; ++i) + if (!server_socket_add_fd(listen_socket, i, error_r)) + return false; + + return true; +#else + (void)error_r; + return false; +#endif +} + bool listen_global_init(GError **error_r) { @@ -71,6 +100,14 @@ listen_global_init(GError **error_r) listen_socket = server_socket_new(listen_callback, NULL); + if (listen_systemd_activation(&error)) + return true; + + if (error != NULL) { + g_propagate_error(error_r, error); + return false; + } + if (param != NULL) { /* "bind_to_address" is configured, create listeners for all values */ diff --git a/src/listen.h b/src/listen.h index 449b5ebae..246e83706 100644 --- a/src/listen.h +++ b/src/listen.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/locate.c b/src/locate.c index e27858a0e..96acb3a39 100644 --- a/src/locate.c +++ b/src/locate.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -64,19 +64,6 @@ locate_item_init(struct locate_item *item, return true; } -struct locate_item * -locate_item_new(const char *type_string, const char *needle) -{ - struct locate_item *ret = g_new(struct locate_item, 1); - - if (!locate_item_init(ret, type_string, needle)) { - g_free(ret); - ret = NULL; - } - - return ret; -} - void locate_item_list_free(struct locate_item_list *list) { diff --git a/src/locate.h b/src/locate.h index 0283f551b..ec20ded24 100644 --- a/src/locate.h +++ b/src/locate.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_LOCATE_H #define MPD_LOCATE_H +#include "gcc.h" + #include <stdint.h> #include <stdbool.h> @@ -49,10 +51,6 @@ struct locate_item_list { int locate_parse_type(const char *str); -/* returns NULL if not a known type */ -struct locate_item * -locate_item_new(const char *type_string, const char *needle); - /** * Allocates a new struct locate_item_list, and initializes all * members with zero bytes. @@ -61,6 +59,7 @@ struct locate_item_list * locate_item_list_new(unsigned length); /* return number of items or -1 on error */ +gcc_nonnull(1) struct locate_item_list * locate_item_list_parse(char *argv[], int argc); @@ -68,19 +67,24 @@ locate_item_list_parse(char *argv[], int argc); * Duplicate the struct locate_item_list object and convert all * needles with g_utf8_casefold(). */ +gcc_nonnull(1) struct locate_item_list * locate_item_list_casefold(const struct locate_item_list *list); +gcc_nonnull(1) void locate_item_list_free(struct locate_item_list *list); +gcc_nonnull(1) void locate_item_free(struct locate_item *item); +gcc_nonnull(1,2) bool locate_song_search(const struct song *song, const struct locate_item_list *criteria); +gcc_nonnull(1,2) bool locate_song_match(const struct song *song, const struct locate_item_list *criteria); @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -133,16 +133,20 @@ open_log_file(void) return open_cloexec(out_filename, O_CREAT | O_WRONLY | O_APPEND, 0666); } -static void -log_init_file(const char *path, unsigned line) +static bool +log_init_file(const char *path, unsigned line, GError **error_r) { out_filename = path; out_fd = open_log_file(); - if (out_fd < 0) - MPD_ERROR("failed to open log file \"%s\" (config line %u): %s", - path, line, g_strerror(errno)); + if (out_fd < 0) { + g_set_error(error_r, log_quark(), errno, + "failed to open log file \"%s\" (config line %u): %s", + path, line, g_strerror(errno)); + return false; + } g_log_set_default_handler(file_log_func, NULL); + return true; } #ifdef HAVE_SYSLOG @@ -232,7 +236,8 @@ log_early_init(bool verbose) log_init_stdout(); } -void log_init(bool verbose, bool use_stdout) +bool +log_init(bool verbose, bool use_stdout, GError **error_r) { const struct config_param *param; @@ -245,6 +250,7 @@ void log_init(bool verbose, bool use_stdout) if (use_stdout) { log_init_stdout(); + return true; } else { param = config_get_param(CONF_LOG_FILE); if (param == NULL) { @@ -252,19 +258,31 @@ void log_init(bool verbose, bool use_stdout) /* no configuration: default to syslog (if available) */ log_init_syslog(); + return true; #else - MPD_ERROR("config parameter \"%s\" not found\n", - CONF_LOG_FILE); + g_set_error(error_r, log_quark(), 0, + "config parameter \"%s\" not found", + CONF_LOG_FILE); + return false; #endif #ifdef HAVE_SYSLOG } else if (strcmp(param->value, "syslog") == 0) { log_init_syslog(); + return true; #endif } else { - const char *path = config_get_path(CONF_LOG_FILE); - assert(path != NULL); - - log_init_file(path, param->line); + GError *error = NULL; + char *path = config_dup_path(CONF_LOG_FILE, &error); + if (path == NULL) { + assert(error != NULL); + g_propagate_error(error_r, error); + return false; + } + + bool success = log_init_file(path, param->line, + error_r); + g_free(path); + return success; } } } @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,6 +23,13 @@ #include <glib.h> #include <stdbool.h> +G_GNUC_CONST +static inline GQuark +log_quark(void) +{ + return g_quark_from_static_string("log"); +} + /** * Configure a logging destination for daemon startup, before the * configuration file is read. This allows the daemon to use the @@ -34,7 +41,8 @@ void log_early_init(bool verbose); -void log_init(bool verbose, bool use_stdout); +bool +log_init(bool verbose, bool use_stdout, GError **error_r); void setup_log_output(bool use_stdout); @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 @@ * connected by IPC socket. */ static const char *remoteUrlPrefixes[] = { -#ifdef ENABLE_CURL +#if defined(ENABLE_CURL) || defined(ENABLE_SOUP) "http://", #endif #ifdef ENABLE_MMS @@ -49,6 +49,12 @@ static const char *remoteUrlPrefixes[] = { "rtmpt://", "rtmps://", #endif +#ifdef ENABLE_CDIO_PARANOIA + "cdda://", +#endif +#ifdef ENABLE_DESPOTIFY + "spt://", +#endif NULL }; @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 a500e2934..4a10f1433 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,7 +20,9 @@ #include "config.h" #include "main.h" #include "daemon.h" +#include "io_thread.h" #include "client.h" +#include "client_idle.h" #include "idle.h" #include "command.h" #include "playlist.h" @@ -37,11 +39,12 @@ #include "player_control.h" #include "stats.h" #include "sig_handlers.h" -#include "audio.h" +#include "audio_config.h" #include "output_all.h" #include "volume.h" #include "log.h" #include "permission.h" +#include "pcm_resample.h" #include "replay_gain_config.h" #include "decoder_list.h" #include "input_init.h" @@ -51,8 +54,6 @@ #include "dbUtils.h" #include "zeroconf.h" #include "event_pipe.h" -#include "dirvec.h" -#include "songvec.h" #include "tag_pool.h" #include "mpd_error.h" @@ -94,31 +95,56 @@ GMainLoop *main_loop; GCond *main_cond; -static void -glue_daemonize_init(const struct options *options) +struct player_control *global_player_control; + +static bool +glue_daemonize_init(const struct options *options, GError **error_r) { + GError *error = NULL; + + char *pid_file = config_dup_path(CONF_PID_FILE, &error); + if (pid_file == NULL && error != NULL) { + g_propagate_error(error_r, error); + return false; + } + daemonize_init(config_get_string(CONF_USER, NULL), config_get_string(CONF_GROUP, NULL), - config_get_path(CONF_PID_FILE)); + pid_file); + g_free(pid_file); if (options->kill) daemonize_kill(); + + return true; } -static void -glue_mapper_init(void) +static bool +glue_mapper_init(GError **error_r) { - const char *music_dir, *playlist_dir; + GError *error = NULL; + char *music_dir = config_dup_path(CONF_MUSIC_DIR, &error); + if (music_dir == NULL && error != NULL) { + g_propagate_error(error_r, error); + return false; + } + + char *playlist_dir = config_dup_path(CONF_PLAYLIST_DIR, &error); + if (playlist_dir == NULL && error != NULL) { + g_propagate_error(error_r, error); + return false; + } - 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); + music_dir = g_strdup(g_get_user_special_dir(G_USER_DIRECTORY_MUSIC)); #endif - playlist_dir = config_get_path(CONF_PLAYLIST_DIR); - mapper_init(music_dir, playlist_dir); + + g_free(music_dir); + g_free(playlist_dir); + return true; } /** @@ -129,38 +155,31 @@ glue_mapper_init(void) static bool glue_db_init_and_load(void) { - const char *path = config_get_path(CONF_DB_FILE); - bool ret; + const struct config_param *path = config_get_param(CONF_DB_FILE); + GError *error = NULL; + bool ret; if (!mapper_has_music_directory()) { if (path != NULL) g_message("Found " CONF_DB_FILE " setting without " CONF_MUSIC_DIR " - disabling database"); - db_init(NULL); + db_init(NULL, NULL); return true; } if (path == NULL) MPD_ERROR(CONF_DB_FILE " setting missing"); - db_init(path); + if (!db_init(path, &error)) + MPD_ERROR("%s", error->message); ret = db_load(&error); - if (!ret) { - g_warning("Failed to load database: %s", error->message); - g_error_free(error); - - if (!db_check()) - exit(EXIT_FAILURE); - - db_clear(); - - /* run database update after daemonization */ - return false; - } + if (!ret) + MPD_ERROR("%s", error->message); - return true; + /* run database update after daemonization? */ + return db_exists(); } /** @@ -170,20 +189,33 @@ static void glue_sticker_init(void) { #ifdef ENABLE_SQLITE - bool success; GError *error = NULL; + char *sticker_file = config_dup_path(CONF_STICKER_FILE, &error); + if (sticker_file == NULL && error != NULL) + MPD_ERROR("%s", error->message); - success = sticker_global_init(config_get_path(CONF_STICKER_FILE), - &error); - if (!success) + if (!sticker_global_init(sticker_file, &error)) MPD_ERROR("%s", error->message); + + g_free(sticker_file); #endif } -static void -glue_state_file_init(void) +static bool +glue_state_file_init(GError **error_r) { - state_file_init(config_get_path(CONF_STATE_FILE)); + GError *error = NULL; + + char *path = config_dup_path(CONF_STATE_FILE, &error); + if (path == NULL && error != NULL) { + g_propagate_error(error_r, error); + return false; + } + + state_file_init(path, global_player_control); + g_free(path); + + return true; } /** @@ -225,10 +257,11 @@ initialize_decoder_and_player(void) param = config_get_param(CONF_AUDIO_BUFFER_SIZE); if (param != NULL) { - buffer_size = strtol(param->value, &test, 10); - if (*test != '\0' || buffer_size <= 0) + long tmp = strtol(param->value, &test, 10); + if (*test != '\0' || tmp <= 0 || tmp == LONG_MAX) MPD_ERROR("buffer size \"%s\" is not a positive integer, " "line %i\n", param->value, param->line); + buffer_size = tmp; } else buffer_size = DEFAULT_BUFFER_SIZE; @@ -254,7 +287,7 @@ initialize_decoder_and_player(void) if (buffered_before_play > buffered_chunks) buffered_before_play = buffered_chunks; - pc_init(buffered_chunks, buffered_before_play); + global_player_control = pc_new(buffered_chunks, buffered_before_play); } /** @@ -308,10 +341,9 @@ int mpd_main(int argc, char *argv[]) /* enable GLib's thread safety code */ g_thread_init(NULL); + io_thread_init(); winsock_init(); idle_init(); - dirvec_init(); - songvec_init(); tag_pool_init(); config_global_init(); @@ -322,11 +354,20 @@ int mpd_main(int argc, char *argv[]) return EXIT_FAILURE; } - glue_daemonize_init(&options); + if (!glue_daemonize_init(&options, &error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } stats_global_init(); tag_lib_init(); - log_init(options.verbose, options.log_stderr); + + if (!log_init(options.verbose, options.log_stderr, &error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } success = listen_global_init(&error); if (!success) { @@ -346,13 +387,26 @@ int mpd_main(int argc, char *argv[]) event_pipe_register(PIPE_EVENT_SHUTDOWN, shutdown_event_emitted); path_global_init(); - glue_mapper_init(); + + if (!glue_mapper_init(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + initPermissions(); playlist_global_init(); spl_global_init(); #ifdef ENABLE_ARCHIVE archive_plugin_init_all(); #endif + + if (!pcm_resample_global_init(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + decoder_plugin_init_all(); update_global_init(); @@ -364,7 +418,7 @@ int mpd_main(int argc, char *argv[]) initialize_decoder_and_player(); volume_init(); initAudioConfig(); - audio_output_all_init(); + audio_output_all_init(global_player_control); client_manager_init(); replay_gain_global_init(); @@ -382,9 +436,15 @@ int mpd_main(int argc, char *argv[]) initSigHandlers(); + if (!io_thread_start(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + initZeroconf(); - player_create(); + player_create(global_player_control); if (create_db) { /* the database failed to load: recreate the @@ -394,7 +454,11 @@ int mpd_main(int argc, char *argv[]) MPD_ERROR("directory update failed"); } - glue_state_file_init(); + if (!glue_state_file_init(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } success = config_get_bool(CONF_AUTO_UPDATE, false); #ifdef ENABLE_INOTIFY @@ -410,7 +474,7 @@ int mpd_main(int argc, char *argv[]) /* enable all audio outputs (if not already done by playlist_state_restore() */ - pc_update_audio(); + pc_update_audio(global_player_control); #ifdef WIN32 win32_app_started(); @@ -431,8 +495,8 @@ int mpd_main(int argc, char *argv[]) mpd_inotify_finish(); #endif - state_file_finish(); - pc_kill(); + state_file_finish(global_player_control); + pc_kill(global_player_control); finishZeroconf(); client_manager_deinit(); listen_global_finish(); @@ -457,7 +521,7 @@ int mpd_main(int argc, char *argv[]) mapper_finish(); path_global_finish(); finishPermissions(); - pc_deinit(); + pc_free(global_player_control); command_finish(); update_global_finish(); decoder_plugin_deinit_all(); @@ -466,10 +530,9 @@ int mpd_main(int argc, char *argv[]) #endif config_global_finish(); tag_pool_deinit(); - songvec_deinit(); - dirvec_deinit(); idle_deinit(); stats_global_finish(); + io_thread_deinit(); daemonize_finish(); #ifdef WIN32 WSACleanup(); diff --git a/src/main.h b/src/main.h index 9b9cba018..2a7d75910 100644 --- a/src/main.h +++ b/src/main.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,6 +28,8 @@ extern GMainLoop *main_loop; extern GCond *main_cond; +extern struct player_control *global_player_control; + /** * A entry point for application. * On non-Windows platforms this is called directly from main() diff --git a/src/main_win32.c b/src/main_win32.c index e1ddb53f5..aac7ad886 100644 --- a/src/main_win32.c +++ b/src/main_win32.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/mapper.c b/src/mapper.c index 3cee41eb3..d230f5d92 100644 --- a/src/mapper.c +++ b/src/mapper.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -117,10 +117,10 @@ void mapper_finish(void) g_free(playlist_dir); } -bool -mapper_has_music_directory(void) +const char * +mapper_get_music_directory(void) { - return music_dir != NULL; + return music_dir; } const char * diff --git a/src/mapper.h b/src/mapper.h index 9f84f96fe..ed4a60b56 100644 --- a/src/mapper.h +++ b/src/mapper.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,6 +24,7 @@ #ifndef MPD_MAPPER_H #define MPD_MAPPER_H +#include <glib.h> #include <stdbool.h> #define PLAYLIST_FILE_SUFFIX ".m3u" @@ -35,17 +36,26 @@ void mapper_init(const char *_music_dir, const char *_playlist_dir); void mapper_finish(void); +G_GNUC_CONST +const char * +mapper_get_music_directory(void); + /** * Returns true if a music directory was configured. */ -bool -mapper_has_music_directory(void); +G_GNUC_CONST +static inline bool +mapper_has_music_directory(void) +{ + return mapper_get_music_directory() != NULL; +} /** * 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. */ +G_GNUC_PURE const char * map_to_relative_path(const char *path_utf8); @@ -54,6 +64,7 @@ map_to_relative_path(const char *path_utf8); * is basically done by converting the URI to the file system charset * and prepending the music directory. */ +G_GNUC_MALLOC char * map_uri_fs(const char *uri); @@ -63,6 +74,7 @@ map_uri_fs(const char *uri); * @param directory the directory object * @return the path in file system encoding, or NULL if mapping failed */ +G_GNUC_MALLOC char * map_directory_fs(const struct directory *directory); @@ -74,6 +86,7 @@ map_directory_fs(const struct directory *directory); * @param name the child's name in UTF-8 * @return the path in file system encoding, or NULL if mapping failed */ +G_GNUC_MALLOC char * map_directory_child_fs(const struct directory *directory, const char *name); @@ -84,6 +97,7 @@ map_directory_child_fs(const struct directory *directory, const char *name); * @param song the song object * @return the path in file system encoding, or NULL if mapping failed */ +G_GNUC_MALLOC char * map_song_fs(const struct song *song); @@ -94,12 +108,14 @@ map_song_fs(const struct song *song); * @param path_fs a path in file system encoding * @return the relative path in UTF-8, or NULL if mapping failed */ +G_GNUC_MALLOC char * map_fs_to_utf8(const char *path_fs); /** * Returns the playlist directory. */ +G_GNUC_CONST const char * map_spl_path(void); @@ -110,6 +126,7 @@ map_spl_path(void); * * @return the path in file system encoding, or NULL if mapping failed */ +G_GNUC_PURE char * map_spl_utf8_to_fs(const char *name); diff --git a/src/mixer/alsa_mixer_plugin.c b/src/mixer/alsa_mixer_plugin.c index 38f36cb8f..22e4e22bd 100644 --- a/src/mixer/alsa_mixer_plugin.c +++ b/src/mixer/alsa_mixer_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ #include "config.h" #include "mixer_api.h" #include "output_api.h" +#include "event_pipe.h" #include <glib.h> #include <alsa/asoundlib.h> @@ -28,6 +29,15 @@ #define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM" #define VOLUME_MIXER_ALSA_INDEX_DEFAULT 0 +struct alsa_mixer_source { + GSource source; + + snd_mixer_t *mixer; + + /** a linked list of all registered GPollFD objects */ + GSList *fds; +}; + struct alsa_mixer { /** the base mixer class */ struct mixer base; @@ -41,6 +51,8 @@ struct alsa_mixer { long volume_min; long volume_max; int volume_set; + + struct alsa_mixer_source *source; }; /** @@ -52,6 +64,161 @@ alsa_mixer_quark(void) return g_quark_from_static_string("alsa_mixer"); } +/* + * GSource helper functions + * + */ + +static GSList ** +find_fd(GSList **list_r, int fd) +{ + while (true) { + GSList *list = *list_r; + if (list == NULL) + return NULL; + + GPollFD *p = list->data; + if (p->fd == fd) + return list_r; + + list_r = &list->next; + } +} + +static void +alsa_mixer_update_fd(struct alsa_mixer_source *source, const struct pollfd *p, + GSList **old_r) +{ + GSList **found_r = find_fd(old_r, p->fd); + if (found_r == NULL) { + /* new fd */ + GPollFD *q = g_new(GPollFD, 1); + q->fd = p->fd; + q->events = p->events; + g_source_add_poll(&source->source, q); + source->fds = g_slist_prepend(source->fds, q); + return; + } + + GSList *found = *found_r; + *found_r = found->next; + + GPollFD *q = found->data; + if (q->events != p->events) { + /* refresh events */ + g_source_remove_poll(&source->source, q); + q->events = p->events; + g_source_add_poll(&source->source, q); + } + + found->next = source->fds; + source->fds = found; +} + +static void +alsa_mixer_update_fds(struct alsa_mixer_source *source) +{ + int count = snd_mixer_poll_descriptors_count(source->mixer); + if (count < 0) + count = 0; + + struct pollfd *pfds = g_new(struct pollfd, count); + count = snd_mixer_poll_descriptors(source->mixer, pfds, count); + if (count < 0) + count = 0; + + GSList *old = source->fds; + source->fds = NULL; + + for (int i = 0; i < count; ++i) + alsa_mixer_update_fd(source, &pfds[i], &old); + g_free(pfds); + + for (; old != NULL; old = old->next) { + GPollFD *q = old->data; + g_source_remove_poll(&source->source, q); + g_free(q); + } + + g_slist_free(old); +} + +/* + * GSource methods + * + */ + +static gboolean +alsa_mixer_source_prepare(GSource *_source, G_GNUC_UNUSED gint *timeout_r) +{ + struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source; + alsa_mixer_update_fds(source); + + return false; +} + +static gboolean +alsa_mixer_source_check(GSource *_source) +{ + struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source; + + for (const GSList *i = source->fds; i != NULL; i = i->next) { + const GPollFD *poll_fd = i->data; + if (poll_fd->revents != 0) + return true; + } + + return false; +} + +static gboolean +alsa_mixer_source_dispatch(GSource *_source, + G_GNUC_UNUSED GSourceFunc callback, + G_GNUC_UNUSED gpointer user_data) +{ + struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source; + + snd_mixer_handle_events(source->mixer); + return true; +} + +static void +alsa_mixer_source_finalize(GSource *_source) +{ + struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source; + + for (GSList *i = source->fds; i != NULL; i = i->next) + g_free(i->data); + + g_slist_free(source->fds); +} + +static GSourceFuncs alsa_mixer_source_funcs = { + .prepare = alsa_mixer_source_prepare, + .check = alsa_mixer_source_check, + .dispatch = alsa_mixer_source_dispatch, + .finalize = alsa_mixer_source_finalize, +}; + +/* + * libasound callbacks + * + */ + +static int +alsa_mixer_elem_callback(G_GNUC_UNUSED snd_mixer_elem_t *elem, unsigned mask) +{ + if (mask & SND_CTL_EVENT_MASK_VALUE) + event_pipe_emit(PIPE_EVENT_MIXER); + + return 0; +} + +/* + * mixer_plugin methods + * + */ + static struct mixer * alsa_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param, G_GNUC_UNUSED GError **error_r) @@ -81,34 +248,28 @@ alsa_mixer_finish(struct mixer *data) snd_config_update_free_global(); } -static void -alsa_mixer_close(struct mixer *data) +G_GNUC_PURE +static snd_mixer_elem_t * +alsa_mixer_lookup_elem(snd_mixer_t *handle, const char *name, unsigned idx) { - struct alsa_mixer *am = (struct alsa_mixer *)data; - - assert(am->handle != NULL); + for (snd_mixer_elem_t *elem = snd_mixer_first_elem(handle); + elem != NULL; elem = snd_mixer_elem_next(elem)) { + if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE && + g_ascii_strcasecmp(snd_mixer_selem_get_name(elem), + name) == 0 && + snd_mixer_selem_get_index(elem) == idx) + return elem; + } - snd_mixer_close(am->handle); + return NULL; } static bool -alsa_mixer_open(struct mixer *data, GError **error_r) +alsa_mixer_setup(struct alsa_mixer *am, GError **error_r) { - struct alsa_mixer *am = (struct alsa_mixer *)data; int err; - snd_mixer_elem_t *elem; - - am->volume_set = -1; - - err = snd_mixer_open(&am->handle, 0); - if (err < 0) { - 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) { - alsa_mixer_close(data); g_set_error(error_r, alsa_mixer_quark(), err, "failed to attach to %s: %s", am->device, snd_strerror(err)); @@ -117,7 +278,6 @@ alsa_mixer_open(struct mixer *data, GError **error_r) if ((err = snd_mixer_selem_register(am->handle, NULL, NULL)) < 0) { - alsa_mixer_close(data); g_set_error(error_r, alsa_mixer_quark(), err, "snd_mixer_selem_register() failed: %s", snd_strerror(err)); @@ -125,38 +285,69 @@ alsa_mixer_open(struct mixer *data, GError **error_r) } if ((err = snd_mixer_load(am->handle)) < 0) { - alsa_mixer_close(data); g_set_error(error_r, alsa_mixer_quark(), err, "snd_mixer_load() failed: %s\n", snd_strerror(err)); return false; } - elem = snd_mixer_first_elem(am->handle); + am->elem = alsa_mixer_lookup_elem(am->handle, am->control, am->index); + if (am->elem == NULL) { + g_set_error(error_r, alsa_mixer_quark(), 0, + "no such mixer control: %s", am->control); + return false; + } + + snd_mixer_selem_get_playback_volume_range(am->elem, + &am->volume_min, + &am->volume_max); + + snd_mixer_elem_set_callback(am->elem, alsa_mixer_elem_callback); - while (elem) { - if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE) { - if ((g_ascii_strcasecmp(am->control, - snd_mixer_selem_get_name(elem)) == 0) && - (am->index == snd_mixer_selem_get_index(elem))) { - break; - } - } - elem = snd_mixer_elem_next(elem); + am->source = (struct alsa_mixer_source *) + g_source_new(&alsa_mixer_source_funcs, sizeof(*am->source)); + am->source->mixer = am->handle; + am->source->fds = NULL; + g_source_attach(&am->source->source, g_main_context_default()); + + return true; +} + +static bool +alsa_mixer_open(struct mixer *data, GError **error_r) +{ + struct alsa_mixer *am = (struct alsa_mixer *)data; + int err; + + am->volume_set = -1; + + err = snd_mixer_open(&am->handle, 0); + if (err < 0) { + g_set_error(error_r, alsa_mixer_quark(), err, + "snd_mixer_open() failed: %s", snd_strerror(err)); + return false; } - if (elem) { - am->elem = elem; - snd_mixer_selem_get_playback_volume_range(am->elem, - &am->volume_min, - &am->volume_max); - return true; + if (!alsa_mixer_setup(am, error_r)) { + snd_mixer_close(am->handle); + return false; } - alsa_mixer_close(data); - g_set_error(error_r, alsa_mixer_quark(), 0, - "no such mixer control: %s", am->control); - return false; + return true; +} + +static void +alsa_mixer_close(struct mixer *data) +{ + struct alsa_mixer *am = (struct alsa_mixer *)data; + + assert(am->handle != NULL); + + g_source_destroy(&am->source->source); + g_source_unref(&am->source->source); + + snd_mixer_elem_set_callback(am->elem, NULL); + snd_mixer_close(am->handle); } static int diff --git a/src/mixer/oss_mixer_plugin.c b/src/mixer/oss_mixer_plugin.c index 418068ac2..608f1f9b8 100644 --- a/src/mixer/oss_mixer_plugin.c +++ b/src/mixer/oss_mixer_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/pulse_mixer_plugin.c b/src/mixer/pulse_mixer_plugin.c index 2be0b8266..a82c032b3 100644 --- a/src/mixer/pulse_mixer_plugin.c +++ b/src/mixer/pulse_mixer_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -191,13 +191,13 @@ 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); + pulse_output_lock(pm->output); ret = pm->online ? (int)((100*(pa_cvolume_avg(&pm->volume)+1))/PA_VOLUME_NORM) : -1; - pa_threaded_mainloop_unlock(pm->output->mainloop); + pulse_output_unlock(pm->output); return ret; } @@ -209,9 +209,10 @@ pulse_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) struct pa_cvolume cvolume; bool success; - pa_threaded_mainloop_lock(pm->output->mainloop); + pulse_output_lock(pm->output); + if (!pm->online) { - pa_threaded_mainloop_unlock(pm->output->mainloop); + pulse_output_unlock(pm->output); g_set_error(error_r, pulse_mixer_quark(), 0, "disconnected"); return false; } @@ -221,7 +222,8 @@ pulse_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) success = pulse_output_set_volume(pm->output, &cvolume, error_r); if (success) pm->volume = cvolume; - pa_threaded_mainloop_unlock(pm->output->mainloop); + + pulse_output_unlock(pm->output); return success; } diff --git a/src/mixer/pulse_mixer_plugin.h b/src/mixer/pulse_mixer_plugin.h index be199f688..461633d37 100644 --- a/src/mixer/pulse_mixer_plugin.h +++ b/src/mixer/pulse_mixer_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/raop_mixer_plugin.c b/src/mixer/raop_mixer_plugin.c new file mode 100644 index 000000000..b05671212 --- /dev/null +++ b/src/mixer/raop_mixer_plugin.c @@ -0,0 +1,67 @@ +/* + * 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/raop_output_plugin.h" +#include "output_plugin.h" +#include "mixer_api.h" + +struct raop_mixer_plugin { + struct mixer base; + struct raop_data *rd; +}; + +static struct mixer * +raop_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct raop_mixer_plugin *rm = g_new(struct raop_mixer_plugin, 1); + rm->rd = (struct raop_data *) ao; + mixer_init(&rm->base, &raop_mixer_plugin); + + return &rm->base; +} + +static void +raop_mixer_finish(struct mixer *data) +{ + struct raop_mixer_plugin *rm = (struct raop_mixer_plugin *) data; + + g_free(rm); +} + +static int +raop_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r) +{ + struct raop_mixer_plugin *rm = (struct raop_mixer_plugin *)mixer; + return raop_get_volume(rm->rd); +} + +static bool +raop_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) +{ + struct raop_mixer_plugin *rm = (struct raop_mixer_plugin *)mixer; + return raop_set_volume(rm->rd, volume, error_r); +} + +const struct mixer_plugin raop_mixer_plugin = { + .init = raop_mixer_init, + .finish = raop_mixer_finish, + .get_volume = raop_mixer_get_volume, + .set_volume = raop_mixer_set_volume, +}; diff --git a/src/mixer/roar_mixer_plugin.c b/src/mixer/roar_mixer_plugin.c new file mode 100644 index 000000000..47d3c17f9 --- /dev/null +++ b/src/mixer/roar_mixer_plugin.c @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft + * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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/roar_output_plugin.h" + +#include <glib.h> + +#include <assert.h> +#include <stdlib.h> +#include <unistd.h> + +typedef struct roar_mpd_mixer +{ + /** the base mixer class */ + struct mixer base; + struct roar *self; +} roar_mixer_t; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +roar_mixer_quark(void) +{ + return g_quark_from_static_string("roar_mixer"); +} + +static struct mixer * +roar_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + roar_mixer_t *self = g_new(roar_mixer_t, 1); + self->self = ao; + + mixer_init(&self->base, &roar_mixer_plugin); + + return &self->base; +} + +static void +roar_mixer_finish(struct mixer *data) +{ + roar_mixer_t *self = (roar_mixer_t *) data; + + g_free(self); +} + +static void +roar_mixer_close(G_GNUC_UNUSED struct mixer *data) +{ +} + +static bool +roar_mixer_open(G_GNUC_UNUSED struct mixer *data, + G_GNUC_UNUSED GError **error_r) +{ + return true; +} + +static int +roar_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r) +{ + roar_mixer_t *self = (roar_mixer_t *)mixer; + return roar_output_get_volume(self->self); +} + +static bool +roar_mixer_set_volume(struct mixer *mixer, unsigned volume, + G_GNUC_UNUSED GError **error_r) +{ + roar_mixer_t *self = (roar_mixer_t *)mixer; + return roar_output_set_volume(self->self, volume); +} + +const struct mixer_plugin roar_mixer_plugin = { + .init = roar_mixer_init, + .finish = roar_mixer_finish, + .open = roar_mixer_open, + .close = roar_mixer_close, + .get_volume = roar_mixer_get_volume, + .set_volume = roar_mixer_set_volume, + .global = false, +}; diff --git a/src/mixer/software_mixer_plugin.c b/src/mixer/software_mixer_plugin.c index 93802e977..0206c3b99 100644 --- a/src/mixer/software_mixer_plugin.c +++ b/src/mixer/software_mixer_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/software_mixer_plugin.h b/src/mixer/software_mixer_plugin.h index 3bd07ac62..ee2b2023c 100644 --- a/src/mixer/software_mixer_plugin.h +++ b/src/mixer/software_mixer_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/winmm_mixer_plugin.c b/src/mixer/winmm_mixer_plugin.c index 5ab3e7525..ceddf6afd 100644 --- a/src/mixer/winmm_mixer_plugin.c +++ b/src/mixer/winmm_mixer_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_all.c b/src/mixer_all.c index ffe610b91..95ba90793 100644 --- a/src/mixer_all.c +++ b/src/mixer_all.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_all.h b/src/mixer_all.h index cece23292..fe873e713 100644 --- a/src/mixer_all.h +++ b/src/mixer_all.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_api.c b/src/mixer_api.c index 4c8959fb8..c85916c94 100644 --- a/src/mixer_api.c +++ b/src/mixer_api.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_api.h b/src/mixer_api.h index 26c001703..29c1e00ca 100644 --- a/src/mixer_api.h +++ b/src/mixer_api.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 458b3abc1..3e984dd04 100644 --- a/src/mixer_control.c +++ b/src/mixer_control.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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.h b/src/mixer_control.h index 1f48e8ca5..6c3468aca 100644 --- a/src/mixer_control.h +++ b/src/mixer_control.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_list.h b/src/mixer_list.h index a472c8807..95ded5c23 100644 --- a/src/mixer_list.h +++ b/src/mixer_list.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,7 +28,9 @@ 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 roar_mixer_plugin; extern const struct mixer_plugin pulse_mixer_plugin; +extern const struct mixer_plugin raop_mixer_plugin; extern const struct mixer_plugin winmm_mixer_plugin; #endif diff --git a/src/mixer_plugin.h b/src/mixer_plugin.h index 0915a03f3..9532b95cb 100644 --- a/src/mixer_plugin.h +++ b/src/mixer_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -41,7 +41,7 @@ struct mixer_plugin { * @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 + * @param error_r location to store the error occurring, or * NULL to ignore errors * @return a mixer object, or NULL on error */ @@ -56,7 +56,7 @@ struct mixer_plugin { /** * Open mixer device * - * @param error_r location to store the error occuring, or + * @param error_r location to store the error occurring, or * NULL to ignore errors * @return true on success, false on error */ @@ -70,7 +70,7 @@ struct mixer_plugin { /** * Reads the current volume. * - * @param error_r location to store the error occuring, or + * @param error_r location to store the error occurring, 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) @@ -80,7 +80,7 @@ struct mixer_plugin { /** * Sets the volume. * - * @param error_r location to store the error occuring, or + * @param error_r location to store the error occurring, or * NULL to ignore errors * @param volume the new volume (0..100 including) * @return true on success, false on error diff --git a/src/mixer_type.c b/src/mixer_type.c index 4f347dd94..a479caf16 100644 --- a/src/mixer_type.c +++ b/src/mixer_type.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_type.h b/src/mixer_type.h index fd1c5576c..15d136b5b 100644 --- a/src/mixer_type.h +++ b/src/mixer_type.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/mpd_error.h b/src/mpd_error.h index 47618d03c..219738ced 100644 --- a/src/mpd_error.h +++ b/src/mpd_error.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/notify.c b/src/notify.c index d148a4bfc..3c0112c91 100644 --- a/src/notify.c +++ b/src/notify.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/notify.h b/src/notify.h index 0c657f2fb..40821690c 100644 --- a/src/notify.h +++ b/src/notify.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/ntp_server.c b/src/ntp_server.c new file mode 100644 index 000000000..f31a2d845 --- /dev/null +++ b/src/ntp_server.c @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ntp_server.h" +#include "udp_server.h" + +#include <glib.h> +#include <assert.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> +#include <unistd.h> +#include <sys/time.h> + +#ifdef WIN32 +#include <ws2tcpip.h> +#include <winsock.h> +#else +#include <sys/socket.h> +#endif + +/* + * Calculate the current NTP time, store it in the buffer. + */ +static void +fill_int(unsigned char *buffer, uint32_t value) +{ + uint32_t be = GINT32_TO_BE(value); + memcpy(buffer, &be, sizeof(be)); +} + +/* + * Store time in the NTP format in the buffer + */ +static void +fill_time_buffer_with_time(unsigned char *buffer, struct timeval *tout) +{ + unsigned long secs_to_baseline = 964697997; + double fraction; + unsigned long long_fraction; + unsigned long secs; + + fraction = ((double) tout->tv_usec) / 1000000.0; + long_fraction = (unsigned long) (fraction * 256.0 * 256.0 * 256.0 * 256.0); + secs = secs_to_baseline + tout->tv_sec; + fill_int(buffer, secs); + fill_int(buffer + 4, long_fraction); +} + +/* + * Calculate the current NTP time, store it in the buffer. + */ +static void +fill_time_buffer(unsigned char *buffer) +{ + struct timeval current_time; + + gettimeofday(¤t_time,NULL); + fill_time_buffer_with_time(buffer, ¤t_time); +} + +static void +ntp_server_datagram(int fd, const void *data, size_t num_bytes, + const struct sockaddr *source_address, + size_t source_address_length, G_GNUC_UNUSED void *ctx) +{ + unsigned char buf[32]; + int iter; + + if (num_bytes > sizeof(buf)) + num_bytes = sizeof(buf); + memcpy(buf, data, num_bytes); + + fill_time_buffer(buf + 16); + // set to response + buf[1] = 0xd3; + // copy request + for (iter = 0; iter < 8; iter++) { + buf[8 + iter] = buf[24 + iter]; + } + fill_time_buffer(buf + 24); + + sendto(fd, (void *)buf, num_bytes, 0, + source_address, source_address_length); +} + +static const struct udp_server_handler ntp_server_handler = { + .datagram = ntp_server_datagram, +}; + +void +ntp_server_init(struct ntp_server *ntp) +{ + ntp->port = 6002; + ntp->udp = NULL; +} + +bool +ntp_server_open(struct ntp_server *ntp, GError **error_r) +{ + assert(ntp->udp == NULL); + + ntp->udp = udp_server_new(ntp->port, &ntp_server_handler, ntp, + error_r); + return ntp->udp != NULL; +} + +void +ntp_server_close(struct ntp_server *ntp) +{ + if (ntp->udp != NULL) { + udp_server_free(ntp->udp); + ntp->udp = NULL; + } +} diff --git a/src/ntp_server.h b/src/ntp_server.h new file mode 100644 index 000000000..fe6f8083b --- /dev/null +++ b/src/ntp_server.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_NTP_SERVER_H +#define MPD_NTP_SERVER_H + +#include <glib.h> + +#include <stdbool.h> + +struct timeval; + +struct ntp_server { + unsigned short port; + + struct udp_server *udp; +}; + +void +ntp_server_init(struct ntp_server *ntp); + +bool +ntp_server_open(struct ntp_server *ntp, GError **error_r); + +void +ntp_server_close(struct ntp_server *ntp); + +#endif diff --git a/src/open.h b/src/open.h index e39c64a97..1fe245dfa 100644 --- a/src/open.h +++ b/src/open.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/alsa_plugin.c b/src/output/alsa_output_plugin.c index ae06847c2..a6dc92fa0 100644 --- a/src/output/alsa_plugin.c +++ b/src/output/alsa_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,8 +18,10 @@ */ #include "config.h" +#include "alsa_output_plugin.h" #include "output_api.h" #include "mixer_list.h" +#include "pcm_export.h" #include <glib.h> #include <alsa/asoundlib.h> @@ -42,6 +44,10 @@ typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer, snd_pcm_uframes_t size); struct alsa_data { + struct audio_output base; + + struct pcm_export_state export; + /** the configured name of the ALSA device; NULL for the default device */ char *device; @@ -49,6 +55,14 @@ struct alsa_data { /** use memory mapped I/O? */ bool use_mmap; + /** + * Enable DSD over USB according to the dCS suggested + * standard? + * + * @see http://www.dcsltd.co.uk/page/assets/DSDoverUSB.pdf + */ + bool dsd_usb; + /** libasound's buffer_time setting (in microseconds) */ unsigned int buffer_time; @@ -68,8 +82,15 @@ struct alsa_data { */ alsa_writei_t *writei; - /** the size of one audio frame */ - size_t frame_size; + /** + * The size of one audio frame passed to method play(). + */ + size_t in_frame_size; + + /** + * The size of one audio frame passed to libasound. + */ + size_t out_frame_size; /** * The size of one period, in number of frames. @@ -109,19 +130,14 @@ alsa_data_new(void) } static void -alsa_data_free(struct alsa_data *ad) -{ - g_free(ad->device); - g_free(ad); -} - -static void alsa_configure(struct alsa_data *ad, const struct config_param *param) { ad->device = config_dup_block_string(param, "device", NULL); ad->use_mmap = config_get_block_bool(param, "use_mmap", false); + ad->dsd_usb = config_get_block_bool(param, "dsd_usb", false); + ad->buffer_time = config_get_block_unsigned(param, "buffer_time", MPD_ALSA_BUFFER_TIME_US); ad->period_time = config_get_block_unsigned(param, "period_time", 0); @@ -142,30 +158,53 @@ alsa_configure(struct alsa_data *ad, const struct config_param *param) #endif } -static void * -alsa_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, - G_GNUC_UNUSED GError **error) +static struct audio_output * +alsa_init(const struct config_param *param, GError **error_r) { struct alsa_data *ad = alsa_data_new(); + if (!ao_base_init(&ad->base, &alsa_output_plugin, param, error_r)) { + g_free(ad); + return NULL; + } + alsa_configure(ad, param); - return ad; + return &ad->base; } static void -alsa_finish(void *data) +alsa_finish(struct audio_output *ao) { - struct alsa_data *ad = data; + struct alsa_data *ad = (struct alsa_data *)ao; - alsa_data_free(ad); + ao_base_finish(&ad->base); + + g_free(ad->device); + g_free(ad); /* free libasound's config cache */ snd_config_update_free_global(); } static bool +alsa_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r) +{ + struct alsa_data *ad = (struct alsa_data *)ao; + + pcm_export_init(&ad->export); + return true; +} + +static void +alsa_output_disable(struct audio_output *ao) +{ + struct alsa_data *ad = (struct alsa_data *)ao; + + pcm_export_deinit(&ad->export); +} + +static bool alsa_test_default_device(void) { snd_pcm_t *handle; @@ -187,6 +226,7 @@ get_bitformat(enum sample_format sample_format) { switch (sample_format) { case SAMPLE_FORMAT_UNDEFINED: + case SAMPLE_FORMAT_DSD: return SND_PCM_FORMAT_UNKNOWN; case SAMPLE_FORMAT_S8: @@ -198,13 +238,11 @@ get_bitformat(enum sample_format sample_format) 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; + + case SAMPLE_FORMAT_FLOAT: + return SND_PCM_FORMAT_FLOAT; } assert(false); @@ -232,44 +270,39 @@ byteswap_bitformat(snd_pcm_format_t fmt) } } -/** - * 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) +static snd_pcm_format_t +alsa_to_packed_format(snd_pcm_format_t fmt) { - snd_pcm_format_t alsa_format = get_bitformat(sample_format); - if (alsa_format == SND_PCM_FORMAT_UNKNOWN) - return -EINVAL; + switch (fmt) { + case SND_PCM_FORMAT_S24_LE: + return SND_PCM_FORMAT_S24_3LE; - int err = snd_pcm_hw_params_set_format(pcm, hwparams, alsa_format); - if (err == 0) - audio_format->format = sample_format; + case SND_PCM_FORMAT_S24_BE: + return SND_PCM_FORMAT_S24_3BE; - return err; + default: + return SND_PCM_FORMAT_UNKNOWN; + } } -/** - * 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) +alsa_try_format_or_packed(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + snd_pcm_format_t fmt, bool *packed_r) { - snd_pcm_format_t alsa_format = - byteswap_bitformat(get_bitformat(sample_format)); - if (alsa_format == SND_PCM_FORMAT_UNKNOWN) + int err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt); + if (err == 0) + *packed_r = false; + + if (err != -EINVAL) + return err; + + fmt = alsa_to_packed_format(fmt); + if (fmt == 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; - } + err = snd_pcm_hw_params_set_format(pcm, hwparams, fmt); + if (err == 0) + *packed_r = true; return err; } @@ -279,15 +312,29 @@ alsa_output_try_reverse(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, * 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) +alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + enum sample_format sample_format, + bool *packed_r, bool *reverse_endian_r) { - 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); + snd_pcm_format_t alsa_format = get_bitformat(sample_format); + if (alsa_format == SND_PCM_FORMAT_UNKNOWN) + return -EINVAL; + + int err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, + packed_r); + if (err == 0) + *reverse_endian_r = false; + + if (err != -EINVAL) + return err; + + alsa_format = byteswap_bitformat(alsa_format); + if (alsa_format == SND_PCM_FORMAT_UNKNOWN) + return -EINVAL; + + err = alsa_try_format_or_packed(pcm, hwparams, alsa_format, packed_r); + if (err == 0) + *reverse_endian_r = true; return err; } @@ -297,37 +344,38 @@ alsa_output_try_format_both(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, */ static int alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, - struct audio_format *audio_format) + struct audio_format *audio_format, + bool *packed_r, bool *reverse_endian_r) { /* try the input format first */ - int err = alsa_output_try_format_both(pcm, hwparams, audio_format, - audio_format->format); - if (err != -EINVAL) - return err; + int err = alsa_output_try_format(pcm, hwparams, audio_format->format, + packed_r, reverse_endian_r); /* 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) + for (unsigned i = 0; + err == -EINVAL && probe_formats[i] != SAMPLE_FORMAT_UNDEFINED; + ++i) { + const enum sample_format mpd_format = probe_formats[i]; + if (mpd_format == audio_format->format) continue; - err = alsa_output_try_format_both(pcm, hwparams, audio_format, - probe_formats[i]); - if (err != -EINVAL) - return err; + err = alsa_output_try_format(pcm, hwparams, mpd_format, + packed_r, reverse_endian_r); + if (err == 0) + audio_format->format = mpd_format; } - return -EINVAL; + return err; } /** @@ -336,7 +384,7 @@ alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, */ static bool alsa_setup(struct alsa_data *ad, struct audio_format *audio_format, - GError **error) + bool *packed_r, bool *reverse_endian_r, GError **error) { snd_pcm_hw_params_t *hwparams; snd_pcm_sw_params_t *swparams; @@ -380,7 +428,8 @@ configure_hw: ad->writei = snd_pcm_writei; } - err = alsa_output_setup_format(ad->pcm, hwparams, audio_format); + err = alsa_output_setup_format(ad->pcm, hwparams, audio_format, + packed_r, reverse_endian_r); if (err < 0) { g_set_error(error, alsa_output_quark(), err, "ALSA device \"%s\" does not support format %s: %s", @@ -390,6 +439,11 @@ configure_hw: return false; } + snd_pcm_format_t format; + if (snd_pcm_hw_params_get_format(hwparams, &format) == 0) + g_debug("format=%s (%s)", snd_pcm_format_name(format), + snd_pcm_format_description(format)); + err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams, &channels); if (err < 0) { @@ -532,9 +586,72 @@ error: } static bool -alsa_open(void *data, struct audio_format *audio_format, GError **error) +alsa_setup_dsd(struct alsa_data *ad, struct audio_format *audio_format, + bool *shift8_r, bool *packed_r, bool *reverse_endian_r, + GError **error_r) { - struct alsa_data *ad = data; + assert(ad->dsd_usb); + assert(audio_format->format == SAMPLE_FORMAT_DSD); + + /* pass 24 bit to alsa_setup() */ + + struct audio_format usb_format = *audio_format; + usb_format.format = SAMPLE_FORMAT_S24_P32; + usb_format.sample_rate /= 2; + + const struct audio_format check = usb_format; + + if (!alsa_setup(ad, &usb_format, packed_r, reverse_endian_r, error_r)) + return false; + + /* if the device allows only 32 bit, shift all DSD-over-USB + samples left by 8 bit and leave the lower 8 bit cleared; + the DSD-over-USB documentation does not specify whether + this is legal, but there is anecdotical evidence that this + is possible (and the only option for some devices) */ + *shift8_r = usb_format.format == SAMPLE_FORMAT_S32; + if (usb_format.format == SAMPLE_FORMAT_S32) + usb_format.format = SAMPLE_FORMAT_S24_P32; + + if (!audio_format_equals(&usb_format, &check)) { + /* no bit-perfect playback, which is required + for DSD over USB */ + g_set_error(error_r, alsa_output_quark(), 0, + "Failed to configure DSD-over-USB on ALSA device \"%s\"", + alsa_device(ad)); + return false; + } + + return true; +} + +static bool +alsa_setup_or_dsd(struct alsa_data *ad, struct audio_format *audio_format, + GError **error_r) +{ + bool shift8 = false, packed, reverse_endian; + + const bool dsd_usb = ad->dsd_usb && + audio_format->format == SAMPLE_FORMAT_DSD; + const bool success = dsd_usb + ? alsa_setup_dsd(ad, audio_format, + &shift8, &packed, &reverse_endian, + error_r) + : alsa_setup(ad, audio_format, &packed, &reverse_endian, + error_r); + if (!success) + return false; + + pcm_export_open(&ad->export, + audio_format->format, audio_format->channels, + dsd_usb, shift8, packed, reverse_endian); + return true; +} + +static bool +alsa_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) +{ + struct alsa_data *ad = (struct alsa_data *)ao; int err; bool success; @@ -547,13 +664,17 @@ alsa_open(void *data, struct audio_format *audio_format, GError **error) return false; } - success = alsa_setup(ad, audio_format, error); + g_debug("opened %s type=%s", snd_pcm_name(ad->pcm), + snd_pcm_type_name(snd_pcm_type(ad->pcm))); + + success = alsa_setup_or_dsd(ad, audio_format, error); if (!success) { snd_pcm_close(ad->pcm); return false; } - ad->frame_size = audio_format_frame_size(audio_format); + ad->in_frame_size = audio_format_frame_size(audio_format); + ad->out_frame_size = ad->export.pack24 ? 3 : ad->in_frame_size; return true; } @@ -596,9 +717,9 @@ alsa_recover(struct alsa_data *ad, int err) } static void -alsa_drain(void *data) +alsa_drain(struct audio_output *ao) { - struct alsa_data *ad = data; + struct alsa_data *ad = (struct alsa_data *)ao; if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING) return; @@ -608,7 +729,7 @@ alsa_drain(void *data) period */ snd_pcm_uframes_t nframes = ad->period_frames - ad->period_position; - size_t nbytes = nframes * ad->frame_size; + size_t nbytes = nframes * ad->out_frame_size; void *buffer = g_malloc(nbytes); snd_pcm_hw_params_t *params; snd_pcm_format_t format; @@ -630,9 +751,9 @@ alsa_drain(void *data) } static void -alsa_cancel(void *data) +alsa_cancel(struct audio_output *ao) { - struct alsa_data *ad = data; + struct alsa_data *ad = (struct alsa_data *)ao; ad->period_position = 0; @@ -640,26 +761,34 @@ alsa_cancel(void *data) } static void -alsa_close(void *data) +alsa_close(struct audio_output *ao) { - struct alsa_data *ad = data; + struct alsa_data *ad = (struct alsa_data *)ao; snd_pcm_close(ad->pcm); } static size_t -alsa_play(void *data, const void *chunk, size_t size, GError **error) +alsa_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error) { - struct alsa_data *ad = data; + struct alsa_data *ad = (struct alsa_data *)ao; + + assert(size % ad->in_frame_size == 0); + + chunk = pcm_export(&ad->export, chunk, size, &size); + + assert(size % ad->out_frame_size == 0); - size /= ad->frame_size; + size /= ad->out_frame_size; while (true) { snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size); if (ret > 0) { ad->period_position = (ad->period_position + ret) % ad->period_frames; - return ret * ad->frame_size; + return pcm_export_source_size(&ad->export, + ret * ad->in_frame_size); } if (ret < 0 && ret != -EAGAIN && ret != -EINTR && @@ -671,11 +800,13 @@ alsa_play(void *data, const void *chunk, size_t size, GError **error) } } -const struct audio_output_plugin alsaPlugin = { +const struct audio_output_plugin alsa_output_plugin = { .name = "alsa", .test_default_device = alsa_test_default_device, .init = alsa_init, .finish = alsa_finish, + .enable = alsa_output_enable, + .disable = alsa_output_disable, .open = alsa_open, .play = alsa_play, .drain = alsa_drain, diff --git a/src/output/alsa_output_plugin.h b/src/output/alsa_output_plugin.h new file mode 100644 index 000000000..daa1f3615 --- /dev/null +++ b/src/output/alsa_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_ALSA_OUTPUT_PLUGIN_H +#define MPD_ALSA_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin alsa_output_plugin; + +#endif diff --git a/src/output/ao_plugin.c b/src/output/ao_output_plugin.c index 42ece5a3a..d7e577fa4 100644 --- a/src/output/ao_plugin.c +++ b/src/output/ao_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "ao_output_plugin.h" #include "output_api.h" #include <ao/ao.h> @@ -32,6 +33,8 @@ static const ao_sample_format OUR_AO_FORMAT_INITIALIZER; static unsigned ao_output_ref; struct ao_data { + struct audio_output base; + size_t write_size; int driver; ao_option *options; @@ -71,19 +74,24 @@ ao_output_error(GError **error_r) break; default: - error = strerror(errno); + error = g_strerror(errno); } g_set_error(error_r, ao_output_quark(), errno, "%s", error); } -static void * -ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, +static struct audio_output * +ao_output_init(const struct config_param *param, GError **error) { struct ao_data *ad = g_new(struct ao_data, 1); + + if (!ao_base_init(&ad->base, &ao_output_plugin, param, error)) { + g_free(ad); + return NULL; + } + ao_info *ai; const char *value; @@ -106,6 +114,7 @@ ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, g_set_error(error, ao_output_quark(), 0, "\"%s\" is not a valid ao driver", value); + ao_base_finish(&ad->base); g_free(ad); return NULL; } @@ -113,6 +122,7 @@ ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, if ((ai = ao_driver_info(ad->driver)) == NULL) { g_set_error(error, ao_output_quark(), 0, "problems getting driver info"); + ao_base_finish(&ad->base); g_free(ad); return NULL; } @@ -131,6 +141,7 @@ ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, g_set_error(error, ao_output_quark(), 0, "problems parsing options \"%s\"", options[i]); + ao_base_finish(&ad->base); g_free(ad); return NULL; } @@ -144,15 +155,16 @@ ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, g_strfreev(options); } - return ad; + return &ad->base; } static void -ao_output_finish(void *data) +ao_output_finish(struct audio_output *ao) { - struct ao_data *ad = (struct ao_data *)data; + struct ao_data *ad = (struct ao_data *)ao; ao_free_options(ad->options); + ao_base_finish(&ad->base); g_free(ad); ao_output_ref--; @@ -162,19 +174,19 @@ ao_output_finish(void *data) } static void -ao_output_close(void *data) +ao_output_close(struct audio_output *ao) { - struct ao_data *ad = (struct ao_data *)data; + struct ao_data *ad = (struct ao_data *)ao; ao_close(ad->device); } static bool -ao_output_open(void *data, struct audio_format *audio_format, +ao_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) { ao_sample_format format = OUR_AO_FORMAT_INITIALIZER; - struct ao_data *ad = (struct ao_data *)data; + struct ao_data *ad = (struct ao_data *)ao; switch (audio_format->format) { case SAMPLE_FORMAT_S8: @@ -226,10 +238,10 @@ static int ao_play_deconst(ao_device *device, const void *output_samples, } static size_t -ao_output_play(void *data, const void *chunk, size_t size, +ao_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error) { - struct ao_data *ad = (struct ao_data *)data; + struct ao_data *ad = (struct ao_data *)ao; if (size > ad->write_size) size = ad->write_size; diff --git a/src/output/ao_output_plugin.h b/src/output/ao_output_plugin.h new file mode 100644 index 000000000..9a3a47c05 --- /dev/null +++ b/src/output/ao_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_AO_OUTPUT_PLUGIN_H +#define MPD_AO_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin ao_output_plugin; + +#endif diff --git a/src/output/ffado_output_plugin.c b/src/output/ffado_output_plugin.c index 723698ed0..ba239a4ad 100644 --- a/src/output/ffado_output_plugin.c +++ b/src/output/ffado_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -30,6 +30,7 @@ */ #include "config.h" +#include "ffado_output_plugin.h" #include "output_api.h" #include "timer.h" @@ -53,6 +54,8 @@ struct mpd_ffado_stream { }; struct mpd_ffado_device { + struct audio_output base; + char *device_name; int verbose; unsigned period_size, nb_buffers; @@ -82,21 +85,26 @@ 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, +static struct audio_output * +ffado_init(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); + if (!ao_base_init(&fd->base, &ffado_output_plugin, param, error_r)) { + g_free(fd); + return NULL; + } + 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) { + ao_base_finish(&fd->base); g_set_error(error_r, ffado_output_quark(), 0, "invalid period_size setting"); return false; @@ -104,20 +112,22 @@ ffado_init(G_GNUC_UNUSED const struct audio_format *audio_format, fd->nb_buffers = config_get_block_unsigned(param, "nb_buffers", 3); if (fd->nb_buffers == 0 || fd->nb_buffers > 1024) { + ao_base_finish(&fd->base); g_set_error(error_r, ffado_output_quark(), 0, "invalid nb_buffers setting"); return false; } - return fd; + return &fd->base; } static void -ffado_finish(void *data) +ffado_finish(struct audio_output *ao) { - struct mpd_ffado_device *fd = data; + struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao; g_free(fd->device_name); + ao_base_finish(&fd->base); g_free(fd); } @@ -227,9 +237,10 @@ ffado_configure(struct mpd_ffado_device *fd, struct audio_format *audio_format, } static bool -ffado_open(void *data, struct audio_format *audio_format, GError **error_r) +ffado_open(struct audio_output *ao, struct audio_format *audio_format, + GError **error_r) { - struct mpd_ffado_device *fd = data; + struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao; /* will be converted to floating point, choose best input format */ @@ -273,9 +284,9 @@ ffado_open(void *data, struct audio_format *audio_format, GError **error_r) } static void -ffado_close(void *data) +ffado_close(struct audio_output *ao) { - struct mpd_ffado_device *fd = data; + struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao; ffado_streaming_stop(fd->dev); ffado_streaming_finish(fd->dev); @@ -287,9 +298,10 @@ ffado_close(void *data) } static size_t -ffado_play(void *data, const void *chunk, size_t size, GError **error_r) +ffado_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error_r) { - struct mpd_ffado_device *fd = data; + struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao; /* wait for prefious buffer to finish (if it was full) */ diff --git a/src/output/ffado_output_plugin.h b/src/output/ffado_output_plugin.h new file mode 100644 index 000000000..4dde01859 --- /dev/null +++ b/src/output/ffado_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_FFADO_OUTPUT_PLUGIN_H +#define MPD_FFADO_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin ffado_output_plugin; + +#endif diff --git a/src/output/fifo_output_plugin.c b/src/output/fifo_output_plugin.c index f4217ec4d..022be0b4a 100644 --- a/src/output/fifo_output_plugin.c +++ b/src/output/fifo_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "fifo_output_plugin.h" #include "output_api.h" #include "utils.h" #include "timer.h" @@ -38,11 +39,13 @@ #define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */ struct fifo_data { + struct audio_output base; + char *path; int input; int output; bool created; - Timer *timer; + struct timer *timer; }; /** @@ -80,7 +83,7 @@ static void fifo_delete(struct fifo_data *fd) if (unlink(fd->path) < 0) { g_warning("Could not remove FIFO \"%s\": %s", - fd->path, strerror(errno)); + fd->path, g_strerror(errno)); return; } @@ -112,7 +115,7 @@ fifo_make(struct fifo_data *fd, GError **error) if (mkfifo(fd->path, 0666) < 0) { g_set_error(error, fifo_output_quark(), errno, "Couldn't create FIFO \"%s\": %s", - fd->path, strerror(errno)); + fd->path, g_strerror(errno)); return false; } @@ -134,7 +137,7 @@ fifo_check(struct fifo_data *fd, GError **error) g_set_error(error, fifo_output_quark(), errno, "Failed to stat FIFO \"%s\": %s", - fd->path, strerror(errno)); + fd->path, g_strerror(errno)); return false; } @@ -158,7 +161,7 @@ fifo_open(struct fifo_data *fd, GError **error) if (fd->input < 0) { g_set_error(error, fifo_output_quark(), errno, "Could not open FIFO \"%s\" for reading: %s", - fd->path, strerror(errno)); + fd->path, g_strerror(errno)); fifo_close(fd); return false; } @@ -167,7 +170,7 @@ fifo_open(struct fifo_data *fd, GError **error) if (fd->output < 0) { g_set_error(error, fifo_output_quark(), errno, "Could not open FIFO \"%s\" for writing: %s", - fd->path, strerror(errno)); + fd->path, g_strerror(errno)); fifo_close(fd); return false; } @@ -175,54 +178,55 @@ fifo_open(struct fifo_data *fd, GError **error) return true; } -static void * -fifo_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, - GError **error) +static struct audio_output * +fifo_output_init(const struct config_param *param, + GError **error_r) { struct fifo_data *fd; - char *value, *path; - - value = config_dup_block_string(param, "path", NULL); - if (value == NULL) { - g_set_error(error, fifo_output_quark(), errno, - "No \"path\" parameter specified"); - return NULL; - } - path = parsePath(value); - g_free(value); + GError *error = NULL; + char *path = config_dup_block_path(param, "path", &error); if (!path) { - g_set_error(error, fifo_output_quark(), errno, - "Could not parse \"path\" parameter"); + if (error != NULL) + g_propagate_error(error_r, error); + else + g_set_error(error_r, fifo_output_quark(), 0, + "No \"path\" parameter specified"); return NULL; } fd = fifo_data_new(); fd->path = path; - if (!fifo_open(fd, error)) { + if (!ao_base_init(&fd->base, &fifo_output_plugin, param, error_r)) { + fifo_data_free(fd); + return NULL; + } + + if (!fifo_open(fd, error_r)) { + ao_base_finish(&fd->base); fifo_data_free(fd); return NULL; } - return fd; + return &fd->base; } static void -fifo_output_finish(void *data) +fifo_output_finish(struct audio_output *ao) { - struct fifo_data *fd = (struct fifo_data *)data; + struct fifo_data *fd = (struct fifo_data *)ao; fifo_close(fd); + ao_base_finish(&fd->base); fifo_data_free(fd); } static bool -fifo_output_open(void *data, struct audio_format *audio_format, +fifo_output_open(struct audio_output *ao, struct audio_format *audio_format, G_GNUC_UNUSED GError **error) { - struct fifo_data *fd = (struct fifo_data *)data; + struct fifo_data *fd = (struct fifo_data *)ao; fd->timer = timer_new(audio_format); @@ -230,17 +234,17 @@ fifo_output_open(void *data, struct audio_format *audio_format, } static void -fifo_output_close(void *data) +fifo_output_close(struct audio_output *ao) { - struct fifo_data *fd = (struct fifo_data *)data; + struct fifo_data *fd = (struct fifo_data *)ao; timer_free(fd->timer); } static void -fifo_output_cancel(void *data) +fifo_output_cancel(struct audio_output *ao) { - struct fifo_data *fd = (struct fifo_data *)data; + struct fifo_data *fd = (struct fifo_data *)ao; char buf[FIFO_BUFFER_SIZE]; int bytes = 1; @@ -251,22 +255,29 @@ fifo_output_cancel(void *data) if (bytes < 0 && errno != EAGAIN) { g_warning("Flush of FIFO \"%s\" failed: %s", - fd->path, strerror(errno)); + fd->path, g_strerror(errno)); } } +static unsigned +fifo_output_delay(struct audio_output *ao) +{ + struct fifo_data *fd = (struct fifo_data *)ao; + + return fd->timer->started + ? timer_delay(fd->timer) + : 0; +} + static size_t -fifo_output_play(void *data, const void *chunk, size_t size, +fifo_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error) { - struct fifo_data *fd = (struct fifo_data *)data; + struct fifo_data *fd = (struct fifo_data *)ao; ssize_t bytes; if (!fd->timer->started) timer_start(fd->timer); - else - timer_sync(fd->timer); - timer_add(fd->timer, size); while (true) { @@ -278,7 +289,7 @@ fifo_output_play(void *data, const void *chunk, size_t size, switch (errno) { case EAGAIN: /* The pipe is full, so empty it */ - fifo_output_cancel(fd); + fifo_output_cancel(&fd->base); continue; case EINTR: continue; @@ -298,6 +309,7 @@ const struct audio_output_plugin fifo_output_plugin = { .finish = fifo_output_finish, .open = fifo_output_open, .close = fifo_output_close, + .delay = fifo_output_delay, .play = fifo_output_play, .cancel = fifo_output_cancel, }; diff --git a/src/output/fifo_output_plugin.h b/src/output/fifo_output_plugin.h new file mode 100644 index 000000000..85f7985e1 --- /dev/null +++ b/src/output/fifo_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_FIFO_OUTPUT_PLUGIN_H +#define MPD_FIFO_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin fifo_output_plugin; + +#endif diff --git a/src/output/httpd_client.c b/src/output/httpd_client.c index 995c1f659..8efedc2b3 100644 --- a/src/output/httpd_client.c +++ b/src/output/httpd_client.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -93,6 +93,11 @@ struct httpd_client { */ size_t current_position; + /** + * If DLNA streaming was an option. + */ + bool dlna_streaming_requested; + /* ICY */ /** @@ -234,6 +239,15 @@ httpd_client_handle_line(struct httpd_client *client, const char *line) return true; } + if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) { + /* Send as dlna */ + client->dlna_streaming_requested = true; + /* metadata is not supported by dlna streaming, so disable it */ + client->metadata_supported = false; + client->metadata_requested = false; + return true; + } + /* expect more request headers */ return true; } @@ -285,16 +299,21 @@ httpd_client_send_response(struct httpd_client *client) assert(client != NULL); assert(client->state == RESPONSE); - if (!client->metadata_requested) { + if (client->dlna_streaming_requested) { g_snprintf(buffer, sizeof(buffer), - "HTTP/1.1 200 OK\r\n" + "HTTP/1.1 206 OK\r\n" "Content-Type: %s\r\n" + "Content-Length: 10000\r\n" + "Content-RangeX: 0-1000000/1000000\r\n" + "transferMode.dlna.org: Streaming\r\n" + "Accept-Ranges: bytes\r\n" "Connection: close\r\n" - "Pragma: no-cache\r\n" - "Cache-Control: no-cache, no-store\r\n" + "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" + "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n" "\r\n", client->httpd->content_type); - } else { + + } else if (client->metadata_requested) { gchar *metadata_header; metadata_header = icy_server_metadata_header( @@ -307,6 +326,16 @@ httpd_client_send_response(struct httpd_client *client) g_strlcpy(buffer, metadata_header, sizeof(buffer)); g_free(metadata_header); + + } else { /* revert to a normal HTTP request */ + g_snprintf(buffer, sizeof(buffer), + "HTTP/1.1 200 OK\r\n" + "Content-Type: %s\r\n" + "Connection: close\r\n" + "Pragma: no-cache\r\n" + "Cache-Control: no-cache, no-store\r\n" + "\r\n", + client->httpd->content_type); } status = g_io_channel_write_chars(client->channel, @@ -476,6 +505,7 @@ httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported) client->input = fifo_buffer_new(4096); client->state = REQUEST; + client->dlna_streaming_requested = false; client->metadata_supported = metadata_supported; client->metadata_requested = false; client->metadata_sent = true; diff --git a/src/output/httpd_client.h b/src/output/httpd_client.h index 7ebd0bbc0..739163f42 100644 --- a/src/output/httpd_client.h +++ b/src/output/httpd_client.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 277e70f11..5dcb8ab9b 100644 --- a/src/output/httpd_internal.h +++ b/src/output/httpd_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 MPD_OUTPUT_HTTPD_INTERNAL_H #define MPD_OUTPUT_HTTPD_INTERNAL_H +#include "output_internal.h" #include "timer.h" #include <glib.h> @@ -34,6 +35,8 @@ struct httpd_client; struct httpd_output { + struct audio_output base; + /** * True if the audio output is open and accepts client * connections. @@ -65,10 +68,10 @@ struct httpd_output { GMutex *mutex; /** - * A #Timer object to synchronize this output with the + * A #timer object to synchronize this output with the * wallclock. */ - Timer *timer; + struct timer *timer; /** * The listener socket. diff --git a/src/output/httpd_output_plugin.c b/src/output/httpd_output_plugin.c index 2c140a300..e7344320c 100644 --- a/src/output/httpd_output_plugin.c +++ b/src/output/httpd_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,12 +18,13 @@ */ #include "config.h" +#include "httpd_output_plugin.h" #include "httpd_internal.h" #include "httpd_client.h" #include "output_api.h" #include "encoder_plugin.h" #include "encoder_list.h" -#include "socket_util.h" +#include "resolver.h" #include "page.h" #include "icy_server.h" #include "fd_util.h" @@ -78,12 +79,16 @@ httpd_output_unbind(struct httpd_output *httpd) g_mutex_unlock(httpd->mutex); } -static void * -httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, +static struct audio_output * +httpd_output_init(const struct config_param *param, GError **error) { struct httpd_output *httpd = g_new(struct httpd_output, 1); + if (!ao_base_init(&httpd->base, &httpd_output_plugin, param, error)) { + g_free(httpd); + return NULL; + } + const char *encoder_name, *bind_to_address; const struct encoder_plugin *encoder_plugin; guint port; @@ -103,6 +108,7 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, if (encoder_plugin == NULL) { g_set_error(error, httpd_output_quark(), 0, "No such encoder: %s", encoder_name); + ao_base_finish(&httpd->base); g_free(httpd); return NULL; } @@ -120,8 +126,11 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, ? server_socket_add_host(httpd->server_socket, bind_to_address, port, error) : server_socket_add_port(httpd->server_socket, port, error); - if (!success) + if (!success) { + ao_base_finish(&httpd->base); + g_free(httpd); return NULL; + } /* initialize metadata */ httpd->metadata = NULL; @@ -130,8 +139,11 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, /* initialize encoder */ httpd->encoder = encoder_init(encoder_plugin, param, error); - if (httpd->encoder == NULL) + if (httpd->encoder == NULL) { + ao_base_finish(&httpd->base); + g_free(httpd); return NULL; + } /* determine content type */ httpd->content_type = encoder_get_mime_type(httpd->encoder); @@ -141,13 +153,13 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, httpd->mutex = g_mutex_new(); - return httpd; + return &httpd->base; } static void -httpd_output_finish(void *data) +httpd_output_finish(struct audio_output *ao) { - struct httpd_output *httpd = data; + struct httpd_output *httpd = (struct httpd_output *)ao; if (httpd->metadata) page_unref(httpd->metadata); @@ -155,6 +167,7 @@ httpd_output_finish(void *data) encoder_finish(httpd->encoder); server_socket_free(httpd->server_socket); g_mutex_free(httpd->mutex); + ao_base_finish(&httpd->base); g_free(httpd); } @@ -286,26 +299,26 @@ httpd_output_encoder_open(struct httpd_output *httpd, } static bool -httpd_output_enable(void *data, GError **error_r) +httpd_output_enable(struct audio_output *ao, GError **error_r) { - struct httpd_output *httpd = data; + struct httpd_output *httpd = (struct httpd_output *)ao; return httpd_output_bind(httpd, error_r); } static void -httpd_output_disable(void *data) +httpd_output_disable(struct audio_output *ao) { - struct httpd_output *httpd = data; + struct httpd_output *httpd = (struct httpd_output *)ao; httpd_output_unbind(httpd); } static bool -httpd_output_open(void *data, struct audio_format *audio_format, +httpd_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) { - struct httpd_output *httpd = data; + struct httpd_output *httpd = (struct httpd_output *)ao; bool success; g_mutex_lock(httpd->mutex); @@ -338,9 +351,10 @@ httpd_client_delete(gpointer data, G_GNUC_UNUSED gpointer user_data) httpd_client_free(client); } -static void httpd_output_close(void *data) +static void +httpd_output_close(struct audio_output *ao) { - struct httpd_output *httpd = data; + struct httpd_output *httpd = (struct httpd_output *)ao; g_mutex_lock(httpd->mutex); @@ -379,9 +393,9 @@ httpd_output_send_header(struct httpd_output *httpd, } static unsigned -httpd_output_delay(void *data) +httpd_output_delay(struct audio_output *ao) { - struct httpd_output *httpd = data; + struct httpd_output *httpd = (struct httpd_output *)ao; return httpd->timer->started ? timer_delay(httpd->timer) @@ -457,9 +471,10 @@ httpd_output_encode_and_play(struct httpd_output *httpd, } static size_t -httpd_output_play(void *data, const void *chunk, size_t size, GError **error) +httpd_output_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error) { - struct httpd_output *httpd = data; + struct httpd_output *httpd = (struct httpd_output *)ao; bool has_clients; g_mutex_lock(httpd->mutex); @@ -483,9 +498,9 @@ httpd_output_play(void *data, const void *chunk, size_t size, GError **error) } static bool -httpd_output_pause(void *data) +httpd_output_pause(struct audio_output *ao) { - struct httpd_output *httpd = data; + struct httpd_output *httpd = (struct httpd_output *)ao; g_mutex_lock(httpd->mutex); bool has_clients = httpd->clients != NULL; @@ -493,7 +508,7 @@ httpd_output_pause(void *data) if (has_clients) { static const char silence[1020]; - return httpd_output_play(data, silence, sizeof(silence), + return httpd_output_play(ao, silence, sizeof(silence), NULL) > 0; } else { g_usleep(100000); @@ -511,9 +526,9 @@ httpd_send_metadata(gpointer data, gpointer user_data) } static void -httpd_output_tag(void *data, const struct tag *tag) +httpd_output_tag(struct audio_output *ao, const struct tag *tag) { - struct httpd_output *httpd = data; + struct httpd_output *httpd = (struct httpd_output *)ao; assert(tag != NULL); @@ -570,9 +585,9 @@ httpd_client_cancel_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) } static void -httpd_output_cancel(void *data) +httpd_output_cancel(struct audio_output *ao) { - struct httpd_output *httpd = data; + struct httpd_output *httpd = (struct httpd_output *)ao; g_mutex_lock(httpd->mutex); g_list_foreach(httpd->clients, httpd_client_cancel_callback, NULL); diff --git a/src/output/httpd_output_plugin.h b/src/output/httpd_output_plugin.h new file mode 100644 index 000000000..d0eb1533f --- /dev/null +++ b/src/output/httpd_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HTTPD_OUTPUT_PLUGIN_H +#define MPD_HTTPD_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin httpd_output_plugin; + +#endif diff --git a/src/output/jack_output_plugin.c b/src/output/jack_output_plugin.c index c67fcd38a..a24cb8557 100644 --- a/src/output/jack_output_plugin.c +++ b/src/output/jack_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "jack_output_plugin.h" #include "output_api.h" #include <assert.h> @@ -43,6 +44,8 @@ enum { static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t); struct jack_data { + struct audio_output base; + /** * libjack options passed to jack_client_open(). */ @@ -304,14 +307,18 @@ parse_port_list(int line, const char *source, char **dest, GError **error_r) return n; } -static void * -mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, GError **error_r) +static struct audio_output * +mpd_jack_init(const struct config_param *param, GError **error_r) { - struct jack_data *jd; + struct jack_data *jd = g_new(struct jack_data, 1); + + if (!ao_base_init(&jd->base, &jack_output_plugin, param, error_r)) { + g_free(jd); + return NULL; + } + const char *value; - jd = g_new(struct jack_data, 1); jd->options = JackNullOption; jd->name = config_get_block_string(param, "client_name", NULL); @@ -374,13 +381,13 @@ mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format, jack_set_info_function(mpd_jack_info); #endif - return jd; + return &jd->base; } static void -mpd_jack_finish(void *data) +mpd_jack_finish(struct audio_output *ao) { - struct jack_data *jd = data; + struct jack_data *jd = (struct jack_data *)ao; for (unsigned i = 0; i < jd->num_source_ports; ++i) g_free(jd->source_ports[i]); @@ -388,13 +395,14 @@ mpd_jack_finish(void *data) for (unsigned i = 0; i < jd->num_destination_ports; ++i) g_free(jd->destination_ports[i]); + ao_base_finish(&jd->base); g_free(jd); } static bool -mpd_jack_enable(void *data, GError **error_r) +mpd_jack_enable(struct audio_output *ao, GError **error_r) { - struct jack_data *jd = (struct jack_data *)data; + struct jack_data *jd = (struct jack_data *)ao; for (unsigned i = 0; i < jd->num_source_ports; ++i) jd->ringbuffer[i] = NULL; @@ -403,9 +411,9 @@ mpd_jack_enable(void *data, GError **error_r) } static void -mpd_jack_disable(void *data) +mpd_jack_disable(struct audio_output *ao) { - struct jack_data *jd = (struct jack_data *)data; + struct jack_data *jd = (struct jack_data *)ao; if (jd->client != NULL) mpd_jack_disconnect(jd); @@ -568,9 +576,10 @@ mpd_jack_start(struct jack_data *jd, GError **error_r) } static bool -mpd_jack_open(void *data, struct audio_format *audio_format, GError **error_r) +mpd_jack_open(struct audio_output *ao, struct audio_format *audio_format, + GError **error_r) { - struct jack_data *jd = data; + struct jack_data *jd = (struct jack_data *)ao; assert(jd != NULL); @@ -592,9 +601,9 @@ mpd_jack_open(void *data, struct audio_format *audio_format, GError **error_r) } static void -mpd_jack_close(G_GNUC_UNUSED void *data) +mpd_jack_close(G_GNUC_UNUSED struct audio_output *ao) { - struct jack_data *jd = data; + struct jack_data *jd = (struct jack_data *)ao; mpd_jack_stop(jd); } @@ -664,9 +673,10 @@ mpd_jack_write_samples(struct jack_data *jd, const void *src, } static size_t -mpd_jack_play(void *data, const void *chunk, size_t size, GError **error_r) +mpd_jack_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error_r) { - struct jack_data *jd = data; + struct jack_data *jd = (struct jack_data *)ao; const size_t frame_size = audio_format_frame_size(&jd->audio_format); size_t space = 0, space1; @@ -708,9 +718,9 @@ mpd_jack_play(void *data, const void *chunk, size_t size, GError **error_r) } static bool -mpd_jack_pause(void *data) +mpd_jack_pause(struct audio_output *ao) { - struct jack_data *jd = data; + struct jack_data *jd = (struct jack_data *)ao; if (jd->shutdown) return false; diff --git a/src/output/jack_output_plugin.h b/src/output/jack_output_plugin.h new file mode 100644 index 000000000..2f94ae7dc --- /dev/null +++ b/src/output/jack_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_JACK_OUTPUT_PLUGIN_H +#define MPD_JACK_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin jack_output_plugin; + +#endif diff --git a/src/output/mvp_plugin.c b/src/output/mvp_output_plugin.c index 6cc8fa34e..37e0f7c93 100644 --- a/src/output/mvp_plugin.c +++ b/src/output/mvp_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 "mvp_output_plugin.h" #include "output_api.h" #include "fd_util.h" @@ -69,6 +70,8 @@ typedef struct { #define MVP_GET_AUD_REGS _IOW('a',28,aud_ctl_regs_t*) struct mvp_data { + struct audio_output base; + struct audio_format audio_format; int fd; }; @@ -125,26 +128,31 @@ mvp_output_test_default_device(void) } g_warning("Error opening PCM device \"/dev/adec_pcm\": %s\n", - strerror(errno)); + g_strerror(errno)); return false; } -static void * -mvp_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, - G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error) +static struct audio_output * +mvp_output_init(G_GNUC_UNUSED const struct config_param *param, GError **error) { struct mvp_data *md = g_new(struct mvp_data, 1); + + if (!ao_base_init(&md->base, &mvp_output_plugin, param, error)) { + g_free(md); + return NULL; + } + md->fd = -1; - return md; + return &md->base; } static void -mvp_output_finish(void *data) +mvp_output_finish(struct audio_output *ao) { - struct mvp_data *md = data; + struct mvp_data *md = (struct mvp_data *)ao; + ao_base_finish(&md->base); g_free(md); } @@ -225,9 +233,10 @@ mvp_set_pcm_params(struct mvp_data *md, struct audio_format *audio_format, } static bool -mvp_output_open(void *data, struct audio_format *audio_format, GError **error) +mvp_output_open(struct audio_output *ao, struct audio_format *audio_format, + GError **error) { - struct mvp_data *md = data; + struct mvp_data *md = (struct mvp_data *)ao; long long int stc = 0; int mix[5] = { 0, 2, 7, 1, 0 }; bool success; @@ -236,32 +245,32 @@ mvp_output_open(void *data, struct audio_format *audio_format, GError **error) if (md->fd < 0) { g_set_error(error, mvp_output_quark(), errno, "Error opening /dev/adec_pcm: %s", - strerror(errno)); + g_strerror(errno)); return false; } if (ioctl(md->fd, MVP_SET_AUD_SRC, 1) < 0) { g_set_error(error, mvp_output_quark(), errno, "Error setting audio source: %s", - strerror(errno)); + g_strerror(errno)); return false; } if (ioctl(md->fd, MVP_SET_AUD_STREAMTYPE, 0) < 0) { g_set_error(error, mvp_output_quark(), errno, "Error setting audio streamtype: %s", - strerror(errno)); + g_strerror(errno)); return false; } if (ioctl(md->fd, MVP_SET_AUD_FORMAT, &mix) < 0) { g_set_error(error, mvp_output_quark(), errno, "Error setting audio format: %s", - strerror(errno)); + g_strerror(errno)); return false; } ioctl(md->fd, MVP_SET_AUD_STC, &stc); if (ioctl(md->fd, MVP_SET_AUD_BYPASS, 1) < 0) { g_set_error(error, mvp_output_quark(), errno, "Error setting audio streamtype: %s", - strerror(errno)); + g_strerror(errno)); return false; } @@ -273,17 +282,17 @@ mvp_output_open(void *data, struct audio_format *audio_format, GError **error) return true; } -static void mvp_output_close(void *data) +static void mvp_output_close(struct audio_output *ao) { - struct mvp_data *md = data; + struct mvp_data *md = (struct mvp_data *)ao; if (md->fd >= 0) close(md->fd); md->fd = -1; } -static void mvp_output_cancel(void *data) +static void mvp_output_cancel(struct audio_output *ao) { - struct mvp_data *md = data; + struct mvp_data *md = (struct mvp_data *)ao; if (md->fd >= 0) { ioctl(md->fd, MVP_SET_AUD_RESET, 0x11); close(md->fd); @@ -292,16 +301,17 @@ static void mvp_output_cancel(void *data) } static size_t -mvp_output_play(void *data, const void *chunk, size_t size, GError **error) +mvp_output_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error) { - struct mvp_data *md = data; + struct mvp_data *md = (struct mvp_data *)ao; ssize_t ret; /* reopen the device since it was closed by dropBufferedAudio */ if (md->fd < 0) { bool success; - success = mvp_output_open(md, &md->audio_format, error); + success = mvp_output_open(ao, &md->audio_format, error); if (!success) return 0; } @@ -316,7 +326,7 @@ mvp_output_play(void *data, const void *chunk, size_t size, GError **error) continue; g_set_error(error, mvp_output_quark(), errno, - "Failed to write: %s", strerror(errno)); + "Failed to write: %s", g_strerror(errno)); return 0; } } diff --git a/src/output/mvp_output_plugin.h b/src/output/mvp_output_plugin.h new file mode 100644 index 000000000..e403de2b7 --- /dev/null +++ b/src/output/mvp_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_MVP_OUTPUT_PLUGIN_H +#define MPD_MVP_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin mvp_output_plugin; + +#endif diff --git a/src/output/null_plugin.c b/src/output/null_output_plugin.c index 89abbd91f..9d7588fff 100644 --- a/src/output/null_plugin.c +++ b/src/output/null_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "null_output_plugin.h" #include "output_api.h" #include "timer.h" @@ -26,39 +27,42 @@ #include <assert.h> struct null_data { + struct audio_output base; + bool sync; - Timer *timer; + struct timer *timer; }; -static void * -null_init(G_GNUC_UNUSED const struct audio_format *audio_format, - G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error) +static struct audio_output * +null_init(const struct config_param *param, GError **error_r) { struct null_data *nd = g_new(struct null_data, 1); + if (!ao_base_init(&nd->base, &null_output_plugin, param, error_r)) { + g_free(nd); + return NULL; + } + nd->sync = config_get_block_bool(param, "sync", true); - nd->timer = NULL; - return nd; + return &nd->base; } static void -null_finish(void *data) +null_finish(struct audio_output *ao) { - struct null_data *nd = data; - - assert(nd->timer == NULL); + struct null_data *nd = (struct null_data *)ao; + ao_base_finish(&nd->base); g_free(nd); } static bool -null_open(void *data, struct audio_format *audio_format, +null_open(struct audio_output *ao, struct audio_format *audio_format, G_GNUC_UNUSED GError **error) { - struct null_data *nd = data; + struct null_data *nd = (struct null_data *)ao; if (nd->sync) nd->timer = timer_new(audio_format); @@ -67,40 +71,45 @@ null_open(void *data, struct audio_format *audio_format, } static void -null_close(void *data) +null_close(struct audio_output *ao) { - struct null_data *nd = data; + struct null_data *nd = (struct null_data *)ao; - if (nd->timer != NULL) { + if (nd->sync) timer_free(nd->timer); - nd->timer = NULL; - } +} + +static unsigned +null_delay(struct audio_output *ao) +{ + struct null_data *nd = (struct null_data *)ao; + + return nd->sync && nd->timer->started + ? timer_delay(nd->timer) + : 0; } static size_t -null_play(void *data, G_GNUC_UNUSED const void *chunk, size_t size, +null_play(struct audio_output *ao, G_GNUC_UNUSED const void *chunk, size_t size, G_GNUC_UNUSED GError **error) { - struct null_data *nd = data; - Timer *timer = nd->timer; + struct null_data *nd = (struct null_data *)ao; + struct timer *timer = nd->timer; if (!nd->sync) return size; if (!timer->started) timer_start(timer); - else - timer_sync(timer); - timer_add(timer, size); return size; } static void -null_cancel(void *data) +null_cancel(struct audio_output *ao) { - struct null_data *nd = data; + struct null_data *nd = (struct null_data *)ao; if (!nd->sync) return; @@ -114,6 +123,7 @@ const struct audio_output_plugin null_output_plugin = { .finish = null_finish, .open = null_open, .close = null_close, + .delay = null_delay, .play = null_play, .cancel = null_cancel, }; diff --git a/src/output/null_output_plugin.h b/src/output/null_output_plugin.h new file mode 100644 index 000000000..392bf0aa3 --- /dev/null +++ b/src/output/null_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_NULL_OUTPUT_PLUGIN_H +#define MPD_NULL_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin null_output_plugin; + +#endif diff --git a/src/output/openal_plugin.c b/src/output/openal_output_plugin.c index e5db8ac34..ebd35ef12 100644 --- a/src/output/openal_plugin.c +++ b/src/output/openal_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,8 +18,8 @@ */ #include "config.h" +#include "openal_output_plugin.h" #include "output_api.h" -#include "timer.h" #include <glib.h> @@ -38,12 +38,13 @@ #define NUM_BUFFERS 16 struct openal_data { + struct audio_output base; + const char *device_name; ALCdevice *device; ALCcontext *context; - Timer *timer; ALuint buffers[NUM_BUFFERS]; - int filled; + unsigned filled; ALuint source; ALenum format; ALuint frequency; @@ -80,6 +81,29 @@ openal_audio_format(struct audio_format *audio_format) } } +G_GNUC_PURE +static inline ALint +openal_get_source_i(const struct openal_data *od, ALenum param) +{ + ALint value; + alGetSourcei(od->source, param, &value); + return value; +} + +G_GNUC_PURE +static inline bool +openal_has_processed(const struct openal_data *od) +{ + return openal_get_source_i(od, AL_BUFFERS_PROCESSED) > 0; +} + +G_GNUC_PURE +static inline ALint +openal_is_playing(const struct openal_data *od) +{ + return openal_get_source_i(od, AL_SOURCE_STATE) == AL_PLAYING; +} + static bool openal_setup_context(struct openal_data *od, GError **error) @@ -106,23 +130,8 @@ openal_setup_context(struct openal_data *od, 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) +static struct audio_output * +openal_init(const struct config_param *param, GError **error_r) { const char *device_name = config_get_block_string(param, "device", NULL); struct openal_data *od; @@ -132,35 +141,33 @@ openal_init(G_GNUC_UNUSED const struct audio_format *audio_format, } od = g_new(struct openal_data, 1); + if (!ao_base_init(&od->base, &openal_output_plugin, param, error_r)) { + g_free(od); + return NULL; + } + od->device_name = device_name; - return od; + return &od->base; } static void -openal_finish(void *data) +openal_finish(struct audio_output *ao) { - struct openal_data *od = data; + struct openal_data *od = (struct openal_data *)ao; + ao_base_finish(&od->base); g_free(od); } static bool -openal_open(void *data, struct audio_format *audio_format, +openal_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) { - struct openal_data *od = data; + struct openal_data *od = (struct openal_data *)ao; 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; } @@ -184,18 +191,16 @@ openal_open(void *data, struct audio_format *audio_format, } od->filled = 0; - od->timer = timer_new(audio_format); od->frequency = audio_format->sample_rate; return true; } static void -openal_close(void *data) +openal_close(struct audio_output *ao) { - struct openal_data *od = data; + struct openal_data *od = (struct openal_data *)ao; - timer_free(od->timer); alcMakeContextCurrent(od->context); alDeleteSources(1, &od->source); alDeleteBuffers(NUM_BUFFERS, od->buffers); @@ -203,61 +208,63 @@ openal_close(void *data) alcCloseDevice(od->device); } +static unsigned +openal_delay(struct audio_output *ao) +{ + struct openal_data *od = (struct openal_data *)ao; + + return od->filled < NUM_BUFFERS || openal_has_processed(od) + ? 0 + /* we don't know exactly how long we must wait for the + next buffer to finish, so this is a random + guess: */ + : 50; +} + static size_t -openal_play(void *data, const void *chunk, size_t size, +openal_play(struct audio_output *ao, const void *chunk, size_t size, G_GNUC_UNUSED GError **error) { - struct openal_data *od = data; + struct openal_data *od = (struct openal_data *)ao; 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); - } + while (!openal_has_processed(od)) + g_usleep(10); 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) { + if (!openal_is_playing(od)) alSourcePlay(od->source); - } return size; } static void -openal_cancel(void *data) +openal_cancel(struct audio_output *ao) { - struct openal_data *od = data; + struct openal_data *od = (struct openal_data *)ao; od->filled = 0; alcMakeContextCurrent(od->context); alSourceStop(od->source); - openal_unqueue_buffers(od); + + /* force-unqueue all buffers */ + alSourcei(od->source, AL_BUFFER, 0); + od->filled = 0; } const struct audio_output_plugin openal_output_plugin = { @@ -266,6 +273,7 @@ const struct audio_output_plugin openal_output_plugin = { .finish = openal_finish, .open = openal_open, .close = openal_close, + .delay = openal_delay, .play = openal_play, .cancel = openal_cancel, }; diff --git a/src/output/openal_output_plugin.h b/src/output/openal_output_plugin.h new file mode 100644 index 000000000..25f6ccf46 --- /dev/null +++ b/src/output/openal_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_OPENAL_OUTPUT_PLUGIN_H +#define MPD_OPENAL_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin openal_output_plugin; + +#endif diff --git a/src/output/oss_plugin.c b/src/output/oss_output_plugin.c index 9261b423c..e366a4537 100644 --- a/src/output/oss_plugin.c +++ b/src/output/oss_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,9 +18,11 @@ */ #include "config.h" +#include "oss_output_plugin.h" #include "output_api.h" #include "mixer_list.h" #include "fd_util.h" +#include "glib_compat.h" #include <glib.h> @@ -50,7 +52,17 @@ #undef AFMT_S24_NE #endif +#ifdef AFMT_S24_PACKED +#include "pcm_export.h" +#endif + struct oss_data { + struct audio_output base; + +#ifdef AFMT_S24_PACKED + struct pcm_export_state export; +#endif + int fd; const char *device; @@ -59,6 +71,12 @@ struct oss_data { * the device after cancel(). */ struct audio_format audio_format; + + /** + * The current OSS audio format. This is needed to reopen the + * device after cancel(). + */ + int oss_format; }; /** @@ -136,13 +154,13 @@ oss_output_test_default_device(void) return true; } g_warning("Error opening OSS device \"%s\": %s\n", - default_devices[i], strerror(errno)); + default_devices[i], g_strerror(errno)); } return false; } -static void * +static struct audio_output * oss_open_default(GError **error) { int i; @@ -153,8 +171,14 @@ oss_open_default(GError **error) ret[i] = oss_stat_device(default_devices[i], &err[i]); if (ret[i] == OSS_STAT_NO_ERROR) { struct oss_data *od = oss_data_new(); + if (!ao_base_init(&od->base, &oss_output_plugin, NULL, + error)) { + g_free(od); + return NULL; + } + od->device = default_devices[i]; - return od; + return &od->base; } } @@ -175,7 +199,7 @@ oss_open_default(GError **error) break; case OSS_STAT_OTHER: g_warning("Error accessing %s: %s\n", - dev, strerror(err[i])); + dev, g_strerror(err[i])); } } @@ -184,29 +208,55 @@ oss_open_default(GError **error) return NULL; } -static void * -oss_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, - GError **error) +static struct audio_output * +oss_output_init(const struct config_param *param, GError **error) { const char *device = config_get_block_string(param, "device", NULL); if (device != NULL) { struct oss_data *od = oss_data_new(); + if (!ao_base_init(&od->base, &oss_output_plugin, param, + error)) { + g_free(od); + return NULL; + } + od->device = device; - return od; + return &od->base; } return oss_open_default(error); } static void -oss_output_finish(void *data) +oss_output_finish(struct audio_output *ao) { - struct oss_data *od = data; + struct oss_data *od = (struct oss_data *)ao; + ao_base_finish(&od->base); oss_data_free(od); } +#ifdef AFMT_S24_PACKED + +static bool +oss_output_enable(struct audio_output *ao, G_GNUC_UNUSED GError **error_r) +{ + struct oss_data *od = (struct oss_data *)ao; + + pcm_export_init(&od->export); + return true; +} + +static void +oss_output_disable(struct audio_output *ao) +{ + struct oss_data *od = (struct oss_data *)ao; + + pcm_export_deinit(&od->export); +} + +#endif + static void oss_close(struct oss_data *od) { @@ -381,6 +431,8 @@ sample_format_to_oss(enum sample_format format) { switch (format) { case SAMPLE_FORMAT_UNDEFINED: + case SAMPLE_FORMAT_FLOAT: + case SAMPLE_FORMAT_DSD: return AFMT_QUERY; case SAMPLE_FORMAT_S8: @@ -389,13 +441,6 @@ sample_format_to_oss(enum sample_format format) 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; @@ -430,7 +475,7 @@ sample_format_from_oss(int format) #ifdef AFMT_S24_PACKED case AFMT_S24_PACKED: - return SAMPLE_FORMAT_S24; + return SAMPLE_FORMAT_S24_P32; #endif #ifdef AFMT_S24_NE @@ -449,33 +494,83 @@ sample_format_from_oss(int format) } /** + * Probe one sample format. + * + * @return the selected sample format or SAMPLE_FORMAT_UNDEFINED on + * error + */ +static enum oss_setup_result +oss_probe_sample_format(int fd, enum sample_format sample_format, + enum sample_format *sample_format_r, + int *oss_format_r, +#ifdef AFMT_S24_PACKED + struct pcm_export_state *export, +#endif + GError **error_r) +{ + int oss_format = sample_format_to_oss(sample_format); + if (oss_format == AFMT_QUERY) + return UNSUPPORTED; + + enum oss_setup_result result = + oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, + &oss_format, + "Failed to set sample format", error_r); + +#ifdef AFMT_S24_PACKED + if (result == UNSUPPORTED && sample_format == SAMPLE_FORMAT_S24_P32) { + /* if the driver doesn't support padded 24 bit, try + packed 24 bit */ + oss_format = AFMT_S24_PACKED; + result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, + &oss_format, + "Failed to set sample format", error_r); + } +#endif + + if (result != SUCCESS) + return result; + + sample_format = sample_format_from_oss(oss_format); + if (sample_format == SAMPLE_FORMAT_UNDEFINED) + return UNSUPPORTED; + + *sample_format_r = sample_format; + *oss_format_r = oss_format; + +#ifdef AFMT_S24_PACKED + pcm_export_open(export, sample_format, 0, false, false, + oss_format == AFMT_S24_PACKED, + oss_format == AFMT_S24_PACKED && + G_BYTE_ORDER != G_LITTLE_ENDIAN); +#endif + + return SUCCESS; +} + +/** * 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, + int *oss_format_r, +#ifdef AFMT_S24_PACKED + struct pcm_export_state *export, +#endif 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; + enum oss_setup_result result = + oss_probe_sample_format(fd, audio_format->format, + &mpd_format, oss_format_r, +#ifdef AFMT_S24_PACKED + export, +#endif + 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; - -#ifdef AFMT_S24_PACKED - if (oss_format == AFMT_S24_PACKED) - audio_format->reverse_endian = - G_BYTE_ORDER != G_LITTLE_ENDIAN; -#endif return true; case ERROR: @@ -485,13 +580,15 @@ oss_setup_sample_format(int fd, struct audio_format *audio_format, break; } + if (result != UNSUPPORTED) + return result == SUCCESS; + /* 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 */ @@ -503,26 +600,15 @@ oss_setup_sample_format(int fd, struct audio_format *audio_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); + result = oss_probe_sample_format(fd, mpd_format, + &mpd_format, oss_format_r, +#ifdef AFMT_S24_PACKED + export, +#endif + 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; - -#ifdef AFMT_S24_PACKED - if (oss_format == AFMT_S24_PACKED) - audio_format->reverse_endian = - G_BYTE_ORDER != G_LITTLE_ENDIAN; -#endif return true; case ERROR: @@ -533,7 +619,8 @@ oss_setup_sample_format(int fd, struct audio_format *audio_format, } } - g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg); + g_set_error_literal(error_r, oss_output_quark(), EINVAL, + "Failed to set sample format"); return false; } @@ -546,7 +633,11 @@ oss_setup(struct oss_data *od, struct audio_format *audio_format, { 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); + oss_setup_sample_format(od->fd, audio_format, &od->oss_format, +#ifdef AFMT_S24_PACKED + &od->export, +#endif + error_r); } /** @@ -561,7 +652,7 @@ oss_reopen(struct oss_data *od, GError **error_r) if (od->fd < 0) { g_set_error(error_r, oss_output_quark(), errno, "Error opening OSS device \"%s\": %s", - od->device, strerror(errno)); + od->device, g_strerror(errno)); return false; } @@ -590,9 +681,8 @@ oss_reopen(struct oss_data *od, GError **error_r) } 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), + od->oss_format, msg3, error_r); if (result != SUCCESS) { oss_close(od); @@ -606,15 +696,16 @@ oss_reopen(struct oss_data *od, GError **error_r) } static bool -oss_output_open(void *data, struct audio_format *audio_format, GError **error) +oss_output_open(struct audio_output *ao, struct audio_format *audio_format, + GError **error) { - struct oss_data *od = data; + struct oss_data *od = (struct oss_data *)ao; 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)); + od->device, g_strerror(errno)); return false; } @@ -628,17 +719,17 @@ oss_output_open(void *data, struct audio_format *audio_format, GError **error) } static void -oss_output_close(void *data) +oss_output_close(struct audio_output *ao) { - struct oss_data *od = data; + struct oss_data *od = (struct oss_data *)ao; oss_close(od); } static void -oss_output_cancel(void *data) +oss_output_cancel(struct audio_output *ao) { - struct oss_data *od = data; + struct oss_data *od = (struct oss_data *)ao; if (od->fd >= 0) { ioctl(od->fd, SNDCTL_DSP_RESET, 0); @@ -647,24 +738,33 @@ oss_output_cancel(void *data) } static size_t -oss_output_play(void *data, const void *chunk, size_t size, GError **error) +oss_output_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error) { - struct oss_data *od = data; + struct oss_data *od = (struct oss_data *)ao; ssize_t ret; /* reopen the device since it was closed by dropBufferedAudio */ if (od->fd < 0 && !oss_reopen(od, error)) return 0; +#ifdef AFMT_S24_PACKED + chunk = pcm_export(&od->export, chunk, size, &size); +#endif + while (true) { ret = write(od->fd, chunk, size); - if (ret > 0) - return (size_t)ret; + if (ret > 0) { +#ifdef AFMT_S24_PACKED + ret = pcm_export_source_size(&od->export, ret); +#endif + return ret; + } if (ret < 0 && errno != EINTR) { g_set_error(error, oss_output_quark(), errno, "Write error on %s: %s", - od->device, strerror(errno)); + od->device, g_strerror(errno)); return 0; } } @@ -675,6 +775,10 @@ const struct audio_output_plugin oss_output_plugin = { .test_default_device = oss_output_test_default_device, .init = oss_output_init, .finish = oss_output_finish, +#ifdef AFMT_S24_PACKED + .enable = oss_output_enable, + .disable = oss_output_disable, +#endif .open = oss_output_open, .close = oss_output_close, .play = oss_output_play, diff --git a/src/output/oss_output_plugin.h b/src/output/oss_output_plugin.h new file mode 100644 index 000000000..2aecc2b3a --- /dev/null +++ b/src/output/oss_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_OSS_OUTPUT_PLUGIN_H +#define MPD_OSS_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin oss_output_plugin; + +#endif diff --git a/src/output/osx_output_plugin.c b/src/output/osx_output_plugin.c new file mode 100644 index 000000000..fbba81749 --- /dev/null +++ b/src/output/osx_output_plugin.c @@ -0,0 +1,434 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "osx_output_plugin.h" +#include "output_api.h" +#include "fifo_buffer.h" + +#include <glib.h> +#include <CoreAudio/AudioHardware.h> +#include <AudioUnit/AudioUnit.h> +#include <CoreServices/CoreServices.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "osx" + +struct osx_output { + struct audio_output base; + + /* configuration settings */ + OSType component_subtype; + /* only applicable with kAudioUnitSubType_HALOutput */ + const char *device_name; + + AudioUnit au; + GMutex *mutex; + GCond *condition; + + struct fifo_buffer *buffer; +}; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +osx_output_quark(void) +{ + return g_quark_from_static_string("osx_output"); +} + +static bool +osx_output_test_default_device(void) +{ + /* on a Mac, this is always the default plugin, if nothing + else is configured */ + return true; +} + +static void +osx_output_configure(struct osx_output *oo, const struct config_param *param) +{ + const char *device = config_get_block_string(param, "device", NULL); + + if (device == NULL || 0 == strcmp(device, "default")) { + oo->component_subtype = kAudioUnitSubType_DefaultOutput; + oo->device_name = NULL; + } + else if (0 == strcmp(device, "system")) { + oo->component_subtype = kAudioUnitSubType_SystemOutput; + oo->device_name = NULL; + } + else { + oo->component_subtype = kAudioUnitSubType_HALOutput; + /* XXX am I supposed to g_strdup() this? */ + oo->device_name = device; + } +} + +static struct audio_output * +osx_output_init(const struct config_param *param, GError **error_r) +{ + struct osx_output *oo = g_new(struct osx_output, 1); + if (!ao_base_init(&oo->base, &osx_output_plugin, param, error_r)) { + g_free(oo); + return NULL; + } + + osx_output_configure(oo, param); + oo->mutex = g_mutex_new(); + oo->condition = g_cond_new(); + + return &oo->base; +} + +static void +osx_output_finish(struct audio_output *ao) +{ + struct osx_output *od = (struct osx_output *)ao; + + g_mutex_free(od->mutex); + g_cond_free(od->condition); + g_free(od); +} + +static bool +osx_output_set_device(struct osx_output *oo, GError **error) +{ + bool ret = true; + OSStatus status; + UInt32 size, numdevices; + AudioDeviceID *deviceids = NULL; + char name[256]; + unsigned int i; + + if (oo->component_subtype != kAudioUnitSubType_HALOutput) + goto done; + + /* how many audio devices are there? */ + status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, + &size, + NULL); + if (status != noErr) { + g_set_error(error, osx_output_quark(), status, + "Unable to determine number of OS X audio devices: %s", + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + + /* what are the available audio device IDs? */ + numdevices = size / sizeof(AudioDeviceID); + deviceids = g_malloc(size); + status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, + &size, + deviceids); + if (status != noErr) { + g_set_error(error, osx_output_quark(), status, + "Unable to determine OS X audio device IDs: %s", + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + + /* which audio device matches oo->device_name? */ + for (i = 0; i < numdevices; i++) { + size = sizeof(name); + status = AudioDeviceGetProperty(deviceids[i], 0, false, + kAudioDevicePropertyDeviceName, + &size, name); + if (status != noErr) { + g_set_error(error, osx_output_quark(), status, + "Unable to determine OS X device name " + "(device %u): %s", + (unsigned int) deviceids[i], + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + if (strcmp(oo->device_name, name) == 0) { + g_debug("found matching device: ID=%u, name=%s", + (unsigned int) deviceids[i], name); + break; + } + } + if (i == numdevices) { + g_warning("Found no audio device with name '%s' " + "(will use default audio device)", + oo->device_name); + goto done; + } + + status = AudioUnitSetProperty(oo->au, + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, + 0, + &(deviceids[i]), + sizeof(AudioDeviceID)); + if (status != noErr) { + g_set_error(error, osx_output_quark(), status, + "Unable to set OS X audio output device: %s", + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + g_debug("set OS X audio output device ID=%u, name=%s", + (unsigned int) deviceids[i], name); + +done: + if (deviceids != NULL) + g_free(deviceids); + return ret; +} + +static OSStatus +osx_render(void *vdata, + G_GNUC_UNUSED AudioUnitRenderActionFlags *io_action_flags, + G_GNUC_UNUSED const AudioTimeStamp *in_timestamp, + G_GNUC_UNUSED UInt32 in_bus_number, + G_GNUC_UNUSED UInt32 in_number_frames, + AudioBufferList *buffer_list) +{ + struct osx_output *od = (struct osx_output *) vdata; + AudioBuffer *buffer = &buffer_list->mBuffers[0]; + size_t buffer_size = buffer->mDataByteSize; + + assert(od->buffer != NULL); + + g_mutex_lock(od->mutex); + + size_t nbytes; + const void *src = fifo_buffer_read(od->buffer, &nbytes); + + if (src != NULL) { + if (nbytes > buffer_size) + nbytes = buffer_size; + + memcpy(buffer->mData, src, nbytes); + fifo_buffer_consume(od->buffer, nbytes); + } else + nbytes = 0; + + g_cond_signal(od->condition); + g_mutex_unlock(od->mutex); + + if (nbytes < buffer_size) + memset((unsigned char*)buffer->mData + nbytes, 0, + buffer_size - nbytes); + + return 0; +} + +static bool +osx_output_enable(struct audio_output *ao, GError **error_r) +{ + struct osx_output *oo = (struct osx_output *)ao; + + ComponentDescription desc; + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = oo->component_subtype; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + Component comp = FindNextComponent(NULL, &desc); + if (comp == 0) { + g_set_error(error_r, osx_output_quark(), 0, + "Error finding OS X component"); + return false; + } + + OSStatus status = OpenAComponent(comp, &oo->au); + if (status != noErr) { + g_set_error(error_r, osx_output_quark(), status, + "Unable to open OS X component: %s", + GetMacOSStatusCommentString(status)); + return false; + } + + if (!osx_output_set_device(oo, error_r)) { + CloseComponent(oo->au); + return false; + } + + AURenderCallbackStruct callback; + callback.inputProc = osx_render; + callback.inputProcRefCon = oo; + + ComponentResult result = + AudioUnitSetProperty(oo->au, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, 0, + &callback, sizeof(callback)); + if (result != noErr) { + CloseComponent(oo->au); + g_set_error(error_r, osx_output_quark(), result, + "unable to set callback for OS X audio unit"); + return false; + } + + return true; +} + +static void +osx_output_disable(struct audio_output *ao) +{ + struct osx_output *oo = (struct osx_output *)ao; + + CloseComponent(oo->au); +} + +static void +osx_output_cancel(struct audio_output *ao) +{ + struct osx_output *od = (struct osx_output *)ao; + + g_mutex_lock(od->mutex); + fifo_buffer_clear(od->buffer); + g_mutex_unlock(od->mutex); +} + +static void +osx_output_close(struct audio_output *ao) +{ + struct osx_output *od = (struct osx_output *)ao; + + AudioOutputUnitStop(od->au); + AudioUnitUninitialize(od->au); + + fifo_buffer_free(od->buffer); +} + +static bool +osx_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) +{ + struct osx_output *od = (struct osx_output *)ao; + + AudioStreamBasicDescription stream_description; + stream_description.mSampleRate = audio_format->sample_rate; + stream_description.mFormatID = kAudioFormatLinearPCM; + stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; + + switch (audio_format->format) { + case SAMPLE_FORMAT_S8: + stream_description.mBitsPerChannel = 8; + break; + + case SAMPLE_FORMAT_S16: + stream_description.mBitsPerChannel = 16; + break; + + case SAMPLE_FORMAT_S32: + stream_description.mBitsPerChannel = 32; + break; + + default: + audio_format->format = SAMPLE_FORMAT_S32; + stream_description.mBitsPerChannel = 32; + break; + } + +#if G_BYTE_ORDER == G_BIG_ENDIAN + stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; +#endif + + stream_description.mBytesPerPacket = + audio_format_frame_size(audio_format); + stream_description.mFramesPerPacket = 1; + stream_description.mBytesPerFrame = stream_description.mBytesPerPacket; + stream_description.mChannelsPerFrame = audio_format->channels; + + ComponentResult result = + AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, 0, + &stream_description, + sizeof(stream_description)); + if (result != noErr) { + g_set_error(error, osx_output_quark(), result, + "Unable to set format on OS X device"); + return false; + } + + OSStatus status = AudioUnitInitialize(od->au); + if (status != noErr) { + g_set_error(error, osx_output_quark(), status, + "Unable to initialize OS X audio unit: %s", + GetMacOSStatusCommentString(status)); + return false; + } + + /* create a buffer of 1s */ + od->buffer = fifo_buffer_new(audio_format->sample_rate * + audio_format_frame_size(audio_format)); + + status = AudioOutputUnitStart(od->au); + if (status != 0) { + AudioUnitUninitialize(od->au); + g_set_error(error, osx_output_quark(), status, + "unable to start audio output: %s", + GetMacOSStatusCommentString(status)); + return false; + } + + return true; +} + +static size_t +osx_output_play(struct audio_output *ao, const void *chunk, size_t size, + G_GNUC_UNUSED GError **error) +{ + struct osx_output *od = (struct osx_output *)ao; + + g_mutex_lock(od->mutex); + + void *dest; + size_t max_length; + + while (true) { + dest = fifo_buffer_write(od->buffer, &max_length); + if (dest != NULL) + break; + + /* wait for some free space in the buffer */ + g_cond_wait(od->condition, od->mutex); + } + + if (size > max_length) + size = max_length; + + memcpy(dest, chunk, size); + fifo_buffer_append(od->buffer, size); + + g_mutex_unlock(od->mutex); + + return size; +} + +const struct audio_output_plugin osx_output_plugin = { + .name = "osx", + .test_default_device = osx_output_test_default_device, + .init = osx_output_init, + .finish = osx_output_finish, + .enable = osx_output_enable, + .disable = osx_output_disable, + .open = osx_output_open, + .close = osx_output_close, + .play = osx_output_play, + .cancel = osx_output_cancel, +}; diff --git a/src/output/osx_output_plugin.h b/src/output/osx_output_plugin.h new file mode 100644 index 000000000..814702d4f --- /dev/null +++ b/src/output/osx_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_OSX_OUTPUT_PLUGIN_H +#define MPD_OSX_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin osx_output_plugin; + +#endif diff --git a/src/output/osx_plugin.c b/src/output/osx_plugin.c deleted file mode 100644 index 501dcec10..000000000 --- a/src/output/osx_plugin.c +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright (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 "fifo_buffer.h" - -#include <glib.h> -#include <AudioUnit/AudioUnit.h> -#include <CoreServices/CoreServices.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "osx" - -struct osx_output { - AudioUnit au; - GMutex *mutex; - GCond *condition; - - struct fifo_buffer *buffer; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -osx_output_quark(void) -{ - return g_quark_from_static_string("osx_output"); -} - -static bool -osx_output_test_default_device(void) -{ - /* on a Mac, this is always the default plugin, if nothing - else is configured */ - return true; -} - -static void * -osx_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 osx_output *oo = g_new(struct osx_output, 1); - - oo->mutex = g_mutex_new(); - oo->condition = g_cond_new(); - - return oo; -} - -static void osx_output_finish(void *data) -{ - struct osx_output *od = data; - - g_mutex_free(od->mutex); - g_cond_free(od->condition); - g_free(od); -} - -static void osx_output_cancel(void *data) -{ - struct osx_output *od = data; - - g_mutex_lock(od->mutex); - fifo_buffer_clear(od->buffer); - g_mutex_unlock(od->mutex); -} - -static void osx_output_close(void *data) -{ - struct osx_output *od = data; - - AudioOutputUnitStop(od->au); - AudioUnitUninitialize(od->au); - CloseComponent(od->au); - - fifo_buffer_free(od->buffer); -} - -static OSStatus -osx_render(void *vdata, - G_GNUC_UNUSED AudioUnitRenderActionFlags *io_action_flags, - G_GNUC_UNUSED const AudioTimeStamp *in_timestamp, - G_GNUC_UNUSED UInt32 in_bus_number, - G_GNUC_UNUSED UInt32 in_number_frames, - AudioBufferList *buffer_list) -{ - struct osx_output *od = (struct osx_output *) vdata; - AudioBuffer *buffer = &buffer_list->mBuffers[0]; - size_t buffer_size = buffer->mDataByteSize; - - assert(od->buffer != NULL); - - g_mutex_lock(od->mutex); - - size_t nbytes; - const void *src = fifo_buffer_read(od->buffer, &nbytes); - - if (src != NULL) { - if (nbytes > buffer_size) - nbytes = buffer_size; - - memcpy(buffer->mData, src, nbytes); - fifo_buffer_consume(od->buffer, nbytes); - } else - nbytes = 0; - - g_cond_signal(od->condition); - g_mutex_unlock(od->mutex); - - if (nbytes < buffer_size) - memset((unsigned char*)buffer->mData + nbytes, 0, - buffer_size - nbytes); - - return 0; -} - -static bool -osx_output_open(void *data, struct audio_format *audio_format, GError **error) -{ - struct osx_output *od = data; - ComponentDescription desc; - Component comp; - AURenderCallbackStruct callback; - AudioStreamBasicDescription stream_description; - OSStatus status; - ComponentResult result; - - desc.componentType = kAudioUnitType_Output; - desc.componentSubType = kAudioUnitSubType_DefaultOutput; - desc.componentManufacturer = kAudioUnitManufacturer_Apple; - desc.componentFlags = 0; - desc.componentFlagsMask = 0; - - comp = FindNextComponent(NULL, &desc); - if (comp == 0) { - g_set_error(error, osx_output_quark(), 0, - "Error finding OS X component"); - return false; - } - - status = OpenAComponent(comp, &od->au); - if (status != noErr) { - g_set_error(error, osx_output_quark(), 0, - "Unable to open OS X component: %s", - GetMacOSStatusCommentString(status)); - return false; - } - - status = AudioUnitInitialize(od->au); - if (status != noErr) { - CloseComponent(od->au); - g_set_error(error, osx_output_quark(), 0, - "Unable to initialize OS X audio unit: %s", - GetMacOSStatusCommentString(status)); - return false; - } - - callback.inputProc = osx_render; - callback.inputProcRefCon = od; - - result = AudioUnitSetProperty(od->au, - kAudioUnitProperty_SetRenderCallback, - kAudioUnitScope_Input, 0, - &callback, sizeof(callback)); - if (result != noErr) { - AudioUnitUninitialize(od->au); - CloseComponent(od->au); - g_set_error(error, osx_output_quark(), 0, - "unable to set callback for OS X audio unit"); - return false; - } - - stream_description.mSampleRate = audio_format->sample_rate; - stream_description.mFormatID = kAudioFormatLinearPCM; - stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; - - 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; - } - -#if G_BYTE_ORDER == G_BIG_ENDIAN - stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; -#endif - - stream_description.mBytesPerPacket = - audio_format_frame_size(audio_format); - stream_description.mFramesPerPacket = 1; - stream_description.mBytesPerFrame = stream_description.mBytesPerPacket; - stream_description.mChannelsPerFrame = audio_format->channels; - - result = AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Input, 0, - &stream_description, - sizeof(stream_description)); - if (result != noErr) { - AudioUnitUninitialize(od->au); - CloseComponent(od->au); - g_set_error(error, osx_output_quark(), 0, - "Unable to set format on OS X device"); - return false; - } - - /* create a buffer of 1s */ - od->buffer = fifo_buffer_new(audio_format->sample_rate * - audio_format_frame_size(audio_format)); - - status = AudioOutputUnitStart(od->au); - if (status != 0) { - fifo_buffer_free(od->buffer); - g_set_error(error, osx_output_quark(), 0, - "unable to start audio output: %s", - GetMacOSStatusCommentString(status)); - return false; - } - - return true; -} - -static size_t -osx_output_play(void *data, const void *chunk, size_t size, - G_GNUC_UNUSED GError **error) -{ - struct osx_output *od = data; - - g_mutex_lock(od->mutex); - - void *dest; - size_t max_length; - - while (true) { - dest = fifo_buffer_write(od->buffer, &max_length); - if (dest != NULL) - break; - - /* wait for some free space in the buffer */ - g_cond_wait(od->condition, od->mutex); - } - - if (size > max_length) - size = max_length; - - memcpy(dest, chunk, size); - fifo_buffer_append(od->buffer, size); - - g_mutex_unlock(od->mutex); - - return size; -} - -const struct audio_output_plugin osxPlugin = { - .name = "osx", - .test_default_device = osx_output_test_default_device, - .init = osx_output_init, - .finish = osx_output_finish, - .open = osx_output_open, - .close = osx_output_close, - .play = osx_output_play, - .cancel = osx_output_cancel, -}; diff --git a/src/output/pipe_output_plugin.c b/src/output/pipe_output_plugin.c index 1d1aec7b1..90c5a5331 100644 --- a/src/output/pipe_output_plugin.c +++ b/src/output/pipe_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,12 +18,15 @@ */ #include "config.h" +#include "pipe_output_plugin.h" #include "output_api.h" #include <stdio.h> #include <errno.h> struct pipe_output { + struct audio_output base; + char *cmd; FILE *fh; }; @@ -37,13 +40,17 @@ pipe_output_quark(void) return g_quark_from_static_string("pipe_output"); } -static void * -pipe_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, +static struct audio_output * +pipe_output_init(const struct config_param *param, GError **error) { struct pipe_output *pd = g_new(struct pipe_output, 1); + if (!ao_base_init(&pd->base, &pipe_output_plugin, param, error)) { + g_free(pd); + return NULL; + } + pd->cmd = config_dup_block_string(param, "command", NULL); if (pd->cmd == NULL) { g_set_error(error, pipe_output_quark(), 0, @@ -51,23 +58,25 @@ pipe_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, return NULL; } - return pd; + return &pd->base; } static void -pipe_output_finish(void *data) +pipe_output_finish(struct audio_output *ao) { - struct pipe_output *pd = data; + struct pipe_output *pd = (struct pipe_output *)ao; g_free(pd->cmd); + ao_base_finish(&pd->base); g_free(pd); } static bool -pipe_output_open(void *data, G_GNUC_UNUSED struct audio_format *audio_format, +pipe_output_open(struct audio_output *ao, + G_GNUC_UNUSED struct audio_format *audio_format, G_GNUC_UNUSED GError **error) { - struct pipe_output *pd = data; + struct pipe_output *pd = (struct pipe_output *)ao; pd->fh = popen(pd->cmd, "w"); if (pd->fh == NULL) { @@ -81,17 +90,17 @@ pipe_output_open(void *data, G_GNUC_UNUSED struct audio_format *audio_format, } static void -pipe_output_close(void *data) +pipe_output_close(struct audio_output *ao) { - struct pipe_output *pd = data; + struct pipe_output *pd = (struct pipe_output *)ao; pclose(pd->fh); } static size_t -pipe_output_play(void *data, const void *chunk, size_t size, GError **error) +pipe_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error) { - struct pipe_output *pd = data; + struct pipe_output *pd = (struct pipe_output *)ao; size_t ret; ret = fwrite(chunk, 1, size, pd->fh); diff --git a/src/output/pipe_output_plugin.h b/src/output/pipe_output_plugin.h new file mode 100644 index 000000000..9f014f829 --- /dev/null +++ b/src/output/pipe_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_PIPE_OUTPUT_PLUGIN_H +#define MPD_PIPE_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin pipe_output_plugin; + +#endif diff --git a/src/output/pulse_output_plugin.c b/src/output/pulse_output_plugin.c index 5fe2f572e..0dc9be0e4 100644 --- a/src/output/pulse_output_plugin.c +++ b/src/output/pulse_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -31,11 +31,44 @@ #include <pulse/introspect.h> #include <pulse/subscribe.h> #include <pulse/error.h> +#include <pulse/version.h> #include <assert.h> +#include <stddef.h> #define MPD_PULSE_NAME "Music Player Daemon" +#if !defined(PA_CHECK_VERSION) +/** + * This macro was implemented in libpulse 0.9.16. + */ +#define PA_CHECK_VERSION(a,b,c) false +#endif + +struct pulse_output { + struct audio_output base; + + 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 +}; + /** * The quark used for GError.domain. */ @@ -46,6 +79,18 @@ pulse_output_quark(void) } void +pulse_output_lock(struct pulse_output *po) +{ + pa_threaded_mainloop_lock(po->mainloop); +} + +void +pulse_output_unlock(struct pulse_output *po) +{ + pa_threaded_mainloop_unlock(po->mainloop); +} + +void pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm) { assert(po != NULL); @@ -299,16 +344,19 @@ pulse_output_setup_context(struct pulse_output *po, GError **error_r) return true; } -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) +static struct audio_output * +pulse_output_init(const struct config_param *param, GError **error_r) { struct pulse_output *po; g_setenv("PULSE_PROP_media.role", "music", true); po = g_new(struct pulse_output, 1); + if (!ao_base_init(&po->base, &pulse_output_plugin, param, error_r)) { + g_free(po); + return NULL; + } + 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); @@ -318,21 +366,22 @@ pulse_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, po->context = NULL; po->stream = NULL; - return po; + return &po->base; } static void -pulse_output_finish(void *data) +pulse_output_finish(struct audio_output *ao) { - struct pulse_output *po = data; + struct pulse_output *po = (struct pulse_output *)ao; + ao_base_finish(&po->base); g_free(po); } static bool -pulse_output_enable(void *data, GError **error_r) +pulse_output_enable(struct audio_output *ao, GError **error_r) { - struct pulse_output *po = data; + struct pulse_output *po = (struct pulse_output *)ao; assert(po->mainloop == NULL); assert(po->context == NULL); @@ -376,9 +425,9 @@ pulse_output_enable(void *data, GError **error_r) } static void -pulse_output_disable(void *data) +pulse_output_disable(struct audio_output *ao) { - struct pulse_output *po = data; + struct pulse_output *po = (struct pulse_output *)ao; assert(po->mainloop != NULL); @@ -494,11 +543,46 @@ pulse_output_stream_write_cb(G_GNUC_UNUSED pa_stream *stream, size_t nbytes, pa_threaded_mainloop_signal(po->mainloop, 0); } +/** + * Create, set up and connect a context. + * + * Caller must lock the main loop. + * + * @return true on success, false on error + */ +static bool +pulse_output_setup_stream(struct pulse_output *po, const pa_sample_spec *ss, + GError **error_r) +{ + assert(po != NULL); + assert(po->context != NULL); + + 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))); + return false; + } + +#if PA_CHECK_VERSION(0,9,8) + pa_stream_set_suspended_callback(po->stream, + pulse_output_stream_suspended_cb, po); +#endif + + 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); + + return true; +} + static bool -pulse_output_open(void *data, struct audio_format *audio_format, +pulse_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error_r) { - struct pulse_output *po = data; + struct pulse_output *po = (struct pulse_output *)ao; pa_sample_spec ss; int error; @@ -540,25 +624,11 @@ pulse_output_open(void *data, struct audio_format *audio_format, /* 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))); + if (!pulse_output_setup_stream(po, &ss, error_r)) { pa_threaded_mainloop_unlock(po->mainloop); return false; } -#if PA_CHECK_VERSION(0,9,8) - pa_stream_set_suspended_callback(po->stream, - pulse_output_stream_suspended_cb, po); -#endif - - 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, @@ -583,9 +653,9 @@ pulse_output_open(void *data, struct audio_format *audio_format, } static void -pulse_output_close(void *data) +pulse_output_close(struct audio_output *ao) { - struct pulse_output *po = data; + struct pulse_output *po = (struct pulse_output *)ao; pa_operation *o; assert(po->mainloop != NULL); @@ -732,9 +802,10 @@ pulse_output_stream_pause(struct pulse_output *po, bool pause, } static size_t -pulse_output_play(void *data, const void *chunk, size_t size, GError **error_r) +pulse_output_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error_r) { - struct pulse_output *po = data; + struct pulse_output *po = (struct pulse_output *)ao; int error; assert(po->mainloop != NULL); @@ -802,9 +873,9 @@ pulse_output_play(void *data, const void *chunk, size_t size, GError **error_r) } static void -pulse_output_cancel(void *data) +pulse_output_cancel(struct audio_output *ao) { - struct pulse_output *po = data; + struct pulse_output *po = (struct pulse_output *)ao; pa_operation *o; assert(po->mainloop != NULL); @@ -834,9 +905,9 @@ pulse_output_cancel(void *data) } static bool -pulse_output_pause(void *data) +pulse_output_pause(struct audio_output *ao) { - struct pulse_output *po = data; + struct pulse_output *po = (struct pulse_output *)ao; GError *error = NULL; assert(po->mainloop != NULL); @@ -881,12 +952,12 @@ pulse_output_test_default_device(void) struct pulse_output *po; bool success; - po = pulse_output_init(NULL, NULL, NULL); + po = (struct pulse_output *)pulse_output_init(NULL, NULL); if (po == NULL) return false; success = pulse_output_wait_connection(po, NULL); - pulse_output_finish(po); + pulse_output_finish(&po->base); return success; } diff --git a/src/output/pulse_output_plugin.h b/src/output/pulse_output_plugin.h index 06e3aec43..02a51f27b 100644 --- a/src/output/pulse_output_plugin.h +++ b/src/output/pulse_output_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,43 +21,20 @@ #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 pulse_output; +struct pulse_mixer; struct pa_cvolume; -struct pulse_output { - const char *name; - const char *server; - const char *sink; - - struct pulse_mixer *mixer; +extern const struct audio_output_plugin pulse_output_plugin; - struct pa_threaded_mainloop *mainloop; - struct pa_context *context; - struct pa_stream *stream; - - size_t writable; +void +pulse_output_lock(struct pulse_output *po); -#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_unlock(struct pulse_output *po); void pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm); diff --git a/src/output/raop_output_plugin.c b/src/output/raop_output_plugin.c new file mode 100644 index 000000000..6177b9b7d --- /dev/null +++ b/src/output/raop_output_plugin.c @@ -0,0 +1,1056 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "raop_output_plugin.h" +#include "output_api.h" +#include "mixer_list.h" +#include "fd_util.h" +#include "ntp_server.h" +#include "rtsp_client.h" +#include "glib_compat.h" + +#include <glib.h> +#include <unistd.h> +#include <sys/time.h> +#include <openssl/aes.h> +#include <openssl/err.h> +#include <openssl/rand.h> +#include <openssl/rsa.h> +#include <openssl/engine.h> + +#ifndef WIN32 +#include <arpa/inet.h> +#include <sys/socket.h> +#include <netdb.h> +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "raop" + +struct play_state { + bool playing; + unsigned short seq_num; + unsigned int rtptime; + unsigned int sync_src; + unsigned int start_rtptime; + struct timeval start_time; + struct timeval last_send; +}; + +/*********************************************************************/ + +enum pause_state { + NO_PAUSE = 0, + OP_PAUSE, + NODATA_PAUSE, +}; + +#define MINIMUM_SAMPLE_SIZE 32 + +#define RAOP_FD_READ (1<<0) +#define RAOP_FD_WRITE (1<<1) + +/*********************************************************************/ + +struct encrypt_data { + AES_KEY ctx; + unsigned char iv[16]; // initialization vector for aes-cbc + unsigned char nv[16]; // next vector for aes-cbc + unsigned char key[16]; // key for aes-cbc +}; + +/*********************************************************************/ + +struct raop_data { + struct audio_output base; + + struct rtspcl_data *rtspcl; + const char *addr; // target host address + short rtsp_port; + struct sockaddr_in ctrl_addr; + struct sockaddr_in data_addr; + + bool is_master; + struct raop_data *next; + + unsigned volume; + + GMutex *control_mutex; + + bool started; + bool paused; +}; + +/*********************************************************************/ + +struct control_data { + unsigned short port; + int fd; +}; + +/*********************************************************************/ + +#define NUMSAMPLES 352 +#define RAOP_BUFFER_SIZE NUMSAMPLES * 4 +#define RAOP_HEADER_SIZE 12 +#define ALAC_MAX_HEADER_SIZE 8 +#define RAOP_MAX_PACKET_SIZE RAOP_BUFFER_SIZE + RAOP_HEADER_SIZE + ALAC_MAX_HEADER_SIZE + +// session +struct raop_session_data { + struct raop_data *raop_list; + struct ntp_server ntp; + struct control_data ctrl; + struct encrypt_data encrypt; + struct play_state play_state; + + int data_fd; + + unsigned char buffer[RAOP_BUFFER_SIZE]; + size_t bufferSize; + + unsigned char data[RAOP_MAX_PACKET_SIZE]; + int wblk_wsize; + int wblk_remsize; + + GMutex *data_mutex; + GMutex *list_mutex; +}; + +/*********************************************************************/ + +static struct raop_session_data *raop_session = NULL; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +raop_output_quark(void) +{ + return g_quark_from_static_string("raop_output"); +} + +static void +raop_session_free(struct raop_session_data *session) +{ + assert(session != NULL); + assert(session->raop_list == NULL); + + ntp_server_close(&session->ntp); + + if (session->data_mutex != NULL) + g_mutex_free(session->data_mutex); + + if (session->list_mutex != NULL) + g_mutex_free(session->list_mutex); + + if (raop_session->data_fd >= 0) + close_socket(raop_session->data_fd); + + if (raop_session->ctrl.fd >= 0) + close_socket(raop_session->ctrl.fd); + + g_free(session); +} + +static struct raop_session_data * +raop_session_new(GError **error_r) +{ + struct raop_session_data *session = g_new(struct raop_session_data, 1); + session->raop_list = NULL; + + session->data_mutex = g_mutex_new(); + session->list_mutex = g_mutex_new(); + + ntp_server_init(&session->ntp); + session->ctrl.port = 6001; + session->ctrl.fd = -1; + session->play_state.playing = false; + session->play_state.seq_num = (short) g_random_int(); + session->play_state.rtptime = g_random_int(); + session->play_state.sync_src = g_random_int(); + session->play_state.last_send.tv_sec = 0; + session->play_state.last_send.tv_usec = 0; + session->data_fd = -1; + + if (!RAND_bytes(session->encrypt.iv, sizeof(session->encrypt.iv)) || + !RAND_bytes(session->encrypt.key, sizeof(session->encrypt.key))) { + raop_session_free(session); + g_set_error(error_r, raop_output_quark(), 0, + "RAND_bytes error code=%ld", ERR_get_error()); + return NULL; + } + memcpy(session->encrypt.nv, session->encrypt.iv, sizeof(session->encrypt.nv)); + for (unsigned i = 0; i < 16; i++) { + printf("0x%x ", session->encrypt.key[i]); + } + printf("\n"); + AES_set_encrypt_key(session->encrypt.key, 128, &session->encrypt.ctx); + + memset(session->buffer, 0, RAOP_BUFFER_SIZE); + session->bufferSize = 0; + + return session; +} + +static struct raop_data * +new_raop_data(const struct config_param *param, GError **error_r) +{ + struct raop_data *ret = g_new(struct raop_data, 1); + if (!ao_base_init(&ret->base, &raop_output_plugin, param, error_r)) { + g_free(ret); + return NULL; + } + + ret->control_mutex = g_mutex_new(); + + ret->next = NULL; + ret->is_master = 0; + ret->started = 0; + ret->paused = 0; + + if (raop_session == NULL && + (raop_session = raop_session_new(error_r)) == NULL) { + g_mutex_free(ret->control_mutex); + ao_base_finish(&ret->base); + g_free(ret); + return NULL; + } + + return ret; +} + +/* + * remove one character from a string + * return the number of deleted characters + */ +static int +remove_char_from_string(char *str, char c) +{ + char *src, *dst; + + /* skip all characters that don't need to be copied */ + src = strchr(str, c); + if (!src) + return 0; + + for (dst = src; *src; src++) + if (*src != c) + *(dst++) = *src; + + *dst = '\0'; + + return src - dst; +} + +/* bind an opened socket to specified hostname and port. + * if hostname=NULL, use INADDR_ANY. + * if *port=0, use dynamically assigned port + */ +static int bind_host(int sd, char *hostname, unsigned long ulAddr, + unsigned short *port, GError **error_r) +{ + struct sockaddr_in my_addr; + socklen_t nlen = sizeof(struct sockaddr); + struct hostent *h; + + memset(&my_addr, 0, sizeof(my_addr)); + /* use specified hostname */ + if (hostname) { + /* get server IP address (no check if input is IP address or DNS name) */ + h = gethostbyname(hostname); + if (h == NULL) { + if (strstr(hostname, "255.255.255.255") == hostname) { + my_addr.sin_addr.s_addr=-1; + } else { + if ((my_addr.sin_addr.s_addr = inet_addr(hostname)) == 0xFFFFFFFF) { + g_set_error(error_r, raop_output_quark(), 0, + "failed to resolve host '%s'", + hostname); + return -1; + } + } + my_addr.sin_family = AF_INET; + } else { + my_addr.sin_family = h->h_addrtype; + memcpy((char *) &my_addr.sin_addr.s_addr, + h->h_addr_list[0], h->h_length); + } + } else { + // if hostname=NULL, use INADDR_ANY + if (ulAddr) + my_addr.sin_addr.s_addr = ulAddr; + else + my_addr.sin_addr.s_addr = htonl(INADDR_ANY); + my_addr.sin_family = AF_INET; + } + + /* bind a specified port */ + my_addr.sin_port = htons(*port); + + if (bind(sd, (struct sockaddr *) &my_addr, sizeof(my_addr)) < 0) { + g_set_error(error_r, raop_output_quark(), errno, + "failed to bind socket: %s", + g_strerror(errno)); + return -1; + } + + if (*port == 0) { + getsockname(sd, (struct sockaddr *) &my_addr, &nlen); + *port = ntohs(my_addr.sin_port); + } + + return 0; +} + +/* + * open udp port + */ +static int +open_udp_socket(char *hostname, unsigned short *port, + GError **error_r) +{ + int sd; + const int size = 30000; + + /* socket creation */ + sd = socket(PF_INET, SOCK_DGRAM, 0); + if (sd < 0) { + g_set_error(error_r, raop_output_quark(), errno, + "failed to create UDP socket: %s", + g_strerror(errno)); + return -1; + } + if (setsockopt(sd, SOL_SOCKET, SO_SNDBUF, (const char *) &size, sizeof(size)) < 0) { + g_set_error(error_r, raop_output_quark(), errno, + "failed to set UDP buffer size: %s", + g_strerror(errno)); + return -1; + } + if (bind_host(sd, hostname, 0, port, error_r)) { + close_socket(sd); + return -1; + } + + return sd; +} + +static bool +get_sockaddr_by_host(const char *host, short destport, + struct sockaddr_in *addr, + GError **error_r) +{ + struct hostent *h; + + h = gethostbyname(host); + if (h) { + addr->sin_family = h->h_addrtype; + memcpy((char *) &addr->sin_addr.s_addr, h->h_addr_list[0], h->h_length); + } else { + addr->sin_family = AF_INET; + if ((addr->sin_addr.s_addr=inet_addr(host))==0xFFFFFFFF) { + g_set_error(error_r, rtsp_client_quark(), 0, + "failed to resolve host '%s'", host); + return false; + } + } + addr->sin_port = htons(destport); + return true; +} + +/* + * Calculate the current NTP time, store it in the buffer. + */ +static void +fill_int(unsigned char *buffer, uint32_t value) +{ + uint32_t be = GINT32_TO_BE(value); + memcpy(buffer, &be, sizeof(be)); +} + +/* + * Store time in the NTP format in the buffer + */ +static void +fill_time_buffer_with_time(unsigned char *buffer, struct timeval *tout) +{ + unsigned long secs_to_baseline = 964697997; + double fraction; + unsigned long long_fraction; + unsigned long secs; + + fraction = ((double) tout->tv_usec) / 1000000.0; + long_fraction = (unsigned long) (fraction * 256.0 * 256.0 * 256.0 * 256.0); + secs = secs_to_baseline + tout->tv_sec; + fill_int(buffer, secs); + fill_int(buffer + 4, long_fraction); +} + +static void +get_time_for_rtp(struct play_state *state, struct timeval *tout) +{ + unsigned long rtp_diff = state->rtptime - state->start_rtptime; + unsigned long add_secs = rtp_diff / 44100; + unsigned long add_usecs = (((rtp_diff % 44100) * 10000) / 441) % 1000000; + tout->tv_sec = state->start_time.tv_sec + add_secs; + tout->tv_usec = state->start_time.tv_usec + add_usecs; + if (tout->tv_usec >= 1000000) { + tout->tv_sec++; + tout->tv_usec = tout->tv_usec % 1000000; + } +} + +/* + * Send a control command + */ +static bool +send_control_command(struct control_data *ctrl, struct raop_data *rd, + struct play_state *state, + GError **error_r) +{ + unsigned char buf[20]; + int diff; + int num_bytes; + struct timeval ctrl_time; + + diff = 88200; + if (rd->started) { + buf[0] = 0x80; + diff += NUMSAMPLES; + } else { + buf[0] = 0x90; + state->playing = true; + state->start_rtptime = state->rtptime; + } + buf[1] = 0xd4; + buf[2] = 0x00; + buf[3] = 0x07; + fill_int(buf + 4, state->rtptime - diff); + get_time_for_rtp(state, &ctrl_time); + fill_time_buffer_with_time(buf + 8, &ctrl_time); + fill_int(buf + 16, state->rtptime); + + num_bytes = sendto(ctrl->fd, (const void *)buf, sizeof(buf), 0, + (struct sockaddr *)&rd->ctrl_addr, + sizeof(rd->ctrl_addr)); + if (num_bytes < 0) { + g_set_error(error_r, raop_output_quark(), errno, + "Unable to send control command: %s", + g_strerror(errno)); + return false; + } + + return true; +} + +static int rsa_encrypt(const unsigned char *text, int len, unsigned char *res) +{ + RSA *rsa; + gsize usize; + unsigned char *modulus; + unsigned char *exponent; + int size; + + char n[] = + "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC" + "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR" + "KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB" + "OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ" + "Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh" + "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew=="; + char e[] = "AQAB"; + + rsa = RSA_new(); + + modulus = g_base64_decode(n, &usize); + rsa->n = BN_bin2bn(modulus, usize, NULL); + exponent = g_base64_decode(e, &usize); + rsa->e = BN_bin2bn(exponent, usize, NULL); + g_free(modulus); + g_free(exponent); + size = RSA_public_encrypt(len, text, res, rsa, RSA_PKCS1_OAEP_PADDING); + + RSA_free(rsa); + return size; +} + +static int +raop_encrypt(struct encrypt_data *encryp, unsigned char *data, int size) +{ + // any bytes that fall beyond the last 16 byte page should be sent + // in the clear + int alt_size = size - (size % 16); + + memcpy(encryp->nv, encryp->iv, 16); + + AES_cbc_encrypt(data, data, alt_size, &encryp->ctx, encryp->nv, 1); + + return size; +} + +/* write bits filed data, *bpos=0 for msb, *bpos=7 for lsb + d=data, blen=length of bits field +*/ +static inline void +bits_write(unsigned char **p, unsigned char d, int blen, int *bpos) +{ + int lb, rb, bd; + lb =7 - *bpos; + rb = lb - blen + 1; + if (rb >= 0) { + bd = d << rb; + if (*bpos) + **p |= bd; + else + **p = bd; + *bpos += blen; + } else { + bd = d >> -rb; + **p |= bd; + *p += 1; + **p = d << (8 + rb); + *bpos = -rb; + } +} + +static bool +wrap_pcm(unsigned char *buffer, int bsize, int *size, unsigned char *inData, int inSize) +{ + unsigned char one[4]; + int count = 0; + int bpos = 0; + unsigned char *bp = buffer; + int i, nodata = 0; + bits_write(&bp, 1, 3, &bpos); // channel=1, stereo + bits_write(&bp, 0, 4, &bpos); // unknown + bits_write(&bp, 0, 8, &bpos); // unknown + bits_write(&bp, 0, 4, &bpos); // unknown + if (bsize != 4096 && false) + bits_write(&bp, 1, 1, &bpos); // hassize + else + bits_write(&bp, 0, 1, &bpos); // hassize + bits_write(&bp, 0, 2, &bpos); // unused + bits_write(&bp, 1, 1, &bpos); // is-not-compressed + if (bsize != 4096 && false) { + // size of data, integer, big endian + bits_write(&bp, (bsize >> 24) & 0xff, 8, &bpos); + bits_write(&bp, (bsize >> 16) & 0xff, 8, &bpos); + bits_write(&bp, (bsize >> 8) & 0xff, 8, &bpos); + bits_write(&bp, bsize&0xff, 8, &bpos); + } + while (1) { + if (inSize <= count * 4) nodata = 1; + if (nodata) break; + one[0] = inData[count * 4]; + one[1] = inData[count * 4 + 1]; + one[2] = inData[count * 4 + 2]; + one[3] = inData[count * 4 + 3]; + +#if BYTE_ORDER == BIG_ENDIAN + bits_write(&bp, one[0], 8, &bpos); + bits_write(&bp, one[1], 8, &bpos); + bits_write(&bp, one[2], 8, &bpos); + bits_write(&bp, one[3], 8, &bpos); +#else + bits_write(&bp, one[1], 8, &bpos); + bits_write(&bp, one[0], 8, &bpos); + bits_write(&bp, one[3], 8, &bpos); + bits_write(&bp, one[2], 8, &bpos); +#endif + + if (++count == bsize) break; + } + if (!count) return false; // when no data at all, it should stop playing + /* when readable size is less than bsize, fill 0 at the bottom */ + for(i = 0; i < (bsize - count) * 4; i++) { + bits_write(&bp, 0, 8, &bpos); + } + *size = (int)(bp - buffer); + if (bpos) *size += 1; + return true; +} + +static bool +raopcl_connect(struct raop_data *rd, GError **error_r) +{ + unsigned char buf[4 + 8 + 16]; + char sid[16]; + char sci[24]; + char act_r[17]; + char *sac=NULL, *key = NULL, *iv = NULL; + char sdp[1024]; + int rval = false; + unsigned char rsakey[512]; + struct timeval current_time; + unsigned int sessionNum; + int i; + + + gettimeofday(¤t_time,NULL); + sessionNum = current_time.tv_sec + 2082844804; + + RAND_bytes(buf, sizeof(buf)); + sprintf(act_r, "%u", (unsigned int) g_random_int()); + sprintf(sid, "%u", sessionNum); + sprintf(sci, "%08x%08x", *((int *)(buf + 4)), *((int *)(buf + 8))); + sac = g_base64_encode(buf + 12, 16); + rd->rtspcl = rtspcl_open(); + rtspcl_set_useragent(rd->rtspcl, "iTunes/8.1.1 (Macintosh; U; PPC Mac OS X 10.4)"); + rtspcl_add_exthds(rd->rtspcl, "Client-Instance", sci); + rtspcl_add_exthds(rd->rtspcl, "DACP-ID", sci); + rtspcl_add_exthds(rd->rtspcl, "Active-Remote", act_r); + if (!rtspcl_connect(rd->rtspcl, rd->addr, rd->rtsp_port, sid, error_r)) + goto erexit; + + i = rsa_encrypt(raop_session->encrypt.key, 16, rsakey); + key = g_base64_encode(rsakey, i); + remove_char_from_string(key, '='); + iv = g_base64_encode(raop_session->encrypt.iv, 16); + remove_char_from_string(iv, '='); + sprintf(sdp, + "v=0\r\n" + "o=iTunes %s 0 IN IP4 %s\r\n" + "s=iTunes\r\n" + "c=IN IP4 %s\r\n" + "t=0 0\r\n" + "m=audio 0 RTP/AVP 96\r\n" + "a=rtpmap:96 AppleLossless\r\n" + "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n" + "a=rsaaeskey:%s\r\n" + "a=aesiv:%s\r\n", + sid, rtspcl_local_ip(rd->rtspcl), rd->addr, NUMSAMPLES, key, iv); + remove_char_from_string(sac, '='); + // rtspcl_add_exthds(rd->rtspcl, "Apple-Challenge", sac); + if (!rtspcl_announce_sdp(rd->rtspcl, sdp, error_r)) + goto erexit; + // if (!rtspcl_mark_del_exthds(rd->rtspcl, "Apple-Challenge")) goto erexit; + if (!rtspcl_setup(rd->rtspcl, NULL, + raop_session->ctrl.port, raop_session->ntp.port, + error_r)) + goto erexit; + + if (!get_sockaddr_by_host(rd->addr, rd->rtspcl->control_port, + &rd->ctrl_addr, error_r)) + goto erexit; + + if (!get_sockaddr_by_host(rd->addr, rd->rtspcl->server_port, + &rd->data_addr, error_r)) + goto erexit; + + if (!rtspcl_record(rd->rtspcl, + raop_session->play_state.seq_num, + raop_session->play_state.rtptime, + error_r)) + goto erexit; + + rval = true; + + erexit: + g_free(sac); + g_free(key); + g_free(iv); + return rval; +} + +static int +difference (struct timeval *t1, struct timeval *t2) +{ + int ret = 150000000; + if (t1->tv_sec - t2->tv_sec < 150) { + ret = (t1->tv_sec - t2->tv_sec) * 1000000; + ret += t1->tv_usec - t2->tv_usec; + } + return ret; +} + +/* + * With airtunes version 2, we don't get responses back when we send audio + * data. The only requests we get from the airtunes device are timing + * requests. + */ +static bool +send_audio_data(int fd, GError **error_r) +{ + int i = 0; + struct timeval current_time, rtp_time; + struct raop_data *rd = raop_session->raop_list; + + get_time_for_rtp(&raop_session->play_state, &rtp_time); + gettimeofday(¤t_time, NULL); + int diff = difference(&rtp_time, ¤t_time); + if (diff > 0) + g_usleep(diff); + + gettimeofday(&raop_session->play_state.last_send, NULL); + while (rd) { + if (rd->started) { + raop_session->data[1] = 0x60; + } else { + rd->started = true; + raop_session->data[1] = 0xe0; + } + i = sendto(fd, (const void *)(raop_session->data + raop_session->wblk_wsize), + raop_session->wblk_remsize, 0, (struct sockaddr *) &rd->data_addr, + sizeof(rd->data_addr)); + if (i < 0) { + g_set_error(error_r, raop_output_quark(), errno, + "write error: %s", + g_strerror(errno)); + return false; + } + if (i == 0) { + g_set_error_literal(error_r, raop_output_quark(), 0, + "disconnected on the other end"); + return false; + } + rd = rd->next; + } + raop_session->wblk_wsize += i; + raop_session->wblk_remsize -= i; + + return true; +} + +static struct audio_output * +raop_output_init(const struct config_param *param, GError **error_r) +{ + const char *host = config_get_block_string(param, "host", NULL); + if (host == NULL) { + g_set_error_literal(error_r, raop_output_quark(), 0, + "missing option 'host'"); + return NULL; + } + + struct raop_data *rd; + + rd = new_raop_data(param, error_r); + if (rd == NULL) + return NULL; + + rd->addr = host; + rd->rtsp_port = config_get_block_unsigned(param, "port", 5000); + rd->volume = config_get_block_unsigned(param, "volume", 75); + return &rd->base; +} + +static bool +raop_set_volume_local(struct raop_data *rd, int volume, GError **error_r) +{ + char vol_str[128]; + sprintf(vol_str, "volume: %d.000000\r\n", volume); + return rtspcl_set_parameter(rd->rtspcl, vol_str, error_r); +} + + +static void +raop_output_finish(struct audio_output *ao) +{ + struct raop_data *rd = (struct raop_data *)ao; + + if (rd->rtspcl) + rtspcl_close(rd->rtspcl); + + g_mutex_free(rd->control_mutex); + ao_base_finish(&rd->base); + g_free(rd); + + if (raop_session->raop_list == NULL) { + raop_session_free(raop_session); + raop_session = NULL; + } +} + +#define RAOP_VOLUME_MIN -30 +#define RAOP_VOLUME_MAX 0 + +int +raop_get_volume(struct raop_data *rd) +{ + return rd->volume; +} + +bool +raop_set_volume(struct raop_data *rd, unsigned volume, GError **error_r) +{ + int raop_volume; + bool rval; + + //set parameter volume + if (volume == 0) { + raop_volume = -144; + } else { + raop_volume = RAOP_VOLUME_MIN + + (RAOP_VOLUME_MAX - RAOP_VOLUME_MIN) * volume / 100; + } + g_mutex_lock(rd->control_mutex); + rval = raop_set_volume_local(rd, raop_volume, error_r); + if (rval) rd->volume = volume; + g_mutex_unlock(rd->control_mutex); + + return rval; +} + +static void +raop_output_cancel(struct audio_output *ao) +{ + //flush + struct key_data kd; + struct raop_data *rd = (struct raop_data *)ao; + int flush_diff = 1; + + rd->started = 0; + if (rd->is_master) { + raop_session->play_state.playing = false; + } + if (rd->paused) { + return; + } + + g_mutex_lock(rd->control_mutex); + static char rtp_key[] = "RTP-Info"; + kd.key = rtp_key; + char buf[128]; + sprintf(buf, "seq=%d; rtptime=%d", raop_session->play_state.seq_num + flush_diff, raop_session->play_state.rtptime + NUMSAMPLES * flush_diff); + kd.data = buf; + kd.next = NULL; + exec_request(rd->rtspcl, "FLUSH", NULL, NULL, 1, + &kd, NULL, NULL); + g_mutex_unlock(rd->control_mutex); +} + +static bool +raop_output_pause(struct audio_output *ao) +{ + struct raop_data *rd = (struct raop_data *)ao; + + rd->paused = true; + return true; +} + +/** + * Remove the output from the session's list. Caller must not lock + * the list_mutex. + */ +static void +raop_output_remove(struct raop_data *rd) +{ + struct raop_data *iter = raop_session->raop_list; + struct raop_data *prev = NULL; + + g_mutex_lock(raop_session->list_mutex); + while (iter) { + if (iter == rd) { + if (prev != NULL) { + prev->next = rd->next; + } else { + raop_session->raop_list = rd->next; + } + if (rd->is_master && raop_session->raop_list != NULL) { + raop_session->raop_list->is_master = true; + } + rd->next = NULL; + rd->is_master = false; + break; + } + prev = iter; + iter = iter->next; + } + g_mutex_unlock(raop_session->list_mutex); + + if (raop_session->raop_list == NULL) { + ntp_server_close(&raop_session->ntp); + close(raop_session->ctrl.fd); + raop_session->ctrl.fd = -1; + } +} + +static void +raop_output_close(struct audio_output *ao) +{ + //teardown + struct raop_data *rd = (struct raop_data *)ao; + + raop_output_remove(rd); + + g_mutex_lock(rd->control_mutex); + exec_request(rd->rtspcl, "TEARDOWN", NULL, NULL, 0, + NULL, NULL, NULL); + g_mutex_unlock(rd->control_mutex); + + rd->started = 0; +} + + +static bool +raop_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error_r) +{ + //setup, etc. + struct raop_data *rd = (struct raop_data *)ao; + + g_mutex_lock(raop_session->list_mutex); + if (raop_session->raop_list == NULL) { + // first raop, need to initialize session data + unsigned short myport = 0; + raop_session->raop_list = rd; + rd->is_master = true; + + raop_session->data_fd = open_udp_socket(NULL, &myport, + error_r); + if (raop_session->data_fd < 0) + return false; + + if (!ntp_server_open(&raop_session->ntp, error_r)) + return false; + + raop_session->ctrl.fd = + open_udp_socket(NULL, &raop_session->ctrl.port, + error_r); + if (raop_session->ctrl.fd < 0) { + ntp_server_close(&raop_session->ntp); + raop_session->ctrl.fd = -1; + g_mutex_unlock(raop_session->list_mutex); + return false; + } + } + g_mutex_unlock(raop_session->list_mutex); + + audio_format->format = SAMPLE_FORMAT_S16; + if (!raopcl_connect(rd, error_r)) { + raop_output_remove(rd); + return false; + } + + if (!raop_set_volume(rd, rd->volume, error_r)) { + raop_output_remove(rd); + return false; + } + + g_mutex_lock(raop_session->list_mutex); + if (!rd->is_master) { + rd->next = raop_session->raop_list; + raop_session->raop_list = rd; + } + g_mutex_unlock(raop_session->list_mutex); + return true; +} + +static size_t +raop_output_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error_r) +{ + //raopcl_send_sample + struct raop_data *rd = (struct raop_data *)ao; + size_t rval = 0, orig_size = size; + + rd->paused = false; + if (!rd->is_master) { + // only process data for the master raop + return size; + } + + g_mutex_lock(raop_session->data_mutex); + + if (raop_session->play_state.rtptime <= NUMSAMPLES) { + // looped over, need new reference point to calculate correct times + raop_session->play_state.playing = false; + } + + while (raop_session->bufferSize + size >= RAOP_BUFFER_SIZE) { + // ntp header + unsigned char header[] = { + 0x80, 0x60, 0x00, 0x00, + // rtptime + 0x00, 0x00, 0x00, 0x00, + // device + 0x7e, 0xad, 0xd2, 0xd3, + }; + + + int count = 0; + int copyBytes = RAOP_BUFFER_SIZE - raop_session->bufferSize; + + if (!raop_session->play_state.playing || + raop_session->play_state.seq_num % (44100 / NUMSAMPLES + 1) == 0) { + struct raop_data *iter; + g_mutex_lock(raop_session->list_mutex); + if (!raop_session->play_state.playing) { + gettimeofday(&raop_session->play_state.start_time,NULL); + } + iter = raop_session->raop_list; + while (iter) { + if (!send_control_command(&raop_session->ctrl, iter, + &raop_session->play_state, + error_r)) + goto erexit; + + iter = iter->next; + } + g_mutex_unlock(raop_session->list_mutex); + } + + fill_int(header + 8, raop_session->play_state.sync_src); + + memcpy(raop_session->buffer + raop_session->bufferSize, chunk, copyBytes); + raop_session->bufferSize += copyBytes; + chunk = ((const char *)chunk) + copyBytes; + size -= copyBytes; + + if (!wrap_pcm(raop_session->data + RAOP_HEADER_SIZE, NUMSAMPLES, &count, raop_session->buffer, RAOP_BUFFER_SIZE)) { + g_warning("unable to encode %d bytes properly\n", RAOP_BUFFER_SIZE); + } + + memcpy(raop_session->data, header, RAOP_HEADER_SIZE); + raop_session->data[2] = raop_session->play_state.seq_num >> 8; + raop_session->data[3] = raop_session->play_state.seq_num & 0xff; + raop_session->play_state.seq_num ++; + + fill_int(raop_session->data + 4, raop_session->play_state.rtptime); + raop_session->play_state.rtptime += NUMSAMPLES; + + raop_encrypt(&raop_session->encrypt, raop_session->data + RAOP_HEADER_SIZE, count); + raop_session->wblk_remsize = count + RAOP_HEADER_SIZE; + raop_session->wblk_wsize = 0; + + if (!send_audio_data(raop_session->data_fd, error_r)) + goto erexit; + + raop_session->bufferSize = 0; + } + if (size > 0) { + memcpy(raop_session->buffer + raop_session->bufferSize, chunk, size); + raop_session->bufferSize += size; + } + rval = orig_size; + erexit: + g_mutex_unlock(raop_session->data_mutex); + return rval; +} + +const struct audio_output_plugin raop_output_plugin = { + .name = "raop", + .init = raop_output_init, + .finish = raop_output_finish, + .open = raop_output_open, + .play = raop_output_play, + .cancel = raop_output_cancel, + .pause = raop_output_pause, + .close = raop_output_close, + .mixer_plugin = &raop_mixer_plugin, +}; diff --git a/src/output/raop_output_plugin.h b/src/output/raop_output_plugin.h new file mode 100644 index 000000000..6aca97836 --- /dev/null +++ b/src/output/raop_output_plugin.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_RAOP_OUTPUT_PLUGIN_H +#define MPD_RAOP_OUTPUT_PLUGIN_H + +#include <glib.h> + +#include <stdbool.h> + +struct raop_data; + +extern const struct audio_output_plugin raop_output_plugin; + +bool +raop_set_volume(struct raop_data *rd, unsigned volume, GError **error_r); + +int +raop_get_volume(struct raop_data *rd); + +#endif diff --git a/src/output/recorder_output_plugin.c b/src/output/recorder_output_plugin.c index 2f088a107..ea299468b 100644 --- a/src/output/recorder_output_plugin.c +++ b/src/output/recorder_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "recorder_output_plugin.h" #include "output_api.h" #include "encoder_plugin.h" #include "encoder_list.h" @@ -34,6 +35,8 @@ #define G_LOG_DOMAIN "recorder" struct recorder_output { + struct audio_output base; + /** * The configured encoder plugin. */ @@ -64,11 +67,16 @@ 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) +static struct audio_output * +recorder_output_init(const struct config_param *param, GError **error_r) { struct recorder_output *recorder = g_new(struct recorder_output, 1); + if (!ao_base_init(&recorder->base, &recorder_output_plugin, param, + error_r)) { + g_free(recorder); + return NULL; + } + const char *encoder_name; const struct encoder_plugin *encoder_plugin; @@ -95,19 +103,21 @@ recorder_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, if (recorder->encoder == NULL) goto failure; - return recorder; + return &recorder->base; failure: + ao_base_finish(&recorder->base); g_free(recorder); return NULL; } static void -recorder_output_finish(void *data) +recorder_output_finish(struct audio_output *ao) { - struct recorder_output *recorder = data; + struct recorder_output *recorder = (struct recorder_output *)ao; encoder_finish(recorder->encoder); + ao_base_finish(&recorder->base); g_free(recorder); } @@ -154,10 +164,11 @@ recorder_output_encoder_to_file(struct recorder_output *recorder, } static bool -recorder_output_open(void *data, struct audio_format *audio_format, +recorder_output_open(struct audio_output *ao, + struct audio_format *audio_format, GError **error_r) { - struct recorder_output *recorder = data; + struct recorder_output *recorder = (struct recorder_output *)ao; bool success; /* create the output file */ @@ -185,9 +196,9 @@ recorder_output_open(void *data, struct audio_format *audio_format, } static void -recorder_output_close(void *data) +recorder_output_close(struct audio_output *ao) { - struct recorder_output *recorder = data; + struct recorder_output *recorder = (struct recorder_output *)ao; /* flush the encoder and write the rest to the file */ @@ -202,10 +213,10 @@ recorder_output_close(void *data) } static size_t -recorder_output_play(void *data, const void *chunk, size_t size, +recorder_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error_r) { - struct recorder_output *recorder = data; + struct recorder_output *recorder = (struct recorder_output *)ao; return encoder_write(recorder->encoder, chunk, size, error_r) && recorder_output_encoder_to_file(recorder, error_r) diff --git a/src/output/recorder_output_plugin.h b/src/output/recorder_output_plugin.h new file mode 100644 index 000000000..a9bf755bd --- /dev/null +++ b/src/output/recorder_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_RECORDER_OUTPUT_PLUGIN_H +#define MPD_RECORDER_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin recorder_output_plugin; + +#endif diff --git a/src/output/roar_output_plugin.c b/src/output/roar_output_plugin.c new file mode 100644 index 000000000..1c2c48321 --- /dev/null +++ b/src/output/roar_output_plugin.c @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft + * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "roar_output_plugin.h" +#include "output_api.h" +#include "mixer_list.h" +#include "roar_output_plugin.h" + +#include <glib.h> +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> + +#include <roaraudio.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "roaraudio" + +typedef struct roar +{ + struct audio_output base; + + roar_vs_t * vss; + int err; + char *host; + char *name; + int role; + struct roar_connection con; + struct roar_audio_info info; + GMutex *lock; + volatile bool alive; +} roar_t; + +static inline GQuark +roar_output_quark(void) +{ + return g_quark_from_static_string("roar_output"); +} + +static int +roar_output_get_volume_locked(struct roar *roar) +{ + if (roar->vss == NULL || !roar->alive) + return -1; + + float l, r; + int error; + if (roar_vs_volume_get(roar->vss, &l, &r, &error) < 0) + return -1; + + return (l + r) * 50; +} + +int +roar_output_get_volume(struct roar *roar) +{ + g_mutex_lock(roar->lock); + int volume = roar_output_get_volume_locked(roar); + g_mutex_unlock(roar->lock); + return volume; +} + +static bool +roar_output_set_volume_locked(struct roar *roar, unsigned volume) +{ + assert(volume <= 100); + + if (roar->vss == NULL || !roar->alive) + return false; + + int error; + float level = volume / 100.0; + + roar_vs_volume_mono(roar->vss, level, &error); + return true; +} + +bool +roar_output_set_volume(struct roar *roar, unsigned volume) +{ + g_mutex_lock(roar->lock); + bool success = roar_output_set_volume_locked(roar, volume); + g_mutex_unlock(roar->lock); + return success; +} + +static void +roar_configure(struct roar * self, const struct config_param *param) +{ + self->host = config_dup_block_string(param, "server", NULL); + self->name = config_dup_block_string(param, "name", "MPD"); + + const char *role = config_get_block_string(param, "role", "music"); + self->role = role != NULL + ? roar_str2role(role) + : ROAR_ROLE_MUSIC; +} + +static struct audio_output * +roar_init(const struct config_param *param, GError **error_r) +{ + struct roar *self = g_new0(struct roar, 1); + + if (!ao_base_init(&self->base, &roar_output_plugin, param, error_r)) { + g_free(self); + return NULL; + } + + self->lock = g_mutex_new(); + self->err = ROAR_ERROR_NONE; + roar_configure(self, param); + return &self->base; +} + +static void +roar_finish(struct audio_output *ao) +{ + struct roar *self = (struct roar *)ao; + + g_free(self->host); + g_free(self->name); + g_mutex_free(self->lock); + + ao_base_finish(&self->base); + g_free(self); +} + +static void +roar_use_audio_format(struct roar_audio_info *info, + struct audio_format *audio_format) +{ + info->rate = audio_format->sample_rate; + info->channels = audio_format->channels; + info->codec = ROAR_CODEC_PCM_S; + + switch (audio_format->format) { + case SAMPLE_FORMAT_UNDEFINED: + info->bits = 16; + audio_format->format = SAMPLE_FORMAT_S16; + break; + + case SAMPLE_FORMAT_S8: + info->bits = 8; + break; + + case SAMPLE_FORMAT_S16: + info->bits = 16; + break; + + case SAMPLE_FORMAT_S24_P32: + info->bits = 32; + audio_format->format = SAMPLE_FORMAT_S32; + break; + + case SAMPLE_FORMAT_S32: + info->bits = 32; + break; + } +} + +static bool +roar_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) +{ + struct roar *self = (struct roar *)ao; + g_mutex_lock(self->lock); + + if (roar_simple_connect(&(self->con), self->host, self->name) < 0) + { + g_set_error(error, roar_output_quark(), 0, + "Failed to connect to Roar server"); + g_mutex_unlock(self->lock); + return false; + } + + self->vss = roar_vs_new_from_con(&(self->con), &(self->err)); + + if (self->vss == NULL || self->err != ROAR_ERROR_NONE) + { + g_set_error(error, roar_output_quark(), 0, + "Failed to connect to server"); + g_mutex_unlock(self->lock); + return false; + } + + roar_use_audio_format(&self->info, audio_format); + + if (roar_vs_stream(self->vss, &(self->info), ROAR_DIR_PLAY, + &(self->err)) < 0) + { + g_set_error(error, roar_output_quark(), 0, "Failed to start stream"); + g_mutex_unlock(self->lock); + return false; + } + roar_vs_role(self->vss, self->role, &(self->err)); + self->alive = true; + + g_mutex_unlock(self->lock); + return true; +} + +static void +roar_close(struct audio_output *ao) +{ + struct roar *self = (struct roar *)ao; + g_mutex_lock(self->lock); + self->alive = false; + + if (self->vss != NULL) + roar_vs_close(self->vss, ROAR_VS_TRUE, &(self->err)); + self->vss = NULL; + roar_disconnect(&(self->con)); + g_mutex_unlock(self->lock); +} + +static void +roar_cancel_locked(struct roar *self) +{ + if (self->vss == NULL) + return; + + roar_vs_t *vss = self->vss; + self->vss = NULL; + roar_vs_close(vss, ROAR_VS_TRUE, &(self->err)); + self->alive = false; + + vss = roar_vs_new_from_con(&(self->con), &(self->err)); + if (vss == NULL) + return; + + if (roar_vs_stream(vss, &(self->info), ROAR_DIR_PLAY, + &(self->err)) < 0) { + roar_vs_close(vss, ROAR_VS_TRUE, &(self->err)); + g_warning("Failed to start stream"); + return; + } + + roar_vs_role(vss, self->role, &(self->err)); + self->vss = vss; + self->alive = true; +} + +static void +roar_cancel(struct audio_output *ao) +{ + struct roar *self = (struct roar *)ao; + + g_mutex_lock(self->lock); + roar_cancel_locked(self); + g_mutex_unlock(self->lock); +} + +static size_t +roar_play(struct audio_output *ao, const void *chunk, size_t size, GError **error) +{ + struct roar *self = (struct roar *)ao; + ssize_t rc; + + if (self->vss == NULL) + { + g_set_error(error, roar_output_quark(), 0, "Connection is invalid"); + return 0; + } + + rc = roar_vs_write(self->vss, chunk, size, &(self->err)); + if ( rc <= 0 ) + { + g_set_error(error, roar_output_quark(), 0, "Failed to play data"); + return 0; + } + + return rc; +} + +static const char* +roar_tag_convert(enum tag_type type, bool *is_uuid) +{ + *is_uuid = false; + switch (type) + { + case TAG_ARTIST: + case TAG_ALBUM_ARTIST: + return "AUTHOR"; + case TAG_ALBUM: + return "ALBUM"; + case TAG_TITLE: + return "TITLE"; + case TAG_TRACK: + return "TRACK"; + case TAG_NAME: + return "NAME"; + case TAG_GENRE: + return "GENRE"; + case TAG_DATE: + return "DATE"; + case TAG_PERFORMER: + return "PERFORMER"; + case TAG_COMMENT: + return "COMMENT"; + case TAG_DISC: + return "DISCID"; + case TAG_COMPOSER: +#ifdef ROAR_META_TYPE_COMPOSER + return "COMPOSER"; +#else + return "AUTHOR"; +#endif + case TAG_MUSICBRAINZ_ARTISTID: + case TAG_MUSICBRAINZ_ALBUMID: + case TAG_MUSICBRAINZ_ALBUMARTISTID: + case TAG_MUSICBRAINZ_TRACKID: + *is_uuid = true; + return "HASH"; + + default: + return NULL; + } +} + +static void +roar_send_tag(struct audio_output *ao, const struct tag *meta) +{ + struct roar *self = (struct roar *)ao; + + if (self->vss == NULL) + return; + + g_mutex_lock(self->lock); + size_t cnt = 1; + struct roar_keyval vals[32]; + memset(vals, 0, sizeof(vals)); + char uuid_buf[32][64]; + + char timebuf[16]; + snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d", + meta->time / 3600, (meta->time % 3600) / 60, meta->time % 60); + + vals[0].key = g_strdup("LENGTH"); + vals[0].value = timebuf; + + for (unsigned i = 0; i < meta->num_items && cnt < 32; i++) + { + bool is_uuid = false; + const char *key = roar_tag_convert(meta->items[i]->type, &is_uuid); + if (key != NULL) + { + if (is_uuid) + { + snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s", + meta->items[i]->value); + vals[cnt].key = g_strdup(key); + vals[cnt].value = uuid_buf[cnt]; + } + else + { + vals[cnt].key = g_strdup(key); + vals[cnt].value = meta->items[i]->value; + } + cnt++; + } + } + + roar_vs_meta(self->vss, vals, cnt, &(self->err)); + + for (unsigned i = 0; i < 32; i++) + g_free(vals[i].key); + + g_mutex_unlock(self->lock); +} + +const struct audio_output_plugin roar_output_plugin = { + .name = "roar", + .init = roar_init, + .finish = roar_finish, + .open = roar_open, + .play = roar_play, + .cancel = roar_cancel, + .close = roar_close, + .send_tag = roar_send_tag, + + .mixer_plugin = &roar_mixer_plugin +}; diff --git a/src/output/roar_output_plugin.h b/src/output/roar_output_plugin.h new file mode 100644 index 000000000..78b628cc4 --- /dev/null +++ b/src/output/roar_output_plugin.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_ROAR_OUTPUT_PLUGIN_H +#define MPD_ROAR_OUTPUT_PLUGIN_H + +#include <stdbool.h> + +struct roar; + +extern const struct audio_output_plugin roar_output_plugin; + +int +roar_output_get_volume(struct roar *roar); + +bool +roar_output_set_volume(struct roar *roar, unsigned volume); + +#endif diff --git a/src/output/shout_plugin.c b/src/output/shout_output_plugin.c index 27ef3b993..7867ae63c 100644 --- a/src/output/shout_plugin.c +++ b/src/output/shout_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "shout_output_plugin.h" #include "output_api.h" #include "encoder_plugin.h" #include "encoder_list.h" @@ -41,6 +42,8 @@ struct shout_buffer { }; struct shout_data { + struct audio_output base; + shout_t *shout_conn; shout_metadata_t *shout_meta; @@ -107,9 +110,8 @@ static void free_shout_data(struct shout_data *sd) } \ } -static void * -my_shout_init_driver(const struct audio_format *audio_format, - const struct config_param *param, +static struct audio_output * +my_shout_init_driver(const struct config_param *param, GError **error) { struct shout_data *sd; @@ -125,18 +127,26 @@ my_shout_init_driver(const struct audio_format *audio_format, const char *user; char *name; const char *value; - struct block_param *block_param; + const struct block_param *block_param; int public; - if (audio_format == NULL || - !audio_format_fully_defined(audio_format)) { + sd = new_shout_data(); + + if (!ao_base_init(&sd->base, &shout_output_plugin, param, error)) { + free_shout_data(sd); + return NULL; + } + + const struct audio_format *audio_format = + &sd->base.config_audio_format; + if (!audio_format_fully_defined(audio_format)) { g_set_error(error, shout_output_quark(), 0, "Need full audio format specification"); + ao_base_finish(&sd->base); + free_shout_data(sd); return NULL; } - sd = new_shout_data(); - if (shout_init_count == 0) shout_init(); @@ -277,6 +287,13 @@ my_shout_init_driver(const struct audio_format *audio_format, goto failure; } + value = config_get_block_string(param, "url", NULL); + if (value != NULL && shout_set_url(sd->shout_conn, value)) { + g_set_error(error, shout_output_quark(), 0, + "%s", shout_get_error(sd->shout_conn)); + goto failure; + } + { char temp[11]; memset(temp, 0, sizeof(temp)); @@ -299,9 +316,10 @@ my_shout_init_driver(const struct audio_format *audio_format, } } - return sd; + return &sd->base; failure: + ao_base_finish(&sd->base); free_shout_data(sd); return NULL; } @@ -371,12 +389,14 @@ static void close_shout_conn(struct shout_data * sd) } } -static void my_shout_finish_driver(void *data) +static void +my_shout_finish_driver(struct audio_output *ao) { - struct shout_data *sd = (struct shout_data *)data; + struct shout_data *sd = (struct shout_data *)ao; encoder_finish(sd->encoder); + ao_base_finish(&sd->base); free_shout_data(sd); shout_init_count--; @@ -385,17 +405,19 @@ static void my_shout_finish_driver(void *data) shout_shutdown(); } -static void my_shout_drop_buffered_audio(void *data) +static void +my_shout_drop_buffered_audio(struct audio_output *ao) { G_GNUC_UNUSED - struct shout_data *sd = (struct shout_data *)data; + struct shout_data *sd = (struct shout_data *)ao; /* needs to be implemented for shout */ } -static void my_shout_close_device(void *data) +static void +my_shout_close_device(struct audio_output *ao) { - struct shout_data *sd = (struct shout_data *)data; + struct shout_data *sd = (struct shout_data *)ao; close_shout_conn(sd); } @@ -422,10 +444,10 @@ shout_connect(struct shout_data *sd, GError **error) } static bool -my_shout_open_device(void *data, struct audio_format *audio_format, +my_shout_open_device(struct audio_output *ao, struct audio_format *audio_format, GError **error) { - struct shout_data *sd = (struct shout_data *)data; + struct shout_data *sd = (struct shout_data *)ao; bool ret; ret = shout_connect(sd, error); @@ -445,9 +467,9 @@ my_shout_open_device(void *data, struct audio_format *audio_format, } static unsigned -my_shout_delay(void *data) +my_shout_delay(struct audio_output *ao) { - struct shout_data *sd = (struct shout_data *)data; + struct shout_data *sd = (struct shout_data *)ao; int delay = shout_delay(sd->shout_conn); if (delay < 0) @@ -457,9 +479,10 @@ my_shout_delay(void *data) } static size_t -my_shout_play(void *data, const void *chunk, size_t size, GError **error) +my_shout_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error) { - struct shout_data *sd = (struct shout_data *)data; + struct shout_data *sd = (struct shout_data *)ao; return encoder_write(sd->encoder, chunk, size, error) && write_page(sd, error) @@ -468,11 +491,11 @@ my_shout_play(void *data, const void *chunk, size_t size, GError **error) } static bool -my_shout_pause(void *data) +my_shout_pause(struct audio_output *ao) { static const char silence[1020]; - return my_shout_play(data, silence, sizeof(silence), NULL); + return my_shout_play(ao, silence, sizeof(silence), NULL); } static void @@ -501,10 +524,10 @@ shout_tag_to_metadata(const struct tag *tag, char *dest, size_t size) snprintf(dest, size, "%s - %s", artist, title); } -static void my_shout_set_tag(void *data, +static void my_shout_set_tag(struct audio_output *ao, const struct tag *tag) { - struct shout_data *sd = (struct shout_data *)data; + struct shout_data *sd = (struct shout_data *)ao; bool ret; GError *error = NULL; @@ -543,7 +566,7 @@ static void my_shout_set_tag(void *data, write_page(sd, NULL); } -const struct audio_output_plugin shoutPlugin = { +const struct audio_output_plugin shout_output_plugin = { .name = "shout", .init = my_shout_init_driver, .finish = my_shout_finish_driver, diff --git a/src/output/shout_output_plugin.h b/src/output/shout_output_plugin.h new file mode 100644 index 000000000..9a7378803 --- /dev/null +++ b/src/output/shout_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SHOUT_OUTPUT_PLUGIN_H +#define MPD_SHOUT_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin shout_output_plugin; + +#endif diff --git a/src/output/solaris_output_plugin.c b/src/output/solaris_output_plugin.c index 6f6f507b3..ce726009a 100644 --- a/src/output/solaris_output_plugin.c +++ b/src/output/solaris_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "solaris_output_plugin.h" #include "output_api.h" #include "fd_util.h" @@ -53,6 +54,8 @@ struct audio_info { #define G_LOG_DOMAIN "solaris_output" struct solaris_output { + struct audio_output base; + /* configuration */ const char *device; @@ -77,31 +80,35 @@ solaris_output_test_default_device(void) access("/dev/audio", W_OK) == 0; } -static void * -solaris_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, - G_GNUC_UNUSED GError **error) +static struct audio_output * +solaris_output_init(const struct config_param *param, GError **error_r) { struct solaris_output *so = g_new(struct solaris_output, 1); + if (!ao_base_init(&so->base, &solaris_output_plugin, param, error_r)) { + g_free(so); + return NULL; + } + so->device = config_get_block_string(param, "device", "/dev/audio"); - return so; + return &so->base; } static void -solaris_output_finish(void *data) +solaris_output_finish(struct audio_output *ao) { - struct solaris_output *so = data; + struct solaris_output *so = (struct solaris_output *)ao; + ao_base_finish(&so->base); g_free(so); } static bool -solaris_output_open(void *data, struct audio_format *audio_format, +solaris_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) { - struct solaris_output *so = data; + struct solaris_output *so = (struct solaris_output *)ao; struct audio_info info; int ret, flags; @@ -152,17 +159,18 @@ solaris_output_open(void *data, struct audio_format *audio_format, } static void -solaris_output_close(void *data) +solaris_output_close(struct audio_output *ao) { - struct solaris_output *so = data; + struct solaris_output *so = (struct solaris_output *)ao; close(so->fd); } static size_t -solaris_output_play(void *data, const void *chunk, size_t size, GError **error) +solaris_output_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error) { - struct solaris_output *so = data; + struct solaris_output *so = (struct solaris_output *)ao; ssize_t nbytes; nbytes = write(so->fd, chunk, size); @@ -176,9 +184,9 @@ solaris_output_play(void *data, const void *chunk, size_t size, GError **error) } static void -solaris_output_cancel(void *data) +solaris_output_cancel(struct audio_output *ao) { - struct solaris_output *so = data; + struct solaris_output *so = (struct solaris_output *)ao; ioctl(so->fd, I_FLUSH); } diff --git a/src/output/solaris_output_plugin.h b/src/output/solaris_output_plugin.h new file mode 100644 index 000000000..600aea8c2 --- /dev/null +++ b/src/output/solaris_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SOLARIS_OUTPUT_PLUGIN_H +#define MPD_SOLARIS_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin solaris_output_plugin; + +#endif diff --git a/src/output/winmm_output_plugin.c b/src/output/winmm_output_plugin.c index 4312c635e..4d95834b9 100644 --- a/src/output/winmm_output_plugin.c +++ b/src/output/winmm_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "winmm_output_plugin.h" #include "output_api.h" #include "pcm_buffer.h" #include "mixer_list.h" @@ -37,6 +38,8 @@ struct winmm_buffer { }; struct winmm_output { + struct audio_output base; + UINT device_id; HWAVEOUT handle; @@ -71,59 +74,80 @@ winmm_output_test_default_device(void) return waveOutGetNumDevs() > 0; } -static UINT -get_device_id(const char *device_name) +static bool +get_device_id(const char *device_name, UINT *device_id, GError **error_r) { /* if device is not specified use wave mapper */ - if (device_name == NULL) - return WAVE_MAPPER; + if (device_name == NULL) { + *device_id = WAVE_MAPPER; + return true; + } + + UINT numdevs = waveOutGetNumDevs(); /* check for device id */ char *endptr; UINT id = strtoul(device_name, &endptr, 0); - if (endptr > device_name && *endptr == 0) - return id; + if (endptr > device_name && *endptr == 0) { + if (id >= numdevs) + goto fail; + *device_id = id; + return true; + } /* check for device name */ - for (UINT i = 0; i < waveOutGetNumDevs(); i++) { + for (UINT i = 0; i < numdevs; 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; + if (strstr(device_name, caps.szPname) == device_name) { + *device_id = i; + return true; + } } - /* fallback to wave mapper */ - return WAVE_MAPPER; +fail: + g_set_error(error_r, winmm_output_quark(), 0, + "device \"%s\" is not found", device_name); + return false; } -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) +static struct audio_output * +winmm_output_init(const struct config_param *param, GError **error_r) { struct winmm_output *wo = g_new(struct winmm_output, 1); + if (!ao_base_init(&wo->base, &winmm_output_plugin, param, error_r)) { + g_free(wo); + return NULL; + } + const char *device = config_get_block_string(param, "device", NULL); - wo->device_id = get_device_id(device); - return wo; + if (!get_device_id(device, &wo->device_id, error_r)) { + ao_base_finish(&wo->base); + g_free(wo); + return NULL; + } + + return &wo->base; } static void -winmm_output_finish(void *data) +winmm_output_finish(struct audio_output *ao) { - struct winmm_output *wo = data; + struct winmm_output *wo = (struct winmm_output *)ao; + ao_base_finish(&wo->base); g_free(wo); } static bool -winmm_output_open(void *data, struct audio_format *audio_format, +winmm_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error_r) { - struct winmm_output *wo = data; + struct winmm_output *wo = (struct winmm_output *)ao; wo->event = CreateEvent(NULL, false, false, NULL); if (wo->event == NULL) { @@ -137,7 +161,6 @@ winmm_output_open(void *data, struct audio_format *audio_format, case SAMPLE_FORMAT_S16: break; - case SAMPLE_FORMAT_S24: case SAMPLE_FORMAT_S24_P32: case SAMPLE_FORMAT_S32: case SAMPLE_FORMAT_UNDEFINED: @@ -179,9 +202,9 @@ winmm_output_open(void *data, struct audio_format *audio_format, } static void -winmm_output_close(void *data) +winmm_output_close(struct audio_output *ao) { - struct winmm_output *wo = data; + struct winmm_output *wo = (struct winmm_output *)ao; for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) pcm_buffer_deinit(&wo->buffers[i].buffer); @@ -248,9 +271,9 @@ winmm_drain_buffer(struct winmm_output *wo, struct winmm_buffer *buffer, } static size_t -winmm_output_play(void *data, const void *chunk, size_t size, GError **error_r) +winmm_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error_r) { - struct winmm_output *wo = data; + struct winmm_output *wo = (struct winmm_output *)ao; /* get the next buffer from the ring and prepare it */ struct winmm_buffer *buffer = &wo->buffers[wo->next_buffer]; @@ -303,18 +326,18 @@ winmm_stop(struct winmm_output *wo) } static void -winmm_output_drain(void *data) +winmm_output_drain(struct audio_output *ao) { - struct winmm_output *wo = data; + struct winmm_output *wo = (struct winmm_output *)ao; if (!winmm_drain_all_buffers(wo, NULL)) winmm_stop(wo); } static void -winmm_output_cancel(void *data) +winmm_output_cancel(struct audio_output *ao) { - struct winmm_output *wo = data; + struct winmm_output *wo = (struct winmm_output *)ao; winmm_stop(wo); } diff --git a/src/output/winmm_output_plugin.h b/src/output/winmm_output_plugin.h index 39507275a..0605530e1 100644 --- a/src/output/winmm_output_plugin.h +++ b/src/output/winmm_output_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,10 +20,18 @@ #ifndef MPD_WINMM_OUTPUT_PLUGIN_H #define MPD_WINMM_OUTPUT_PLUGIN_H +#include "check.h" + +#ifdef ENABLE_WINMM_OUTPUT + #include <windows.h> struct winmm_output; +extern const struct audio_output_plugin winmm_output_plugin; + HWAVEOUT winmm_output_get_handle(struct winmm_output*); #endif + +#endif diff --git a/src/output_all.c b/src/output_all.c index 4e0b2eb22..f56cd04ee 100644 --- a/src/output_all.c +++ b/src/output_all.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,6 +27,7 @@ #include "buffer.h" #include "player_control.h" #include "mpd_error.h" +#include "notify.h" #ifndef NDEBUG #include "chunk.h" @@ -40,7 +41,7 @@ static struct audio_format input_audio_format; -static struct audio_output *audio_outputs; +static struct audio_output **audio_outputs; static unsigned int num_audio_outputs; /** @@ -69,7 +70,9 @@ audio_output_get(unsigned i) { assert(i < num_audio_outputs); - return &audio_outputs[i]; + assert(audio_outputs[i] != NULL); + + return audio_outputs[i]; } struct audio_output * @@ -100,7 +103,7 @@ audio_output_config_count(void) } void -audio_output_all_init(void) +audio_output_all_init(struct player_control *pc) { const struct config_param *param = NULL; unsigned int i; @@ -109,11 +112,10 @@ audio_output_all_init(void) notify_init(&audio_output_client_notify); num_audio_outputs = audio_output_config_count(); - audio_outputs = g_new(struct audio_output, num_audio_outputs); + audio_outputs = g_new(struct audio_output *, num_audio_outputs); for (i = 0; i < num_audio_outputs; i++) { - struct audio_output *output = &audio_outputs[i]; unsigned int j; param = config_get_next_param(CONF_AUDIO_OUTPUT, param); @@ -121,7 +123,8 @@ audio_output_all_init(void) /* only allow param to be NULL if there just one audioOutput */ assert(param || (num_audio_outputs == 1)); - if (!audio_output_init(output, param, &error)) { + struct audio_output *output = audio_output_new(param, pc, &error); + if (output == NULL) { if (param != NULL) MPD_ERROR("line %i: %s", param->line, error->message); @@ -129,9 +132,11 @@ audio_output_all_init(void) MPD_ERROR("%s", error->message); } + audio_outputs[i] = output; + /* require output names to be unique: */ for (j = 0; j < i; j++) { - if (!strcmp(output->name, audio_outputs[j].name)) { + if (!strcmp(output->name, audio_outputs[j]->name)) { MPD_ERROR("output devices with identical " "names: %s\n", output->name); } @@ -145,8 +150,8 @@ 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]); + audio_output_disable(audio_outputs[i]); + audio_output_finish(audio_outputs[i]); } g_free(audio_outputs); @@ -160,7 +165,7 @@ void audio_output_all_enable_disable(void) { for (unsigned i = 0; i < num_audio_outputs; i++) { - struct audio_output *ao = &audio_outputs[i]; + struct audio_output *ao = audio_outputs[i]; bool enabled; g_mutex_lock(ao->mutex); @@ -184,7 +189,7 @@ static bool audio_output_all_finished(void) { for (unsigned i = 0; i < num_audio_outputs; ++i) { - struct audio_output *ao = &audio_outputs[i]; + struct audio_output *ao = audio_outputs[i]; bool not_finished; g_mutex_lock(ao->mutex); @@ -212,7 +217,7 @@ static void audio_output_allow_play_all(void) { for (unsigned i = 0; i < num_audio_outputs; ++i) - audio_output_allow_play(&audio_outputs[i]); + audio_output_allow_play(audio_outputs[i]); } static void @@ -237,7 +242,7 @@ static void audio_output_all_reset_reopen(void) { for (unsigned i = 0; i < num_audio_outputs; ++i) { - struct audio_output *ao = &audio_outputs[i]; + struct audio_output *ao = audio_outputs[i]; audio_output_reset_reopen(ao); } @@ -258,7 +263,7 @@ audio_output_all_update(void) return false; for (i = 0; i < num_audio_outputs; ++i) - ret = audio_output_update(&audio_outputs[i], + ret = audio_output_update(audio_outputs[i], &input_audio_format, g_mp) || ret; return ret; @@ -282,7 +287,7 @@ audio_output_all_play(struct music_chunk *chunk) music_pipe_push(g_mp, chunk); for (i = 0; i < num_audio_outputs; ++i) - audio_output_play(&audio_outputs[i]); + audio_output_play(audio_outputs[i]); return true; } @@ -321,10 +326,10 @@ audio_output_all_open(const struct audio_format *audio_format, audio_output_all_update(); for (i = 0; i < num_audio_outputs; ++i) { - if (audio_outputs[i].enabled) + if (audio_outputs[i]->enabled) enabled = true; - if (audio_outputs[i].open) + if (audio_outputs[i]->open) ret = true; } @@ -368,7 +373,7 @@ static bool chunk_is_consumed(const struct music_chunk *chunk) { for (unsigned i = 0; i < num_audio_outputs; ++i) { - const struct audio_output *ao = &audio_outputs[i]; + const struct audio_output *ao = audio_outputs[i]; bool consumed; g_mutex_lock(ao->mutex); @@ -393,7 +398,7 @@ clear_tail_chunk(G_GNUC_UNUSED const struct music_chunk *chunk, bool *locked) assert(music_pipe_contains(g_mp, chunk)); for (unsigned i = 0; i < num_audio_outputs; ++i) { - struct audio_output *ao = &audio_outputs[i]; + struct audio_output *ao = audio_outputs[i]; /* this mutex will be unlocked by the caller when it's ready */ @@ -450,7 +455,7 @@ audio_output_all_check(void) by clear_tail_chunk() */ for (unsigned i = 0; i < num_audio_outputs; ++i) if (locked[i]) - g_mutex_unlock(audio_outputs[i].mutex); + g_mutex_unlock(audio_outputs[i]->mutex); /* return the chunk to the buffer */ music_buffer_return(g_music_buffer, shifted); @@ -460,17 +465,17 @@ audio_output_all_check(void) } bool -audio_output_all_wait(unsigned threshold) +audio_output_all_wait(struct player_control *pc, unsigned threshold) { - player_lock(); + player_lock(pc); if (audio_output_all_check() < threshold) { - player_unlock(); + player_unlock(pc); return true; } - player_wait(); - player_unlock(); + player_wait(pc); + player_unlock(pc); return audio_output_all_check() < threshold; } @@ -483,7 +488,7 @@ audio_output_all_pause(void) audio_output_all_update(); for (i = 0; i < num_audio_outputs; ++i) - audio_output_pause(&audio_outputs[i]); + audio_output_pause(audio_outputs[i]); audio_output_wait_all(); } @@ -492,7 +497,7 @@ void audio_output_all_drain(void) { for (unsigned i = 0; i < num_audio_outputs; ++i) - audio_output_drain_async(&audio_outputs[i]); + audio_output_drain_async(audio_outputs[i]); audio_output_wait_all(); } @@ -505,7 +510,7 @@ audio_output_all_cancel(void) /* send the cancel() command to all audio outputs */ for (i = 0; i < num_audio_outputs; ++i) - audio_output_cancel(&audio_outputs[i]); + audio_output_cancel(audio_outputs[i]); audio_output_wait_all(); @@ -530,7 +535,7 @@ audio_output_all_close(void) unsigned int i; for (i = 0; i < num_audio_outputs; ++i) - audio_output_close(&audio_outputs[i]); + audio_output_close(audio_outputs[i]); if (g_mp != NULL) { assert(g_music_buffer != NULL); @@ -553,7 +558,7 @@ audio_output_all_release(void) unsigned int i; for (i = 0; i < num_audio_outputs; ++i) - audio_output_release(&audio_outputs[i]); + audio_output_release(audio_outputs[i]); if (g_mp != NULL) { assert(g_music_buffer != NULL); diff --git a/src/output_all.h b/src/output_all.h index a579bf5f1..4eeb94f13 100644 --- a/src/output_all.h +++ b/src/output_all.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -32,13 +32,14 @@ struct audio_format; struct music_buffer; struct music_chunk; +struct player_control; /** * Global initialization: load audio outputs from the configuration * file and initialize them. */ void -audio_output_all_init(void); +audio_output_all_init(struct player_control *pc); /** * Global finalization: free memory occupied by audio outputs. All @@ -127,7 +128,7 @@ audio_output_all_check(void); * @return true if there are less than #threshold chunks in the pipe */ bool -audio_output_all_wait(unsigned threshold); +audio_output_all_wait(struct player_control *pc, unsigned threshold); /** * Puts all audio outputs into pause mode. Most implementations will diff --git a/src/output_api.h b/src/output_api.h index 8e002dd48..dfeef3518 100644 --- a/src/output_api.h +++ b/src/output_api.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,6 +21,7 @@ #define MPD_OUTPUT_API_H #include "output_plugin.h" +#include "output_internal.h" #include "audio_format.h" #include "tag.h" #include "conf.h" diff --git a/src/output_command.c b/src/output_command.c index 825884e8e..3988f350a 100644 --- a/src/output_command.c +++ b/src/output_command.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -50,7 +50,7 @@ audio_output_enable_index(unsigned idx) ao->enabled = true; idle_add(IDLE_OUTPUT); - pc_update_audio(); + pc_update_audio(ao->player_control); ++audio_output_state_version; @@ -79,7 +79,7 @@ audio_output_disable_index(unsigned idx) idle_add(IDLE_MIXER); } - pc_update_audio(); + pc_update_audio(ao->player_control); ++audio_output_state_version; diff --git a/src/output_command.h b/src/output_command.h index fab015c3f..eda30acc8 100644 --- a/src/output_command.h +++ b/src/output_command.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 24485f1c2..7b95be49b 100644 --- a/src/output_control.c +++ b/src/output_control.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -38,6 +38,11 @@ enum { struct notify audio_output_client_notify; +/** + * Waits for command completion. + * + * @param ao the #audio_output instance; must be locked + */ static void ao_command_wait(struct audio_output *ao) { while (ao->command != AO_COMMAND_NONE) { @@ -47,20 +52,43 @@ static void ao_command_wait(struct audio_output *ao) } } -static void ao_command(struct audio_output *ao, enum audio_output_command cmd) +/** + * Sends a command to the #audio_output object, but does not wait for + * completion. + * + * @param ao the #audio_output instance; must be locked + */ +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); +} + +/** + * Sends a command to the #audio_output object and waits for + * completion. + * + * @param ao the #audio_output instance; must be locked + */ +static void +ao_command(struct audio_output *ao, enum audio_output_command cmd) +{ + ao_command_async(ao, cmd); ao_command_wait(ao); } -static void ao_command_async(struct audio_output *ao, - enum audio_output_command cmd) +/** + * Lock the #audio_output object and execute the command + * synchronously. + */ +static void +ao_lock_command(struct audio_output *ao, enum audio_output_command cmd) { - assert(ao->command == AO_COMMAND_NONE); - ao->command = cmd; - g_cond_signal(ao->cond); + g_mutex_lock(ao->mutex); + ao_command(ao, cmd); + g_mutex_unlock(ao->mutex); } void @@ -78,9 +106,7 @@ audio_output_enable(struct audio_output *ao) audio_output_thread_start(ao); } - g_mutex_lock(ao->mutex); - ao_command(ao, AO_COMMAND_ENABLE); - g_mutex_unlock(ao->mutex); + ao_lock_command(ao, AO_COMMAND_ENABLE); } void @@ -97,9 +123,7 @@ audio_output_disable(struct audio_output *ao) return; } - g_mutex_lock(ao->mutex); - ao_command(ao, AO_COMMAND_DISABLE); - g_mutex_unlock(ao->mutex); + ao_lock_command(ao, AO_COMMAND_DISABLE); } /** @@ -302,28 +326,11 @@ void audio_output_finish(struct audio_output *ao) assert(ao->fail_timer == NULL); if (ao->thread != NULL) { - g_mutex_lock(ao->mutex); assert(ao->allow_play); - ao_command(ao, AO_COMMAND_KILL); - g_mutex_unlock(ao->mutex); + ao_lock_command(ao, AO_COMMAND_KILL); g_thread_join(ao->thread); + ao->thread = NULL; } - if (ao->mixer != NULL) - mixer_free(ao->mixer); - - ao_plugin_finish(ao->plugin, ao->data); - - 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); + audio_output_free(ao); } diff --git a/src/output_control.h b/src/output_control.h index f0e317d6e..874a53518 100644 --- a/src/output_control.h +++ b/src/output_control.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -29,6 +29,7 @@ struct audio_output; struct audio_format; struct config_param; struct music_pipe; +struct player_control; static inline GQuark audio_output_quark(void) @@ -36,10 +37,6 @@ audio_output_quark(void) return g_quark_from_static_string("audio_output"); } -bool -audio_output_init(struct audio_output *ao, const struct config_param *param, - GError **error_r); - /** * Enables the device. */ diff --git a/src/output_finish.c b/src/output_finish.c new file mode 100644 index 000000000..e11b43675 --- /dev/null +++ b/src/output_finish.c @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_internal.h" +#include "output_plugin.h" +#include "mixer_control.h" +#include "filter_plugin.h" + +#include <assert.h> + +void +ao_base_finish(struct audio_output *ao) +{ + assert(!ao->open); + assert(ao->fail_timer == NULL); + assert(ao->thread == NULL); + + if (ao->mixer != NULL) + mixer_free(ao->mixer); + + 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); +} + +void +audio_output_free(struct audio_output *ao) +{ + assert(!ao->open); + assert(ao->fail_timer == NULL); + assert(ao->thread == NULL); + + ao_plugin_finish(ao); +} diff --git a/src/output_init.c b/src/output_init.c index 96f87f512..3b8a7a9f1 100644 --- a/src/output_init.c +++ b/src/output_init.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -94,7 +94,8 @@ audio_output_mixer_type(const struct config_param *param) } static struct mixer * -audio_output_load_mixer(void *ao, const struct config_param *param, +audio_output_load_mixer(struct audio_output *ao, + const struct config_param *param, const struct mixer_plugin *plugin, struct filter *filter_chain, GError **error_r) @@ -126,29 +127,22 @@ audio_output_load_mixer(void *ao, const struct config_param *param, } bool -audio_output_init(struct audio_output *ao, const struct config_param *param, - GError **error_r) +ao_base_init(struct audio_output *ao, + const struct audio_output_plugin *plugin, + const struct config_param *param, GError **error_r) { - const struct audio_output_plugin *plugin = NULL; + assert(ao != NULL); + assert(plugin != NULL); + assert(plugin->finish != NULL); + assert(plugin->open != NULL); + assert(plugin->close != NULL); + assert(plugin->play != NULL); + GError *error = NULL; if (param) { const char *p; - 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(p); - if (plugin == NULL) { - 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) { @@ -168,16 +162,6 @@ audio_output_init(struct audio_output *ao, const struct config_param *param, } 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_r); - if (plugin == NULL) - return false; - - g_message("Successfully detected a %s audio device", - plugin->name); - ao->name = "default detected output"; audio_format_clear(&ao->config_audio_format); @@ -199,29 +183,6 @@ audio_output_init(struct audio_output *ao, const struct config_param *param, 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)) { @@ -251,14 +212,55 @@ audio_output_init(struct audio_output *ao, const struct config_param *param, ao->mutex = g_mutex_new(); ao->cond = g_cond_new(); - ao->data = ao_plugin_init(plugin, - &ao->config_audio_format, - param, error_r); - if (ao->data == NULL) - return false; + ao->mixer = NULL; + ao->replay_gain_filter = NULL; + ao->other_replay_gain_filter = NULL; + + /* 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; +} + +static bool +audio_output_setup(struct audio_output *ao, const struct config_param *param, + GError **error_r) +{ + + /* create the replay_gain filter */ - ao->mixer = audio_output_load_mixer(ao->data, param, - plugin->mixer_plugin, + 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; + } + + /* set up the mixer */ + + GError *error = NULL; + ao->mixer = audio_output_load_mixer(ao, param, + ao->plugin->mixer_plugin, ao->filter, &error); if (ao->mixer == NULL && error != NULL) { g_warning("Failed to initialize hardware mixer for '%s': %s", @@ -281,14 +283,53 @@ audio_output_init(struct audio_output *ao, const struct config_param *param, return false; } - /* the "convert" filter must be the last one in the chain */ + return true; +} - ao->convert_filter = filter_new(&convert_filter_plugin, NULL, NULL); - assert(ao->convert_filter != NULL); +struct audio_output * +audio_output_new(const struct config_param *param, + struct player_control *pc, + GError **error_r) +{ + const struct audio_output_plugin *plugin; - filter_chain_append(ao->filter, ao->convert_filter); + if (param) { + const char *p; - /* done */ + 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; + } - return true; + plugin = audio_output_plugin_get(p); + if (plugin == NULL) { + g_set_error(error_r, audio_output_quark(), 0, + "No such audio output plugin: %s", p); + return false; + } + } else { + g_warning("No \"%s\" defined in config file\n", + CONF_AUDIO_OUTPUT); + + plugin = audio_output_detect(error_r); + if (plugin == NULL) + return false; + + g_message("Successfully detected a %s audio device", + plugin->name); + } + + struct audio_output *ao = ao_plugin_init(plugin, param, error_r); + if (ao == NULL) + return NULL; + + if (!audio_output_setup(ao, param, error_r)) { + ao_plugin_finish(ao); + return NULL; + } + + ao->player_control = pc; + return ao; } diff --git a/src/output_internal.h b/src/output_internal.h index 7102ea5cd..9d975d789 100644 --- a/src/output_internal.h +++ b/src/output_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 @@ #include <time.h> +struct config_param; + enum audio_output_command { AO_COMMAND_NONE = 0, AO_COMMAND_ENABLE, @@ -64,12 +66,6 @@ struct audio_output { const struct audio_output_plugin *plugin; /** - * The plugin's internal data. It is passed to every plugin - * method. - */ - void *data; - - /** * The #mixer object associated with this audio output device. * May be NULL if none is available, or if software volume is * configured. @@ -217,6 +213,12 @@ struct audio_output { GCond *cond; /** + * The player_control object which "owns" this output. This + * object is needed to signal command completion. + */ + struct player_control *player_control; + + /** * 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 @@ -248,4 +250,20 @@ audio_output_command_is_finished(const struct audio_output *ao) return ao->command == AO_COMMAND_NONE; } +struct audio_output * +audio_output_new(const struct config_param *param, + struct player_control *pc, + GError **error_r); + +bool +ao_base_init(struct audio_output *ao, + const struct audio_output_plugin *plugin, + const struct config_param *param, GError **error_r); + +void +ao_base_finish(struct audio_output *ao); + +void +audio_output_free(struct audio_output *ao); + #endif diff --git a/src/output_list.c b/src/output_list.c index 8238f581b..e269086cf 100644 --- a/src/output_list.c +++ b/src/output_list.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,28 +20,29 @@ #include "config.h" #include "output_list.h" #include "output_api.h" - -extern const struct audio_output_plugin shoutPlugin; -extern const struct audio_output_plugin null_output_plugin; -extern const struct audio_output_plugin fifo_output_plugin; -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_output_plugin; -extern const struct audio_output_plugin mvp_output_plugin; -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; +#include "output/alsa_output_plugin.h" +#include "output/ao_output_plugin.h" +#include "output/ffado_output_plugin.h" +#include "output/fifo_output_plugin.h" +#include "output/httpd_output_plugin.h" +#include "output/jack_output_plugin.h" +#include "output/mvp_output_plugin.h" +#include "output/null_output_plugin.h" +#include "output/openal_output_plugin.h" +#include "output/oss_output_plugin.h" +#include "output/osx_output_plugin.h" +#include "output/pipe_output_plugin.h" +#include "output/pulse_output_plugin.h" +#include "output/raop_output_plugin.h" +#include "output/recorder_output_plugin.h" +#include "output/roar_output_plugin.h" +#include "output/shout_output_plugin.h" +#include "output/solaris_output_plugin.h" +#include "output/winmm_output_plugin.h" const struct audio_output_plugin *audio_output_plugins[] = { #ifdef HAVE_SHOUT - &shoutPlugin, + &shout_output_plugin, #endif &null_output_plugin, #ifdef HAVE_FIFO @@ -51,7 +52,10 @@ const struct audio_output_plugin *audio_output_plugins[] = { &pipe_output_plugin, #endif #ifdef HAVE_ALSA - &alsaPlugin, + &alsa_output_plugin, +#endif +#ifdef HAVE_ROAR + &roar_output_plugin, #endif #ifdef HAVE_AO &ao_output_plugin, @@ -63,7 +67,10 @@ const struct audio_output_plugin *audio_output_plugins[] = { &openal_output_plugin, #endif #ifdef HAVE_OSX - &osxPlugin, + &osx_output_plugin, +#endif +#ifdef ENABLE_RAOP_OUTPUT + &raop_output_plugin, #endif #ifdef ENABLE_SOLARIS_OUTPUT &solaris_output_plugin, @@ -99,8 +106,8 @@ audio_output_plugin_get(const char *name) const struct audio_output_plugin *plugin; audio_output_plugins_for_each(plugin, i) - if (strcmp(audio_output_plugins[i]->name, name) == 0) - return audio_output_plugins[i]; + if (strcmp(plugin->name, name) == 0) + return plugin; return NULL; } diff --git a/src/output_list.h b/src/output_list.h index d72bc224b..3deb31c00 100644 --- a/src/output_list.h +++ b/src/output_list.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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.c b/src/output_plugin.c new file mode 100644 index 000000000..221570c1c --- /dev/null +++ b/src/output_plugin.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_plugin.h" +#include "output_internal.h" + +struct audio_output * +ao_plugin_init(const struct audio_output_plugin *plugin, + const struct config_param *param, + GError **error) +{ + assert(plugin != NULL); + assert(plugin->init != NULL); + + return plugin->init(param, error); +} + +void +ao_plugin_finish(struct audio_output *ao) +{ + ao->plugin->finish(ao); +} + +bool +ao_plugin_enable(struct audio_output *ao, GError **error_r) +{ + return ao->plugin->enable != NULL + ? ao->plugin->enable(ao, error_r) + : true; +} + +void +ao_plugin_disable(struct audio_output *ao) +{ + if (ao->plugin->disable != NULL) + ao->plugin->disable(ao); +} + +bool +ao_plugin_open(struct audio_output *ao, struct audio_format *audio_format, + GError **error) +{ + return ao->plugin->open(ao, audio_format, error); +} + +void +ao_plugin_close(struct audio_output *ao) +{ + ao->plugin->close(ao); +} + +unsigned +ao_plugin_delay(struct audio_output *ao) +{ + return ao->plugin->delay != NULL + ? ao->plugin->delay(ao) + : 0; +} + +void +ao_plugin_send_tag(struct audio_output *ao, const struct tag *tag) +{ + if (ao->plugin->send_tag != NULL) + ao->plugin->send_tag(ao, tag); +} + +size_t +ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error) +{ + return ao->plugin->play(ao, chunk, size, error); +} + +void +ao_plugin_drain(struct audio_output *ao) +{ + if (ao->plugin->drain != NULL) + ao->plugin->drain(ao); +} + +void +ao_plugin_cancel(struct audio_output *ao) +{ + if (ao->plugin->cancel != NULL) + ao->plugin->cancel(ao); +} + +bool +ao_plugin_pause(struct audio_output *ao) +{ + return ao->plugin->pause != NULL && ao->plugin->pause(ao); +} diff --git a/src/output_plugin.h b/src/output_plugin.h index 36e17ed1b..209ca6221 100644 --- a/src/output_plugin.h +++ b/src/output_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -48,23 +48,20 @@ struct audio_output_plugin { * Configure and initialize the device, but do not open it * yet. * - * @param audio_format the configured audio format, or NULL if - * none is configured * @param param the configuration section, or NULL if there is * no configuration - * @param error location to store the error occuring, or NULL + * @param error location to store the error occurring, or NULL * to ignore errors * @return NULL on error, or an opaque pointer to the plugin's * data */ - void *(*init)(const struct audio_format *audio_format, - const struct config_param *param, - GError **error); + struct audio_output *(*init)(const struct config_param *param, + GError **error); /** * Free resources allocated by this device. */ - void (*finish)(void *data); + void (*finish)(struct audio_output *data); /** * Enable the device. This may allocate resources, preparing @@ -72,33 +69,33 @@ struct audio_output_plugin { * 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 + * @param error_r location to store the error occurring, or * NULL to ignore errors * @return true on success, false on error */ - bool (*enable)(void *data, GError **error_r); + bool (*enable)(struct audio_output *data, GError **error_r); /** * Disables the device. It is closed before this method is * called. */ - void (*disable)(void *data); + void (*disable)(struct audio_output *data); /** * Really open the device. * * @param audio_format the audio format in which data is going * to be delivered; may be modified by the plugin - * @param error location to store the error occuring, or NULL + * @param error location to store the error occurring, or NULL * to ignore errors */ - bool (*open)(void *data, struct audio_format *audio_format, + bool (*open)(struct audio_output *data, struct audio_format *audio_format, GError **error); /** * Close the device. */ - void (*close)(void *data); + void (*close)(struct audio_output *data); /** * Returns a positive number if the output thread shall delay @@ -108,34 +105,35 @@ struct audio_output_plugin { * * @return the number of milliseconds to wait */ - unsigned (*delay)(void *data); + unsigned (*delay)(struct audio_output *data); /** * Display metadata for the next chunk. Optional method, * because not all devices can display metadata. */ - void (*send_tag)(void *data, const struct tag *tag); + void (*send_tag)(struct audio_output *data, const struct tag *tag); /** * Play a chunk of audio data. * - * @param error location to store the error occuring, or NULL + * @param error location to store the error occurring, or NULL * to ignore errors * @return the number of bytes played, or 0 on error */ - size_t (*play)(void *data, const void *chunk, size_t size, + size_t (*play)(struct audio_output *data, + const void *chunk, size_t size, GError **error); /** * Wait until the device has finished playing. */ - void (*drain)(void *data); + void (*drain)(struct audio_output *data); /** * Try to cancel data which may still be in the device's * buffers. */ - void (*cancel)(void *data); + void (*cancel)(struct audio_output *data); /** * Pause the device. If supported, it may perform a special @@ -148,7 +146,7 @@ struct audio_output_plugin { * @return false on error (output will be closed then), true * for continue to pause */ - bool (*pause)(void *data); + bool (*pause)(struct audio_output *data); /** * The mixer plugin associated with this output plugin. This @@ -167,95 +165,46 @@ ao_plugin_test_default_device(const struct audio_output_plugin *plugin) : false; } -static inline void * +G_GNUC_MALLOC +struct audio_output * ao_plugin_init(const struct audio_output_plugin *plugin, - const struct audio_format *audio_format, const struct config_param *param, - GError **error) -{ - return plugin->init(audio_format, param, error); -} + GError **error); -static inline void -ao_plugin_finish(const struct audio_output_plugin *plugin, void *data) -{ - plugin->finish(data); -} +void +ao_plugin_finish(struct audio_output *ao); -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; -} +bool +ao_plugin_enable(struct audio_output *ao, GError **error_r); -static inline void -ao_plugin_disable(const struct audio_output_plugin *plugin, void *data) -{ - if (plugin->disable != NULL) - plugin->disable(data); -} +void +ao_plugin_disable(struct audio_output *ao); -static inline bool -ao_plugin_open(const struct audio_output_plugin *plugin, - void *data, struct audio_format *audio_format, - GError **error) -{ - return plugin->open(data, audio_format, error); -} +bool +ao_plugin_open(struct audio_output *ao, struct audio_format *audio_format, + GError **error); -static inline void -ao_plugin_close(const struct audio_output_plugin *plugin, void *data) -{ - plugin->close(data); -} +void +ao_plugin_close(struct audio_output *ao); -static inline unsigned -ao_plugin_delay(const struct audio_output_plugin *plugin, void *data) -{ - return plugin->delay != NULL - ? plugin->delay(data) - : 0; -} +G_GNUC_PURE +unsigned +ao_plugin_delay(struct audio_output *ao); -static inline void -ao_plugin_send_tag(const struct audio_output_plugin *plugin, - void *data, const struct tag *tag) -{ - if (plugin->send_tag != NULL) - plugin->send_tag(data, tag); -} +void +ao_plugin_send_tag(struct audio_output *ao, const struct tag *tag); -static inline size_t -ao_plugin_play(const struct audio_output_plugin *plugin, - void *data, const void *chunk, size_t size, - GError **error) -{ - return plugin->play(data, chunk, size, error); -} +size_t +ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error); -static inline void -ao_plugin_drain(const struct audio_output_plugin *plugin, void *data) -{ - if (plugin->drain != NULL) - plugin->drain(data); -} +void +ao_plugin_drain(struct audio_output *ao); -static inline void -ao_plugin_cancel(const struct audio_output_plugin *plugin, void *data) -{ - if (plugin->cancel != NULL) - plugin->cancel(data); -} +void +ao_plugin_cancel(struct audio_output *ao); -static inline bool -ao_plugin_pause(const struct audio_output_plugin *plugin, void *data) -{ - return plugin->pause != NULL - ? plugin->pause(data) - : false; -} +bool +ao_plugin_pause(struct audio_output *ao); #endif diff --git a/src/output_print.c b/src/output_print.c index 7a747ad2f..483648ca2 100644 --- a/src/output_print.c +++ b/src/output_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_print.h b/src/output_print.h index 5ad7e34c7..e02f4e9f5 100644 --- a/src/output_print.h +++ b/src/output_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 e1187b951..7bcafb36b 100644 --- a/src/output_state.c +++ b/src/output_state.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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.h b/src/output_state.h index 962ccd97a..320a3520a 100644 --- a/src/output_state.h +++ b/src/output_state.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_thread.c b/src/output_thread.c index bf56ca971..02c315af9 100644 --- a/src/output_thread.c +++ b/src/output_thread.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -29,6 +29,7 @@ #include "filter/convert_filter_plugin.h" #include "filter/replay_gain_filter_plugin.h" #include "mpd_error.h" +#include "notify.h" #include <glib.h> @@ -59,7 +60,7 @@ ao_enable(struct audio_output *ao) return true; g_mutex_unlock(ao->mutex); - success = ao_plugin_enable(ao->plugin, ao->data, &error); + success = ao_plugin_enable(ao, &error); g_mutex_lock(ao->mutex); if (!success) { g_warning("Failed to enable \"%s\" [%s]: %s\n", @@ -85,7 +86,7 @@ ao_disable(struct audio_output *ao) ao->really_enabled = false; g_mutex_unlock(ao->mutex); - ao_plugin_disable(ao->plugin, ao->data); + ao_plugin_disable(ao); g_mutex_lock(ao->mutex); } } @@ -174,9 +175,7 @@ ao_open(struct audio_output *ao) &ao->config_audio_format); g_mutex_unlock(ao->mutex); - success = ao_plugin_open(ao->plugin, ao->data, - &ao->out_audio_format, - &error); + success = ao_plugin_open(ao, &ao->out_audio_format, &error); g_mutex_lock(ao->mutex); assert(!ao->open); @@ -220,11 +219,11 @@ ao_close(struct audio_output *ao, bool drain) g_mutex_unlock(ao->mutex); if (drain) - ao_plugin_drain(ao->plugin, ao->data); + ao_plugin_drain(ao); else - ao_plugin_cancel(ao->plugin, ao->data); + ao_plugin_cancel(ao); - ao_plugin_close(ao->plugin, ao->data); + ao_plugin_close(ao); ao_filter_close(ao); g_mutex_lock(ao->mutex); @@ -256,7 +255,7 @@ ao_reopen_filter(struct audio_output *ao) ao->fail_timer = g_timer_new(); g_mutex_unlock(ao->mutex); - ao_plugin_close(ao->plugin, ao->data); + ao_plugin_close(ao); g_mutex_lock(ao->mutex); return; @@ -301,7 +300,7 @@ static bool ao_wait(struct audio_output *ao) { while (true) { - unsigned delay = ao_plugin_delay(ao->plugin, ao->data); + unsigned delay = ao_plugin_delay(ao); if (delay == 0) return true; @@ -402,8 +401,12 @@ ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk, 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); + if (!pcm_mix(dest, data, length, ao->in_audio_format.format, + 1.0 - chunk->mix_ratio)) { + g_warning("Cannot cross-fade format %s", + sample_format_to_string(ao->in_audio_format.format)); + return NULL; + } data = dest; length = other_length; @@ -433,7 +436,7 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) if (chunk->tag != NULL) { g_mutex_unlock(ao->mutex); - ao_plugin_send_tag(ao->plugin, ao->data, chunk->tag); + ao_plugin_send_tag(ao, chunk->tag); g_mutex_lock(ao->mutex); } @@ -455,8 +458,7 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) break; g_mutex_unlock(ao->mutex); - nbytes = ao_plugin_play(ao->plugin, ao->data, data, size, - &error); + nbytes = ao_plugin_play(ao, data, size, &error); g_mutex_lock(ao->mutex); if (nbytes == 0) { /* play()==0 means failure */ @@ -535,7 +537,7 @@ ao_play(struct audio_output *ao) ao->chunk_finished = true; g_mutex_unlock(ao->mutex); - player_lock_signal(); + player_lock_signal(ao->player_control); g_mutex_lock(ao->mutex); return true; @@ -546,7 +548,7 @@ static void ao_pause(struct audio_output *ao) bool ret; g_mutex_unlock(ao->mutex); - ao_plugin_cancel(ao->plugin, ao->data); + ao_plugin_cancel(ao); g_mutex_lock(ao->mutex); ao->pause = true; @@ -557,7 +559,7 @@ static void ao_pause(struct audio_output *ao) break; g_mutex_unlock(ao->mutex); - ret = ao_plugin_pause(ao->plugin, ao->data); + ret = ao_plugin_pause(ao); g_mutex_lock(ao->mutex); if (!ret) { @@ -631,7 +633,7 @@ static gpointer audio_output_task(gpointer arg) assert(music_pipe_peek(ao->pipe) == NULL); g_mutex_unlock(ao->mutex); - ao_plugin_drain(ao->plugin, ao->data); + ao_plugin_drain(ao); g_mutex_lock(ao->mutex); } @@ -643,7 +645,7 @@ static gpointer audio_output_task(gpointer arg) if (ao->open) { g_mutex_unlock(ao->mutex); - ao_plugin_cancel(ao->plugin, ao->data); + ao_plugin_cancel(ao); g_mutex_lock(ao->mutex); } diff --git a/src/output_thread.h b/src/output_thread.h index 1ee0856f2..5ad9a7527 100644 --- a/src/output_thread.h +++ b/src/output_thread.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 59369cb34..e2e22791f 100644 --- a/src/page.c +++ b/src/page.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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.h b/src/page.h index 652c4ad6e..8a3aaf396 100644 --- a/src/page.h +++ b/src/page.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 d1b9ad6ee..59a91a0f7 100644 --- a/src/path.c +++ b/src/path.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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.h b/src/path.h index 512cd13ea..00c368e70 100644 --- a/src/path.h +++ b/src/path.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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.c b/src/pcm_buffer.c index 60a699b20..4b1eb875a 100644 --- a/src/pcm_buffer.c +++ b/src/pcm_buffer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 "pcm_buffer.h" +#include "poison.h" /** * Align the specified size to the next 8k boundary. @@ -45,6 +47,9 @@ pcm_buffer_get(struct pcm_buffer *buffer, size_t size) buffer->size = align_8k(size); buffer->buffer = g_malloc(buffer->size); + } else { + /* discard old buffer contents */ + poison_undefined(buffer->buffer, buffer->size); } assert(buffer->size >= size); diff --git a/src/pcm_buffer.h b/src/pcm_buffer.h index b132d4fd2..4502976f6 100644 --- a/src/pcm_buffer.h +++ b/src/pcm_buffer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_BUFFER_H #define PCM_BUFFER_H +#include "check.h" + #include <glib.h> #include <assert.h> diff --git a/src/pcm_byteswap.c b/src/pcm_byteswap.c deleted file mode 100644 index 6577319d4..000000000 --- a/src/pcm_byteswap.c +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (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 deleted file mode 100644 index 005e75ded..000000000 --- a/src/pcm_byteswap.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (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 34e72ca4e..ec2bd69a5 100644 --- a/src/pcm_channels.c +++ b/src/pcm_channels.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,14 +20,16 @@ #include "config.h" #include "pcm_channels.h" #include "pcm_buffer.h" +#include "pcm_utils.h" #include <assert.h> static void -pcm_convert_channels_16_1_to_2(int16_t *dest, const int16_t *src, - unsigned num_frames) +pcm_convert_channels_16_1_to_2(int16_t *restrict dest, + const int16_t *restrict src, + const int16_t *restrict src_end) { - while (num_frames-- > 0) { + while (src < src_end) { int16_t value = *src++; *dest++ = value; @@ -36,10 +38,11 @@ pcm_convert_channels_16_1_to_2(int16_t *dest, const int16_t *src, } static void -pcm_convert_channels_16_2_to_1(int16_t *dest, const int16_t *src, - unsigned num_frames) +pcm_convert_channels_16_2_to_1(int16_t *restrict dest, + const int16_t *restrict src, + const int16_t *restrict src_end) { - while (num_frames-- > 0) { + while (src < src_end) { int32_t a = *src++, b = *src++; *dest++ = (a + b) / 2; @@ -47,15 +50,16 @@ pcm_convert_channels_16_2_to_1(int16_t *dest, const int16_t *src, } static void -pcm_convert_channels_16_n_to_2(int16_t *dest, - unsigned src_channels, const int16_t *src, - unsigned num_frames) +pcm_convert_channels_16_n_to_2(int16_t *restrict dest, + unsigned src_channels, + const int16_t *restrict src, + const int16_t *restrict src_end) { unsigned c; assert(src_channels > 0); - while (num_frames-- > 0) { + while (src < src_end) { int32_t sum = 0; int16_t value; @@ -71,23 +75,25 @@ pcm_convert_channels_16_n_to_2(int16_t *dest, const int16_t * pcm_convert_channels_16(struct pcm_buffer *buffer, - uint8_t dest_channels, - uint8_t src_channels, const int16_t *src, + unsigned dest_channels, + unsigned src_channels, const int16_t *src, size_t src_size, size_t *dest_size_r) { - unsigned num_frames = src_size / src_channels / sizeof(*src); - unsigned dest_size = num_frames * dest_channels * sizeof(*src); - int16_t *dest = pcm_buffer_get(buffer, dest_size); + assert(src_size % (sizeof(*src) * src_channels) == 0); + size_t dest_size = src_size / src_channels * dest_channels; *dest_size_r = dest_size; + int16_t *dest = pcm_buffer_get(buffer, dest_size); + const int16_t *src_end = pcm_end_pointer(src, src_size); + if (src_channels == 1 && dest_channels == 2) - pcm_convert_channels_16_1_to_2(dest, src, num_frames); + pcm_convert_channels_16_1_to_2(dest, src, src_end); else if (src_channels == 2 && dest_channels == 1) - pcm_convert_channels_16_2_to_1(dest, src, num_frames); + pcm_convert_channels_16_2_to_1(dest, src, src_end); else if (dest_channels == 2) pcm_convert_channels_16_n_to_2(dest, src_channels, src, - num_frames); + src_end); else return NULL; @@ -95,10 +101,11 @@ pcm_convert_channels_16(struct pcm_buffer *buffer, } static void -pcm_convert_channels_24_1_to_2(int32_t *dest, const int32_t *src, - unsigned num_frames) +pcm_convert_channels_24_1_to_2(int32_t *restrict dest, + const int32_t *restrict src, + const int32_t *restrict src_end) { - while (num_frames-- > 0) { + while (src < src_end) { int32_t value = *src++; *dest++ = value; @@ -107,10 +114,11 @@ pcm_convert_channels_24_1_to_2(int32_t *dest, const int32_t *src, } static void -pcm_convert_channels_24_2_to_1(int32_t *dest, const int32_t *src, - unsigned num_frames) +pcm_convert_channels_24_2_to_1(int32_t *restrict dest, + const int32_t *restrict src, + const int32_t *restrict src_end) { - while (num_frames-- > 0) { + while (src < src_end) { int32_t a = *src++, b = *src++; *dest++ = (a + b) / 2; @@ -118,15 +126,16 @@ pcm_convert_channels_24_2_to_1(int32_t *dest, const int32_t *src, } static void -pcm_convert_channels_24_n_to_2(int32_t *dest, - unsigned src_channels, const int32_t *src, - unsigned num_frames) +pcm_convert_channels_24_n_to_2(int32_t *restrict dest, + unsigned src_channels, + const int32_t *restrict src, + const int32_t *restrict src_end) { unsigned c; assert(src_channels > 0); - while (num_frames-- > 0) { + while (src < src_end) { int32_t sum = 0; int32_t value; @@ -142,23 +151,25 @@ pcm_convert_channels_24_n_to_2(int32_t *dest, const int32_t * pcm_convert_channels_24(struct pcm_buffer *buffer, - uint8_t dest_channels, - uint8_t src_channels, const int32_t *src, + unsigned dest_channels, + unsigned src_channels, const int32_t *src, size_t src_size, size_t *dest_size_r) { - unsigned num_frames = src_size / src_channels / sizeof(*src); - unsigned dest_size = num_frames * dest_channels * sizeof(*src); - int32_t *dest = pcm_buffer_get(buffer, dest_size); + assert(src_size % (sizeof(*src) * src_channels) == 0); + size_t dest_size = src_size / src_channels * dest_channels; *dest_size_r = dest_size; + int32_t *dest = pcm_buffer_get(buffer, dest_size); + const int32_t *src_end = pcm_end_pointer(src, src_size); + if (src_channels == 1 && dest_channels == 2) - pcm_convert_channels_24_1_to_2(dest, src, num_frames); + pcm_convert_channels_24_1_to_2(dest, src, src_end); else if (src_channels == 2 && dest_channels == 1) - pcm_convert_channels_24_2_to_1(dest, src, num_frames); + pcm_convert_channels_24_2_to_1(dest, src, src_end); else if (dest_channels == 2) pcm_convert_channels_24_n_to_2(dest, src_channels, src, - num_frames); + src_end); else return NULL; @@ -167,16 +178,17 @@ pcm_convert_channels_24(struct pcm_buffer *buffer, static void pcm_convert_channels_32_1_to_2(int32_t *dest, const int32_t *src, - unsigned num_frames) + const int32_t *src_end) { - pcm_convert_channels_24_1_to_2(dest, src, num_frames); + pcm_convert_channels_24_1_to_2(dest, src, src_end); } static void -pcm_convert_channels_32_2_to_1(int32_t *dest, const int32_t *src, - unsigned num_frames) +pcm_convert_channels_32_2_to_1(int32_t *restrict dest, + const int32_t *restrict src, + const int32_t *restrict src_end) { - while (num_frames-- > 0) { + while (src < src_end) { int64_t a = *src++, b = *src++; *dest++ = (a + b) / 2; @@ -186,13 +198,13 @@ pcm_convert_channels_32_2_to_1(int32_t *dest, const int32_t *src, static void pcm_convert_channels_32_n_to_2(int32_t *dest, unsigned src_channels, const int32_t *src, - unsigned num_frames) + const int32_t *src_end) { unsigned c; assert(src_channels > 0); - while (num_frames-- > 0) { + while (src < src_end) { int64_t sum = 0; int32_t value; @@ -208,23 +220,25 @@ pcm_convert_channels_32_n_to_2(int32_t *dest, const int32_t * pcm_convert_channels_32(struct pcm_buffer *buffer, - uint8_t dest_channels, - uint8_t src_channels, const int32_t *src, + unsigned dest_channels, + unsigned src_channels, const int32_t *src, size_t src_size, size_t *dest_size_r) { - unsigned num_frames = src_size / src_channels / sizeof(*src); - unsigned dest_size = num_frames * dest_channels * sizeof(*src); - int32_t *dest = pcm_buffer_get(buffer, dest_size); + assert(src_size % (sizeof(*src) * src_channels) == 0); + size_t dest_size = src_size / src_channels * dest_channels; *dest_size_r = dest_size; + int32_t *dest = pcm_buffer_get(buffer, dest_size); + const int32_t *src_end = pcm_end_pointer(src, src_size); + if (src_channels == 1 && dest_channels == 2) - pcm_convert_channels_32_1_to_2(dest, src, num_frames); + pcm_convert_channels_32_1_to_2(dest, src, src_end); else if (src_channels == 2 && dest_channels == 1) - pcm_convert_channels_32_2_to_1(dest, src, num_frames); + pcm_convert_channels_32_2_to_1(dest, src, src_end); else if (dest_channels == 2) pcm_convert_channels_32_n_to_2(dest, src_channels, src, - num_frames); + src_end); else return NULL; diff --git a/src/pcm_channels.h b/src/pcm_channels.h index a23cbd364..1e4a0991f 100644 --- a/src/pcm_channels.h +++ b/src/pcm_channels.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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, - uint8_t dest_channels, - uint8_t src_channels, const int16_t *src, + unsigned dest_channels, + unsigned 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, - uint8_t dest_channels, - uint8_t src_channels, const int32_t *src, + unsigned dest_channels, + unsigned 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, - uint8_t dest_channels, - uint8_t src_channels, const int32_t *src, + unsigned dest_channels, + unsigned 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 7bd4d7215..63f9a1b98 100644 --- a/src/pcm_convert.c +++ b/src/pcm_convert.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,9 +21,9 @@ #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 "glib_compat.h" #include <assert.h> #include <string.h> @@ -37,23 +37,77 @@ void pcm_convert_init(struct pcm_convert_state *state) { memset(state, 0, sizeof(*state)); + pcm_dsd_init(&state->dsd); pcm_resample_init(&state->resample); 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) { + pcm_dsd_deinit(&state->dsd); 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); +} + +void +pcm_convert_reset(struct pcm_convert_state *state) +{ + pcm_dsd_reset(&state->dsd); + pcm_resample_reset(&state->resample); +} + +static const void * +pcm_convert_channels(struct pcm_buffer *buffer, enum sample_format format, + uint8_t dest_channels, + uint8_t src_channels, const void *src, + size_t src_size, size_t *dest_size_r, + GError **error_r) +{ + const void *dest = NULL; + + switch (format) { + case SAMPLE_FORMAT_UNDEFINED: + case SAMPLE_FORMAT_S8: + case SAMPLE_FORMAT_FLOAT: + case SAMPLE_FORMAT_DSD: + g_set_error(error_r, pcm_convert_quark(), 0, + "Channel conversion not implemented for format '%s'", + sample_format_to_string(format)); + return NULL; + + case SAMPLE_FORMAT_S16: + dest = pcm_convert_channels_16(buffer, dest_channels, + src_channels, src, + src_size, dest_size_r); + break; + + case SAMPLE_FORMAT_S24_P32: + dest = pcm_convert_channels_24(buffer, dest_channels, + src_channels, src, + src_size, dest_size_r); + break; + + case SAMPLE_FORMAT_S32: + dest = pcm_convert_channels_32(buffer, dest_channels, + src_channels, src, + src_size, dest_size_r); + break; + } + + if (dest == NULL) { + g_set_error(error_r, pcm_convert_quark(), 0, + "Conversion from %u to %u channels " + "is not implemented", + src_channels, dest_channels); + return NULL; + } + + return dest; } static const int16_t * @@ -103,11 +157,6 @@ pcm_convert_16(struct pcm_convert_state *state, 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; } @@ -158,54 +207,10 @@ pcm_convert_24(struct pcm_convert_state *state, 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, @@ -252,15 +257,65 @@ pcm_convert_32(struct pcm_convert_state *state, 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; } +static const float * +pcm_convert_float(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) +{ + const float *buffer = src_buffer; + size_t size = src_size; + + assert(dest_format->format == SAMPLE_FORMAT_FLOAT); + + /* convert channels first, hoping the source format is + supported (float is not) */ + + if (dest_format->channels != src_format->channels) { + buffer = pcm_convert_channels(&state->channels_buffer, + src_format->format, + dest_format->channels, + src_format->channels, + buffer, size, &size, error_r); + if (buffer == NULL) + return NULL; + } + + /* convert to float now */ + + buffer = pcm_convert_to_float(&state->format_buffer, + src_format->format, + buffer, size, &size); + if (buffer == NULL) { + g_set_error(error_r, pcm_convert_quark(), 0, + "Conversion from %s to float is not implemented", + sample_format_to_string(src_format->format)); + return NULL; + } + + /* resample with float, because this is the best format for + libsamplerate */ + + if (src_format->sample_rate != dest_format->sample_rate) { + buffer = pcm_resample_float(&state->resample, + dest_format->channels, + src_format->sample_rate, + buffer, size, + dest_format->sample_rate, &size, + error_r); + if (buffer == NULL) + return NULL; + } + + *dest_size_r = size; + return buffer; +} + const void * pcm_convert(struct pcm_convert_state *state, const struct audio_format *src_format, @@ -269,6 +324,27 @@ pcm_convert(struct pcm_convert_state *state, size_t *dest_size_r, GError **error_r) { + struct audio_format float_format; + if (src_format->format == SAMPLE_FORMAT_DSD) { + size_t f_size; + const float *f = pcm_dsd_to_float(&state->dsd, + src_format->channels, + false, src, src_size, + &f_size); + if (f == NULL) { + g_set_error_literal(error_r, pcm_convert_quark(), 0, + "DSD to PCM conversion failed"); + return NULL; + } + + float_format = *src_format; + float_format.format = SAMPLE_FORMAT_FLOAT; + + src_format = &float_format; + src = f; + src_size = f_size; + } + switch (dest_format->format) { case SAMPLE_FORMAT_S16: return pcm_convert_16(state, @@ -276,12 +352,6 @@ pcm_convert(struct pcm_convert_state *state, 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 SAMPLE_FORMAT_S24_P32: return pcm_convert_24(state, src_format, src, src_size, @@ -294,6 +364,12 @@ pcm_convert(struct pcm_convert_state *state, dest_format, dest_size_r, error_r); + case SAMPLE_FORMAT_FLOAT: + return pcm_convert_float(state, + src_format, src, src_size, + dest_format, dest_size_r, + error_r); + default: g_set_error(error_r, pcm_convert_quark(), 0, "PCM conversion to %s is not implemented", diff --git a/src/pcm_convert.h b/src/pcm_convert.h index 01ba2c787..be11a6e41 100644 --- a/src/pcm_convert.h +++ b/src/pcm_convert.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ #ifndef PCM_CONVERT_H #define PCM_CONVERT_H +#include "pcm_dsd.h" #include "pcm_resample.h" #include "pcm_dither.h" #include "pcm_buffer.h" @@ -32,6 +33,8 @@ struct audio_format; * conversions. */ struct pcm_convert_state { + struct pcm_dsd dsd; + struct pcm_resample_state resample; struct pcm_dither dither; @@ -39,14 +42,8 @@ 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 @@ -67,6 +64,13 @@ void pcm_convert_init(struct pcm_convert_state *state); void pcm_convert_deinit(struct pcm_convert_state *state); /** + * Reset the pcm_convert_state object. Use this at the boundary + * between two distinct songs and each time the format changes. + */ +void +pcm_convert_reset(struct pcm_convert_state *state); + +/** * Converts PCM data between two audio formats. * * @param state an initialized pcm_convert_state object @@ -75,7 +79,7 @@ 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 - * @param error_r location to store the error occuring, or NULL to + * @param error_r location to store the error occurring, or NULL to * ignore errors * @return the destination buffer, or NULL on error */ diff --git a/src/pcm_dither.c b/src/pcm_dither.c index 03388f0e0..4811946c8 100644 --- a/src/pcm_dither.c +++ b/src/pcm_dither.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -72,10 +72,9 @@ pcm_dither_sample_24_to_16(int32_t sample, struct pcm_dither *dither) void pcm_dither_24_to_16(struct pcm_dither *dither, - int16_t *dest, const int32_t *src, - unsigned num_samples) + int16_t *dest, const int32_t *src, const int32_t *src_end) { - while (num_samples-- > 0) + while (src < src_end) *dest++ = pcm_dither_sample_24_to_16(*src++, dither); } @@ -87,9 +86,8 @@ pcm_dither_sample_32_to_16(int32_t sample, struct pcm_dither *dither) void pcm_dither_32_to_16(struct pcm_dither *dither, - int16_t *dest, const int32_t *src, - unsigned num_samples) + int16_t *dest, const int32_t *src, const int32_t *src_end) { - while (num_samples-- > 0) + while (src < src_end) *dest++ = pcm_dither_sample_32_to_16(*src++, dither); } diff --git a/src/pcm_dither.h b/src/pcm_dither.h index dafae957f..046dea21e 100644 --- a/src/pcm_dither.h +++ b/src/pcm_dither.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -36,12 +36,10 @@ pcm_dither_24_init(struct pcm_dither *dither) void pcm_dither_24_to_16(struct pcm_dither *dither, - int16_t *dest, const int32_t *src, - unsigned num_samples); + int16_t *dest, const int32_t *src, const int32_t *src_end); void pcm_dither_32_to_16(struct pcm_dither *dither, - int16_t *dest, const int32_t *src, - unsigned num_samples); + int16_t *dest, const int32_t *src, const int32_t *src_end); #endif diff --git a/src/pcm_dsd.c b/src/pcm_dsd.c new file mode 100644 index 000000000..76266b4cc --- /dev/null +++ b/src/pcm_dsd.c @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_dsd.h" +#include "dsd2pcm/dsd2pcm.h" + +#include <glib.h> +#include <string.h> + +void +pcm_dsd_init(struct pcm_dsd *dsd) +{ + pcm_buffer_init(&dsd->buffer); + + memset(dsd->dsd2pcm, 0, sizeof(dsd->dsd2pcm)); +} + +void +pcm_dsd_deinit(struct pcm_dsd *dsd) +{ + pcm_buffer_deinit(&dsd->buffer); + + for (unsigned i = 0; i < G_N_ELEMENTS(dsd->dsd2pcm); ++i) + if (dsd->dsd2pcm[i] != NULL) + dsd2pcm_destroy(dsd->dsd2pcm[i]); +} + +void +pcm_dsd_reset(struct pcm_dsd *dsd) +{ + for (unsigned i = 0; i < G_N_ELEMENTS(dsd->dsd2pcm); ++i) + if (dsd->dsd2pcm[i] != NULL) + dsd2pcm_reset(dsd->dsd2pcm[i]); +} + +const float * +pcm_dsd_to_float(struct pcm_dsd *dsd, unsigned channels, bool lsbfirst, + const uint8_t *src, size_t src_size, + size_t *dest_size_r) +{ + assert(dsd != NULL); + assert(src != NULL); + assert(src_size > 0); + assert(src_size % channels == 0); + assert(channels <= G_N_ELEMENTS(dsd->dsd2pcm)); + + const unsigned num_samples = src_size; + const unsigned num_frames = src_size / channels; + + float *dest; + const size_t dest_size = num_samples * sizeof(*dest); + *dest_size_r = dest_size; + dest = pcm_buffer_get(&dsd->buffer, dest_size); + + for (unsigned c = 0; c < channels; ++c) { + if (dsd->dsd2pcm[c] == NULL) { + dsd->dsd2pcm[c] = dsd2pcm_init(); + if (dsd->dsd2pcm[c] == NULL) + return NULL; + } + + dsd2pcm_translate(dsd->dsd2pcm[c], num_frames, + src + c, channels, + lsbfirst, dest + c, channels); + } + + return dest; +} diff --git a/src/pcm_dsd.h b/src/pcm_dsd.h new file mode 100644 index 000000000..85c2455aa --- /dev/null +++ b/src/pcm_dsd.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_DSD_H +#define MPD_PCM_DSD_H + +#include "check.h" +#include "pcm_buffer.h" + +#include <stdbool.h> +#include <stdint.h> + +/** + * Wrapper for the dsd2pcm library. + */ +struct pcm_dsd { + struct pcm_buffer buffer; + + struct dsd2pcm_ctx_s *dsd2pcm[32]; +}; + +void +pcm_dsd_init(struct pcm_dsd *dsd); + +void +pcm_dsd_deinit(struct pcm_dsd *dsd); + +void +pcm_dsd_reset(struct pcm_dsd *dsd); + +const float * +pcm_dsd_to_float(struct pcm_dsd *dsd, unsigned channels, bool lsbfirst, + const uint8_t *src, size_t src_size, + size_t *dest_size_r); + +#endif diff --git a/src/pcm_dsd_usb.c b/src/pcm_dsd_usb.c new file mode 100644 index 000000000..4b5e39f39 --- /dev/null +++ b/src/pcm_dsd_usb.c @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_dsd_usb.h" +#include "pcm_buffer.h" +#include "audio_format.h" + +G_GNUC_CONST +static inline uint32_t +pcm_two_dsd_to_usb_marker1(uint8_t a, uint8_t b) +{ + return 0xff050000 | (a << 8) | b; +} + +G_GNUC_CONST +static inline uint32_t +pcm_two_dsd_to_usb_marker2(uint8_t a, uint8_t b) +{ + return 0xfffa0000 | (a << 8) | b; +} + + +const uint32_t * +pcm_dsd_to_usb(struct pcm_buffer *buffer, unsigned channels, + const uint8_t *src, size_t src_size, + size_t *dest_size_r) +{ + assert(buffer != NULL); + assert(audio_valid_channel_count(channels)); + assert(src != NULL); + assert(src_size > 0); + assert(src_size % channels == 0); + + const unsigned num_src_samples = src_size; + const unsigned num_src_frames = num_src_samples / channels; + + /* this rounds down and discards the last odd frame; not + elegant, but good enough for now */ + const unsigned num_frames = num_src_frames / 2; + const unsigned num_samples = num_frames * channels; + + const size_t dest_size = num_samples * 4; + *dest_size_r = dest_size; + uint32_t *const dest0 = pcm_buffer_get(buffer, dest_size), + *dest = dest0; + + for (unsigned i = num_frames / 2; i > 0; --i) { + for (unsigned c = channels; c > 0; --c) { + /* each 24 bit sample has 16 DSD sample bits + plus the magic 0x05 marker */ + + *dest++ = pcm_two_dsd_to_usb_marker1(src[0], src[channels]); + + /* seek the source pointer to the next + channel */ + ++src; + } + + /* skip the second byte of each channel, because we + have already copied it */ + src += channels; + + for (unsigned c = channels; c > 0; --c) { + /* each 24 bit sample has 16 DSD sample bits + plus the magic 0xfa marker */ + + *dest++ = pcm_two_dsd_to_usb_marker2(src[0], src[channels]); + + /* seek the source pointer to the next + channel */ + ++src; + } + + /* skip the second byte of each channel, because we + have already copied it */ + src += channels; + } + + return dest0; +} diff --git a/src/songvec.h b/src/pcm_dsd_usb.h index 8a50b974b..389358459 100644 --- a/src/songvec.h +++ b/src/pcm_dsd_usb.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,35 +17,26 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_SONGVEC_H -#define MPD_SONGVEC_H +#ifndef MPD_PCM_DSD_USB_H +#define MPD_PCM_DSD_USB_H -#include <stddef.h> - -struct songvec { - struct song **base; - size_t nr; -}; - -void songvec_init(void); - -void songvec_deinit(void); - -void songvec_sort(struct songvec *sv); +#include "check.h" -struct song * -songvec_find(const struct songvec *sv, const char *uri); - -int -songvec_delete(struct songvec *sv, const struct song *del); - -void -songvec_add(struct songvec *sv, struct song *add); +#include <stdbool.h> +#include <stdint.h> +#include <stddef.h> -void songvec_destroy(struct songvec *sv); +struct pcm_buffer; -int -songvec_for_each(const struct songvec *sv, - int (*fn)(struct song *, void *), void *arg); +/** + * Pack DSD 1 bit samples into (padded) 24 bit PCM samples for + * playback over USB, according to the proposed standard by + * dCS and others: + * http://www.sonore.us/DoP_openStandard_1v1.pdf + */ +const uint32_t * +pcm_dsd_to_usb(struct pcm_buffer *buffer, unsigned channels, + const uint8_t *src, size_t src_size, + size_t *dest_size_r); -#endif /* SONGVEC_H */ +#endif diff --git a/src/pcm_export.c b/src/pcm_export.c new file mode 100644 index 000000000..e586b51d2 --- /dev/null +++ b/src/pcm_export.c @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_export.h" +#include "pcm_dsd_usb.h" +#include "pcm_pack.h" +#include "util/byte_reverse.h" + +void +pcm_export_init(struct pcm_export_state *state) +{ + pcm_buffer_init(&state->reverse_buffer); + pcm_buffer_init(&state->pack_buffer); + pcm_buffer_init(&state->dsd_buffer); +} + +void pcm_export_deinit(struct pcm_export_state *state) +{ + pcm_buffer_deinit(&state->reverse_buffer); + pcm_buffer_deinit(&state->pack_buffer); + pcm_buffer_deinit(&state->dsd_buffer); +} + +void +pcm_export_open(struct pcm_export_state *state, + enum sample_format sample_format, unsigned channels, + bool dsd_usb, bool shift8, bool pack, bool reverse_endian) +{ + assert(audio_valid_sample_format(sample_format)); + assert(!dsd_usb || audio_valid_channel_count(channels)); + + state->channels = channels; + state->dsd_usb = dsd_usb && sample_format == SAMPLE_FORMAT_DSD; + if (state->dsd_usb) + /* after the conversion to DSD-over-USB, the DSD + samples are stuffed inside fake 24 bit samples */ + sample_format = SAMPLE_FORMAT_S24_P32; + + state->shift8 = shift8 && sample_format == SAMPLE_FORMAT_S24_P32; + state->pack24 = pack && sample_format == SAMPLE_FORMAT_S24_P32; + + assert(!state->shift8 || !state->pack24); + + state->reverse_endian = 0; + if (reverse_endian) { + size_t sample_size = state->pack24 + ? 3 + : sample_format_size(sample_format); + assert(sample_size <= 0xff); + + if (sample_size > 1) + state->reverse_endian = sample_size; + } +} + +const void * +pcm_export(struct pcm_export_state *state, const void *data, size_t size, + size_t *dest_size_r) +{ + if (state->dsd_usb) + data = pcm_dsd_to_usb(&state->dsd_buffer, state->channels, + data, size, &size); + + if (state->pack24) { + assert(size % 4 == 0); + + const size_t num_samples = size / 4; + const size_t dest_size = num_samples * 3; + + const uint8_t *src8 = data, *src_end8 = src8 + size; + uint8_t *dest = pcm_buffer_get(&state->pack_buffer, dest_size); + assert(dest != NULL); + + pcm_pack_24(dest, (const int32_t *)src8, + (const int32_t *)src_end8); + + data = dest; + size = dest_size; + } else if (state->shift8) { + assert(size % 4 == 0); + + const uint8_t *src8 = data, *src_end8 = src8 + size; + const uint32_t *src = (const uint32_t *)src8; + const uint32_t *const src_end = (const uint32_t *)src_end8; + + uint32_t *dest = pcm_buffer_get(&state->pack_buffer, size); + data = dest; + + while (src < src_end) + *dest++ = *src++ << 8; + } + + + if (state->reverse_endian > 0) { + assert(state->reverse_endian >= 2); + + void *dest = pcm_buffer_get(&state->reverse_buffer, size); + assert(dest != NULL); + + const uint8_t *src = data, *src_end = src + size; + reverse_bytes(dest, src, src_end, state->reverse_endian); + + data = dest; + } + + *dest_size_r = size; + return data; +} + +size_t +pcm_export_source_size(const struct pcm_export_state *state, size_t size) +{ + if (state->dsd_usb) + /* DSD over USB doubles the transport size */ + size /= 2; + + return size; +} diff --git a/src/pcm_export.h b/src/pcm_export.h new file mode 100644 index 000000000..418dcdfa3 --- /dev/null +++ b/src/pcm_export.h @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 PCM_EXPORT_H +#define PCM_EXPORT_H + +#include "check.h" +#include "pcm_buffer.h" +#include "audio_format.h" + +#include <stdbool.h> + +struct audio_format; + +/** + * An object that handles export of PCM samples to some instance + * outside of MPD. It has a few more options to tweak the binary + * representation which are not supported by the pcm_convert library. + */ +struct pcm_export_state { + /** + * The buffer is used to convert DSD samples to the + * DSD-over-USB format. + * + * @see #dsd_usb + */ + struct pcm_buffer dsd_buffer; + + /** + * The buffer is used to pack samples, removing padding. + * + * @see #pack24 + */ + struct pcm_buffer pack_buffer; + + /** + * The buffer is used to reverse the byte order. + * + * @see #reverse_endian + */ + struct pcm_buffer reverse_buffer; + + /** + * The number of channels. + */ + uint8_t channels; + + /** + * Convert DSD to DSD-over-USB? Input format must be + * SAMPLE_FORMAT_DSD and output format must be + * SAMPLE_FORMAT_S24_P32. + */ + bool dsd_usb; + + /** + * Convert (padded) 24 bit samples to 32 bit by shifting 8 + * bits to the left? + */ + bool shift8; + + /** + * Pack 24 bit samples? + */ + bool pack24; + + /** + * Export the samples in reverse byte order? A non-zero value + * means the option is enabled and represents the size of each + * sample (2 or bigger). + */ + uint8_t reverse_endian; +}; + +/** + * Initialize a #pcm_export_state object. + */ +void +pcm_export_init(struct pcm_export_state *state); + +/** + * Deinitialize a #pcm_export_state object and free allocated memory. + */ +void +pcm_export_deinit(struct pcm_export_state *state); + +/** + * Open the #pcm_export_state object. + * + * There is no "close" method. This function may be called multiple + * times to reuse the object, until pcm_export_deinit() is called. + * + * This function cannot fail. + * + * @param channels the number of channels; ignored unless dsd_usb is set + */ +void +pcm_export_open(struct pcm_export_state *state, + enum sample_format sample_format, unsigned channels, + bool dsd_usb, bool shift8, bool pack, bool reverse_endian); + +/** + * Export a PCM buffer. + * + * @param state an initialized and open pcm_export_state object + * @param src the source PCM buffer + * @param src_size the size of #src in bytes + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer (may be a pointer to the source buffer) + */ +const void * +pcm_export(struct pcm_export_state *state, const void *src, size_t src_size, + size_t *dest_size_r); + +/** + * Converts the number of consumed bytes from the pcm_export() + * destination buffer to the according number of bytes from the + * pcm_export() source buffer. + */ +G_GNUC_PURE +size_t +pcm_export_source_size(const struct pcm_export_state *state, size_t dest_size); + +#endif diff --git a/src/pcm_format.c b/src/pcm_format.c index 1e4b8d705..d3ea3acb0 100644 --- a/src/pcm_format.c +++ b/src/pcm_format.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,39 +22,92 @@ #include "pcm_dither.h" #include "pcm_buffer.h" #include "pcm_pack.h" +#include "pcm_utils.h" static void -pcm_convert_8_to_16(int16_t *out, const int8_t *in, - unsigned num_samples) +pcm_convert_8_to_16(int16_t *out, const int8_t *in, const int8_t *in_end) { - while (num_samples > 0) { + while (in < in_end) { *out++ = *in++ << 8; - --num_samples; } } static void pcm_convert_24_to_16(struct pcm_dither *dither, - int16_t *out, const int32_t *in, - unsigned num_samples) + int16_t *out, const int32_t *in, const int32_t *in_end) { - pcm_dither_24_to_16(dither, out, in, num_samples); + pcm_dither_24_to_16(dither, out, in, in_end); } static void pcm_convert_32_to_16(struct pcm_dither *dither, - int16_t *out, const int32_t *in, - unsigned num_samples) + int16_t *out, const int32_t *in, const int32_t *in_end) { - pcm_dither_32_to_16(dither, out, in, num_samples); + pcm_dither_32_to_16(dither, out, in, in_end); } -static int32_t * -pcm_convert_24_to_24p32(struct pcm_buffer *buffer, const uint8_t *src, - unsigned num_samples) +static void +pcm_convert_float_to_16(int16_t *out, const float *in, const float *in_end) +{ + const unsigned OUT_BITS = 16; + const float factor = 1 << (OUT_BITS - 1); + + while (in < in_end) { + int sample = *in++ * factor; + *out++ = pcm_clamp_16(sample); + } +} + +static int16_t * +pcm_allocate_8_to_16(struct pcm_buffer *buffer, + const int8_t *src, size_t src_size, size_t *dest_size_r) +{ + int16_t *dest; + *dest_size_r = src_size / sizeof(*src) * sizeof(*dest); + dest = pcm_buffer_get(buffer, *dest_size_r); + pcm_convert_8_to_16(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + +static int16_t * +pcm_allocate_24p32_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, + const int32_t *src, size_t src_size, + size_t *dest_size_r) +{ + int16_t *dest; + *dest_size_r = src_size / 2; + assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); + dest = pcm_buffer_get(buffer, *dest_size_r); + pcm_convert_24_to_16(dither, dest, src, + pcm_end_pointer(src, src_size)); + return dest; +} + +static int16_t * +pcm_allocate_32_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, + const int32_t *src, size_t src_size, + size_t *dest_size_r) +{ + int16_t *dest; + *dest_size_r = src_size / 2; + assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); + dest = pcm_buffer_get(buffer, *dest_size_r); + pcm_convert_32_to_16(dither, dest, src, + pcm_end_pointer(src, src_size)); + return dest; +} + +static int16_t * +pcm_allocate_float_to_16(struct pcm_buffer *buffer, + const float *src, size_t src_size, + size_t *dest_size_r) { - int32_t *dest = pcm_buffer_get(buffer, num_samples * 4); - pcm_unpack_24(dest, src, num_samples, false); + int16_t *dest; + *dest_size_r = src_size / 2; + assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); + dest = pcm_buffer_get(buffer, *dest_size_r); + pcm_convert_float_to_16(dest, src, + pcm_end_pointer(src, src_size)); return dest; } @@ -63,176 +116,221 @@ pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, 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; + assert(src_size % sample_format_size(src_format) == 0); switch (src_format) { case SAMPLE_FORMAT_UNDEFINED: + case SAMPLE_FORMAT_DSD: break; case SAMPLE_FORMAT_S8: - num_samples = src_size; - *dest_size_r = src_size * sizeof(*dest); - dest = pcm_buffer_get(buffer, *dest_size_r); - - pcm_convert_8_to_16(dest, - (const int8_t *)src, - num_samples); - return dest; + return pcm_allocate_8_to_16(buffer, + src, src_size, dest_size_r); case SAMPLE_FORMAT_S16: *dest_size_r = src_size; return src; - 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); - - pcm_convert_24_to_16(dither, dest, - (const int32_t *)src, - num_samples); - return dest; + return pcm_allocate_24p32_to_16(buffer, dither, src, src_size, + dest_size_r); case SAMPLE_FORMAT_S32: - num_samples = src_size / 4; - *dest_size_r = num_samples * sizeof(*dest); - dest = pcm_buffer_get(buffer, *dest_size_r); - - pcm_convert_32_to_16(dither, dest, - (const int32_t *)src, - num_samples); - return dest; + return pcm_allocate_32_to_16(buffer, dither, src, src_size, + dest_size_r); + + case SAMPLE_FORMAT_FLOAT: + return pcm_allocate_float_to_16(buffer, src, src_size, + dest_size_r); } return NULL; } static void -pcm_convert_8_to_24(int32_t *out, const int8_t *in, - unsigned num_samples) +pcm_convert_8_to_24(int32_t *out, const int8_t *in, const int8_t *in_end) { - while (num_samples > 0) { + while (in < in_end) *out++ = *in++ << 16; - --num_samples; - } } static void -pcm_convert_16_to_24(int32_t *out, const int16_t *in, - unsigned num_samples) +pcm_convert_16_to_24(int32_t *out, const int16_t *in, const int16_t *in_end) { - while (num_samples > 0) { + while (in < in_end) *out++ = *in++ << 8; - --num_samples; - } } static void -pcm_convert_32_to_24(int32_t *out, const int32_t *in, - unsigned num_samples) +pcm_convert_32_to_24(int32_t *restrict out, + const int32_t *restrict in, + const int32_t *restrict in_end) { - while (num_samples > 0) { + while (in < in_end) *out++ = *in++ >> 8; - --num_samples; +} + +static void +pcm_convert_float_to_24(int32_t *out, const float *in, const float *in_end) +{ + const unsigned OUT_BITS = 24; + const float factor = 1 << (OUT_BITS - 1); + + while (in < in_end) { + int sample = *in++ * factor; + *out++ = pcm_clamp_24(sample); } } +static int32_t * +pcm_allocate_8_to_24(struct pcm_buffer *buffer, + const int8_t *src, size_t src_size, size_t *dest_size_r) +{ + int32_t *dest; + *dest_size_r = src_size / sizeof(*src) * sizeof(*dest); + dest = pcm_buffer_get(buffer, *dest_size_r); + pcm_convert_8_to_24(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + +static int32_t * +pcm_allocate_16_to_24(struct pcm_buffer *buffer, + const int16_t *src, size_t src_size, size_t *dest_size_r) +{ + int32_t *dest; + *dest_size_r = src_size * 2; + assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); + dest = pcm_buffer_get(buffer, *dest_size_r); + pcm_convert_16_to_24(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + +static int32_t * +pcm_allocate_32_to_24(struct pcm_buffer *buffer, + const int32_t *src, size_t src_size, size_t *dest_size_r) +{ + *dest_size_r = src_size; + int32_t *dest = pcm_buffer_get(buffer, *dest_size_r); + pcm_convert_32_to_24(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + +static int32_t * +pcm_allocate_float_to_24(struct pcm_buffer *buffer, + const float *src, size_t src_size, + size_t *dest_size_r) +{ + *dest_size_r = src_size; + int32_t *dest = pcm_buffer_get(buffer, *dest_size_r); + pcm_convert_float_to_24(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + const int32_t * pcm_convert_to_24(struct pcm_buffer *buffer, enum sample_format src_format, const void *src, size_t src_size, size_t *dest_size_r) { - unsigned num_samples; - int32_t *dest; + assert(src_size % sample_format_size(src_format) == 0); switch (src_format) { case SAMPLE_FORMAT_UNDEFINED: + case SAMPLE_FORMAT_DSD: break; case SAMPLE_FORMAT_S8: - num_samples = src_size; - *dest_size_r = src_size * sizeof(*dest); - dest = pcm_buffer_get(buffer, *dest_size_r); - - pcm_convert_8_to_24(dest, (const int8_t *)src, - num_samples); - return dest; + return pcm_allocate_8_to_24(buffer, + src, src_size, dest_size_r); case SAMPLE_FORMAT_S16: - num_samples = src_size / 2; - *dest_size_r = num_samples * sizeof(*dest); - dest = pcm_buffer_get(buffer, *dest_size_r); - - pcm_convert_16_to_24(dest, (const int16_t *)src, - num_samples); - return dest; - - 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); + return pcm_allocate_16_to_24(buffer, + src, src_size, dest_size_r); case SAMPLE_FORMAT_S24_P32: *dest_size_r = src_size; return src; case SAMPLE_FORMAT_S32: - num_samples = src_size / 4; - *dest_size_r = num_samples * sizeof(*dest); - dest = pcm_buffer_get(buffer, *dest_size_r); + return pcm_allocate_32_to_24(buffer, src, src_size, + dest_size_r); - pcm_convert_32_to_24(dest, (const int32_t *)src, - num_samples); - return dest; + case SAMPLE_FORMAT_FLOAT: + return pcm_allocate_float_to_24(buffer, src, src_size, + dest_size_r); } return NULL; } static void -pcm_convert_8_to_32(int32_t *out, const int8_t *in, - unsigned num_samples) +pcm_convert_8_to_32(int32_t *out, const int8_t *in, const int8_t *in_end) { - while (num_samples > 0) { + while (in < in_end) *out++ = *in++ << 24; - --num_samples; - } } static void -pcm_convert_16_to_32(int32_t *out, const int16_t *in, - unsigned num_samples) +pcm_convert_16_to_32(int32_t *out, const int16_t *in, const int16_t *in_end) { - while (num_samples > 0) { + while (in < in_end) *out++ = *in++ << 16; - --num_samples; - } } static void -pcm_convert_24_to_32(int32_t *out, const int32_t *in, - unsigned num_samples) +pcm_convert_24_to_32(int32_t *restrict out, + const int32_t *restrict in, + const int32_t *restrict in_end) { - while (num_samples > 0) { + while (in < in_end) *out++ = *in++ << 8; - --num_samples; - } +} + +static int32_t * +pcm_allocate_8_to_32(struct pcm_buffer *buffer, + const int8_t *src, size_t src_size, size_t *dest_size_r) +{ + int32_t *dest; + *dest_size_r = src_size / sizeof(*src) * sizeof(*dest); + dest = pcm_buffer_get(buffer, *dest_size_r); + pcm_convert_8_to_32(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + +static int32_t * +pcm_allocate_16_to_32(struct pcm_buffer *buffer, + const int16_t *src, size_t src_size, size_t *dest_size_r) +{ + int32_t *dest; + *dest_size_r = src_size * 2; + assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); + dest = pcm_buffer_get(buffer, *dest_size_r); + pcm_convert_16_to_32(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + +static int32_t * +pcm_allocate_24p32_to_32(struct pcm_buffer *buffer, + const int32_t *src, size_t src_size, + size_t *dest_size_r) +{ + *dest_size_r = src_size; + int32_t *dest = pcm_buffer_get(buffer, *dest_size_r); + pcm_convert_24_to_32(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + +static int32_t * +pcm_allocate_float_to_32(struct pcm_buffer *buffer, + const float *src, size_t src_size, + size_t *dest_size_r) +{ + /* convert to S24_P32 first */ + int32_t *dest = pcm_allocate_float_to_24(buffer, src, src_size, + dest_size_r); + + /* convert to 32 bit in-place */ + pcm_convert_24_to_32(dest, dest, pcm_end_pointer(dest, *dest_size_r)); + return dest; } const int32_t * @@ -240,52 +338,147 @@ pcm_convert_to_32(struct pcm_buffer *buffer, enum sample_format src_format, const void *src, size_t src_size, size_t *dest_size_r) { - unsigned num_samples; - int32_t *dest; + assert(src_size % sample_format_size(src_format) == 0); switch (src_format) { case SAMPLE_FORMAT_UNDEFINED: + case SAMPLE_FORMAT_DSD: break; case SAMPLE_FORMAT_S8: - num_samples = src_size; - *dest_size_r = src_size * sizeof(*dest); - dest = pcm_buffer_get(buffer, *dest_size_r); - - pcm_convert_8_to_32(dest, (const int8_t *)src, - num_samples); - return dest; + return pcm_allocate_8_to_32(buffer, src, src_size, + dest_size_r); case SAMPLE_FORMAT_S16: - num_samples = src_size / 2; - *dest_size_r = num_samples * sizeof(*dest); - dest = pcm_buffer_get(buffer, *dest_size_r); + return pcm_allocate_16_to_32(buffer, src, src_size, + dest_size_r); - pcm_convert_16_to_32(dest, (const int16_t *)src, - num_samples); - return dest; + case SAMPLE_FORMAT_S24_P32: + return pcm_allocate_24p32_to_32(buffer, src, src_size, + dest_size_r); - case SAMPLE_FORMAT_S24: - /* convert to S24_P32 first */ - num_samples = src_size / 3; + case SAMPLE_FORMAT_S32: + *dest_size_r = src_size; + return src; - dest = pcm_convert_24_to_24p32(buffer, src, num_samples); + case SAMPLE_FORMAT_FLOAT: + return pcm_allocate_float_to_32(buffer, src, src_size, + dest_size_r); + } - /* convert to 32 bit in-place */ - *dest_size_r = num_samples * sizeof(*dest); - pcm_convert_24_to_32(dest, dest, num_samples); - return dest; + return NULL; +} - 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); +static void +pcm_convert_8_to_float(float *out, const int8_t *in, const int8_t *in_end) +{ + enum { in_bits = sizeof(*in) * 8 }; + static const float factor = 2.0f / (1 << in_bits); + while (in < in_end) + *out++ = (float)*in++ * factor; +} - pcm_convert_24_to_32(dest, (const int32_t *)src, - num_samples); - return dest; +static void +pcm_convert_16_to_float(float *out, const int16_t *in, const int16_t *in_end) +{ + enum { in_bits = sizeof(*in) * 8 }; + static const float factor = 2.0f / (1 << in_bits); + while (in < in_end) + *out++ = (float)*in++ * factor; +} + +static void +pcm_convert_24_to_float(float *out, const int32_t *in, const int32_t *in_end) +{ + enum { in_bits = 24 }; + static const float factor = 2.0f / (1 << in_bits); + while (in < in_end) + *out++ = (float)*in++ * factor; +} + +static void +pcm_convert_32_to_float(float *out, const int32_t *in, const int32_t *in_end) +{ + enum { in_bits = sizeof(*in) * 8 }; + static const float factor = 0.5f / (1 << (in_bits - 2)); + while (in < in_end) + *out++ = (float)*in++ * factor; +} + +static float * +pcm_allocate_8_to_float(struct pcm_buffer *buffer, + const int8_t *src, size_t src_size, + size_t *dest_size_r) +{ + float *dest; + *dest_size_r = src_size / sizeof(*src) * sizeof(*dest); + dest = pcm_buffer_get(buffer, *dest_size_r); + pcm_convert_8_to_float(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + +static float * +pcm_allocate_16_to_float(struct pcm_buffer *buffer, + const int16_t *src, size_t src_size, + size_t *dest_size_r) +{ + float *dest; + *dest_size_r = src_size * 2; + assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest)); + dest = pcm_buffer_get(buffer, *dest_size_r); + pcm_convert_16_to_float(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + +static float * +pcm_allocate_24p32_to_float(struct pcm_buffer *buffer, + const int32_t *src, size_t src_size, + size_t *dest_size_r) +{ + *dest_size_r = src_size; + float *dest = pcm_buffer_get(buffer, *dest_size_r); + pcm_convert_24_to_float(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + +static float * +pcm_allocate_32_to_float(struct pcm_buffer *buffer, + const int32_t *src, size_t src_size, + size_t *dest_size_r) +{ + *dest_size_r = src_size; + float *dest = pcm_buffer_get(buffer, *dest_size_r); + pcm_convert_32_to_float(dest, src, pcm_end_pointer(src, src_size)); + return dest; +} + +const float * +pcm_convert_to_float(struct pcm_buffer *buffer, + enum sample_format src_format, const void *src, + size_t src_size, size_t *dest_size_r) +{ + switch (src_format) { + case SAMPLE_FORMAT_UNDEFINED: + case SAMPLE_FORMAT_DSD: + break; + + case SAMPLE_FORMAT_S8: + return pcm_allocate_8_to_float(buffer, + src, src_size, dest_size_r); + + case SAMPLE_FORMAT_S16: + return pcm_allocate_16_to_float(buffer, + src, src_size, dest_size_r); + + case SAMPLE_FORMAT_S24_P32: + return pcm_allocate_24p32_to_float(buffer, + src, src_size, dest_size_r); case SAMPLE_FORMAT_S32: + return pcm_allocate_32_to_float(buffer, + src, src_size, dest_size_r); + + case SAMPLE_FORMAT_FLOAT: *dest_size_r = src_size; return src; } diff --git a/src/pcm_format.h b/src/pcm_format.h index 3e96fc65f..48bcd0662 100644 --- a/src/pcm_format.h +++ b/src/pcm_format.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -75,4 +75,19 @@ pcm_convert_to_32(struct pcm_buffer *buffer, enum sample_format src_format, const void *src, size_t src_size, size_t *dest_size_r); +/** + * Converts PCM samples to 32 bit floating point. + * + * @param buffer a pcm_buffer object + * @param bits the number of in the source buffer + * @param src the source PCM buffer + * @param src_size the size of #src in bytes + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer + */ +const float * +pcm_convert_to_float(struct pcm_buffer *buffer, + 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 3145c07be..6c6d1b4ab 100644 --- a/src/pcm_mix.c +++ b/src/pcm_mix.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,7 +22,6 @@ #include "pcm_volume.h" #include "pcm_utils.h" #include "audio_format.h" -#include "mpd_error.h" #include <glib.h> @@ -100,35 +99,60 @@ pcm_add_vol_32(int32_t *buffer1, const int32_t *buffer2, } static void +pcm_add_vol_float(float *buffer1, const float *buffer2, + unsigned num_samples, float volume1, float volume2) +{ + while (num_samples > 0) { + float sample1 = *buffer1; + float sample2 = *buffer2++; + + sample1 = (sample1 * volume1 + sample2 * volume2); + *buffer1++ = sample1; + --num_samples; + } +} + +static bool pcm_add_vol(void *buffer1, const void *buffer2, size_t size, int vol1, int vol2, - const struct audio_format *format) + enum sample_format format) { - switch (format->format) { + switch (format) { + case SAMPLE_FORMAT_UNDEFINED: + case SAMPLE_FORMAT_DSD: + /* not implemented */ + return false; + case SAMPLE_FORMAT_S8: pcm_add_vol_8((int8_t *)buffer1, (const int8_t *)buffer2, size, vol1, vol2); - break; + return true; case SAMPLE_FORMAT_S16: pcm_add_vol_16((int16_t *)buffer1, (const int16_t *)buffer2, size / 2, vol1, vol2); - break; + return true; case SAMPLE_FORMAT_S24_P32: pcm_add_vol_24((int32_t *)buffer1, (const int32_t *)buffer2, size / 4, vol1, vol2); - break; + return true; case SAMPLE_FORMAT_S32: pcm_add_vol_32((int32_t *)buffer1, (const int32_t *)buffer2, size / 4, vol1, vol2); - break; + return true; - default: - MPD_ERROR("format %s not supported by pcm_add_vol", - sample_format_to_string(format->format)); + case SAMPLE_FORMAT_FLOAT: + pcm_add_vol_float(buffer1, buffer2, size / 4, + pcm_volume_to_float(vol1), + pcm_volume_to_float(vol2)); + return true; } + + /* unreachable */ + assert(false); + return false; } static void @@ -188,45 +212,63 @@ pcm_add_32(int32_t *buffer1, const int32_t *buffer2, unsigned num_samples) } static void +pcm_add_float(float *buffer1, const float *buffer2, unsigned num_samples) +{ + while (num_samples > 0) { + float sample1 = *buffer1; + float sample2 = *buffer2++; + *buffer1++ = sample1 + sample2; + --num_samples; + } +} + +static bool pcm_add(void *buffer1, const void *buffer2, size_t size, - const struct audio_format *format) + enum sample_format format) { - switch (format->format) { + switch (format) { + case SAMPLE_FORMAT_UNDEFINED: + case SAMPLE_FORMAT_DSD: + /* not implemented */ + return false; + case SAMPLE_FORMAT_S8: pcm_add_8((int8_t *)buffer1, (const int8_t *)buffer2, size); - break; + return true; case SAMPLE_FORMAT_S16: pcm_add_16((int16_t *)buffer1, (const int16_t *)buffer2, size / 2); - break; + return true; case SAMPLE_FORMAT_S24_P32: pcm_add_24((int32_t *)buffer1, (const int32_t *)buffer2, size / 4); - break; + return true; case SAMPLE_FORMAT_S32: pcm_add_32((int32_t *)buffer1, (const int32_t *)buffer2, size / 4); - break; + return true; - default: - MPD_ERROR("format %s not supported by pcm_add", - sample_format_to_string(format->format)); + case SAMPLE_FORMAT_FLOAT: + pcm_add_float(buffer1, buffer2, size / 4); + return true; } + + /* unreachable */ + assert(false); + return false; } -void +bool pcm_mix(void *buffer1, const void *buffer2, size_t size, - const struct audio_format *format, float portion1) + enum sample_format format, float portion1) { int vol1; 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; - } + if (isnan(portion1)) + return pcm_add(buffer1, buffer2, size, format); s = sin(M_PI_2 * portion1); s *= s; @@ -234,5 +276,5 @@ pcm_mix(void *buffer1, const void *buffer2, size_t size, vol1 = s * PCM_VOLUME_1 + 0.5; vol1 = vol1 > PCM_VOLUME_1 ? PCM_VOLUME_1 : (vol1 < 0 ? 0 : vol1); - pcm_add_vol(buffer1, buffer2, size, vol1, PCM_VOLUME_1 - vol1, format); + return 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 086d5501e..0e58d01ee 100644 --- a/src/pcm_mix.h +++ b/src/pcm_mix.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,9 +20,10 @@ #ifndef PCM_MIX_H #define PCM_MIX_H -#include <stddef.h> +#include "audio_format.h" -struct audio_format; +#include <stdbool.h> +#include <stddef.h> /* * Linearly mixes two PCM buffers. Both must have the same length and @@ -33,13 +34,16 @@ struct audio_format; * @param buffer1 the first PCM buffer, and the destination buffer * @param buffer2 the second PCM buffer * @param size the size of both buffers in bytes - * @param format the audio format of both buffers + * @param format the sample 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). The value * NaN is used by the MixRamp code to specify that simple addition is required. + * + * @return true on success, false if the format is not supported */ -void +G_GNUC_WARN_UNUSED_RESULT +bool pcm_mix(void *buffer1, const void *buffer2, size_t size, - const struct audio_format *format, float portion1); + enum sample_format format, float portion1); #endif diff --git a/src/pcm_pack.c b/src/pcm_pack.c index 9af0ab1ed..921d880c0 100644 --- a/src/pcm_pack.c +++ b/src/pcm_pack.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,11 +22,11 @@ #include <glib.h> static void -pack_sample(uint8_t *dest, const int32_t *src0, bool reverse_endian) +pack_sample(uint8_t *dest, const int32_t *src0) { const uint8_t *src = (const uint8_t *)src0; - if ((G_BYTE_ORDER == G_BIG_ENDIAN) != reverse_endian) + if (G_BYTE_ORDER == G_BIG_ENDIAN) ++src; *dest++ = *src++; @@ -35,31 +35,23 @@ pack_sample(uint8_t *dest, const int32_t *src0, bool reverse_endian) } void -pcm_pack_24(uint8_t *dest, const int32_t *src, unsigned num_samples, - bool reverse_endian) +pcm_pack_24(uint8_t *dest, const int32_t *src, const int32_t *src_end) { /* 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; - } + while (src < src_end) { + pack_sample(dest, src++); + dest += 3; } } static void -unpack_sample(int32_t *dest0, const uint8_t *src, bool reverse_endian) +unpack_sample(int32_t *dest0, const uint8_t *src) { uint8_t *dest = (uint8_t *)dest0; - if ((G_BYTE_ORDER == G_BIG_ENDIAN) != reverse_endian) + if (G_BYTE_ORDER == G_BIG_ENDIAN) /* extend the sign bit to the most fourth byte */ *dest++ = *src & 0x80 ? 0xff : 0x00; @@ -67,27 +59,19 @@ unpack_sample(int32_t *dest0, const uint8_t *src, bool reverse_endian) *dest++ = *src++; *dest++ = *src; - if ((G_BYTE_ORDER == G_LITTLE_ENDIAN) != reverse_endian) + if (G_BYTE_ORDER == G_LITTLE_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) +pcm_unpack_24(int32_t *dest, const uint8_t *src, const uint8_t *src_end) { /* 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; - } + while (src < src_end) { + unpack_sample(dest++, src); + src += 3; } } diff --git a/src/pcm_pack.h b/src/pcm_pack.h index 3c99eaa35..f3184b403 100644 --- a/src/pcm_pack.h +++ b/src/pcm_pack.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -37,11 +37,9 @@ * @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); +pcm_pack_24(uint8_t *dest, const int32_t *src, const int32_t *src_end); /** * Converts packed 24 bit samples (3 bytes per sample) to padded 24 @@ -50,10 +48,8 @@ pcm_pack_24(uint8_t *dest, const int32_t *src, unsigned num_samples, * @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); +pcm_unpack_24(int32_t *dest, const uint8_t *src, const uint8_t *src_end); #endif diff --git a/src/pcm_prng.h b/src/pcm_prng.h index 186ed9d0e..457ba4b66 100644 --- a/src/pcm_prng.h +++ b/src/pcm_prng.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 4a7578e09..4bc057a7e 100644 --- a/src/pcm_resample.c +++ b/src/pcm_resample.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,26 +27,43 @@ #include <string.h> #ifdef HAVE_LIBSAMPLERATE +static bool lsr_enabled; +#endif + +#ifdef HAVE_LIBSAMPLERATE static bool pcm_resample_lsr_enabled(void) { - return strcmp(config_get_string(CONF_SAMPLERATE_CONVERTER, ""), - "internal") != 0; + return lsr_enabled; } #endif -void pcm_resample_init(struct pcm_resample_state *state) +bool +pcm_resample_global_init(GError **error_r) { - memset(state, 0, sizeof(*state)); - #ifdef HAVE_LIBSAMPLERATE - if (pcm_resample_lsr_enabled()) { - pcm_buffer_init(&state->in); - pcm_buffer_init(&state->out); - } + const char *converter = + config_get_string(CONF_SAMPLERATE_CONVERTER, ""); + + lsr_enabled = strcmp(converter, "internal") != 0; + if (lsr_enabled) + return pcm_resample_lsr_global_init(converter, error_r); + else + return true; +#else + (void)error_r; + return true; #endif +} - pcm_buffer_init(&state->buffer); +void pcm_resample_init(struct pcm_resample_state *state) +{ +#ifdef HAVE_LIBSAMPLERATE + if (pcm_resample_lsr_enabled()) + pcm_resample_lsr_init(state); + else +#endif + pcm_resample_fallback_init(state); } void pcm_resample_deinit(struct pcm_resample_state *state) @@ -59,9 +76,47 @@ void pcm_resample_deinit(struct pcm_resample_state *state) pcm_resample_fallback_deinit(state); } +void +pcm_resample_reset(struct pcm_resample_state *state) +{ +#ifdef HAVE_LIBSAMPLERATE + pcm_resample_lsr_reset(state); +#else + (void)state; +#endif +} + +const float * +pcm_resample_float(struct pcm_resample_state *state, + unsigned channels, + unsigned src_rate, + const float *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_float(state, channels, + src_rate, src_buffer, src_size, + dest_rate, dest_size_r, + error_r); +#else + (void)error_r; +#endif + + /* sizeof(float)==sizeof(int32_t); the fallback resampler does + not do any math on the sample values, so this hack is + possible: */ + return (const float *) + pcm_resample_fallback_32(state, channels, + src_rate, (const int32_t *)src_buffer, + src_size, + dest_rate, dest_size_r); +} + const int16_t * pcm_resample_16(struct pcm_resample_state *state, - uint8_t channels, + unsigned channels, unsigned src_rate, const int16_t *src_buffer, size_t src_size, unsigned dest_rate, size_t *dest_size_r, GError **error_r) @@ -83,7 +138,7 @@ pcm_resample_16(struct pcm_resample_state *state, const int32_t * pcm_resample_32(struct pcm_resample_state *state, - uint8_t channels, + unsigned channels, unsigned src_rate, const int32_t *src_buffer, size_t src_size, unsigned dest_rate, size_t *dest_size_r, GError **error_r) diff --git a/src/pcm_resample.h b/src/pcm_resample.h index 24d17ff9b..a49a24142 100644 --- a/src/pcm_resample.h +++ b/src/pcm_resample.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -45,7 +45,7 @@ struct pcm_resample_state { struct { unsigned src_rate; unsigned dest_rate; - uint8_t channels; + unsigned channels; } prev; int error; @@ -54,6 +54,9 @@ struct pcm_resample_state { struct pcm_buffer buffer; }; +bool +pcm_resample_global_init(GError **error_r); + /** * Initializes a pcm_resample_state object. */ @@ -66,6 +69,32 @@ void pcm_resample_init(struct pcm_resample_state *state); void pcm_resample_deinit(struct pcm_resample_state *state); /** + * @see pcm_convert_reset() + */ +void +pcm_resample_reset(struct pcm_resample_state *state); + +/** + * Resamples 32 bit float data. + * + * @param state an initialized pcm_resample_state object + * @param channels the number of channels + * @param src_rate the source sample rate + * @param src the source PCM buffer + * @param src_size the size of #src in bytes + * @param dest_rate the requested destination sample rate + * @param dest_size_r returns the number of bytes of the destination buffer + * @return the destination buffer + */ +const float * +pcm_resample_float(struct pcm_resample_state *state, + unsigned channels, + unsigned src_rate, + const float *src_buffer, size_t src_size, + unsigned dest_rate, size_t *dest_size_r, + GError **error_r); + +/** * Resamples 16 bit PCM data. * * @param state an initialized pcm_resample_state object @@ -79,7 +108,7 @@ void pcm_resample_deinit(struct pcm_resample_state *state); */ const int16_t * pcm_resample_16(struct pcm_resample_state *state, - uint8_t channels, + unsigned channels, unsigned src_rate, const int16_t *src_buffer, size_t src_size, unsigned dest_rate, size_t *dest_size_r, @@ -99,7 +128,7 @@ pcm_resample_16(struct pcm_resample_state *state, */ const int32_t * pcm_resample_32(struct pcm_resample_state *state, - uint8_t channels, + unsigned channels, unsigned src_rate, const int32_t *src_buffer, size_t src_size, unsigned dest_rate, size_t *dest_size_r, @@ -119,7 +148,7 @@ pcm_resample_32(struct pcm_resample_state *state, */ static inline const int32_t * pcm_resample_24(struct pcm_resample_state *state, - uint8_t channels, + unsigned channels, unsigned src_rate, const int32_t *src_buffer, size_t src_size, unsigned dest_rate, size_t *dest_size_r, diff --git a/src/pcm_resample_fallback.c b/src/pcm_resample_fallback.c index 0c75d8ba4..1d1dfdf59 100644 --- a/src/pcm_resample_fallback.c +++ b/src/pcm_resample_fallback.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,6 +23,12 @@ #include <assert.h> void +pcm_resample_fallback_init(struct pcm_resample_state *state) +{ + pcm_buffer_init(&state->buffer); +} + +void pcm_resample_fallback_deinit(struct pcm_resample_state *state) { pcm_buffer_deinit(&state->buffer); @@ -31,7 +37,7 @@ pcm_resample_fallback_deinit(struct pcm_resample_state *state) /* resampling code blatantly ripped from ESD */ const int16_t * pcm_resample_fallback_16(struct pcm_resample_state *state, - uint8_t channels, + unsigned channels, unsigned src_rate, const int16_t *src_buffer, size_t src_size, unsigned dest_rate, @@ -72,7 +78,7 @@ pcm_resample_fallback_16(struct pcm_resample_state *state, const int32_t * pcm_resample_fallback_32(struct pcm_resample_state *state, - uint8_t channels, + unsigned channels, unsigned src_rate, const int32_t *src_buffer, size_t src_size, unsigned dest_rate, diff --git a/src/pcm_resample_internal.h b/src/pcm_resample_internal.h index 26acc809d..a0e108d4b 100644 --- a/src/pcm_resample_internal.h +++ b/src/pcm_resample_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -32,12 +32,29 @@ #ifdef HAVE_LIBSAMPLERATE +bool +pcm_resample_lsr_global_init(const char *converter, GError **error_r); + +void +pcm_resample_lsr_init(struct pcm_resample_state *state); + void pcm_resample_lsr_deinit(struct pcm_resample_state *state); +void +pcm_resample_lsr_reset(struct pcm_resample_state *state); + +const float * +pcm_resample_lsr_float(struct pcm_resample_state *state, + unsigned channels, + unsigned src_rate, + const float *src_buffer, size_t src_size, + unsigned dest_rate, size_t *dest_size_r, + GError **error_r); + const int16_t * pcm_resample_lsr_16(struct pcm_resample_state *state, - uint8_t channels, + unsigned channels, unsigned src_rate, const int16_t *src_buffer, size_t src_size, unsigned dest_rate, size_t *dest_size_r, @@ -45,7 +62,7 @@ pcm_resample_lsr_16(struct pcm_resample_state *state, const int32_t * pcm_resample_lsr_32(struct pcm_resample_state *state, - uint8_t channels, + unsigned channels, unsigned src_rate, const int32_t *src_buffer, G_GNUC_UNUSED size_t src_size, @@ -55,11 +72,14 @@ pcm_resample_lsr_32(struct pcm_resample_state *state, #endif void +pcm_resample_fallback_init(struct pcm_resample_state *state); + +void pcm_resample_fallback_deinit(struct pcm_resample_state *state); const int16_t * pcm_resample_fallback_16(struct pcm_resample_state *state, - uint8_t channels, + unsigned channels, unsigned src_rate, const int16_t *src_buffer, size_t src_size, unsigned dest_rate, @@ -67,7 +87,7 @@ pcm_resample_fallback_16(struct pcm_resample_state *state, const int32_t * pcm_resample_fallback_32(struct pcm_resample_state *state, - uint8_t channels, + unsigned channels, unsigned src_rate, const int32_t *src_buffer, G_GNUC_UNUSED size_t src_size, diff --git a/src/pcm_resample_libsamplerate.c b/src/pcm_resample_libsamplerate.c index 99ca53da4..f957e5155 100644 --- a/src/pcm_resample_libsamplerate.c +++ b/src/pcm_resample_libsamplerate.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -30,12 +30,69 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "pcm" +static int lsr_converter = SRC_SINC_FASTEST; + static inline GQuark libsamplerate_quark(void) { return g_quark_from_static_string("libsamplerate"); } +static bool +lsr_parse_converter(const char *s) +{ + assert(s != NULL); + + if (*s == 0) + return true; + + char *endptr; + long l = strtol(s, &endptr, 10); + if (*endptr == 0 && src_get_name(l) != NULL) { + lsr_converter = l; + return true; + } + + size_t length = strlen(s); + for (int i = 0;; ++i) { + const char *name = src_get_name(i); + if (name == NULL) + break; + + if (g_ascii_strncasecmp(s, name, length) == 0) { + lsr_converter = i; + return true; + } + } + + return false; +} + +bool +pcm_resample_lsr_global_init(const char *converter, GError **error_r) +{ + if (!lsr_parse_converter(converter)) { + g_set_error(error_r, libsamplerate_quark(), 0, + "unknown samplerate converter '%s'", converter); + return false; + } + + g_debug("libsamplerate converter '%s'", + src_get_name(lsr_converter)); + + return true; +} + +void +pcm_resample_lsr_init(struct pcm_resample_state *state) +{ + memset(state, 0, sizeof(*state)); + + pcm_buffer_init(&state->in); + pcm_buffer_init(&state->out); + pcm_buffer_init(&state->buffer); +} + void pcm_resample_lsr_deinit(struct pcm_resample_state *state) { @@ -47,54 +104,21 @@ pcm_resample_lsr_deinit(struct pcm_resample_state *state) pcm_buffer_deinit(&state->buffer); } -static int pcm_resample_get_converter(void) +void +pcm_resample_lsr_reset(struct pcm_resample_state *state) { - const char *conf = config_get_string(CONF_SAMPLERATE_CONVERTER, NULL); - long convalgo; - char *test; - const char *test2; - size_t len; - - if (!conf) { - convalgo = SRC_SINC_FASTEST; - goto out; - } - - convalgo = strtol(conf, &test, 10); - if (*test == '\0' && src_get_name(convalgo)) - goto out; - - len = strlen(conf); - for (convalgo = 0 ; ; convalgo++) { - test2 = src_get_name(convalgo); - if (!test2) { - convalgo = SRC_SINC_FASTEST; - break; - } - if (g_ascii_strncasecmp(test2, conf, len) == 0) - goto out; - } - - g_warning("unknown samplerate converter \"%s\"", conf); -out: - g_debug("selecting samplerate converter \"%s\"", - src_get_name(convalgo)); - - return convalgo; + if (state->state != NULL) + src_reset(state->state); } static bool pcm_resample_set(struct pcm_resample_state *state, - uint8_t channels, unsigned src_rate, unsigned dest_rate, + unsigned channels, unsigned src_rate, unsigned dest_rate, GError **error_r) { - static int convalgo = -1; int error; SRC_DATA *data = &state->data; - if (convalgo < 0) - convalgo = pcm_resample_get_converter(); - /* (re)set the state/ratio if the in or out format changed */ if (channels == state->prev.channels && src_rate == state->prev.src_rate && @@ -109,7 +133,7 @@ pcm_resample_set(struct pcm_resample_state *state, if (state->state) state->state = src_delete(state->state); - state->state = src_new(convalgo, channels, &error); + state->state = src_new(lsr_converter, channels, &error); if (!state->state) { g_set_error(error_r, libsamplerate_quark(), state->error, "libsamplerate initialization has failed: %s", @@ -125,9 +149,63 @@ pcm_resample_set(struct pcm_resample_state *state, return true; } +static bool +lsr_process(struct pcm_resample_state *state, GError **error_r) +{ + if (state->error == 0) + state->error = src_process(state->state, &state->data); + if (state->error) { + g_set_error(error_r, libsamplerate_quark(), state->error, + "libsamplerate has failed: %s", + src_strerror(state->error)); + return false; + } + + return true; +} + +static float * +deconst_float_buffer(const float *in) +{ + union { + const float *in; + float *out; + } u = { .in = in }; + return u.out; +} + +const float * +pcm_resample_lsr_float(struct pcm_resample_state *state, + unsigned channels, + unsigned src_rate, + const float *src_buffer, size_t src_size, + unsigned dest_rate, size_t *dest_size_r, + GError **error_r) +{ + assert((src_size % (sizeof(*src_buffer) * channels)) == 0); + + if (!pcm_resample_set(state, channels, src_rate, dest_rate, error_r)) + return NULL; + + SRC_DATA *data = &state->data; + data->input_frames = src_size / sizeof(*src_buffer) / channels; + data->data_in = deconst_float_buffer(src_buffer); + + data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate; + size_t data_out_size = data->output_frames * sizeof(float) * channels; + data->data_out = pcm_buffer_get(&state->out, data_out_size); + + if (!lsr_process(state, error_r)) + return NULL; + + *dest_size_r = data->output_frames_gen * + sizeof(*data->data_out) * channels; + return data->data_out; +} + const int16_t * pcm_resample_lsr_16(struct pcm_resample_state *state, - uint8_t channels, + unsigned channels, unsigned src_rate, const int16_t *src_buffer, size_t src_size, unsigned dest_rate, size_t *dest_size_r, @@ -137,7 +215,6 @@ pcm_resample_lsr_16(struct pcm_resample_state *state, SRC_DATA *data = &state->data; size_t data_in_size; size_t data_out_size; - int error; int16_t *dest_buffer; assert((src_size % (sizeof(*src_buffer) * channels)) == 0); @@ -147,14 +224,6 @@ pcm_resample_lsr_16(struct pcm_resample_state *state, if (!success) return NULL; - /* there was an error previously, and nothing has changed */ - 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; data->data_in = pcm_buffer_get(&state->in, data_in_size); @@ -166,14 +235,8 @@ pcm_resample_lsr_16(struct pcm_resample_state *state, src_short_to_float_array(src_buffer, data->data_in, data->input_frames * channels); - error = src_process(state->state, data); - if (error) { - g_set_error(error_r, libsamplerate_quark(), error, - "libsamplerate has failed: %s", - src_strerror(error)); - state->error = error; + if (!lsr_process(state, error_r)) return NULL; - } *dest_size_r = data->output_frames_gen * sizeof(*dest_buffer) * channels; @@ -206,7 +269,7 @@ src_float_to_int_array (const float *in, int *out, int len) const int32_t * pcm_resample_lsr_32(struct pcm_resample_state *state, - uint8_t channels, + unsigned channels, unsigned src_rate, const int32_t *src_buffer, size_t src_size, unsigned dest_rate, size_t *dest_size_r, @@ -216,7 +279,6 @@ pcm_resample_lsr_32(struct pcm_resample_state *state, SRC_DATA *data = &state->data; size_t data_in_size; size_t data_out_size; - int error; int32_t *dest_buffer; assert((src_size % (sizeof(*src_buffer) * channels)) == 0); @@ -226,14 +288,6 @@ pcm_resample_lsr_32(struct pcm_resample_state *state, if (!success) return NULL; - /* there was an error previously, and nothing has changed */ - 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; data->data_in = pcm_buffer_get(&state->in, data_in_size); @@ -245,14 +299,8 @@ pcm_resample_lsr_32(struct pcm_resample_state *state, src_int_to_float_array(src_buffer, data->data_in, data->input_frames * channels); - error = src_process(state->state, data); - if (error) { - g_set_error(error_r, libsamplerate_quark(), error, - "libsamplerate has failed: %s", - src_strerror(error)); - state->error = error; + if (!lsr_process(state, error_r)) return NULL; - } *dest_size_r = data->output_frames_gen * sizeof(*dest_buffer) * channels; diff --git a/src/pcm_utils.h b/src/pcm_utils.h index 15f9e1b10..4ad896570 100644 --- a/src/pcm_utils.h +++ b/src/pcm_utils.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,6 +25,17 @@ #include <stdint.h> /** + * Add a byte count to the specified pointer. This is a utility + * function to convert a source pointer and a byte count to an "end" + * pointer for use in loops. + */ +static inline const void * +pcm_end_pointer(const void *p, size_t size) +{ + return (const char *)p + size; +} + +/** * Check if the value is within the range of the provided bit size, * and caps it if necessary. */ @@ -52,4 +63,32 @@ pcm_range_64(int64_t sample, unsigned bits) return sample; } +G_GNUC_CONST +static inline int16_t +pcm_clamp_16(int x) +{ + static const int32_t MIN_VALUE = G_MININT16; + static const int32_t MAX_VALUE = G_MAXINT16; + + if (G_UNLIKELY(x < MIN_VALUE)) + return MIN_VALUE; + if (G_UNLIKELY(x > MAX_VALUE)) + return MAX_VALUE; + return x; +} + +G_GNUC_CONST +static inline int32_t +pcm_clamp_24(int x) +{ + static const int32_t MIN_VALUE = -(1 << 23); + static const int32_t MAX_VALUE = (1 << 23) - 1; + + if (G_UNLIKELY(x < MIN_VALUE)) + return MIN_VALUE; + if (G_UNLIKELY(x > MAX_VALUE)) + return MAX_VALUE; + return x; +} + #endif diff --git a/src/pcm_volume.c b/src/pcm_volume.c index 240c779d8..49c86026f 100644 --- a/src/pcm_volume.c +++ b/src/pcm_volume.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -31,9 +31,9 @@ #define G_LOG_DOMAIN "pcm_volume" static void -pcm_volume_change_8(int8_t *buffer, unsigned num_samples, int volume) +pcm_volume_change_8(int8_t *buffer, const int8_t *end, int volume) { - while (num_samples > 0) { + while (buffer < end) { int32_t sample = *buffer; sample = (sample * volume + pcm_volume_dither() + @@ -41,14 +41,13 @@ pcm_volume_change_8(int8_t *buffer, unsigned num_samples, int volume) / PCM_VOLUME_1; *buffer++ = pcm_range(sample, 8); - --num_samples; } } static void -pcm_volume_change_16(int16_t *buffer, unsigned num_samples, int volume) +pcm_volume_change_16(int16_t *buffer, const int16_t *end, int volume) { - while (num_samples > 0) { + while (buffer < end) { int32_t sample = *buffer; sample = (sample * volume + pcm_volume_dither() + @@ -56,7 +55,6 @@ pcm_volume_change_16(int16_t *buffer, unsigned num_samples, int volume) / PCM_VOLUME_1; *buffer++ = pcm_range(sample, 16); - --num_samples; } } @@ -92,9 +90,9 @@ pcm_volume_sample_24(int32_t sample, int32_t volume, G_GNUC_UNUSED int32_t dithe #endif static void -pcm_volume_change_24(int32_t *buffer, unsigned num_samples, int volume) +pcm_volume_change_24(int32_t *buffer, const int32_t *end, int volume) { - while (num_samples > 0) { + while (buffer < end) { #ifdef __i386__ /* assembly version for i386 */ int32_t sample = *buffer; @@ -110,14 +108,13 @@ pcm_volume_change_24(int32_t *buffer, unsigned num_samples, int volume) / PCM_VOLUME_1; #endif *buffer++ = pcm_range(sample, 24); - --num_samples; } } static void -pcm_volume_change_32(int32_t *buffer, unsigned num_samples, int volume) +pcm_volume_change_32(int32_t *buffer, const int32_t *end, int volume) { - while (num_samples > 0) { + while (buffer < end) { #ifdef __i386__ /* assembly version for i386 */ int32_t sample = *buffer; @@ -132,14 +129,22 @@ pcm_volume_change_32(int32_t *buffer, unsigned num_samples, int volume) / PCM_VOLUME_1; *buffer++ = pcm_range_64(sample, 32); #endif + } +} - --num_samples; +static void +pcm_volume_change_float(float *buffer, const float *end, float volume) +{ + while (buffer < end) { + float sample = *buffer; + sample *= volume; + *buffer++ = sample; } } bool -pcm_volume(void *buffer, int length, - const struct audio_format *format, +pcm_volume(void *buffer, size_t length, + enum sample_format format, int volume) { if (volume == PCM_VOLUME_1) @@ -150,27 +155,36 @@ pcm_volume(void *buffer, int length, return true; } - switch (format->format) { + const void *end = pcm_end_pointer(buffer, length); + switch (format) { + case SAMPLE_FORMAT_UNDEFINED: + case SAMPLE_FORMAT_DSD: + /* not implemented */ + return false; + case SAMPLE_FORMAT_S8: - pcm_volume_change_8((int8_t *)buffer, length, volume); + pcm_volume_change_8(buffer, end, volume); return true; case SAMPLE_FORMAT_S16: - pcm_volume_change_16((int16_t *)buffer, length / 2, - volume); + pcm_volume_change_16(buffer, end, volume); return true; case SAMPLE_FORMAT_S24_P32: - pcm_volume_change_24((int32_t*)buffer, length / 4, - volume); + pcm_volume_change_24(buffer, end, volume); return true; case SAMPLE_FORMAT_S32: - pcm_volume_change_32((int32_t*)buffer, length / 4, - volume); + pcm_volume_change_32(buffer, end, volume); return true; - default: - return false; + case SAMPLE_FORMAT_FLOAT: + pcm_volume_change_float(buffer, end, + pcm_volume_to_float(volume)); + return true; } + + /* unreachable */ + assert(false); + return false; } diff --git a/src/pcm_volume.h b/src/pcm_volume.h index eb61e9526..64e3c7641 100644 --- a/src/pcm_volume.h +++ b/src/pcm_volume.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,6 +21,7 @@ #define PCM_VOLUME_H #include "pcm_prng.h" +#include "audio_format.h" #include <stdint.h> #include <stdbool.h> @@ -42,6 +43,12 @@ pcm_float_to_volume(float volume) return volume * PCM_VOLUME_1 + 0.5; } +static inline float +pcm_volume_to_float(int volume) +{ + return (float)volume / (float)PCM_VOLUME_1; +} + /** * Returns the next volume dithering number, between -511 and +511. * This number is taken from a global PRNG, see pcm_prng(). @@ -62,13 +69,13 @@ pcm_volume_dither(void) * * @param buffer the PCM buffer * @param length the length of the PCM buffer - * @param format the audio format of the PCM buffer + * @param format the sample format of the PCM buffer * @param volume the volume between 0 and #PCM_VOLUME_1 * @return true on success, false if the audio format is not supported */ bool -pcm_volume(void *buffer, int length, - const struct audio_format *format, +pcm_volume(void *buffer, size_t length, + enum sample_format format, int volume); #endif diff --git a/src/permission.c b/src/permission.c index 17b443e0a..cd52b9c86 100644 --- a/src/permission.c +++ b/src/permission.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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.h b/src/permission.h index 9b3a60a66..6c3771362 100644 --- a/src/permission.h +++ b/src/permission.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/pipe.c b/src/pipe.c index 2f5f70e43..d8131432f 100644 --- a/src/pipe.c +++ b/src/pipe.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/pipe.h b/src/pipe.h index efa7a84f0..84b9869e0 100644 --- a/src/pipe.h +++ b/src/pipe.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 e3e6b7739..d8d54dfd6 100644 --- a/src/player_control.c +++ b/src/player_control.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -32,237 +32,247 @@ #include <stdio.h> #include <math.h> -struct player_control pc; - static void -pc_enqueue_song_locked(struct song *song); +pc_enqueue_song_locked(struct player_control *pc, struct song *song); -void pc_init(unsigned buffer_chunks, unsigned int buffered_before_play) +struct player_control * +pc_new(unsigned buffer_chunks, unsigned int buffered_before_play) { - pc.buffer_chunks = buffer_chunks; - pc.buffered_before_play = buffered_before_play; - - 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.mixramp_db = 0; - pc.mixramp_delay_seconds = nanf(""); + struct player_control *pc = g_new0(struct player_control, 1); + + pc->buffer_chunks = buffer_chunks; + pc->buffered_before_play = buffered_before_play; + + 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->mixramp_db = 0; + pc->mixramp_delay_seconds = nanf(""); + + return pc; } -void pc_deinit(void) +void +pc_free(struct player_control *pc) { - g_cond_free(pc.cond); - g_mutex_free(pc.mutex); + g_cond_free(pc->cond); + g_mutex_free(pc->mutex); + g_free(pc); } void -player_wait_decoder(struct decoder_control *dc) +player_wait_decoder(struct player_control *pc, struct decoder_control *dc) { + assert(pc != NULL); + assert(dc != NULL); + assert(dc->client_cond == pc->cond); + /* during this function, the decoder lock is held, because we're waiting for the decoder thread */ - g_cond_wait(pc.cond, dc->mutex); + g_cond_wait(pc->cond, dc->mutex); } void -pc_song_deleted(const struct song *song) +pc_song_deleted(struct player_control *pc, const struct song *song) { - if (pc.errored_song == song) { - pc.error = PLAYER_ERROR_NOERROR; - pc.errored_song = NULL; + if (pc->errored_song == song) { + pc->error = PLAYER_ERROR_NOERROR; + pc->errored_song = NULL; } } static void -player_command_wait_locked(void) +player_command_wait_locked(struct player_control *pc) { - while (pc.command != PLAYER_COMMAND_NONE) - g_cond_wait(main_cond, pc.mutex); + while (pc->command != PLAYER_COMMAND_NONE) + g_cond_wait(main_cond, pc->mutex); } static void -player_command_locked(enum player_command cmd) +player_command_locked(struct player_control *pc, enum player_command cmd) { - assert(pc.command == PLAYER_COMMAND_NONE); + assert(pc->command == PLAYER_COMMAND_NONE); - pc.command = cmd; - player_signal(); - player_command_wait_locked(); + pc->command = cmd; + player_signal(pc); + player_command_wait_locked(pc); } static void -player_command(enum player_command cmd) +player_command(struct player_control *pc, enum player_command cmd) { - player_lock(); - player_command_locked(cmd); - player_unlock(); + player_lock(pc); + player_command_locked(pc, cmd); + player_unlock(pc); } void -pc_play(struct song *song) +pc_play(struct player_control *pc, struct song *song) { assert(song != NULL); - player_lock(); + player_lock(pc); - if (pc.state != PLAYER_STATE_STOP) - player_command_locked(PLAYER_COMMAND_STOP); + if (pc->state != PLAYER_STATE_STOP) + player_command_locked(pc, PLAYER_COMMAND_STOP); - assert(pc.next_song == NULL); + assert(pc->next_song == NULL); - pc_enqueue_song_locked(song); + pc_enqueue_song_locked(pc, song); - assert(pc.next_song == NULL); + assert(pc->next_song == NULL); - player_unlock(); + player_unlock(pc); idle_add(IDLE_PLAYER); } -void pc_cancel(void) +void +pc_cancel(struct player_control *pc) { - player_command(PLAYER_COMMAND_CANCEL); - assert(pc.next_song == NULL); + player_command(pc, PLAYER_COMMAND_CANCEL); + assert(pc->next_song == NULL); } void -pc_stop(void) +pc_stop(struct player_control *pc) { - player_command(PLAYER_COMMAND_CLOSE_AUDIO); - assert(pc.next_song == NULL); + player_command(pc, PLAYER_COMMAND_CLOSE_AUDIO); + assert(pc->next_song == NULL); idle_add(IDLE_PLAYER); } void -pc_update_audio(void) +pc_update_audio(struct player_control *pc) { - player_command(PLAYER_COMMAND_UPDATE_AUDIO); + player_command(pc, PLAYER_COMMAND_UPDATE_AUDIO); } void -pc_kill(void) +pc_kill(struct player_control *pc) { - assert(pc.thread != NULL); + assert(pc->thread != NULL); - player_command(PLAYER_COMMAND_EXIT); - g_thread_join(pc.thread); - pc.thread = NULL; + player_command(pc, PLAYER_COMMAND_EXIT); + g_thread_join(pc->thread); + pc->thread = NULL; idle_add(IDLE_PLAYER); } void -pc_pause(void) +pc_pause(struct player_control *pc) { - player_lock(); + player_lock(pc); - if (pc.state != PLAYER_STATE_STOP) { - player_command_locked(PLAYER_COMMAND_PAUSE); + if (pc->state != PLAYER_STATE_STOP) { + player_command_locked(pc, PLAYER_COMMAND_PAUSE); idle_add(IDLE_PLAYER); } - player_unlock(); + player_unlock(pc); } static void -pc_pause_locked(void) +pc_pause_locked(struct player_control *pc) { - if (pc.state != PLAYER_STATE_STOP) { - player_command_locked(PLAYER_COMMAND_PAUSE); + if (pc->state != PLAYER_STATE_STOP) { + player_command_locked(pc, PLAYER_COMMAND_PAUSE); idle_add(IDLE_PLAYER); } } void -pc_set_pause(bool pause_flag) +pc_set_pause(struct player_control *pc, bool pause_flag) { - player_lock(); + player_lock(pc); - switch (pc.state) { + switch (pc->state) { case PLAYER_STATE_STOP: break; case PLAYER_STATE_PLAY: if (pause_flag) - pc_pause_locked(); + pc_pause_locked(pc); break; case PLAYER_STATE_PAUSE: if (!pause_flag) - pc_pause_locked(); + pc_pause_locked(pc); break; } - player_unlock(); + player_unlock(pc); } void -pc_get_status(struct player_status *status) +pc_get_status(struct player_control *pc, struct player_status *status) { - player_lock(); - player_command_locked(PLAYER_COMMAND_REFRESH); + player_lock(pc); + player_command_locked(pc, PLAYER_COMMAND_REFRESH); - status->state = pc.state; + 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; + 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(); + player_unlock(pc); } enum player_state -pc_get_state(void) +pc_get_state(struct player_control *pc) { - return pc.state; + return pc->state; } void -pc_clear_error(void) +pc_clear_error(struct player_control *pc) { - player_lock(); - pc.error = PLAYER_ERROR_NOERROR; - pc.errored_song = NULL; - player_unlock(); + player_lock(pc); + pc->error = PLAYER_ERROR_NOERROR; + pc->errored_song = NULL; + player_unlock(pc); } enum player_error -pc_get_error(void) +pc_get_error(struct player_control *pc) { - return pc.error; + return pc->error; } static char * -pc_errored_song_uri(void) +pc_errored_song_uri(struct player_control *pc) { - return song_get_uri(pc.errored_song); + return song_get_uri(pc->errored_song); } char * -pc_get_error_message(void) +pc_get_error_message(struct player_control *pc) { char *error; char *uri; - switch (pc.error) { + switch (pc->error) { case PLAYER_ERROR_NOERROR: return NULL; case PLAYER_ERROR_FILENOTFOUND: - uri = pc_errored_song_uri(); + uri = pc_errored_song_uri(pc); error = g_strdup_printf("file \"%s\" does not exist or is inaccessible", uri); g_free(uri); return error; case PLAYER_ERROR_FILE: - uri = pc_errored_song_uri(); + uri = pc_errored_song_uri(pc); error = g_strdup_printf("problems decoding \"%s\"", uri); g_free(uri); return error; @@ -271,10 +281,10 @@ pc_get_error_message(void) return g_strdup("problems opening audio device"); case PLAYER_ERROR_SYSTEM: - return g_strdup("system error occured"); + return g_strdup("system error occurred"); case PLAYER_ERROR_UNKTYPE: - uri = pc_errored_song_uri(); + uri = pc_errored_song_uri(pc); error = g_strdup_printf("file type of \"%s\" is unknown", uri); g_free(uri); return error; @@ -285,37 +295,37 @@ pc_get_error_message(void) } static void -pc_enqueue_song_locked(struct song *song) +pc_enqueue_song_locked(struct player_control *pc, struct song *song) { assert(song != NULL); - assert(pc.next_song == NULL); + assert(pc->next_song == NULL); - pc.next_song = song; - player_command_locked(PLAYER_COMMAND_QUEUE); + pc->next_song = song; + player_command_locked(pc, PLAYER_COMMAND_QUEUE); } void -pc_enqueue_song(struct song *song) +pc_enqueue_song(struct player_control *pc, struct song *song) { assert(song != NULL); - player_lock(); - pc_enqueue_song_locked(song); - player_unlock(); + player_lock(pc); + pc_enqueue_song_locked(pc, song); + player_unlock(pc); } bool -pc_seek(struct song *song, float seek_time) +pc_seek(struct player_control *pc, struct song *song, float seek_time) { assert(song != NULL); - player_lock(); - pc.next_song = song; - pc.seek_where = seek_time; - player_command_locked(PLAYER_COMMAND_SEEK); - player_unlock(); + player_lock(pc); + pc->next_song = song; + pc->seek_where = seek_time; + player_command_locked(pc, PLAYER_COMMAND_SEEK); + player_unlock(pc); - assert(pc.next_song == NULL); + assert(pc->next_song == NULL); idle_add(IDLE_PLAYER); @@ -323,51 +333,51 @@ pc_seek(struct song *song, float seek_time) } float -pc_get_cross_fade(void) +pc_get_cross_fade(const struct player_control *pc) { - return pc.cross_fade_seconds; + return pc->cross_fade_seconds; } void -pc_set_cross_fade(float cross_fade_seconds) +pc_set_cross_fade(struct player_control *pc, float cross_fade_seconds) { if (cross_fade_seconds < 0) cross_fade_seconds = 0; - pc.cross_fade_seconds = cross_fade_seconds; + pc->cross_fade_seconds = cross_fade_seconds; idle_add(IDLE_OPTIONS); } float -pc_get_mixramp_db(void) +pc_get_mixramp_db(const struct player_control *pc) { - return pc.mixramp_db; + return pc->mixramp_db; } void -pc_set_mixramp_db(float mixramp_db) +pc_set_mixramp_db(struct player_control *pc, float mixramp_db) { - pc.mixramp_db = mixramp_db; + pc->mixramp_db = mixramp_db; idle_add(IDLE_OPTIONS); } float -pc_get_mixramp_delay(void) +pc_get_mixramp_delay(const struct player_control *pc) { - return pc.mixramp_delay_seconds; + return pc->mixramp_delay_seconds; } void -pc_set_mixramp_delay(float mixramp_delay_seconds) +pc_set_mixramp_delay(struct player_control *pc, float mixramp_delay_seconds) { - pc.mixramp_delay_seconds = mixramp_delay_seconds; + pc->mixramp_delay_seconds = mixramp_delay_seconds; idle_add(IDLE_OPTIONS); } double -pc_get_total_play_time(void) +pc_get_total_play_time(const struct player_control *pc) { - return pc.total_play_time; + return pc->total_play_time; } diff --git a/src/player_control.h b/src/player_control.h index 76c47609a..5a04ab0f9 100644 --- a/src/player_control.h +++ b/src/player_control.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,9 +20,10 @@ #ifndef MPD_PLAYER_H #define MPD_PLAYER_H -#include "notify.h" #include "audio_format.h" +#include <glib.h> + #include <stdint.h> struct decoder_control; @@ -116,28 +117,28 @@ struct player_control { double total_play_time; }; -extern struct player_control pc; - -void pc_init(unsigned buffer_chunks, unsigned buffered_before_play); +struct player_control * +pc_new(unsigned buffer_chunks, unsigned buffered_before_play); -void pc_deinit(void); +void +pc_free(struct player_control *pc); /** * Locks the #player_control object. */ static inline void -player_lock(void) +player_lock(struct player_control *pc) { - g_mutex_lock(pc.mutex); + g_mutex_lock(pc->mutex); } /** * Unlocks the #player_control object. */ static inline void -player_unlock(void) +player_unlock(struct player_control *pc) { - g_mutex_unlock(pc.mutex); + g_mutex_unlock(pc->mutex); } /** @@ -146,9 +147,9 @@ player_unlock(void) * to calling this function. */ static inline void -player_wait(void) +player_wait(struct player_control *pc) { - g_cond_wait(pc.cond, pc.mutex); + g_cond_wait(pc->cond, pc->mutex); } /** @@ -159,16 +160,16 @@ player_wait(void) * Note the small difference to the player_wait() function! */ void -player_wait_decoder(struct decoder_control *dc); +player_wait_decoder(struct player_control *pc, 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) +player_signal(struct player_control *pc) { - g_cond_signal(pc.cond); + g_cond_signal(pc->cond); } /** @@ -176,11 +177,11 @@ player_signal(void) * locked by this function. */ static inline void -player_lock_signal(void) +player_lock_signal(struct player_control *pc) { - player_lock(); - player_signal(); - player_unlock(); + player_lock(pc); + player_signal(pc); + player_unlock(pc); } /** @@ -189,33 +190,34 @@ player_lock_signal(void) * not point to an invalid pointer. */ void -pc_song_deleted(const struct song *song); +pc_song_deleted(struct player_control *pc, const struct song *song); void -pc_play(struct song *song); +pc_play(struct player_control *pc, struct song *song); /** * see PLAYER_COMMAND_CANCEL */ -void pc_cancel(void); +void +pc_cancel(struct player_control *pc); void -pc_set_pause(bool pause_flag); +pc_set_pause(struct player_control *pc, bool pause_flag); void -pc_pause(void); +pc_pause(struct player_control *pc); void -pc_kill(void); +pc_kill(struct player_control *pc); void -pc_get_status(struct player_status *status); +pc_get_status(struct player_control *pc, struct player_status *status); enum player_state -pc_get_state(void); +pc_get_state(struct player_control *pc); void -pc_clear_error(void); +pc_clear_error(struct player_control *pc); /** * Returns the human-readable message describing the last error during @@ -223,19 +225,19 @@ pc_clear_error(void); * returned string. */ char * -pc_get_error_message(void); +pc_get_error_message(struct player_control *pc); enum player_error -pc_get_error(void); +pc_get_error(struct player_control *pc); void -pc_stop(void); +pc_stop(struct player_control *pc); void -pc_update_audio(void); +pc_update_audio(struct player_control *pc); void -pc_enqueue_song(struct song *song); +pc_enqueue_song(struct player_control *pc, struct song *song); /** * Makes the player thread seek the specified song to a position. @@ -244,27 +246,27 @@ pc_enqueue_song(struct song *song); * playing currently) */ bool -pc_seek(struct song *song, float seek_time); +pc_seek(struct player_control *pc, struct song *song, float seek_time); void -pc_set_cross_fade(float cross_fade_seconds); +pc_set_cross_fade(struct player_control *pc, float cross_fade_seconds); float -pc_get_cross_fade(void); +pc_get_cross_fade(const struct player_control *pc); void -pc_set_mixramp_db(float mixramp_db); +pc_set_mixramp_db(struct player_control *pc, float mixramp_db); float -pc_get_mixramp_db(void); +pc_get_mixramp_db(const struct player_control *pc); void -pc_set_mixramp_delay(float mixramp_delay_seconds); +pc_set_mixramp_delay(struct player_control *pc, float mixramp_delay_seconds); float -pc_get_mixramp_delay(void); +pc_get_mixramp_delay(const struct player_control *pc); double -pc_get_total_play_time(void); +pc_get_total_play_time(const struct player_control *pc); #endif diff --git a/src/player_thread.c b/src/player_thread.c index 52788e518..c0243fa00 100644 --- a/src/player_thread.c +++ b/src/player_thread.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -48,6 +48,8 @@ enum xfade_state { }; struct player { + struct player_control *pc; + struct decoder_control *dc; struct music_pipe *pipe; @@ -118,26 +120,28 @@ struct player { * 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. + * precisely. */ float elapsed_time; }; static struct music_buffer *player_buffer; -static void player_command_finished_locked(void) +static void +player_command_finished_locked(struct player_control *pc) { - assert(pc.command != PLAYER_COMMAND_NONE); + assert(pc->command != PLAYER_COMMAND_NONE); - pc.command = PLAYER_COMMAND_NONE; + pc->command = PLAYER_COMMAND_NONE; g_cond_signal(main_cond); } -static void player_command_finished(void) +static void +player_command_finished(struct player_control *pc) { - player_lock(); - player_command_finished_locked(); - player_unlock(); + player_lock(pc); + player_command_finished_locked(pc); + player_unlock(pc); } /** @@ -148,17 +152,18 @@ static void player_command_finished(void) static void player_dc_start(struct player *player, struct music_pipe *pipe) { + struct player_control *pc = player->pc; struct decoder_control *dc = player->dc; - assert(player->queued || pc.command == PLAYER_COMMAND_SEEK); - assert(pc.next_song != NULL); + assert(player->queued || pc->command == PLAYER_COMMAND_SEEK); + assert(pc->next_song != NULL); - unsigned start_ms = pc.next_song->start_ms; - if (pc.command == PLAYER_COMMAND_SEEK) - start_ms += (unsigned)(pc.seek_where * 1000); + unsigned start_ms = pc->next_song->start_ms; + if (pc->command == PLAYER_COMMAND_SEEK) + start_ms += (unsigned)(pc->seek_where * 1000); - dc_start(dc, pc.next_song, - start_ms, pc.next_song->end_ms, + dc_start(dc, pc->next_song, + start_ms, pc->next_song->end_ms, player_buffer, pipe); } @@ -222,41 +227,42 @@ player_dc_stop(struct player *player) static bool player_wait_for_decoder(struct player *player) { + struct player_control *pc = player->pc; struct decoder_control *dc = player->dc; - assert(player->queued || pc.command == PLAYER_COMMAND_SEEK); - assert(pc.next_song != NULL); + assert(player->queued || pc->command == PLAYER_COMMAND_SEEK); + assert(pc->next_song != NULL); player->queued = false; if (decoder_lock_has_failed(dc)) { - player_lock(); - pc.errored_song = dc->song; - pc.error = PLAYER_ERROR_FILE; - pc.next_song = NULL; - player_unlock(); + player_lock(pc); + pc->errored_song = dc->song; + pc->error = PLAYER_ERROR_FILE; + pc->next_song = NULL; + player_unlock(pc); return false; } - player->song = pc.next_song; + player->song = pc->next_song; player->elapsed_time = 0.0; /* set the "starting" flag, which will be cleared by player_check_decoder_startup() */ player->decoder_starting = true; - player_lock(); + player_lock(pc); /* 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); + 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; + pc->next_song = NULL; - player_unlock(); + player_unlock(pc); /* call syncPlaylistWithQueue() in the main thread */ event_pipe_emit(PIPE_EVENT_PLAYLIST); @@ -293,17 +299,19 @@ real_song_duration(const struct song *song, double decoder_duration) static bool player_open_output(struct player *player) { + struct player_control *pc = player->pc; + assert(audio_format_defined(&player->play_audio_format)); - assert(pc.state == PLAYER_STATE_PLAY || - pc.state == PLAYER_STATE_PAUSE); + assert(pc->state == PLAYER_STATE_PLAY || + pc->state == PLAYER_STATE_PAUSE); if (audio_output_all_open(&player->play_audio_format, player_buffer)) { player->output_open = true; player->paused = false; - player_lock(); - pc.state = PLAYER_STATE_PLAY; - player_unlock(); + player_lock(pc); + pc->state = PLAYER_STATE_PLAY; + player_unlock(pc); return true; } else { @@ -313,10 +321,10 @@ player_open_output(struct player *player) audio output becomes available */ player->paused = true; - player_lock(); - pc.error = PLAYER_ERROR_AUDIO; - pc.state = PLAYER_STATE_PAUSE; - player_unlock(); + player_lock(pc); + pc->error = PLAYER_ERROR_AUDIO; + pc->state = PLAYER_STATE_PAUSE; + player_unlock(pc); return false; } @@ -332,6 +340,7 @@ player_open_output(struct player *player) static bool player_check_decoder_startup(struct player *player) { + struct player_control *pc = player->pc; struct decoder_control *dc = player->dc; assert(player->decoder_starting); @@ -342,10 +351,10 @@ player_check_decoder_startup(struct player *player) /* the decoder failed */ decoder_unlock(dc); - player_lock(); - pc.errored_song = dc->song; - pc.error = PLAYER_ERROR_FILE; - player_unlock(); + player_lock(pc); + pc->errored_song = dc->song; + pc->error = PLAYER_ERROR_FILE; + player_unlock(pc); return false; } else if (!decoder_is_starting(dc)) { @@ -354,15 +363,15 @@ player_check_decoder_startup(struct player *player) decoder_unlock(dc); if (player->output_open && - !audio_output_all_wait(1)) + !audio_output_all_wait(pc, 1)) /* the output devices havn't finished playing all chunks yet - wait for that */ return true; - player_lock(); - pc.total_time = real_song_duration(dc->song, dc->total_time); - pc.audio_format = dc->in_audio_format; - player_unlock(); + player_lock(pc); + pc->total_time = real_song_duration(dc->song, dc->total_time); + pc->audio_format = dc->in_audio_format; + player_unlock(pc); player->play_audio_format = dc->out_audio_format; player->decoder_starting = false; @@ -380,7 +389,7 @@ player_check_decoder_startup(struct player *player) } else { /* the decoder is not yet ready; wait some more */ - player_wait_decoder(dc); + player_wait_decoder(pc, dc); decoder_unlock(dc); return true; @@ -435,10 +444,11 @@ player_send_silence(struct player *player) */ static bool player_seek_decoder(struct player *player) { - struct song *song = pc.next_song; + struct player_control *pc = player->pc; + struct song *song = pc->next_song; struct decoder_control *dc = player->dc; - assert(pc.next_song != NULL); + assert(pc->next_song != NULL); if (decoder_current_song(dc) != song) { /* the decoder is already decoding the "next" song - @@ -454,7 +464,7 @@ static bool player_seek_decoder(struct player *player) player_dc_start(player, player->pipe); if (!player_wait_for_decoder(player)) { /* decoder failure */ - player_command_finished(); + player_command_finished(pc); return false; } } else { @@ -466,7 +476,7 @@ static bool player_seek_decoder(struct player *player) player->pipe = dc->pipe; } - pc.next_song = NULL; + pc->next_song = NULL; player->queued = false; } @@ -475,28 +485,28 @@ static bool player_seek_decoder(struct player *player) while (player->decoder_starting) { if (!player_check_decoder_startup(player)) { /* decoder failure */ - player_command_finished(); + player_command_finished(pc); return false; } } /* send the SEEK command */ - double where = pc.seek_where; - if (where > pc.total_time) - where = pc.total_time - 0.1; + double where = pc->seek_where; + if (where > pc->total_time) + where = pc->total_time - 0.1; if (where < 0.0) where = 0.0; if (!dc_seek(dc, where + song->start_ms / 1000.0)) { /* decoder failure */ - player_command_finished(); + player_command_finished(pc); return false; } player->elapsed_time = where; - player_command_finished(); + player_command_finished(pc); player->xfade = XFADE_UNKNOWN; @@ -513,9 +523,10 @@ static bool player_seek_decoder(struct player *player) */ static void player_process_command(struct player *player) { + struct player_control *pc = player->pc; G_GNUC_UNUSED struct decoder_control *dc = player->dc; - switch (pc.command) { + switch (pc->command) { case PLAYER_COMMAND_NONE: case PLAYER_COMMAND_STOP: case PLAYER_COMMAND_EXIT: @@ -523,85 +534,85 @@ static void player_process_command(struct player *player) break; case PLAYER_COMMAND_UPDATE_AUDIO: - player_unlock(); + player_unlock(pc); audio_output_all_enable_disable(); - player_lock(); - player_command_finished_locked(); + player_lock(pc); + player_command_finished_locked(pc); break; case PLAYER_COMMAND_QUEUE: - assert(pc.next_song != NULL); + assert(pc->next_song != NULL); assert(!player->queued); assert(!player_dc_at_next_song(player)); player->queued = true; - player_command_finished_locked(); + player_command_finished_locked(pc); break; case PLAYER_COMMAND_PAUSE: - player_unlock(); + player_unlock(pc); player->paused = !player->paused; if (player->paused) { audio_output_all_pause(); - player_lock(); + player_lock(pc); - pc.state = PLAYER_STATE_PAUSE; + 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(); + player_lock(pc); - pc.state = PLAYER_STATE_PLAY; + pc->state = PLAYER_STATE_PLAY; } else { player_open_output(player); - player_lock(); + player_lock(pc); } - player_command_finished_locked(); + player_command_finished_locked(pc); break; case PLAYER_COMMAND_SEEK: - player_unlock(); + player_unlock(pc); player_seek_decoder(player); - player_lock(); + player_lock(pc); break; case PLAYER_COMMAND_CANCEL: - if (pc.next_song == NULL) { + if (pc->next_song == NULL) { /* the cancel request arrived too late, we're already playing the queued song... stop everything now */ - pc.command = PLAYER_COMMAND_STOP; + pc->command = PLAYER_COMMAND_STOP; return; } if (player_dc_at_next_song(player)) { /* the decoder is already decoding the song - stop it and reset the position */ - player_unlock(); + player_unlock(pc); player_dc_stop(player); - player_lock(); + player_lock(pc); } - pc.next_song = NULL; + pc->next_song = NULL; player->queued = false; - player_command_finished_locked(); + player_command_finished_locked(pc); break; case PLAYER_COMMAND_REFRESH: if (player->output_open && !player->paused) { - player_unlock(); + player_unlock(pc); audio_output_all_check(); - player_lock(); + player_lock(pc); } - pc.elapsed_time = audio_output_all_get_elapsed_time(); - if (pc.elapsed_time < 0.0) - pc.elapsed_time = player->elapsed_time; + 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(); + player_command_finished_locked(pc); break; } } @@ -637,7 +648,8 @@ update_song_tag(struct song *song, const struct tag *new_tag) * Player lock is not held. */ static bool -play_chunk(struct song *song, struct music_chunk *chunk, +play_chunk(struct player_control *pc, + struct song *song, struct music_chunk *chunk, const struct audio_format *format) { assert(music_chunk_check_format(chunk, format)); @@ -650,16 +662,16 @@ play_chunk(struct song *song, struct music_chunk *chunk, return true; } - player_lock(); - pc.bit_rate = chunk->bit_rate; - player_unlock(); + player_lock(pc); + pc->bit_rate = chunk->bit_rate; + player_unlock(pc); /* send the chunk to the audio outputs */ if (!audio_output_all_play(chunk)) return false; - pc.total_play_time += (double)chunk->length / + pc->total_play_time += (double)chunk->length / audio_format_time_to_size(format); return true; } @@ -673,9 +685,10 @@ play_chunk(struct song *song, struct music_chunk *chunk, static bool play_next_chunk(struct player *player) { + struct player_control *pc = player->pc; struct decoder_control *dc = player->dc; - if (!audio_output_all_wait(64)) + if (!audio_output_all_wait(pc, 64)) /* the output pipe is still large enough, don't send another chunk */ return true; @@ -712,7 +725,7 @@ play_next_chunk(struct player *player) other_chunk->tag); other_chunk->tag = NULL; - if (isnan(pc.mixramp_delay_seconds)) { + if (isnan(pc->mixramp_delay_seconds)) { chunk->mix_ratio = ((float)cross_fade_position) / player->cross_fade_chunks; } else { @@ -747,7 +760,7 @@ play_next_chunk(struct player *player) } else { /* wait for the decoder */ decoder_signal(dc); - player_wait_decoder(dc); + player_wait_decoder(pc, dc); decoder_unlock(dc); return true; @@ -770,19 +783,20 @@ play_next_chunk(struct player *player) /* play the current chunk */ - if (!play_chunk(player->song, chunk, &player->play_audio_format)) { + if (!play_chunk(player->pc, player->song, chunk, + &player->play_audio_format)) { music_buffer_return(player_buffer, chunk); - player_lock(); + player_lock(pc); - pc.error = PLAYER_ERROR_AUDIO; + pc->error = PLAYER_ERROR_AUDIO; /* pause: the user may resume playback as soon as an audio output becomes available */ - pc.state = PLAYER_STATE_PAUSE; + pc->state = PLAYER_STATE_PAUSE; player->paused = true; - player_unlock(); + player_unlock(pc); return false; } @@ -792,7 +806,7 @@ play_next_chunk(struct player *player) larger block at a time */ decoder_lock(dc); if (!decoder_is_idle(dc) && - music_pipe_size(dc->pipe) <= (pc.buffered_before_play + + music_pipe_size(dc->pipe) <= (pc->buffered_before_play + music_buffer_size(player_buffer) * 3) / 4) decoder_signal(dc); decoder_unlock(dc); @@ -834,9 +848,10 @@ 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(struct decoder_control *dc) +static void do_play(struct player_control *pc, struct decoder_control *dc) { struct player player = { + .pc = pc, .dc = dc, .buffering = true, .decoder_starting = false, @@ -851,46 +866,46 @@ static void do_play(struct decoder_control *dc) .elapsed_time = 0.0, }; - player_unlock(); + player_unlock(pc); player.pipe = music_pipe_new(); player_dc_start(&player, player.pipe); if (!player_wait_for_decoder(&player)) { player_dc_stop(&player); - player_command_finished(); + player_command_finished(pc); music_pipe_free(player.pipe); event_pipe_emit(PIPE_EVENT_PLAYLIST); - player_lock(); + player_lock(pc); return; } - player_lock(); + player_lock(pc); + pc->state = PLAYER_STATE_PLAY; - if (pc.command == PLAYER_COMMAND_SEEK) - player.elapsed_time = pc.seek_where; + if (pc->command == PLAYER_COMMAND_SEEK) + player.elapsed_time = pc->seek_where; - pc.state = PLAYER_STATE_PLAY; - player_command_finished_locked(); + player_command_finished_locked(pc); 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(); + if (pc->command == PLAYER_COMMAND_STOP || + pc->command == PLAYER_COMMAND_EXIT || + pc->command == PLAYER_COMMAND_CLOSE_AUDIO) { + player_unlock(pc); audio_output_all_cancel(); break; } - player_unlock(); + player_unlock(pc); 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 && + if (music_pipe_size(player.pipe) < pc->buffered_before_play && !decoder_lock_is_idle(dc)) { /* not enough decoded buffer space yet */ @@ -902,9 +917,9 @@ static void do_play(struct decoder_control *dc) decoder_lock(dc); /* XXX race condition: check decoder again */ - player_wait_decoder(dc); + player_wait_decoder(pc, dc); decoder_unlock(dc); - player_lock(); + player_lock(pc); continue; } else { /* buffering is complete */ @@ -918,7 +933,7 @@ static void do_play(struct decoder_control *dc) if (!player_check_decoder_startup(&player)) break; - player_lock(); + player_lock(pc); continue; } @@ -947,9 +962,9 @@ static void do_play(struct decoder_control *dc) calculate how many chunks will be required for it */ player.cross_fade_chunks = - cross_fade_calc(pc.cross_fade_seconds, dc->total_time, - pc.mixramp_db, - pc.mixramp_delay_seconds, + 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, @@ -957,7 +972,7 @@ static void do_play(struct decoder_control *dc) &dc->out_audio_format, &player.play_audio_format, music_buffer_size(player_buffer) - - pc.buffered_before_play); + pc->buffered_before_play); if (player.cross_fade_chunks > 0) { player.xfade = XFADE_ENABLED; player.cross_fading = false; @@ -968,10 +983,10 @@ static void do_play(struct decoder_control *dc) } if (player.paused) { - player_lock(); + player_lock(pc); - if (pc.command == PLAYER_COMMAND_NONE) - player_wait(); + if (pc->command == PLAYER_COMMAND_NONE) + player_wait(pc); continue; } else if (!music_pipe_empty(player.pipe)) { /* at least one music chunk is ready - send it @@ -1008,7 +1023,7 @@ static void do_play(struct decoder_control *dc) break; } - player_lock(); + player_lock(pc); } player_dc_stop(&player); @@ -1019,113 +1034,115 @@ static void do_play(struct decoder_control *dc) if (player.cross_fade_tag != NULL) tag_free(player.cross_fade_tag); - player_lock(); + player_lock(pc); if (player.queued) { - assert(pc.next_song != NULL); - pc.next_song = NULL; + assert(pc->next_song != NULL); + pc->next_song = NULL; } - pc.state = PLAYER_STATE_STOP; + pc->state = PLAYER_STATE_STOP; - player_unlock(); + player_unlock(pc); event_pipe_emit(PIPE_EVENT_PLAYLIST); - player_lock(); + player_lock(pc); } -static gpointer player_task(G_GNUC_UNUSED gpointer arg) +static gpointer +player_task(gpointer arg) { - struct decoder_control dc; + struct player_control *pc = arg; - dc_init(&dc); - decoder_thread_start(&dc); + struct decoder_control *dc = dc_new(pc->cond); + decoder_thread_start(dc); - player_buffer = music_buffer_new(pc.buffer_chunks); + player_buffer = music_buffer_new(pc->buffer_chunks); - player_lock(); + player_lock(pc); while (1) { - switch (pc.command) { + switch (pc->command) { case PLAYER_COMMAND_SEEK: case PLAYER_COMMAND_QUEUE: - assert(pc.next_song != NULL); + assert(pc->next_song != NULL); - do_play(&dc); + do_play(pc, dc); break; case PLAYER_COMMAND_STOP: - player_unlock(); + player_unlock(pc); audio_output_all_cancel(); - player_lock(); + player_lock(pc); /* fall through */ case PLAYER_COMMAND_PAUSE: - pc.next_song = NULL; - player_command_finished_locked(); + pc->next_song = NULL; + player_command_finished_locked(pc); break; case PLAYER_COMMAND_CLOSE_AUDIO: - player_unlock(); + player_unlock(pc); audio_output_all_release(); - player_lock(); - player_command_finished_locked(); + player_lock(pc); + player_command_finished_locked(pc); #ifndef NDEBUG /* in the DEBUG build, check for leaked music_chunk objects by freeing the music_buffer */ music_buffer_free(player_buffer); - player_buffer = music_buffer_new(pc.buffer_chunks); + player_buffer = music_buffer_new(pc->buffer_chunks); #endif break; case PLAYER_COMMAND_UPDATE_AUDIO: - player_unlock(); + player_unlock(pc); audio_output_all_enable_disable(); - player_lock(); - player_command_finished_locked(); + player_lock(pc); + player_command_finished_locked(pc); break; case PLAYER_COMMAND_EXIT: - player_unlock(); + player_unlock(pc); - dc_quit(&dc); - dc_deinit(&dc); + dc_quit(dc); + dc_free(dc); audio_output_all_close(); music_buffer_free(player_buffer); - player_command_finished(); + player_command_finished(pc); return NULL; case PLAYER_COMMAND_CANCEL: - pc.next_song = NULL; - player_command_finished_locked(); + pc->next_song = NULL; + player_command_finished_locked(pc); break; case PLAYER_COMMAND_REFRESH: /* no-op when not playing */ - player_command_finished_locked(); + player_command_finished_locked(pc); break; case PLAYER_COMMAND_NONE: - player_wait(); + player_wait(pc); break; } } } -void player_create(void) +void +player_create(struct player_control *pc) { - assert(pc.thread == NULL); + assert(pc->thread == NULL); GError *e = NULL; - pc.thread = g_thread_create(player_task, NULL, true, &e); - if (pc.thread == NULL) + pc->thread = g_thread_create(player_task, pc, true, &e); + if (pc->thread == NULL) MPD_ERROR("Failed to spawn player task: %s", e->message); } diff --git a/src/player_thread.h b/src/player_thread.h index e645b1d09..7373eb438 100644 --- a/src/player_thread.h +++ b/src/player_thread.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -37,6 +37,9 @@ #ifndef MPD_PLAYER_THREAD_H #define MPD_PLAYER_THREAD_H -void player_create(void); +struct player_control; + +void +player_create(struct player_control *pc); #endif diff --git a/src/playlist.c b/src/playlist.c index 4a1e54814..0c9eea92d 100644 --- a/src/playlist.c +++ b/src/playlist.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -75,7 +75,8 @@ playlist_finish(struct playlist *playlist) * Queue a song, addressed by its order number. */ static void -playlist_queue_song_order(struct playlist *playlist, unsigned order) +playlist_queue_song_order(struct playlist *playlist, struct player_control *pc, + unsigned order) { struct song *song; char *uri; @@ -89,16 +90,16 @@ playlist_queue_song_order(struct playlist *playlist, unsigned order) g_debug("queue song %i:\"%s\"", playlist->queued, uri); g_free(uri); - pc_enqueue_song(song); + pc_enqueue_song(pc, song); } /** * Called if the player thread has started playing the "queued" song. */ static void -playlist_song_started(struct playlist *playlist) +playlist_song_started(struct playlist *playlist, struct player_control *pc) { - assert(pc.next_song == NULL); + assert(pc->next_song == NULL); assert(playlist->queued >= -1); /* queued song has started: copy queued to current, @@ -110,11 +111,13 @@ playlist_song_started(struct playlist *playlist) /* Pause if we are in single mode. */ if(playlist->queue.single && !playlist->queue.repeat) { - pc_set_pause(true); + pc_set_pause(pc, true); } if(playlist->queue.consume) - playlist_delete(playlist, queue_order_to_position(&playlist->queue, current)); + playlist_delete(playlist, pc, + queue_order_to_position(&playlist->queue, + current)); idle_add(IDLE_PLAYER); } @@ -129,7 +132,9 @@ playlist_get_queued_song(struct playlist *playlist) } void -playlist_update_queued_song(struct playlist *playlist, const struct song *prev) +playlist_update_queued_song(struct playlist *playlist, + struct player_control *pc, + const struct song *prev) { int next_order; const struct song *next_song; @@ -170,20 +175,21 @@ playlist_update_queued_song(struct playlist *playlist, const struct song *prev) if (prev != NULL && next_song != prev) { /* clear the currently queued song */ - pc_cancel(); + pc_cancel(pc); playlist->queued = -1; } if (next_order >= 0) { if (next_song != prev) - playlist_queue_song_order(playlist, next_order); + playlist_queue_song_order(playlist, pc, next_order); else playlist->queued = next_order; } } void -playlist_play_order(struct playlist *playlist, int orderNum) +playlist_play_order(struct playlist *playlist, struct player_control *pc, + int orderNum) { struct song *song; char *uri; @@ -197,46 +203,46 @@ playlist_play_order(struct playlist *playlist, int orderNum) g_debug("play %i:\"%s\"", orderNum, uri); g_free(uri); - pc_play(song); + pc_play(pc, song); playlist->current = orderNum; } static void -playlist_resume_playback(struct playlist *playlist); +playlist_resume_playback(struct playlist *playlist, struct player_control *pc); /** * 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 -playlist_sync(struct playlist *playlist) +playlist_sync(struct playlist *playlist, struct player_control *pc) { if (!playlist->playing) /* this event has reached us out of sync: we aren't playing anymore; ignore the event */ return; - player_lock(); - enum player_state pc_state = pc_get_state(); - const struct song *pc_next_song = pc.next_song; - player_unlock(); + player_lock(pc); + enum player_state pc_state = pc_get_state(pc); + const struct song *pc_next_song = pc->next_song; + player_unlock(pc); 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 */ - playlist_resume_playback(playlist); + playlist_resume_playback(playlist, pc); else { /* check if the player thread has already started playing the queued song */ if (pc_next_song == NULL && playlist->queued != -1) - playlist_song_started(playlist); + playlist_song_started(playlist, pc); /* make sure the queued song is always set (if possible) */ - if (pc.next_song == NULL && playlist->queued < 0) - playlist_update_queued_song(playlist, NULL); + if (pc->next_song == NULL && playlist->queued < 0) + playlist_update_queued_song(playlist, pc, NULL); } } @@ -245,14 +251,14 @@ playlist_sync(struct playlist *playlist) * decide whether to re-start playback */ static void -playlist_resume_playback(struct playlist *playlist) +playlist_resume_playback(struct playlist *playlist, struct player_control *pc) { enum player_error error; assert(playlist->playing); - assert(pc_get_state() == PLAYER_STATE_STOP); + assert(pc_get_state(pc) == PLAYER_STATE_STOP); - error = pc_get_error(); + error = pc_get_error(pc); if (error == PLAYER_ERROR_NOERROR) playlist->error_count = 0; else @@ -263,10 +269,10 @@ playlist_resume_playback(struct playlist *playlist) playlist->error_count >= queue_length(&playlist->queue)) /* too many errors, or critical error: stop playback */ - playlist_stop(playlist); + playlist_stop(playlist, pc); else /* continue playback at the next song */ - playlist_next(playlist); + playlist_next(playlist, pc); } bool @@ -294,7 +300,8 @@ playlist_get_consume(const struct playlist *playlist) } void -playlist_set_repeat(struct playlist *playlist, bool status) +playlist_set_repeat(struct playlist *playlist, struct player_control *pc, + bool status) { if (status == playlist->queue.repeat) return; @@ -303,7 +310,7 @@ playlist_set_repeat(struct playlist *playlist, bool status) /* if the last song is currently being played, the "next song" might change when repeat mode is toggled */ - playlist_update_queued_song(playlist, + playlist_update_queued_song(playlist, pc, playlist_get_queued_song(playlist)); idle_add(IDLE_OPTIONS); @@ -321,7 +328,8 @@ playlist_order(struct playlist *playlist) } void -playlist_set_single(struct playlist *playlist, bool status) +playlist_set_single(struct playlist *playlist, struct player_control *pc, + bool status) { if (status == playlist->queue.single) return; @@ -330,7 +338,7 @@ playlist_set_single(struct playlist *playlist, bool status) /* if the last song is currently being played, the "next song" might change when single mode is toggled */ - playlist_update_queued_song(playlist, + playlist_update_queued_song(playlist, pc, playlist_get_queued_song(playlist)); idle_add(IDLE_OPTIONS); @@ -347,7 +355,8 @@ playlist_set_consume(struct playlist *playlist, bool status) } void -playlist_set_random(struct playlist *playlist, bool status) +playlist_set_random(struct playlist *playlist, struct player_control *pc, + bool status) { const struct song *queued; @@ -384,7 +393,7 @@ playlist_set_random(struct playlist *playlist, bool status) } else playlist_order(playlist); - playlist_update_queued_song(playlist, queued); + playlist_update_queued_song(playlist, pc, queued); idle_add(IDLE_OPTIONS); } diff --git a/src/playlist.h b/src/playlist.h index 3ba90ff91..a21bdf24a 100644 --- a/src/playlist.h +++ b/src/playlist.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,24 +21,11 @@ #define MPD_PLAYLIST_H #include "queue.h" +#include "playlist_error.h" #include <stdbool.h> -#define PLAYLIST_COMMENT '#' - -enum playlist_result { - PLAYLIST_RESULT_SUCCESS, - PLAYLIST_RESULT_ERRNO, - PLAYLIST_RESULT_DENIED, - PLAYLIST_RESULT_NO_SUCH_SONG, - PLAYLIST_RESULT_NO_SUCH_LIST, - PLAYLIST_RESULT_LIST_EXISTS, - PLAYLIST_RESULT_BAD_NAME, - PLAYLIST_RESULT_BAD_RANGE, - PLAYLIST_RESULT_NOT_PLAYING, - PLAYLIST_RESULT_TOO_LARGE, - PLAYLIST_RESULT_DISABLED, -}; +struct player_control; struct playlist { /** @@ -111,28 +98,28 @@ playlist_get_queue(const struct playlist *playlist) } void -playlist_clear(struct playlist *playlist); +playlist_clear(struct playlist *playlist, struct player_control *pc); -#ifndef WIN32 /** - * Appends a local file (outside the music database) to the playlist, - * but only if the file's owner is equal to the specified uid. + * Appends a local file (outside the music database) to the playlist. + * + * Note: the caller is responsible for checking permissions. */ enum playlist_result -playlist_append_file(struct playlist *playlist, const char *path, int uid, - unsigned *added_id); -#endif +playlist_append_file(struct playlist *playlist, struct player_control *pc, + const char *path_fs, unsigned *added_id); enum playlist_result -playlist_append_uri(struct playlist *playlist, const char *file, - unsigned *added_id); +playlist_append_uri(struct playlist *playlist, struct player_control *pc, + const char *file, unsigned *added_id); enum playlist_result -playlist_append_song(struct playlist *playlist, +playlist_append_song(struct playlist *playlist, struct player_control *pc, struct song *song, unsigned *added_id); enum playlist_result -playlist_delete(struct playlist *playlist, unsigned song); +playlist_delete(struct playlist *playlist, struct player_control *pc, + unsigned song); /** * Deletes a range of songs from the playlist. @@ -141,64 +128,86 @@ playlist_delete(struct playlist *playlist, unsigned song); * @param end the position after the last song to delete */ enum playlist_result -playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end); +playlist_delete_range(struct playlist *playlist, struct player_control *pc, + unsigned start, unsigned end); enum playlist_result -playlist_delete_id(struct playlist *playlist, unsigned song); +playlist_delete_id(struct playlist *playlist, struct player_control *pc, + unsigned song); void -playlist_stop(struct playlist *playlist); +playlist_stop(struct playlist *playlist, struct player_control *pc); enum playlist_result -playlist_play(struct playlist *playlist, int song); +playlist_play(struct playlist *playlist, struct player_control *pc, + int song); enum playlist_result -playlist_play_id(struct playlist *playlist, int song); +playlist_play_id(struct playlist *playlist, struct player_control *pc, + int song); void -playlist_next(struct playlist *playlist); +playlist_next(struct playlist *playlist, struct player_control *pc); void -playlist_sync(struct playlist *playlist); +playlist_sync(struct playlist *playlist, struct player_control *pc); void -playlist_previous(struct playlist *playlist); +playlist_previous(struct playlist *playlist, struct player_control *pc); void -playlist_shuffle(struct playlist *playlist, unsigned start, unsigned end); +playlist_shuffle(struct playlist *playlist, struct player_control *pc, + unsigned start, unsigned end); void -playlist_delete_song(struct playlist *playlist, const struct song *song); +playlist_delete_song(struct playlist *playlist, struct player_control *pc, + const struct song *song); + +enum playlist_result +playlist_move_range(struct playlist *playlist, struct player_control *pc, + unsigned start, unsigned end, int to); + +enum playlist_result +playlist_move_id(struct playlist *playlist, struct player_control *pc, + unsigned id, int to); enum playlist_result -playlist_move_range(struct playlist *playlist, unsigned start, unsigned end, int to); +playlist_swap_songs(struct playlist *playlist, struct player_control *pc, + unsigned song1, unsigned song2); enum playlist_result -playlist_move_id(struct playlist *playlist, unsigned id, int to); +playlist_swap_songs_id(struct playlist *playlist, struct player_control *pc, + unsigned id1, unsigned id2); enum playlist_result -playlist_swap_songs(struct playlist *playlist, unsigned song1, unsigned song2); +playlist_set_priority(struct playlist *playlist, struct player_control *pc, + unsigned start_position, unsigned end_position, + uint8_t priority); enum playlist_result -playlist_swap_songs_id(struct playlist *playlist, unsigned id1, unsigned id2); +playlist_set_priority_id(struct playlist *playlist, struct player_control *pc, + unsigned song_id, uint8_t priority); bool playlist_get_repeat(const struct playlist *playlist); void -playlist_set_repeat(struct playlist *playlist, bool status); +playlist_set_repeat(struct playlist *playlist, struct player_control *pc, + bool status); bool playlist_get_random(const struct playlist *playlist); void -playlist_set_random(struct playlist *playlist, bool status); +playlist_set_random(struct playlist *playlist, struct player_control *pc, + bool status); bool playlist_get_single(const struct playlist *playlist); void -playlist_set_single(struct playlist *playlist, bool status); +playlist_set_single(struct playlist *playlist, struct player_control *pc, + bool status); bool playlist_get_consume(const struct playlist *playlist); @@ -222,12 +231,25 @@ unsigned long playlist_get_version(const struct playlist *playlist); enum playlist_result -playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time); +playlist_seek_song(struct playlist *playlist, struct player_control *pc, + unsigned song, float seek_time); enum playlist_result -playlist_seek_song_id(struct playlist *playlist, +playlist_seek_song_id(struct playlist *playlist, struct player_control *pc, unsigned id, float seek_time); +/** + * Seek within the current song. Fails if MPD is not currently + * playing. + * + * @param time the time in seconds + * @param relative if true, then the specified time is relative to the + * current position + */ +enum playlist_result +playlist_seek_current(struct playlist *playlist, struct player_control *pc, + float seek_time, bool relative); + void playlist_increment_version_all(struct playlist *playlist); diff --git a/src/playlist/asx_playlist_plugin.c b/src/playlist/asx_playlist_plugin.c index 39513e710..298687859 100644 --- a/src/playlist/asx_playlist_plugin.c +++ b/src/playlist/asx_playlist_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -233,7 +233,8 @@ asx_open_stream(struct input_stream *is) &parser, asx_parser_destroy); while (true) { - nbytes = input_stream_read(is, buffer, sizeof(buffer), &error); + nbytes = input_stream_lock_read(is, buffer, sizeof(buffer), + &error); if (nbytes == 0) { if (error != NULL) { g_markup_parse_context_free(context); diff --git a/src/playlist/asx_playlist_plugin.h b/src/playlist/asx_playlist_plugin.h index 7ce91aa41..6c01c1209 100644 --- a/src/playlist/asx_playlist_plugin.h +++ b/src/playlist/asx_playlist_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/cue_playlist_plugin.c b/src/playlist/cue_playlist_plugin.c index b22712bc7..b85de77d3 100644 --- a/src/playlist/cue_playlist_plugin.c +++ b/src/playlist/cue_playlist_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,10 +22,11 @@ #include "playlist_plugin.h" #include "tag.h" #include "song.h" -#include "cue/cue_tag.h" +#include "cue/cue_parser.h" +#include "input_stream.h" +#include "text_input_stream.h" #include <glib.h> -#include <libcue/libcue.h> #include <assert.h> #include <string.h> @@ -35,31 +36,21 @@ struct cue_playlist { struct playlist_provider base; - struct Cd *cd; - - unsigned next; + struct input_stream *is; + struct text_input_stream *tis; + struct cue_parser *parser; }; static struct playlist_provider * -cue_playlist_open_uri(const char *uri) +cue_playlist_open_stream(struct input_stream *is) { - struct cue_playlist *playlist; - FILE *file; - struct Cd *cd; - - file = fopen(uri, "rt"); - if (file == NULL) - return NULL; + struct cue_playlist *playlist = g_new(struct cue_playlist, 1); + playlist_provider_init(&playlist->base, &cue_playlist_plugin); - cd = cue_parse_file(file); - fclose(file); - if (cd == NULL) - return NULL; + playlist->is = is; + playlist->tis = text_input_stream_new(is); + playlist->parser = cue_parser_new(); - playlist = g_new(struct cue_playlist, 1); - playlist_provider_init(&playlist->base, &cue_playlist_plugin); - playlist->cd = cd; - playlist->next = 1; return &playlist->base; } @@ -69,7 +60,8 @@ cue_playlist_close(struct playlist_provider *_playlist) { struct cue_playlist *playlist = (struct cue_playlist *)_playlist; - cd_delete(playlist->cd); + cue_parser_free(playlist->parser); + text_input_stream_free(playlist->tis); g_free(playlist); } @@ -77,45 +69,21 @@ 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; + + struct song *song = cue_parser_get(playlist->parser); + if (song != NULL) + return song; + + const char *line; + while ((line = text_input_stream_read(playlist->tis)) != NULL) { + cue_parser_feed(playlist->parser, line); + song = cue_parser_get(playlist->parser); + if (song != NULL) + return song; } - 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; + cue_parser_finish(playlist->parser); + return cue_parser_get(playlist->parser); } static const char *const cue_playlist_suffixes[] = { @@ -131,7 +99,7 @@ static const char *const cue_playlist_mime_types[] = { const struct playlist_plugin cue_playlist_plugin = { .name = "cue", - .open_uri = cue_playlist_open_uri, + .open_stream = cue_playlist_open_stream, .close = cue_playlist_close, .read = cue_playlist_read, diff --git a/src/playlist/cue_playlist_plugin.h b/src/playlist/cue_playlist_plugin.h index c89ec55c5..c02e2235a 100644 --- a/src/playlist/cue_playlist_plugin.h +++ b/src/playlist/cue_playlist_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/despotify_playlist_plugin.c b/src/playlist/despotify_playlist_plugin.c new file mode 100644 index 000000000..08a32d79d --- /dev/null +++ b/src/playlist/despotify_playlist_plugin.c @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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/despotify_playlist_plugin.h" +#include "playlist_plugin.h" +#include "playlist_list.h" +#include "conf.h" +#include "uri.h" +#include "tag.h" +#include "song.h" +#include "input_stream.h" +#include "glib_compat.h" +#include "despotify_utils.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> +#include <stdlib.h> +#include <despotify.h> + +struct despotify_playlist { + struct playlist_provider base; + + struct despotify_session *session; + GSList *list; +}; + +static void +add_song(struct despotify_playlist *ctx, struct ds_track *track) +{ + const char *dsp_scheme = despotify_playlist_plugin.schemes[0]; + struct song *song; + char uri[128]; + char *ds_uri; + + /* Create a spt://... URI for MPD */ + g_snprintf(uri, sizeof(uri), "%s://", dsp_scheme); + ds_uri = uri + strlen(dsp_scheme) + 3; + + if (despotify_track_to_uri(track, ds_uri) != ds_uri) { + /* Should never really fail, but let's be sure */ + g_debug("Can't add track %s\n", track->title); + return; + } + + song = song_remote_new(uri); + song->tag = mpd_despotify_tag_from_track(track); + + ctx->list = g_slist_prepend(ctx->list, song); +} + +static bool +parse_track(struct despotify_playlist *ctx, + struct ds_link *link) +{ + struct ds_track *track; + + track = despotify_link_get_track(ctx->session, link); + if (!track) + return false; + add_song(ctx, track); + + return true; +} + +static bool +parse_playlist(struct despotify_playlist *ctx, + struct ds_link *link) +{ + struct ds_playlist *playlist; + struct ds_track *track; + + playlist = despotify_link_get_playlist(ctx->session, link); + if (!playlist) + return false; + + for (track = playlist->tracks; track; track = track->next) + add_song(ctx, track); + + return true; +} + +static bool +despotify_playlist_init(G_GNUC_UNUSED const struct config_param *param) +{ + return true; +} + +static void +despotify_playlist_finish(void) +{ +} + + +static struct playlist_provider * +despotify_playlist_open_uri(const char *url, G_GNUC_UNUSED GMutex *mutex, + G_GNUC_UNUSED GCond *cond) +{ + struct despotify_playlist *ctx; + struct despotify_session *session; + struct ds_link *link; + bool parse_result; + + session = mpd_despotify_get_session(); + if (!session) + goto clean_none; + + /* Get link without spt:// */ + link = despotify_link_from_uri(url + strlen(despotify_playlist_plugin.schemes[0]) + 3); + if (!link) { + g_debug("Can't find %s\n", url); + goto clean_none; + } + + ctx = g_new(struct despotify_playlist, 1); + + ctx->list = NULL; + ctx->session = session; + playlist_provider_init(&ctx->base, &despotify_playlist_plugin); + + switch (link->type) + { + case LINK_TYPE_TRACK: + parse_result = parse_track(ctx, link); + break; + case LINK_TYPE_PLAYLIST: + parse_result = parse_playlist(ctx, link); + break; + default: + parse_result = false; + break; + } + despotify_free_link(link); + if (!parse_result) + goto clean_playlist; + + ctx->list = g_slist_reverse(ctx->list); + + return &ctx->base; + +clean_playlist: + g_slist_free(ctx->list); +clean_none: + + return NULL; +} + +static void +track_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) +{ + struct song *song = (struct song *)data; + + song_free(song); +} + +static void +despotify_playlist_close(struct playlist_provider *_playlist) +{ + struct despotify_playlist *ctx = (struct despotify_playlist *)_playlist; + + g_slist_foreach(ctx->list, track_free_callback, NULL); + g_slist_free(ctx->list); + + g_free(ctx); +} + + +static struct song * +despotify_playlist_read(struct playlist_provider *_playlist) +{ + struct despotify_playlist *ctx = (struct despotify_playlist *)_playlist; + struct song *out; + + if (!ctx->list) + return NULL; + + /* Remove the current track */ + out = ctx->list->data; + ctx->list = g_slist_remove(ctx->list, out); + + return out; +} + + +static const char *const despotify_schemes[] = { + "spt", + NULL +}; + +const struct playlist_plugin despotify_playlist_plugin = { + .name = "despotify", + + .init = despotify_playlist_init, + .finish = despotify_playlist_finish, + .open_uri = despotify_playlist_open_uri, + .read = despotify_playlist_read, + .close = despotify_playlist_close, + + .schemes = despotify_schemes, +}; diff --git a/src/playlist/despotify_playlist_plugin.h b/src/playlist/despotify_playlist_plugin.h new file mode 100644 index 000000000..f8ee20de0 --- /dev/null +++ b/src/playlist/despotify_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_DESPOTIFY_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin despotify_playlist_plugin; + +#endif diff --git a/src/playlist/embcue_playlist_plugin.c b/src/playlist/embcue_playlist_plugin.c new file mode 100644 index 000000000..6d9a957f9 --- /dev/null +++ b/src/playlist/embcue_playlist_plugin.c @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * + * Playlist plugin that reads embedded cue sheets from the "CUESHEET" + * tag of a music file. + */ + +#include "config.h" +#include "playlist/embcue_playlist_plugin.h" +#include "playlist_plugin.h" +#include "tag.h" +#include "tag_handler.h" +#include "tag_file.h" +#include "tag_ape.h" +#include "tag_id3.h" +#include "song.h" +#include "cue/cue_parser.h" + +#include <glib.h> +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cue" + +struct embcue_playlist { + struct playlist_provider base; + + /** + * This is an override for the CUE's "FILE". An embedded CUE + * sheet must always point to the song file it is contained + * in. + */ + char *filename; + + /** + * The value of the file's "CUESHEET" tag. + */ + char *cuesheet; + + /** + * The offset of the next line within "cuesheet". + */ + char *next; + + struct cue_parser *parser; +}; + +static void +embcue_tag_pair(const char *name, const char *value, void *ctx) +{ + struct embcue_playlist *playlist = ctx; + + if (playlist->cuesheet == NULL && + g_ascii_strcasecmp(name, "cuesheet") == 0) + playlist->cuesheet = g_strdup(value); +} + +static const struct tag_handler embcue_tag_handler = { + .pair = embcue_tag_pair, +}; + +static struct playlist_provider * +embcue_playlist_open_uri(const char *uri, + G_GNUC_UNUSED GMutex *mutex, + G_GNUC_UNUSED GCond *cond) +{ + if (!g_path_is_absolute(uri)) + /* only local files supported */ + return NULL; + + struct embcue_playlist *playlist = g_new(struct embcue_playlist, 1); + playlist_provider_init(&playlist->base, &embcue_playlist_plugin); + playlist->cuesheet = NULL; + + tag_file_scan(uri, &embcue_tag_handler, playlist); + if (playlist->cuesheet == NULL) { + tag_ape_scan2(uri, &embcue_tag_handler, playlist); + if (playlist->cuesheet == NULL) + tag_id3_scan(uri, &embcue_tag_handler, playlist); + } + + if (playlist->cuesheet == NULL) { + /* no "CUESHEET" tag found */ + g_free(playlist); + return NULL; + } + + playlist->filename = g_path_get_basename(uri); + + playlist->next = playlist->cuesheet; + playlist->parser = cue_parser_new(); + + return &playlist->base; +} + +static void +embcue_playlist_close(struct playlist_provider *_playlist) +{ + struct embcue_playlist *playlist = (struct embcue_playlist *)_playlist; + + cue_parser_free(playlist->parser); + g_free(playlist->cuesheet); + g_free(playlist->filename); + g_free(playlist); +} + +static struct song * +embcue_playlist_read(struct playlist_provider *_playlist) +{ + struct embcue_playlist *playlist = (struct embcue_playlist *)_playlist; + + struct song *song = cue_parser_get(playlist->parser); + if (song != NULL) + return song; + + while (*playlist->next != 0) { + const char *line = playlist->next; + char *eol = strpbrk(playlist->next, "\r\n"); + if (eol != NULL) { + /* null-terminate the line */ + *eol = 0; + playlist->next = eol + 1; + } else + /* last line; put the "next" pointer to the + end of the buffer */ + playlist->next += strlen(line); + + cue_parser_feed(playlist->parser, line); + song = cue_parser_get(playlist->parser); + if (song != NULL) + return song_replace_uri(song, playlist->filename); + } + + cue_parser_finish(playlist->parser); + song = cue_parser_get(playlist->parser); + if (song != NULL) + song = song_replace_uri(song, playlist->filename); + return song; +} + +static const char *const embcue_playlist_suffixes[] = { + /* a few codecs that are known to be supported; there are + probably many more */ + "flac", + "mp3", "mp2", + "mp4", "mp4a", "m4b", + "ape", + "wv", + "ogg", "oga", + NULL +}; + +const struct playlist_plugin embcue_playlist_plugin = { + .name = "cue", + + .open_uri = embcue_playlist_open_uri, + .close = embcue_playlist_close, + .read = embcue_playlist_read, + + .suffixes = embcue_playlist_suffixes, + .mime_types = NULL, +}; diff --git a/src/playlist/embcue_playlist_plugin.h b/src/playlist/embcue_playlist_plugin.h new file mode 100644 index 000000000..c5f21b27e --- /dev/null +++ b/src/playlist/embcue_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_EMBCUE_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_EMBCUE_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin embcue_playlist_plugin; + +#endif diff --git a/src/playlist/extm3u_playlist_plugin.c b/src/playlist/extm3u_playlist_plugin.c index 9a04aa066..19be8d1c4 100644 --- a/src/playlist/extm3u_playlist_plugin.c +++ b/src/playlist/extm3u_playlist_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,6 +24,7 @@ #include "uri.h" #include "song.h" #include "tag.h" +#include "string_util.h" #include <glib.h> @@ -89,7 +90,7 @@ extm3u_parse_tag(const char *line) /* 0 means unknown duration */ duration = 0; - name = g_strchug(endptr + 1); + name = strchug_fast_c(endptr + 1); if (*name == 0 && duration == 0) /* no information available; don't allocate a tag object */ diff --git a/src/playlist/extm3u_playlist_plugin.h b/src/playlist/extm3u_playlist_plugin.h index fa726c5f6..5f611ac9c 100644 --- a/src/playlist/extm3u_playlist_plugin.h +++ b/src/playlist/extm3u_playlist_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/flac_playlist_plugin.c b/src/playlist/flac_playlist_plugin.c deleted file mode 100644 index 9d66fb331..000000000 --- a/src/playlist/flac_playlist_plugin.c +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (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/lastfm_playlist_plugin.c b/src/playlist/lastfm_playlist_plugin.c index afb3979d9..86113643c 100644 --- a/src/playlist/lastfm_playlist_plugin.c +++ b/src/playlist/lastfm_playlist_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -83,15 +83,14 @@ lastfm_finish(void) * @return data fetched, or NULL on error. Must be freed with g_free. */ static char * -lastfm_get(const char *url) +lastfm_get(const char *url, GMutex *mutex, GCond *cond) { 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); + input_stream = input_stream_open(url, mutex, cond, &error); if (input_stream == NULL) { if (error != NULL) { g_warning("%s", error->message); @@ -101,15 +100,9 @@ lastfm_get(const char *url) 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; - } - } + g_mutex_lock(mutex); + + input_stream_wait_ready(input_stream); do { nbytes = input_stream_read(input_stream, buffer + length, @@ -124,6 +117,7 @@ lastfm_get(const char *url) break; /* I/O error */ + g_mutex_unlock(mutex); input_stream_close(input_stream); return NULL; } @@ -131,6 +125,8 @@ lastfm_get(const char *url) length += nbytes; } while (length < sizeof(buffer)); + g_mutex_unlock(mutex); + input_stream_close(input_stream); return g_strndup(buffer, length); } @@ -139,7 +135,7 @@ lastfm_get(const char *url) * 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. + * @return value for param name in param response or NULL on error. Free with g_free. */ static char * lastfm_find(const char *response, const char *name) @@ -162,7 +158,7 @@ lastfm_find(const char *response, const char *name) } static struct playlist_provider * -lastfm_open_uri(const char *uri) +lastfm_open_uri(const char *uri, GMutex *mutex, GCond *cond) { struct lastfm_playlist *playlist; GError *error = NULL; @@ -175,7 +171,7 @@ lastfm_open_uri(const char *uri) "username=", lastfm_config.user, "&" "passwordmd5=", lastfm_config.md5, "&" "debug=0&partner=", NULL); - response = lastfm_get(p); + response = lastfm_get(p, mutex, cond); g_free(p); if (response == NULL) return NULL; @@ -207,7 +203,7 @@ lastfm_open_uri(const char *uri) NULL); g_free(escaped_uri); - response = lastfm_get(p); + response = lastfm_get(p, mutex, cond); g_free(response); g_free(p); @@ -229,7 +225,7 @@ lastfm_open_uri(const char *uri) NULL); g_free(session); - playlist->is = input_stream_open(p, &error); + playlist->is = input_stream_open(p, mutex, cond, &error); g_free(p); if (playlist->is == NULL) { @@ -243,26 +239,17 @@ lastfm_open_uri(const char *uri) 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; - } + g_mutex_lock(mutex); - if (ret == 0) - /* nothing was buffered - wait */ - g_usleep(10000); - } + input_stream_wait_ready(playlist->is); /* 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"); + g_mutex_unlock(mutex); + /* parse the XSPF playlist */ playlist->xspf = playlist_list_open_stream(playlist->is, NULL); diff --git a/src/playlist/lastfm_playlist_plugin.h b/src/playlist/lastfm_playlist_plugin.h index 363377c21..46a8b0caf 100644 --- a/src/playlist/lastfm_playlist_plugin.h +++ b/src/playlist/lastfm_playlist_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/m3u_playlist_plugin.c b/src/playlist/m3u_playlist_plugin.c index 221c27277..45b70d2b1 100644 --- a/src/playlist/m3u_playlist_plugin.c +++ b/src/playlist/m3u_playlist_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/m3u_playlist_plugin.h b/src/playlist/m3u_playlist_plugin.h index 98dcc4729..3890a5fc2 100644 --- a/src/playlist/m3u_playlist_plugin.h +++ b/src/playlist/m3u_playlist_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/pls_playlist_plugin.c b/src/playlist/pls_playlist_plugin.c index 2a36f12f5..c4e5492af 100644 --- a/src/playlist/pls_playlist_plugin.c +++ b/src/playlist/pls_playlist_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -115,7 +115,8 @@ pls_open_stream(struct input_stream *is) GString *kf_data = g_string_new(""); do { - nbytes = input_stream_read(is, buffer, sizeof(buffer), &error); + nbytes = input_stream_lock_read(is, buffer, sizeof(buffer), + &error); if (nbytes == 0) { if (error != NULL) { g_string_free(kf_data, TRUE); diff --git a/src/playlist/pls_playlist_plugin.h b/src/playlist/pls_playlist_plugin.h index c3bcf3f05..d03435f6d 100644 --- a/src/playlist/pls_playlist_plugin.h +++ b/src/playlist/pls_playlist_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/rss_playlist_plugin.c b/src/playlist/rss_playlist_plugin.c index b5787bb68..6740cba7e 100644 --- a/src/playlist/rss_playlist_plugin.c +++ b/src/playlist/rss_playlist_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -231,7 +231,8 @@ rss_open_stream(struct input_stream *is) &parser, rss_parser_destroy); while (true) { - nbytes = input_stream_read(is, buffer, sizeof(buffer), &error); + nbytes = input_stream_lock_read(is, buffer, sizeof(buffer), + &error); if (nbytes == 0) { if (error != NULL) { g_markup_parse_context_free(context); diff --git a/src/playlist/rss_playlist_plugin.h b/src/playlist/rss_playlist_plugin.h index d8992f2e5..3b376de79 100644 --- a/src/playlist/rss_playlist_plugin.h +++ b/src/playlist/rss_playlist_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/soundcloud_playlist_plugin.c b/src/playlist/soundcloud_playlist_plugin.c new file mode 100644 index 000000000..7c79f880a --- /dev/null +++ b/src/playlist/soundcloud_playlist_plugin.c @@ -0,0 +1,448 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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/soundcloud_playlist_plugin.h" +#include "conf.h" +#include "input_stream.h" +#include "playlist_plugin.h" +#include "song.h" +#include "tag.h" + +#include <glib.h> +#include <yajl/yajl_parse.h> + +#include <string.h> + +struct soundcloud_playlist { + struct playlist_provider base; + + GSList *songs; +}; + +static struct { + char *apikey; +} soundcloud_config; + +static bool +soundcloud_init(const struct config_param *param) +{ + soundcloud_config.apikey = + config_dup_block_string(param, "apikey", NULL); + if (soundcloud_config.apikey == NULL) { + g_debug("disabling the soundcloud playlist plugin " + "because API key is not set"); + return false; + } + + return true; +} + +static void +soundcloud_finish(void) +{ + g_free(soundcloud_config.apikey); +} + +/** + * Construct a full soundcloud resolver URL from the given fragment. + * @param uri uri of a soundcloud page (or just the path) + * @return Constructed URL. Must be freed with g_free. + */ +static char * +soundcloud_resolve(const char* uri) { + char *u, *ru; + + if (g_str_has_prefix(uri, "http://")) { + u = g_strdup(uri); + } else if (g_str_has_prefix(uri, "soundcloud.com")) { + u = g_strconcat("http://", uri, NULL); + } else { + /* assume it's just a path on soundcloud.com */ + u = g_strconcat("http://soundcloud.com/", uri, NULL); + } + + ru = g_strconcat("http://api.soundcloud.com/resolve.json?url=", + u, "&client_id=", soundcloud_config.apikey, NULL); + g_free(u); + + return ru; +} + +/* YAJL parser for track data from both /tracks/ and /playlists/ JSON */ + +enum key { + Duration, + Title, + Stream_URL, + Other, +}; + +const char* key_str[] = { + "duration", + "title", + "stream_url", + NULL, +}; + +struct parse_data { + int key; + char* stream_url; + long duration; + char* title; + int got_url; /* nesting level of last stream_url */ + GSList* songs; +}; + +static int handle_integer(void *ctx, + long +#ifndef HAVE_YAJL1 + long +#endif + intval) +{ + struct parse_data *data = (struct parse_data *) ctx; + + switch (data->key) { + case Duration: + data->duration = intval; + break; + default: + break; + } + + return 1; +} + +static int handle_string(void *ctx, const unsigned char* stringval, +#ifdef HAVE_YAJL1 + unsigned int +#else + size_t +#endif + stringlen) +{ + struct parse_data *data = (struct parse_data *) ctx; + const char *s = (const char *) stringval; + + switch (data->key) { + case Title: + if (data->title != NULL) + g_free(data->title); + data->title = g_strndup(s, stringlen); + break; + case Stream_URL: + if (data->stream_url != NULL) + g_free(data->stream_url); + data->stream_url = g_strndup(s, stringlen); + data->got_url = 1; + break; + default: + break; + } + + return 1; +} + +static int handle_mapkey(void *ctx, const unsigned char* stringval, +#ifdef HAVE_YAJL1 + unsigned int +#else + size_t +#endif + stringlen) +{ + struct parse_data *data = (struct parse_data *) ctx; + + int i; + data->key = Other; + + for (i = 0; i < Other; ++i) { + if (strncmp((const char *)stringval, key_str[i], stringlen) == 0) { + data->key = i; + break; + } + } + + return 1; +} + +static int handle_start_map(void *ctx) +{ + struct parse_data *data = (struct parse_data *) ctx; + + if (data->got_url > 0) + data->got_url++; + + return 1; +} + +static int handle_end_map(void *ctx) +{ + struct parse_data *data = (struct parse_data *) ctx; + + if (data->got_url > 1) { + data->got_url--; + return 1; + } + + if (data->got_url == 0) + return 1; + + /* got_url == 1, track finished, make it into a song */ + data->got_url = 0; + + struct song *s; + struct tag *t; + char *u; + + u = g_strconcat(data->stream_url, "?client_id=", soundcloud_config.apikey, NULL); + s = song_remote_new(u); + g_free(u); + t = tag_new(); + t->time = data->duration / 1000; + if (data->title != NULL) + tag_add_item(t, TAG_NAME, data->title); + s->tag = t; + + data->songs = g_slist_prepend(data->songs, s); + + return 1; +} + +static yajl_callbacks parse_callbacks = { + NULL, + NULL, + handle_integer, + NULL, + NULL, + handle_string, + handle_start_map, + handle_mapkey, + handle_end_map, + NULL, + NULL, +}; + +/** + * Read JSON data and parse it using the given YAJL parser. + * @param url URL of the JSON data. + * @param hand YAJL parser handle. + * @return -1 on error, 0 on success. + */ +static int +soundcloud_parse_json(const char *url, yajl_handle hand, GMutex* mutex, GCond* cond) +{ + struct input_stream *input_stream; + GError *error = NULL; + char buffer[4096]; + unsigned char *ubuffer = (unsigned char *)buffer; + size_t nbytes; + + input_stream = input_stream_open(url, mutex, cond, &error); + if (input_stream == NULL) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + return -1; + } + + g_mutex_lock(mutex); + input_stream_wait_ready(input_stream); + + yajl_status stat; + int done = 0; + + while (!done) { + nbytes = input_stream_read(input_stream, buffer, sizeof(buffer), &error); + if (nbytes == 0) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + if (input_stream_eof(input_stream)) { + done = true; + } else { + g_mutex_unlock(mutex); + input_stream_close(input_stream); + return -1; + } + } + + if (done) { +#ifdef HAVE_YAJL1 + stat = yajl_parse_complete(hand); +#else + stat = yajl_complete_parse(hand); +#endif + } else + stat = yajl_parse(hand, ubuffer, nbytes); + + if (stat != yajl_status_ok +#ifdef HAVE_YAJL1 + && stat != yajl_status_insufficient_data +#endif + ) + { + unsigned char *str = yajl_get_error(hand, 1, ubuffer, nbytes); + g_warning("%s", str); + yajl_free_error(hand, str); + break; + } + } + + g_mutex_unlock(mutex); + input_stream_close(input_stream); + + return 0; +} + +/** + * Parse a soundcloud:// URL and create a playlist. + * @param uri A soundcloud URL. Accepted forms: + * soundcloud://track/<track-id> + * soundcloud://playlist/<playlist-id> + * soundcloud://url/<url or path of soundcloud page> + */ + +static struct playlist_provider * +soundcloud_open_uri(const char *uri, GMutex *mutex, GCond *cond) +{ + struct soundcloud_playlist *playlist = NULL; + + char *s, *p; + char *scheme, *arg, *rest; + s = g_strdup(uri); + scheme = s; + for (p = s; *p; p++) { + if (*p == ':' && *(p+1) == '/' && *(p+2) == '/') { + *p = 0; + p += 3; + break; + } + } + arg = p; + for (; *p; p++) { + if (*p == '/') { + *p = 0; + p++; + break; + } + } + rest = p; + + if (strcmp(scheme, "soundcloud") != 0) { + g_warning("incompatible scheme for soundcloud plugin: %s", scheme); + g_free(s); + return NULL; + } + + char *u = NULL; + if (strcmp(arg, "track") == 0) { + u = g_strconcat("http://api.soundcloud.com/tracks/", + rest, ".json?client_id=", soundcloud_config.apikey, NULL); + } else if (strcmp(arg, "playlist") == 0) { + u = g_strconcat("http://api.soundcloud.com/playlists/", + rest, ".json?client_id=", soundcloud_config.apikey, NULL); + } else if (strcmp(arg, "url") == 0) { + /* Translate to soundcloud resolver call. libcurl will automatically + follow the redirect to the right resource. */ + u = soundcloud_resolve(rest); + } + g_free(s); + + if (u == NULL) { + g_warning("unknown soundcloud URI"); + return NULL; + } + + yajl_handle hand; + struct parse_data data; + + data.got_url = 0; + data.songs = NULL; + data.title = NULL; + data.stream_url = NULL; +#ifdef HAVE_YAJL1 + hand = yajl_alloc(&parse_callbacks, NULL, NULL, (void *) &data); +#else + hand = yajl_alloc(&parse_callbacks, NULL, (void *) &data); +#endif + + int ret = soundcloud_parse_json(u, hand, mutex, cond); + + g_free(u); + yajl_free(hand); + if (data.title != NULL) + g_free(data.title); + if (data.stream_url != NULL) + g_free(data.stream_url); + + if (ret == -1) + return NULL; + + playlist = g_new(struct soundcloud_playlist, 1); + playlist_provider_init(&playlist->base, &soundcloud_playlist_plugin); + playlist->songs = g_slist_reverse(data.songs); + + return &playlist->base; +} + +static void +soundcloud_close(struct playlist_provider *_playlist) +{ + struct soundcloud_playlist *playlist = (struct soundcloud_playlist *)_playlist; + + g_free(playlist); +} + + +static struct song * +soundcloud_read(struct playlist_provider *_playlist) +{ + struct soundcloud_playlist *playlist = (struct soundcloud_playlist *)_playlist; + + if (playlist->songs == NULL) + return NULL; + + struct song* s; + s = (struct song *)playlist->songs->data; + playlist->songs = g_slist_remove(playlist->songs, s); + return s; +} + +static const char *const soundcloud_schemes[] = { + "soundcloud", + NULL +}; + +const struct playlist_plugin soundcloud_playlist_plugin = { + .name = "soundcloud", + + .init = soundcloud_init, + .finish = soundcloud_finish, + .open_uri = soundcloud_open_uri, + .close = soundcloud_close, + .read = soundcloud_read, + + .schemes = soundcloud_schemes, +}; + + diff --git a/src/playlist/soundcloud_playlist_plugin.h b/src/playlist/soundcloud_playlist_plugin.h new file mode 100644 index 000000000..e09e2dd46 --- /dev/null +++ b/src/playlist/soundcloud_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SOUNDCLOUD_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_SOUNDCLOUD_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin soundcloud_playlist_plugin; + +#endif diff --git a/src/playlist/xspf_playlist_plugin.c b/src/playlist/xspf_playlist_plugin.c index 50f6bd1e7..17d9040e2 100644 --- a/src/playlist/xspf_playlist_plugin.c +++ b/src/playlist/xspf_playlist_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -253,7 +253,8 @@ xspf_open_stream(struct input_stream *is) &parser, xspf_parser_destroy); while (true) { - nbytes = input_stream_read(is, buffer, sizeof(buffer), &error); + nbytes = input_stream_lock_read(is, buffer, sizeof(buffer), + &error); if (nbytes == 0) { if (error != NULL) { g_markup_parse_context_free(context); diff --git a/src/playlist/xspf_playlist_plugin.h b/src/playlist/xspf_playlist_plugin.h index ea832207d..4636d7e83 100644 --- a/src/playlist/xspf_playlist_plugin.h +++ b/src/playlist/xspf_playlist_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_any.c b/src/playlist_any.c index 39e21b178..450ca5932 100644 --- a/src/playlist_any.c +++ b/src/playlist_any.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,18 +27,20 @@ #include <assert.h> static struct playlist_provider * -playlist_open_remote(const char *uri, struct input_stream **is_r) +playlist_open_remote(const char *uri, GMutex *mutex, GCond *cond, + struct input_stream **is_r) { assert(uri_has_scheme(uri)); - struct playlist_provider *playlist = playlist_list_open_uri(uri); + struct playlist_provider *playlist = + playlist_list_open_uri(uri, mutex, cond); if (playlist != NULL) { *is_r = NULL; return playlist; } GError *error = NULL; - struct input_stream *is = input_stream_open(uri, &error); + struct input_stream *is = input_stream_open(uri, mutex, cond, &error); if (is == NULL) { if (error != NULL) { g_warning("Failed to open %s: %s", @@ -60,9 +62,10 @@ playlist_open_remote(const char *uri, struct input_stream **is_r) } struct playlist_provider * -playlist_open_any(const char *uri, struct input_stream **is_r) +playlist_open_any(const char *uri, GMutex *mutex, GCond *cond, + struct input_stream **is_r) { return uri_has_scheme(uri) - ? playlist_open_remote(uri, is_r) - : playlist_mapper_open(uri, is_r); + ? playlist_open_remote(uri, mutex, cond, is_r) + : playlist_mapper_open(uri, mutex, cond, is_r); } diff --git a/src/playlist_any.h b/src/playlist_any.h index 6fed97d15..310913de9 100644 --- a/src/playlist_any.h +++ b/src/playlist_any.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_PLAYLIST_ANY_H #define MPD_PLAYLIST_ANY_H -#include <stdbool.h> +#include <glib.h> struct playlist_provider; struct input_stream; @@ -35,6 +35,7 @@ struct input_stream; * freed */ struct playlist_provider * -playlist_open_any(const char *uri, struct input_stream **is_r); +playlist_open_any(const char *uri, GMutex *mutex, GCond *cond, + struct input_stream **is_r); #endif diff --git a/src/playlist_control.c b/src/playlist_control.c index 76066d274..0dea7676a 100644 --- a/src/playlist_control.c +++ b/src/playlist_control.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -32,7 +32,8 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "playlist" -void playlist_stop(struct playlist *playlist) +void +playlist_stop(struct playlist *playlist, struct player_control *pc) { if (!playlist->playing) return; @@ -40,7 +41,7 @@ void playlist_stop(struct playlist *playlist) assert(playlist->current >= 0); g_debug("stop"); - pc_stop(); + pc_stop(pc); playlist->queued = -1; playlist->playing = false; @@ -62,11 +63,13 @@ void playlist_stop(struct playlist *playlist) } } -enum playlist_result playlist_play(struct playlist *playlist, int song) +enum playlist_result +playlist_play(struct playlist *playlist, struct player_control *pc, + int song) { unsigned i = song; - pc_clear_error(); + pc_clear_error(pc); if (song == -1) { /* play any song ("current" song, or the first song */ @@ -77,7 +80,7 @@ enum playlist_result playlist_play(struct playlist *playlist, int song) if (playlist->playing) { /* already playing: unpause playback, just in case it was paused, and return */ - pc_set_pause(false); + pc_set_pause(pc, false); return PLAYLIST_RESULT_SUCCESS; } @@ -109,28 +112,29 @@ enum playlist_result playlist_play(struct playlist *playlist, int song) playlist->stop_on_error = false; playlist->error_count = 0; - playlist_play_order(playlist, i); + playlist_play_order(playlist, pc, i); return PLAYLIST_RESULT_SUCCESS; } enum playlist_result -playlist_play_id(struct playlist *playlist, int id) +playlist_play_id(struct playlist *playlist, struct player_control *pc, + int id) { int song; if (id == -1) { - return playlist_play(playlist, id); + return playlist_play(playlist, pc, id); } song = queue_id_to_position(&playlist->queue, id); if (song < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return playlist_play(playlist, song); + return playlist_play(playlist, pc, song); } void -playlist_next(struct playlist *playlist) +playlist_next(struct playlist *playlist, struct player_control *pc) { int next_order; int current; @@ -149,7 +153,7 @@ playlist_next(struct playlist *playlist) next_order = queue_next_order(&playlist->queue, playlist->current); if (next_order < 0) { /* no song after this one: stop playback */ - playlist_stop(playlist); + playlist_stop(playlist, pc); /* reset "current song" */ playlist->current = -1; @@ -170,15 +174,18 @@ playlist_next(struct playlist *playlist) discard them anyway */ } - playlist_play_order(playlist, next_order); + playlist_play_order(playlist, pc, next_order); } /* Consume mode removes each played songs. */ if(playlist->queue.consume) - playlist_delete(playlist, queue_order_to_position(&playlist->queue, current)); + playlist_delete(playlist, pc, + queue_order_to_position(&playlist->queue, + current)); } -void playlist_previous(struct playlist *playlist) +void +playlist_previous(struct playlist *playlist, struct player_control *pc) { if (!playlist->playing) return; @@ -187,21 +194,22 @@ void playlist_previous(struct playlist *playlist) if (playlist->current > 0) { /* play the preceding song */ - playlist_play_order(playlist, + playlist_play_order(playlist, pc, playlist->current - 1); } else if (playlist->queue.repeat) { /* play the last song in "repeat" mode */ - playlist_play_order(playlist, + playlist_play_order(playlist, pc, queue_length(&playlist->queue) - 1); } else { /* re-start playing the current song if it's the first one */ - playlist_play_order(playlist, playlist->current); + playlist_play_order(playlist, pc, playlist->current); } } enum playlist_result -playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time) +playlist_seek_song(struct playlist *playlist, struct player_control *pc, + unsigned song, float seek_time) { const struct song *queued; unsigned i; @@ -217,7 +225,7 @@ playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time) else i = song; - pc_clear_error(); + pc_clear_error(pc); playlist->stop_on_error = true; playlist->error_count = 0; @@ -231,25 +239,50 @@ playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time) queued = NULL; } - success = pc_seek(queue_get_order(&playlist->queue, i), seek_time); + success = pc_seek(pc, queue_get_order(&playlist->queue, i), seek_time); if (!success) { - playlist_update_queued_song(playlist, queued); + playlist_update_queued_song(playlist, pc, queued); return PLAYLIST_RESULT_NOT_PLAYING; } playlist->queued = -1; - playlist_update_queued_song(playlist, NULL); + playlist_update_queued_song(playlist, pc, NULL); return PLAYLIST_RESULT_SUCCESS; } enum playlist_result -playlist_seek_song_id(struct playlist *playlist, unsigned id, float seek_time) +playlist_seek_song_id(struct playlist *playlist, struct player_control *pc, + unsigned id, float seek_time) { int song = queue_id_to_position(&playlist->queue, id); if (song < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return playlist_seek_song(playlist, song, seek_time); + return playlist_seek_song(playlist, pc, song, seek_time); +} + +enum playlist_result +playlist_seek_current(struct playlist *playlist, struct player_control *pc, + float seek_time, bool relative) +{ + if (!playlist->playing) + return PLAYLIST_RESULT_NOT_PLAYING; + + if (relative) { + struct player_status status; + pc_get_status(pc, &status); + + if (status.state != PLAYER_STATE_PLAY && + status.state != PLAYER_STATE_PAUSE) + return PLAYLIST_RESULT_NOT_PLAYING; + + seek_time += (int)status.elapsed_time; + } + + if (seek_time < 0) + seek_time = 0; + + return playlist_seek_song(playlist, pc, playlist->current, seek_time); } diff --git a/src/playlist_database.c b/src/playlist_database.c index 0a8a6f139..6b9d87155 100644 --- a/src/playlist_database.c +++ b/src/playlist_database.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,6 +21,7 @@ #include "playlist_database.h" #include "playlist_vector.h" #include "text_file.h" +#include "string_util.h" #include <string.h> #include <stdlib.h> @@ -32,10 +33,10 @@ playlist_database_quark(void) } void -playlist_vector_save(FILE *fp, const struct playlist_vector *pv) +playlist_vector_save(FILE *fp, const struct list_head *pv) { - for (const struct playlist_metadata *pm = pv->head; - pm != NULL; pm = pm->next) + struct playlist_metadata *pm; + playlist_vector_for_each(pm, pv) fprintf(fp, PLAYLIST_META_BEGIN "%s\n" "mtime: %li\n" "playlist_end\n", @@ -43,7 +44,7 @@ playlist_vector_save(FILE *fp, const struct playlist_vector *pv) } bool -playlist_metadata_load(FILE *fp, struct playlist_vector *pv, const char *name, +playlist_metadata_load(FILE *fp, struct list_head *pv, const char *name, GString *buffer, GError **error_r) { struct playlist_metadata pm = { @@ -62,7 +63,7 @@ playlist_metadata_load(FILE *fp, struct playlist_vector *pv, const char *name, } *colon++ = 0; - value = g_strchug(colon); + value = strchug_fast_c(colon); if (strcmp(line, "mtime") == 0) pm.mtime = strtol(value, NULL, 10); diff --git a/src/playlist_database.h b/src/playlist_database.h index 7e114abdd..3238fa06b 100644 --- a/src/playlist_database.h +++ b/src/playlist_database.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,13 +28,13 @@ #define PLAYLIST_META_BEGIN "playlist_begin: " -struct playlist_vector; +struct list_head; void -playlist_vector_save(FILE *fp, const struct playlist_vector *pv); +playlist_vector_save(FILE *fp, const struct list_head *pv); bool -playlist_metadata_load(FILE *fp, struct playlist_vector *pv, const char *name, +playlist_metadata_load(FILE *fp, struct list_head *pv, const char *name, GString *buffer, GError **error_r); #endif diff --git a/src/playlist_edit.c b/src/playlist_edit.c index 3bcb2ce14..7adbccd7c 100644 --- a/src/playlist_edit.c +++ b/src/playlist_edit.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -31,9 +31,6 @@ #include "song.h" #include "idle.h" -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> #include <stdlib.h> static void playlist_increment_version(struct playlist *playlist) @@ -43,16 +40,17 @@ static void playlist_increment_version(struct playlist *playlist) idle_add(IDLE_PLAYLIST); } -void playlist_clear(struct playlist *playlist) +void +playlist_clear(struct playlist *playlist, struct player_control *pc) { - playlist_stop(playlist); + playlist_stop(playlist, pc); /* make sure there are no references to allocated songs anymore */ for (unsigned i = 0; i < queue_length(&playlist->queue); i++) { const struct song *song = queue_get(&playlist->queue, i); if (!song_in_database(song)) - pc_song_deleted(song); + pc_song_deleted(pc, song); } queue_clear(&playlist->queue); @@ -62,37 +60,19 @@ void playlist_clear(struct playlist *playlist) playlist_increment_version(playlist); } -#ifndef WIN32 enum playlist_result -playlist_append_file(struct playlist *playlist, const char *path, int uid, - unsigned *added_id) +playlist_append_file(struct playlist *playlist, struct player_control *pc, + const char *path_fs, unsigned *added_id) { - int ret; - struct stat st; - struct song *song; - - if (uid <= 0) - /* unauthenticated client */ - return PLAYLIST_RESULT_DENIED; - - ret = stat(path, &st); - if (ret < 0) - return PLAYLIST_RESULT_ERRNO; - - if (st.st_uid != (uid_t)uid && (st.st_mode & 0444) != 0444) - /* client is not owner */ - return PLAYLIST_RESULT_DENIED; - - song = song_file_load(path, NULL); + struct song *song = song_file_load(path_fs, NULL); if (song == NULL) return PLAYLIST_RESULT_NO_SUCH_SONG; - return playlist_append_song(playlist, song, added_id); + return playlist_append_song(playlist, pc, song, added_id); } -#endif enum playlist_result -playlist_append_song(struct playlist *playlist, +playlist_append_song(struct playlist *playlist, struct player_control *pc, struct song *song, unsigned *added_id) { const struct song *queued; @@ -121,7 +101,7 @@ playlist_append_song(struct playlist *playlist, playlist_increment_version(playlist); - playlist_update_queued_song(playlist, queued); + playlist_update_queued_song(playlist, pc, queued); if (added_id) *added_id = id; @@ -145,8 +125,8 @@ song_by_uri(const char *uri) } enum playlist_result -playlist_append_uri(struct playlist *playlist, const char *uri, - unsigned *added_id) +playlist_append_uri(struct playlist *playlist, struct player_control *pc, + const char *uri, unsigned *added_id) { struct song *song; @@ -156,11 +136,12 @@ playlist_append_uri(struct playlist *playlist, const char *uri, if (song == NULL) return PLAYLIST_RESULT_NO_SUCH_SONG; - return playlist_append_song(playlist, song, added_id); + return playlist_append_song(playlist, pc, song, added_id); } enum playlist_result -playlist_swap_songs(struct playlist *playlist, unsigned song1, unsigned song2) +playlist_swap_songs(struct playlist *playlist, struct player_control *pc, + unsigned song1, unsigned song2) { const struct song *queued; @@ -192,13 +173,14 @@ playlist_swap_songs(struct playlist *playlist, unsigned song1, unsigned song2) playlist_increment_version(playlist); - playlist_update_queued_song(playlist, queued); + playlist_update_queued_song(playlist, pc, queued); return PLAYLIST_RESULT_SUCCESS; } enum playlist_result -playlist_swap_songs_id(struct playlist *playlist, unsigned id1, unsigned id2) +playlist_swap_songs_id(struct playlist *playlist, struct player_control *pc, + unsigned id1, unsigned id2) { int song1 = queue_id_to_position(&playlist->queue, id1); int song2 = queue_id_to_position(&playlist->queue, id2); @@ -206,12 +188,67 @@ playlist_swap_songs_id(struct playlist *playlist, unsigned id1, unsigned id2) if (song1 < 0 || song2 < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return playlist_swap_songs(playlist, song1, song2); + return playlist_swap_songs(playlist, pc, song1, song2); +} + +enum playlist_result +playlist_set_priority(struct playlist *playlist, struct player_control *pc, + unsigned start, unsigned end, + uint8_t priority) +{ + 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; + + /* remember "current" and "queued" */ + + int current_position = playlist->current >= 0 + ? (int)queue_order_to_position(&playlist->queue, + playlist->current) + : -1; + + const struct song *queued = playlist_get_queued_song(playlist); + + /* apply the priority changes */ + + queue_set_priority_range(&playlist->queue, start, end, priority, + playlist->current); + + playlist_increment_version(playlist); + + /* restore "current" and choose a new "queued" */ + + if (current_position >= 0) + playlist->current = queue_position_to_order(&playlist->queue, + current_position); + + playlist_update_queued_song(playlist, pc, queued); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist_set_priority_id(struct playlist *playlist, struct player_control *pc, + unsigned song_id, uint8_t priority) +{ + int song_position = queue_id_to_position(&playlist->queue, song_id); + if (song_position < 0) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return playlist_set_priority(playlist, pc, + song_position, song_position + 1, + priority); + } static void -playlist_delete_internal(struct playlist *playlist, unsigned song, - const struct song **queued_p) +playlist_delete_internal(struct playlist *playlist, struct player_control *pc, + unsigned song, const struct song **queued_p) { unsigned songOrder; @@ -220,11 +257,11 @@ playlist_delete_internal(struct playlist *playlist, unsigned song, songOrder = queue_position_to_order(&playlist->queue, song); if (playlist->playing && playlist->current == (int)songOrder) { - bool paused = pc_get_state() == PLAYER_STATE_PAUSE; + bool paused = pc_get_state(pc) == PLAYER_STATE_PAUSE; /* the current song is going to be deleted: stop the player */ - pc_stop(); + pc_stop(pc); playlist->playing = false; /* see which song is going to be played instead */ @@ -236,11 +273,11 @@ playlist_delete_internal(struct playlist *playlist, unsigned song, if (playlist->current >= 0 && !paused) /* play the song after the deleted one */ - playlist_play_order(playlist, playlist->current); + playlist_play_order(playlist, pc, playlist->current); else /* no songs left to play, stop playback completely */ - playlist_stop(playlist); + playlist_stop(playlist, pc); *queued_p = NULL; } else if (playlist->current == (int)songOrder) @@ -251,7 +288,7 @@ playlist_delete_internal(struct playlist *playlist, unsigned song, /* now do it: remove the song */ if (!song_in_database(queue_get(&playlist->queue, song))) - pc_song_deleted(queue_get(&playlist->queue, song)); + pc_song_deleted(pc, queue_get(&playlist->queue, song)); queue_delete(&playlist->queue, song); @@ -263,7 +300,8 @@ playlist_delete_internal(struct playlist *playlist, unsigned song, } enum playlist_result -playlist_delete(struct playlist *playlist, unsigned song) +playlist_delete(struct playlist *playlist, struct player_control *pc, + unsigned song) { const struct song *queued; @@ -272,16 +310,17 @@ playlist_delete(struct playlist *playlist, unsigned song) queued = playlist_get_queued_song(playlist); - playlist_delete_internal(playlist, song, &queued); + playlist_delete_internal(playlist, pc, song, &queued); playlist_increment_version(playlist); - playlist_update_queued_song(playlist, queued); + playlist_update_queued_song(playlist, pc, queued); return PLAYLIST_RESULT_SUCCESS; } enum playlist_result -playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end) +playlist_delete_range(struct playlist *playlist, struct player_control *pc, + unsigned start, unsigned end) { const struct song *queued; @@ -297,37 +336,39 @@ playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end) queued = playlist_get_queued_song(playlist); do { - playlist_delete_internal(playlist, --end, &queued); + playlist_delete_internal(playlist, pc, --end, &queued); } while (end != start); playlist_increment_version(playlist); - playlist_update_queued_song(playlist, queued); + playlist_update_queued_song(playlist, pc, queued); return PLAYLIST_RESULT_SUCCESS; } enum playlist_result -playlist_delete_id(struct playlist *playlist, unsigned id) +playlist_delete_id(struct playlist *playlist, struct player_control *pc, + unsigned id) { int song = queue_id_to_position(&playlist->queue, id); if (song < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return playlist_delete(playlist, song); + return playlist_delete(playlist, pc, song); } void -playlist_delete_song(struct playlist *playlist, const struct song *song) +playlist_delete_song(struct playlist *playlist, struct player_control *pc, + const struct song *song) { for (int i = queue_length(&playlist->queue) - 1; i >= 0; --i) if (song == queue_get(&playlist->queue, i)) - playlist_delete(playlist, i); + playlist_delete(playlist, pc, i); - pc_song_deleted(song); + pc_song_deleted(pc, song); } enum playlist_result -playlist_move_range(struct playlist *playlist, +playlist_move_range(struct playlist *playlist, struct player_control *pc, unsigned start, unsigned end, int to) { const struct song *queued; @@ -382,23 +423,25 @@ playlist_move_range(struct playlist *playlist, playlist_increment_version(playlist); - playlist_update_queued_song(playlist, queued); + playlist_update_queued_song(playlist, pc, queued); return PLAYLIST_RESULT_SUCCESS; } enum playlist_result -playlist_move_id(struct playlist *playlist, unsigned id1, int to) +playlist_move_id(struct playlist *playlist, struct player_control *pc, + unsigned id1, int to) { int song = queue_id_to_position(&playlist->queue, id1); if (song < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return playlist_move_range(playlist, song, song+1, to); + return playlist_move_range(playlist, pc, song, song+1, to); } void -playlist_shuffle(struct playlist *playlist, unsigned start, unsigned end) +playlist_shuffle(struct playlist *playlist, struct player_control *pc, + unsigned start, unsigned end) { const struct song *queued; @@ -440,5 +483,5 @@ playlist_shuffle(struct playlist *playlist, unsigned start, unsigned end) playlist_increment_version(playlist); - playlist_update_queued_song(playlist, queued); + playlist_update_queued_song(playlist, pc, queued); } diff --git a/src/playlist_error.h b/src/playlist_error.h new file mode 100644 index 000000000..ad9c62cf1 --- /dev/null +++ b/src/playlist_error.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_ERROR_H +#define MPD_PLAYLIST_ERROR_H + +#include <glib.h> + +enum playlist_result { + PLAYLIST_RESULT_SUCCESS, + PLAYLIST_RESULT_ERRNO, + PLAYLIST_RESULT_DENIED, + PLAYLIST_RESULT_NO_SUCH_SONG, + PLAYLIST_RESULT_NO_SUCH_LIST, + PLAYLIST_RESULT_LIST_EXISTS, + PLAYLIST_RESULT_BAD_NAME, + PLAYLIST_RESULT_BAD_RANGE, + PLAYLIST_RESULT_NOT_PLAYING, + PLAYLIST_RESULT_TOO_LARGE, + PLAYLIST_RESULT_DISABLED, +}; + +/** + * Quark for GError.domain; the code is an enum #playlist_result. + */ +G_GNUC_CONST +static inline GQuark +playlist_quark(void) +{ + return g_quark_from_static_string("playlist"); +} + +#endif diff --git a/src/playlist_global.c b/src/playlist_global.c index 2833b62ed..650b88bb8 100644 --- a/src/playlist_global.c +++ b/src/playlist_global.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,6 +26,7 @@ #include "playlist.h" #include "playlist_state.h" #include "event_pipe.h" +#include "main.h" struct playlist g_playlist; @@ -38,7 +39,7 @@ playlist_tag_event(void) static void playlist_event(void) { - playlist_sync(&g_playlist); + playlist_sync(&g_playlist, global_player_control); } void diff --git a/src/playlist_internal.h b/src/playlist_internal.h index 9d205188f..81b175176 100644 --- a/src/playlist_internal.h +++ b/src/playlist_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 @@ #include "playlist.h" +struct player_control; + /** * Returns the song object which is currently queued. Returns none if * there is none (yet?) or if MPD isn't playing. @@ -44,9 +46,11 @@ playlist_get_queued_song(struct playlist *playlist); */ void playlist_update_queued_song(struct playlist *playlist, + struct player_control *pc, const struct song *prev); void -playlist_play_order(struct playlist *playlist, int orderNum); +playlist_play_order(struct playlist *playlist, struct player_control *pc, + int orderNum); #endif diff --git a/src/playlist_list.c b/src/playlist_list.c index 019654bfc..64f03d46d 100644 --- a/src/playlist_list.c +++ b/src/playlist_list.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,14 +24,16 @@ #include "playlist/m3u_playlist_plugin.h" #include "playlist/xspf_playlist_plugin.h" #include "playlist/lastfm_playlist_plugin.h" +#include "playlist/despotify_playlist_plugin.h" +#include "playlist/soundcloud_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 "playlist/embcue_playlist_plugin.h" #include "input_stream.h" #include "uri.h" -#include "utils.h" +#include "string_util.h" #include "conf.h" #include "glib_compat.h" #include "mpd_error.h" @@ -47,15 +49,17 @@ static const struct playlist_plugin *const playlist_plugins[] = { &pls_playlist_plugin, &asx_playlist_plugin, &rss_playlist_plugin, +#ifdef ENABLE_DESPOTIFY + &despotify_playlist_plugin, +#endif #ifdef ENABLE_LASTFM &lastfm_playlist_plugin, #endif -#ifdef HAVE_CUE - &cue_playlist_plugin, -#endif -#ifdef HAVE_FLAC - &flac_playlist_plugin, +#ifdef ENABLE_SOUNDCLOUD + &soundcloud_playlist_plugin, #endif + &cue_playlist_plugin, + &embcue_playlist_plugin, NULL }; @@ -115,7 +119,8 @@ playlist_list_global_finish(void) } static struct playlist_provider * -playlist_list_open_uri_scheme(const char *uri, bool *tried) +playlist_list_open_uri_scheme(const char *uri, GMutex *mutex, GCond *cond, + bool *tried) { char *scheme; struct playlist_provider *playlist = NULL; @@ -134,7 +139,8 @@ playlist_list_open_uri_scheme(const char *uri, bool *tried) 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); + playlist = playlist_plugin_open_uri(plugin, uri, + mutex, cond); if (playlist != NULL) break; @@ -147,7 +153,8 @@ playlist_list_open_uri_scheme(const char *uri, bool *tried) } static struct playlist_provider * -playlist_list_open_uri_suffix(const char *uri, const bool *tried) +playlist_list_open_uri_suffix(const char *uri, GMutex *mutex, GCond *cond, + const bool *tried) { const char *suffix; struct playlist_provider *playlist = NULL; @@ -164,7 +171,8 @@ playlist_list_open_uri_suffix(const char *uri, const bool *tried) 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); + playlist = playlist_plugin_open_uri(plugin, uri, + mutex, cond); if (playlist != NULL) break; } @@ -174,7 +182,7 @@ playlist_list_open_uri_suffix(const char *uri, const bool *tried) } struct playlist_provider * -playlist_list_open_uri(const char *uri) +playlist_list_open_uri(const char *uri, GMutex *mutex, GCond *cond) { struct playlist_provider *playlist; /** this array tracks which plugins have already been tried by @@ -185,9 +193,10 @@ playlist_list_open_uri(const char *uri) memset(tried, false, sizeof(tried)); - playlist = playlist_list_open_uri_scheme(uri, tried); + playlist = playlist_list_open_uri_scheme(uri, mutex, cond, tried); if (playlist == NULL) - playlist = playlist_list_open_uri_suffix(uri, tried); + playlist = playlist_list_open_uri_suffix(uri, mutex, cond, + tried); return playlist; } @@ -274,16 +283,7 @@ 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; - } - } + input_stream_lock_wait_ready(is); if (is->mime != NULL) { playlist = playlist_list_open_stream_mime(is); @@ -318,7 +318,8 @@ playlist_suffix_supported(const char *suffix) } struct playlist_provider * -playlist_list_open_path(const char *path_fs, struct input_stream **is_r) +playlist_list_open_path(const char *path_fs, GMutex *mutex, GCond *cond, + struct input_stream **is_r) { GError *error = NULL; const char *suffix; @@ -331,7 +332,7 @@ playlist_list_open_path(const char *path_fs, struct input_stream **is_r) if (suffix == NULL || !playlist_suffix_supported(suffix)) return NULL; - is = input_stream_open(path_fs, &error); + is = input_stream_open(path_fs, mutex, cond, &error); if (is == NULL) { if (error != NULL) { g_warning("%s", error->message); @@ -341,15 +342,7 @@ playlist_list_open_path(const char *path_fs, struct input_stream **is_r) 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; - } - } + input_stream_lock_wait_ready(is); playlist = playlist_list_open_stream_suffix(is, suffix); if (playlist != NULL) diff --git a/src/playlist_list.h b/src/playlist_list.h index 3710589a2..4a2485303 100644 --- a/src/playlist_list.h +++ b/src/playlist_list.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_PLAYLIST_LIST_H #define MPD_PLAYLIST_LIST_H +#include <glib.h> + #include <stdbool.h> struct playlist_provider; @@ -41,7 +43,7 @@ playlist_list_global_finish(void); * Opens a playlist by its URI. */ struct playlist_provider * -playlist_list_open_uri(const char *uri); +playlist_list_open_uri(const char *uri, GMutex *mutex, GCond *cond); /** * Opens a playlist from an input stream. @@ -69,6 +71,7 @@ playlist_suffix_supported(const char *suffix); * @return a playlist, or NULL on error */ struct playlist_provider * -playlist_list_open_path(const char *path_fs, struct input_stream **is_r); +playlist_list_open_path(const char *path_fs, GMutex *mutex, GCond *cond, + struct input_stream **is_r); #endif diff --git a/src/playlist_mapper.c b/src/playlist_mapper.c index 99b322073..13adb80d0 100644 --- a/src/playlist_mapper.c +++ b/src/playlist_mapper.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,15 +27,16 @@ #include <assert.h> static struct playlist_provider * -playlist_open_path(const char *path_fs, struct input_stream **is_r) +playlist_open_path(const char *path_fs, GMutex *mutex, GCond *cond, + struct input_stream **is_r) { struct playlist_provider *playlist; - playlist = playlist_list_open_uri(path_fs); + playlist = playlist_list_open_uri(path_fs, mutex, cond); if (playlist != NULL) *is_r = NULL; else - playlist = playlist_list_open_path(path_fs, is_r); + playlist = playlist_list_open_path(path_fs, mutex, cond, is_r); return playlist; } @@ -44,7 +45,8 @@ playlist_open_path(const char *path_fs, struct input_stream **is_r) * 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) +playlist_open_in_playlist_dir(const char *uri, GMutex *mutex, GCond *cond, + struct input_stream **is_r) { char *path_fs; @@ -56,7 +58,8 @@ playlist_open_in_playlist_dir(const char *uri, struct input_stream **is_r) path_fs = g_build_filename(playlist_directory_fs, uri, NULL); - struct playlist_provider *playlist = playlist_open_path(path_fs, is_r); + struct playlist_provider *playlist = + playlist_open_path(path_fs, mutex, cond, is_r); g_free(path_fs); return playlist; @@ -66,7 +69,8 @@ playlist_open_in_playlist_dir(const char *uri, struct input_stream **is_r) * 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) +playlist_open_in_music_dir(const char *uri, GMutex *mutex, GCond *cond, + struct input_stream **is_r) { char *path_fs; @@ -76,25 +80,28 @@ playlist_open_in_music_dir(const char *uri, struct input_stream **is_r) if (path_fs == NULL) return NULL; - struct playlist_provider *playlist = playlist_open_path(path_fs, is_r); + struct playlist_provider *playlist = + playlist_open_path(path_fs, mutex, cond, is_r); g_free(path_fs); return playlist; } struct playlist_provider * -playlist_mapper_open(const char *uri, struct input_stream **is_r) +playlist_mapper_open(const char *uri, GMutex *mutex, GCond *cond, + struct input_stream **is_r) { struct playlist_provider *playlist; if (spl_valid_name(uri)) { - playlist = playlist_open_in_playlist_dir(uri, is_r); + playlist = playlist_open_in_playlist_dir(uri, mutex, cond, + is_r); if (playlist != NULL) return playlist; } if (uri_safe_local(uri)) { - playlist = playlist_open_in_music_dir(uri, is_r); + playlist = playlist_open_in_music_dir(uri, mutex, cond, is_r); if (playlist != NULL) return playlist; } diff --git a/src/playlist_mapper.h b/src/playlist_mapper.h index b98af1b13..9a7187d93 100644 --- a/src/playlist_mapper.h +++ b/src/playlist_mapper.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_PLAYLIST_MAPPER_H #define MPD_PLAYLIST_MAPPER_H +#include <glib.h> + struct input_stream; /** @@ -31,6 +33,7 @@ struct input_stream; * freed */ struct playlist_provider * -playlist_mapper_open(const char *uri, struct input_stream **is_r); +playlist_mapper_open(const char *uri, GMutex *mutex, GCond *cond, + struct input_stream **is_r); #endif diff --git a/src/playlist_plugin.h b/src/playlist_plugin.h index 3d840573e..a27f651c0 100644 --- a/src/playlist_plugin.h +++ b/src/playlist_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_PLAYLIST_PLUGIN_H #define MPD_PLAYLIST_PLUGIN_H +#include <glib.h> + #include <stdbool.h> #include <stddef.h> @@ -64,7 +66,8 @@ struct playlist_plugin { * 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); + struct playlist_provider *(*open_uri)(const char *uri, + GMutex *mutex, GCond *cond); /** * Opens the playlist in the specified input stream. It has @@ -110,9 +113,10 @@ playlist_plugin_finish(const struct playlist_plugin *plugin) } static inline struct playlist_provider * -playlist_plugin_open_uri(const struct playlist_plugin *plugin, const char *uri) +playlist_plugin_open_uri(const struct playlist_plugin *plugin, const char *uri, + GMutex *mutex, GCond *cond) { - return plugin->open_uri(uri); + return plugin->open_uri(uri, mutex, cond); } static inline struct playlist_provider * diff --git a/src/playlist_print.c b/src/playlist_print.c index 89ab2e5ab..a6bf84ccd 100644 --- a/src/playlist_print.c +++ b/src/playlist_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 "playlist_plugin.h" #include "playlist_any.h" #include "playlist_song.h" +#include "playlist.h" #include "queue_print.h" #include "stored_playlist.h" #include "song_print.h" @@ -116,11 +117,12 @@ playlist_print_changes_position(struct client *client, } bool -spl_print(struct client *client, const char *name_utf8, bool detail) +spl_print(struct client *client, const char *name_utf8, bool detail, + GError **error_r) { GPtrArray *list; - list = spl_load(name_utf8); + list = spl_load(name_utf8, error_r); if (list == NULL) return false; @@ -153,7 +155,7 @@ playlist_provider_print(struct client *client, const char *uri, 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); + song = playlist_check_translate_song(song, base_uri, false); if (song == NULL) continue; @@ -169,10 +171,17 @@ playlist_provider_print(struct client *client, const char *uri, bool playlist_file_print(struct client *client, const char *uri, bool detail) { + GMutex *mutex = g_mutex_new(); + GCond *cond = g_cond_new(); + struct input_stream *is; - struct playlist_provider *playlist = playlist_open_any(uri, &is); - if (playlist == NULL) + struct playlist_provider *playlist = + playlist_open_any(uri, mutex, cond, &is); + if (playlist == NULL) { + g_cond_free(cond); + g_mutex_free(mutex); return false; + } playlist_provider_print(client, uri, playlist, detail); playlist_plugin_close(playlist); @@ -180,5 +189,8 @@ playlist_file_print(struct client *client, const char *uri, bool detail) if (is != NULL) input_stream_close(is); + g_cond_free(cond); + g_mutex_free(mutex); + return true; } diff --git a/src/playlist_print.h b/src/playlist_print.h index b3a0446ed..d4f1911d2 100644 --- a/src/playlist_print.h +++ b/src/playlist_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ #ifndef PLAYLIST_PRINT_H #define PLAYLIST_PRINT_H +#include <glib.h> #include <stdbool.h> #include <stdint.h> @@ -99,7 +100,8 @@ playlist_print_changes_position(struct client *client, * @return true on success, false if the playlist does not exist */ bool -spl_print(struct client *client, const char *name_utf8, bool detail); +spl_print(struct client *client, const char *name_utf8, bool detail, + GError **error_r); /** * Send the playlist file to the client. diff --git a/src/playlist_queue.c b/src/playlist_queue.c index 635e23a28..aada94984 100644 --- a/src/playlist_queue.c +++ b/src/playlist_queue.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,23 +22,35 @@ #include "playlist_plugin.h" #include "playlist_any.h" #include "playlist_song.h" +#include "playlist.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) + unsigned start_index, unsigned end_index, + struct playlist *dest, struct player_control *pc, + bool secure) { 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); + for (unsigned i = 0; + i < end_index && (song = playlist_plugin_read(source)) != NULL; + ++i) { + if (i < start_index) { + /* skip songs before the start index */ + if (!song_in_database(song)) + song_free(song); + continue; + } + + song = playlist_check_translate_song(song, base_uri, secure); if (song == NULL) continue; - result = playlist_append_song(dest, song, NULL); + result = playlist_append_song(dest, pc, song, NULL); if (result != PLAYLIST_RESULT_SUCCESS) { if (!song_in_database(song)) song_free(song); @@ -53,19 +65,33 @@ playlist_load_into_queue(const char *uri, struct playlist_provider *source, } enum playlist_result -playlist_open_into_queue(const char *uri, struct playlist *dest) +playlist_open_into_queue(const char *uri, + unsigned start_index, unsigned end_index, + struct playlist *dest, struct player_control *pc, + bool secure) { + GMutex *mutex = g_mutex_new(); + GCond *cond = g_cond_new(); + struct input_stream *is; - struct playlist_provider *playlist = playlist_open_any(uri, &is); - if (playlist == NULL) + struct playlist_provider *playlist = + playlist_open_any(uri, mutex, cond, &is); + if (playlist == NULL) { + g_cond_free(cond); + g_mutex_free(mutex); return PLAYLIST_RESULT_NO_SUCH_LIST; + } enum playlist_result result = - playlist_load_into_queue(uri, playlist, dest); + playlist_load_into_queue(uri, playlist, start_index, end_index, + dest, pc, secure); playlist_plugin_close(playlist); if (is != NULL) input_stream_close(is); + g_cond_free(cond); + g_mutex_free(mutex); + return result; } diff --git a/src/playlist_queue.h b/src/playlist_queue.h index 530d4b4be..24a851aab 100644 --- a/src/playlist_queue.h +++ b/src/playlist_queue.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,10 +24,13 @@ #ifndef MPD_PLAYLIST_QUEUE_H #define MPD_PLAYLIST_QUEUE_H -#include "playlist.h" +#include "playlist_error.h" + +#include <stdbool.h> struct playlist_provider; struct playlist; +struct player_control; /** * Loads the contents of a playlist and append it to the specified @@ -35,17 +38,24 @@ struct playlist; * * @param uri the URI of the playlist, used to resolve relative song * URIs + * @param start_index the index of the first song + * @param end_index the index of the last song (excluding) */ enum playlist_result playlist_load_into_queue(const char *uri, struct playlist_provider *source, - struct playlist *dest); + unsigned start_index, unsigned end_index, + struct playlist *dest, struct player_control *pc, + bool secure); /** * 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); +playlist_open_into_queue(const char *uri, + unsigned start_index, unsigned end_index, + struct playlist *dest, struct player_control *pc, + bool secure); #endif diff --git a/src/playlist_save.c b/src/playlist_save.c index 8ddc93ec9..6571e286c 100644 --- a/src/playlist_save.c +++ b/src/playlist_save.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,7 +19,9 @@ #include "config.h" #include "playlist_save.h" +#include "playlist.h" #include "stored_playlist.h" +#include "queue.h" #include "song.h" #include "mapper.h" #include "path.h" @@ -108,18 +110,24 @@ spl_save_playlist(const char *name_utf8, const struct playlist *playlist) return spl_save_queue(name_utf8, &playlist->queue); } -enum playlist_result -playlist_load_spl(struct playlist *playlist, const char *name_utf8) +bool +playlist_load_spl(struct playlist *playlist, struct player_control *pc, + const char *name_utf8, + unsigned start_index, unsigned end_index, + GError **error_r) { GPtrArray *list; - list = spl_load(name_utf8); + list = spl_load(name_utf8, error_r); if (list == NULL) - return PLAYLIST_RESULT_NO_SUCH_LIST; + return false; + + if (list->len < end_index) + end_index = list->len; - for (unsigned i = 0; i < list->len; ++i) { + for (unsigned i = start_index; i < end_index; ++i) { const char *temp = g_ptr_array_index(list, i); - if ((playlist_append_uri(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { + if ((playlist_append_uri(playlist, pc, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { /* for windows compatibility, convert slashes */ char *temp2 = g_strdup(temp); char *p = temp2; @@ -128,7 +136,7 @@ playlist_load_spl(struct playlist *playlist, const char *name_utf8) *p = '/'; p++; } - if ((playlist_append_uri(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { + if ((playlist_append_uri(playlist, pc, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { g_warning("can't add file \"%s\"", temp2); } g_free(temp2); @@ -136,5 +144,5 @@ playlist_load_spl(struct playlist *playlist, const char *name_utf8) } spl_free(list); - return PLAYLIST_RESULT_SUCCESS; + return true; } diff --git a/src/playlist_save.h b/src/playlist_save.h index a0131cf7f..a6c31a9a6 100644 --- a/src/playlist_save.h +++ b/src/playlist_save.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,11 +20,15 @@ #ifndef MPD_PLAYLIST_SAVE_H #define MPD_PLAYLIST_SAVE_H -#include "playlist.h" +#include "playlist_error.h" +#include <stdbool.h> #include <stdio.h> struct song; +struct queue; +struct playlist; +struct player_control; void playlist_print_song(FILE *fp, const struct song *song); @@ -48,7 +52,10 @@ spl_save_playlist(const char *name_utf8, const struct playlist *playlist); * Loads a stored playlist file, and append all songs to the global * playlist. */ -enum playlist_result -playlist_load_spl(struct playlist *playlist, const char *name_utf8); +bool +playlist_load_spl(struct playlist *playlist, struct player_control *pc, + const char *name_utf8, + unsigned start_index, unsigned end_index, + GError **error_r); #endif diff --git a/src/playlist_song.c b/src/playlist_song.c index 827098655..8a3ba303e 100644 --- a/src/playlist_song.c +++ b/src/playlist_song.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -84,7 +84,8 @@ apply_song_metadata(struct song *dest, const struct song *src) } struct song * -playlist_check_translate_song(struct song *song, const char *base_uri) +playlist_check_translate_song(struct song *song, const char *base_uri, + bool secure) { struct song *dest; @@ -114,20 +115,19 @@ playlist_check_translate_song(struct song *song, const char *base_uri) if (g_path_is_absolute(uri)) { /* XXX fs_charset vs utf8? */ - char *prefix = map_directory_fs(db_get_root()); + const char *prefix = mapper_get_music_directory(); - if (prefix == NULL || !g_str_has_prefix(uri, prefix) || - uri[strlen(prefix)] != '/') { + if (prefix != NULL && g_str_has_prefix(uri, prefix) && + uri[strlen(prefix)] == '/') + uri += strlen(prefix) + 1; + else if (!secure) { /* local files must be relative to the music - directory */ - g_free(prefix); + directory when "secure" is enabled */ song_free(song); return NULL; } base_uri = NULL; - uri += strlen(prefix) + 1; - g_free(prefix); } if (base_uri != NULL) @@ -138,6 +138,12 @@ playlist_check_translate_song(struct song *song, const char *base_uri) if (uri_has_scheme(uri)) { dest = song_remote_new(uri); g_free(uri); + } else if (g_path_is_absolute(uri) && secure) { + dest = song_file_load(uri, NULL); + if (dest == NULL) { + song_free(song); + return NULL; + } } else { dest = db_get_song(uri); g_free(uri); diff --git a/src/playlist_song.h b/src/playlist_song.h index 5a2e4c2b0..ea8786912 100644 --- a/src/playlist_song.h +++ b/src/playlist_song.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,12 +20,18 @@ #ifndef MPD_PLAYLIST_SONG_H #define MPD_PLAYLIST_SONG_H +#include <stdbool.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. + * + * @param secure if true, then local files are only allowed if they + * are relative to base_uri */ struct song * -playlist_check_translate_song(struct song *song, const char *base_uri); +playlist_check_translate_song(struct song *song, const char *base_uri, + bool secure); #endif diff --git a/src/playlist_state.c b/src/playlist_state.c index bb9897e01..4aa2c2c92 100644 --- a/src/playlist_state.c +++ b/src/playlist_state.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -29,6 +29,7 @@ #include "queue_save.h" #include "path.h" #include "text_file.h" +#include "conf.h" #include <string.h> #include <stdlib.h> @@ -53,11 +54,12 @@ #define PLAYLIST_BUFFER_SIZE 2*MPD_PATH_MAX void -playlist_state_save(FILE *fp, const struct playlist *playlist) +playlist_state_save(FILE *fp, const struct playlist *playlist, + struct player_control *pc) { struct player_status player_status; - pc_get_status(&player_status); + pc_get_status(pc, &player_status); fputs(PLAYLIST_STATE_FILE_STATE, fp); @@ -89,10 +91,11 @@ playlist_state_save(FILE *fp, const struct playlist *playlist) fprintf(fp, PLAYLIST_STATE_FILE_CONSUME "%i\n", playlist->queue.consume); 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()); + (int)(pc_get_cross_fade(pc))); + fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n", + pc_get_mixramp_db(pc)); fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n", - pc_get_mixramp_delay()); + pc_get_mixramp_delay(pc)); fputs(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n", fp); queue_save(fp, &playlist->queue); fputs(PLAYLIST_STATE_FILE_PLAYLIST_END "\n", fp); @@ -123,11 +126,11 @@ playlist_state_load(FILE *fp, GString *buffer, struct playlist *playlist) bool playlist_state_restore(const char *line, FILE *fp, GString *buffer, - struct playlist *playlist) + struct playlist *playlist, struct player_control *pc) { int current = -1; int seek_time = 0; - int state = PLAYER_STATE_STOP; + enum player_state state = PLAYER_STATE_STOP; bool random_mode = false; if (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_STATE)) @@ -148,16 +151,16 @@ playlist_state_restore(const char *line, FILE *fp, GString *buffer, if (strcmp (&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]), "1") == 0) { - playlist_set_repeat(playlist, true); + playlist_set_repeat(playlist, pc, true); } else - playlist_set_repeat(playlist, false); + playlist_set_repeat(playlist, pc, false); } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_SINGLE)) { if (strcmp (&(line[strlen(PLAYLIST_STATE_FILE_SINGLE)]), "1") == 0) { - playlist_set_single(playlist, true); + playlist_set_single(playlist, pc, true); } else - playlist_set_single(playlist, false); + playlist_set_single(playlist, pc, false); } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CONSUME)) { if (strcmp (&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]), @@ -166,11 +169,14 @@ playlist_state_restore(const char *line, FILE *fp, GString *buffer, } else 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))); + pc_set_cross_fade(pc, + 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))); + pc_set_mixramp_db(pc, + 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))); + pc_set_mixramp_delay(pc, + atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY))); } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_RANDOM)) { random_mode = strcmp(line + strlen(PLAYLIST_STATE_FILE_RANDOM), @@ -185,38 +191,46 @@ playlist_state_restore(const char *line, FILE *fp, GString *buffer, } } - playlist_set_random(playlist, random_mode); + playlist_set_random(playlist, pc, random_mode); if (!queue_is_empty(&playlist->queue)) { if (!queue_valid_position(&playlist->queue, current)) current = 0; + if (state == PLAYER_STATE_PLAY && + config_get_bool("restore_paused", false)) + /* the user doesn't want MPD to auto-start + playback after startup; fall back to + "pause" */ + state = PLAYER_STATE_PAUSE; + /* 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(); + pc_update_audio(pc); if (state == PLAYER_STATE_STOP /* && config_option */) playlist->current = current; else if (seek_time == 0) - playlist_play(playlist, current); + playlist_play(playlist, pc, current); else - playlist_seek_song(playlist, current, seek_time); + playlist_seek_song(playlist, pc, current, seek_time); if (state == PLAYER_STATE_PAUSE) - pc_pause(); + pc_pause(pc); } return true; } unsigned -playlist_state_get_hash(const struct playlist *playlist) +playlist_state_get_hash(const struct playlist *playlist, + struct player_control *pc) { struct player_status player_status; - pc_get_status(&player_status); + pc_get_status(pc, &player_status); return playlist->queue.version ^ (player_status.state != PLAYER_STATE_STOP @@ -226,7 +240,7 @@ playlist_state_get_hash(const struct playlist *playlist) ? (queue_order_to_position(&playlist->queue, playlist->current) << 16) : 0) ^ - ((int)pc_get_cross_fade() << 20) ^ + ((int)pc_get_cross_fade(pc) << 20) ^ (player_status.state << 24) ^ (playlist->queue.random << 27) ^ (playlist->queue.repeat << 28) ^ diff --git a/src/playlist_state.h b/src/playlist_state.h index 8ca3657f2..f67d01d2c 100644 --- a/src/playlist_state.h +++ b/src/playlist_state.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -30,13 +30,15 @@ #include <stdio.h> struct playlist; +struct player_control; void -playlist_state_save(FILE *fp, const struct playlist *playlist); +playlist_state_save(FILE *fp, const struct playlist *playlist, + struct player_control *pc); bool playlist_state_restore(const char *line, FILE *fp, GString *buffer, - struct playlist *playlist); + struct playlist *playlist, struct player_control *pc); /** * Generates a hash number for the current state of the playlist and @@ -45,6 +47,7 @@ playlist_state_restore(const char *line, FILE *fp, GString *buffer, * be saved. */ unsigned -playlist_state_get_hash(const struct playlist *playlist); +playlist_state_get_hash(const struct playlist *playlist, + struct player_control *pc); #endif diff --git a/src/playlist_vector.c b/src/playlist_vector.c index 7c1765a98..74c7bf089 100644 --- a/src/playlist_vector.c +++ b/src/playlist_vector.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,6 +19,7 @@ #include "config.h" #include "playlist_vector.h" +#include "db_lock.h" #include <assert.h> #include <string.h> @@ -46,60 +47,48 @@ playlist_metadata_free(struct playlist_metadata *pm) } void -playlist_vector_deinit(struct playlist_vector *pv) +playlist_vector_deinit(struct list_head *pv) { assert(pv != NULL); - while (pv->head != NULL) { - struct playlist_metadata *pm = pv->head; - pv->head = pm->next; + struct playlist_metadata *pm, *n; + playlist_vector_for_each_safe(pm, n, pv) playlist_metadata_free(pm); - } } -static struct playlist_metadata ** -playlist_vector_find_p(struct playlist_vector *pv, const char *name) +struct playlist_metadata * +playlist_vector_find(struct list_head *pv, const char *name) { + assert(holding_db_lock()); assert(pv != NULL); assert(name != NULL); - struct playlist_metadata **pmp = &pv->head; - - for (;;) { - struct playlist_metadata *pm = *pmp; - if (pm == NULL) - return NULL; - + struct playlist_metadata *pm; + playlist_vector_for_each(pm, pv) if (strcmp(pm->name, name) == 0) - return pmp; + return pm; - 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; + return NULL; } void -playlist_vector_add(struct playlist_vector *pv, +playlist_vector_add(struct list_head *pv, const char *name, time_t mtime) { + assert(holding_db_lock()); + struct playlist_metadata *pm = playlist_metadata_new(name, mtime); - pm->next = pv->head; - pv->head = pm; + list_add_tail(&pm->siblings, pv); } bool -playlist_vector_update_or_add(struct playlist_vector *pv, +playlist_vector_update_or_add(struct list_head *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; + assert(holding_db_lock()); + + struct playlist_metadata *pm = playlist_vector_find(pv, name); + if (pm != NULL) { if (mtime == pm->mtime) return false; @@ -111,15 +100,15 @@ playlist_vector_update_or_add(struct playlist_vector *pv, } bool -playlist_vector_remove(struct playlist_vector *pv, const char *name) +playlist_vector_remove(struct list_head *pv, const char *name) { - struct playlist_metadata **pmp = playlist_vector_find_p(pv, name); - if (pmp == NULL) - return false; + assert(holding_db_lock()); - struct playlist_metadata *pm = *pmp; - *pmp = pm->next; + struct playlist_metadata *pm = playlist_vector_find(pv, name); + if (pm == NULL) + return false; + list_del(&pm->siblings); playlist_metadata_free(pm); return true; } diff --git a/src/playlist_vector.h b/src/playlist_vector.h index 62861ae49..0af6df8b4 100644 --- a/src/playlist_vector.h +++ b/src/playlist_vector.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,15 +20,23 @@ #ifndef MPD_PLAYLIST_VECTOR_H #define MPD_PLAYLIST_VECTOR_H +#include "util/list.h" + #include <stdbool.h> #include <stddef.h> #include <sys/time.h> +#define playlist_vector_for_each(pos, head) \ + list_for_each_entry(pos, head, siblings) + +#define playlist_vector_for_each_safe(pos, n, head) \ + list_for_each_entry_safe(pos, n, head, siblings) + /** * A directory entry pointing to a playlist file. */ struct playlist_metadata { - struct playlist_metadata *next; + struct list_head siblings; /** * The UTF-8 encoded name of the playlist file. @@ -38,40 +46,35 @@ struct playlist_metadata { 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); - -static inline bool -playlist_vector_is_empty(const struct playlist_vector *pv) -{ - return pv->head == NULL; -} +playlist_vector_deinit(struct list_head *pv); +/** + * Caller must lock the #db_mutex. + */ struct playlist_metadata * -playlist_vector_find(struct playlist_vector *pv, const char *name); +playlist_vector_find(struct list_head *pv, const char *name); +/** + * Caller must lock the #db_mutex. + */ void -playlist_vector_add(struct playlist_vector *pv, +playlist_vector_add(struct list_head *pv, const char *name, time_t mtime); /** + * Caller must lock the #db_mutex. + * * @return true if the vector or one of its items was modified */ bool -playlist_vector_update_or_add(struct playlist_vector *pv, +playlist_vector_update_or_add(struct list_head *pv, const char *name, time_t mtime); +/** + * Caller must lock the #db_mutex. + */ bool -playlist_vector_remove(struct playlist_vector *pv, const char *name); +playlist_vector_remove(struct list_head *pv, const char *name); #endif /* SONGVEC_H */ diff --git a/src/poison.h b/src/poison.h index 3654f2e9c..c95b5d005 100644 --- a/src/poison.h +++ b/src/poison.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/protocol/argparser.c b/src/protocol/argparser.c new file mode 100644 index 000000000..b21d4c53c --- /dev/null +++ b/src/protocol/argparser.c @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "argparser.h" +#include "result.h" + +#include <glib.h> +#include <stdlib.h> + +bool +check_uint32(struct client *client, uint32_t *dst, const char *s) +{ + char *test; + + *dst = strtoul(s, &test, 10); + if (test == s || *test != '\0') { + command_error(client, ACK_ERROR_ARG, + "Integer expected: %s", s); + return false; + } + return true; +} + +bool +check_int(struct client *client, int *value_r, const char *s) +{ + char *test; + long value; + + value = strtol(s, &test, 10); + if (test == s || *test != '\0') { + command_error(client, ACK_ERROR_ARG, + "Integer expected: %s", s); + return false; + } + +#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; + } +#endif + + *value_r = (int)value; + return true; +} + +bool +check_range(struct client *client, unsigned *value_r1, unsigned *value_r2, + const char *s) +{ + char *test, *test2; + long value; + + value = strtol(s, &test, 10); + if (test == s || (*test != '\0' && *test != ':')) { + command_error(client, ACK_ERROR_ARG, + "Integer or range expected: %s", s); + return false; + } + + if (value == -1 && *test == 0) { + /* compatibility with older MPD versions: specifying + "-1" makes MPD display the whole list */ + *value_r1 = 0; + *value_r2 = G_MAXUINT; + return true; + } + + if (value < 0) { + command_error(client, ACK_ERROR_ARG, + "Number is negative: %s", s); + return false; + } + +#if G_MAXLONG > G_MAXUINT + if (value > G_MAXUINT) { + command_error(client, ACK_ERROR_ARG, + "Number too large: %s", s); + return false; + } +#endif + + *value_r1 = (unsigned)value; + + if (*test == ':') { + value = strtol(++test, &test2, 10); + if (test2 == test || *test2 != '\0') { + command_error(client, ACK_ERROR_ARG, + "Integer or range expected: %s", s); + 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 G_MAXLONG > G_MAXUINT + if (value > G_MAXUINT) { + command_error(client, ACK_ERROR_ARG, + "Number too large: %s", s); + return false; + } +#endif + *value_r2 = (unsigned)value; + } else { + *value_r2 = (unsigned)value + 1; + } + + return true; +} + +bool +check_unsigned(struct client *client, unsigned *value_r, const char *s) +{ + unsigned long value; + char *endptr; + + value = strtoul(s, &endptr, 10); + if (endptr == s || *endptr != 0) { + command_error(client, ACK_ERROR_ARG, + "Integer expected: %s", s); + return false; + } + + if (value > G_MAXUINT) { + command_error(client, ACK_ERROR_ARG, + "Number too large: %s", s); + return false; + } + + *value_r = (unsigned)value; + return true; +} + +bool +check_bool(struct client *client, bool *value_r, const char *s) +{ + long value; + char *endptr; + + value = strtol(s, &endptr, 10); + if (endptr == s || *endptr != 0 || (value != 0 && value != 1)) { + command_error(client, ACK_ERROR_ARG, + "Boolean (0/1) expected: %s", s); + return false; + } + + *value_r = !!value; + return true; +} + +bool +check_float(struct client *client, float *value_r, const char *s) +{ + float value; + char *endptr; + + value = strtof(s, &endptr); + if (endptr == s || *endptr != 0) { + command_error(client, ACK_ERROR_ARG, + "Float expected: %s", s); + return false; + } + + *value_r = value; + return true; +} diff --git a/src/protocol/argparser.h b/src/protocol/argparser.h new file mode 100644 index 000000000..e88aea478 --- /dev/null +++ b/src/protocol/argparser.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_PROTOCOL_ARGPARSER_H +#define MPD_PROTOCOL_ARGPARSER_H + +#include "check.h" + +#include <stdbool.h> +#include <stdint.h> + +struct client; + +bool +check_uint32(struct client *client, uint32_t *dst, const char *s); + +bool +check_int(struct client *client, int *value_r, const char *s); + +bool +check_range(struct client *client, unsigned *value_r1, unsigned *value_r2, + const char *s); + +bool +check_unsigned(struct client *client, unsigned *value_r, const char *s); + +bool +check_bool(struct client *client, bool *value_r, const char *s); + +bool +check_float(struct client *client, float *value_r, const char *s); + +#endif diff --git a/src/protocol/result.c b/src/protocol/result.c new file mode 100644 index 000000000..30cd0a266 --- /dev/null +++ b/src/protocol/result.c @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "result.h" +#include "client.h" + +#include <assert.h> + +const char *current_command; +int command_list_num; + +void +command_success(struct client *client) +{ + client_puts(client, "OK\n"); +} + +void +command_error_v(struct client *client, enum ack error, + const char *fmt, va_list args) +{ + assert(client != NULL); + assert(current_command != NULL); + + client_printf(client, "ACK [%i@%i] {%s} ", + (int)error, command_list_num, current_command); + client_vprintf(client, fmt, args); + client_puts(client, "\n"); + + current_command = NULL; +} + +void +command_error(struct client *client, enum ack error, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + command_error_v(client, error, fmt, args); + va_end(args); +} diff --git a/src/protocol/result.h b/src/protocol/result.h new file mode 100644 index 000000000..8b9e44bfd --- /dev/null +++ b/src/protocol/result.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_PROTOCOL_RESULT_H +#define MPD_PROTOCOL_RESULT_H + +#include "check.h" +#include "ack.h" + +#include <glib.h> + +struct client; + +extern const char *current_command; +extern int command_list_num; + +void +command_success(struct client *client); + +void +command_error_v(struct client *client, enum ack error, + const char *fmt, va_list args); + +G_GNUC_PRINTF(3, 4) +void +command_error(struct client *client, enum ack error, const char *fmt, ...); + +#endif diff --git a/src/queue.c b/src/queue.c index dd0b48cb5..cd932875e 100644 --- a/src/queue.c +++ b/src/queue.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,6 +21,8 @@ #include "queue.h" #include "song.h" +#include <stdlib.h> + /** * Generate a non-existing id number. */ @@ -104,6 +106,7 @@ queue_append(struct queue *queue, struct song *song) .song = song, .id = id, .version = queue->version, + .priority = 0, }; queue->order[queue->length] = queue->length; @@ -220,6 +223,30 @@ queue_move_range(struct queue *queue, unsigned start, unsigned end, unsigned to) } } +/** + * Moves a song to a new position in the "order" list. + */ +static void +queue_move_order(struct queue *queue, unsigned from_order, unsigned to_order) +{ + assert(queue != NULL); + assert(from_order < queue->length); + assert(to_order <= queue->length); + + const unsigned from_position = + queue_order_to_position(queue, from_order); + + if (from_order < to_order) { + for (unsigned i = from_order; i < to_order; ++i) + queue->order[i] = queue->order[i + 1]; + } else { + for (unsigned i = from_order; i > to_order; --i) + queue->order[i] = queue->order[i - 1]; + } + + queue->order[to_order] = from_position; +} + void queue_delete(struct queue *queue, unsigned position) { @@ -308,15 +335,123 @@ queue_finish(struct queue *queue) g_rand_free(queue->rand); } -void -queue_shuffle_order(struct queue *queue) +static const struct queue_item * +queue_get_order_item_const(const struct queue *queue, unsigned order) +{ + assert(queue != NULL); + assert(order < queue->length); + + return &queue->items[queue->order[order]]; +} + +static uint8_t +queue_get_order_priority(const struct queue *queue, unsigned order) +{ + return queue_get_order_item_const(queue, order)->priority; +} + +static gint +queue_item_compare_order_priority(gconstpointer av, gconstpointer bv, + gpointer user_data) +{ + const struct queue *queue = user_data; + const unsigned *const ap = av; + const unsigned *const bp = bv; + assert(ap >= queue->order && ap < queue->order + queue->length); + assert(bp >= queue->order && bp < queue->order + queue->length); + uint8_t a = queue->items[*ap].priority; + uint8_t b = queue->items[*bp].priority; + + if (G_LIKELY(a == b)) + return 0; + else if (a > b) + return -1; + else + return 1; +} + +static void +queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end) { + assert(queue != NULL); assert(queue->random); + assert(start <= end); + assert(end <= queue->length); - for (unsigned i = 0; i < queue->length; i++) + g_qsort_with_data(&queue->order[start], end - start, + sizeof(queue->order[0]), + queue_item_compare_order_priority, + queue); +} + +/** + * Shuffle the order of items in the specified range, ignoring their + * priorities. + */ +static void +queue_shuffle_order_range(struct queue *queue, unsigned start, unsigned end) +{ + assert(queue != NULL); + assert(queue->random); + assert(start <= end); + assert(end <= queue->length); + + for (unsigned i = start; i < end; ++i) queue_swap_order(queue, i, - g_rand_int_range(queue->rand, i, - queue->length)); + g_rand_int_range(queue->rand, i, end)); +} + +/** + * Sort the "order" of items by priority, and then shuffle each + * priority group. + */ +void +queue_shuffle_order_range_with_priority(struct queue *queue, + unsigned start, unsigned end) +{ + assert(queue != NULL); + assert(queue->random); + assert(start <= end); + assert(end <= queue->length); + + if (start == end) + return; + + /* first group the range by priority */ + queue_sort_order_by_priority(queue, start, end); + + /* now shuffle each priority group */ + unsigned group_start = start; + uint8_t group_priority = queue_get_order_priority(queue, start); + + for (unsigned i = start + 1; i < end; ++i) { + uint8_t priority = queue_get_order_priority(queue, i); + assert(priority <= group_priority); + + if (priority != group_priority) { + /* start of a new group - shuffle the one that + has just ended */ + queue_shuffle_order_range(queue, group_start, i); + group_start = i; + group_priority = priority; + } + } + + /* shuffle the last group */ + queue_shuffle_order_range(queue, group_start, end); +} + +void +queue_shuffle_order(struct queue *queue) +{ + queue_shuffle_order_range_with_priority(queue, 0, queue->length); +} + +static void +queue_shuffle_order_first(struct queue *queue, unsigned start, unsigned end) +{ + queue_swap_order(queue, start, + g_rand_int_range(queue->rand, start, end)); } void @@ -337,3 +472,132 @@ queue_shuffle_range(struct queue *queue, unsigned start, unsigned end) queue_swap(queue, i, ri); } } + +/** + * Find the first item that has this specified priority or higher. + */ +G_GNUC_PURE +static unsigned +queue_find_priority_order(const struct queue *queue, unsigned start_order, + uint8_t priority, unsigned exclude_order) +{ + assert(queue != NULL); + assert(queue->random); + assert(start_order <= queue->length); + + for (unsigned order = start_order; order < queue->length; ++order) { + const unsigned position = queue_order_to_position(queue, order); + const struct queue_item *item = &queue->items[position]; + if (item->priority <= priority && order != exclude_order) + return order; + } + + return queue->length; +} + +G_GNUC_PURE +static unsigned +queue_count_same_priority(const struct queue *queue, unsigned start_order, + uint8_t priority) +{ + assert(queue != NULL); + assert(queue->random); + assert(start_order <= queue->length); + + for (unsigned order = start_order; order < queue->length; ++order) { + const unsigned position = queue_order_to_position(queue, order); + const struct queue_item *item = &queue->items[position]; + if (item->priority != priority) + return order - start_order; + } + + return queue->length - start_order; +} + +bool +queue_set_priority(struct queue *queue, unsigned position, uint8_t priority, + int after_order) +{ + assert(queue != NULL); + assert(position < queue->length); + + struct queue_item *item = &queue->items[position]; + uint8_t old_priority = item->priority; + if (old_priority == priority) + return false; + + item->version = queue->version; + item->priority = priority; + + if (!queue->random) + /* don't reorder if not in random mode */ + return true; + + unsigned order = queue_position_to_order(queue, position); + if (after_order >= 0) { + if (order == (unsigned)after_order) + /* don't reorder the current song */ + return true; + + if (order < (unsigned)after_order) { + /* the specified song has been played already + - enqueue it only if its priority has just + become bigger than the current one's */ + + const unsigned after_position = + queue_order_to_position(queue, after_order); + const struct queue_item *after_item = + &queue->items[after_position]; + if (old_priority > after_item->priority || + priority <= after_item->priority) + /* priority hasn't become bigger */ + return true; + } + } + + /* move the item to the beginning of the priority group (or + create a new priority group) */ + + const unsigned before_order = + queue_find_priority_order(queue, after_order + 1, priority, + order); + const unsigned new_order = before_order > order + ? before_order - 1 + : before_order; + queue_move_order(queue, order, new_order); + + /* shuffle the song within that priority group */ + + const unsigned priority_count = + queue_count_same_priority(queue, new_order, priority); + assert(priority_count >= 1); + queue_shuffle_order_first(queue, new_order, + new_order + priority_count); + + return true; +} + +bool +queue_set_priority_range(struct queue *queue, + unsigned start_position, unsigned end_position, + uint8_t priority, int after_order) +{ + assert(queue != NULL); + assert(start_position <= end_position); + assert(end_position <= queue->length); + + bool modified = false; + int after_position = after_order >= 0 + ? (int)queue_order_to_position(queue, after_order) + : -1; + for (unsigned i = start_position; i < end_position; ++i) { + after_order = after_position >= 0 + ? (int)queue_position_to_order(queue, after_position) + : -1; + + modified |= queue_set_priority(queue, i, priority, + after_order); + } + + return modified; +} diff --git a/src/queue.h b/src/queue.h index 05eeafa22..5cb5c196b 100644 --- a/src/queue.h +++ b/src/queue.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -46,6 +46,13 @@ struct queue_item { /** when was this item last changed? */ uint32_t version; + + /** + * The priority of this item, between 0 and 255. High + * priority value means that this song gets played first in + * "random" mode. + */ + uint8_t priority; }; /** @@ -181,6 +188,15 @@ queue_position_to_order(const struct queue *queue, unsigned position) } } +G_GNUC_PURE +static inline uint8_t +queue_get_priority_at_position(const struct queue *queue, unsigned position) +{ + assert(position < queue->length); + + return queue->items[position].priority; +} + /** * Returns the song at the specified position. */ @@ -320,6 +336,14 @@ queue_restore_order(struct queue *queue) } /** + * Shuffle the order of items in the specified range, taking their + * priorities into account. + */ +void +queue_shuffle_order_range_with_priority(struct queue *queue, + unsigned start, unsigned end); + +/** * Shuffles the virtual order of songs, but does not move them * physically. This is used in random mode. */ @@ -341,4 +365,13 @@ queue_shuffle_order_last(struct queue *queue, unsigned start, unsigned end); void queue_shuffle_range(struct queue *queue, unsigned start, unsigned end); +bool +queue_set_priority(struct queue *queue, unsigned position, + uint8_t priority, int after_order); + +bool +queue_set_priority_range(struct queue *queue, + unsigned start_position, unsigned end_position, + uint8_t priority, int after_order); + #endif diff --git a/src/queue_print.c b/src/queue_print.c index 53ddfb689..d149e8b6f 100644 --- a/src/queue_print.c +++ b/src/queue_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -41,6 +41,10 @@ queue_print_song_info(struct client *client, const struct queue *queue, song_print_info(client, queue_get(queue, position)); client_printf(client, "Pos: %u\nId: %u\n", position, queue_position_to_id(queue, position)); + + uint8_t priority = queue_get_priority_at_position(queue, position); + if (priority != 0) + client_printf(client, "Prio: %u\n", priority); } void diff --git a/src/queue_print.h b/src/queue_print.h index d754a9673..371e20416 100644 --- a/src/queue_print.h +++ b/src/queue_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 afe04ca2d..a7c511c0e 100644 --- a/src/queue_save.c +++ b/src/queue_save.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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.h b/src/queue_save.h index 287683390..5526d615d 100644 --- a/src/queue_save.h +++ b/src/queue_save.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/refcount.h b/src/refcount.h index 87a2715a4..a882d76b0 100644 --- a/src/refcount.h +++ b/src/refcount.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * Redistribution and use in source and binary forms, with or without @@ -37,6 +37,7 @@ #define MPD_REFCOUNT_H #include <glib.h> +#include <stdbool.h> struct refcount { gint n; diff --git a/src/replay_gain_ape.c b/src/replay_gain_ape.c index 9ae47468f..0b59e3c02 100644 --- a/src/replay_gain_ape.c +++ b/src/replay_gain_ape.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/replay_gain_ape.h b/src/replay_gain_ape.h index 8525ac85e..35760a0aa 100644 --- a/src/replay_gain_ape.h +++ b/src/replay_gain_ape.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/replay_gain_config.c b/src/replay_gain_config.c index bbfe127a7..2181387b7 100644 --- a/src/replay_gain_config.c +++ b/src/replay_gain_config.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/replay_gain_config.h b/src/replay_gain_config.h index 8fb77a5f6..18747cef2 100644 --- a/src/replay_gain_config.h +++ b/src/replay_gain_config.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/replay_gain_info.c b/src/replay_gain_info.c index 3b4ab4577..1f09e7a1a 100644 --- a/src/replay_gain_info.c +++ b/src/replay_gain_info.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/replay_gain_info.h b/src/replay_gain_info.h index 83b46df84..9097c3e02 100644 --- a/src/replay_gain_info.h +++ b/src/replay_gain_info.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/resolver.c b/src/resolver.c new file mode 100644 index 000000000..5d8d299c6 --- /dev/null +++ b/src/resolver.c @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "resolver.h" +#include "glib_compat.h" + +#ifndef G_OS_WIN32 +#include <sys/socket.h> +#include <netdb.h> +#else /* G_OS_WIN32 */ +#include <ws2tcpip.h> +#include <winsock.h> +#endif /* G_OS_WIN32 */ + +#include <string.h> + +char * +sockaddr_to_string(const struct sockaddr *sa, size_t length, GError **error) +{ +#if defined(HAVE_IPV6) && defined(IN6_IS_ADDR_V4MAPPED) + const struct sockaddr_in6 *a6 = (const struct sockaddr_in6 *)sa; + struct sockaddr_in a4; +#endif + int ret; + char host[NI_MAXHOST], serv[NI_MAXSERV]; + +#if defined(HAVE_IPV6) && defined(IN6_IS_ADDR_V4MAPPED) + if (sa->sa_family == AF_INET6 && + IN6_IS_ADDR_V4MAPPED(&a6->sin6_addr)) { + /* convert "::ffff:127.0.0.1" to "127.0.0.1" */ + + memset(&a4, 0, sizeof(a4)); + a4.sin_family = AF_INET; + memcpy(&a4.sin_addr, ((const char *)&a6->sin6_addr) + 12, + sizeof(a4.sin_addr)); + a4.sin_port = a6->sin6_port; + + sa = (const struct sockaddr *)&a4; + length = sizeof(a4); + } +#endif + + ret = getnameinfo(sa, length, host, sizeof(host), serv, sizeof(serv), + NI_NUMERICHOST|NI_NUMERICSERV); + if (ret != 0) { + g_set_error(error, g_quark_from_static_string("netdb"), ret, + "%s", gai_strerror(ret)); + return NULL; + } + +#ifdef HAVE_UN + if (sa->sa_family == AF_UNIX) + /* "serv" contains corrupt information with unix + sockets */ + return g_strdup(host); +#endif + +#ifdef HAVE_IPV6 + if (strchr(host, ':') != NULL) + return g_strconcat("[", host, "]:", serv, NULL); +#endif + + return g_strconcat(host, ":", serv, NULL); +} + +struct addrinfo * +resolve_host_port(const char *host_port, unsigned default_port, + int flags, int socktype, + GError **error_r) +{ + char *p = g_strdup(host_port); + const char *host = p, *port = NULL; + + if (host_port[0] == '[') { + /* IPv6 needs enclosing square braces, to + differentiate between IP colons and the port + separator */ + + char *q = strchr(p + 1, ']'); + if (q != NULL && q[1] == ':' && q[2] != 0) { + *q = 0; + ++host; + port = q + 2; + } + } + + if (port == NULL) { + /* port is after the colon, but only if it's the only + colon (don't split IPv6 addresses) */ + + char *q = strchr(p, ':'); + if (q != NULL && q[1] != 0 && strchr(q + 1, ':') == NULL) { + *q = 0; + port = q + 1; + } + } + + char buffer[32]; + if (port == NULL && default_port != 0) { + g_snprintf(buffer, sizeof(buffer), "%u", default_port); + port = buffer; + } + + if ((flags & AI_PASSIVE) != 0 && strcmp(host, "*") == 0) + host = NULL; + + const struct addrinfo hints = { + .ai_flags = flags, + .ai_family = AF_UNSPEC, + .ai_socktype = socktype, + }; + + struct addrinfo *ai; + int ret = getaddrinfo(host, port, &hints, &ai); + g_free(p); + if (ret != 0) { + g_set_error(error_r, resolver_quark(), ret, + "Failed to look up '%s': %s", + host_port, gai_strerror(ret)); + return NULL; + } + + return ai; +} diff --git a/src/resolver.h b/src/resolver.h new file mode 100644 index 000000000..e5ad06754 --- /dev/null +++ b/src/resolver.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_RESOLVER_H +#define MPD_RESOLVER_H + +#include <glib.h> + +struct sockaddr; +struct addrinfo; + +G_GNUC_CONST +static inline GQuark +resolver_quark(void) +{ + return g_quark_from_static_string("resolver"); +} + +/** + * Converts the specified socket address into a string in the form + * "IP:PORT". The return value must be freed with g_free() when you + * don't need it anymore. + * + * @param sa the sockaddr struct + * @param length the length of #sa in bytes + * @param error location to store the error occurring, or NULL to + * ignore errors + */ +G_GNUC_MALLOC +char * +sockaddr_to_string(const struct sockaddr *sa, size_t length, GError **error); + +/** + * Resolve a specification in the form "host", "host:port", + * "[host]:port". This is a convenience wrapper for getaddrinfo(). + * + * @param default_port a default port number that will be used if none + * is given in the string (if applicable); pass 0 to go without a + * default + * @return an #addrinfo linked list that must be freed with + * freeaddrinfo(), or NULL on error + */ +struct addrinfo * +resolve_host_port(const char *host_port, unsigned default_port, + int flags, int socktype, + GError **error_r); + +#endif diff --git a/src/riff.c b/src/riff.c index 2e8648ff6..9ee916971 100644 --- a/src/riff.c +++ b/src/riff.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -57,7 +57,7 @@ riff_seek_id3(FILE *file) ret = fstat(fileno(file), &st); if (ret < 0) { g_warning("Failed to stat file descriptor: %s", - strerror(errno)); + g_strerror(errno)); return 0; } diff --git a/src/riff.h b/src/riff.h index bfcb69a7d..7b35e092a 100644 --- a/src/riff.h +++ b/src/riff.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/rtsp_client.c b/src/rtsp_client.c new file mode 100644 index 000000000..ea993a163 --- /dev/null +++ b/src/rtsp_client.c @@ -0,0 +1,732 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Based on the RTSP client by Shiro Ninomiya <shiron@snino.com> + */ + +#include "config.h" +#include "rtsp_client.h" +#include "tcp_socket.h" +#include "fd_util.h" +#include "glib_compat.h" + +#include <assert.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> +#include <sys/time.h> + +#ifdef WIN32 +#include <ws2tcpip.h> +#include <winsock.h> +#else +#include <arpa/inet.h> +#include <sys/socket.h> +#include <netdb.h> +#endif + +/* + * Free all memory associated with key_data + */ +void +free_kd(struct key_data *kd) +{ + struct key_data *iter = kd; + while (iter) { + g_free(iter->key); + g_free(iter->data); + iter = iter->next; + g_free(kd); + kd = iter; + } +} + +/* + * key_data type data look up + */ +char * +kd_lookup(struct key_data *kd, const char *key) +{ + while (kd) { + if (!strcmp(kd->key, key)) { + return kd->data; + } + kd = kd->next; + } + return NULL; +} + +struct rtspcl_data * +rtspcl_open(void) +{ + struct rtspcl_data *rtspcld; + rtspcld = g_new0(struct rtspcl_data, 1); + rtspcld->mutex = g_mutex_new(); + rtspcld->cond = g_cond_new(); + rtspcld->received_lines = g_queue_new(); + rtspcld->useragent = "RTSPClient"; + return rtspcld; +} + +/* bind an opened socket to specified hostname and port. + * if hostname=NULL, use INADDR_ANY. + * if *port=0, use dynamically assigned port + */ +static int bind_host(int sd, char *hostname, unsigned long ulAddr, + unsigned short *port, GError **error_r) +{ + struct sockaddr_in my_addr; + socklen_t nlen = sizeof(struct sockaddr); + struct hostent *h; + + memset(&my_addr, 0, sizeof(my_addr)); + /* use specified hostname */ + if (hostname) { + /* get server IP address (no check if input is IP address or DNS name) */ + h = gethostbyname(hostname); + if (h == NULL) { + if (strstr(hostname, "255.255.255.255") == hostname) { + my_addr.sin_addr.s_addr=-1; + } else { + if ((my_addr.sin_addr.s_addr = inet_addr(hostname)) == 0xFFFFFFFF) { + g_set_error(error_r, rtsp_client_quark(), 0, + "failed to resolve host '%s'", + hostname); + return -1; + } + } + my_addr.sin_family = AF_INET; + } else { + my_addr.sin_family = h->h_addrtype; + memcpy((char *) &my_addr.sin_addr.s_addr, + h->h_addr_list[0], h->h_length); + } + } else { + // if hostname=NULL, use INADDR_ANY + if (ulAddr) + my_addr.sin_addr.s_addr = ulAddr; + else + my_addr.sin_addr.s_addr = htonl(INADDR_ANY); + my_addr.sin_family = AF_INET; + } + + /* bind a specified port */ + my_addr.sin_port = htons(*port); + + if (bind(sd, (struct sockaddr *) &my_addr, sizeof(my_addr)) < 0) { + g_set_error(error_r, rtsp_client_quark(), errno, + "failed to bind socket: %s", + g_strerror(errno)); + return -1; + } + + if (*port == 0) { + getsockname(sd, (struct sockaddr *) &my_addr, &nlen); + *port = ntohs(my_addr.sin_port); + } + + return 0; +} + +/* + * open tcp port + */ +static int +open_tcp_socket(char *hostname, unsigned short *port, + GError **error_r) +{ + int sd; + + /* socket creation */ + sd = socket(AF_INET, SOCK_STREAM, 0); + if (sd < 0) { + g_set_error(error_r, rtsp_client_quark(), errno, + "failed to create TCP socket: %s", + g_strerror(errno)); + return -1; + } + if (bind_host(sd, hostname, 0, port, error_r)) { + close_socket(sd); + return -1; + } + + return sd; +} + +static bool +get_sockaddr_by_host(const char *host, short destport, + struct sockaddr_in *addr, + GError **error_r) +{ + struct hostent *h; + + h = gethostbyname(host); + if (h) { + addr->sin_family = h->h_addrtype; + memcpy((char *) &addr->sin_addr.s_addr, h->h_addr_list[0], h->h_length); + } else { + addr->sin_family = AF_INET; + if ((addr->sin_addr.s_addr=inet_addr(host))==0xFFFFFFFF) { + g_set_error(error_r, rtsp_client_quark(), 0, + "failed to resolve host '%s'", host); + return false; + } + } + addr->sin_port = htons(destport); + return true; +} + +/* + * create tcp connection + * as long as the socket is not non-blocking, this can block the process + * nsport is network byte order + */ +static bool +get_tcp_connect(int sd, struct sockaddr_in dest_addr, GError **error_r) +{ + if (connect(sd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr))){ + g_usleep(100000); + // try one more time + if (connect(sd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr))) { + g_set_error(error_r, rtsp_client_quark(), errno, + "failed to connect to %s:%d: %s", + inet_ntoa(dest_addr.sin_addr), + ntohs(dest_addr.sin_port), + g_strerror(errno)); + return false; + } + } + return true; +} + +static bool +get_tcp_connect_by_host(int sd, const char *host, short destport, + GError **error_r) +{ + struct sockaddr_in addr; + + return get_sockaddr_by_host(host, destport, &addr, error_r) && + get_tcp_connect(sd, addr, error_r); +} + +static void +rtsp_client_flush_received(struct rtspcl_data *rtspcld) +{ + char *line; + while ((line = g_queue_pop_head(rtspcld->received_lines)) != NULL) + g_free(line); +} + +static size_t +rtsp_client_socket_data(const void *_data, size_t length, void *ctx) +{ + struct rtspcl_data *rtspcld = ctx; + + g_mutex_lock(rtspcld->mutex); + + if (rtspcld->tcp_socket == NULL) { + g_mutex_unlock(rtspcld->mutex); + return 0; + } + + const bool was_empty = g_queue_is_empty(rtspcld->received_lines); + bool added = false; + const char *data = _data, *end = data + length, *p = data, *eol; + while ((eol = memchr(p, '\n', end - p)) != NULL) { + const char *next = eol + 1; + + if (rtspcld->received_lines->length < 64) { + if (eol > p && eol[-1] == '\r') + --eol; + + g_queue_push_tail(rtspcld->received_lines, + g_strndup(p, eol - p)); + added = true; + } + + p = next; + } + + if (was_empty && added) + g_cond_broadcast(rtspcld->cond); + + g_mutex_unlock(rtspcld->mutex); + + return p - data; +} + +static void +rtsp_client_socket_error(GError *error, void *ctx) +{ + struct rtspcl_data *rtspcld = ctx; + + g_warning("%s", error->message); + g_error_free(error); + + g_mutex_lock(rtspcld->mutex); + + rtsp_client_flush_received(rtspcld); + + struct tcp_socket *s = rtspcld->tcp_socket; + rtspcld->tcp_socket = NULL; + + g_cond_broadcast(rtspcld->cond); + + g_mutex_unlock(rtspcld->mutex); + + if (s != NULL) + tcp_socket_free(s); +} + +static void +rtsp_client_socket_disconnected(void *ctx) +{ + struct rtspcl_data *rtspcld = ctx; + + g_mutex_lock(rtspcld->mutex); + + rtsp_client_flush_received(rtspcld); + + struct tcp_socket *s = rtspcld->tcp_socket; + rtspcld->tcp_socket = NULL; + + g_cond_broadcast(rtspcld->cond); + + g_mutex_unlock(rtspcld->mutex); + + if (s != NULL) + tcp_socket_free(s); +} + +static const struct tcp_socket_handler rtsp_client_socket_handler = { + .data = rtsp_client_socket_data, + .error = rtsp_client_socket_error, + .disconnected = rtsp_client_socket_disconnected, +}; + +bool +rtspcl_connect(struct rtspcl_data *rtspcld, const char *host, short destport, + const char *sid, GError **error_r) +{ + assert(rtspcld->tcp_socket == NULL); + + unsigned short myport = 0; + struct sockaddr_in name; + socklen_t namelen = sizeof(name); + + int fd = open_tcp_socket(NULL, &myport, error_r); + if (fd < 0) + return false; + + if (!get_tcp_connect_by_host(fd, host, destport, error_r)) + return false; + + getsockname(fd, (struct sockaddr*)&name, &namelen); + memcpy(&rtspcld->local_addr, &name.sin_addr,sizeof(struct in_addr)); + sprintf(rtspcld->url, "rtsp://%s/%s", inet_ntoa(name.sin_addr), sid); + getpeername(fd, (struct sockaddr*)&name, &namelen); + memcpy(&rtspcld->host_addr, &name.sin_addr, sizeof(struct in_addr)); + + rtspcld->tcp_socket = tcp_socket_new(fd, &rtsp_client_socket_handler, + rtspcld); + + return true; +} + +static void +rtspcl_disconnect(struct rtspcl_data *rtspcld) +{ + g_mutex_lock(rtspcld->mutex); + rtsp_client_flush_received(rtspcld); + g_mutex_unlock(rtspcld->mutex); + + if (rtspcld->tcp_socket != NULL) { + tcp_socket_free(rtspcld->tcp_socket); + rtspcld->tcp_socket = NULL; + } +} + +static void +rtspcl_remove_all_exthds(struct rtspcl_data *rtspcld) +{ + free_kd(rtspcld->exthds); + rtspcld->exthds = NULL; +} + +void +rtspcl_close(struct rtspcl_data *rtspcld) +{ + rtspcl_disconnect(rtspcld); + g_queue_free(rtspcld->received_lines); + rtspcl_remove_all_exthds(rtspcld); + g_free(rtspcld->session); + g_cond_free(rtspcld->cond); + g_mutex_free(rtspcld->mutex); + g_free(rtspcld); +} + +void +rtspcl_add_exthds(struct rtspcl_data *rtspcld, const char *key, char *data) +{ + struct key_data *new_kd; + new_kd = g_new(struct key_data, 1); + new_kd->key = g_strdup(key); + new_kd->data = g_strdup(data); + new_kd->next = NULL; + if (!rtspcld->exthds) { + rtspcld->exthds = new_kd; + } else { + struct key_data *iter = rtspcld->exthds; + while (iter->next) { + iter = iter->next; + } + iter->next = new_kd; + } +} + +/* + * read one line from the file descriptor + * timeout: msec unit, -1 for infinite + * if CR comes then following LF is expected + * returned string in line is always null terminated, maxlen-1 is maximum string length + */ +static int +read_line(struct rtspcl_data *rtspcld, char *line, int maxlen, + int timeout) +{ + g_mutex_lock(rtspcld->mutex); + + GTimeVal end_time; + if (timeout >= 0) { + g_get_current_time(&end_time); + + end_time.tv_sec += timeout / 1000; + timeout %= 1000; + end_time.tv_usec = timeout * 1000; + if (end_time.tv_usec > 1000000) { + end_time.tv_usec -= 1000000; + ++end_time.tv_sec; + } + } + + while (true) { + if (!g_queue_is_empty(rtspcld->received_lines)) { + /* success, copy to buffer */ + + char *p = g_queue_pop_head(rtspcld->received_lines); + g_mutex_unlock(rtspcld->mutex); + + g_strlcpy(line, p, maxlen); + g_free(p); + + return strlen(line); + } + + if (rtspcld->tcp_socket == NULL) { + /* error */ + g_mutex_unlock(rtspcld->mutex); + return -1; + } + + if (timeout < 0) { + g_cond_wait(rtspcld->cond, rtspcld->mutex); + } else if (!g_cond_timed_wait(rtspcld->cond, rtspcld->mutex, + &end_time)) { + g_mutex_unlock(rtspcld->mutex); + return 0; + } + } +} + +/* + * send RTSP request, and get response if it's needed + * if this gets a success, *kd is allocated or reallocated (if *kd is not NULL) + */ +bool +exec_request(struct rtspcl_data *rtspcld, const char *cmd, + const char *content_type, const char *content, + int get_response, + const struct key_data *hds, struct key_data **kd, + GError **error_r) +{ + char line[1024]; + char req[1024]; + char reql[128]; + const char delimiters[] = " "; + char *token, *dp; + int dsize = 0; + int timeout = 5000; // msec unit + + if (!rtspcld) { + g_set_error_literal(error_r, rtsp_client_quark(), 0, + "not connected"); + return false; + } + + sprintf(req, "%s %s RTSP/1.0\r\nCSeq: %d\r\n", cmd, rtspcld->url, ++rtspcld->cseq ); + + if ( rtspcld->session != NULL ) { + sprintf(reql,"Session: %s\r\n", rtspcld->session ); + g_strlcat(req, reql, sizeof(req)); + } + + const struct key_data *hd_iter = hds; + while (hd_iter) { + sprintf(reql, "%s: %s\r\n", hd_iter->key, hd_iter->data); + g_strlcat(req, reql, sizeof(req)); + hd_iter = hd_iter->next; + } + + if (content_type && content) { + sprintf(reql, "Content-Type: %s\r\nContent-Length: %d\r\n", + content_type, (int) strlen(content)); + g_strlcat(req, reql, sizeof(req)); + } + + sprintf(reql, "User-Agent: %s\r\n", rtspcld->useragent); + g_strlcat(req, reql, sizeof(req)); + + hd_iter = rtspcld->exthds; + while (hd_iter) { + sprintf(reql, "%s: %s\r\n", hd_iter->key, hd_iter->data); + g_strlcat(req, reql, sizeof(req)); + hd_iter = hd_iter->next; + } + g_strlcat(req, "\r\n", sizeof(req)); + + if (content_type && content) + g_strlcat(req, content, sizeof(req)); + + if (!tcp_socket_send(rtspcld->tcp_socket, req, strlen(req))) { + g_set_error(error_r, rtsp_client_quark(), errno, + "write error: %s", + g_strerror(errno)); + return false; + } + + if (!get_response) return true; + + if (read_line(rtspcld, line, sizeof(line), timeout) <= 0) { + g_set_error_literal(error_r, rtsp_client_quark(), 0, + "request failed"); + return false; + } + + token = strtok(line, delimiters); + token = strtok(NULL, delimiters); + if (token == NULL) { + g_set_error_literal(error_r, rtsp_client_quark(), 0, + "request failed"); + return false; + } + + if (strcmp(token, "200") != 0) { + g_set_error(error_r, rtsp_client_quark(), 0, + "request failed: %s", token); + return false; + } + + /* if the caller isn't interested in response headers, put + them on the trash, which is freed before returning from + this function */ + struct key_data *trash = NULL; + if (kd == NULL) + kd = &trash; + + struct key_data *cur_kd = *kd; + + struct key_data *new_kd = NULL; + while (read_line(rtspcld, line, sizeof(line), timeout) > 0) { + timeout = 1000; // once it started, it shouldn't take a long time + if (new_kd != NULL && line[0] == ' ') { + const char *j = line; + while (*j == ' ') + ++j; + + dsize += strlen(j); + new_kd->data = g_realloc(new_kd->data, dsize); + strcat(new_kd->data, j); + continue; + } + dp = strstr(line, ":"); + if (!dp) { + free_kd(*kd); + *kd = NULL; + + g_set_error_literal(error_r, rtsp_client_quark(), 0, + "request failed, bad header"); + return false; + } + + *dp++ = 0; + new_kd = g_new(struct key_data, 1); + new_kd->key = g_strdup(line); + dsize = strlen(dp) + 1; + new_kd->data = g_strdup(dp); + new_kd->next = NULL; + if (cur_kd == NULL) { + cur_kd = *kd = new_kd; + } else { + cur_kd->next = new_kd; + cur_kd = new_kd; + } + } + + free_kd(trash); + + return true; +} + +bool +rtspcl_set_parameter(struct rtspcl_data *rtspcld, const char *parameter, + GError **error_r) +{ + return exec_request(rtspcld, "SET_PARAMETER", "text/parameters", + parameter, 1, NULL, NULL, error_r); +} + +void +rtspcl_set_useragent(struct rtspcl_data *rtspcld, const char *name) +{ + rtspcld->useragent = name; +} + +bool +rtspcl_announce_sdp(struct rtspcl_data *rtspcld, const char *sdp, + GError **error_r) +{ + return exec_request(rtspcld, "ANNOUNCE", "application/sdp", sdp, 1, + NULL, NULL, error_r); +} + +bool +rtspcl_setup(struct rtspcl_data *rtspcld, struct key_data **kd, + int control_port, int ntp_port, + GError **error_r) +{ + struct key_data *rkd = NULL, hds; + const char delimiters[] = ";"; + char *buf = NULL; + char *token, *pc; + int rval = false; + + static char transport_key[] = "Transport"; + + char transport_value[256]; + snprintf(transport_value, sizeof(transport_value), + "RTP/AVP/UDP;unicast;interleaved=0-1;mode=record;control_port=%d;timing_port=%d", + control_port, ntp_port); + + hds.key = transport_key; + hds.data = transport_value; + hds.next = NULL; + if (!exec_request(rtspcld, "SETUP", NULL, NULL, 1, + &hds, &rkd, error_r)) + return false; + + if (!(rtspcld->session = g_strdup(kd_lookup(rkd, "Session")))) { + g_set_error_literal(error_r, rtsp_client_quark(), 0, + "no session in response"); + goto erexit; + } + if (!(rtspcld->transport = kd_lookup(rkd, "Transport"))) { + g_set_error_literal(error_r, rtsp_client_quark(), 0, + "no transport in response"); + goto erexit; + } + buf = g_strdup(rtspcld->transport); + token = strtok(buf, delimiters); + rtspcld->server_port = 0; + rtspcld->control_port = 0; + while (token) { + if ((pc = strstr(token, "="))) { + *pc = 0; + if (!strcmp(token,"server_port")) { + rtspcld->server_port=atoi(pc + 1); + } + if (!strcmp(token,"control_port")) { + rtspcld->control_port=atoi(pc + 1); + } + } + token = strtok(NULL, delimiters); + } + if (rtspcld->server_port == 0) { + g_set_error_literal(error_r, rtsp_client_quark(), 0, + "no server_port in response"); + goto erexit; + } + if (rtspcld->control_port == 0) { + g_set_error_literal(error_r, rtsp_client_quark(), 0, + "no control_port in response"); + goto erexit; + } + rval = true; + erexit: + g_free(buf); + + if (!rval || kd == NULL) { + free_kd(rkd); + rkd = NULL; + } + + if (kd != NULL) + *kd = rkd; + + return rval; +} + +bool +rtspcl_record(struct rtspcl_data *rtspcld, + int seq_num, int rtptime, + GError **error_r) +{ + if (!rtspcld->session) { + g_set_error_literal(error_r, rtsp_client_quark(), 0, + "no session in progress"); + return false; + } + + char buf[128]; + sprintf(buf, "seq=%d,rtptime=%u", seq_num, rtptime); + + struct key_data rtp; + static char rtp_key[] = "RTP-Info"; + rtp.key = rtp_key; + rtp.data = buf; + rtp.next = NULL; + + struct key_data range; + static char range_key[] = "Range"; + range.key = range_key; + static char range_value[] = "npt=0-"; + range.data = range_value; + range.next = &rtp; + + return exec_request(rtspcld, "RECORD", NULL, NULL, 1, &range, + NULL, error_r); +} + +char * +rtspcl_local_ip(struct rtspcl_data *rtspcld) +{ + return inet_ntoa(rtspcld->local_addr); +} diff --git a/src/rtsp_client.h b/src/rtsp_client.h new file mode 100644 index 000000000..21660e609 --- /dev/null +++ b/src/rtsp_client.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Based on the RTSP client by Shiro Ninomiya <shiron@snino.com> + */ + +#ifndef MPD_RTSP_CLIENT_H +#define MPD_RTSP_CLIENT_H + +#include <stdbool.h> +#include <glib.h> + +#ifdef WIN32 +#include <ws2tcpip.h> +#include <winsock.h> +#else +#include <netinet/in.h> +#endif + +struct key_data { + char *key; + char *data; + struct key_data *next; +}; + +struct rtspcl_data { + GMutex *mutex; + GCond *cond; + + GQueue *received_lines; + + struct tcp_socket *tcp_socket; + + char url[128]; + int cseq; + struct key_data *exthds; + char *session; + char *transport; + unsigned short server_port; + unsigned short control_port; + struct in_addr host_addr; + struct in_addr local_addr; + const char *useragent; + +}; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +rtsp_client_quark(void) +{ + return g_quark_from_static_string("rtsp_client"); +} + +void +free_kd(struct key_data *kd); + +char * +kd_lookup(struct key_data *kd, const char *key); + +G_GNUC_MALLOC +struct rtspcl_data * +rtspcl_open(void); + +bool +rtspcl_connect(struct rtspcl_data *rtspcld, const char *host, short destport, + const char *sid, GError **error_r); + +void +rtspcl_close(struct rtspcl_data *rtspcld); + +void +rtspcl_add_exthds(struct rtspcl_data *rtspcld, const char *key, char *data); + +bool +exec_request(struct rtspcl_data *rtspcld, const char *cmd, + const char *content_type, const char *content, + int get_response, + const struct key_data *hds, struct key_data **kd, + GError **error_r); + +bool +rtspcl_set_parameter(struct rtspcl_data *rtspcld, const char *parameter, + GError **error_r); + +void +rtspcl_set_useragent(struct rtspcl_data *rtspcld, const char *name); + +bool +rtspcl_announce_sdp(struct rtspcl_data *rtspcld, const char *sdp, + GError **error_r); + +bool +rtspcl_setup(struct rtspcl_data *rtspcld, struct key_data **kd, + int control_port, int ntp_port, + GError **error_r); + +bool +rtspcl_record(struct rtspcl_data *rtspcld, + int seq_num, int rtptime, + GError **error_r); + +char * +rtspcl_local_ip(struct rtspcl_data *rtspcld); + +#endif diff --git a/src/server_socket.c b/src/server_socket.c index 482e0cda1..e4b5e3ece 100644 --- a/src/server_socket.c +++ b/src/server_socket.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,8 +18,14 @@ */ #include "config.h" + +#ifdef HAVE_STRUCT_UCRED +#define _GNU_SOURCE 1 +#endif + #include "server_socket.h" #include "socket_util.h" +#include "resolver.h" #include "fd_util.h" #include "glib_compat.h" #include "glib_socket.h" @@ -155,16 +161,35 @@ server_socket_in_event(G_GNUC_UNUSED GIOChannel *source, size_t address_length = sizeof(address); int fd = accept_cloexec_nonblock(s->fd, (struct sockaddr*)&address, &address_length); - if (fd >= 0) + if (fd >= 0) { + if (socket_keepalive(fd)) + g_warning("Could not set TCP keepalive option: %s", + g_strerror(errno)); s->parent->callback(fd, (const struct sockaddr*)&address, address_length, get_remote_uid(fd), s->parent->callback_ctx); - else + } else { g_warning("accept() failed: %s", g_strerror(errno)); + } return true; } +static void +set_fd(struct one_socket *s, int fd) +{ + assert(s != NULL); + assert(s->fd < 0); + assert(fd >= 0); + + s->fd = fd; + + GIOChannel *channel = g_io_channel_new_socket(s->fd); + s->source_id = g_io_add_watch(channel, G_IO_IN, + server_socket_in_event, s); + g_io_channel_unref(channel); +} + bool server_socket_open(struct server_socket *ss, GError **error_r) { @@ -183,10 +208,11 @@ server_socket_open(struct server_socket *ss, GError **error_r) } 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) { + int fd = socket_bind_listen(s->address.sa_family, + SOCK_STREAM, 0, + &s->address, s->address_length, 5, + &error); + if (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); @@ -218,10 +244,7 @@ server_socket_open(struct server_socket *ss, GError **error_r) /* register in the GLib main loop */ - GIOChannel *channel = g_io_channel_new_socket(s->fd); - s->source_id = g_io_add_watch(channel, G_IO_IN, - server_socket_in_event, s); - g_io_channel_unref(channel); + set_fd(s, fd); /* mark this socket as "good", and clear previous errors */ @@ -276,6 +299,36 @@ one_socket_new(unsigned serial, const struct sockaddr *address, return s; } +bool +server_socket_add_fd(struct server_socket *ss, int fd, GError **error_r) +{ + assert(ss != NULL); + assert(ss->sockets_tail_r != NULL); + assert(*ss->sockets_tail_r == NULL); + assert(fd >= 0); + + struct sockaddr_storage address; + socklen_t address_length; + if (getsockname(fd, (struct sockaddr *)&address, + &address_length) < 0) { + g_set_error(error_r, server_socket_quark(), errno, + "Failed to get socket address: %s", + g_strerror(errno)); + return false; + } + + struct one_socket *s = one_socket_new(ss->next_serial, + (struct sockaddr *)&address, + address_length); + s->parent = ss; + *ss->sockets_tail_r = s; + ss->sockets_tail_r = &s->next; + + set_fd(s, fd); + + return true; +} + static struct one_socket * server_socket_add_address(struct server_socket *ss, const struct sockaddr *address, @@ -369,24 +422,11 @@ 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)); + struct addrinfo *ai = resolve_host_port(hostname, port, + AI_PASSIVE, SOCK_STREAM, + error_r); + if (ai == NULL) 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); diff --git a/src/server_socket.h b/src/server_socket.h index ae0ce0c8d..7caa4bbf2 100644 --- a/src/server_socket.h +++ b/src/server_socket.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -44,10 +44,18 @@ void server_socket_close(struct server_socket *ss); /** + * Add a socket descriptor that is accepting connections. After this + * has been called, don't call server_socket_open(), because the + * socket is already open. + */ +bool +server_socket_add_fd(struct server_socket *ss, int fd, GError **error_r); + +/** * 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 + * @param error_r location to store the error occurring, or NULL to * ignore errors * @return true on success */ @@ -61,7 +69,7 @@ server_socket_add_port(struct server_socket *ss, unsigned port, * * @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 + * @param error_r location to store the error occurring, or NULL to * ignore errors * @return true on success */ @@ -73,7 +81,7 @@ server_socket_add_host(struct server_socket *ss, const char *hostname, * 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 + * @param error_r location to store the error occurring, or NULL to * ignore errors * @return true on success */ diff --git a/src/sig_handlers.c b/src/sig_handlers.c index 8aa85cf88..b23f9e778 100644 --- a/src/sig_handlers.c +++ b/src/sig_handlers.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/sig_handlers.h b/src/sig_handlers.h index a578cd243..32e9bad95 100644 --- a/src/sig_handlers.h +++ b/src/sig_handlers.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 d1651066f..a06a0cbd5 100644 --- a/src/socket_util.c +++ b/src/socket_util.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,7 +26,6 @@ #ifndef G_OS_WIN32 #include <sys/socket.h> -#include <netdb.h> #else /* G_OS_WIN32 */ #include <ws2tcpip.h> #include <winsock.h> @@ -42,55 +41,6 @@ listen_quark(void) return g_quark_from_static_string("listen"); } -char * -sockaddr_to_string(const struct sockaddr *sa, size_t length, GError **error) -{ -#if defined(HAVE_IPV6) && defined(IN6_IS_ADDR_V4MAPPED) - const struct sockaddr_in6 *a6 = (const struct sockaddr_in6 *)sa; - struct sockaddr_in a4; -#endif - int ret; - char host[NI_MAXHOST], serv[NI_MAXSERV]; - -#if defined(HAVE_IPV6) && defined(IN6_IS_ADDR_V4MAPPED) - if (sa->sa_family == AF_INET6 && - IN6_IS_ADDR_V4MAPPED(&a6->sin6_addr)) { - /* convert "::ffff:127.0.0.1" to "127.0.0.1" */ - - memset(&a4, 0, sizeof(a4)); - a4.sin_family = AF_INET; - memcpy(&a4.sin_addr, ((const char *)&a6->sin6_addr) + 12, - sizeof(a4.sin_addr)); - a4.sin_port = a6->sin6_port; - - sa = (const struct sockaddr *)&a4; - length = sizeof(a4); - } -#endif - - ret = getnameinfo(sa, length, host, sizeof(host), serv, sizeof(serv), - NI_NUMERICHOST|NI_NUMERICSERV); - if (ret != 0) { - g_set_error(error, g_quark_from_static_string("netdb"), ret, - "%s", gai_strerror(ret)); - return NULL; - } - -#ifdef HAVE_UN - if (sa->sa_family == AF_UNIX) - /* "serv" contains corrupt information with unix - sockets */ - return g_strdup(host); -#endif - -#ifdef HAVE_IPV6 - if (strchr(host, ':') != NULL) - return g_strconcat("[", host, "]:", serv, NULL); -#endif - - return g_strconcat(host, ":", serv, NULL); -} - int socket_bind_listen(int domain, int type, int protocol, const struct sockaddr *address, size_t address_length, @@ -99,9 +49,6 @@ socket_bind_listen(int domain, int type, int protocol, { int fd, ret; const int reuse = 1; -#ifdef HAVE_STRUCT_UCRED - int passcred = 1; -#endif fd = socket_cloexec_nonblock(domain, type, protocol); if (fd < 0) { @@ -110,14 +57,8 @@ socket_bind_listen(int domain, int type, int protocol, return -1; } -#ifdef WIN32 - const char *optval = (const char *)&reuse; -#else - const void *optval = &reuse; -#endif - ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, - optval, sizeof(reuse)); + (const char *) &reuse, sizeof(reuse)); if (ret < 0) { g_set_error(error, listen_quark(), errno, "setsockopt() failed: %s", g_strerror(errno)); @@ -142,8 +83,18 @@ socket_bind_listen(int domain, int type, int protocol, } #ifdef HAVE_STRUCT_UCRED - setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &passcred, sizeof(passcred)); + setsockopt(fd, SOL_SOCKET, SO_PASSCRED, + (const char *) &reuse, sizeof(reuse)); #endif return fd; } + +int +socket_keepalive(int fd) +{ + const int reuse = 1; + + return setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, + (const char *)&reuse, sizeof(reuse)); +} diff --git a/src/socket_util.h b/src/socket_util.h index 7ef081362..93bd27362 100644 --- a/src/socket_util.h +++ b/src/socket_util.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -31,19 +31,6 @@ struct sockaddr; /** - * Converts the specified socket address into a string in the form - * "IP:PORT". The return value must be freed with g_free() when you - * don't need it anymore. - * - * @param sa the sockaddr struct - * @param length the length of #sa in bytes - * @param error location to store the error occuring, or NULL to - * ignore errors - */ -char * -sockaddr_to_string(const struct sockaddr *sa, size_t length, GError **error); - -/** * Creates a socket listening on the specified address. This is a * shortcut for socket(), bind() and listen(). * @@ -53,7 +40,7 @@ sockaddr_to_string(const struct sockaddr *sa, size_t length, GError **error); * @param address the address to listen on * @param address_length the size of #address * @param backlog the backlog parameter for the listen() system call - * @param error location to store the error occuring, or NULL to + * @param error location to store the error occurring, or NULL to * ignore errors * @return the socket file descriptor or -1 on error */ @@ -63,4 +50,7 @@ socket_bind_listen(int domain, int type, int protocol, int backlog, GError **error); +int +socket_keepalive(int fd); + #endif diff --git a/src/song.c b/src/song.c index 13fd476b9..f5cc7c35a 100644 --- a/src/song.c +++ b/src/song.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -61,6 +61,18 @@ song_file_new(const char *path, struct directory *parent) return song_alloc(path, parent); } +struct song * +song_replace_uri(struct song *old_song, const char *uri) +{ + struct song *new_song = song_alloc(uri, old_song->parent); + new_song->tag = old_song->tag; + new_song->mtime = old_song->mtime; + new_song->start_ms = old_song->start_ms; + new_song->end_ms = old_song->end_ms; + g_free(old_song); + return new_song; +} + void song_free(struct song *song) { diff --git a/src/song.h b/src/song.h index 26a1dc806..8b97d45d0 100644 --- a/src/song.h +++ b/src/song.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_SONG_H #define MPD_SONG_H +#include "util/list.h" + #include <stddef.h> #include <stdbool.h> #include <sys/time.h> @@ -28,6 +30,16 @@ #define SONG_TIME "Time: " struct song { + /** + * Pointers to the siblings of this directory within the + * parent directory. It is unused (undefined) if this song is + * not in the database. + * + * This attribute is protected with the global #db_mutex. + * Read access in the update thread does not need protection. + */ + struct list_head siblings; + struct tag *tag; struct directory *parent; time_t mtime; @@ -62,6 +74,15 @@ song_file_new(const char *path, struct directory *parent); struct song * song_file_load(const char *path, struct directory *parent); +/** + * Replaces the URI of a song object. The given song object is + * destroyed, and a newly allocated one is returned. It does not + * update the reference within the parent directory; the caller is + * responsible for doing that. + */ +struct song * +song_replace_uri(struct song *song, const char *uri); + void song_free(struct song *song); diff --git a/src/song_print.c b/src/song_print.c index 16239e03b..fb608a8b2 100644 --- a/src/song_print.c +++ b/src/song_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,7 +20,6 @@ #include "config.h" #include "song_print.h" #include "song.h" -#include "songvec.h" #include "directory.h" #include "tag_print.h" #include "client.h" @@ -94,18 +93,3 @@ song_print_info(struct client *client, struct song *song) if (song->tag) tag_print(client, song->tag); } - -static int -song_print_info_x(struct song *song, void *data) -{ - struct client *client = data; - song_print_info(client, song); - - return 0; -} - -void -songvec_print(struct client *client, const struct songvec *sv) -{ - songvec_for_each(sv, song_print_info_x, client); -} diff --git a/src/song_print.h b/src/song_print.h index cb83f4711..8f1f0cc65 100644 --- a/src/song_print.h +++ b/src/song_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,9 +28,6 @@ void song_print_info(struct client *client, struct song *song); void -songvec_print(struct client *client, const struct songvec *sv); - -void song_print_uri(struct client *client, struct song *song); #endif diff --git a/src/song_save.c b/src/song_save.c index a1a573298..4fcb46e22 100644 --- a/src/song_save.c +++ b/src/song_save.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,6 +24,7 @@ #include "directory.h" #include "tag.h" #include "text_file.h" +#include "string_util.h" #include <glib.h> @@ -58,19 +59,6 @@ song_save(FILE *fp, const struct song *song) fprintf(fp, SONG_END "\n"); } -static int -song_save_callback(struct song *song, void *data) -{ - FILE *fp = data; - song_save(fp, song); - return 0; -} - -void songvec_save(FILE *fp, const struct songvec *sv) -{ - songvec_for_each(sv, song_save_callback, fp); -} - struct song * song_load(FILE *fp, struct directory *parent, const char *uri, GString *buffer, GError **error_r) @@ -96,7 +84,7 @@ song_load(FILE *fp, struct directory *parent, const char *uri, } *colon++ = 0; - value = g_strchug(colon); + value = strchug_fast_c(colon); if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) { if (!song->tag) { @@ -112,6 +100,13 @@ song_load(FILE *fp, struct directory *parent, const char *uri, } song->tag->time = atoi(value); + } else if (strcmp(line, "Playlist") == 0) { + if (!song->tag) { + song->tag = tag_new(); + tag_begin_add(song->tag); + } + + song->tag->has_playlist = strcmp(value, "yes") == 0; } else if (strcmp(line, SONG_MTIME) == 0) { song->mtime = atoi(value); } else if (strcmp(line, "Range") == 0) { diff --git a/src/song_save.h b/src/song_save.h index 03285015a..f6ecbbfeb 100644 --- a/src/song_save.h +++ b/src/song_save.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,20 +27,16 @@ #define SONG_BEGIN "song_begin: " struct song; -struct songvec; struct directory; void song_save(FILE *fp, const struct song *song); -void -songvec_save(FILE *fp, const struct songvec *sv); - /** * 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 + * @param error_r location to store the error occurring, or NULL to * ignore errors * @return true on success, false on error */ diff --git a/src/songvec.c b/src/song_sort.c index 38bcbac88..397d2c7a9 100644 --- a/src/songvec.c +++ b/src/song_sort.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,18 +18,17 @@ */ #include "config.h" -#include "songvec.h" +#include "song_sort.h" #include "song.h" +#include "util/list.h" +#include "util/list_sort.h" #include "tag.h" #include <glib.h> #include <assert.h> -#include <string.h> #include <stdlib.h> -static GMutex *nr_lock = NULL; - static const char * tag_get_value_checked(const struct tag *tag, enum tag_type type) { @@ -89,10 +88,11 @@ compare_tag_item(const struct tag *a, const struct tag *b, enum tag_type type) } /* Only used for sorting/searchin a songvec, not general purpose compares */ -static int songvec_cmp(const void *s1, const void *s2) +static int +song_cmp(G_GNUC_UNUSED void *priv, struct list_head *_a, struct list_head *_b) { - const struct song *a = ((const struct song * const *)s1)[0]; - const struct song *b = ((const struct song * const *)s2)[0]; + const struct song *a = (const struct song *)_a; + const struct song *b = (const struct song *)_b; int ret; /* first sort by album */ @@ -114,117 +114,8 @@ static int songvec_cmp(const void *s1, const void *s2) return g_utf8_collate(a->uri, b->uri); } -static size_t sv_size(const struct songvec *sv) -{ - return sv->nr * sizeof(struct song *); -} - -void songvec_init(void) -{ - g_assert(nr_lock == NULL); - nr_lock = g_mutex_new(); -} - -void songvec_deinit(void) -{ - g_assert(nr_lock != NULL); - g_mutex_free(nr_lock); - nr_lock = NULL; -} - -void songvec_sort(struct songvec *sv) -{ - g_mutex_lock(nr_lock); - qsort(sv->base, sv->nr, sizeof(struct song *), songvec_cmp); - g_mutex_unlock(nr_lock); -} - -struct song * -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]->uri, uri)) - continue; - ret = sv->base[i]; - break; - } - g_mutex_unlock(nr_lock); - return ret; -} - -int -songvec_delete(struct songvec *sv, const struct song *del) -{ - size_t i; - - g_mutex_lock(nr_lock); - for (i = 0; i < sv->nr; ++i) { - if (sv->base[i] != del) - continue; - /* we _don't_ call song_free() here */ - if (!--sv->nr) { - g_free(sv->base); - sv->base = NULL; - } else { - memmove(&sv->base[i], &sv->base[i + 1], - (sv->nr - i) * sizeof(struct song *)); - sv->base = g_realloc(sv->base, sv_size(sv)); - } - g_mutex_unlock(nr_lock); - return i; - } - g_mutex_unlock(nr_lock); - - return -1; /* not found */ -} - void -songvec_add(struct songvec *sv, struct song *add) -{ - g_mutex_lock(nr_lock); - ++sv->nr; - sv->base = g_realloc(sv->base, sv_size(sv)); - sv->base[sv->nr - 1] = add; - g_mutex_unlock(nr_lock); -} - -void songvec_destroy(struct songvec *sv) -{ - g_mutex_lock(nr_lock); - sv->nr = 0; - g_mutex_unlock(nr_lock); - - g_free(sv->base); - sv->base = NULL; -} - -int -songvec_for_each(const struct songvec *sv, - int (*fn)(struct song *, void *), void *arg) +song_list_sort(struct list_head *songs) { - size_t i; - size_t prev_nr; - - g_mutex_lock(nr_lock); - for (i = 0; i < sv->nr; ) { - struct song *song = sv->base[i]; - - assert(song); - assert(*song->uri); - - prev_nr = sv->nr; - g_mutex_unlock(nr_lock); /* fn() may block */ - if (fn(song, arg) < 0) - return -1; - g_mutex_lock(nr_lock); /* sv->nr may change in fn() */ - if (prev_nr == sv->nr) - ++i; - } - g_mutex_unlock(nr_lock); - - return 0; + list_sort(NULL, songs, song_cmp); } diff --git a/src/directory_print.h b/src/song_sort.h index 0933f5a97..ec124cf4a 100644 --- a/src/directory_print.h +++ b/src/song_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2012 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DIRECTORY_PRINT_H -#define MPD_DIRECTORY_PRINT_H +#ifndef MPD_SONG_SORT_H +#define MPD_SONG_SORT_H -struct client; -struct directory; +struct list_head; void -directory_print(struct client *client, const struct directory *directory); +song_list_sort(struct list_head *songs); #endif diff --git a/src/song_sticker.c b/src/song_sticker.c index c3c64c8d1..78025906e 100644 --- a/src/song_sticker.c +++ b/src/song_sticker.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_sticker.h b/src/song_sticker.h index 6318ccf48..20ae68ce9 100644 --- a/src/song_sticker.h +++ b/src/song_sticker.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -68,6 +68,8 @@ sticker_song_get(const struct song *song); * Finds stickers with the specified name below the specified * directory. * + * Caller must lock the #db_mutex. + * * @param directory the base directory to search in * @param name the name of the sticker * @return true on success (even if no sticker was found), false on diff --git a/src/song_update.c b/src/song_update.c index b418b600e..37f502a20 100644 --- a/src/song_update.c +++ b/src/song_update.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,6 +27,7 @@ #include "tag_ape.h" #include "tag_id3.h" #include "tag.h" +#include "tag_handler.h" #include "input_stream.h" #include <glib.h> @@ -65,33 +66,12 @@ song_file_load(const char *path, struct directory *parent) /** * Attempts to load APE or ID3 tags from the specified file. */ -static struct tag * -tag_load_fallback(const char *path) +static bool +tag_scan_fallback(const char *path, + const struct tag_handler *handler, void *handler_ctx) { - 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; + return tag_ape_scan2(path, handler, handler_ctx) || + tag_id3_scan(path, handler, handler_ctx); } bool @@ -131,27 +111,47 @@ song_file_update(struct song *song) song->mtime = st.st_mtime; + GMutex *mutex = NULL; + GCond *cond; +#if !GCC_CHECK_VERSION(4, 2) + /* work around "may be used uninitialized in this function" + false positive */ + cond = NULL; +#endif + do { /* load file tag */ - song->tag = decoder_plugin_tag_dup(plugin, path_fs); - if (song->tag != NULL) + song->tag = tag_new(); + if (decoder_plugin_scan_file(plugin, path_fs, + &full_tag_handler, song->tag)) break; + tag_free(song->tag); + song->tag = NULL; + /* fall back to stream tag */ - if (plugin->stream_tag != NULL) { + if (plugin->scan_stream != NULL) { /* open the input_stream (if not already open) */ - if (is == NULL) - is = input_stream_open(path_fs, NULL); + if (is == NULL) { + mutex = g_mutex_new(); + cond = g_cond_new(); + is = input_stream_open(path_fs, mutex, cond, + NULL); + } /* now try the stream_tag() method */ if (is != NULL) { - song->tag = decoder_plugin_stream_tag(plugin, - is); - if (song->tag != NULL) + song->tag = tag_new(); + if (decoder_plugin_scan_stream(plugin, is, + &full_tag_handler, + song->tag)) break; - input_stream_seek(is, 0, SEEK_SET, NULL); + tag_free(song->tag); + song->tag = NULL; + + input_stream_lock_seek(is, 0, SEEK_SET, NULL); } } @@ -161,8 +161,13 @@ song_file_update(struct song *song) if (is != NULL) input_stream_close(is); + if (mutex != NULL) { + g_cond_free(cond); + g_mutex_free(mutex); + } + if (song->tag != NULL && tag_is_empty(song->tag)) - song->tag = tag_fallback(path_fs, song->tag); + tag_scan_fallback(path_fs, &full_tag_handler, song->tag); g_free(path_fs); return song->tag != NULL; @@ -190,7 +195,7 @@ song_file_update_inarchive(struct song *song) tag_free(song->tag); //accept every file that has music suffix - //because we dont support tag reading throught + //because we don't support tag reading through //input streams song->tag = tag_new(); diff --git a/src/state_file.c b/src/state_file.c index 55af25d5c..de7fa2d02 100644 --- a/src/state_file.c +++ b/src/state_file.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -47,7 +47,7 @@ static unsigned prev_volume_version, prev_output_version, prev_playlist_version; static void -state_file_write(void) +state_file_write(struct player_control *pc) { FILE *fp; @@ -58,23 +58,23 @@ state_file_write(void) fp = fopen(state_file_path, "w"); if (G_UNLIKELY(!fp)) { g_warning("failed to create %s: %s", - state_file_path, strerror(errno)); + state_file_path, g_strerror(errno)); return; } save_sw_volume_state(fp); audio_output_state_save(fp); - playlist_state_save(fp, &g_playlist); + playlist_state_save(fp, &g_playlist, pc); 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); + prev_playlist_version = playlist_state_get_hash(&g_playlist, pc); } static void -state_file_read(void) +state_file_read(struct player_control *pc) { FILE *fp; bool success; @@ -86,7 +86,7 @@ state_file_read(void) fp = fopen(state_file_path, "r"); if (G_UNLIKELY(!fp)) { g_warning("failed to open %s: %s", - state_file_path, strerror(errno)); + state_file_path, g_strerror(errno)); return; } @@ -95,7 +95,8 @@ state_file_read(void) 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); + playlist_state_restore(line, fp, buffer, + &g_playlist, pc); if (!success) g_warning("Unrecognized line in state file: %s", line); } @@ -104,7 +105,7 @@ state_file_read(void) 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); + prev_playlist_version = playlist_state_get_hash(&g_playlist, pc); g_string_free(buffer, true); @@ -115,21 +116,23 @@ state_file_read(void) * saves the state file. */ static gboolean -timer_save_state_file(G_GNUC_UNUSED gpointer data) +timer_save_state_file(gpointer data) { + struct player_control *pc = 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)) + prev_playlist_version == playlist_state_get_hash(&g_playlist, pc)) /* nothing has changed - don't save the state file, don't spin up the hard disk */ return true; - state_file_write(); + state_file_write(pc); return true; } void -state_file_init(const char *path) +state_file_init(const char *path, struct player_control *pc) { assert(state_file_path == NULL); @@ -137,15 +140,15 @@ state_file_init(const char *path) return; state_file_path = g_strdup(path); - state_file_read(); + state_file_read(pc); save_state_source_id = g_timeout_add_seconds(5 * 60, timer_save_state_file, - NULL); + pc); } void -state_file_finish(void) +state_file_finish(struct player_control *pc) { if (state_file_path == NULL) /* no state file configured, no cleanup required */ @@ -154,7 +157,7 @@ state_file_finish(void) if (save_state_source_id != 0) g_source_remove(save_state_source_id); - state_file_write(); + state_file_write(pc); g_free(state_file_path); } diff --git a/src/state_file.h b/src/state_file.h index ec01fcbed..4c4f881cc 100644 --- a/src/state_file.h +++ b/src/state_file.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,11 +20,13 @@ #ifndef MPD_STATE_FILE_H #define MPD_STATE_FILE_H +struct player_control; + void -state_file_init(const char *path); +state_file_init(const char *path, struct player_control *pc); void -state_file_finish(void); +state_file_finish(struct player_control *pc); void write_state_file(void); diff --git a/src/stats.c b/src/stats.c index 718d8633a..fe6a064a6 100644 --- a/src/stats.c +++ b/src/stats.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,11 +20,13 @@ #include "config.h" #include "stats.h" #include "database.h" +#include "db_visitor.h" #include "tag.h" #include "song.h" #include "client.h" #include "player_control.h" #include "strset.h" +#include "client_internal.h" struct stats stats; @@ -67,8 +69,9 @@ visit_tag(struct visit_data *data, const struct tag *tag) } } -static int -stats_collect_song(struct song *song, void *_data) +static bool +collect_stats_song(struct song *song, void *_data, + G_GNUC_UNUSED GError **error_r) { struct visit_data *data = _data; @@ -77,9 +80,13 @@ stats_collect_song(struct song *song, void *_data) if (song->tag != NULL) visit_tag(data, song->tag); - return 0; + return true; } +static const struct db_visitor collect_stats_visitor = { + .song = collect_stats_song, +}; + void stats_update(void) { struct visit_data data; @@ -91,7 +98,7 @@ void stats_update(void) data.artists = strset_new(); data.albums = strset_new(); - db_walk(NULL, stats_collect_song, NULL, &data); + db_walk("", &collect_stats_visitor, &data, NULL); stats.artist_count = strset_size(data.artists); stats.album_count = strset_size(data.albums); @@ -114,7 +121,7 @@ int stats_print(struct client *client) stats.album_count, stats.song_count, (long)g_timer_elapsed(stats.timer, NULL), - (long)(pc_get_total_play_time() + 0.5), + (long)(pc_get_total_play_time(client->player_control) + 0.5), stats.song_duration, (long)db_get_mtime()); return 0; diff --git a/src/stats.h b/src/stats.h index fbb2e4a46..a686477de 100644 --- a/src/stats.h +++ b/src/stats.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 f6cd04346..346a827a5 100644 --- a/src/sticker.c +++ b/src/sticker.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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.h b/src/sticker.h index 6cc0ebcee..5545206a5 100644 --- a/src/sticker.h +++ b/src/sticker.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -51,7 +51,7 @@ struct sticker; /** * Opens the sticker database (if path is not NULL). * - * @param error_r location to store the error occuring, or NULL to + * @param error_r location to store the error occurring, or NULL to * ignore errors * @return true on success, false on error */ diff --git a/src/sticker_print.c b/src/sticker_print.c index b158c8af3..65e79513c 100644 --- a/src/sticker_print.c +++ b/src/sticker_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_print.h b/src/sticker_print.h index ac542709c..7398c8083 100644 --- a/src/sticker_print.h +++ b/src/sticker_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 cd2818522..39ba2bac1 100644 --- a/src/stored_playlist.c +++ b/src/stored_playlist.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ #include "config.h" #include "stored_playlist.h" #include "playlist_save.h" +#include "text_file.h" #include "song.h" #include "mapper.h" #include "path.h" @@ -27,6 +28,7 @@ #include "database.h" #include "idle.h" #include "conf.h" +#include "glib_compat.h" #include <assert.h> #include <sys/types.h> @@ -36,6 +38,8 @@ #include <string.h> #include <errno.h> +static const char PLAYLIST_COMMENT = '#'; + static unsigned playlist_max_length; bool playlist_saveAbsolutePaths = DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS; @@ -69,6 +73,67 @@ spl_valid_name(const char *name_utf8) strchr(name_utf8, '\r') == NULL; } +static const char * +spl_map(GError **error_r) +{ + const char *path_fs = map_spl_path(); + if (path_fs == NULL) + g_set_error_literal(error_r, playlist_quark(), + PLAYLIST_RESULT_DISABLED, + "Stored playlists are disabled"); + + return path_fs; +} + +static bool +spl_check_name(const char *name_utf8, GError **error_r) +{ + if (!spl_valid_name(name_utf8)) { + g_set_error_literal(error_r, playlist_quark(), + PLAYLIST_RESULT_BAD_NAME, + "Bad playlist name"); + return false; + } + + return true; +} + +static char * +spl_map_to_fs(const char *name_utf8, GError **error_r) +{ + if (spl_map(error_r) == NULL || + !spl_check_name(name_utf8, error_r)) + return NULL; + + char *path_fs = map_spl_utf8_to_fs(name_utf8); + if (path_fs == NULL) + g_set_error_literal(error_r, playlist_quark(), + PLAYLIST_RESULT_BAD_NAME, + "Bad playlist name"); + + return path_fs; +} + +/** + * Create a GError for the current errno. + */ +static void +playlist_errno(GError **error_r) +{ + switch (errno) { + case ENOENT: + g_set_error_literal(error_r, playlist_quark(), + PLAYLIST_RESULT_NO_SUCH_LIST, + "No such playlist"); + break; + + default: + g_set_error_literal(error_r, g_file_error_quark(), errno, + g_strerror(errno)); + break; + } +} + static struct stored_playlist_info * load_playlist_info(const char *parent_path_fs, const char *name_fs) { @@ -105,9 +170,9 @@ load_playlist_info(const char *parent_path_fs, const char *name_fs) } GPtrArray * -spl_list(void) +spl_list(GError **error_r) { - const char *parent_path_fs = map_spl_path(); + const char *parent_path_fs = spl_map(error_r); DIR *dir; struct dirent *ent; GPtrArray *list; @@ -117,8 +182,11 @@ spl_list(void) return NULL; dir = opendir(parent_path_fs); - if (dir == NULL) + if (dir == NULL) { + g_set_error_literal(error_r, g_file_error_quark(), errno, + g_strerror(errno)); return NULL; + } list = g_ptr_array_new(); @@ -145,25 +213,26 @@ spl_list_free(GPtrArray *list) g_ptr_array_free(list, true); } -static enum playlist_result -spl_save(GPtrArray *list, const char *utf8path) +static bool +spl_save(GPtrArray *list, const char *utf8path, GError **error_r) { FILE *file; - char *path_fs; assert(utf8path != NULL); - if (map_spl_path() == NULL) - return PLAYLIST_RESULT_DISABLED; + if (spl_map(error_r) == NULL) + return false; - path_fs = map_spl_utf8_to_fs(utf8path); + char *path_fs = spl_map_to_fs(utf8path, error_r); if (path_fs == NULL) - return PLAYLIST_RESULT_BAD_NAME; + return false; file = fopen(path_fs, "w"); g_free(path_fs); - if (file == NULL) - return PLAYLIST_RESULT_ERRNO; + if (file == NULL) { + playlist_errno(error_r); + return false; + } for (unsigned i = 0; i < list->len; ++i) { const char *uri = g_ptr_array_index(list, i); @@ -171,53 +240,46 @@ spl_save(GPtrArray *list, const char *utf8path) } fclose(file); - return PLAYLIST_RESULT_SUCCESS; + return true; } GPtrArray * -spl_load(const char *utf8path) +spl_load(const char *utf8path, GError **error_r) { FILE *file; GPtrArray *list; - char buffer[MPD_PATH_MAX]; char *path_fs; - if (!spl_valid_name(utf8path) || map_spl_path() == NULL) + if (spl_map(error_r) == NULL) return NULL; - path_fs = map_spl_utf8_to_fs(utf8path); + path_fs = spl_map_to_fs(utf8path, error_r); if (path_fs == NULL) return NULL; file = fopen(path_fs, "r"); g_free(path_fs); - if (file == NULL) + if (file == NULL) { + playlist_errno(error_r); return NULL; + } list = g_ptr_array_new(); - while (fgets(buffer, sizeof(buffer), file)) { - char *s = buffer; - - if (*s == PLAYLIST_COMMENT) + GString *buffer = g_string_sized_new(1024); + char *s; + while ((s = read_text_line(file, buffer)) != NULL) { + if (*s == 0 || *s == PLAYLIST_COMMENT) continue; - g_strchomp(buffer); - if (!uri_has_scheme(s)) { char *path_utf8; - struct song *song; path_utf8 = map_fs_to_utf8(s); if (path_utf8 == NULL) continue; - song = db_get_song(path_utf8); - g_free(path_utf8); - if (song == NULL) - continue; - - s = song_get_uri(song); + s = path_utf8; } else s = g_strdup(s); @@ -266,30 +328,33 @@ spl_insert_index_internal(GPtrArray *list, unsigned idx, char *uri) g_ptr_array_index(list, idx) = uri; } -enum playlist_result -spl_move_index(const char *utf8path, unsigned src, unsigned dest) +bool +spl_move_index(const char *utf8path, unsigned src, unsigned dest, + GError **error_r) { - GPtrArray *list; char *uri; - enum playlist_result result; if (src == dest) /* this doesn't check whether the playlist exists, but what the hell.. */ - return PLAYLIST_RESULT_SUCCESS; + return true; - if (!(list = spl_load(utf8path))) - return PLAYLIST_RESULT_NO_SUCH_LIST; + GPtrArray *list = spl_load(utf8path, error_r); + if (list == NULL) + return false; if (src >= list->len || dest >= list->len) { spl_free(list); - return PLAYLIST_RESULT_BAD_RANGE; + g_set_error_literal(error_r, playlist_quark(), + PLAYLIST_RESULT_BAD_RANGE, + "Bad range"); + return false; } uri = spl_remove_index_internal(list, src); spl_insert_index_internal(list, dest, uri); - result = spl_save(list, utf8path); + bool result = spl_save(list, utf8path, error_r); spl_free(list); @@ -297,78 +362,72 @@ spl_move_index(const char *utf8path, unsigned src, unsigned dest) return result; } -enum playlist_result -spl_clear(const char *utf8path) +bool +spl_clear(const char *utf8path, GError **error_r) { - char *path_fs; FILE *file; - if (map_spl_path() == NULL) - return PLAYLIST_RESULT_DISABLED; + if (spl_map(error_r) == NULL) + return false; - if (!spl_valid_name(utf8path)) - return PLAYLIST_RESULT_BAD_NAME; - - path_fs = map_spl_utf8_to_fs(utf8path); + char *path_fs = spl_map_to_fs(utf8path, error_r); if (path_fs == NULL) - return PLAYLIST_RESULT_BAD_NAME; + return false; file = fopen(path_fs, "w"); g_free(path_fs); - if (file == NULL) - return PLAYLIST_RESULT_ERRNO; + if (file == NULL) { + playlist_errno(error_r); + return false; + } fclose(file); idle_add(IDLE_STORED_PLAYLIST); - return PLAYLIST_RESULT_SUCCESS; + return true; } -enum playlist_result -spl_delete(const char *name_utf8) +bool +spl_delete(const char *name_utf8, GError **error_r) { char *path_fs; int ret; - if (map_spl_path() == NULL) - return PLAYLIST_RESULT_DISABLED; - - if (!spl_valid_name(name_utf8)) - return PLAYLIST_RESULT_BAD_NAME; - - path_fs = map_spl_utf8_to_fs(name_utf8); + path_fs = spl_map_to_fs(name_utf8, error_r); if (path_fs == NULL) - return PLAYLIST_RESULT_BAD_NAME; + return false; ret = unlink(path_fs); g_free(path_fs); - if (ret < 0) - return errno == ENOENT - ? PLAYLIST_RESULT_NO_SUCH_LIST - : PLAYLIST_RESULT_ERRNO; + if (ret < 0) { + playlist_errno(error_r); + return false; + } idle_add(IDLE_STORED_PLAYLIST); - return PLAYLIST_RESULT_SUCCESS; + return true; } -enum playlist_result -spl_remove_index(const char *utf8path, unsigned pos) +bool +spl_remove_index(const char *utf8path, unsigned pos, GError **error_r) { - GPtrArray *list; char *uri; - enum playlist_result result; - if (!(list = spl_load(utf8path))) - return PLAYLIST_RESULT_NO_SUCH_LIST; + GPtrArray *list = spl_load(utf8path, error_r); + if (list == NULL) + return false; if (pos >= list->len) { spl_free(list); - return PLAYLIST_RESULT_BAD_RANGE; + g_set_error_literal(error_r, playlist_quark(), + PLAYLIST_RESULT_BAD_RANGE, + "Bad range"); + return false; } uri = spl_remove_index_internal(list, pos); g_free(uri); - result = spl_save(list, utf8path); + bool result = spl_save(list, utf8path, error_r); spl_free(list); @@ -376,38 +435,38 @@ spl_remove_index(const char *utf8path, unsigned pos) return result; } -enum playlist_result -spl_append_song(const char *utf8path, struct song *song) +bool +spl_append_song(const char *utf8path, struct song *song, GError **error_r) { FILE *file; struct stat st; - char *path_fs; - - if (map_spl_path() == NULL) - return PLAYLIST_RESULT_DISABLED; - if (!spl_valid_name(utf8path)) - return PLAYLIST_RESULT_BAD_NAME; + if (spl_map(error_r) == NULL) + return false; - path_fs = map_spl_utf8_to_fs(utf8path); + char *path_fs = spl_map_to_fs(utf8path, error_r); if (path_fs == NULL) - return PLAYLIST_RESULT_BAD_NAME; + return false; file = fopen(path_fs, "a"); g_free(path_fs); - if (file == NULL) - return PLAYLIST_RESULT_ERRNO; + if (file == NULL) { + playlist_errno(error_r); + return false; + } if (fstat(fileno(file), &st) < 0) { - int save_errno = errno; + playlist_errno(error_r); fclose(file); - errno = save_errno; - return PLAYLIST_RESULT_ERRNO; + return false; } if (st.st_size / (MPD_PATH_MAX + 1) >= (off_t)playlist_max_length) { fclose(file); - return PLAYLIST_RESULT_TOO_LARGE; + g_set_error_literal(error_r, playlist_quark(), + PLAYLIST_RESULT_TOO_LARGE, + "Stored playlist is too large"); + return false; } playlist_print_song(file, song); @@ -415,68 +474,79 @@ spl_append_song(const char *utf8path, struct song *song) fclose(file); idle_add(IDLE_STORED_PLAYLIST); - return PLAYLIST_RESULT_SUCCESS; + return true; } -enum playlist_result -spl_append_uri(const char *url, const char *utf8file) +bool +spl_append_uri(const char *url, const char *utf8file, GError **error_r) { struct song *song; if (uri_has_scheme(url)) { - enum playlist_result ret; - song = song_remote_new(url); - ret = spl_append_song(utf8file, song); + bool success = spl_append_song(utf8file, song, error_r); song_free(song); - return ret; + return success; } else { song = db_get_song(url); - if (song == NULL) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return spl_append_song(utf8file, song); + if (song == NULL) { + g_set_error_literal(error_r, playlist_quark(), + PLAYLIST_RESULT_NO_SUCH_SONG, + "No such song"); + return false; + } + + return spl_append_song(utf8file, song, error_r); } } -static enum playlist_result -spl_rename_internal(const char *from_path_fs, const char *to_path_fs) +static bool +spl_rename_internal(const char *from_path_fs, const char *to_path_fs, + GError **error_r) { - if (!g_file_test(from_path_fs, G_FILE_TEST_IS_REGULAR)) - return PLAYLIST_RESULT_NO_SUCH_LIST; + if (!g_file_test(from_path_fs, G_FILE_TEST_IS_REGULAR)) { + g_set_error_literal(error_r, playlist_quark(), + PLAYLIST_RESULT_NO_SUCH_LIST, + "No such playlist"); + return false; + } - if (g_file_test(to_path_fs, G_FILE_TEST_EXISTS)) - return PLAYLIST_RESULT_LIST_EXISTS; + if (g_file_test(to_path_fs, G_FILE_TEST_EXISTS)) { + g_set_error_literal(error_r, playlist_quark(), + PLAYLIST_RESULT_LIST_EXISTS, + "Playlist exists already"); + return false; + } - if (rename(from_path_fs, to_path_fs) < 0) - return PLAYLIST_RESULT_ERRNO; + if (rename(from_path_fs, to_path_fs) < 0) { + playlist_errno(error_r); + return false; + } idle_add(IDLE_STORED_PLAYLIST); - return PLAYLIST_RESULT_SUCCESS; + return true; } -enum playlist_result -spl_rename(const char *utf8from, const char *utf8to) +bool +spl_rename(const char *utf8from, const char *utf8to, GError **error_r) { - char *from_path_fs, *to_path_fs; - static enum playlist_result ret; - - if (map_spl_path() == NULL) - return PLAYLIST_RESULT_DISABLED; + if (spl_map(error_r) == NULL) + return false; - if (!spl_valid_name(utf8from) || !spl_valid_name(utf8to)) - return PLAYLIST_RESULT_BAD_NAME; + char *from_path_fs = spl_map_to_fs(utf8from, error_r); + if (from_path_fs == NULL) + return false; - from_path_fs = map_spl_utf8_to_fs(utf8from); - to_path_fs = map_spl_utf8_to_fs(utf8to); + char *to_path_fs = spl_map_to_fs(utf8to, error_r); + if (to_path_fs == NULL) { + g_free(from_path_fs); + return false; + } - if (from_path_fs != NULL && to_path_fs != NULL) - ret = spl_rename_internal(from_path_fs, to_path_fs); - else - ret = PLAYLIST_RESULT_BAD_NAME; + bool success = spl_rename_internal(from_path_fs, to_path_fs, error_r); g_free(from_path_fs); g_free(to_path_fs); - return ret; + return success; } diff --git a/src/stored_playlist.h b/src/stored_playlist.h index 3afdbb0f0..cfe49633c 100644 --- a/src/stored_playlist.h +++ b/src/stored_playlist.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_STORED_PLAYLIST_H #define MPD_STORED_PLAYLIST_H -#include "playlist.h" - #include <glib.h> #include <stdbool.h> #include <time.h> @@ -51,39 +49,40 @@ spl_valid_name(const char *name_utf8); /** * Returns a list of stored_playlist_info struct pointers. Returns - * NULL if an error occured. + * NULL if an error occurred. */ GPtrArray * -spl_list(void); +spl_list(GError **error_r); void spl_list_free(GPtrArray *list); GPtrArray * -spl_load(const char *utf8path); +spl_load(const char *utf8path, GError **error_r); void spl_free(GPtrArray *list); -enum playlist_result -spl_move_index(const char *utf8path, unsigned src, unsigned dest); +bool +spl_move_index(const char *utf8path, unsigned src, unsigned dest, + GError **error_r); -enum playlist_result -spl_clear(const char *utf8path); +bool +spl_clear(const char *utf8path, GError **error_r); -enum playlist_result -spl_delete(const char *name_utf8); +bool +spl_delete(const char *name_utf8, GError **error_r); -enum playlist_result -spl_remove_index(const char *utf8path, unsigned pos); +bool +spl_remove_index(const char *utf8path, unsigned pos, GError **error_r); -enum playlist_result -spl_append_song(const char *utf8path, struct song *song); +bool +spl_append_song(const char *utf8path, struct song *song, GError **error_r); -enum playlist_result -spl_append_uri(const char *file, const char *utf8file); +bool +spl_append_uri(const char *file, const char *utf8file, GError **error_r); -enum playlist_result -spl_rename(const char *utf8from, const char *utf8to); +bool +spl_rename(const char *utf8from, const char *utf8to, GError **error_r); #endif diff --git a/src/string_util.c b/src/string_util.c new file mode 100644 index 000000000..6e5429076 --- /dev/null +++ b/src/string_util.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "string_util.h" + +#include <glib.h> + +#include <assert.h> + +const char * +strchug_fast_c(const char *p) +{ + while (*p != 0 && g_ascii_isspace(*p)) + ++p; + + return p; +} + +bool +string_array_contains(const char *const* haystack, const char *needle) +{ + assert(haystack != NULL); + assert(needle != NULL); + + for (; *haystack != NULL; ++haystack) + if (g_ascii_strcasecmp(*haystack, needle) == 0) + return true; + + return false; +} diff --git a/src/string_util.h b/src/string_util.h new file mode 100644 index 000000000..dc80a46ef --- /dev/null +++ b/src/string_util.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_STRING_UTIL_H +#define MPD_STRING_UTIL_H + +#include <glib.h> + +#include <stdbool.h> + +/** + * Remove the "const" attribute from a string pointer. This is a + * dirty hack, don't use it unless you know what you're doing! + */ +G_GNUC_CONST +static inline char * +deconst_string(const char *p) +{ + union { + const char *in; + char *out; + } u = { + .in = p, + }; + + return u.out; +} + +/** + * Returns a pointer to the first non-whitespace character in the + * string, or to the end of the string. + * + * This is a faster version of g_strchug(), because it does not move + * data. + */ +G_GNUC_PURE +const char * +strchug_fast_c(const char *p); + +/** + * Same as strchug_fast_c(), but works with a writable pointer. + */ +G_GNUC_PURE +static inline char * +strchug_fast(char *p) +{ + return deconst_string(strchug_fast_c(p)); +} + +/** + * 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/strset.c b/src/strset.c index e071fbc98..5862e4075 100644 --- a/src/strset.c +++ b/src/strset.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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.h b/src/strset.h index 9a7aa45e5..5382e59b8 100644 --- a/src/strset.h +++ b/src/strset.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -158,6 +158,7 @@ struct tag *tag_new(void) struct tag *ret = g_new(struct tag, 1); ret->items = NULL; ret->time = -1; + ret->has_playlist = false; ret->num_items = 0; return ret; } @@ -226,6 +227,7 @@ struct tag *tag_dup(const struct tag *tag) ret = tag_new(); ret->time = tag->time; + ret->has_playlist = tag->has_playlist; ret->num_items = tag->num_items; ret->items = ret->num_items > 0 ? g_malloc(items_size(tag)) : NULL; @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -73,7 +73,7 @@ struct tag_item { * the value of this tag; this is a variable length string */ char value[sizeof(long)]; -} mpd_packed; +} gcc_packed; /** * The meta information about a song file. It is a MPD specific @@ -88,6 +88,12 @@ struct tag { */ int time; + /** + * Does this file have an embedded playlist (e.g. embedded CUE + * sheet)? + */ + bool has_playlist; + /** an array of tag items */ struct tag_item **items; diff --git a/src/tag_ape.c b/src/tag_ape.c index 79facba1b..31c177aa7 100644 --- a/src/tag_ape.c +++ b/src/tag_ape.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,37 +21,39 @@ #include "tag_ape.h" #include "tag.h" #include "tag_table.h" +#include "tag_handler.h" #include "ape.h" -static const char *const ape_tag_names[TAG_NUM_OF_ITEM_TYPES] = { - [TAG_ALBUM_ARTIST] = "album artist", - [TAG_DATE] = "year", +static const struct tag_table ape_tags[] = { + { "album artist", TAG_ALBUM_ARTIST }, + { "year", TAG_DATE }, + { NULL, TAG_NUM_OF_ITEM_TYPES } }; static enum tag_type tag_ape_name_parse(const char *name) { - enum tag_type type = tag_table_lookup(ape_tag_names, name); + enum tag_type type = tag_table_lookup_i(ape_tags, name); if (type == TAG_NUM_OF_ITEM_TYPES) type = tag_name_parse_i(name); return type; } -static struct tag * -tag_ape_import_item(struct tag *tag, unsigned long flags, - const char *key, const char *value, size_t value_length) +static void +tag_ape_import_item(unsigned long flags, + const char *key, const char *value, size_t value_length, + const struct tag_handler *handler, void *handler_ctx) { /* we only care about utf-8 text tags */ if ((flags & (0x3 << 1)) != 0) - return tag; + return; + + tag_handler_invoke_pair(handler, handler_ctx, key, value); enum tag_type type = tag_ape_name_parse(key); if (type == TAG_NUM_OF_ITEM_TYPES) - return tag; - - if (tag == NULL) - tag = tag_new(); + return; const char *end = value + value_length; while (true) { @@ -59,20 +61,22 @@ tag_ape_import_item(struct tag *tag, unsigned long flags, const char *n = memchr(value, 0, end - value); if (n != NULL) { if (n > value) - tag_add_item_n(tag, type, value, n - value); + tag_handler_invoke_tag(handler, handler_ctx, + type, value); value = n + 1; } else { - if (end > value) - tag_add_item_n(tag, type, value, end - value); + char *p = g_strndup(value, end - value); + tag_handler_invoke_tag(handler, handler_ctx, + type, p); + g_free(p); break; } } - - return tag; } struct tag_ape_ctx { - struct tag *tag; + const struct tag_handler *handler; + void *handler_ctx; }; static bool @@ -81,16 +85,19 @@ tag_ape_callback(unsigned long flags, const char *key, { struct tag_ape_ctx *ctx = _ctx; - ctx->tag = tag_ape_import_item(ctx->tag, flags, key, - value, value_length); + tag_ape_import_item(flags, key, value, value_length, + ctx->handler, ctx->handler_ctx); return true; } -struct tag * -tag_ape_load(const char *file) +bool +tag_ape_scan2(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) { - struct tag_ape_ctx ctx = { .tag = NULL }; + struct tag_ape_ctx ctx = { + .handler = handler, + .handler_ctx = handler_ctx, + }; - tag_ape_scan(file, tag_ape_callback, &ctx); - return ctx.tag; + return tag_ape_scan(path_fs, tag_ape_callback, &ctx); } diff --git a/src/tag_ape.h b/src/tag_ape.h index 150659685..9b5856115 100644 --- a/src/tag_ape.h +++ b/src/tag_ape.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,13 +20,17 @@ #ifndef MPD_TAG_APE_H #define MPD_TAG_APE_H +#include <stdbool.h> + +struct tag_handler; + /** - * Loads the APE tag from a file. + * Scan the APE tags of a file. * * @param path_fs the path of the file in filesystem encoding - * @return a tag object, or NULL if the file has no APE tag */ -struct tag * -tag_ape_load(const char *path_fs); +bool +tag_ape_scan2(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx); #endif diff --git a/src/tag_file.c b/src/tag_file.c new file mode 100644 index 000000000..8d8a0f5fb --- /dev/null +++ b/src/tag_file.c @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_file.h" +#include "uri.h" +#include "decoder_list.h" +#include "decoder_plugin.h" +#include "input_stream.h" + +#include <assert.h> +#include <unistd.h> /* for SEEK_SET */ + +bool +tag_file_scan(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + assert(path_fs != NULL); + assert(handler != NULL); + + /* check if there's a suffix and a plugin */ + + const char *suffix = uri_get_suffix(path_fs); + if (suffix == NULL) + return false; + + const struct decoder_plugin *plugin = + decoder_plugin_from_suffix(suffix, NULL); + if (plugin == NULL) + return false; + + struct input_stream *is = NULL; + GMutex *mutex = NULL; + GCond *cond = NULL; + + do { + /* load file tag */ + if (decoder_plugin_scan_file(plugin, path_fs, + handler, handler_ctx)) + break; + + /* fall back to stream tag */ + if (plugin->scan_stream != NULL) { + /* open the input_stream (if not already + open) */ + if (is == NULL) { + mutex = g_mutex_new(); + cond = g_cond_new(); + is = input_stream_open(path_fs, mutex, cond, + NULL); + } + + /* now try the stream_tag() method */ + if (is != NULL) { + if (decoder_plugin_scan_stream(plugin, is, + handler, + handler_ctx)) + break; + + input_stream_lock_seek(is, 0, SEEK_SET, NULL); + } + } + + plugin = decoder_plugin_from_suffix(suffix, plugin); + } while (plugin != NULL); + + if (is != NULL) { + input_stream_close(is); + g_cond_free(cond); + g_mutex_free(mutex); + } + + return plugin != NULL; +} diff --git a/src/tag_file.h b/src/tag_file.h new file mode 100644 index 000000000..8cf1af3cb --- /dev/null +++ b/src/tag_file.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_FILE_H +#define MPD_TAG_FILE_H + +#include "check.h" + +#include <stdbool.h> + +struct tag_handler; + +/** + * Scan the tags of a song file. Invokes matching decoder plugins, + * but does not invoke the special "APE" and "ID3" scanners. + */ +bool +tag_file_scan(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx); + +#endif diff --git a/src/tag_handler.c b/src/tag_handler.c new file mode 100644 index 000000000..316715e87 --- /dev/null +++ b/src/tag_handler.c @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_handler.h" + +#include <glib.h> + +static void +add_tag_duration(unsigned seconds, void *ctx) +{ + struct tag *tag = ctx; + + tag->time = seconds; +} + +static void +add_tag_tag(enum tag_type type, const char *value, void *ctx) +{ + struct tag *tag = ctx; + + tag_add_item(tag, type, value); +} + +const struct tag_handler add_tag_handler = { + .duration = add_tag_duration, + .tag = add_tag_tag, +}; + +static void +full_tag_pair(const char *name, G_GNUC_UNUSED const char *value, void *ctx) +{ + struct tag *tag = ctx; + + if (g_ascii_strcasecmp(name, "cuesheet") == 0) + tag->has_playlist = true; +} + +const struct tag_handler full_tag_handler = { + .duration = add_tag_duration, + .tag = add_tag_tag, + .pair = full_tag_pair, +}; + diff --git a/src/tag_handler.h b/src/tag_handler.h new file mode 100644 index 000000000..7f15c2a48 --- /dev/null +++ b/src/tag_handler.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_HANDLER_H +#define MPD_TAG_HANDLER_H + +#include "check.h" +#include "tag.h" + +#include <assert.h> + +/** + * A callback table for receiving metadata of a song. + */ +struct tag_handler { + /** + * Declare the duration of a song, in seconds. Do not call + * this when the duration could not be determined, because + * there is no magic value for "unknown duration". + */ + void (*duration)(unsigned seconds, void *ctx); + + /** + * A tag has been read. + * + * @param the value of the tag; the pointer will become + * invalid after returning + */ + void (*tag)(enum tag_type type, const char *value, void *ctx); + + /** + * A name-value pair has been read. It is the codec specific + * representation of tags. + */ + void (*pair)(const char *key, const char *value, void *ctx); +}; + +static inline void +tag_handler_invoke_duration(const struct tag_handler *handler, void *ctx, + unsigned seconds) +{ + assert(handler != NULL); + + if (handler->duration != NULL) + handler->duration(seconds, ctx); +} + +static inline void +tag_handler_invoke_tag(const struct tag_handler *handler, void *ctx, + enum tag_type type, const char *value) +{ + assert(handler != NULL); + assert((unsigned)type < TAG_NUM_OF_ITEM_TYPES); + assert(value != NULL); + + if (handler->tag != NULL) + handler->tag(type, value, ctx); +} + +static inline void +tag_handler_invoke_pair(const struct tag_handler *handler, void *ctx, + const char *name, const char *value) +{ + assert(handler != NULL); + assert(name != NULL); + assert(value != NULL); + + if (handler->pair != NULL) + handler->pair(name, value, ctx); +} + +/** + * This #tag_handler implementation adds tag values to a #tag object + * (casted from the context pointer). + */ +extern const struct tag_handler add_tag_handler; + +/** + * This #tag_handler implementation adds tag values to a #tag object + * (casted from the context pointer), and supports the has_playlist + * attribute. + */ +extern const struct tag_handler full_tag_handler; + +#endif diff --git a/src/tag_id3.c b/src/tag_id3.c index 9c0a98d40..c98179f0a 100644 --- a/src/tag_id3.c +++ b/src/tag_id3.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,6 +19,8 @@ #include "config.h" #include "tag_id3.h" +#include "tag_handler.h" +#include "tag_table.h" #include "tag.h" #include "riff.h" #include "aiff.h" @@ -126,9 +128,9 @@ import_id3_string(bool is_id3v1, const id3_ucs4_t *ucs4) * - string list */ static void -tag_id3_import_text_frame(struct tag *dest, struct id3_tag *tag, - const struct id3_frame *frame, - enum tag_type type) +tag_id3_import_text_frame(struct id3_tag *tag, const struct id3_frame *frame, + enum tag_type type, + const struct tag_handler *handler, void *handler_ctx) { id3_ucs4_t const *ucs4; id3_utf8_t *utf8; @@ -164,7 +166,8 @@ tag_id3_import_text_frame(struct tag *dest, struct id3_tag *tag, if (utf8 == NULL) continue; - tag_add_item(dest, type, (char *)utf8); + tag_handler_invoke_tag(handler, handler_ctx, + type, (const char *)utf8); g_free(utf8); } } @@ -174,13 +177,14 @@ tag_id3_import_text_frame(struct tag *dest, struct id3_tag *tag, * 4.2). This is a wrapper for tag_id3_import_text_frame(). */ static void -tag_id3_import_text(struct tag *dest, struct id3_tag *tag, const char *id, - enum tag_type type) +tag_id3_import_text(struct id3_tag *tag, const char *id, enum tag_type type, + const struct tag_handler *handler, void *handler_ctx) { const struct id3_frame *frame; for (unsigned i = 0; (frame = id3_tag_findframe(tag, id, i)) != NULL; ++i) - tag_id3_import_text_frame(dest, tag, frame, type); + tag_id3_import_text_frame(tag, frame, type, + handler, handler_ctx); } /** @@ -193,9 +197,10 @@ tag_id3_import_text(struct tag *dest, struct id3_tag *tag, const char *id, * - full string (we use this one) */ static void -tag_id3_import_comment_frame(struct tag *dest, struct id3_tag *tag, - const struct id3_frame *frame, - enum tag_type type) +tag_id3_import_comment_frame(struct id3_tag *tag, + const struct id3_frame *frame, enum tag_type type, + const struct tag_handler *handler, + void *handler_ctx) { id3_ucs4_t const *ucs4; id3_utf8_t *utf8; @@ -217,7 +222,7 @@ tag_id3_import_comment_frame(struct tag *dest, struct id3_tag *tag, if (utf8 == NULL) return; - tag_add_item(dest, type, (char *)utf8); + tag_handler_invoke_tag(handler, handler_ctx, type, (const char *)utf8); g_free(utf8); } @@ -226,13 +231,14 @@ tag_id3_import_comment_frame(struct tag *dest, struct id3_tag *tag, * wrapper for tag_id3_import_comment_frame(). */ static void -tag_id3_import_comment(struct tag *dest, struct id3_tag *tag, const char *id, - enum tag_type type) +tag_id3_import_comment(struct id3_tag *tag, const char *id, enum tag_type type, + const struct tag_handler *handler, void *handler_ctx) { const struct id3_frame *frame; for (unsigned i = 0; (frame = id3_tag_findframe(tag, id, i)) != NULL; ++i) - tag_id3_import_comment_frame(dest, tag, frame, type); + tag_id3_import_comment_frame(tag, frame, type, + handler, handler_ctx); } /** @@ -242,30 +248,26 @@ tag_id3_import_comment(struct tag *dest, struct id3_tag *tag, const char *id, static enum tag_type tag_id3_parse_txxx_name(const char *name) { - static const struct { - 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, - "MusicBrainz Album Artist Id" }, - { TAG_MUSICBRAINZ_TRACKID, "MusicBrainz Track Id" }, + static const struct tag_table txxx_tags[] = { + { "ALBUMARTISTSORT", TAG_ALBUM_ARTIST_SORT }, + { "MusicBrainz Artist Id", TAG_MUSICBRAINZ_ARTISTID }, + { "MusicBrainz Album Id", TAG_MUSICBRAINZ_ALBUMID }, + { "MusicBrainz Album Artist Id", + TAG_MUSICBRAINZ_ALBUMARTISTID }, + { "MusicBrainz Track Id", TAG_MUSICBRAINZ_TRACKID }, + { NULL, TAG_NUM_OF_ITEM_TYPES } }; - for (unsigned i = 0; i < G_N_ELEMENTS(musicbrainz_txxx); ++i) - if (strcmp(name, musicbrainz_txxx[i].name) == 0) - return musicbrainz_txxx[i].type; - - return TAG_NUM_OF_ITEM_TYPES; + return tag_table_lookup(txxx_tags, name); } /** * Import all known MusicBrainz tags from TXXX frames. */ static void -tag_id3_import_musicbrainz(struct tag *mpd_tag, struct id3_tag *id3_tag) +tag_id3_import_musicbrainz(struct id3_tag *id3_tag, + const struct tag_handler *handler, + void *handler_ctx) { for (unsigned i = 0;; ++i) { const struct id3_frame *frame; @@ -280,17 +282,21 @@ tag_id3_import_musicbrainz(struct tag *mpd_tag, struct id3_tag *id3_tag) if (name == NULL) continue; - type = tag_id3_parse_txxx_name((const char*)name); - free(name); - - if (type == TAG_NUM_OF_ITEM_TYPES) - continue; - value = tag_id3_getstring(frame, 2); if (value == NULL) continue; - tag_add_item(mpd_tag, type, (const char*)value); + tag_handler_invoke_pair(handler, handler_ctx, + (const char *)name, + (const char *)value); + + type = tag_id3_parse_txxx_name((const char*)name); + free(name); + + if (type != TAG_NUM_OF_ITEM_TYPES) + tag_handler_invoke_tag(handler, handler_ctx, + type, (const char*)value); + free(value); } } @@ -299,7 +305,8 @@ tag_id3_import_musicbrainz(struct tag *mpd_tag, struct id3_tag *id3_tag) * Imports the MusicBrainz TrackId from the UFID tag. */ static void -tag_id3_import_ufid(struct tag *mpd_tag, struct id3_tag *id3_tag) +tag_id3_import_ufid(struct id3_tag *id3_tag, + const struct tag_handler *handler, void *handler_ctx) { for (unsigned i = 0;; ++i) { const struct id3_frame *frame; @@ -329,35 +336,54 @@ tag_id3_import_ufid(struct tag *mpd_tag, struct id3_tag *id3_tag) if (value == NULL || length == 0) continue; - tag_add_item_n(mpd_tag, TAG_MUSICBRAINZ_TRACKID, - (const char*)value, length); + char *p = g_strndup((const char *)value, length); + tag_handler_invoke_tag(handler, handler_ctx, + TAG_MUSICBRAINZ_TRACKID, p); + g_free(p); } } +static void +scan_id3_tag(struct id3_tag *tag, + const struct tag_handler *handler, void *handler_ctx) +{ + tag_id3_import_text(tag, ID3_FRAME_ARTIST, TAG_ARTIST, + handler, handler_ctx); + tag_id3_import_text(tag, ID3_FRAME_ALBUM_ARTIST, + TAG_ALBUM_ARTIST, handler, handler_ctx); + tag_id3_import_text(tag, ID3_FRAME_ARTIST_SORT, + TAG_ARTIST_SORT, handler, handler_ctx); + tag_id3_import_text(tag, ID3_FRAME_ALBUM_ARTIST_SORT, + TAG_ALBUM_ARTIST_SORT, handler, handler_ctx); + tag_id3_import_text(tag, ID3_FRAME_TITLE, TAG_TITLE, + handler, handler_ctx); + tag_id3_import_text(tag, ID3_FRAME_ALBUM, TAG_ALBUM, + handler, handler_ctx); + tag_id3_import_text(tag, ID3_FRAME_TRACK, TAG_TRACK, + handler, handler_ctx); + tag_id3_import_text(tag, ID3_FRAME_YEAR, TAG_DATE, + handler, handler_ctx); + tag_id3_import_text(tag, ID3_FRAME_GENRE, TAG_GENRE, + handler, handler_ctx); + tag_id3_import_text(tag, ID3_FRAME_COMPOSER, TAG_COMPOSER, + handler, handler_ctx); + tag_id3_import_text(tag, "TPE3", TAG_PERFORMER, + handler, handler_ctx); + tag_id3_import_text(tag, "TPE4", TAG_PERFORMER, handler, handler_ctx); + tag_id3_import_comment(tag, ID3_FRAME_COMMENT, TAG_COMMENT, + handler, handler_ctx); + tag_id3_import_text(tag, ID3_FRAME_DISC, TAG_DISC, + handler, handler_ctx); + + tag_id3_import_musicbrainz(tag, handler, handler_ctx); + tag_id3_import_ufid(tag, handler, handler_ctx); +} + struct tag *tag_id3_import(struct id3_tag * tag) { struct tag *ret = tag_new(); - 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); + scan_id3_tag(tag, &add_tag_handler, ret); if (tag_is_empty(ret)) { tag_free(ret); @@ -515,17 +541,18 @@ tag_id3_riff_aiff_load(FILE *file) return tag; } -struct tag *tag_id3_load(const char *file) +bool +tag_id3_scan(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) { - struct tag *ret; struct id3_tag *tag; FILE *stream; - stream = fopen(file, "rb"); + stream = fopen(path_fs, "rb"); if (!stream) { g_debug("tag_id3_load: Failed to open file: '%s', %s", - file, strerror(errno)); - return NULL; + path_fs, g_strerror(errno)); + return false; } tag = tag_id3_find_from_beginning(stream); @@ -537,8 +564,9 @@ struct tag *tag_id3_load(const char *file) fclose(stream); if (!tag) - return NULL; - ret = tag_id3_import(tag); + return false; + + scan_id3_tag(tag, handler, handler_ctx); id3_tag_delete(tag); - return ret; + return true; } diff --git a/src/tag_id3.h b/src/tag_id3.h index 43f9678b4..b99b4480f 100644 --- a/src/tag_id3.h +++ b/src/tag_id3.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,22 +22,30 @@ #include "check.h" +#include <stdbool.h> + +struct tag_handler; struct tag; #ifdef HAVE_ID3TAG + +bool +tag_id3_scan(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx); + struct id3_tag; struct tag *tag_id3_import(struct id3_tag *); -struct tag *tag_id3_load(const char *file); - #else #include <glib.h> -static inline struct tag * -tag_id3_load(G_GNUC_UNUSED const char *file) +static inline bool +tag_id3_scan(G_GNUC_UNUSED const char *path_fs, + G_GNUC_UNUSED const struct tag_handler *handler, + G_GNUC_UNUSED void *handler_ctx) { - return NULL; + return false; } #endif diff --git a/src/tag_internal.h b/src/tag_internal.h index 9d76efed1..af05cc6b6 100644 --- a/src/tag_internal.h +++ b/src/tag_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_TAG_INTERNAL_H #define MPD_TAG_INTERNAL_H +#include "tag.h" + #include <stdbool.h> extern bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES]; diff --git a/src/tag_pool.c b/src/tag_pool.c index 6ad1e1f2d..eabf3e369 100644 --- a/src/tag_pool.c +++ b/src/tag_pool.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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.h b/src/tag_pool.h index 289d6fe5f..a96c00d85 100644 --- a/src/tag_pool.h +++ b/src/tag_pool.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 493fa89b5..9a46b247a 100644 --- a/src/tag_print.c +++ b/src/tag_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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.h b/src/tag_print.h index e16e2c441..b9eeeaecf 100644 --- a/src/tag_print.h +++ b/src/tag_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 index 35f12118f..68ae9d5e5 100644 --- a/src/tag_rva2.c +++ b/src/tag_rva2.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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.h b/src/tag_rva2.h index a92c97912..8aac2fe9f 100644 --- a/src/tag_rva2.h +++ b/src/tag_rva2.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_save.c b/src/tag_save.c index 9b90d1b92..2fdaef56c 100644 --- a/src/tag_save.c +++ b/src/tag_save.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,6 +28,9 @@ void tag_save(FILE *file, const struct tag *tag) if (tag->time >= 0) fprintf(file, SONG_TIME "%i\n", tag->time); + if (tag->has_playlist) + fprintf(file, "Playlist: yes\n"); + for (unsigned i = 0; i < tag->num_items; i++) fprintf(file, "%s: %s\n", tag_item_names[tag->items[i]->type], diff --git a/src/tag_save.h b/src/tag_save.h index 2e8924c20..9f6a580c8 100644 --- a/src/tag_save.h +++ b/src/tag_save.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_table.h b/src/tag_table.h index ce47d69fc..d87d4869a 100644 --- a/src/tag_table.h +++ b/src/tag_table.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,18 +24,40 @@ #include <glib.h> +struct tag_table { + const char *name; + + enum tag_type type; +}; + +/** + * Looks up a string in a tag translation table (case sensitive). + * Returns TAG_NUM_OF_ITEM_TYPES if the specified name was not found + * in the table. + */ +G_GNUC_PURE +static inline enum tag_type +tag_table_lookup(const struct tag_table *table, const char *name) +{ + for (; table->name != NULL; ++table) + if (strcmp(name, table->name) == 0) + return table->type; + + return TAG_NUM_OF_ITEM_TYPES; +} + /** * Looks up a string in a tag translation table (case insensitive). * Returns TAG_NUM_OF_ITEM_TYPES if the specified name was not found * in the table. */ +G_GNUC_PURE static inline enum tag_type -tag_table_lookup(const char *const* table, const char *name) +tag_table_lookup_i(const struct tag_table *table, const char *name) { - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) - if (table[i] != NULL && - g_ascii_strcasecmp(name, table[i]) == 0) - return (enum tag_type)i; + for (; table->name != NULL; ++table) + if (g_ascii_strcasecmp(name, table->name) == 0) + return table->type; return TAG_NUM_OF_ITEM_TYPES; } diff --git a/src/tcp_connect.c b/src/tcp_connect.c new file mode 100644 index 000000000..88e2348e6 --- /dev/null +++ b/src/tcp_connect.c @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "tcp_connect.h" +#include "fd_util.h" +#include "io_thread.h" +#include "glib_compat.h" +#include "glib_socket.h" + +#include <assert.h> +#include <errno.h> + +#ifdef WIN32 +#include <ws2tcpip.h> +#include <winsock.h> +#else +#include <sys/socket.h> +#include <unistd.h> +#endif + +struct tcp_connect { + const struct tcp_connect_handler *handler; + void *handler_ctx; + + int fd; + GSource *source; + + unsigned timeout_ms; + GSource *timeout_source; +}; + +static bool +is_in_progress_errno(int e) +{ +#ifdef WIN32 + return e == WSAEINPROGRESS || e == WSAEWOULDBLOCK; +#else + return e == EINPROGRESS; +#endif +} + +static gboolean +tcp_connect_event(G_GNUC_UNUSED GIOChannel *source, + G_GNUC_UNUSED GIOCondition condition, + gpointer data) +{ + struct tcp_connect *c = data; + + assert(c->source != NULL); + assert(c->timeout_source != NULL); + + /* clear the socket source */ + g_source_unref(c->source); + c->source = NULL; + + /* delete the timeout source */ + g_source_destroy(c->timeout_source); + g_source_unref(c->timeout_source); + c->timeout_source = NULL; + + /* obtain the connect result */ + int s_err = 0; + socklen_t s_err_size = sizeof(s_err); + if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, + (char*)&s_err, &s_err_size) < 0) + s_err = errno; + + if (s_err == 0) { + /* connection established successfully */ + + c->handler->success(c->fd, c->handler_ctx); + } else { + /* there was an I/O error; close the socket and pass + the error to the handler */ + + close_socket(c->fd); + + GError *error = + g_error_new_literal(g_file_error_quark(), s_err, + g_strerror(s_err)); + c->handler->error(error, c->handler_ctx); + } + + return false; +} + +static gboolean +tcp_connect_timeout(gpointer data) +{ + struct tcp_connect *c = data; + + assert(c->source != NULL); + assert(c->timeout_source != NULL); + + /* clear the timeout source */ + g_source_unref(c->timeout_source); + c->timeout_source = NULL; + + /* delete the socket source */ + g_source_destroy(c->source); + g_source_unref(c->source); + c->source = NULL; + + /* report timeout to handler */ + c->handler->timeout(c->handler_ctx); + + return false; +} + +static gpointer +tcp_connect_init(gpointer data) +{ + struct tcp_connect *c = data; + + /* create a connect source */ + GIOChannel *channel = g_io_channel_new_socket(c->fd); + c->source = g_io_create_watch(channel, G_IO_OUT); + g_io_channel_unref(channel); + + g_source_set_callback(c->source, (GSourceFunc)tcp_connect_event, c, + NULL); + g_source_attach(c->source, io_thread_context()); + + /* create a timeout source */ + if (c->timeout_ms > 0) + c->timeout_source = + io_thread_timeout_add(c->timeout_ms, + tcp_connect_timeout, c); + + return NULL; +} + +void +tcp_connect_address(const struct sockaddr *address, size_t address_length, + unsigned timeout_ms, + const struct tcp_connect_handler *handler, void *ctx, + struct tcp_connect **handle_r) +{ + assert(address != NULL); + assert(address_length > 0); + assert(handler != NULL); + assert(handler->success != NULL); + assert(handler->error != NULL); + assert(handler->canceled != NULL); + assert(handler->timeout != NULL || timeout_ms == 0); + assert(handle_r != NULL); + assert(*handle_r == NULL); + + int fd = socket_cloexec_nonblock(address->sa_family, SOCK_STREAM, 0); + if (fd < 0) { + GError *error = + g_error_new_literal(g_file_error_quark(), errno, + g_strerror(errno)); + handler->error(error, ctx); + return; + } + + int ret = connect(fd, address, address_length); + if (ret >= 0) { + /* quick connect, no I/O thread */ + handler->success(fd, ctx); + return; + } + + if (!is_in_progress_errno(errno)) { + GError *error = + g_error_new_literal(g_file_error_quark(), errno, + g_strerror(errno)); + close_socket(fd); + handler->error(error, ctx); + return; + } + + /* got EINPROGRESS, use the I/O thread to wait for the + operation to finish */ + + struct tcp_connect *c = g_new(struct tcp_connect, 1); + c->handler = handler; + c->handler_ctx = ctx; + c->fd = fd; + c->source = NULL; + c->timeout_ms = timeout_ms; + c->timeout_source = NULL; + + *handle_r = c; + + io_thread_call(tcp_connect_init, c); +} + +static gpointer +tcp_connect_cancel_callback(gpointer data) +{ + struct tcp_connect *c = data; + + assert((c->source == NULL) == (c->timeout_source == NULL)); + + if (c->source == NULL) + return NULL; + + /* delete the socket source */ + g_source_destroy(c->source); + g_source_unref(c->source); + c->source = NULL; + + /* delete the timeout source */ + g_source_destroy(c->timeout_source); + g_source_unref(c->timeout_source); + c->timeout_source = NULL; + + /* close the socket */ + close_socket(c->fd); + + /* notify the handler */ + c->handler->canceled(c->handler_ctx); + + return NULL; +} + +void +tcp_connect_cancel(struct tcp_connect *c) +{ + if (c->source == NULL) + return; + + io_thread_call(tcp_connect_cancel_callback, c); +} + +void +tcp_connect_free(struct tcp_connect *c) +{ + assert(c->source == NULL); + + g_free(c); +} diff --git a/src/tcp_connect.h b/src/tcp_connect.h new file mode 100644 index 000000000..bdbe85c14 --- /dev/null +++ b/src/tcp_connect.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_TCP_CONNECT_H +#define MPD_TCP_CONNECT_H + +#include <glib.h> + +struct sockaddr; + +struct tcp_connect_handler { + /** + * The connection was established successfully. + * + * @param fd a file descriptor that must be closed with + * close_socket() when finished + */ + void (*success)(int fd, void *ctx); + + /** + * An error has occurred. The method is responsible for + * freeing the GError. + */ + void (*error)(GError *error, void *ctx); + + /** + * The connection could not be established in the specified + * time span. + */ + void (*timeout)(void *ctx); + + /** + * The operation was canceled before a result was available. + */ + void (*canceled)(void *ctx); +}; + +struct tcp_connect; + +/** + * Establish a TCP connection to the specified address. + * + * Note that the result may be available before this function returns. + * + * The caller must free this object with tcp_connect_free(). + * + * @param timeout_ms time out after this number of milliseconds; 0 + * means no timeout + * @param handle_r a handle that can be used to cancel the operation; + * the caller must initialize it to NULL + */ +void +tcp_connect_address(const struct sockaddr *address, size_t address_length, + unsigned timeout_ms, + const struct tcp_connect_handler *handler, void *ctx, + struct tcp_connect **handle_r); + +/** + * Cancel the operation. It is possible that the result is delivered + * before the operation has been canceled; in that case, the + * canceled() handler method will not be invoked. + * + * Even after calling this function, tcp_connect_free() must still be + * called to free memory. + */ +void +tcp_connect_cancel(struct tcp_connect *handle); + +/** + * Free memory used by this object. + * + * This function is not thread safe. It must not be called while + * other threads are still working with it. If no callback has been + * invoked so far, then you must call tcp_connect_cancel() to release + * I/O thread resources, before calling this function. + */ +void +tcp_connect_free(struct tcp_connect *handle); + +#endif diff --git a/src/tcp_socket.c b/src/tcp_socket.c new file mode 100644 index 000000000..bfed4dc56 --- /dev/null +++ b/src/tcp_socket.c @@ -0,0 +1,377 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "tcp_socket.h" +#include "fifo_buffer.h" +#include "io_thread.h" +#include "glib_socket.h" + +#include <assert.h> +#include <string.h> + +#ifdef WIN32 +#include <ws2tcpip.h> +#include <winsock.h> +#else +#include <sys/socket.h> +#include <netinet/in.h> +#endif + +struct tcp_socket { + const struct tcp_socket_handler *handler; + void *handler_ctx; + + GMutex *mutex; + + GIOChannel *channel; + GSource *in_source, *out_source; + + struct fifo_buffer *input, *output; +}; + +static gboolean +tcp_event(GIOChannel *source, GIOCondition condition, gpointer data); + +static void +tcp_socket_schedule_read(struct tcp_socket *s) +{ + assert(s->input != NULL); + assert(!fifo_buffer_is_full(s->input)); + + if (s->in_source != NULL) + return; + + s->in_source = g_io_create_watch(s->channel, + G_IO_IN|G_IO_ERR|G_IO_HUP); + g_source_set_callback(s->in_source, (GSourceFunc)tcp_event, s, NULL); + g_source_attach(s->in_source, io_thread_context()); +} + +static void +tcp_socket_unschedule_read(struct tcp_socket *s) +{ + if (s->in_source == NULL) + return; + + g_source_destroy(s->in_source); + g_source_unref(s->in_source); + s->in_source = NULL; +} + +static void +tcp_socket_schedule_write(struct tcp_socket *s) +{ + assert(s->output != NULL); + assert(!fifo_buffer_is_empty(s->output)); + + if (s->out_source != NULL) + return; + + s->out_source = g_io_create_watch(s->channel, G_IO_OUT); + g_source_set_callback(s->out_source, (GSourceFunc)tcp_event, s, NULL); + g_source_attach(s->out_source, io_thread_context()); +} + +static void +tcp_socket_unschedule_write(struct tcp_socket *s) +{ + if (s->out_source == NULL) + return; + + g_source_destroy(s->out_source); + g_source_unref(s->out_source); + s->out_source = NULL; +} + +/** + * Close the socket. Caller must lock the mutex. + */ +static void +tcp_socket_close(struct tcp_socket *s) +{ + tcp_socket_unschedule_read(s); + tcp_socket_unschedule_write(s); + + if (s->channel != NULL) { + g_io_channel_unref(s->channel); + s->channel = NULL; + } + + if (s->input != NULL) { + fifo_buffer_free(s->input); + s->input = NULL; + } + + if (s->output != NULL) { + fifo_buffer_free(s->output); + s->output = NULL; + } +} + +static gpointer +tcp_socket_close_callback(gpointer data) +{ + struct tcp_socket *s = data; + + g_mutex_lock(s->mutex); + tcp_socket_close(s); + g_mutex_unlock(s->mutex); + + return NULL; +} + +static void +tcp_socket_close_indirect(struct tcp_socket *s) +{ + io_thread_call(tcp_socket_close_callback, s); + + assert(s->channel == NULL); + assert(s->in_source == NULL); + assert(s->out_source == NULL); +} + +static void +tcp_handle_input(struct tcp_socket *s) +{ + size_t length; + const void *p = fifo_buffer_read(s->input, &length); + if (p == NULL) + return; + + g_mutex_unlock(s->mutex); + size_t consumed = s->handler->data(p, length, s->handler_ctx); + g_mutex_lock(s->mutex); + if (consumed > 0 && s->input != NULL) + fifo_buffer_consume(s->input, consumed); +} + +static bool +tcp_in_event(struct tcp_socket *s) +{ + assert(s != NULL); + assert(s->channel != NULL); + + g_mutex_lock(s->mutex); + + size_t max_length; + void *p = fifo_buffer_write(s->input, &max_length); + if (p == NULL) { + GError *error = g_error_new_literal(tcp_socket_quark(), 0, + "buffer overflow"); + tcp_socket_close(s); + g_mutex_unlock(s->mutex); + s->handler->error(error, s->handler_ctx); + return false; + } + + gsize bytes_read; + GError *error = NULL; + GIOStatus status = g_io_channel_read_chars(s->channel, + p, max_length, + &bytes_read, &error); + switch (status) { + case G_IO_STATUS_NORMAL: + fifo_buffer_append(s->input, bytes_read); + tcp_handle_input(s); + g_mutex_unlock(s->mutex); + return true; + + case G_IO_STATUS_AGAIN: + /* try again later */ + g_mutex_unlock(s->mutex); + return true; + + case G_IO_STATUS_EOF: + /* peer disconnected */ + tcp_socket_close(s); + g_mutex_unlock(s->mutex); + s->handler->disconnected(s->handler_ctx); + return false; + + case G_IO_STATUS_ERROR: + /* I/O error */ + tcp_socket_close(s); + g_mutex_unlock(s->mutex); + s->handler->error(error, s->handler_ctx); + return false; + } + + /* unreachable */ + assert(false); + return true; +} + +static bool +tcp_out_event(struct tcp_socket *s) +{ + assert(s != NULL); + assert(s->channel != NULL); + + g_mutex_lock(s->mutex); + + size_t length; + const void *p = fifo_buffer_read(s->output, &length); + if (p == NULL) { + /* no more data in the output buffer, remove the + output event */ + tcp_socket_unschedule_write(s); + g_mutex_unlock(s->mutex); + return false; + } + + gsize bytes_written; + GError *error = NULL; + GIOStatus status = g_io_channel_write_chars(s->channel, p, length, + &bytes_written, &error); + switch (status) { + case G_IO_STATUS_NORMAL: + fifo_buffer_consume(s->output, bytes_written); + g_mutex_unlock(s->mutex); + return true; + + case G_IO_STATUS_AGAIN: + tcp_socket_schedule_write(s); + g_mutex_unlock(s->mutex); + return true; + + case G_IO_STATUS_EOF: + /* peer disconnected */ + tcp_socket_close(s); + g_mutex_unlock(s->mutex); + s->handler->disconnected(s->handler_ctx); + return false; + + case G_IO_STATUS_ERROR: + /* I/O error */ + tcp_socket_close(s); + g_mutex_unlock(s->mutex); + s->handler->error(error, s->handler_ctx); + return false; + } + + /* unreachable */ + g_mutex_unlock(s->mutex); + assert(false); + return true; +} + +static gboolean +tcp_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, + gpointer data) +{ + struct tcp_socket *s = data; + + assert(source == s->channel); + + switch (condition) { + case G_IO_IN: + case G_IO_PRI: + return tcp_in_event(s); + + case G_IO_OUT: + return tcp_out_event(s); + + case G_IO_ERR: + case G_IO_HUP: + case G_IO_NVAL: + tcp_socket_close(s); + s->handler->disconnected(s->handler_ctx); + return false; + } + + /* unreachable */ + assert(false); + return false; +} + +struct tcp_socket * +tcp_socket_new(int fd, + const struct tcp_socket_handler *handler, void *ctx) +{ + assert(fd >= 0); + assert(handler != NULL); + assert(handler->data != NULL); + assert(handler->error != NULL); + assert(handler->disconnected != NULL); + + struct tcp_socket *s = g_new(struct tcp_socket, 1); + s->handler = handler; + s->handler_ctx = ctx; + s->mutex = g_mutex_new(); + + g_mutex_lock(s->mutex); + + s->channel = g_io_channel_new_socket(fd); + /* GLib is responsible for closing the file descriptor */ + g_io_channel_set_close_on_unref(s->channel, true); + /* NULL encoding means the stream is binary safe */ + g_io_channel_set_encoding(s->channel, NULL, NULL); + /* no buffering */ + g_io_channel_set_buffered(s->channel, false); + + s->input = fifo_buffer_new(4096); + s->output = fifo_buffer_new(4096); + + s->in_source = NULL; + s->out_source = NULL; + + tcp_socket_schedule_read(s); + + g_mutex_unlock(s->mutex); + + return s; +} + +void +tcp_socket_free(struct tcp_socket *s) +{ + tcp_socket_close_indirect(s); + g_mutex_free(s->mutex); + g_free(s); +} + +bool +tcp_socket_send(struct tcp_socket *s, const void *data, size_t length) +{ + assert(s != NULL); + + g_mutex_lock(s->mutex); + + if (s->output == NULL || s->channel == NULL) { + /* already disconnected */ + g_mutex_unlock(s->mutex); + return false; + } + + size_t max_length; + void *p = fifo_buffer_write(s->output, &max_length); + if (p == NULL || max_length < length) { + /* buffer is full */ + g_mutex_unlock(s->mutex); + return false; + } + + memcpy(p, data, length); + fifo_buffer_append(s->output, length); + tcp_socket_schedule_write(s); + + g_mutex_unlock(s->mutex); + return true; +} + diff --git a/src/tcp_socket.h b/src/tcp_socket.h new file mode 100644 index 000000000..b6b367b86 --- /dev/null +++ b/src/tcp_socket.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_TCP_SOCKET_H +#define MPD_TCP_SOCKET_H + +#include <glib.h> + +#include <stdbool.h> +#include <stddef.h> + +struct sockaddr; + +struct tcp_socket_handler { + /** + * New data has arrived. + * + * @return the number of bytes consumed; 0 if more data is + * needed + */ + size_t (*data)(const void *data, size_t length, void *ctx); + + void (*error)(GError *error, void *ctx); + + void (*disconnected)(void *ctx); +}; + +static inline GQuark +tcp_socket_quark(void) +{ + return g_quark_from_static_string("tcp_socket"); +} + +G_GNUC_MALLOC +struct tcp_socket * +tcp_socket_new(int fd, + const struct tcp_socket_handler *handler, void *ctx); + +void +tcp_socket_free(struct tcp_socket *s); + +bool +tcp_socket_send(struct tcp_socket *s, const void *data, size_t length); + +#endif diff --git a/src/text_file.c b/src/text_file.c index 355217aba..3674e5ce2 100644 --- a/src/text_file.c +++ b/src/text_file.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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.h b/src/text_file.h index d016f8f7a..9dd810943 100644 --- a/src/text_file.h +++ b/src/text_file.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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_input_stream.c b/src/text_input_stream.c index 6d0436d41..4a858fc85 100644 --- a/src/text_input_stream.c +++ b/src/text_input_stream.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 @@ text_input_stream_read(struct text_input_stream *tis) newline character */ --length; - nbytes = input_stream_read(tis->is, dest, length, - &error); + nbytes = input_stream_lock_read(tis->is, dest, length, + &error); if (nbytes > 0) fifo_buffer_append(tis->buffer, nbytes); else if (error != NULL) { diff --git a/src/text_input_stream.h b/src/text_input_stream.h index a1fda065d..9b3245689 100644 --- a/src/text_input_stream.h +++ b/src/text_input_stream.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/timer.c b/src/timer.c index ba82fc522..691ab76be 100644 --- a/src/timer.c +++ b/src/timer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -37,9 +37,9 @@ static uint64_t now(void) return ((uint64_t)tv.tv_sec * 1000000) + tv.tv_usec; } -Timer *timer_new(const struct audio_format *af) +struct timer *timer_new(const struct audio_format *af) { - Timer *timer = g_new(Timer, 1); + struct timer *timer = g_new(struct timer, 1); timer->time = 0; timer->started = 0; timer->rate = af->sample_rate * audio_format_frame_size(af); @@ -47,24 +47,24 @@ Timer *timer_new(const struct audio_format *af) return timer; } -void timer_free(Timer *timer) +void timer_free(struct timer *timer) { g_free(timer); } -void timer_start(Timer *timer) +void timer_start(struct timer *timer) { timer->time = now(); timer->started = 1; } -void timer_reset(Timer *timer) +void timer_reset(struct timer *timer) { timer->time = 0; timer->started = 0; } -void timer_add(Timer *timer, int size) +void timer_add(struct timer *timer, int size) { assert(timer->started); @@ -72,7 +72,7 @@ void timer_add(Timer *timer, int size) } unsigned -timer_delay(const Timer *timer) +timer_delay(const struct timer *timer) { int64_t delay = (int64_t)(timer->time - now()) / 1000; if (delay < 0) @@ -84,7 +84,7 @@ timer_delay(const Timer *timer) return delay; } -void timer_sync(Timer *timer) +void timer_sync(struct timer *timer) { int64_t sleep_duration; diff --git a/src/timer.h b/src/timer.h index bbd895b31..184881249 100644 --- a/src/timer.h +++ b/src/timer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,28 +24,28 @@ struct audio_format; -typedef struct _Timer { +struct timer { uint64_t time; int started; int rate; -} Timer; +}; -Timer *timer_new(const struct audio_format *af); +struct timer *timer_new(const struct audio_format *af); -void timer_free(Timer *timer); +void timer_free(struct timer *timer); -void timer_start(Timer *timer); +void timer_start(struct timer *timer); -void timer_reset(Timer *timer); +void timer_reset(struct timer *timer); -void timer_add(Timer *timer, int size); +void timer_add(struct timer *timer, int size); /** * Returns the number of milliseconds to sleep to get back to sync. */ unsigned -timer_delay(const Timer *timer); +timer_delay(const struct timer *timer); -void timer_sync(Timer *timer); +void timer_sync(struct timer *timer); #endif diff --git a/src/tokenizer.c b/src/tokenizer.c index 2b9e05070..bbb34e100 100644 --- a/src/tokenizer.c +++ b/src/tokenizer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,6 +19,7 @@ #include "config.h" #include "tokenizer.h" +#include "string_util.h" #include <stdbool.h> #include <assert.h> @@ -72,7 +73,7 @@ tokenizer_next_word(char **input_p, GError **error_r) /* a whitespace: the word ends here */ *input = 0; /* skip all following spaces, too */ - input = g_strchug(input + 1); + input = strchug_fast(input + 1); break; } @@ -126,7 +127,7 @@ tokenizer_next_unquoted(char **input_p, GError **error_r) /* a whitespace: the word ends here */ *input = 0; /* skip all following spaces, too */ - input = g_strchug(input + 1); + input = strchug_fast(input + 1); break; } @@ -205,7 +206,7 @@ tokenizer_next_string(char **input_p, GError **error_r) /* finish the string and return it */ *dest = 0; - *input_p = g_strchug(input); + *input_p = strchug_fast(input); return word; } diff --git a/src/tokenizer.h b/src/tokenizer.h index 61ff398a4..d55eb3ca6 100644 --- a/src/tokenizer.h +++ b/src/tokenizer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/udp_server.c b/src/udp_server.c new file mode 100644 index 000000000..f96b40b6b --- /dev/null +++ b/src/udp_server.c @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "udp_server.h" +#include "io_thread.h" +#include "glib_socket.h" +#include "gcc.h" + +#include <glib.h> +#include <unistd.h> +#include <sys/time.h> +#include <errno.h> + +#ifdef WIN32 +#include <ws2tcpip.h> +#include <winsock.h> +#else +#include <sys/socket.h> +#include <netinet/in.h> +#endif + +#if GCC_CHECK_VERSION(4, 2) +/* allow C99 initialisers on struct sockaddr_in, even if the + (non-portable) attribute "sin_zero" is missing */ +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + +struct udp_server { + const struct udp_server_handler *handler; + void *handler_ctx; + + int fd; + GIOChannel *channel; + GSource *source; + + char buffer[8192]; +}; + +static gboolean +udp_in_event(G_GNUC_UNUSED GIOChannel *source, + G_GNUC_UNUSED GIOCondition condition, + gpointer data) +{ + struct udp_server *udp = data; + + struct sockaddr_storage address_storage; + struct sockaddr *address = (struct sockaddr *)&address_storage; + socklen_t address_length = sizeof(address_storage); + + ssize_t nbytes = recvfrom(udp->fd, udp->buffer, sizeof(udp->buffer), +#ifdef WIN32 + 0, +#else + MSG_DONTWAIT, +#endif + address, &address_length); + if (nbytes <= 0) + return true; + + udp->handler->datagram(udp->fd, udp->buffer, nbytes, + address, address_length, udp->handler_ctx); + return true; +} + +struct udp_server * +udp_server_new(unsigned port, + const struct udp_server_handler *handler, void *ctx, + GError **error_r) +{ + int fd = socket(PF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + g_set_error(error_r, udp_server_quark(), errno, + "failed to create UDP socket: %s", + g_strerror(errno)); + return NULL; + } + + const struct sockaddr_in address = { + .sin_family = AF_INET, + .sin_addr = { + .s_addr = htonl(INADDR_ANY), + }, + .sin_port = htons(port), +#if defined(__linux__) && !GCC_CHECK_VERSION(4, 2) + .sin_zero = { 0 }, +#endif + }; + + if (bind(fd, (const struct sockaddr *)&address, sizeof(address)) < 0) { + g_set_error(error_r, udp_server_quark(), errno, + "failed to bind UDP port %u: %s", + port, g_strerror(errno)); + close(fd); + return NULL; + } + + struct udp_server *udp = g_new(struct udp_server, 1); + udp->handler = handler; + udp->handler_ctx = ctx; + + udp->fd = fd; + udp->channel = g_io_channel_new_socket(fd); + /* NULL encoding means the stream is binary safe */ + g_io_channel_set_encoding(udp->channel, NULL, NULL); + /* no buffering */ + g_io_channel_set_buffered(udp->channel, false); + + udp->source = g_io_create_watch(udp->channel, G_IO_IN); + g_source_set_callback(udp->source, (GSourceFunc)udp_in_event, udp, + NULL); + g_source_attach(udp->source, io_thread_context()); + + return udp; +} + +void +udp_server_free(struct udp_server *udp) +{ + g_source_destroy(udp->source); + g_source_unref(udp->source); + g_io_channel_unref(udp->channel); + close(udp->fd); + g_free(udp); +} diff --git a/src/dirvec.h b/src/udp_server.h index a1a97d9f1..9e3471a45 100644 --- a/src/dirvec.h +++ b/src/udp_server.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,37 +17,36 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DIRVEC_H -#define MPD_DIRVEC_H +#ifndef MPD_UDP_SERVER_H +#define MPD_UDP_SERVER_H -#include <stddef.h> - -struct dirvec { - struct directory **base; - size_t nr; -}; - -void dirvec_init(void); +#include <glib.h> -void dirvec_deinit(void); - -void dirvec_sort(struct dirvec *dv); - -struct directory *dirvec_find(const struct dirvec *dv, const char *path); +#include <stddef.h> -int dirvec_delete(struct dirvec *dv, struct directory *del); +struct sockaddr; -void dirvec_add(struct dirvec *dv, struct directory *add); +struct udp_server_handler { + /** + * A datagram was received. + */ + void (*datagram)(int fd, const void *data, size_t length, + const struct sockaddr *source_address, + size_t source_address_length, void *ctx); +}; -static inline void -dirvec_clear(struct dirvec *dv) +static inline GQuark +udp_server_quark(void) { - dv->nr = 0; + return g_quark_from_static_string("udp_server"); } -void dirvec_destroy(struct dirvec *dv); +struct udp_server * +udp_server_new(unsigned port, + const struct udp_server_handler *handler, void *ctx, + GError **error_r); -int dirvec_for_each(const struct dirvec *dv, - int (*fn)(struct directory *, void *), void *arg); +void +udp_server_free(struct udp_server *udp); -#endif /* DIRVEC_H */ +#endif diff --git a/src/update.c b/src/update.c index d57fb114d..2df1a5bf6 100644 --- a/src/update.c +++ b/src/update.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,6 +19,7 @@ #include "config.h" #include "update_internal.h" +#include "update_remove.h" #include "update.h" #include "database.h" #include "mapper.h" @@ -68,8 +69,14 @@ static void * update_task(void *_path) modified = update_walk(path, discard); - if (modified || !db_exists()) - db_save(); + if (modified || !db_exists()) { + GError *error = NULL; + if (!db_save(&error)) { + g_warning("Failed to save database: %s", + error->message); + g_error_free(error); + } + } if (path != NULL && *path != 0) g_debug("finished: %s", path); diff --git a/src/update.h b/src/update.h index 3f8a6f6a4..3d586b694 100644 --- a/src/update.h +++ b/src/update.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/update_db.c b/src/update_db.c new file mode 100644 index 000000000..8982a53e2 --- /dev/null +++ b/src/update_db.c @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_db.h" +#include "update_remove.h" +#include "directory.h" +#include "song.h" +#include "playlist_vector.h" +#include "db_lock.h" + +#include <glib.h> +#include <assert.h> + +void +delete_song(struct directory *dir, struct song *del) +{ + assert(del->parent == dir); + + /* first, prevent traversers in main task from getting this */ + directory_remove_song(dir, del); + + db_unlock(); /* temporary unlock, because update_remove_song() blocks */ + + /* 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); + + db_lock(); +} + +/** + * Recursively remove all sub directories and songs from a directory, + * leaving an empty directory. + * + * Caller must lock the #db_mutex. + */ +static void +clear_directory(struct directory *directory) +{ + struct directory *child, *n; + directory_for_each_child_safe(child, n, directory) + delete_directory(child); + + struct song *song, *ns; + directory_for_each_song_safe(song, ns, directory) { + assert(song->parent == directory); + delete_song(directory, song); + } +} + +void +delete_directory(struct directory *directory) +{ + assert(directory->parent != NULL); + + clear_directory(directory); + + directory_delete(directory); +} + +bool +delete_name_in(struct directory *parent, const char *name) +{ + bool modified = false; + + db_lock(); + struct directory *directory = directory_get_child(parent, name); + + if (directory != NULL) { + delete_directory(directory); + modified = true; + } + + struct song *song = directory_get_song(parent, name); + if (song != NULL) { + delete_song(parent, song); + modified = true; + } + + playlist_vector_remove(&parent->playlists, name); + + db_unlock(); + + return modified; +} diff --git a/src/update_db.h b/src/update_db.h new file mode 100644 index 000000000..0a9e46b05 --- /dev/null +++ b/src/update_db.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_DB_H +#define MPD_UPDATE_DB_H + +#include "check.h" + +#include <stdbool.h> + +struct directory; +struct song; + +/** + * Caller must lock the #db_mutex. + */ +void +delete_song(struct directory *parent, struct song *song); + +/** + * Recursively free a directory and all its contents. + * + * Caller must lock the #db_mutex. + */ +void +delete_directory(struct directory *directory); + +/** + * Caller must NOT lock the #db_mutex. + * + * @return true if the database was modified + */ +bool +delete_name_in(struct directory *parent, const char *name); + +#endif diff --git a/src/update_internal.h b/src/update_internal.h index 65744f0d6..9da74d523 100644 --- a/src/update_internal.h +++ b/src/update_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -47,18 +47,4 @@ update_walk_global_finish(void); 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_io.c b/src/update_io.c new file mode 100644 index 000000000..887ebe2e5 --- /dev/null +++ b/src/update_io.c @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_io.h" +#include "mapper.h" +#include "directory.h" + +#include <glib.h> + +#include <errno.h> +#include <unistd.h> + +int +stat_directory(const struct directory *directory, struct stat *st) +{ + char *path_fs = map_directory_fs(directory); + if (path_fs == NULL) + return -1; + + int ret = stat(path_fs, st); + if (ret < 0) + g_warning("Failed to stat %s: %s", path_fs, g_strerror(errno)); + + g_free(path_fs); + return ret; +} + +int +stat_directory_child(const struct directory *parent, const char *name, + struct stat *st) +{ + char *path_fs = map_directory_child_fs(parent, name); + if (path_fs == NULL) + return -1; + + int ret = stat(path_fs, st); + if (ret < 0) + g_warning("Failed to stat %s: %s", path_fs, g_strerror(errno)); + + g_free(path_fs); + return ret; +} + +bool +directory_exists(const struct directory *directory) +{ + char *path_fs = map_directory_fs(directory); + if (path_fs == NULL) + /* invalid path: cannot exist */ + return false; + + GFileTest test = directory->device == DEVICE_INARCHIVE || + directory->device == DEVICE_CONTAINER + ? G_FILE_TEST_IS_REGULAR + : G_FILE_TEST_IS_DIR; + + bool exists = g_file_test(path_fs, test); + g_free(path_fs); + + return exists; +} + +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; +} + +bool +directory_child_access(const struct directory *directory, + const char *name, int mode) +{ +#ifdef WIN32 + /* access() is useless on WIN32 */ + (void)directory; + (void)name; + (void)mode; + 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 +} diff --git a/src/update_io.h b/src/update_io.h new file mode 100644 index 000000000..6ff1ccebd --- /dev/null +++ b/src/update_io.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_IO_H +#define MPD_UPDATE_IO_H + +#include "check.h" + +#include <stdbool.h> +#include <sys/stat.h> + +struct directory; + +int +stat_directory(const struct directory *directory, struct stat *st); + +int +stat_directory_child(const struct directory *parent, const char *name, + struct stat *st); + +bool +directory_exists(const struct directory *directory); + +bool +directory_child_is_regular(const struct directory *directory, + const char *name_utf8); + +/** + * Checks if the given permissions on the mapped file are given. + */ +bool +directory_child_access(const struct directory *directory, + const char *name, int mode); + +#endif diff --git a/src/update_queue.c b/src/update_queue.c index d7b2d4e5f..4de250cc2 100644 --- a/src/update_queue.c +++ b/src/update_queue.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/update_remove.c b/src/update_remove.c index 8d60be222..f443f5eb2 100644 --- a/src/update_remove.c +++ b/src/update_remove.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,11 +18,11 @@ */ #include "config.h" /* must be first for large file support */ -#include "update_internal.h" -#include "notify.h" +#include "update_remove.h" #include "event_pipe.h" #include "song.h" #include "playlist.h" +#include "main.h" #ifdef ENABLE_SQLITE #include "sticker.h" @@ -35,7 +35,8 @@ static const struct song *removed_song; -static struct notify remove_notify; +static GMutex *remove_mutex; +static GCond *remove_cond; /** * Safely remove a song from the database. This must be done in the @@ -58,16 +59,20 @@ song_remove_event(void) sticker_song_delete(removed_song); #endif - playlist_delete_song(&g_playlist, removed_song); - removed_song = NULL; + playlist_delete_song(&g_playlist, global_player_control, removed_song); - notify_signal(&remove_notify); + /* clear "removed_song" and send signal to update thread */ + g_mutex_lock(remove_mutex); + removed_song = NULL; + g_cond_signal(remove_cond); + g_mutex_unlock(remove_mutex); } void update_remove_global_init(void) { - notify_init(&remove_notify); + remove_mutex = g_mutex_new(); + remove_cond = g_cond_new(); event_pipe_register(PIPE_EVENT_DELETE, song_remove_event); } @@ -75,7 +80,8 @@ update_remove_global_init(void) void update_remove_global_finish(void) { - notify_deinit(&remove_notify); + g_mutex_free(remove_mutex); + g_cond_free(remove_cond); } void @@ -87,8 +93,10 @@ update_remove_song(const struct song *song) event_pipe_emit(PIPE_EVENT_DELETE); - do { - notify_wait(&remove_notify); - } while (removed_song != NULL); + g_mutex_lock(remove_mutex); + + while (removed_song != NULL) + g_cond_wait(remove_cond, remove_mutex); + g_mutex_unlock(remove_mutex); } diff --git a/src/update_remove.h b/src/update_remove.h new file mode 100644 index 000000000..479ef83ff --- /dev/null +++ b/src/update_remove.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_REMOVE_H +#define MPD_UPDATE_REMOVE_H + +#include "check.h" + +struct song; + +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_walk.c b/src/update_walk.c index 5d2f778ff..9ca9115bd 100644 --- a/src/update_walk.c +++ b/src/update_walk.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,10 +19,14 @@ #include "config.h" /* must be first for large file support */ #include "update_internal.h" +#include "update_io.h" +#include "update_db.h" #include "database.h" +#include "db_lock.h" #include "exclude.h" #include "directory.h" #include "song.h" +#include "playlist_vector.h" #include "uri.h" #include "mapper.h" #include "path.h" @@ -30,6 +34,8 @@ #include "decoder_plugin.h" #include "playlist_list.h" #include "conf.h" +#include "tag.h" +#include "tag_handler.h" #ifdef ENABLE_ARCHIVE #include "archive_list.h" @@ -90,108 +96,13 @@ directory_set_stat(struct directory *dir, const struct stat *st) } 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; + db_lock(); - for (i = dv->nr; --i >= 0; ) { - struct directory *child = dv->base[i]; + struct directory *child, *n; + directory_for_each_child_safe(child, n, directory) { char *name_fs = utf8_to_fs_charset(directory_get_name(child)); if (exclude_list_check(exclude_list, name_fs)) { @@ -202,128 +113,61 @@ remove_excluded_from_directory(struct directory *directory, g_free(name_fs); } - songvec_for_each(&directory->songs, - delete_song_if_excluded, exclude_list); -} + struct song *song, *ns; + directory_for_each_song_safe(song, ns, directory) { + assert(song->parent == directory); -/* 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; + char *name_fs = utf8_to_fs_charset(song->uri); + if (exclude_list_check(exclude_list, name_fs)) { + delete_song(directory, song); + modified = true; + } - if ((path = map_song_fs(song)) == NULL || - stat(path, &st) < 0 || !S_ISREG(st.st_mode)) { - delete_song(dir, song); - modified = true; + g_free(name_fs); } - 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; + db_unlock(); } 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])) + struct directory *child, *n; + directory_for_each_child_safe(child, n, directory) { + if (directory_exists(child)) continue; - g_debug("removing directory: %s", dv->base[i]->path); - delete_directory(dv->base[i]); + db_lock(); + delete_directory(child); + db_unlock(); + 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; + struct song *song, *ns; + directory_for_each_song_safe(song, ns, directory) { + char *path; + struct stat st; + if ((path = map_song_fs(song)) == NULL || + stat(path, &st) < 0 || !S_ISREG(st.st_mode)) { + db_lock(); + delete_song(directory, song); + db_unlock(); - if (!directory_child_is_regular(directory, pm->name)) - playlist_vector_remove(&directory->playlists, pm->name); + modified = true; + } - pm = next; + g_free(path); } -} - -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); - if (ret < 0) - g_warning("Failed to stat %s: %s", path_fs, g_strerror(errno)); - - 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); - if (ret < 0) - g_warning("Failed to stat %s: %s", path_fs, g_strerror(errno)); - - g_free(path_fs); - return ret; + struct playlist_metadata *pm, *np; + directory_for_each_playlist_safe(pm, np, directory) { + if (!directory_child_is_regular(directory, pm->name)) { + db_lock(); + playlist_vector_remove(&directory->playlists, pm->name); + db_unlock(); + } + } } #ifndef G_OS_WIN32 @@ -363,45 +207,21 @@ inodeFoundInParent(struct directory *parent, ino_t inode, dev_t device) 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; - } + db_lock(); + subdir = directory_make_child(directory, name); + subdir->device = DEVICE_INARCHIVE; + db_unlock(); //create directories first update_archive_tree(subdir, tmp+1); } else { @@ -410,11 +230,13 @@ update_archive_tree(struct directory *directory, char *name) return; } //add file - song = songvec_find(&directory->songs, name); + db_lock(); + struct song *song = directory_get_song(directory, name); + db_unlock(); if (song == NULL) { song = song_file_load(name, directory); if (song != NULL) { - songvec_add(&directory->songs, song); + directory_add_song(directory, song); modified = true; g_message("added %s/%s", directory_get_path(directory), name); @@ -442,7 +264,9 @@ update_archive_file(struct directory *parent, const char *name, struct directory *directory; char *filepath; - directory = dirvec_find(&parent->children, name); + db_lock(); + directory = directory_get_child(parent, name); + db_unlock(); if (directory != NULL && directory->mtime == st->st_mtime && !walk_discard) /* MPD has already scanned the archive, and it hasn't @@ -465,10 +289,12 @@ update_archive_file(struct directory *parent, const char *name, if (directory == NULL) { g_debug("creating archive directory: %s", name); - directory = make_subdir(parent, name); + db_lock(); + directory = directory_new_child(parent, name); /* mark this directory as archive (we use device for this) */ directory->device = DEVICE_INARCHIVE; + db_unlock(); } directory->mtime = st->st_mtime; @@ -494,7 +320,9 @@ update_container_file( struct directory* directory, char* vtrack = NULL; unsigned int tnum = 0; char* pathname = map_directory_child_fs(directory, name); - struct directory* contdir = dirvec_find(&directory->children, name); + + db_lock(); + struct directory *contdir = directory_get_child(directory, name); // directory exists already if (contdir != NULL) @@ -510,14 +338,16 @@ update_container_file( struct directory* directory, modified = true; } else { + db_unlock(); g_free(pathname); return true; } } - contdir = make_subdir(directory, name); + contdir = directory_make_child(directory, name); contdir->mtime = st->st_mtime; contdir->device = DEVICE_CONTAINER; + db_unlock(); while ((vtrack = plugin->container_scan(pathname, ++tnum)) != NULL) { @@ -529,10 +359,12 @@ update_container_file( struct directory* directory, child_path_fs = map_directory_child_fs(contdir, vtrack); - song->tag = plugin->tag_dup(child_path_fs); + song->tag = tag_new(); + decoder_plugin_scan_file(plugin, child_path_fs, + &add_tag_handler, song->tag); g_free(child_path_fs); - songvec_add(&contdir->songs, song); + directory_add_song(contdir, song); modified = true; @@ -545,37 +377,76 @@ update_container_file( struct directory* directory, if (tnum == 1) { + db_lock(); delete_directory(contdir); + db_unlock(); 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; - (void)mode; - 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; +static void +update_song_file(struct directory *directory, + const char *name, const struct stat *st, + const struct decoder_plugin *plugin) +{ + db_lock(); + struct song *song = directory_get_song(directory, name); + db_unlock(); + + if (!directory_child_access(directory, name, R_OK)) { + g_warning("no read permissions on %s/%s", + directory_get_path(directory), name); + if (song != NULL) { + db_lock(); + delete_song(directory, song); + db_unlock(); + } - bool success = access(path, mode) == 0 || errno != EACCES; - g_free(path); - return success; -#endif + return; + } + + if (!(song != NULL && st->st_mtime == song->mtime && + !walk_discard) && + plugin->container_scan != NULL && + update_container_file(directory, name, st, plugin)) { + if (song != NULL) { + db_lock(); + delete_song(directory, song); + db_unlock(); + } + + return; + } + + if (song == NULL) { + g_debug("reading %s/%s", + directory_get_path(directory), name); + song = song_file_load(name, directory); + if (song == NULL) { + g_debug("ignoring unrecognized file %s/%s", + directory_get_path(directory), name); + return; + } + + directory_add_song(directory, 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); + db_lock(); + delete_song(directory, song); + db_unlock(); + } + + modified = true; + } } static void @@ -592,63 +463,18 @@ update_regular_file(struct directory *directory, 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) { - g_debug("reading %s/%s", - directory_get_path(directory), name); - 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; - } + update_song_file(directory, name, st, plugin); #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)) { + db_lock(); if (playlist_vector_update_or_add(&directory->playlists, name, st->st_mtime)) modified = true; + db_unlock(); } } @@ -670,12 +496,18 @@ updateInDirectory(struct directory *directory, if (inodeFoundInParent(directory, st->st_ino, st->st_dev)) return; - subdir = make_subdir(directory, name); + db_lock(); + subdir = directory_make_child(directory, name); + db_unlock(); + assert(directory == subdir->parent); ret = updateDirectory(subdir, st); - if (!ret) + if (!ret) { + db_lock(); delete_directory(subdir); + db_unlock(); + } } else { g_debug("update: %s is not a directory, archive or music", name); } @@ -806,7 +638,7 @@ updateDirectory(struct directory *directory, const struct stat *st) continue; if (skip_symlink(directory, utf8)) { - delete_name_in(directory, utf8); + modified |= delete_name_in(directory, utf8); g_free(utf8); continue; } @@ -814,7 +646,7 @@ updateDirectory(struct directory *directory, const struct stat *st) if (stat_directory_child(directory, utf8, &st2) == 0) updateInDirectory(directory, utf8, &st2); else - delete_name_in(directory, utf8); + modified |= delete_name_in(directory, utf8); g_free(utf8); } @@ -829,37 +661,34 @@ updateDirectory(struct directory *directory, const struct stat *st) } static struct directory * -directory_make_child_checked(struct directory *parent, const char *path) +directory_make_child_checked(struct directory *parent, const char *name_utf8) { struct directory *directory; - char *base; struct stat st; - struct song *conflicting; - directory = directory_get_child(parent, path); + db_lock(); + directory = directory_get_child(parent, name_utf8); + db_unlock(); 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); + if (stat_directory_child(parent, name_utf8, &st) < 0 || + inodeFoundInParent(parent, st.st_ino, st.st_dev)) return NULL; - } - if (skip_symlink(parent, path)) + if (skip_symlink(parent, name_utf8)) return NULL; /* if we're adding directory paths, make sure to delete filenames with potentially the same name */ - conflicting = songvec_find(&parent->songs, base); + db_lock(); + struct song *conflicting = directory_get_song(parent, name_utf8); if (conflicting) delete_song(parent, conflicting); - g_free(base); + directory = directory_new_child(parent, name_utf8); + db_unlock(); - directory = directory_new_child(parent, path); directory_set_stat(directory, &st); return directory; } @@ -869,17 +698,20 @@ addParentPathToDB(const char *utf8path) { struct directory *directory = db_get_root(); char *duplicated = g_strdup(utf8path); - char *slash = duplicated; + char *name_utf8 = duplicated, *slash; - while ((slash = strchr(slash, '/')) != NULL) { + while ((slash = strchr(name_utf8, '/')) != NULL) { *slash = 0; + if (*name_utf8 == 0) + continue; + directory = directory_make_child_checked(directory, - duplicated); - if (directory == NULL || slash == NULL) + name_utf8); + if (directory == NULL) break; - *slash++ = '/'; + name_utf8 = slash + 1; } g_free(duplicated); @@ -903,7 +735,7 @@ updatePath(const char *path) stat_directory_child(parent, name, &st) == 0) updateInDirectory(parent, name, &st); else - delete_name_in(parent, name); + modified |= delete_name_in(parent, name); g_free(name); } @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/util/bit_reverse.c b/src/util/bit_reverse.c new file mode 100644 index 000000000..ba8a23ef1 --- /dev/null +++ b/src/util/bit_reverse.c @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "bit_reverse.h" + +/** + * @see http://graphics.stanford.edu/~seander/bithacks.html#BitReverseTable + */ +const uint8_t bit_reverse_table[256] = +{ +#define R2(n) n, n + 2*64, n + 1*64, n + 3*64 +#define R4(n) R2(n), R2(n + 2*16), R2(n + 1*16), R2(n + 3*16) +#define R6(n) R4(n), R4(n + 2*4 ), R4(n + 1*4 ), R4(n + 3*4 ) + R6(0), R6(2), R6(1), R6(3) +}; diff --git a/src/util/bit_reverse.h b/src/util/bit_reverse.h new file mode 100644 index 000000000..e44693b1d --- /dev/null +++ b/src/util/bit_reverse.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_BIT_REVERSE_H +#define MPD_BIT_REVERSE_H + +#include <glib.h> +#include <stdint.h> + +extern const uint8_t bit_reverse_table[256]; + +G_GNUC_CONST +static inline uint8_t +bit_reverse(uint8_t x) +{ + return bit_reverse_table[x]; +} + +#endif diff --git a/src/util/byte_reverse.c b/src/util/byte_reverse.c new file mode 100644 index 000000000..e96af14b9 --- /dev/null +++ b/src/util/byte_reverse.c @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "byte_reverse.h" + +#include <glib.h> +#include <assert.h> + +void +reverse_bytes_16(uint16_t *dest, const uint16_t *src, const uint16_t *src_end) +{ + assert(dest != NULL); + assert(src != NULL); + assert(src_end >= src); + + while (src < src_end) { + const uint16_t x = *src++; + *dest++ = GUINT16_SWAP_LE_BE(x); + } +} + +void +reverse_bytes_32(uint32_t *dest, const uint32_t *src, const uint32_t *src_end) +{ + assert(dest != NULL); + assert(src != NULL); + assert(src_end >= src); + + while (src < src_end) { + const uint32_t x = *src++; + *dest++ = GUINT32_SWAP_LE_BE(x); + } +} + +void +reverse_bytes_64(uint64_t *dest, const uint64_t *src, const uint64_t *src_end) +{ + assert(dest != NULL); + assert(src != NULL); + assert(src_end >= src); + + while (src < src_end) { + const uint64_t x = *src++; + *dest++ = GUINT64_SWAP_LE_BE(x); + } +} + +static void +reverse_bytes_linear(uint8_t *dest, const uint8_t *src, size_t n) +{ + src += n; + + while (n-- > 0) + *dest++ = *--src; +} + +static void +reverse_bytes_generic(uint8_t *dest, + const uint8_t *src, const uint8_t *src_end, + size_t frame_size) +{ + assert(dest != NULL); + assert(src != NULL); + assert(src_end >= src); + assert(frame_size > 0); + assert((src_end - src) % frame_size == 0); + + while (src < src_end) { + reverse_bytes_linear(dest, src, frame_size); + dest += frame_size; + src += frame_size; + } +} + +void +reverse_bytes(uint8_t *dest, const uint8_t *src, const uint8_t *src_end, + size_t frame_size) +{ + assert(dest != NULL); + assert(src != NULL); + assert(src_end >= src); + assert(frame_size > 0); + assert((src_end - src) % frame_size == 0); + + switch (frame_size) { + case 2: + reverse_bytes_16((uint16_t *)dest, + (const uint16_t *)src, + (const uint16_t *)src_end); + break; + + case 4: + reverse_bytes_32((uint32_t *)dest, + (const uint32_t *)src, + (const uint32_t *)src_end); + break; + + case 8: + reverse_bytes_64((uint64_t *)dest, + (const uint64_t *)src, + (const uint64_t *)src_end); + break; + + default: + reverse_bytes_generic(dest, src, src_end, frame_size); + } +} diff --git a/src/util/byte_reverse.h b/src/util/byte_reverse.h new file mode 100644 index 000000000..63213d6c2 --- /dev/null +++ b/src/util/byte_reverse.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_BYTE_REVERSE_H +#define MPD_BYTE_REVERSE_H + +#include <stdint.h> +#include <stddef.h> + +/** + * Reverse the bytes in each 16 bit "frame". This function can be + * used for in-place operation. + */ +void +reverse_bytes_16(uint16_t *dest, const uint16_t *src, const uint16_t *src_end); + +/** + * Reverse the bytes in each 32 bit "frame". This function can be + * used for in-place operation. + */ +void +reverse_bytes_32(uint32_t *dest, const uint32_t *src, const uint32_t *src_end); + +/** + * Reverse the bytes in each 64 bit "frame". This function can be + * used for in-place operation. + */ +void +reverse_bytes_64(uint64_t *dest, const uint64_t *src, const uint64_t *src_end); + +/** + * Reverse the bytes in each "frame". This function cannot be used + * for in-place operation. + */ +void +reverse_bytes(uint8_t *dest, const uint8_t *src, const uint8_t *src_end, + size_t frame_size); + +#endif diff --git a/src/util/list.h b/src/util/list.h new file mode 100644 index 000000000..fdab47675 --- /dev/null +++ b/src/util/list.h @@ -0,0 +1,608 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 code was imported from the Linux kernel. + * + */ + +#ifndef _LINUX_LIST_H +#define _LINUX_LIST_H + +#include <glib.h> + +#ifdef __clang__ +/* allow typeof() */ +#pragma GCC diagnostic ignored "-Wlanguage-extension-token" +#endif + +/** + * container_of - cast a member of a structure out to the containing structure + * @ptr: the pointer to the member. + * @type: the type of the container struct this is embedded in. + * @member: the name of the member within the struct. + * + */ +#define container_of(ptr, type, member) \ + (&G_STRUCT_MEMBER(type, ptr, -G_STRUCT_OFFSET(type, member))) + +/* + * These are non-NULL pointers that will result in page faults + * under normal circumstances, used to verify that nobody uses + * non-initialized list entries. + */ +#define LIST_POISON1 ((void *) 0x00100100) +#define LIST_POISON2 ((void *) 0x00200200) + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +static inline void INIT_LIST_HEAD(struct list_head *list) +{ + list->next = list; + list->prev = list; +} + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +#ifndef CONFIG_DEBUG_LIST +static inline void __list_add(struct list_head *new, + struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} +#else +extern void __list_add(struct list_head *new, + struct list_head *prev, + struct list_head *next); +#endif + +/** + * list_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static inline void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + + +/** + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_del(struct list_head * prev, struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty() on entry does not return true after this, the entry is + * in an undefined state. + */ +#ifndef CONFIG_DEBUG_LIST +static inline void __list_del_entry(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); +} + +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + entry->next = LIST_POISON1; + entry->prev = LIST_POISON2; +} +#else +extern void __list_del_entry(struct list_head *entry); +extern void list_del(struct list_head *entry); +#endif + +/** + * list_replace - replace old entry by new one + * @old : the element to be replaced + * @new : the new element to insert + * + * If @old was empty, it will be overwritten. + */ +static inline void list_replace(struct list_head *old, + struct list_head *new) +{ + new->next = old->next; + new->next->prev = new; + new->prev = old->prev; + new->prev->next = new; +} + +static inline void list_replace_init(struct list_head *old, + struct list_head *new) +{ + list_replace(old, new); + INIT_LIST_HEAD(old); +} + +/** + * list_del_init - deletes entry from list and reinitialize it. + * @entry: the element to delete from the list. + */ +static inline void list_del_init(struct list_head *entry) +{ + __list_del_entry(entry); + INIT_LIST_HEAD(entry); +} + +/** + * list_move - delete from one list and add as another's head + * @list: the entry to move + * @head: the head that will precede our entry + */ +static inline void list_move(struct list_head *list, struct list_head *head) +{ + __list_del_entry(list); + list_add(list, head); +} + +/** + * list_move_tail - delete from one list and add as another's tail + * @list: the entry to move + * @head: the head that will follow our entry + */ +static inline void list_move_tail(struct list_head *list, + struct list_head *head) +{ + __list_del_entry(list); + list_add_tail(list, head); +} + +/** + * list_is_last - tests whether @list is the last entry in list @head + * @list: the entry to test + * @head: the head of the list + */ +static inline int list_is_last(const struct list_head *list, + const struct list_head *head) +{ + return list->next == head; +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static inline int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +/** + * list_empty_careful - tests whether a list is empty and not being modified + * @head: the list to test + * + * Description: + * tests whether a list is empty _and_ checks that no other CPU might be + * in the process of modifying either member (next or prev) + * + * NOTE: using list_empty_careful() without synchronization + * can only be safe if the only activity that can happen + * to the list entry is list_del_init(). Eg. it cannot be used + * if another CPU could re-list_add() it. + */ +static inline int list_empty_careful(const struct list_head *head) +{ + struct list_head *next = head->next; + return (next == head) && (next == head->prev); +} + +/** + * list_rotate_left - rotate the list to the left + * @head: the head of the list + */ +static inline void list_rotate_left(struct list_head *head) +{ + struct list_head *first; + + if (!list_empty(head)) { + first = head->next; + list_move_tail(first, head); + } +} + +/** + * list_is_singular - tests whether a list has just one entry. + * @head: the list to test. + */ +static inline int list_is_singular(const struct list_head *head) +{ + return !list_empty(head) && (head->next == head->prev); +} + +static inline void __list_cut_position(struct list_head *list, + struct list_head *head, struct list_head *entry) +{ + struct list_head *new_first = entry->next; + list->next = head->next; + list->next->prev = list; + list->prev = entry; + entry->next = list; + head->next = new_first; + new_first->prev = head; +} + +/** + * list_cut_position - cut a list into two + * @list: a new list to add all removed entries + * @head: a list with entries + * @entry: an entry within head, could be the head itself + * and if so we won't cut the list + * + * This helper moves the initial part of @head, up to and + * including @entry, from @head to @list. You should + * pass on @entry an element you know is on @head. @list + * should be an empty list or a list you do not care about + * losing its data. + * + */ +static inline void list_cut_position(struct list_head *list, + struct list_head *head, struct list_head *entry) +{ + if (list_empty(head)) + return; + if (list_is_singular(head) && + (head->next != entry && head != entry)) + return; + if (entry == head) + INIT_LIST_HEAD(list); + else + __list_cut_position(list, head, entry); +} + +static inline void __list_splice(const struct list_head *list, + struct list_head *prev, + struct list_head *next) +{ + struct list_head *first = list->next; + struct list_head *last = list->prev; + + first->prev = prev; + prev->next = first; + + last->next = next; + next->prev = last; +} + +/** + * list_splice - join two lists, this is designed for stacks + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +static inline void list_splice(const struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) + __list_splice(list, head, head->next); +} + +/** + * list_splice_tail - join two lists, each list being a queue + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +static inline void list_splice_tail(struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) + __list_splice(list, head->prev, head); +} + +/** + * list_splice_init - join two lists and reinitialise the emptied list. + * @list: the new list to add. + * @head: the place to add it in the first list. + * + * The list at @list is reinitialised + */ +static inline void list_splice_init(struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) { + __list_splice(list, head, head->next); + INIT_LIST_HEAD(list); + } +} + +/** + * list_splice_tail_init - join two lists and reinitialise the emptied list + * @list: the new list to add. + * @head: the place to add it in the first list. + * + * Each of the lists is a queue. + * The list at @list is reinitialised + */ +static inline void list_splice_tail_init(struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) { + __list_splice(list, head->prev, head); + INIT_LIST_HEAD(list); + } +} + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +/** + * list_first_entry - get the first element from a list + * @ptr: the list head to take the element from. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + * + * Note, that list is expected to be not empty. + */ +#define list_first_entry(ptr, type, member) \ + list_entry((ptr)->next, type, member) + +/** + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop cursor. + * @head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +/** + * __list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop cursor. + * @head: the head for your list. + * + * This variant doesn't differ from list_for_each() any more. + * We don't do prefetching in either case. + */ +#define __list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +/** + * list_for_each_prev - iterate over a list backwards + * @pos: the &struct list_head to use as a loop cursor. + * @head: the head for your list. + */ +#define list_for_each_prev(pos, head) \ + for (pos = (head)->prev; pos != (head); pos = pos->prev) + +/** + * list_for_each_safe - iterate over a list safe against removal of list entry + * @pos: the &struct list_head to use as a loop cursor. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +/** + * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry + * @pos: the &struct list_head to use as a loop cursor. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_prev_safe(pos, n, head) \ + for (pos = (head)->prev, n = pos->prev; \ + pos != (head); \ + pos = n, n = pos->prev) + +/** + * list_for_each_entry - iterate over list of given type + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry(pos, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_reverse - iterate backwards over list of given type. + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_reverse(pos, head, member) \ + for (pos = list_entry((head)->prev, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.prev, typeof(*pos), member)) + +/** + * list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue() + * @pos: the type * to use as a start point + * @head: the head of the list + * @member: the name of the list_struct within the struct. + * + * Prepares a pos entry for use as a start point in list_for_each_entry_continue(). + */ +#define list_prepare_entry(pos, head, member) \ + ((pos) ? : list_entry(head, typeof(*pos), member)) + +/** + * list_for_each_entry_continue - continue iteration over list of given type + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Continue to iterate over list of given type, continuing after + * the current position. + */ +#define list_for_each_entry_continue(pos, head, member) \ + for (pos = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_continue_reverse - iterate backwards from the given point + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Start to iterate over list of given type backwards, continuing after + * the current position. + */ +#define list_for_each_entry_continue_reverse(pos, head, member) \ + for (pos = list_entry(pos->member.prev, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.prev, typeof(*pos), member)) + +/** + * list_for_each_entry_from - iterate over list of given type from the current point + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Iterate over list of given type, continuing from current position. + */ +#define list_for_each_entry_from(pos, head, member) \ + for (; &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @pos: the type * to use as a loop cursor. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +/** + * list_for_each_entry_safe_continue - continue list iteration safe against removal + * @pos: the type * to use as a loop cursor. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Iterate over list of given type, continuing after current point, + * safe against removal of list entry. + */ +#define list_for_each_entry_safe_continue(pos, n, head, member) \ + for (pos = list_entry(pos->member.next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +/** + * list_for_each_entry_safe_from - iterate over list from current point safe against removal + * @pos: the type * to use as a loop cursor. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Iterate over list of given type from current point, safe against + * removal of list entry. + */ +#define list_for_each_entry_safe_from(pos, n, head, member) \ + for (n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +/** + * list_for_each_entry_safe_reverse - iterate backwards over list safe against removal + * @pos: the type * to use as a loop cursor. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Iterate backwards over list of given type, safe against removal + * of list entry. + */ +#define list_for_each_entry_safe_reverse(pos, n, head, member) \ + for (pos = list_entry((head)->prev, typeof(*pos), member), \ + n = list_entry(pos->member.prev, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.prev, typeof(*n), member)) + +/** + * list_safe_reset_next - reset a stale list_for_each_entry_safe loop + * @pos: the loop cursor used in the list_for_each_entry_safe loop + * @n: temporary storage used in list_for_each_entry_safe + * @member: the name of the list_struct within the struct. + * + * list_safe_reset_next is not safe to use in general if the list may be + * modified concurrently (eg. the lock is dropped in the loop body). An + * exception to this is if the cursor element (pos) is pinned in the list, + * and list_safe_reset_next is called after re-taking the lock and before + * completing the current iteration of the loop body. + */ +#define list_safe_reset_next(pos, n, member) \ + n = list_entry(pos->member.next, typeof(*pos), member) + +#endif diff --git a/src/util/list_sort.c b/src/util/list_sort.c new file mode 100644 index 000000000..ddf3208e9 --- /dev/null +++ b/src/util/list_sort.c @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 code was imported from the Linux kernel. + * + */ + +#include "list_sort.h" +#include "list.h" + +#include <glib.h> +#include <string.h> + +#define unlikely G_UNLIKELY +#define ARRAY_SIZE G_N_ELEMENTS + +#define MAX_LIST_LENGTH_BITS 20 + +/* + * Returns a list organized in an intermediate format suited + * to chaining of merge() calls: null-terminated, no reserved or + * sentinel head node, "prev" links not maintained. + */ +static struct list_head *merge(void *priv, + int (*cmp)(void *priv, struct list_head *a, + struct list_head *b), + struct list_head *a, struct list_head *b) +{ + struct list_head head, *tail = &head; + + while (a && b) { + /* if equal, take 'a' -- important for sort stability */ + if ((*cmp)(priv, a, b) <= 0) { + tail->next = a; + a = a->next; + } else { + tail->next = b; + b = b->next; + } + tail = tail->next; + } + tail->next = a?a:b; + return head.next; +} + +/* + * Combine final list merge with restoration of standard doubly-linked + * list structure. This approach duplicates code from merge(), but + * runs faster than the tidier alternatives of either a separate final + * prev-link restoration pass, or maintaining the prev links + * throughout. + */ +static void merge_and_restore_back_links(void *priv, + int (*cmp)(void *priv, struct list_head *a, + struct list_head *b), + struct list_head *head, + struct list_head *a, struct list_head *b) +{ + struct list_head *tail = head; + + while (a && b) { + /* if equal, take 'a' -- important for sort stability */ + if ((*cmp)(priv, a, b) <= 0) { + tail->next = a; + a->prev = tail; + a = a->next; + } else { + tail->next = b; + b->prev = tail; + b = b->next; + } + tail = tail->next; + } + tail->next = a ? a : b; + + do { + /* + * In worst cases this loop may run many iterations. + * Continue callbacks to the client even though no + * element comparison is needed, so the client's cmp() + * routine can invoke cond_resched() periodically. + */ + (*cmp)(priv, tail->next, tail->next); + + tail->next->prev = tail; + tail = tail->next; + } while (tail->next); + + tail->next = head; + head->prev = tail; +} + +/** + * list_sort - sort a list + * @priv: private data, opaque to list_sort(), passed to @cmp + * @head: the list to sort + * @cmp: the elements comparison function + * + * This function implements "merge sort", which has O(nlog(n)) + * complexity. + * + * The comparison function @cmp must return a negative value if @a + * should sort before @b, and a positive value if @a should sort after + * @b. If @a and @b are equivalent, and their original relative + * ordering is to be preserved, @cmp must return 0. + */ +void list_sort(void *priv, struct list_head *head, + int (*cmp)(void *priv, struct list_head *a, + struct list_head *b)) +{ + struct list_head *part[MAX_LIST_LENGTH_BITS+1]; /* sorted partial lists + -- last slot is a sentinel */ + int lev; /* index into part[] */ + int max_lev = 0; + struct list_head *list; + + if (list_empty(head)) + return; + + memset(part, 0, sizeof(part)); + + head->prev->next = NULL; + list = head->next; + + while (list) { + struct list_head *cur = list; + list = list->next; + cur->next = NULL; + + for (lev = 0; part[lev]; lev++) { + cur = merge(priv, cmp, part[lev], cur); + part[lev] = NULL; + } + if (lev > max_lev) { + max_lev = lev; + } + part[lev] = cur; + } + + for (lev = 0; lev < max_lev; lev++) + if (part[lev]) + list = merge(priv, cmp, part[lev], list); + + merge_and_restore_back_links(priv, cmp, head, part[max_lev], list); +} diff --git a/src/util/list_sort.h b/src/util/list_sort.h new file mode 100644 index 000000000..7a65020b9 --- /dev/null +++ b/src/util/list_sort.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 code was imported from the Linux kernel. + * + */ + +#ifndef _LINUX_LIST_SORT_H +#define _LINUX_LIST_SORT_H + +struct list_head; + +void list_sort(void *priv, struct list_head *head, + int (*cmp)(void *priv, struct list_head *a, + struct list_head *b)); +#endif diff --git a/src/utils.c b/src/utils.c index 6e85dd5ff..a2de3212e 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,6 +19,7 @@ #include "config.h" #include "utils.h" +#include "glib_compat.h" #include "conf.h" #include <glib.h> @@ -45,14 +46,25 @@ #include <windows.h> #endif -char *parsePath(char *path) +G_GNUC_CONST +static inline GQuark +parse_path_quark(void) { + return g_quark_from_static_string("path"); +} + +char * +parsePath(const char *path, G_GNUC_UNUSED GError **error_r) +{ + assert(path != NULL); + assert(error_r == NULL || *error_r == NULL); + #ifndef WIN32 if (!g_path_is_absolute(path) && path[0] != '~') { - g_warning("\"%s\" is not an absolute path", path); + g_set_error(error_r, parse_path_quark(), 0, + "not an absolute path: %s", path); return NULL; } else if (path[0] == '~') { - size_t pos = 1; const char *home; if (path[1] == '/' || path[1] == '\0') { @@ -60,7 +72,8 @@ char *parsePath(char *path) if (user != NULL) { struct passwd *passwd = getpwnam(user); if (!passwd) { - g_warning("no such user %s", user); + g_set_error(error_r, parse_path_quark(), 0, + "no such user: %s", user); return NULL; } @@ -68,36 +81,37 @@ char *parsePath(char *path) } else { home = g_get_home_dir(); if (home == NULL) { - g_warning("problems getting home " - "for current user"); + g_set_error_literal(error_r, parse_path_quark(), 0, + "problems getting home " + "for current user"); return NULL; } } + + ++path; } else { - bool foundSlash = false; - struct passwd *passwd; - char *c; - - for (c = path + 1; *c != '\0' && *c != '/'; c++); - if (*c == '/') { - foundSlash = true; - *c = '\0'; - } - pos = c - path; + ++path; - passwd = getpwnam(path + 1); + const char *slash = strchr(path, '/'); + char *user = slash != NULL + ? g_strndup(path, slash - path) + : g_strdup(path); + + struct passwd *passwd = getpwnam(user); if (!passwd) { - g_warning("user \"%s\" not found", path + 1); + g_set_error(error_r, parse_path_quark(), 0, + "no such user: %s", user); + g_free(user); return NULL; } - if (foundSlash) - *c = '/'; + g_free(user); home = passwd->pw_dir; + path = slash; } - return g_strconcat(home, path + pos, NULL); + return g_strconcat(home, path, NULL); } else { #endif return g_strdup(path); @@ -105,16 +119,3 @@ char *parsePath(char *path) } #endif } - -bool -string_array_contains(const char *const* haystack, const char *needle) -{ - assert(haystack != NULL); - assert(needle != NULL); - - for (; *haystack != NULL; ++haystack) - if (g_ascii_strcasecmp(*haystack, needle) == 0) - return true; - - return false; -} diff --git a/src/utils.h b/src/utils.h index 629056637..f8d6657f2 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ #ifndef MPD_UTILS_H #define MPD_UTILS_H +#include <glib.h> #include <stdbool.h> #ifndef assert_static @@ -31,17 +32,7 @@ } while (0) #endif /* !assert_static */ -char *parsePath(char *path); - -/** - * 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); +char * +parsePath(const char *path, GError **error_r); #endif diff --git a/src/volume.c b/src/volume.c index d7b72dd56..819e6fbfa 100644 --- a/src/volume.c +++ b/src/volume.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/volume.h b/src/volume.h index db266fec9..b08899a84 100644 --- a/src/volume.h +++ b/src/volume.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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/win/mpd.ico b/src/win/mpd.ico Binary files differnew file mode 100644 index 000000000..86fd9fe43 --- /dev/null +++ b/src/win/mpd.ico diff --git a/src/win/mpd_win32_rc.rc.in b/src/win/mpd_win32_rc.rc.in new file mode 100644 index 000000000..a31118a0c --- /dev/null +++ b/src/win/mpd_win32_rc.rc.in @@ -0,0 +1,34 @@ +#include <windows.h> + +#define VERSION_NUMBER @VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_REVISION@,@VERSION_EXTRA@ +#define VERSION_NUMBER_STR "@VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_REVISION@,@VERSION_EXTRA@" + +MPD_ICON ICON "@top_srcdir@/src/win/mpd.ico" + +1 VERSIONINFO +FILETYPE VFT_APP +FILEOS VOS__WINDOWS32 +PRODUCTVERSION VERSION_NUMBER + +FILEVERSION VERSION_NUMBER +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "Music Player Daemon Project" + VALUE "ProductName", "Music Player Daemon" + VALUE "ProductVersion", VERSION_NUMBER_STR + VALUE "InternalName", "mpd" + VALUE "OriginalFilename", "mpd.exe" + VALUE "FileVersion", "@VERSION@" + VALUE "FileDescription", "Music Player Daemon @VERSION@" + VALUE "LegalCopyright", "Copyright \251 The Music Player Daemon Project" + END + END + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/zeroconf-avahi.c b/src/zeroconf-avahi.c index 518a7a481..f2cc5359b 100644 --- a/src/zeroconf-avahi.c +++ b/src/zeroconf-avahi.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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-bonjour.c b/src/zeroconf-bonjour.c index 84f777c50..0f216aade 100644 --- a/src/zeroconf-bonjour.c +++ b/src/zeroconf-bonjour.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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-internal.h b/src/zeroconf-internal.h index 7cb962431..983e5c556 100644 --- a/src/zeroconf-internal.h +++ b/src/zeroconf-internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 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 7b00789b6..4a399e4a2 100644 --- a/src/zeroconf.c +++ b/src/zeroconf.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,6 +21,7 @@ #include "zeroconf.h" #include "zeroconf-internal.h" #include "conf.h" +#include "listen.h" #include <glib.h> @@ -42,6 +43,12 @@ void initZeroconf(void) if (!zeroconfEnabled) return; + if (listen_port <= 0) { + g_warning("No global port, disabling zeroconf"); + zeroconfEnabled = false; + return; + } + serviceName = config_get_string(CONF_ZEROCONF_NAME, SERVICE_NAME); #ifdef HAVE_AVAHI diff --git a/src/zeroconf.h b/src/zeroconf.h index 23354f87d..8e33a3d89 100644 --- a/src/zeroconf.h +++ b/src/zeroconf.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/test/dump_playlist.c b/test/dump_playlist.c index a8cb4d750..0570f6e49 100644 --- a/test/dump_playlist.c +++ b/test/dump_playlist.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,18 +18,22 @@ */ #include "config.h" +#include "io_thread.h" #include "input_init.h" #include "input_stream.h" #include "tag_pool.h" #include "tag_save.h" #include "conf.h" #include "song.h" +#include "decoder_api.h" +#include "decoder_list.h" #include "playlist_list.h" #include "playlist_plugin.h" #include <glib.h> #include <unistd.h> +#include <stdlib.h> static void my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, @@ -41,6 +45,95 @@ my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, g_printerr("%s\n", message); } +void +decoder_initialized(G_GNUC_UNUSED struct decoder *decoder, + G_GNUC_UNUSED const struct audio_format *audio_format, + G_GNUC_UNUSED bool seekable, + G_GNUC_UNUSED float total_time) +{ +} + +enum decoder_command +decoder_get_command(G_GNUC_UNUSED struct decoder *decoder) +{ + return DECODE_COMMAND_NONE; +} + +void +decoder_command_finished(G_GNUC_UNUSED struct decoder *decoder) +{ +} + +double +decoder_seek_where(G_GNUC_UNUSED struct decoder *decoder) +{ + return 1.0; +} + +void +decoder_seek_error(G_GNUC_UNUSED struct decoder *decoder) +{ +} + +size_t +decoder_read(G_GNUC_UNUSED struct decoder *decoder, + struct input_stream *is, + void *buffer, size_t length) +{ + return input_stream_lock_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 uint16_t kbit_rate) +{ + G_GNUC_UNUSED ssize_t nbytes = write(1, data, datalen); + return DECODE_COMMAND_NONE; +} + +enum decoder_command +decoder_tag(G_GNUC_UNUSED struct decoder *decoder, + G_GNUC_UNUSED struct input_stream *is, + G_GNUC_UNUSED const struct tag *tag) +{ + 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) { const char *uri; @@ -73,6 +166,13 @@ int main(int argc, char **argv) return 1; } + io_thread_init(); + if (!io_thread_start(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + if (!input_stream_global_init(&error)) { g_warning("%s", error->message); g_error_free(error); @@ -80,14 +180,18 @@ int main(int argc, char **argv) } playlist_list_global_init(); + decoder_plugin_init_all(); /* open the playlist */ - playlist = playlist_list_open_uri(uri); + GMutex *mutex = g_mutex_new(); + GCond *cond = g_cond_new(); + + playlist = playlist_list_open_uri(uri, mutex, cond); if (playlist == NULL) { /* open the stream and wait until it becomes ready */ - is = input_stream_open(uri, &error); + is = input_stream_open(uri, mutex, cond, &error); if (is == NULL) { if (error != NULL) { g_warning("%s", error->message); @@ -97,19 +201,7 @@ int main(int argc, char **argv) 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); - } + input_stream_lock_wait_ready(is); /* open the playlist */ @@ -148,8 +240,14 @@ int main(int argc, char **argv) playlist_plugin_close(playlist); if (is != NULL) input_stream_close(is); + + g_cond_free(cond); + g_mutex_free(mutex); + + decoder_plugin_deinit_all(); playlist_list_global_finish(); input_stream_global_finish(); + io_thread_deinit(); config_global_finish(); tag_pool_deinit(); diff --git a/test/dump_text_file.c b/test/dump_text_file.c new file mode 100644 index 000000000..f14371441 --- /dev/null +++ b/test/dump_text_file.c @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "io_thread.h" +#include "input_init.h" +#include "input_stream.h" +#include "text_input_stream.h" +#include "tag_pool.h" +#include "conf.h" +#include "stdbin.h" + +#ifdef ENABLE_ARCHIVE +#include "archive_list.h" +#endif + +#include <glib.h> + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.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); +} + +static void +dump_text_file(struct text_input_stream *is) +{ + const char *line; + while ((line = text_input_stream_read(is)) != NULL) + printf("'%s'\n", line); +} + +static int +dump_input_stream(struct input_stream *is) +{ + GError *error = NULL; + + input_stream_lock(is); + + /* wait until the stream becomes ready */ + + input_stream_wait_ready(is); + + if (!input_stream_check(is, &error)) { + g_warning("%s", error->message); + g_error_free(error); + input_stream_unlock(is); + return EXIT_FAILURE; + } + + /* read data and tags from the stream */ + + input_stream_unlock(is); + + struct text_input_stream *tis = text_input_stream_new(is); + dump_text_file(tis); + text_input_stream_free(tis); + + input_stream_lock(is); + + if (!input_stream_check(is, &error)) { + g_warning("%s", error->message); + g_error_free(error); + input_stream_unlock(is); + return EXIT_FAILURE; + } + + input_stream_unlock(is); + + return 0; +} + +int main(int argc, char **argv) +{ + GError *error = NULL; + struct input_stream *is; + int ret; + + if (argc != 2) { + g_printerr("Usage: run_input URI\n"); + return 1; + } + + /* initialize GLib */ + + g_thread_init(NULL); + g_log_set_default_handler(my_log_func, NULL); + + /* initialize MPD */ + + tag_pool_init(); + config_global_init(); + + io_thread_init(); + if (!io_thread_start(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + +#ifdef ENABLE_ARCHIVE + archive_plugin_init_all(); +#endif + + if (!input_stream_global_init(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return 2; + } + + /* open the stream and dump it */ + + GMutex *mutex = g_mutex_new(); + GCond *cond = g_cond_new(); + + is = input_stream_open(argv[1], mutex, cond, &error); + if (is != NULL) { + ret = dump_input_stream(is); + input_stream_close(is); + } else { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } else + g_printerr("input_stream_open() failed\n"); + ret = 2; + } + + g_cond_free(cond); + g_mutex_free(mutex); + + /* deinitialize everything */ + + input_stream_global_finish(); + +#ifdef ENABLE_ARCHIVE + archive_plugin_deinit_all(); +#endif + + io_thread_deinit(); + + config_global_finish(); + tag_pool_deinit(); + + return ret; +} diff --git a/test/read_conf.c b/test/read_conf.c index f1b38cafe..4f6005c6f 100644 --- a/test/read_conf.c +++ b/test/read_conf.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/test/read_mixer.c b/test/read_mixer.c index 1b5b093a3..0a0719460 100644 --- a/test/read_mixer.c +++ b/test/read_mixer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -34,6 +34,16 @@ #include "output/pulse_output_plugin.h" void +pulse_output_lock(G_GNUC_UNUSED struct pulse_output *po) +{ +} + +void +pulse_output_unlock(G_GNUC_UNUSED struct pulse_output *po) +{ +} + +void pulse_output_set_mixer(G_GNUC_UNUSED struct pulse_output *po, G_GNUC_UNUSED struct pulse_mixer *pm) { @@ -55,6 +65,43 @@ pulse_output_set_volume(G_GNUC_UNUSED struct pulse_output *po, #endif +#ifdef HAVE_ROAR +#include "output/roar_output_plugin.h" + +int +roar_output_get_volume(G_GNUC_UNUSED struct roar *roar) +{ + return -1; +} + +bool +roar_output_set_volume(G_GNUC_UNUSED struct roar *roar, + G_GNUC_UNUSED unsigned volume) +{ + return true; +} + +#endif + +#ifdef ENABLE_RAOP_OUTPUT +#include "output/raop_output_plugin.h" + +bool +raop_set_volume(G_GNUC_UNUSED struct raop_data *rd, + G_GNUC_UNUSED unsigned volume, + G_GNUC_UNUSED GError **error_r) +{ + return false; +} + +int +raop_get_volume(G_GNUC_UNUSED struct raop_data *rd) +{ + return -1; +} + +#endif + void event_pipe_emit(G_GNUC_UNUSED enum pipe_event event) { @@ -68,8 +115,8 @@ filter_plugin_by_name(G_GNUC_UNUSED const char *name) } bool -pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED int length, - G_GNUC_UNUSED const struct audio_format *format, +pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED size_t length, + G_GNUC_UNUSED enum sample_format format, G_GNUC_UNUSED int volume) { assert(false); diff --git a/test/read_tags.c b/test/read_tags.c index 8906e1c8a..faf9a45c0 100644 --- a/test/read_tags.c +++ b/test/read_tags.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,21 +18,23 @@ */ #include "config.h" +#include "io_thread.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_pool.h" #include "tag_ape.h" #include "tag_id3.h" +#include "tag_handler.h" #include "idle.h" #include <glib.h> #include <assert.h> #include <unistd.h> +#include <stdlib.h> #ifdef HAVE_LOCALE_H #include <locale.h> @@ -50,8 +52,8 @@ idle_add(G_GNUC_UNUSED unsigned flags) * No-op dummy. */ bool -pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED int length, - G_GNUC_UNUSED const struct audio_format *format, +pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED size_t length, + G_GNUC_UNUSED enum sample_format format, G_GNUC_UNUSED int volume) { return true; @@ -89,7 +91,7 @@ decoder_read(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is, void *buffer, size_t length) { - return input_stream_read(is, buffer, length, NULL); + return input_stream_lock_read(is, buffer, length, NULL); } void @@ -132,25 +134,38 @@ decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder, g_free(mixramp_end); } +static bool empty = true; + +static void +print_duration(unsigned seconds, G_GNUC_UNUSED void *ctx) +{ + g_print("duration=%d\n", seconds); +} + static void -print_tag(const struct tag *tag) +print_tag(enum tag_type type, const char *value, G_GNUC_UNUSED void *ctx) { - if (tag->time >= 0) - g_print("time=%d\n", tag->time); + g_print("[%s]=%s\n", tag_item_names[type], value); + empty = false; +} - for (unsigned i = 0; i < tag->num_items; ++i) - g_print("%s=%s\n", - tag_item_names[tag->items[i]->type], - tag->items[i]->value); +static void +print_pair(const char *name, const char *value, G_GNUC_UNUSED void *ctx) +{ + g_print("\"%s\"=%s\n", name, value); } +static const struct tag_handler print_handler = { + .duration = print_duration, + .tag = print_tag, + .pair = print_pair, +}; + 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 */ @@ -166,7 +181,12 @@ int main(int argc, char **argv) path = argv[2]; g_thread_init(NULL); - tag_pool_init(); + io_thread_init(); + if (!io_thread_start(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } if (!input_stream_global_init(&error)) { g_warning("%s", error->message); @@ -182,9 +202,14 @@ int main(int argc, char **argv) return 1; } - tag = decoder_plugin_tag_dup(plugin, path); - if (tag == NULL && plugin->stream_tag != NULL) { - struct input_stream *is = input_stream_open(path, &error); + bool success = decoder_plugin_scan_file(plugin, path, + &print_handler, NULL); + if (!success && plugin->scan_stream != NULL) { + GMutex *mutex = g_mutex_new(); + GCond *cond = g_cond_new(); + + struct input_stream *is = + input_stream_open(path, mutex, cond, &error); if (is == NULL) { g_printerr("Failed to open %s: %s\n", @@ -193,33 +218,28 @@ int main(int argc, char **argv) return 1; } - tag = decoder_plugin_stream_tag(plugin, is); + success = decoder_plugin_scan_stream(plugin, is, + &print_handler, NULL); input_stream_close(is); + + g_cond_free(cond); + g_mutex_free(mutex); } decoder_plugin_deinit_all(); input_stream_global_finish(); - if (tag == NULL) { + io_thread_deinit(); + + if (!success) { g_printerr("Failed to read tags\n"); return 1; } - print_tag(tag); - - empty = tag_is_empty(tag); - tag_free(tag); - if (empty) { - tag = tag_ape_load(path); - if (tag == NULL) - tag = tag_id3_load(path); - if (tag != NULL) { - print_tag(tag); - tag_free(tag); - } + tag_ape_scan2(path, &print_handler, NULL); + if (empty) + tag_id3_scan(path, &print_handler, NULL); } - tag_pool_deinit(); - return 0; } diff --git a/test/run_convert.c b/test/run_convert.c index ae76bc367..4f57400fd 100644 --- a/test/run_convert.c +++ b/test/run_convert.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -76,13 +76,17 @@ int main(int argc, char **argv) return 1; } - if (!audio_format_parse(&out_audio_format, argv[2], - false, &error)) { + struct audio_format out_audio_format_mask; + if (!audio_format_parse(&out_audio_format_mask, argv[2], + true, &error)) { g_printerr("Failed to parse audio format: %s\n", error->message); return 1; } + out_audio_format = in_audio_format; + audio_format_mask_apply(&out_audio_format, &out_audio_format_mask); + const size_t in_frame_size = audio_format_frame_size(&in_audio_format); pcm_convert_init(&state); diff --git a/test/run_decoder.c b/test/run_decoder.c index 154b47324..e6712c75b 100644 --- a/test/run_decoder.c +++ b/test/run_decoder.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "io_thread.h" #include "decoder_list.h" #include "decoder_api.h" #include "tag_pool.h" @@ -32,6 +33,7 @@ #include <assert.h> #include <unistd.h> +#include <stdlib.h> static void my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, @@ -55,8 +57,8 @@ idle_add(G_GNUC_UNUSED unsigned flags) * No-op dummy. */ bool -pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED int length, - G_GNUC_UNUSED const struct audio_format *format, +pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED size_t length, + G_GNUC_UNUSED enum sample_format format, G_GNUC_UNUSED int volume) { return true; @@ -111,7 +113,7 @@ decoder_read(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is, void *buffer, size_t length) { - return input_stream_read(is, buffer, length, NULL); + return input_stream_lock_read(is, buffer, length, NULL); } void @@ -182,6 +184,13 @@ int main(int argc, char **argv) g_thread_init(NULL); g_log_set_default_handler(my_log_func, NULL); + io_thread_init(); + if (!io_thread_start(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + tag_pool_init(); if (!input_stream_global_init(&error)) { @@ -204,8 +213,11 @@ int main(int argc, char **argv) decoder_plugin_file_decode(decoder.plugin, &decoder, decoder.uri); } else if (decoder.plugin->stream_decode != NULL) { + GMutex *mutex = g_mutex_new(); + GCond *cond = g_cond_new(); + struct input_stream *is = - input_stream_open(decoder.uri, &error); + input_stream_open(decoder.uri, mutex, cond, &error); if (is == NULL) { if (error != NULL) { g_warning("%s", error->message); @@ -219,6 +231,9 @@ int main(int argc, char **argv) decoder_plugin_stream_decode(decoder.plugin, &decoder, is); input_stream_close(is); + + g_cond_free(cond); + g_mutex_free(mutex); } else { g_printerr("Decoder plugin is not usable\n"); return 1; @@ -226,6 +241,7 @@ int main(int argc, char **argv) decoder_plugin_deinit_all(); input_stream_global_finish(); + io_thread_deinit(); if (!decoder.initialized) { g_printerr("Decoding failed\n"); diff --git a/test/run_encoder.c b/test/run_encoder.c index 4c05a06c7..6a4108620 100644 --- a/test/run_encoder.c +++ b/test/run_encoder.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -76,7 +76,7 @@ int main(int argc, char **argv) } param = config_new_param(NULL, -1); - config_add_block_param(param, "quality", "5.0", -1, NULL); + config_add_block_param(param, "quality", "5.0", -1); encoder = encoder_init(plugin, param, &error); if (encoder == NULL) { diff --git a/test/run_filter.c b/test/run_filter.c index 3758eb5bb..8e793b768 100644 --- a/test/run_filter.c +++ b/test/run_filter.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -183,7 +183,7 @@ int main(int argc, char **argv) nbytes = write(1, dest, length); if (nbytes < 0) { - g_printerr("Failed to write: %s\n", strerror(errno)); + g_printerr("Failed to write: %s\n", g_strerror(errno)); filter_close(filter); filter_free(filter); return 1; diff --git a/test/run_inotify.c b/test/run_inotify.c index 9f3c30b8c..3e7c70dba 100644 --- a/test/run_inotify.c +++ b/test/run_inotify.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/test/run_input.c b/test/run_input.c index a50cd70ab..676e4e618 100644 --- a/test/run_input.c +++ b/test/run_input.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "io_thread.h" #include "input_init.h" #include "input_stream.h" #include "tag_pool.h" @@ -32,6 +33,7 @@ #include <glib.h> #include <unistd.h> +#include <stdlib.h> static void my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level, @@ -51,20 +53,17 @@ dump_input_stream(struct input_stream *is) size_t num_read; ssize_t num_written; + input_stream_lock(is); + /* wait until the stream becomes ready */ - 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; - } + input_stream_wait_ready(is); - if (ret == 0) - /* nothing was buffered - wait */ - g_usleep(10000); + if (!input_stream_check(is, &error)) { + g_warning("%s", error->message); + g_error_free(error); + input_stream_unlock(is); + return EXIT_FAILURE; } /* print meta data */ @@ -98,6 +97,15 @@ dump_input_stream(struct input_stream *is) break; } + if (!input_stream_check(is, &error)) { + g_warning("%s", error->message); + g_error_free(error); + input_stream_unlock(is); + return EXIT_FAILURE; + } + + input_stream_unlock(is); + return 0; } @@ -122,6 +130,13 @@ int main(int argc, char **argv) tag_pool_init(); config_global_init(); + io_thread_init(); + if (!io_thread_start(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + #ifdef ENABLE_ARCHIVE archive_plugin_init_all(); #endif @@ -134,7 +149,10 @@ int main(int argc, char **argv) /* open the stream and dump it */ - is = input_stream_open(argv[1], &error); + GMutex *mutex = g_mutex_new(); + GCond *cond = g_cond_new(); + + is = input_stream_open(argv[1], mutex, cond, &error); if (is != NULL) { ret = dump_input_stream(is); input_stream_close(is); @@ -147,6 +165,9 @@ int main(int argc, char **argv) ret = 2; } + g_cond_free(cond); + g_mutex_free(mutex); + /* deinitialize everything */ input_stream_global_finish(); @@ -155,6 +176,8 @@ int main(int argc, char **argv) archive_plugin_deinit_all(); #endif + io_thread_deinit(); + config_global_finish(); tag_pool_deinit(); diff --git a/test/run_normalize.c b/test/run_normalize.c index 958015973..fc26829ed 100644 --- a/test/run_normalize.c +++ b/test/run_normalize.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/test/run_ntp_server.c b/test/run_ntp_server.c new file mode 100644 index 000000000..842d1852e --- /dev/null +++ b/test/run_ntp_server.c @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "ntp_server.h" +#include "signals.h" +#include "io_thread.h" + +#include <glib.h> + +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> + +#ifdef WIN32 +#include <ws2tcpip.h> +#include <winsock.h> +#else +#include <sys/socket.h> +#include <netdb.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#endif + +void +on_quit(void) +{ + io_thread_quit(); +} + +int +main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv) +{ + g_thread_init(NULL); + signals_init(); + io_thread_init(); + + struct ntp_server ntp; + ntp_server_init(&ntp); + + GError *error = NULL; + if (!ntp_server_open(&ntp, &error)) { + io_thread_deinit(); + g_printerr("%s\n", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + io_thread_run(); + + ntp_server_close(&ntp); + io_thread_deinit(); + return EXIT_SUCCESS; +} diff --git a/test/run_output.c b/test/run_output.c index 5028068ff..bbb1be7d2 100644 --- a/test/run_output.c +++ b/test/run_output.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "io_thread.h" #include "output_plugin.h" #include "output_internal.h" #include "output_control.h" @@ -28,6 +29,7 @@ #include "event_pipe.h" #include "idle.h" #include "playlist.h" +#include "player_control.h" #include "stdbin.h" #include <glib.h> @@ -35,6 +37,7 @@ #include <assert.h> #include <string.h> #include <unistd.h> +#include <stdlib.h> struct playlist g_playlist; @@ -91,11 +94,10 @@ find_named_config_block(const char *block, const char *name) return NULL; } -static bool -load_audio_output(struct audio_output *ao, const char *name) +static struct audio_output * +load_audio_output(const char *name) { const struct config_param *param; - bool success; GError *error = NULL; param = find_named_config_block(CONF_AUDIO_OUTPUT, name); @@ -104,25 +106,91 @@ load_audio_output(struct audio_output *ao, const char *name) return false; } - success = audio_output_init(ao, param, &error); - if (!success) { + static struct player_control dummy_player_control; + + struct audio_output *ao = + audio_output_new(param, &dummy_player_control, &error); + if (ao == NULL) { g_printerr("%s\n", error->message); g_error_free(error); } - return success; + return ao; +} + +static bool +run_output(struct audio_output *ao, struct audio_format *audio_format) +{ + /* open the audio output */ + + GError *error = NULL; + if (!ao_plugin_enable(ao, &error)) { + g_printerr("Failed to enable audio output: %s\n", + error->message); + g_error_free(error); + return false; + } + + if (!ao_plugin_open(ao, audio_format, &error)) { + ao_plugin_disable(ao); + g_printerr("Failed to open audio output: %s\n", + error->message); + g_error_free(error); + return false; + } + + struct audio_format_string af_string; + g_printerr("audio_format=%s\n", + audio_format_to_string(audio_format, &af_string)); + + size_t frame_size = audio_format_frame_size(audio_format); + + /* play */ + + size_t length = 0; + char buffer[4096]; + while (true) { + if (length < sizeof(buffer)) { + ssize_t nbytes = read(0, buffer + length, + sizeof(buffer) - length); + if (nbytes <= 0) + break; + + length += (size_t)nbytes; + } + + size_t play_length = (length / frame_size) * frame_size; + if (play_length > 0) { + size_t consumed = ao_plugin_play(ao, + buffer, play_length, + &error); + if (consumed == 0) { + ao_plugin_close(ao); + ao_plugin_disable(ao); + g_printerr("Failed to play: %s\n", + error->message); + g_error_free(error); + return false; + } + + assert(consumed <= length); + assert(consumed % frame_size == 0); + + length -= consumed; + memmove(buffer, buffer + consumed, length); + } + } + + ao_plugin_close(ao); + ao_plugin_disable(ao); + return true; } int main(int argc, char **argv) { - struct audio_output ao; struct audio_format audio_format; - struct audio_format_string af_string; bool success; GError *error = NULL; - char buffer[4096]; - ssize_t nbytes; - size_t frame_size, length = 0, play_length, consumed; if (argc < 3 || argc > 4) { g_printerr("Usage: run_output CONFIG NAME [FORMAT] <IN\n"); @@ -143,9 +211,17 @@ int main(int argc, char **argv) return 1; } + io_thread_init(); + if (!io_thread_start(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + /* initialize the audio output */ - if (!load_audio_output(&ao, argv[2])) + struct audio_output *ao = load_audio_output(argv[2]); + if (ao == NULL) return 1; /* parse the audio format */ @@ -161,59 +237,17 @@ int main(int argc, char **argv) } } - /* open the audio output */ - - success = ao_plugin_open(ao.plugin, ao.data, &audio_format, &error); - if (!success) { - g_printerr("Failed to open audio output: %s\n", - error->message); - g_error_free(error); - return 1; - } - - g_printerr("audio_format=%s\n", - audio_format_to_string(&audio_format, &af_string)); - - frame_size = audio_format_frame_size(&audio_format); - - /* play */ - - while (true) { - if (length < sizeof(buffer)) { - nbytes = read(0, buffer + length, sizeof(buffer) - length); - if (nbytes <= 0) - break; + /* do it */ - length += (size_t)nbytes; - } - - play_length = (length / frame_size) * frame_size; - if (play_length > 0) { - consumed = ao_plugin_play(ao.plugin, ao.data, - buffer, play_length, - &error); - if (consumed == 0) { - g_printerr("Failed to play: %s\n", - error->message); - g_error_free(error); - return 1; - } - - assert(consumed <= length); - assert(consumed % frame_size == 0); - - length -= consumed; - memmove(buffer, buffer + consumed, length); - } - } + success = run_output(ao, &audio_format); /* cleanup and exit */ - ao_plugin_close(ao.plugin, ao.data); - ao_plugin_finish(ao.plugin, ao.data); - g_mutex_free(ao.mutex); + audio_output_free(ao); + + io_thread_deinit(); config_global_finish(); - return 0; + return success ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/test/run_resolver.c b/test/run_resolver.c new file mode 100644 index 000000000..ee0bc0172 --- /dev/null +++ b/test/run_resolver.c @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "resolver.h" + +#ifdef WIN32 +#include <ws2tcpip.h> +#include <winsock.h> +#else +#include <sys/socket.h> +#include <netdb.h> +#endif + +#include <stdlib.h> + +int main(int argc, char **argv) +{ + if (argc != 2) { + g_printerr("Usage: run_resolver HOST\n"); + return EXIT_FAILURE; + } + + GError *error = NULL; + struct addrinfo *ai = + resolve_host_port(argv[1], 80, AI_PASSIVE, SOCK_STREAM, + &error); + if (ai == NULL) { + g_printerr("%s\n", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + for (const struct addrinfo *i = ai; i != NULL; i = i->ai_next) { + char *p = sockaddr_to_string(i->ai_addr, i->ai_addrlen, + &error); + if (p == NULL) { + freeaddrinfo(ai); + g_printerr("%s\n", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + g_print("%s\n", p); + g_free(p); + } + + freeaddrinfo(ai); + return EXIT_SUCCESS; +} diff --git a/test/run_tcp_connect.c b/test/run_tcp_connect.c new file mode 100644 index 000000000..bf8d9b82f --- /dev/null +++ b/test/run_tcp_connect.c @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "resolver.h" +#include "io_thread.h" +#include "tcp_connect.h" +#include "fd_util.h" + +#include <assert.h> +#include <stdlib.h> + +#ifdef WIN32 +#include <ws2tcpip.h> +#include <winsock.h> +#else +#include <sys/socket.h> +#include <netdb.h> +#endif + +static struct tcp_connect *handle; +static GMutex *mutex; +static GCond *cond; +static bool done, success; + +static void +my_tcp_connect_success(int fd, G_GNUC_UNUSED void *ctx) +{ + assert(!done); + assert(!success); + + close_socket(fd); + g_print("success\n"); + + g_mutex_lock(mutex); + done = success = true; + g_cond_signal(cond); + g_mutex_unlock(mutex); +} + +static void +my_tcp_connect_error(GError *error, G_GNUC_UNUSED void *ctx) +{ + assert(!done); + assert(!success); + + g_printerr("error: %s\n", error->message); + g_error_free(error); + + g_mutex_lock(mutex); + done = true; + g_cond_signal(cond); + g_mutex_unlock(mutex); +} + +static void +my_tcp_connect_timeout(G_GNUC_UNUSED void *ctx) +{ + assert(!done); + assert(!success); + + g_printerr("timeout\n"); + + g_mutex_lock(mutex); + done = true; + g_cond_signal(cond); + g_mutex_unlock(mutex); +} + +static void +my_tcp_connect_canceled(G_GNUC_UNUSED void *ctx) +{ + assert(!done); + assert(!success); + + g_printerr("canceled\n"); + + g_mutex_lock(mutex); + done = true; + g_cond_signal(cond); + g_mutex_unlock(mutex); +} + +static const struct tcp_connect_handler my_tcp_connect_handler = { + .success = my_tcp_connect_success, + .error = my_tcp_connect_error, + .timeout = my_tcp_connect_timeout, + .canceled = my_tcp_connect_canceled, +}; + +int main(int argc, char **argv) +{ + if (argc != 2) { + g_printerr("Usage: run_tcp_connect IP:PORT\n"); + return 1; + } + + GError *error = NULL; + struct addrinfo *ai = resolve_host_port(argv[1], 80, 0, SOCK_STREAM, + &error); + if (ai == NULL) { + g_printerr("%s\n", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + /* initialize GLib */ + + g_thread_init(NULL); + + /* initialize MPD */ + + io_thread_init(); + if (!io_thread_start(&error)) { + freeaddrinfo(ai); + g_printerr("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + /* open the connection */ + + mutex = g_mutex_new(); + cond = g_cond_new(); + + tcp_connect_address(ai->ai_addr, ai->ai_addrlen, 5000, + &my_tcp_connect_handler, NULL, + &handle); + freeaddrinfo(ai); + + if (handle != NULL) { + g_mutex_lock(mutex); + while (!done) + g_cond_wait(cond, mutex); + g_mutex_unlock(mutex); + + tcp_connect_free(handle); + } + + g_cond_free(cond); + g_mutex_free(mutex); + + /* deinitialize everything */ + + io_thread_deinit(); + + return EXIT_SUCCESS; +} diff --git a/test/signals.c b/test/signals.c new file mode 100644 index 000000000..15761f6b0 --- /dev/null +++ b/test/signals.c @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "signals.h" +#ifndef WIN32 + +#include "mpd_error.h" + +#include <glib.h> + +#include <signal.h> +#include <errno.h> +#include <string.h> + +static void +quit_signal_handler(G_GNUC_UNUSED int signum) +{ + on_quit(); +} + +static void +x_sigaction(int signum, const struct sigaction *act) +{ + if (sigaction(signum, act, NULL) < 0) + MPD_ERROR("sigaction() failed: %s", g_strerror(errno)); +} + +#endif + +void +signals_init(void) +{ +#ifndef WIN32 + struct sigaction sa; + + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_IGN; + x_sigaction(SIGPIPE, &sa); + + sa.sa_handler = quit_signal_handler; + x_sigaction(SIGINT, &sa); + x_sigaction(SIGTERM, &sa); +#endif +} diff --git a/test/signals.h b/test/signals.h new file mode 100644 index 000000000..e524d35e2 --- /dev/null +++ b/test/signals.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_SIGNALS_H +#define MPD_SIGNALS_H + +void +on_quit(void); + +void +signals_init(void); + +#endif diff --git a/test/software_volume.c b/test/software_volume.c index 789fffe61..2357da672 100644 --- a/test/software_volume.c +++ b/test/software_volume.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -59,7 +59,7 @@ int main(int argc, char **argv) audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, 2); while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) { - if (!pcm_volume(buffer, nbytes, &audio_format, + if (!pcm_volume(buffer, nbytes, audio_format.format, PCM_VOLUME_1 / 2)) { g_printerr("pcm_volume() has failed\n"); return 2; diff --git a/test/stdbin.h b/test/stdbin.h index 362605ad9..48cac7338 100644 --- a/test/stdbin.h +++ b/test/stdbin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/test/test_byte_reverse.c b/test/test_byte_reverse.c new file mode 100644 index 000000000..7678e9cef --- /dev/null +++ b/test/test_byte_reverse.c @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "util/byte_reverse.h" +#include "test_glib_compat.h" + +#include <glib.h> + +static void +test_byte_reverse_2(void) +{ + static const char src[] = "123456"; + static const char result[] = "214365"; + static uint8_t dest[G_N_ELEMENTS(src)]; + + reverse_bytes(dest, (const uint8_t *)src, + (const uint8_t *)(src + G_N_ELEMENTS(src) - 1), 2); + g_assert_cmpstr((const char *)dest, ==, result); +} + +static void +test_byte_reverse_3(void) +{ + static const char src[] = "123456"; + static const char result[] = "321654"; + static uint8_t dest[G_N_ELEMENTS(src)]; + + reverse_bytes(dest, (const uint8_t *)src, + (const uint8_t *)(src + G_N_ELEMENTS(src) - 1), 3); + g_assert_cmpstr((const char *)dest, ==, result); +} + +static void +test_byte_reverse_4(void) +{ + static const char src[] = "12345678"; + static const char result[] = "43218765"; + static uint8_t dest[G_N_ELEMENTS(src)]; + + reverse_bytes(dest, (const uint8_t *)src, + (const uint8_t *)(src + G_N_ELEMENTS(src) - 1), 4); + g_assert_cmpstr((const char *)dest, ==, result); +} + +static void +test_byte_reverse_5(void) +{ + static const char src[] = "1234567890"; + static const char result[] = "5432109876"; + static uint8_t dest[G_N_ELEMENTS(src)]; + + reverse_bytes(dest, (const uint8_t *)src, + (const uint8_t *)(src + G_N_ELEMENTS(src) - 1), 5); + g_assert_cmpstr((const char *)dest, ==, result); +} + +int +main(int argc, char **argv) +{ + g_test_init (&argc, &argv, NULL); + g_test_add_func("/util/byte_reverse/2", test_byte_reverse_2); + g_test_add_func("/util/byte_reverse/3", test_byte_reverse_3); + g_test_add_func("/util/byte_reverse/4", test_byte_reverse_4); + g_test_add_func("/util/byte_reverse/5", test_byte_reverse_5); + + g_test_run(); +} diff --git a/test/test_glib_compat.h b/test/test_glib_compat.h new file mode 100644 index 000000000..2e4ab123c --- /dev/null +++ b/test/test_glib_compat.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 header for GLib before 2.16. + */ + +#ifndef MPD_TEST_GLIB_COMPAT_H +#define MPD_TEST_GLIB_COMPAT_H + +#include <glib.h> + +#if !GLIB_CHECK_VERSION(2,16,0) + +#include <string.h> + +#define g_assert_cmpint(n1, cmp, n2) g_assert((n1) cmp (n2)) +#define g_assert_cmpstr(a, cmp, b) g_assert(strcmp(a, b) cmp 0) + +static void (*test_functions[256])(void); +static unsigned num_test_functions; + +static inline void +g_test_init(G_GNUC_UNUSED int *argc, G_GNUC_UNUSED char ***argv, ...) +{ +} + +static inline void +g_test_add_func(G_GNUC_UNUSED const char *testpath, void (test_funcvoid)(void)) +{ + test_functions[num_test_functions++] = test_funcvoid; +} + +static inline int +g_test_run(void) +{ + for (unsigned i = 0; i < num_test_functions; ++i) + test_functions[i](); + return 0; +} + +#endif /* !2.16 */ + +#endif diff --git a/test/test_pcm_all.h b/test/test_pcm_all.h new file mode 100644 index 000000000..14e4c2980 --- /dev/null +++ b/test/test_pcm_all.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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_TEST_PCM_ALL_H +#define MPD_TEST_PCM_ALL_H + +void +test_pcm_dither_24(void); + +void +test_pcm_dither_32(void); + +void +test_pcm_pack_24(void); + +void +test_pcm_unpack_24(void); + +void +test_pcm_channels_16(void); + +void +test_pcm_channels_32(void); + +#endif diff --git a/test/test_pcm_channels.c b/test/test_pcm_channels.c new file mode 100644 index 000000000..877ae3262 --- /dev/null +++ b/test/test_pcm_channels.c @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "test_pcm_all.h" +#include "test_glib_compat.h" +#include "pcm_channels.h" +#include "pcm_buffer.h" + +#include <glib.h> + +void +test_pcm_channels_16(void) +{ + enum { N = 256 }; + int16_t src[N * 2]; + + for (unsigned i = 0; i < G_N_ELEMENTS(src); ++i) + src[i] = g_random_int(); + + struct pcm_buffer buffer; + pcm_buffer_init(&buffer); + + /* stereo to mono */ + + size_t dest_size; + const int16_t *dest = + pcm_convert_channels_16(&buffer, 1, 2, src, sizeof(src), + &dest_size); + g_assert(dest != NULL); + g_assert_cmpint(dest_size, ==, sizeof(src) / 2); + for (unsigned i = 0; i < N; ++i) + g_assert_cmpint(dest[i], ==, + (src[i * 2] + src[i * 2 + 1]) / 2); + + /* mono to stereo */ + + dest = pcm_convert_channels_16(&buffer, 2, 1, src, sizeof(src), + &dest_size); + g_assert(dest != NULL); + g_assert_cmpint(dest_size, ==, sizeof(src) * 2); + for (unsigned i = 0; i < N; ++i) { + g_assert_cmpint(dest[i * 2], ==, src[i]); + g_assert_cmpint(dest[i * 2 + 1], ==, src[i]); + } + + pcm_buffer_deinit(&buffer); +} + +void +test_pcm_channels_32(void) +{ + enum { N = 256 }; + int32_t src[N * 2]; + + for (unsigned i = 0; i < G_N_ELEMENTS(src); ++i) + src[i] = g_random_int(); + + struct pcm_buffer buffer; + pcm_buffer_init(&buffer); + + /* stereo to mono */ + + size_t dest_size; + const int32_t *dest = + pcm_convert_channels_32(&buffer, 1, 2, src, sizeof(src), + &dest_size); + g_assert(dest != NULL); + g_assert_cmpint(dest_size, ==, sizeof(src) / 2); + for (unsigned i = 0; i < N; ++i) + g_assert_cmpint(dest[i], ==, + ((int64_t)src[i * 2] + (int64_t)src[i * 2 + 1]) / 2); + + /* mono to stereo */ + + dest = pcm_convert_channels_32(&buffer, 2, 1, src, sizeof(src), + &dest_size); + g_assert(dest != NULL); + g_assert_cmpint(dest_size, ==, sizeof(src) * 2); + for (unsigned i = 0; i < N; ++i) { + g_assert_cmpint(dest[i * 2], ==, src[i]); + g_assert_cmpint(dest[i * 2 + 1], ==, src[i]); + } + + pcm_buffer_deinit(&buffer); +} diff --git a/test/test_pcm_dither.c b/test/test_pcm_dither.c new file mode 100644 index 000000000..24b0dd040 --- /dev/null +++ b/test/test_pcm_dither.c @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "test_pcm_all.h" +#include "test_glib_compat.h" +#include "pcm_dither.h" + +#include <glib.h> + +/** + * Generate a random 24 bit PCM sample. + */ +static int32_t +random24(void) +{ + int32_t x = g_random_int() & 0xffffff; + if (x & 0x800000) + x |= 0xff000000; + return x; +} + +void +test_pcm_dither_24(void) +{ + struct pcm_dither dither; + + pcm_dither_24_init(&dither); + + enum { N = 256 }; + int32_t src[N]; + for (unsigned i = 0; i < N; ++i) + src[i] = random24(); + + int16_t dest[N]; + + pcm_dither_24_to_16(&dither, dest, src, src + N); + + for (unsigned i = 0; i < N; ++i) { + g_assert_cmpint(dest[i], >=, (src[i] >> 8) - 8); + g_assert_cmpint(dest[i], <, (src[i] >> 8) + 8); + } +} + +void +test_pcm_dither_32(void) +{ + struct pcm_dither dither; + + pcm_dither_24_init(&dither); + + enum { N = 256 }; + int32_t src[N]; + for (unsigned i = 0; i < N; ++i) + src[i] = g_random_int(); + + int16_t dest[N]; + + pcm_dither_32_to_16(&dither, dest, src, src + N); + + for (unsigned i = 0; i < N; ++i) { + g_assert_cmpint(dest[i], >=, (src[i] >> 16) - 8); + g_assert_cmpint(dest[i], <, (src[i] >> 16) + 8); + } +} diff --git a/test/test_pcm_main.c b/test/test_pcm_main.c new file mode 100644 index 000000000..7047b5251 --- /dev/null +++ b/test/test_pcm_main.c @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "test_pcm_all.h" +#include "test_glib_compat.h" + +#include <glib.h> + +int +main(int argc, char **argv) +{ + g_test_init (&argc, &argv, NULL); + g_test_add_func("/pcm/dither/24", test_pcm_dither_24); + g_test_add_func("/pcm/dither/32", test_pcm_dither_32); + g_test_add_func("/pcm/pack/pack24", test_pcm_pack_24); + g_test_add_func("/pcm/pack/unpack24", test_pcm_unpack_24); + g_test_add_func("/pcm/channels/16", test_pcm_channels_16); + g_test_add_func("/pcm/channels/32", test_pcm_channels_32); + + g_test_run(); +} diff --git a/test/test_pcm_pack.c b/test/test_pcm_pack.c new file mode 100644 index 000000000..de5d8b6e5 --- /dev/null +++ b/test/test_pcm_pack.c @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "test_pcm_all.h" +#include "pcm_pack.h" +#include "test_glib_compat.h" + +#include <glib.h> + +/** + * Generate a random 24 bit PCM sample. + */ +static int32_t +random24(void) +{ + int32_t x = g_random_int() & 0xffffff; + if (x & 0x800000) + x |= 0xff000000; + return x; +} + +void +test_pcm_pack_24(void) +{ + enum { N = 256 }; + int32_t src[N * 3]; + for (unsigned i = 0; i < N; ++i) + src[i] = random24(); + + uint8_t dest[N * 3]; + + pcm_pack_24(dest, src, src + N); + + for (unsigned i = 0; i < N; ++i) { + int32_t d; + if (G_BYTE_ORDER == G_BIG_ENDIAN) + d = (dest[i * 3] << 16) | (dest[i * 3 + 1] << 8) + | dest[i * 3 + 2]; + else + d = (dest[i * 3 + 2] << 16) | (dest[i * 3 + 1] << 8) + | dest[i * 3]; + if (d & 0x800000) + d |= 0xff000000; + + g_assert_cmpint(d, ==, src[i]); + } +} + +void +test_pcm_unpack_24(void) +{ + enum { N = 256 }; + uint8_t src[N * 3]; + for (unsigned i = 0; i < G_N_ELEMENTS(src); ++i) + src[i] = g_random_int_range(0, 256); + + int32_t dest[N]; + + pcm_unpack_24(dest, src, src + G_N_ELEMENTS(src)); + + for (unsigned i = 0; i < N; ++i) { + int32_t s; + if (G_BYTE_ORDER == G_BIG_ENDIAN) + s = (src[i * 3] << 16) | (src[i * 3 + 1] << 8) + | src[i * 3 + 2]; + else + s = (src[i * 3 + 2] << 16) | (src[i * 3 + 1] << 8) + | src[i * 3]; + if (s & 0x800000) + s |= 0xff000000; + + g_assert_cmpint(s, ==, dest[i]); + } +} diff --git a/test/test_queue_priority.c b/test/test_queue_priority.c new file mode 100644 index 000000000..a7106a8e9 --- /dev/null +++ b/test/test_queue_priority.c @@ -0,0 +1,175 @@ +#include "queue.h" +#include "song.h" + +void +song_free(G_GNUC_UNUSED struct song *song) +{ +} + +G_GNUC_UNUSED +static void +dump_order(const struct queue *queue) +{ + g_printerr("queue length=%u, order:\n", queue_length(queue)); + for (unsigned i = 0; i < queue_length(queue); ++i) + g_printerr(" [%u] -> %u (prio=%u)\n", i, queue->order[i], + queue->items[queue->order[i]].priority); +} + +static void +check_descending_priority(G_GNUC_UNUSED const struct queue *queue, + unsigned start_order) +{ + assert(start_order < queue_length(queue)); + + uint8_t last_priority = 0xff; + for (unsigned order = start_order; order < queue_length(queue); ++order) { + unsigned position = queue_order_to_position(queue, order); + uint8_t priority = queue->items[position].priority; + assert(priority <= last_priority); + (void)last_priority; + last_priority = priority; + } +} + +int +main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv) +{ + struct song songs[16]; + + struct queue queue; + queue_init(&queue, 32); + + for (unsigned i = 0; i < G_N_ELEMENTS(songs); ++i) + queue_append(&queue, &songs[i]); + + assert(queue_length(&queue) == G_N_ELEMENTS(songs)); + + /* priority=10 for 4 items */ + + queue_set_priority_range(&queue, 4, 8, 10, -1); + + queue.random = true; + queue_shuffle_order(&queue); + check_descending_priority(&queue, 0); + + for (unsigned i = 0; i < 4; ++i) { + assert(queue_position_to_order(&queue, i) >= 4); + } + + for (unsigned i = 4; i < 8; ++i) { + assert(queue_position_to_order(&queue, i) < 4); + } + + for (unsigned i = 8; i < G_N_ELEMENTS(songs); ++i) { + assert(queue_position_to_order(&queue, i) >= 4); + } + + /* priority=50 one more item */ + + queue_set_priority_range(&queue, 15, 16, 50, -1); + check_descending_priority(&queue, 0); + + assert(queue_position_to_order(&queue, 15) == 0); + + for (unsigned i = 0; i < 4; ++i) { + assert(queue_position_to_order(&queue, i) >= 4); + } + + for (unsigned i = 4; i < 8; ++i) { + assert(queue_position_to_order(&queue, i) >= 1 && + queue_position_to_order(&queue, i) < 5); + } + + for (unsigned i = 8; i < 15; ++i) { + assert(queue_position_to_order(&queue, i) >= 5); + } + + /* priority=20 for one of the 4 priority=10 items */ + + queue_set_priority_range(&queue, 3, 4, 20, -1); + check_descending_priority(&queue, 0); + + assert(queue_position_to_order(&queue, 3) == 1); + assert(queue_position_to_order(&queue, 15) == 0); + + for (unsigned i = 0; i < 3; ++i) { + assert(queue_position_to_order(&queue, i) >= 5); + } + + for (unsigned i = 4; i < 8; ++i) { + assert(queue_position_to_order(&queue, i) >= 2 && + queue_position_to_order(&queue, i) < 6); + } + + for (unsigned i = 8; i < 15; ++i) { + assert(queue_position_to_order(&queue, i) >= 6); + } + + /* priority=20 for another one of the 4 priority=10 items; + pass "after_order" (with priority=10) and see if it's moved + after that one */ + + unsigned current_order = 4; + unsigned current_position = + queue_order_to_position(&queue, current_order); + + unsigned a_order = 3; + unsigned a_position = queue_order_to_position(&queue, a_order); + assert(queue.items[a_position].priority == 10); + queue_set_priority(&queue, a_position, 20, current_order); + + current_order = queue_position_to_order(&queue, current_position); + assert(current_order == 3); + + a_order = queue_position_to_order(&queue, a_position); + assert(a_order == 4); + + check_descending_priority(&queue, current_order + 1); + + /* priority=70 for one of the last items; must be inserted + right after the current song, before the priority=20 one we + just created */ + + unsigned b_order = 10; + unsigned b_position = queue_order_to_position(&queue, b_order); + assert(queue.items[b_position].priority == 0); + queue_set_priority(&queue, b_position, 70, current_order); + + current_order = queue_position_to_order(&queue, current_position); + assert(current_order == 3); + + b_order = queue_position_to_order(&queue, b_position); + assert(b_order == 4); + + check_descending_priority(&queue, current_order + 1); + + /* priority=60 for the old prio50 item; must not be moved, + because it's before the current song, and it's status + hasn't changed (it was already higher before) */ + + unsigned c_order = 0; + unsigned c_position = queue_order_to_position(&queue, c_order); + assert(queue.items[c_position].priority == 50); + queue_set_priority(&queue, c_position, 60, current_order); + + current_order = queue_position_to_order(&queue, current_position); + assert(current_order == 3); + + c_order = queue_position_to_order(&queue, c_position); + assert(c_order == 0); + + /* move the prio=20 item back */ + + a_order = queue_position_to_order(&queue, a_position); + assert(a_order == 5); + assert(queue.items[a_position].priority == 20); + queue_set_priority(&queue, a_position, 5, current_order); + + + current_order = queue_position_to_order(&queue, current_position); + assert(current_order == 3); + + a_order = queue_position_to_order(&queue, a_position); + assert(a_order == 6); +} diff --git a/test/test_vorbis_encoder.c b/test/test_vorbis_encoder.c index 969ab7687..048d26f0a 100644 --- a/test/test_vorbis_encoder.c +++ b/test/test_vorbis_encoder.c @@ -54,7 +54,7 @@ main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv) assert(plugin != NULL); struct config_param *param = config_new_param(NULL, -1); - config_add_block_param(param, "quality", "5.0", -1, NULL); + config_add_block_param(param, "quality", "5.0", -1); struct encoder *encoder = encoder_init(plugin, param, NULL); assert(encoder != NULL); diff --git a/valgrind.suppressions b/valgrind.suppressions index 8d687f7b8..a6ffa13c9 100644 --- a/valgrind.suppressions +++ b/valgrind.suppressions @@ -4,66 +4,29 @@ # bogus messages. { - g_main_context_dispatch - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_slice_alloc - fun:g_slice_alloc0 - fun:get_dispatch - fun:g_main_context_dispatch -} - -{ - g_main_context_default - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_slice_alloc - fun:g_slist_append - fun:g_main_context_new - fun:g_main_context_default -} - -{ - g_main_context_default - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_slice_alloc - fun:g_ptr_array_sized_new - fun:g_main_context_new - fun:g_main_context_default -} - -{ - g_main_context_default + <insert_a_suppression_name_here> Memcheck:Leak - fun:calloc - fun:g_malloc0 - fun:g_main_context_new - fun:g_main_context_default + fun:*alloc + ... + fun:g_random_int } { - g_main_context_default + g_main_context_dispatch Memcheck:Leak fun:malloc fun:g_malloc fun:g_slice_alloc - fun:g_main_context_add_poll_unlocked - fun:g_main_context_new - fun:g_main_context_default + fun:g_slice_alloc0 + fun:get_dispatch + fun:g_main_context_dispatch } { 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:?alloc + ... fun:g_main_context_default } @@ -90,108 +53,34 @@ } { - g_get_language_names - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_strdup - fun:g_get_language_names -} - -{ - g_get_language_names - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_strconcat - fun:_g_compute_locale_variants - fun:g_get_language_names -} - -{ - 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 -} - -{ - g_get_language_names - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_get_language_names -} - -{ g_static_private_set Memcheck:Leak - fun:realloc - fun:g_realloc - fun:g_array_maybe_expand - fun:g_array_set_size + fun:memalign + ... fun:g_static_private_set } { g_static_private_set Memcheck:Leak - fun:malloc - fun:realloc - fun:g_realloc - fun:g_array_maybe_expand - fun:g_array_set_size + fun:*alloc + ... fun:g_static_private_set } { - g_get_language_names - Memcheck:Leak - fun:calloc - fun:g_malloc0 - 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_insert_internal - fun:g_get_language_names -} - -{ - g_get_language_names + g_static_private_set Memcheck:Leak - fun:calloc - fun:g_malloc0 - fun:g_hash_table_resize - fun:g_hash_table_insert_internal - fun:g_get_language_names + fun:*alloc + ... + fun:g_intern_static_string } { g_get_language_names Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_slice_alloc - fun:g_hash_table_new_full + fun:*alloc + ... fun:g_get_language_names } @@ -199,49 +88,31 @@ 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 } { g_set_prgname Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_strdup + fun:*alloc + ... fun:g_set_prgname } { g_set_application_name Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_strdup + fun:*alloc + ... fun:g_set_application_name } { g_thread_init_glib Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_private_new_posix_impl - fun:_g_messages_thread_init_nomessage - fun:g_thread_init_glib -} - -{ - g_thread_init_glib - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_private_new_posix_impl - fun:_g_slice_thread_init_nomessage + fun:*alloc + ... fun:g_thread_init_glib } @@ -254,197 +125,43 @@ } { - g_thread_init_glib - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_private_new_posix_impl - fun:g_thread_init_glib -} - -{ - g_thread_init_glib - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_mutex_new_posix_impl - fun:_g_messages_thread_init_nomessage - fun:g_thread_init_glib -} - -{ - g_thread_init_glib - Memcheck:Leak - fun:calloc - fun:g_malloc0 - fun:g_thread_self - fun:g_thread_init_glib -} - -{ - g_thread_init_glib - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_mutex_new_posix_impl - fun:_g_slice_thread_init_nomessage - fun:g_thread_init_glib -} - -{ - g_thread_init_glib - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_cond_new_posix_impl - fun:g_thread_init_glib -} - -{ - g_thread_init_glib - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_mutex_new_posix_impl - fun:g_thread_init_glib -} - -{ - g_thread_init_glib - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_mutex_new_posix_impl - fun:_g_mem_thread_init_noprivate_nomessage - fun:g_thread_init_glib -} - -{ - g_get_filename_charsets - Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_strdup - fun:g_get_filename_charsets -} - -{ g_get_filename_charsets Memcheck:Leak - fun:calloc - fun:g_malloc0 + fun:*alloc + ... fun:g_get_filename_charsets } { - 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 - fun:g_malloc - fun:g_slice_alloc - fun:g_array_sized_new - fun:g_static_private_set -} - -{ - g_static_private_get - Memcheck:Leak - fun:calloc - fun:g_malloc0 - fun:g_thread_self - fun:g_static_private_get -} - -{ g_get_charset Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_strdup - fun:g_get_charset -} - -{ - g_get_charset - Memcheck:Leak - fun:calloc - fun:g_malloc0 - fun:g_get_charset -} - -{ - 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:*alloc + ... fun:g_get_charset } { openssl Memcheck:Leak - fun:malloc - fun:CRYPTO_malloc - fun:engine_cleanup_add_last - fun:ENGINE_add + fun:*alloc + ... fun:ENGINE_load_dynamic } { - openssl + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - fun:CRYPTO_malloc - fun:ENGINE_new - fun:ENGINE_load_dynamic + fun:*alloc + ... + fun:g_data_initialize } { - openssl + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - fun:CRYPTO_malloc - obj:/usr/lib/libssl.so.0.9.8 - fun:SSL_COMP_get_compression_methods - fun:SSL_library_init + fun:*alloc + ... + fun:g_resolver_get_default } { @@ -452,358 +169,285 @@ Memcheck:Leak fun:malloc fun:CRYPTO_malloc - fun:sk_new - obj:/usr/lib/libssl.so.0.9.8 + ... fun:SSL_COMP_get_compression_methods fun:SSL_library_init } { - openssl + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc + fun:*alloc fun:CRYPTO_malloc - fun:sk_new - fun:engine_cleanup_add_last - fun:ENGINE_add - fun:ENGINE_load_dynamic + ... + fun:ERR_get_state } { <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc + fun:*alloc fun:CRYPTO_malloc - fun:ERR_get_state - fun:ERR_clear_error - fun:Curl_ossl_init - fun:curl_global_init + ... + fun:RSA_new_method } { - openssl + <insert_a_suppression_name_here> 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 + fun:?alloc + ... + fun:do_dlopen } { - openssl + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - fun:CRYPTO_malloc - fun:lh_insert - obj:/usr/lib/libcrypto.so.0.9.8 - fun:ERR_get_state - fun:ERR_clear_error + fun:?alloc + ... + fun:dlopen* } { - <insert a suppression name here> + <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 - fun:do_dlopen - fun:_dl_catch_error - fun:dlerror_run - fun:__libc_dlopen_mode - fun:pthread_cancel_init + ... + fun:dlclose } +# is that a leak in libdbus? + { - <insert_a_suppression_name_here> + <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 + fun:?alloc + ... + obj:*/libdbus-*.so.* + fun:avahi_client_new } { - <insert_a_suppression_name_here> + <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 + obj:/usr/lib/libdbus-1.so.3.4.0 + fun:dbus_message_new_error + obj:/usr/lib/libdbus-1.so.3.4.0 + fun:dbus_connection_send_with_reply + fun:dbus_connection_send_with_reply_and_block + obj:/usr/lib/libavahi-client.so.3.2.4 + fun:avahi_entry_group_new + fun:avahiRegisterService + fun:avahiClientCallback + obj:/usr/lib/libavahi-client.so.3.2.4 + fun:avahi_client_new } { - <insert_a_suppression_name_here> + inet_ntoa Memcheck:Leak fun:malloc - fun:local_strdup - fun:_dl_map_object - fun:dl_open_worker - fun:_dl_catch_error - fun:_dl_open + fun:inet_ntoa } { - <insert_a_suppression_name_here> + wildmidi Memcheck:Leak - fun:calloc - fun:_dl_check_map_versions - fun:dl_open_worker - fun:_dl_catch_error - fun:_dl_open + fun:malloc + fun:realloc + fun:init_gauss + fun:WildMidi_Init } { - <insert_a_suppression_name_here> + g_quark_from_string Memcheck:Leak - fun:malloc - fun:_dl_map_object_deps - fun:dl_open_worker - fun:_dl_catch_error - fun:_dl_open + fun:*alloc + ... + fun:g_quark_from_* } { - <insert_a_suppression_name_here> + g_get_any_init_do Memcheck:Leak fun:malloc - fun:_dl_map_object_deps - fun:dl_open_worker - fun:_dl_catch_error - fun:_dl_open + fun:g_malloc + fun:g_strdup + fun:g_get_any_init_do } { - <insert_a_suppression_name_here> + g_get_any_init_do 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 + fun:g_malloc + fun:g_strjoinv + fun:g_get_any_init_do } { - <insert_a_suppression_name_here> + nss 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 + fun:__nss_lookup_function } { - dlopen + nss Memcheck:Leak - fun:calloc - fun:_dlerror_run + fun:malloc + fun:tsearch + fun:__nss_lookup_function } { - dlopen + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - fun:_dl_scope_free - fun:_dl_map_object_deps - fun:dl_open_worker - fun:_dl_catch_error - fun:_dl_open - fun:do_dlopen - fun:_dl_catch_error - fun:dlerror_run + fun:*alloc + ... + fun:g_type_init_with_debug_flags } -# is that a leak in libdbus? - { - <insert a suppression name here> + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - obj:/usr/lib/libdbus-1.so.3.4.0 - obj:/usr/lib/libdbus-1.so.3.4.0 - fun:avahi_client_new + fun:*alloc + ... + fun:g_type_register_static } { - <insert a suppression name here> + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - obj:/usr/lib/libdbus-1.so.3.4.0 - fun:dbus_message_unref - obj:/usr/lib/libdbus-1.so.3.4.0 - fun:dbus_connection_send_with_reply_and_block - fun:dbus_bus_register - obj:/usr/lib/libdbus-1.so.3.4.0 - fun:avahi_client_new + fun:*alloc + ... + fun:g_type_add_interface_static } { - <insert a suppression name here> + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - obj:/usr/lib/libdbus-1.so.3.4.0 - fun:dbus_message_new_method_call - fun:dbus_bus_register - obj:/usr/lib/libdbus-1.so.3.4.0 - fun:avahi_client_new + fun:*alloc + ... + fun:g_type_add_interface_check } { - <insert a suppression name here> + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - obj:/usr/lib/libdbus-1.so.3.4.0 - obj:/usr/lib/libdbus-1.so.3.4.0 - fun:avahi_client_new + fun:*alloc + ... + fun:g_type_interface_add_prerequisite } { - <insert a suppression name here> + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - obj:/usr/lib/libdbus-1.so.3.4.0 - fun:dbus_message_new_error - obj:/usr/lib/libdbus-1.so.3.4.0 - fun:dbus_connection_send_with_reply - fun:dbus_connection_send_with_reply_and_block - obj:/usr/lib/libavahi-client.so.3.2.4 - fun:avahi_entry_group_new - fun:avahiRegisterService - fun:avahiClientCallback - obj:/usr/lib/libavahi-client.so.3.2.4 - fun:avahi_client_new + fun:calloc + fun:g_malloc0 + fun:g_type_class_ref } { - inet_ntoa + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - fun:inet_ntoa + fun:*alloc + ... + fun:g_*_class_intern_init } { - wildmidi + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - fun:realloc - fun:init_gauss - fun:WildMidi_Init + fun:*alloc + ... + fun:type_iface_vtable_base_init_Wm } { - g_quark_from_static_string + <insert_a_suppression_name_here> Memcheck:Leak - fun:calloc - fun:g_malloc0 - fun:g_hash_table_new_full - fun:g_quark_from_static_string + fun:*alloc + ... + fun:g_object_do_class_init } { - g_quark_from_static_string + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - fun:realloc - fun:g_realloc - fun:g_quark_from_static_string + fun:*alloc + ... + fun:g_object_base_class_init } { - g_quark_from_string + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_strdup - fun:g_quark_from_string + fun:*alloc + ... + fun:g_object_class_install_property } { - g_quark_from_string + <insert_a_suppression_name_here> Memcheck:Leak - fun:calloc - fun:g_malloc0 - fun:g_hash_table_new_full - fun:g_quark_from_string + fun:*alloc + ... + fun:soup_*_class_intern_init } { - g_quark_from_string + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - fun:realloc - fun:g_realloc - fun:g_quark_from_string + fun:*alloc + ... + fun:soup_auth_manager_add_type } { - g_quark_from_string + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_slice_alloc - fun:g_hash_table_new_full - fun:g_quark_from_string + fun:*alloc + ... + fun:soup_auth_manager_class_intern_init } { - g_get_any_init_do + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_strdup - fun:g_get_any_init_do + fun:*alloc + ... + fun:soup_auth_manager_ntlm_class_intern_init } { - g_get_any_init_do + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - fun:g_malloc - fun:g_strjoinv - fun:g_get_any_init_do + fun:*alloc + ... + fun:intern_header_name } { nss Memcheck:Leak fun:malloc - fun:__nss_lookup_function + fun:nss_parse_service_list + fun:__nss_database_lookup } { - nss + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - fun:tsearch - fun:__nss_lookup_function + fun:?alloc + ... + fun:xmlInitParser } { - nss + <insert_a_suppression_name_here> Memcheck:Leak - fun:malloc - fun:nss_parse_service_list - fun:__nss_database_lookup + fun:?alloc + fun:snd1_dlobj_cache_get } |