Compare commits

..

23 Commits

Author SHA1 Message Date
Avuton Olrich
dec7090198 mpd version 0.15.14 2010-11-06 14:42:02 -07:00
Max Kellermann
83ec0e5552 player_thread: fix assertion failure due to wrong music pipe on seek
When one song is played twice, and the decoder is working on the
second "instance", but the first should be seeked, the check in
player_seek_decoder() may assume that it can reuse the decoder without
exchanging pipes.  The last thing was the mistake: the pipe pointer
was different, which led to an assertion failure.  This patch adds
another check which exchanges the player pipe.
2010-11-05 19:24:42 +01:00
Max Kellermann
cc261872c2 decoder_control: pass music_pipe to dc_start()
More abstraction for decoder_control.pipe.
2010-11-05 19:18:44 +01:00
Max Kellermann
5223261f19 player_thread: add helper function player_dc_at_next_song()
Some abstraction for decoder_control.pipe access.
2010-11-05 19:08:59 +01:00
Max Kellermann
c594afeee7 pipe: add helper function music_pipe_empty() 2010-11-05 18:40:23 +01:00
Max Kellermann
32d10eedbd input/rewind: remove redundant NULL check before g_free() call 2010-11-05 18:40:14 +01:00
Max Kellermann
dfd98eede7 input/rewind: add two assertions 2010-11-05 18:40:07 +01:00
Max Kellermann
a728d7a026 input/rewind: fix double free bug
Duplicate the "mime" attribute of the inner input_stream object,
instead of copying the pointer.
2010-11-05 18:39:40 +01:00
Max Kellermann
e8d8bd4c0d decoder/{mp4ff,ffmpeg}: add extension ".m4b" (audio book)
Same as ".m4a".
2010-11-05 02:01:35 +01:00
Max Kellermann
8d5fa754e8 output_thread: fix assertion failure due to race condition in OPEN
Change the assertion on "fail_timer==NULL" in OPEN to a runtime check.
This assertion crashed when the output thread failed while the player
thread was calling audio_output_open().
2010-11-04 23:44:23 +01:00
Max Kellermann
2ee047a1dd output_internal: protect attribute "fail_timer" with mutex 2010-11-04 23:40:43 +01:00
Max Kellermann
9562f66741 output_control: lock object in audio_output_open()
Protect the attributes "open" and "fail_timer".
2010-11-04 23:28:18 +01:00
Max Kellermann
21223154aa output_control: lock object in audio_output_close()
Protect the attributes "open" and "fail_timer".
2010-11-04 21:51:02 +01:00
Avuton Olrich
a549d871f3 Modify version string to post-release version 0.15.14~git 2010-10-10 09:57:57 -07:00
Avuton Olrich
b552e9a120 mpd version 0.15.13 2010-10-10 09:57:52 -07:00
Max Kellermann
5923cfcde3 output/httpd: MIME type audio/ogg for Ogg Vorbis
RFC 5334 10.3 defines the MIME type "audio/ogg".  We could use
"application/ogg" as well, but we know for sure that we only emit
audio data.
2010-10-03 16:22:03 +02:00
Thomas Jansen
e3f4c7b91c input/rewind: enable for MMS 2010-09-28 12:56:47 +02:00
Thomas Jansen
54294366d5 rewind_input_plugin: Update MIME not only once
The assumption that MIME type is set only once is not valid with CURL,
as URL redirections may update the MIME type.

This fixes bug #3044.
2010-09-23 20:39:13 +02:00
Qball Cow
4a7abc9d44 Correctly terminate stream_title.
This caused random data to be send via icy-server if the played
song had no tags.
2010-09-08 13:19:59 +02:00
Max Kellermann
589bb54111 input/curl: fix version check for curl_multi_timeout()
According to the CURL web site, curl_multi_timeout() was added in
version 7.15.4:

 http://curl.haxx.se/libcurl/c/curl_multi_timeout.html
2010-09-07 21:40:56 +02:00
Max Kellermann
64dacd175a output_thread: fix race condition after CANCEL command
Clear the notification before finishing the CANCEL command, so the
notify_wait() after that will always wait for the right notification,
sent by audio_output_all_cancel().
2010-08-19 11:05:24 +02:00
Max Kellermann
625e4755d1 notify: add function notify_clear() 2010-08-19 11:03:53 +02:00
Avuton Olrich
676739c426 Modify version string to post-release version 0.15.13~git 2010-07-21 06:40:33 +02:00
463 changed files with 13264 additions and 32578 deletions

8
.gitignore vendored
View File

@@ -24,7 +24,6 @@ config.sub
config_detected.h
config_detected.mk
configure
configure.lineno
depcomp
depmode
install-sh
@@ -36,7 +35,6 @@ mpd
stamp-h1
tags
*~
.#*
.stgit*
doc/protocol.html
doc/protocol
@@ -45,16 +43,10 @@ doc/developer
doc/sticker
doc/api
test/software_volume
test/run_convert
test/run_decoder
test/read_tags
test/run_filter
test/run_encoder
test/run_output
test/read_conf
test/run_input
test/read_mixer
test/dump_playlist
test/run_normalize
test/tmp
test/run_inotify

View File

@@ -19,6 +19,9 @@ Eric Wollesen <encoded@xmtp.net>
Thomas Jansen <mithi@mithi.net>
multithreading tweaks, miscellaneous
Rasmus Steinke <rasi1979@googlemail.com>
documentation
Romain Bignon <romain@peerfuse.org>
playlist manipulation

14
INSTALL
View File

@@ -13,7 +13,7 @@ Dependencies
gcc - http://gcc.gnu.org/
Any other C99 compliant compiler should also work.
GLib 2.12 - http://www.gtk.org/
glib - http://www.gtk.org/
General-purpose utility library.
@@ -56,9 +56,6 @@ libshout - http://www.icecast.org/
For streaming to an Icecast or Shoutcast server.
You also need an encoder: either libvorbisenc (ogg), or liblame (mp3).
OpenAL - http://kcat.strangesoft.net/openal.html
Open Audio Library
Optional Input Dependencies
---------------------------
@@ -72,9 +69,6 @@ MAD - http://www.underbit.com/products/mad/
For MP3 support. You will need libmad, and optionally libid3tag if you want
ID3 tag support.
libmpg123 - http://www.mpg123.de/
Alternative for MP3 support.
Ogg Vorbis - http://www.xiph.org/ogg/vorbis/
For Ogg Vorbis support. You will need libogg and libvorbis.
@@ -110,12 +104,6 @@ For MIDI support (DO NOT USE - use libwildmidi instead)
libwildmidi - http://wildmidi.sourceforge.net/
For MIDI support.
libsndfile - http://www.mega-nerd.com/libsndfile/
WAVE, AIFF, and many others.
libwavpack - http://www.wavpack.com/
For WavPack playback.
Optional Miscellaneous Dependencies
-----------------------------------

View File

@@ -1,15 +1,12 @@
ACLOCAL_AMFLAGS = -I m4
AUTOMAKE_OPTIONS = foreign 1.10 dist-bzip2 subdir-objects
AUTOMAKE_OPTIONS = foreign 1.9 dist-bzip2
AM_CPPFLAGS = -I$(srcdir)/src $(GLIB_CFLAGS)
AM_CPPFLAGS += -DSYSTEM_CONFIG_FILE_LOCATION='"$(sysconfdir)/mpd.conf"'
bin_PROGRAMS = src/mpd
src_mpd_CFLAGS = $(AM_CFLAGS) $(MPD_CFLAGS)
src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \
$(LIBWRAP_CFLAGS) \
$(SQLITE_CFLAGS) \
$(ARCHIVE_CFLAGS) \
$(INPUT_CFLAGS) \
@@ -19,7 +16,6 @@ src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \
$(FILTER_CFLAGS) \
$(OUTPUT_CFLAGS)
src_mpd_LDADD = $(MPD_LIBS) \
$(LIBWRAP_LDFLAGS) \
$(SQLITE_LIBS) \
$(ARCHIVE_LIBS) \
$(INPUT_LIBS) \
@@ -31,12 +27,10 @@ src_mpd_LDADD = $(MPD_LIBS) \
$(GLIB_LIBS)
mpd_headers = \
src/check.h \
src/notify.h \
src/ack.h \
src/audio.h \
src/audio_format.h \
src/audio_check.h \
src/audio_parser.h \
src/output_internal.h \
src/output_api.h \
@@ -48,15 +42,7 @@ mpd_headers = \
src/output_state.h \
src/output_print.h \
src/output_command.h \
src/filter_internal.h \
src/filter_config.h \
src/filter_plugin.h \
src/filter_registry.h \
src/filter/autoconvert_filter_plugin.h \
src/filter/chain_filter_plugin.h \
src/filter/convert_filter_plugin.h \
src/filter/replay_gain_filter_plugin.h \
src/filter/volume_filter_plugin.h \
src/buffer2array.h \
src/command.h \
src/idle.h \
src/cmdline.h \
@@ -78,39 +64,23 @@ mpd_headers = \
src/encoder_plugin.h \
src/encoder_list.h \
src/encoder_api.h \
src/exclude.h \
src/fd_util.h \
src/fifo_buffer.h \
src/glib_compat.h \
src/update.h \
src/update_internal.h \
src/inotify_source.h \
src/inotify_queue.h \
src/inotify_update.h \
src/dirvec.h \
src/gcc.h \
src/decoder_list.h \
src/decoder_print.h \
src/decoder/flac_compat.h \
src/decoder/flac_metadata.h \
src/decoder/flac_pcm.h \
src/decoder/_flac_common.h \
src/decoder/_ogg_common.h \
src/input_init.h \
src/input_plugin.h \
src/input_registry.h \
src/input_stream.h \
src/input/file_input_plugin.h \
src/input/ffmpeg_input_plugin.h \
src/input/curl_input_plugin.h \
src/input/rewind_input_plugin.h \
src/input/lastfm_input_plugin.h \
src/input/mms_input_plugin.h \
src/text_file.h \
src/text_input_stream.h \
src/icy_server.h \
src/icy_metadata.h \
src/client.h \
src/client_internal.h \
src/listen.h \
src/log.h \
src/ls.h \
@@ -121,34 +91,27 @@ mpd_headers = \
src/mixer_list.h \
src/event_pipe.h \
src/mixer_plugin.h \
src/mixer_type.h \
src/mixer/software_mixer_plugin.h \
src/mixer/pulse_mixer_plugin.h \
src/daemon.h \
src/AudioCompress/config.h \
src/AudioCompress/compress.h \
src/normalize.h \
src/compress.h \
src/buffer.h \
src/pipe.h \
src/chunk.h \
src/path.h \
src/mapper.h \
src/open.h \
src/output/httpd_client.h \
src/output/httpd_internal.h \
src/output/pulse_output_plugin.h \
src/page.h \
src/pcm_buffer.h \
src/pcm_utils.h \
src/pcm_convert.h \
src/pcm_volume.h \
src/pcm_mix.h \
src/pcm_byteswap.h \
src/pcm_channels.h \
src/pcm_format.h \
src/pcm_resample.h \
src/pcm_resample_internal.h \
src/pcm_dither.h \
src/pcm_pack.h \
src/pcm_prng.h \
src/permission.h \
src/player_thread.h \
@@ -158,31 +121,13 @@ mpd_headers = \
src/playlist_print.h \
src/playlist_save.h \
src/playlist_state.h \
src/playlist_plugin.h \
src/playlist_list.h \
src/playlist_mapper.h \
src/playlist_any.h \
src/playlist_song.h \
src/playlist_queue.h \
src/playlist_vector.h \
src/playlist_database.h \
src/playlist/extm3u_playlist_plugin.h \
src/playlist/m3u_playlist_plugin.h \
src/playlist/pls_playlist_plugin.h \
src/playlist/xspf_playlist_plugin.h \
src/playlist/asx_playlist_plugin.h \
src/playlist/lastfm_playlist_plugin.h \
src/playlist/cue_playlist_plugin.h \
src/playlist/flac_playlist_plugin.h \
src/poison.h \
src/riff.h \
src/aiff.h \
src/queue.h \
src/queue_print.h \
src/queue_save.h \
src/refcount.h \
src/replay_gain_config.h \
src/replay_gain_info.h \
src/replay_gain.h \
src/sig_handlers.h \
src/song.h \
src/song_print.h \
@@ -200,10 +145,8 @@ mpd_headers = \
src/tag_table.h \
src/tag_ape.h \
src/tag_id3.h \
src/tag_rva2.h \
src/tag_print.h \
src/tag_save.h \
src/tokenizer.h \
src/strset.h \
src/uri.h \
src/utils.h \
@@ -215,10 +158,6 @@ mpd_headers = \
src/archive_api.h \
src/archive_internal.h \
src/archive_list.h \
src/archive_plugin.h \
src/archive/bz2_archive_plugin.h \
src/archive/iso9660_archive_plugin.h \
src/archive/zzip_archive_plugin.h \
src/input/archive_input_plugin.h \
src/cue/cue_tag.h
@@ -226,18 +165,15 @@ src_mpd_SOURCES = \
$(mpd_headers) \
$(ARCHIVE_SRC) \
$(INPUT_SRC) \
$(PLAYLIST_SRC) \
$(TAG_SRC) \
$(DECODER_SRC) \
$(ENCODER_SRC) \
$(OUTPUT_API_SRC) $(OUTPUT_SRC) \
$(MIXER_API_SRC) $(MIXER_SRC) \
$(FILTER_SRC) \
src/notify.c \
src/audio.c \
src/audio_check.c \
src/audio_format.c \
src/audio_parser.c \
src/buffer2array.c \
src/command.c \
src/idle.c \
src/cmdline.c \
@@ -248,39 +184,22 @@ src_mpd_SOURCES = \
src/decoder_control.c \
src/decoder_api.c \
src/decoder_internal.c \
src/decoder_print.c \
src/directory.c \
src/directory_save.c \
src/directory_print.c \
src/database.c \
src/dirvec.c \
src/exclude.c \
src/fd_util.c \
src/fifo_buffer.c \
src/filter_config.c \
src/filter_plugin.c \
src/filter_registry.c \
src/update.c \
src/update_queue.c \
src/update_walk.c \
src/update_remove.c \
src/client.c \
src/client_event.c \
src/client_expire.c \
src/client_global.c \
src/client_idle.c \
src/client_list.c \
src/client_new.c \
src/client_process.c \
src/client_read.c \
src/client_write.c \
src/listen.c \
src/log.c \
src/ls.c \
src/main.c \
src/event_pipe.c \
src/daemon.c \
src/AudioCompress/compress.c \
src/normalize.c \
src/compress.c \
src/buffer.c \
src/pipe.c \
src/chunk.c \
@@ -290,9 +209,7 @@ src_mpd_SOURCES = \
src/pcm_convert.c \
src/pcm_volume.c \
src/pcm_mix.c \
src/pcm_byteswap.c \
src/pcm_channels.c \
src/pcm_pack.c \
src/pcm_format.c \
src/pcm_resample.c \
src/pcm_resample_fallback.c \
@@ -306,21 +223,13 @@ src_mpd_SOURCES = \
src/playlist_edit.c \
src/playlist_print.c \
src/playlist_save.c \
src/playlist_mapper.c \
src/playlist_any.c \
src/playlist_song.c \
src/playlist_state.c \
src/playlist_queue.c \
src/playlist_vector.c \
src/playlist_database.c \
src/queue.c \
src/queue_print.c \
src/queue_save.c \
src/replay_gain_config.c \
src/replay_gain_info.c \
src/replay_gain.c \
src/sig_handlers.c \
src/song.c \
src/song_update.c \
src/song_print.c \
src/song_save.c \
src/songvec.c \
@@ -331,9 +240,6 @@ src_mpd_SOURCES = \
src/tag_pool.c \
src/tag_print.c \
src/tag_save.c \
src/tokenizer.c \
src/text_file.c \
src/text_input_stream.c \
src/strset.c \
src/uri.c \
src/utils.c \
@@ -342,13 +248,6 @@ src_mpd_SOURCES = \
src/stored_playlist.c \
src/timer.c
if ENABLE_INOTIFY
src_mpd_SOURCES += \
src/inotify_source.c \
src/inotify_queue.c \
src/inotify_update.c
endif
if ENABLE_SQLITE
src_mpd_SOURCES += \
src/sticker.c \
@@ -378,22 +277,21 @@ ARCHIVE_LIBS = \
ARCHIVE_SRC =
if HAVE_BZ2
ARCHIVE_SRC += src/archive/bz2_archive_plugin.c
ARCHIVE_SRC += src/archive/bz2_plugin.c
endif
if HAVE_ZZIP
ARCHIVE_SRC += src/archive/zzip_archive_plugin.c
if HAVE_ZIP
ARCHIVE_SRC += src/archive/zip_plugin.c
endif
if HAVE_ISO9660
ARCHIVE_SRC += src/archive/iso9660_archive_plugin.c
if HAVE_ISO
ARCHIVE_SRC += src/archive/iso_plugin.c
endif
if ENABLE_ARCHIVE
ARCHIVE_SRC += \
src/archive_api.c \
src/archive_list.c \
src/archive_plugin.c \
src/input/archive_input_plugin.c
endif
@@ -410,7 +308,6 @@ TAG_SRC = \
if HAVE_ID3TAG
TAG_SRC += src/tag_id3.c \
src/tag_rva2.c \
src/riff.c src/aiff.c
endif
@@ -419,64 +316,51 @@ endif
DECODER_CFLAGS = \
$(VORBIS_CFLAGS) $(TREMOR_CFLAGS) \
$(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \
$(SNDFILE_CFLAGS) \
$(AUDIOFILE_CFLAGS) \
$(LIBMIKMOD_CFLAGS) \
$(MODPLUG_CFLAGS) \
$(GME_CFLAGS) \
$(SIDPLAY_CFLAGS) \
$(FLUIDSYNTH_CFLAGS) \
$(WILDMIDI_CFLAGS) \
$(WAVPACK_CFLAGS) \
$(MAD_CFLAGS) \
$(MPG123_CFLAGS) \
$(FFMPEG_CFLAGS) \
$(CUE_CFLAGS)
DECODER_LIBS = \
$(VORBIS_LIBS) $(TREMOR_LIBS) \
$(FLAC_LIBS) \
$(SNDFILE_LIBS) \
$(AUDIOFILE_LIBS) $(LIBMIKMOD_LIBS) \
$(MODPLUG_LIBS) \
$(GME_LIBS) \
$(SIDPLAY_LIBS) \
$(FLUIDSYNTH_LIBS) \
$(WILDMIDI_LIBS) \
$(WAVPACK_LIBS) \
$(MAD_LIBS) \
$(MPG123_LIBS) \
$(MP4FF_LIBS) \
$(FFMPEG_LIBS) \
$(CUE_LIBS)
DECODER_SRC = \
src/decoder_buffer.c \
src/decoder_plugin.c \
src/decoder_list.c
if HAVE_MAD
DECODER_SRC += src/decoder/mad_decoder_plugin.c
endif
if HAVE_MPG123
DECODER_SRC += src/decoder/mpg123_decoder_plugin.c
DECODER_SRC += src/decoder/mad_plugin.c
endif
if HAVE_MPCDEC
DECODER_SRC += src/decoder/mpcdec_decoder_plugin.c
DECODER_SRC += src/decoder/mpcdec_plugin.c
endif
if HAVE_WAVPACK
DECODER_SRC += src/decoder/wavpack_decoder_plugin.c
DECODER_SRC += src/decoder/wavpack_plugin.c
endif
if HAVE_FAAD
DECODER_SRC += src/decoder/faad_decoder_plugin.c
DECODER_SRC += src/decoder/faad_plugin.c
endif
if HAVE_MP4
DECODER_SRC += src/decoder/mp4ff_decoder_plugin.c
DECODER_SRC += src/decoder/mp4ff_plugin.c
endif
if HAVE_OGG_COMMON
@@ -484,83 +368,63 @@ DECODER_SRC += src/decoder/_ogg_common.c
endif
if HAVE_FLAC_COMMON
DECODER_SRC += \
src/decoder/flac_metadata.c \
src/decoder/flac_pcm.c \
src/decoder/_flac_common.c
DECODER_SRC += src/decoder/_flac_common.c
endif
if ENABLE_VORBIS_DECODER
DECODER_SRC += src/decoder/vorbis_decoder_plugin.c
DECODER_SRC += src/decoder/vorbis_plugin.c
endif
if HAVE_FLAC
DECODER_SRC += src/decoder/flac_decoder_plugin.c
DECODER_SRC += src/decoder/flac_plugin.c
endif
if HAVE_OGGFLAC
DECODER_SRC += src/decoder/oggflac_decoder_plugin.c
DECODER_SRC += src/decoder/oggflac_plugin.c
endif
if HAVE_AUDIOFILE
DECODER_SRC += src/decoder/audiofile_decoder_plugin.c
DECODER_SRC += src/decoder/audiofile_plugin.c
endif
if ENABLE_MIKMOD_DECODER
DECODER_SRC += src/decoder/mikmod_decoder_plugin.c
DECODER_SRC += src/decoder/mikmod_plugin.c
endif
if HAVE_MODPLUG
DECODER_SRC += src/decoder/modplug_decoder_plugin.c
DECODER_SRC += src/decoder/modplug_plugin.c
endif
if ENABLE_SIDPLAY
DECODER_SRC += src/decoder/sidplay_decoder_plugin.cxx
DECODER_SRC += src/decoder/sidplay_plugin.cxx
endif
if ENABLE_FLUIDSYNTH
DECODER_SRC += src/decoder/fluidsynth_decoder_plugin.c
DECODER_SRC += src/decoder/fluidsynth_plugin.c
endif
if ENABLE_WILDMIDI
DECODER_SRC += src/decoder/wildmidi_decoder_plugin.c
DECODER_SRC += src/decoder/wildmidi_plugin.c
endif
if HAVE_FFMPEG
DECODER_SRC += src/decoder/ffmpeg_decoder_plugin.c
endif
if ENABLE_SNDFILE
DECODER_SRC += src/decoder/sndfile_decoder_plugin.c
endif
if HAVE_GME
DECODER_SRC += src/decoder/gme_decoder_plugin.c
DECODER_SRC += src/decoder/ffmpeg_plugin.c
endif
# encoder plugins
ENCODER_CFLAGS = \
$(LAME_CFLAGS) \
$(TWOLAME_CFLAGS) \
$(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \
$(VORBISENC_CFLAGS)
ENCODER_LIBS = \
$(LAME_LIBS) \
$(TWOLAME_LIBS) \
$(FLAC_LIBS) \
$(VORBISENC_LIBS)
ENCODER_SRC =
if ENABLE_ENCODER
ENCODER_SRC += src/encoder_list.c
ENCODER_SRC += src/encoder/null_encoder.c
if ENABLE_WAVE_ENCODER
ENCODER_SRC += src/encoder/wave_encoder.c
endif
if ENABLE_VORBIS_ENCODER
ENCODER_SRC += src/encoder/vorbis_encoder.c
@@ -569,14 +433,6 @@ endif
if ENABLE_LAME_ENCODER
ENCODER_SRC += src/encoder/lame_encoder.c
endif
if ENABLE_TWOLAME_ENCODER
ENCODER_SRC += src/encoder/twolame_encoder.c
endif
if ENABLE_FLAC_ENCODER
ENCODER_SRC += src/encoder/flac_encoder.c
endif
endif
@@ -602,28 +458,24 @@ endif
INPUT_CFLAGS = \
$(CURL_CFLAGS) \
$(FFMPEG_CFLAGS) \
$(MMS_CFLAGS)
INPUT_LIBS = \
$(CURL_LIBS) \
$(FFMPEG_LIBS) \
$(MMS_LIBS)
INPUT_SRC = \
src/input_init.c \
src/input_registry.c \
src/input_stream.c \
src/input/rewind_input_plugin.c \
src/input/file_input_plugin.c
if ENABLE_CURL
if HAVE_CURL
INPUT_SRC += src/input/curl_input_plugin.c \
src/input/rewind_input_plugin.c \
src/icy_metadata.c
endif
if HAVE_FFMPEG
INPUT_SRC += src/input/ffmpeg_input_plugin.c
if ENABLE_LASTFM
INPUT_SRC += src/input/lastfm_input_plugin.c
endif
if ENABLE_MMS
@@ -635,16 +487,13 @@ OUTPUT_CFLAGS = \
$(AO_CFLAGS) \
$(ALSA_CFLAGS) \
$(JACK_CFLAGS) \
$(OPENAL_CFLAGS) \
$(PULSE_CFLAGS) \
$(SHOUT_CFLAGS)
OUTPUT_LIBS = \
$(LIBWRAP_LDFLAGS) \
$(AO_LIBS) \
$(ALSA_LIBS) \
$(JACK_LIBS) \
$(OPENAL_LIBS) \
$(PULSE_LIBS) \
$(SHOUT_LIBS)
@@ -663,16 +512,14 @@ OUTPUT_SRC = \
MIXER_API_SRC = \
src/mixer_control.c \
src/mixer_type.c \
src/mixer_all.c \
src/mixer_api.c
MIXER_SRC = \
src/mixer/software_mixer_plugin.c
MIXER_SRC =
if HAVE_ALSA
OUTPUT_SRC += src/output/alsa_plugin.c
MIXER_SRC += src/mixer/alsa_mixer_plugin.c
MIXER_SRC += src/mixer/alsa_mixer.c
endif
if HAVE_AO
@@ -680,7 +527,7 @@ OUTPUT_SRC += src/output/ao_plugin.c
endif
if HAVE_FIFO
OUTPUT_SRC += src/output/fifo_output_plugin.c
OUTPUT_SRC += src/output/fifo_plugin.c
endif
if ENABLE_PIPE_OUTPUT
@@ -688,7 +535,7 @@ OUTPUT_SRC += src/output/pipe_output_plugin.c
endif
if HAVE_JACK
OUTPUT_SRC += src/output/jack_output_plugin.c
OUTPUT_SRC += src/output/jack_plugin.c
endif
if HAVE_MVP
@@ -697,11 +544,7 @@ endif
if HAVE_OSS
OUTPUT_SRC += src/output/oss_plugin.c
MIXER_SRC += src/mixer/oss_mixer_plugin.c
endif
if HAVE_OPENAL
OUTPUT_SRC += src/output/openal_plugin.c
MIXER_SRC += src/mixer/oss_mixer.c
endif
if HAVE_OSX
@@ -709,18 +552,14 @@ OUTPUT_SRC += src/output/osx_plugin.c
endif
if HAVE_PULSE
OUTPUT_SRC += src/output/pulse_output_plugin.c
MIXER_SRC += src/mixer/pulse_mixer_plugin.c
OUTPUT_SRC += src/output/pulse_plugin.c
MIXER_SRC += src/mixer/pulse_mixer.c
endif
if HAVE_SHOUT
OUTPUT_SRC += src/output/shout_plugin.c
endif
if ENABLE_RECORDER_OUTPUT
OUTPUT_SRC += src/output/recorder_output_plugin.c
endif
if ENABLE_HTTPD_OUTPUT
OUTPUT_SRC += \
src/icy_server.c \
@@ -732,50 +571,6 @@ if ENABLE_SOLARIS_OUTPUT
OUTPUT_SRC += src/output/solaris_output_plugin.c
endif
if ENABLE_WIN32_OUTPUT
OUTPUT_SRC += src/output/win32_output_plugin.c
endif
#
# Playlist plugins
#
PLAYLIST_SRC = \
src/playlist/extm3u_playlist_plugin.c \
src/playlist/m3u_playlist_plugin.c \
src/playlist/pls_playlist_plugin.c \
src/playlist/xspf_playlist_plugin.c \
src/playlist/asx_playlist_plugin.c \
src/playlist_list.c
if ENABLE_LASTFM
PLAYLIST_SRC += src/playlist/lastfm_playlist_plugin.c
endif
if HAVE_CUE
PLAYLIST_SRC += src/playlist/cue_playlist_plugin.c
endif
if HAVE_FLAC
PLAYLIST_SRC += src/playlist/flac_playlist_plugin.c
endif
#
# Filter plugins
#
FILTER_SRC = \
src/filter/null_filter_plugin.c \
src/filter/chain_filter_plugin.c \
src/filter/autoconvert_filter_plugin.c \
src/filter/convert_filter_plugin.c \
src/filter/route_filter_plugin.c \
src/filter/normalize_filter_plugin.c \
src/filter/replay_gain_filter_plugin.c \
src/filter/volume_filter_plugin.c
#
# Sparse code analysis
@@ -803,31 +598,21 @@ sparse-check:
if ENABLE_TEST
TESTS =
noinst_PROGRAMS = \
test/read_conf \
test/run_input \
test/dump_playlist \
test/run_decoder \
test/read_tags \
test/run_filter \
test/run_output \
test/run_convert \
test/run_normalize \
test/read_mixer \
test/software_volume
if HAVE_ALSA
# this debug program is still ALSA specific
noinst_PROGRAMS += test/read_mixer
endif
test_read_conf_CPPFLAGS = $(AM_CPPFLAGS) \
$(GLIB_CFLAGS)
test_read_conf_LDADD = $(MPD_LIBS) \
$(GLIB_LIBS)
test_read_conf_SOURCES = test/read_conf.c \
src/conf.c src/tokenizer.c src/utils.c
src/conf.c src/buffer2array.c src/utils.c
test_run_input_CPPFLAGS = $(AM_CPPFLAGS) \
$(ARCHIVE_CFLAGS) \
@@ -837,43 +622,11 @@ test_run_input_LDADD = $(MPD_LIBS) \
$(INPUT_LIBS) \
$(GLIB_LIBS)
test_run_input_SOURCES = test/run_input.c \
src/conf.c src/tokenizer.c src/utils.c \
src/conf.c src/buffer2array.c src/utils.c \
src/tag.c src/tag_pool.c src/tag_save.c \
src/fd_util.c \
$(ARCHIVE_SRC) \
$(INPUT_SRC)
test_dump_playlist_CPPFLAGS = $(AM_CPPFLAGS) \
$(CUE_CFLAGS) \
$(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \
$(ARCHIVE_CFLAGS) \
$(INPUT_CFLAGS)
test_dump_playlist_LDADD = $(MPD_LIBS) \
$(CUE_LIBS) \
$(FLAC_LIBS) \
$(ARCHIVE_LIBS) \
$(INPUT_LIBS) \
$(GLIB_LIBS)
test_dump_playlist_SOURCES = test/dump_playlist.c \
src/conf.c src/tokenizer.c src/utils.c \
src/uri.c \
src/song.c src/tag.c src/tag_pool.c src/tag_save.c \
src/text_input_stream.c src/fifo_buffer.c \
src/fd_util.c \
$(ARCHIVE_SRC) \
$(INPUT_SRC) \
$(PLAYLIST_SRC)
if HAVE_CUE
test_dump_playlist_SOURCES += src/cue/cue_tag.c
endif
if HAVE_FLAC
test_dump_playlist_SOURCES += \
src/replay_gain_info.c \
src/decoder/flac_metadata.c
endif
test_run_decoder_CPPFLAGS = $(AM_CPPFLAGS) \
$(TAG_CFLAGS) \
$(ARCHIVE_CFLAGS) \
@@ -884,13 +637,10 @@ test_run_decoder_LDADD = $(MPD_LIBS) \
$(INPUT_LIBS) $(DECODER_LIBS) \
$(GLIB_LIBS)
test_run_decoder_SOURCES = test/run_decoder.c \
src/conf.c src/tokenizer.c src/utils.c src/log.c \
src/conf.c src/buffer2array.c src/utils.c src/log.c \
src/tag.c src/tag_pool.c \
src/replay_gain_info.c \
src/replay_gain.c \
src/uri.c \
src/fd_util.c \
src/audio_check.c \
src/audio_format.c \
src/timer.c \
$(ARCHIVE_SRC) \
$(INPUT_SRC) \
@@ -907,50 +657,22 @@ test_read_tags_LDADD = $(MPD_LIBS) \
$(INPUT_LIBS) $(DECODER_LIBS) \
$(GLIB_LIBS)
test_read_tags_SOURCES = test/read_tags.c \
src/conf.c src/tokenizer.c src/utils.c src/log.c \
src/conf.c src/buffer2array.c src/utils.c src/log.c \
src/tag.c src/tag_pool.c \
src/replay_gain_info.c \
src/replay_gain.c \
src/uri.c \
src/fd_util.c \
src/audio_check.c \
src/timer.c \
$(ARCHIVE_SRC) \
$(INPUT_SRC) \
$(TAG_SRC) \
$(DECODER_SRC)
test_run_filter_CPPFLAGS = $(AM_CPPFLAGS)
test_run_filter_LDADD = $(MPD_LIBS) \
$(SAMPLERATE_LIBS) \
$(GLIB_LIBS)
test_run_filter_SOURCES = test/run_filter.c \
src/filter_plugin.c \
src/filter_registry.c \
src/conf.c src/tokenizer.c src/utils.c \
src/pcm_volume.c src/pcm_convert.c src/pcm_byteswap.c \
src/pcm_format.c src/pcm_channels.c src/pcm_dither.c \
src/pcm_pack.c \
src/pcm_resample.c src/pcm_resample_fallback.c \
src/audio_check.c \
src/audio_format.c \
src/audio_parser.c \
src/replay_gain_config.c \
src/replay_gain_info.c \
src/AudioCompress/compress.c \
$(FILTER_SRC)
if HAVE_LIBSAMPLERATE
test_run_filter_SOURCES += src/pcm_resample_libsamplerate.c
endif
if ENABLE_ENCODER
noinst_PROGRAMS += test/run_encoder
test_run_encoder_SOURCES = test/run_encoder.c \
src/conf.c src/tokenizer.c \
src/conf.c src/buffer2array.c \
src/utils.c \
src/tag.c src/tag_pool.c \
src/audio_check.c \
src/audio_format.c \
src/audio_parser.c \
$(ENCODER_SRC)
test_run_encoder_LDADD = $(MPD_LIBS) \
@@ -959,41 +681,11 @@ test_run_encoder_LDADD = $(MPD_LIBS) \
endif
test_software_volume_SOURCES = test/software_volume.c \
src/audio_check.c \
src/audio_parser.c \
src/pcm_volume.c
test_software_volume_LDADD = \
$(GLIB_LIBS)
test_run_normalize_SOURCES = test/run_normalize.c \
src/audio_check.c \
src/audio_parser.c \
src/AudioCompress/compress.c
test_run_normalize_LDADD = \
$(GLIB_LIBS)
test_run_convert_SOURCES = test/run_convert.c \
src/fifo_buffer.c \
src/audio_format.c \
src/audio_check.c \
src/audio_parser.c \
src/pcm_channels.c \
src/pcm_format.c \
src/pcm_pack.c \
src/pcm_dither.c \
src/pcm_byteswap.c \
src/pcm_resample.c \
src/pcm_resample_fallback.c \
src/pcm_convert.c
test_run_convert_CPPFLAGS = $(AM_CPPFLAGS) $(SAMPLERATE_CFLAGS)
test_run_convert_LDADD = \
$(SAMPLERATE_LIBS) \
$(GLIB_LIBS)
if HAVE_LIBSAMPLERATE
test_run_convert_SOURCES += src/pcm_resample_libsamplerate.c
endif
test_run_output_CPPFLAGS = $(AM_CPPFLAGS) \
$(ENCODER_CFLAGS) \
$(OUTPUT_CFLAGS)
@@ -1002,9 +694,7 @@ test_run_output_LDADD = $(MPD_LIBS) \
$(OUTPUT_LIBS) \
$(GLIB_LIBS)
test_run_output_SOURCES = test/run_output.c \
src/conf.c src/tokenizer.c src/utils.c src/log.c \
src/audio_check.c \
src/audio_format.c \
src/conf.c src/buffer2array.c src/utils.c src/log.c \
src/audio_parser.c \
src/timer.c \
src/tag.c src/tag_pool.c \
@@ -1015,20 +705,7 @@ test_run_output_SOURCES = test/run_output.c \
$(ENCODER_SRC) \
src/mixer_api.c \
src/mixer_control.c \
src/mixer_type.c \
$(MIXER_SRC) \
src/filter_plugin.c src/filter/chain_filter_plugin.c \
src/filter_config.c \
src/filter/autoconvert_filter_plugin.c \
src/filter/convert_filter_plugin.c \
src/filter/replay_gain_filter_plugin.c \
src/filter/normalize_filter_plugin.c \
src/filter/volume_filter_plugin.c \
src/pcm_volume.c \
src/AudioCompress/compress.c \
src/replay_gain_info.c \
src/replay_gain_config.c \
src/fd_util.c \
$(OUTPUT_SRC)
test_read_mixer_CPPFLAGS = $(AM_CPPFLAGS) \
@@ -1037,34 +714,10 @@ test_read_mixer_LDADD = $(MPD_LIBS) \
$(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/buffer2array.c src/utils.c src/log.c \
src/mixer_control.c src/mixer_api.c \
src/filter_plugin.c \
src/filter/volume_filter_plugin.c \
src/fd_util.c \
$(MIXER_SRC)
if ENABLE_BZIP2_TEST
TESTS += test/test_archive_bzip2.sh
endif
if ENABLE_ZZIP_TEST
TESTS += test/test_archive_zzip.sh
endif
if ENABLE_ISO9660_TEST
TESTS += test/test_archive_iso9660.sh
endif
if ENABLE_INOTIFY
noinst_PROGRAMS += test/run_inotify
test_run_inotify_SOURCES = test/run_inotify.c \
src/fd_util.c \
src/fifo_buffer.c \
src/inotify_source.c
test_run_inotify_LDADD = $(GLIB_LIBS)
endif
endif
@@ -1075,7 +728,7 @@ endif
man_MANS = doc/mpd.1 doc/mpd.conf.5
doc_DATA = AUTHORS COPYING NEWS README UPGRADING doc/mpdconf.example
DOCBOOK_FILES = doc/protocol.xml doc/user.xml doc/developer.xml
DOCBOOK_FILES = doc/protocol.xml doc/user.xml doc/developer.xml doc/sticker.xml
if ENABLE_DOCUMENTATION
protocoldir = $(docdir)/protocol
@@ -1102,8 +755,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
@@ -1113,12 +765,10 @@ clean-local:
install-data-local: doc/api/html/index.html
$(mkinstalldirs) $(DESTDIR)$(docdir)/api/html
$(INSTALL_DATA) -c -m 644 doc/api/html/*.* \
$(INSTALL_DATA) -c -m 644 doc/api/html/*.html doc/api/html/*.css \
doc/api/html/*.png doc/api/html/*.gif \
$(DESTDIR)$(docdir)/api/html
uninstall-local:
rm -f $(DESTDIR)$(docdir)/api/html/*.*
upload: $(DOCBOOK_HTML) doc/api/html/index.html
rsync -vpruz --delete doc/ cirrus@www.musicpd.org:/var/www/musicpd.org/www/doc/ \
--chmod=Dug+rwx,Do+rx,Fug+rw,Fo+r \

114
NEWS
View File

@@ -1,106 +1,20 @@
ver 0.16 (20??/??/??)
* protocol:
- send song modification time to client
- added "update" idle event
- removed the deprecated "volume" command
- added the "findadd" command
- range support for "delete"
- "previous" really plays the previous song
- "addid" with negative position is deprecated
- "load" supports remote playlists (extm3u, pls, asx, xspf, lastfm://)
- allow changing replay gain mode on-the-fly
- omitting the range end is possible
- "update" checks if the path is malformed
* archive:
- iso: renamed plugin to "iso9660"
- zip: renamed plugin to "zzip"
ver 0.15.14 (2010/11/06)
* player_thread: fix assertion failure due to wrong music pipe on seek
* output_thread: fix assertion failure due to race condition in OPEN
* input:
- lastfm: obsolete plugin removed
- ffmpeg: new input plugin using libavformat's "avio" library
* tags:
- added tags "ArtistSort", "AlbumArtistSort"
- id3: revised "performer" tag support
- ape: MusicBrainz tags
- rewind: fix double free bug
* decoders:
- don't try a plugin twice (MIME type & suffix)
- don't fall back to "mad" unless no plugin matches
- ffmpeg: support multiple tags
- ffmpeg: convert metadata to generic format
- ffmpeg: implement the libavutil log callback
- sndfile: new decoder plugin based on libsndfile
- flac: moved CUE sheet support to a playlist plugin
- flac: support streams without STREAMINFO block
- mikmod: sample rate is configurable
- mpg123: new decoder plugin based on libmpg123
- sidplay: support sub-tunes
- sidplay: implemented songlength database
- sidplay: support seeking
- wavpack: activate 32 bit support
- wavpack: allow more than 2 channels
- mp4ff: rename plugin "mp4" to "mp4ff"
* encoders:
- twolame: new encoder plugin based on libtwolame
- flac: new encoder plugin based on libFLAC
- wave: new encoder plugin for PCM WAV format
- mp4ff, ffmpeg: add extension ".m4b" (audio book)
ver 0.15.13 (2010/10/10)
* output_thread: fix race condition after CANCEL command
* output:
- recorder: new output plugin for recording radio streams
- alsa: don't recover on CANCEL
- alsa: fill period buffer with silence before draining
- openal: new output plugin
- pulse: announce "media.role=music"
- pulse: renamed context to "Music Player Daemon"
- pulse: connect to server on MPD startup, implement pause
- jack: require libjack 0.100
- jack: don't disconnect during pause
- jack: connect to server on MPD startup
- jack: added options "client_name", "server_name"
- jack: clear ring buffers before activating
- jack: renamed option "ports" to "destination_ports"
- jack: support more than two audio channels
- httpd: bind port when output is enabled
- httpd: added name/genre/website configuration
- oss: 24 bit support via OSS4
- win32: new output plugin for Windows Wave
- wildcards allowed in audio_format configuration
- consistently lock audio output objects
* player:
- drain audio outputs at the end of the playlist
* mixers:
- removed support for legacy mixer configuration
- reimplemented software volume as mixer+filter plugin
- per-device software/hardware mixer setting
* commands:
- added new "status" line with more precise "elapsed time"
* update:
- automatically update the database with Linux inotify
- support .mpdignore files in the music directory
- sort songs by album name first, then disc/track number
- rescan after metadata_to_use change
* normalize: upgraded to AudioCompress 2.0
- automatically convert to 16 bit samples
* replay gain:
- reimplemented as a filter plugin
- fall back to track gain if album gain is unavailable
- optionally use hardware mixer to apply replay gain
- added mode "auto"
* log unused/unknown block parameters
* removed the deprecated "error_file" option
* save state when stopped
* renamed option "--stdout" to "--stderr"
* removed options --create-db and --no-create-db
* state_file: save only if something has changed
* database: eliminated maximum line length
* log: redirect stdout/stderr to /dev/null if syslog is used
* set the close-on-exec flag on all file descriptors
* pcm_volume, pcm_mix: implemented 32 bit support
* support packed 24 bit samples
* CUE sheet support
* support for MixRamp tags
* obey $(sysconfdir) for default mpd.conf location
* build with large file support by default
* added test suite ("make check")
* require GLib 2.12
* added libwrap support
- httpd: fix random data in stream title
- httpd: MIME type audio/ogg for Ogg Vorbis
* input:
- rewind: update MIME not only once
- rewind: enable for MMS
ver 0.15.12 (2010/07/20)

View File

@@ -9,20 +9,20 @@ srcdir="`dirname $0`"
test -z "$srcdir" && srcdir=.
cd "$srcdir"
DIE=
AM_VERSIONGREP="sed -e s/.*[^0-9\.]\([0-9]\.[0-9][0-9]*\).*/\1/"
AM_VERSIONGREP="sed -e s/.*[^0-9\.]\([0-9]\.[0-9]\).*/\1/"
AC_VERSIONGREP="sed -e s/.*[^0-9\.]\([0-9]\.[0-9][0-9]\).*/\1/"
VERSIONMKINT="sed -e s/[^0-9]//"
if test -n "$AM_FORCE_VERSION"
then
AM_VERSIONS="$AM_FORCE_VERSION"
else
AM_VERSIONS='1.10'
AM_VERSIONS='1.9 1.10'
fi
if test -n "$AC_FORCE_VERSION"
then
AC_VERSIONS="$AC_FORCE_VERSION"
else
AC_VERSIONS='2.60 2.61'
AC_VERSIONS='2.58 2.59 2.60 2.61'
fi
versioned_bins ()

File diff suppressed because it is too large Load Diff

View File

@@ -534,7 +534,7 @@ WARN_LOGFILE =
# directories like "/usr/src/myproject". Separate the files or directories
# with spaces.
INPUT = src/
INPUT = src
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is

View File

@@ -25,11 +25,17 @@ Output a brief help message.
Kill the currently running mpd session. The pid_file parameter must be
specified in the config file for this to work.
.TP
.BI --create-db
Force (re)creation of database.
.TP
.BI --no-create-db
Do not create database, even if it doesn't exist.
.TP
.BI --no-daemon
Don't detach from console.
.TP
.BI --stderr
Print messages stderr.
.BI --stdout
Print messages to stdout and stderr.
.TP
.BI --verbose
Verbose logging.

View File

@@ -129,8 +129,6 @@ audio that is sent to each audio output. Note that audio outputs may specify
their own audio format which will be used for actual output to the audio
device. An example is "44100:16:2" for 44100Hz, 16 bits, and 2 channels. The
default is to use the audio format of the input file.
Any of the three attributes may be an asterisk to specify that this
attribute should not be enforced
.TP
.B samplerate_converter <integer or prefix>
This specifies the libsamplerate converter to use. The supplied value should
@@ -170,14 +168,31 @@ only choice) if MPD was compiled without libsamplerate.
For an up-to-date list of available converters, please see the libsamplerate
documentation (available online at <\fBhttp://www.mega-nerd.com/SRC/\fP>).
.TP
.B replaygain <off or album or track or auto>
.B mixer_type <alsa, oss, software, hardware or disabled>
This specifies which mixer to use. The default is hardware and depends on
what audio output support mpd was built with. Options alsa and oss are
legacy and should not be used in new configs, but when set mixer_device
and mixer_control will apply.
.TP
.B mixer_device <mixer dev>
This specifies which mixer to use. The default for oss is
"/dev/mixer"; the default for alsa is "default". This global option is
deprecated and should not be used. Look at the mixer_device option of
corresponding output device instead.
.TP
.B mixer_control <mixer ctrl>
This specifies which mixer control to use (sometimes referred to as
the "device"). Examples of mixer controls are PCM, Line1, Master,
etc. An example for OSS is "Pcm", and an example for alsa is
"PCM". This global option is deprecated and should not be used. Look
at the mixer_control option of corresponding output device instead.
.TP
.B replaygain <album or track>
If specified, mpd will adjust the volume of songs played using ReplayGain tags
(see <\fBhttp://www.replaygain.org/\fP>). Setting this to "album" will adjust
volume using the album's ReplayGain tags, while setting it to "track" will
adjust it using the track ReplayGain tags. "auto" uses the track ReplayGain
tags if random play is activated otherwise the album ReplayGain tags. Currently
only FLAC, Ogg Vorbis, Musepack, and MP3 (through ID3v2 ReplayGain tags, not
APEv2) are supported.
adjust it using the track ReplayGain tags. Currently only FLAC, Ogg Vorbis,
Musepack, and MP3 (through ID3v2 ReplayGain tags, not APEv2) are supported.
.TP
.B replaygain_preamp <-15 to 15>
This is the gain (in dB) applied to songs with ReplayGain tags.
@@ -250,15 +265,6 @@ tags may be specified as a comma separated list. An example value is
"artist,album,title,track". The special value "none" may be used alone to
disable all metadata. The default is to use all known tag types except for
comments.
.TP
.B auto_update <yes or no>
This specifies the wheter to support automatic update of music database when
files are changed in music_directory. The default is to disable autoupdate
of database.
.TP
.B auto_update_depth <N>
Limit the depth of the directories being watched, 0 means only watch
the music directory itself. There is no limit by default.
.SH REQUIRED AUDIO OUTPUT PARAMETERS
.TP
.B type <type>
@@ -274,25 +280,11 @@ This specifies the sample rate, bits per sample, and number of channels of
audio that is sent to the audio output device. See documentation for the
\fBaudio_output_format\fP parameter for more details. The default is to use
whatever audio format is passed to the audio output.
Any of the three attributes may be an asterisk to specify that this
attribute should not be enforced
.TP
.B replay_gain_handler <software, mixer or none>
Specifies how replay gain is applied. The default is "software",
which uses an internal software volume control. "mixer" uses the
configured (hardware) mixer control. "none" disables replay gain on
this audio output.
.SH OPTIONAL ALSA OUTPUT PARAMETERS
.TP
.B device <dev>
This specifies the device to use for audio output. The default is "default".
.TP
.B mixer_type <hardware, software or none>
Specifies which mixer should be used for this audio output: the
hardware mixer (available for ALSA, OSS and PulseAudio), the software
mixer or no mixer ("none"). By default, the hardware mixer is used
for devices which support it, and none for the others.
.TP
.B mixer_device <mixer dev>
This specifies which mixer to use. The default is "default". To use
the second sound card in a system, use "hw:1".
@@ -360,12 +352,13 @@ after another until it successfully establishes a connection.
.TP
.B sink <sink>
The sink to output to. The default is to let PulseAudio choose a sink.
.SH OPTIONAL JACK OUTPUT PARAMETERS
.SH REQUIRED JACK OUTPUT PARAMETERS
.TP
.B client_name <name>
.B name <name>
The client name to use when connecting to JACK. The output ports <name>:left
and <name>:right will also be created for the left and right channels,
respectively.
.SH OPTIONAL JACK OUTPUT PARAMETERS
.TP
.B ports <left_port,right_port>
This specifies the left and right ports to connect to for the left and right

View File

@@ -49,11 +49,6 @@
#
#state_file "~/.mpd/state"
#
# The location of the sticker database. This is a database which
# manages dynamic information attached to songs.
#
#sticker_file "~/.mpd/sticker.sql"
#
###############################################################################
@@ -66,13 +61,6 @@
#
#user "nobody"
#
# This setting specifies the group that MPD will run as. If not specified
# primary group of user specified with "user" setting will be used (if set).
# This is useful if MPD needs to be a member of group such as "audio" to
# have permission to use sound card.
#
#group "nogroup"
#
# This setting sets the address for the daemon to listen on. Careful attention
# should be paid if this is assigned to anything other then the default, any.
# This setting can deny access to control of the daemon.
@@ -114,16 +102,6 @@
#
#metadata_to_use "artist,album,title,track,name,genre,date,composer,performer,disc"
#
# This setting enables automatic update of MPD's database when files in
# music_directory are changed.
#
#auto_update "yes"
#
# Limit the depth of the directories being watched, 0 means only watch
# the music directory itself. There is no limit by default.
#
#auto_update_depth "3"
#
###############################################################################
@@ -201,7 +179,6 @@ input {
# name "My ALSA Device"
## device "hw:0,0" # optional
## format "44100:16:2" # optional
## mixer_type "hardware" # optional
## mixer_device "default" # optional
## mixer_control "PCM" # optional
## mixer_index "0" # optional
@@ -214,7 +191,6 @@ input {
# name "My OSS Device"
## device "/dev/dsp" # optional
## format "44100:16:2" # optional
## mixer_type "hardware" # optional
## mixer_device "/dev/mixer" # optional
## mixer_control "PCM" # optional
#}
@@ -238,19 +214,6 @@ input {
## genre "jazz" # optional
## public "no" # optional
## timeout "2" # optional
## mixer_type "software" # optional
#}
#
# An example of a recorder output:
#
#audio_output {
# type "recorder"
# name "My recorder"
# encoder "vorbis" # optional, vorbis or lame
# path "/var/lib/mpd/recorder/mpd.ogg"
## quality "5.0" # do not define if bitrate is defined
# bitrate "128" # do not define if quality is defined
# format "44100:16:1"
#}
#
# An example of a httpd output (built-in HTTP streaming server):
@@ -263,7 +226,6 @@ input {
## quality "5.0" # do not define if bitrate is defined
# bitrate "128" # do not define if quality is defined
# format "44100:16:1"
# max_clients "0" # optional 0=no limit
#}
#
# An example of a pulseaudio output (streaming to a remote pulseaudio server)
@@ -293,7 +255,6 @@ input {
#audio_output {
# type "null"
# name "My Null Output"
# mixer_type "none" # optional
#}
#
# This setting will change all decoded audio to be converted to the specified
@@ -312,11 +273,38 @@ input {
###############################################################################
# Volume control mixer ########################################################
#
# These are the global volume control settings. By default, this setting will
# be detected to the available audio output device, with preference going to
# hardware mixing. Hardware and software mixers for individual audio_output
# sections cannot yet be mixed.
#
# An example for controlling an ALSA, OSS or Pulseaudio mixer; If this
# setting is used other sound applications will be affected by the volume
# being controlled by MPD.
#
#mixer_type "hardware"
#
# An example for controlling all mixers through software. This will control
# all controls, even if the mixer is not supported by the device and will not
# affect any other sound producing applications.
#
#mixer_type "software"
#
# This example will not allow MPD to touch the mixer at all and will disable
# all volume controls.
#
#mixer_type "disabled"
#
###############################################################################
# Normalization automatic volume adjustments ##################################
#
# This setting specifies the type of ReplayGain to use. This setting can have
# the argument "off", "album" or "track". See <http://www.replaygain.org>
# for more details. This setting is off by default.
# the argument "album" or "track". See <http://www.replaygain.org> for more
# details. This setting is disabled by default.
#
#replaygain "album"
#
@@ -369,7 +357,8 @@ input {
# Character Encoding ##########################################################
#
# If file or directory names do not display correctly for your locale then you
# may need to modify this setting.
# may need to modify this setting. After modification of this setting mpd
# --create-db must be run to change the database.
#
#filesystem_charset "UTF-8"
#
@@ -378,29 +367,3 @@ input {
#id3v1_encoding "ISO-8859-1"
#
###############################################################################
# SIDPlay decoder #############################################################
#
# songlength_database:
# Location of your songlengths file, as distributed with the HVSC.
# The sidplay plugin checks this for matching MD5 fingerprints.
# See http://www.c64.org/HVSC/DOCUMENTS/Songlengths.faq
#
# default_songlength:
# This is the default playing time in seconds for songs not in the
# songlength database, or in case you're not using a database.
# A value of 0 means play indefinitely.
#
# filter:
# Turns the SID filter emulation on or off.
#
#decoder {
# plugin "sidplay"
# songlength_database "/media/C64Music/DOCUMENTS/Songlengths.txt"
# default_songlength "120"
# filter "true"
#}
#
###############################################################################

View File

@@ -67,20 +67,6 @@
successful command executed in the command list.
</para>
</section>
<section>
<title>Ranges</title>
<para>
Some commands (e.g. <link
linkend="command_delete"><command>delete</command></link>)
allow specifying a range in the form
<parameter>START:END</parameter> (the <varname>END</varname>
item is not included in the range, similar to ranges in the
Python programming language). If <varname>END</varname> is
omitted, then the maximum possible value is assumed.
</para>
</section>
</chapter>
<chapter>
@@ -137,7 +123,7 @@
</term>
<listitem>
<para>
<footnote id="since_0_14"><simpara>Introduced with MPD 0.14</simpara></footnote>
<footnote id="since_0_14"><simpara>Since MPD 0.14</simpara></footnote>
Waits until there is a noteworthy change in one or more
of MPD's subsystems. As soon as there is one, it lists
all changed systems in a line in the format
@@ -151,15 +137,6 @@
has been modified after <command>update</command>.
</para>
</listitem>
<listitem>
<para>
<returnvalue>update</returnvalue>: a database update
has started or finished. If the database was
modified during the update, the
<returnvalue>database</returnvalue> event is also
emitted.
</para>
</listitem>
<listitem>
<para>
<returnvalue>stored_playlist</returnvalue>: a stored
@@ -195,7 +172,7 @@
<para>
<returnvalue>options</returnvalue>: options like
<option>repeat</option>, <option>random</option>,
<option>crossfade</option>, replay gain
<option>crossfade</option>
</para>
</listitem>
</itemizedlist>
@@ -214,6 +191,9 @@
MPD will only send notifications when something changed in
one of the specified subsytems.
</para>
<simpara>
Since <application>MPD</application> 0.14
</simpara>
</listitem>
</varlistentry>
<varlistentry id="command_status">
@@ -243,15 +223,15 @@
<listitem>
<para>
<varname>single</varname>:
<footnote id="since_0_15"><simpara>Introduced with MPD 0.15</simpara></footnote>
<returnvalue>0 or 1</returnvalue>
<footnote id="since_0_15"><simpara>Since MPD 0.15</simpara></footnote>
</para>
</listitem>
<listitem>
<para>
<varname>consume</varname>:
<footnoteref linkend="since_0_15"/>
<returnvalue>0 or 1</returnvalue>
<footnoteref linkend="since_0_15"/>
</para>
</listitem>
<listitem>
@@ -315,16 +295,6 @@
playing/paused song)</returnvalue>
</para>
</listitem>
<listitem>
<para>
<varname>elapsed</varname>:
<footnote id="since_0_16"><simpara>Introduced with MPD 0.16</simpara></footnote>
<returnvalue>
Total time elapsed within the current song, but
with higher resolution.
</returnvalue>
</para>
</listitem>
<listitem>
<para>
<varname>bitrate</varname>:
@@ -338,18 +308,6 @@
<returnvalue>crossfade in seconds</returnvalue>
</para>
</listitem>
<listitem>
<para>
<varname>mixrampdb</varname>:
<returnvalue>mixramp threshold in dB</returnvalue>
</para>
</listitem>
<listitem>
<para>
<varname>mixrampdelay</varname>:
<returnvalue>mixrampdelay in seconds</returnvalue>
</para>
</listitem>
<listitem>
<para>
<varname>audio</varname>:
@@ -454,32 +412,6 @@
</para>
</listitem>
</varlistentry>
<varlistentry id="command_mixrampdb">
<term>
<cmdsynopsis>
<command>mixrampdb</command>
<arg choice="req"><replaceable>deciBels</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
<para>
Sets the threshold at which songs will be overlapped. Like crossfading but doesn't fade the track volume, just overlaps. The songs need to have MixRamp tags added by an external tool. 0dB is the normalized maximum volume so use negative values, I prefer -17dB. In the absence of mixramp tags crossfading will be used. See http://sourceforge.net/projects/mixramp
</para>
</listitem>
</varlistentry>
<varlistentry id="command_mixrampdelay">
<term>
<cmdsynopsis>
<command>mixrampdelay</command>
<arg choice="req"><replaceable>SECONDS</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
<para>
Additional time subtracted from the overlap calculated by mixrampdb. A value of "nan" disables MixRamp overlapping and falls back to crossfading.
</para>
</listitem>
</varlistentry>
<varlistentry id="command_random">
<term>
<cmdsynopsis>
@@ -539,43 +471,23 @@
</para>
</listitem>
</varlistentry>
<varlistentry id="command_replay_gain_mode">
<varlistentry id="command_volume">
<term>
<cmdsynopsis>
<command>replay_gain_mode</command>
<arg choice="req"><replaceable>MODE</replaceable></arg>
<command>volume</command>
<arg choice="req"><replaceable>CHANGE</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
<para>
Sets the replay gain mode. One of
<parameter>off</parameter>,
<parameter>track</parameter>,
<parameter>album</parameter>.
</para>
<para>
Changing the mode during playback may take several
seconds, because the new settings does not affect the
buffered data.
</para>
<para>
This command triggers the
<returnvalue>options</returnvalue> idle event.
</para>
</listitem>
</varlistentry>
<varlistentry id="command_replay_gain_status">
<term>
<cmdsynopsis>
<command>replay_gain_status</command>
</cmdsynopsis>
</term>
<listitem>
<para>
Prints replay gain options. Currently, only the
variable <varname>replay_gain_mode</varname> is
returned.
Changes volume by amount <varname>CHANGE</varname>.
</para>
<note>
<para>
<command>volume</command> is deprecated, use
<command>setvol</command> instead.
</para>
</note>
</listitem>
</varlistentry>
</variablelist>
@@ -736,7 +648,10 @@
</para>
<para>
<varname>URI</varname> is always a single file or
URL. For example:
URL. <varname>POSITION</varname> is optional, a
negative number means it is relative to the currently
playing song in the playlist (if there is one).
For example:
</para>
<screen>
addid "foo.mp3"
@@ -761,10 +676,7 @@ OK
<term>
<cmdsynopsis>
<command>delete</command>
<group>
<arg choice="req"><replaceable>POS</replaceable></arg>
<arg choice="req"><replaceable>START:END</replaceable></arg>
</group>
<arg choice="req"><replaceable>SONGPOS</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
@@ -998,20 +910,6 @@ OK
<section>
<title>Stored playlists</title>
<para>
Playlists are stored inside the configured playlist directory.
They are addressed with their file name (without the directory
and without the <filename>.m3u</filename> suffix).
</para>
<para>
Some of the commands described in this section can be used to
run playlist plugins instead of the hard-coded simple
<filename>m3u</filename> parser. They can access playlists in
the music directory (relative path including the suffix) or
remote playlists (absolute URI with a supported scheme).
</para>
<variablelist>
<varlistentry id="command_listplaylist">
<term>
@@ -1022,8 +920,8 @@ OK
</term>
<listitem>
<para>
Lists the songs in the playlist. Playlist plugins are
supported.
Lists the files in the playlist
<filename>NAME.m3u</filename>.
</para>
</listitem>
</varlistentry>
@@ -1036,8 +934,7 @@ OK
</term>
<listitem>
<para>
Lists the songs with metadata in the playlist. Playlist
plugins are supported.
Lists songs in the playlist <filename>NAME.m3u</filename>.
</para>
</listitem>
</varlistentry>
@@ -1069,8 +966,8 @@ OK
</term>
<listitem>
<para>
Loads the playlist into the current queue. Playlist
plugins are supported.
Loads the playlist <filename>NAME.m3u</filename> from
the playlist directory.
</para>
</listitem>
</varlistentry>
@@ -1221,23 +1118,6 @@ OK
</para>
</listitem>
</varlistentry>
<varlistentry id="command_findadd">
<term>
<cmdsynopsis>
<command>findadd</command>
<arg choice="req"><replaceable>TYPE</replaceable></arg>
<arg choice="req"><replaceable>WHAT</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
<para>
Finds songs in the db that are exactly
<varname>WHAT</varname> and adds them to current playlist.
<varname>TYPE</varname> can be any tag supported by MPD.
<varname>WHAT</varname> is what to find.
</para>
</listitem>
</varlistentry>
<varlistentry id="command_list">
<term>
<cmdsynopsis>
@@ -1351,20 +1231,6 @@ OK
</para>
</listitem>
</varlistentry>
<varlistentry id="command_rescan">
<term>
<cmdsynopsis>
<command>rescan</command>
<arg choice="opt"><replaceable>URI</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
<para>
Same as <command>update</command>, but also rescans
unmodified files.
</para>
</listitem>
</varlistentry>
</variablelist>
</section>
@@ -1642,25 +1508,6 @@ OK
</para>
</listitem>
</varlistentry>
<varlistentry id="command_decoders">
<term>
<cmdsynopsis>
<command>decoders</command>
</cmdsynopsis>
</term>
<listitem>
<para>
Print a list of decoder plugins, followed by their
supported suffixes and MIME types. Example response:
</para>
<programlisting>plugin: mad
suffix: mp3
suffix: mp2
mime_type: audio/mpeg
plugin: mpcdec
suffix: mpc</programlisting>
</listitem>
</varlistentry>
</variablelist>
</section>
</chapter>

91
doc/sticker.xml Normal file
View File

@@ -0,0 +1,91 @@
<?xml version='1.0' encoding="utf-8"?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"docbook/dtd/xml/4.2/docbookx.dtd">
<book>
<title>The Music Player Daemon Sticker Database</title>
<chapter>
<title>Introduction to MPD's Sticker Database</title>
<para>
This document shell give a short guideline for recommended tags
for use in MPD's Sticker Database.
MPD's Sticker Database is a subsystem that enables users to add
custom tags. MPD does not alter the media files.
</para>
</chapter>
<chapter>
<title>Guideline for recommended tags</title>
<para>
Since there is no standard for tags in media files, this
document is trying to give you some help deciding what tags to
use. The selection of these tags tries to cover the most
widely used tags. This way the tags might still work in other
players, if you sync the database with your original media
files.
Keep in mind that we stick with lower case tags with underscores
instead of spaces. If there will be a Sync tool in future
its easy to change this on the fly, if needed.
</para>
<variablelist>
<varlistentry>
<term><varname>rating</varname></term>
<listitem>
<para>
Will store a rating value from 1 (worst) to 5 (best) for a
given song.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>album_rating</varname></term>
<listitem>
<para>
Will store a rating value from 1 (worst) to 5 (best) for a
given album.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>style</varname></term>
<listitem>
<para>
This tag is used to keep the Genre tag clean, by now
having 1000's of genres. Instead you define a Main Genre
for each file and can make a more specific
description. This should be one Keyword like "Post Punk"
or "Progressive Death Metal" An Alternative name for this
tag is "Subgenre", time will tell which one gets more
support.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>lyrics</varname></term>
<listitem>
<para>
This one is self explaining. This gives the option to
store lyrics of a song where they belong to: mapped to the
song
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>similar_artists</varname> (Comma seperated list of artists)</term>
<listitem>
<para>
This tag enables a last.fm alike aproach which will still
work when being offline Keep in mind, that this tag is
absolutely non-standard! I am not aware of any other
player that uses a comparable tag.
</para>
</listitem>
</varlistentry>
</variablelist>
</chapter>
</book>

View File

@@ -300,30 +300,10 @@ cd mpd-version</programlisting>
<varname>format</varname>
</entry>
<entry>
<para>
Always open the audio output with the specified audio
format (samplerate:bits:channels), regardless of the
format of the input file. This is optional for most
plugins.
</para>
<para>
Any of the three attributes may be an asterisk to
specify that this attribute should not be enforced,
example: <parameter>48000:16:*</parameter>.
<parameter>*:*:*</parameter> is equal to not having
a <varname>format</varname> specification.
</para>
<para>
The following values are valid for
<varname>bits</varname>: <varname>8</varname>
(signed 8 bit integer samples),
<varname>16</varname>, <varname>24</varname> (signed
24 bit integer samples padded to 32 bit),
<varname>24_3</varname> (signed 24 bit integer
samples, no padding, 3 bytes per sample),
<varname>32</varname> (signed 32 bit integer
samples).
</para>
Always open the audio output with the specified audio
format (samplerate:bits:channels), regardless of the
format of the input file. This is optional for most
plugins.
</entry>
</row>
<row>
@@ -339,159 +319,13 @@ cd mpd-version</programlisting>
</row>
<row>
<entry>
<varname>always_on</varname>
<parameter>yes|no</parameter>
</entry>
<entry>
If set to "yes", then MPD attempts to keep this audio
output always open. This may be useful for streaming
servers, when you don't want to disconnect all
listeners even when playback is accidently stopped.
</entry>
</row>
<row>
<entry>
<varname>mixer_type</varname>
<parameter>hardware|software|none</parameter>
</entry>
<entry>
Specifies which mixer should be used for this audio
output: the hardware mixer (available for ALSA, OSS
and PulseAudio), the software mixer or no mixer
("none"). By default, the hardware mixer is used for
devices which support it, and none for the others.
</entry>
</row>
<row>
<entry>
<varname>replay_gain_handler</varname>
<parameter>software|mixer|none</parameter>
</entry>
<entry>
Specifies how replay gain is applied. The default is
"software", which uses an internal software volume
control. "mixer" uses the configured (hardware) mixer
control. "none" disables replay gain on this audio
output.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</section>
<section>
<title>Configuring filters</title>
<para>
Filters are plugins which modify an audio stream.
</para>
<para>
To configure a filter, add a <varname>filter</varname> block
to <filename>mpd.conf</filename>:
</para>
<programlisting>filter {
plugin "volume"
name "software volume"
}
</programlisting>
<para>
The following table lists the <varname>filter</varname>
options valid for all plugins:
</para>
<informaltable>
<tgroup cols="2">
<thead>
<row>
<entry>
Name
</entry>
<entry>
Description
</entry>
</row>
</thead>
<tbody>
<row>
<entry>
<varname>plugin</varname>
</entry>
<entry>
The name of the plugin.
</entry>
</row>
<row>
<entry>
<varname>name</varname>
</entry>
<entry>
The name of the filter.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</section>
<section>
<title>Configuring playlist plugins</title>
<para>
Playlist plugins are used to load remote playlists. This is
not related to MPD's playlist directory.
</para>
<para>
To configure a filter, add a
<varname>playlist_plugin</varname> block to
<filename>mpd.conf</filename>:
</para>
<programlisting>playlist_plugin {
name "m3u"
enabled "true"
}
</programlisting>
<para>
The following table lists the
<varname>playlist_plugin</varname> options valid for all
plugins:
</para>
<informaltable>
<tgroup cols="2">
<thead>
<row>
<entry>
Name
</entry>
<entry>
Description
</entry>
</row>
</thead>
<tbody>
<row>
<entry>
<varname>name</varname>
</entry>
<entry>
The name of the plugin.
</entry>
</row>
<row>
<entry>
<varname>enabled</varname>
<varname>mixer_enabled</varname>
<parameter>yes|no</parameter>
</entry>
<entry>
Allows you to disable a input plugin without
recompiling. By default, all plugins are enabled.
Specifies whether the hardware mixer of this audio
output should be used. By default, all hardware
mixers are enabled if available.
</entry>
</row>
</tbody>
@@ -500,68 +334,6 @@ cd mpd-version</programlisting>
</section>
</chapter>
<chapter>
<title>Using MPD</title>
<section>
<title>The client</title>
<para>
After you have installed, configured and started MPD, you
choose a client to control the playback.
</para>
<para>
The most basic client is <filename>mpc</filename>, which
provides a command line interface. It is useful in shell
scripts. Many people bind specific <filename>mpc</filename>
commands to hotkeys.
</para>
<para>
The <ulink url="http://mpd.wikia.com/wiki/Clients">MPD
Wiki</ulink> contains an extensive list of clients to choose
from.
</para>
</section>
<section>
<title>The music directory and the database</title>
<para>
The "music directory" is where you store your music files.
MPD stores all relevant meta information about all songs in
its "database". Whenever you add, modify or remove songs in
the music directory, you have to update the database, for
example with <filename>mpc</filename>:
</para>
<programlisting>mpc update</programlisting>
<para>
Depending on the size of your music collection and the speed
of the storage, this can take a while.
</para>
<para>
To exclude a file from the update, create a file called
<filename>.mpdignore</filename> in its parent directory. Each
line of that file may contain a list of shell wildcards.
</para>
</section>
<section>
<title>The queue</title>
<para>
The queue (sometimes called "current playlist") is a list of
songs to be played by MPD. To play a song, add it to the
queue and start playback. Most clients offer an interface to
edit the queue.
</para>
</section>
</chapter>
<chapter>
<title>Plugin reference</title>
@@ -614,6 +386,15 @@ cd mpd-version</programlisting>
</para>
</section>
<section>
<title><varname>lastfm</varname></title>
<para>
Plays last.fm radio. This plugin is experimental, and will
be superseded by a better solution in MPD 0.16.
</para>
</section>
<section>
<title><varname>mms</varname></title>
@@ -623,40 +404,6 @@ cd mpd-version</programlisting>
</section>
</section>
<section>
<title>Decoder plugins</title>
<section>
<title><varname>mikmod</varname></title>
<para>
Module player based on MikMod.
</para>
<informaltable>
<tgroup cols="2">
<thead>
<row>
<entry>Setting</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>
<varname>sample_rate</varname>
</entry>
<entry>
Sets the sample rate generated by
<filename>libmikmod</filename>. Default is 44100.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</section>
</section>
<section>
<title>Output plugins</title>
@@ -795,81 +542,6 @@ cd mpd-version</programlisting>
The <varname>jack</varname> plugin connects to a JACK
server.
</para>
<informaltable>
<tgroup cols="2">
<thead>
<row>
<entry>Setting</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>
<varname>client_name</varname>
<parameter>NAME</parameter>
</entry>
<entry>
The name of the JACK client. Defaults to "Music
Player Daemon".
</entry>
</row>
<row>
<entry>
<varname>server_name</varname>
<parameter>NAME</parameter>
</entry>
<entry>
Optional name of the JACK server.
</entry>
</row>
<row>
<entry>
<varname>autostart</varname>
<parameter>yes|no</parameter>
</entry>
<entry>
If set to <parameter>yes</parameter>, then
<filename>libjack</filename> will automatically
launch the JACK daemon. Disabled by default.
</entry>
</row>
<row>
<entry>
<varname>source_ports</varname>
<parameter>A,B</parameter>
</entry>
<entry>
The names of the JACK source ports to be created.
By default, the ports "left" and "right" are
created. To use more ports, you have to tweak this
option.
</entry>
</row>
<row>
<entry>
<varname>destination_ports</varname>
<parameter>A,B</parameter>
</entry>
<entry>
The names of the JACK destination ports to connect to.
</entry>
</row>
<row>
<entry>
<varname>ringbuffer_size</varname>
<parameter>NBYTES</parameter>
</entry>
<entry>
Sets the size of the ring buffer for each channel.
Do not configure this value unless you know what
you're doing.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</section>
<section>
@@ -948,16 +620,6 @@ cd mpd-version</programlisting>
second.
</entry>
</row>
<row>
<entry>
<varname>max_clients</varname>
<parameter>MC</parameter>
</entry>
<entry>
Sets a limit, number of concurrent clients. When set
to 0 no limit will apply.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
@@ -1031,39 +693,6 @@ cd mpd-version</programlisting>
</informaltable>
</section>
<section>
<title><varname>openal</varname></title>
<para>
The "OpenAL" plugin uses <filename>libopenal</filename>.
It is supported on many platforms.
</para>
<informaltable>
<tgroup cols="2">
<thead>
<row>
<entry>Setting</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>
<varname>device</varname>
<parameter>NAME</parameter>
</entry>
<entry>
Sets the device which should be used. This can be
any valid OpenAL device name. If not specified, then
<filename>libopenal</filename> will choose a default device.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</section>
<section>
<title><varname>osx</varname></title>
@@ -1146,73 +775,6 @@ cd mpd-version</programlisting>
</informaltable>
</section>
<section>
<title><varname>recorder</varname></title>
<para>
The <varname>recorder</varname> plugin writes the audio
played by MPD to a file. This may be useful for recording
radio streams.
</para>
<para>
You must configure either <varname>quality</varname> or
<varname>bitrate</varname>.
</para>
<informaltable>
<tgroup cols="2">
<thead>
<row>
<entry>Setting</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>
<varname>path</varname>
<parameter>P</parameter>
</entry>
<entry>
Write to this file.
</entry>
</row>
<row>
<entry>
<varname>encoder</varname>
<parameter>NAME</parameter>
</entry>
<entry>
Chooses an encoder plugin,
e.g. <parameter>vorbis</parameter>.
</entry>
</row>
<row>
<entry>
<varname>quality</varname>
<parameter>Q</parameter>
</entry>
<entry>
Configures the encoder quality (for VBR) in the
range -1 .. 10.
</entry>
</row>
<row>
<entry>
<varname>bitrate</varname>
<parameter>BR</parameter>
</entry>
<entry>
Sets a constant encoder bit rate, in kilobit per
second.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</section>
<section>
<title><varname>shout</varname></title>
@@ -1377,81 +939,5 @@ cd mpd-version</programlisting>
</informaltable>
</section>
</section>
<section>
<title>Playlist plugins</title>
<section>
<title><varname>lastfm</varname></title>
<para>
Plays last.fm radio.
</para>
<informaltable>
<tgroup cols="2">
<thead>
<row>
<entry>Setting</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>
<varname>user</varname>
<parameter>USERNAME</parameter>
</entry>
<entry>
The last.fm user name.
</entry>
</row>
<row>
<entry>
<varname>password</varname>
<parameter>PWD</parameter>
</entry>
<entry>
The last.fm password.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</section>
<section>
<title><varname>m3u</varname></title>
<para>
Reads <filename>.m3u</filename> playlist files.
</para>
</section>
<section>
<title><varname>extm3u</varname></title>
<para>
Reads extended <filename>.m3u</filename> playlist files.
</para>
</section>
<section>
<title><varname>pls</varname></title>
<para>
Reads <filename>.pls</filename> playlist files.
</para>
</section>
<section>
<title><varname>xspf</varname></title>
<para>
Reads <ulink url="http://www.xspf.org/">XSPF</ulink>
playlist files.
</para>
</section>
</section>
</chapter>
</book>

111
m4/lame.m4 Normal file
View File

@@ -0,0 +1,111 @@
dnl borrowed from oddsock.org
dnl AM_PATH_LAME([ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]])
dnl Test for liblame, and define LAME_CFLAGS and LAME_LIBS
dnl
AC_DEFUN([AM_PATH_LAME],
[dnl
dnl Get the cflags and libraries
dnl
AC_ARG_WITH(lame,
AS_HELP_STRING([--with-lame=PFX],
[prefix where liblame is installed (optional)]),,
lame_prefix="")
AC_ARG_WITH(lame-libraries,
AS_HELP_STRING([--with-lame-libraries=DIR],
[directory where liblame library is installed (optional)]),,
lame_libraries="")
AC_ARG_WITH(lame-includes,
AS_HELP_STRING([--with-lame-includes=DIR],
[directory where liblame header files are installed (optional)]),,
lame_includes="")
if test "x$lame_prefix" != "xno" ; then
if test "x$lame_libraries" != "x" ; then
LAME_LIBS="-L$lame_libraries"
elif test "x$lame_prefix" != "x" ; then
LAME_LIBS="-L$lame_prefix/lib"
elif test "x$prefix" != "xNONE" ; then
LAME_LIBS="-L$prefix/lib"
fi
LAME_LIBS="$LAME_LIBS -lmp3lame -lm"
if test "x$lame_includes" != "x" ; then
LAME_CFLAGS="-I$lame_includes"
elif test "x$lame_prefix" != "x" ; then
LAME_CFLAGS="-I$lame_prefix/include"
elif test "x$prefix" != "xNONE"; then
LAME_CFLAGS="-I$prefix/include"
fi
AC_MSG_CHECKING(for liblame)
no_lame=""
ac_save_CFLAGS="$CFLAGS"
ac_save_LIBS="$LIBS"
CFLAGS="$CFLAGS $LAME_CFLAGS"
LIBS="$LIBS $LAME_LIBS"
dnl
dnl Now check if the installed liblame is sufficiently new.
dnl
rm -f conf.lametest
AC_TRY_RUN([
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <lame/lame.h>
int main ()
{
system("touch conf.lametest");
return 0;
}
],, no_lame=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"])
CFLAGS="$ac_save_CFLAGS"
LIBS="$ac_save_LIBS"
fi
if test "x$no_lame" = "x" ; then
AC_MSG_RESULT(yes)
ifelse([$1], , :, [$1])
else
AC_MSG_RESULT(no)
if test -f conf.lametest ; then
:
else
echo "*** Could not run liblame test program, checking why..."
CFLAGS="$CFLAGS $LAME_CFLAGS"
LIBS="$LIBS $LAME_LIBS"
AC_TRY_LINK([
#include <stdio.h>
#include <lame/lame.h>
], [ return 0; ],
[ echo "*** The test program compiled, but did not run. This usually means"
echo "*** that the run-time linker is not finding liblame or finding the wrong"
echo "*** version of liblame. If it is not finding liblame, you'll need to set your"
echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point"
echo "*** to the installed location Also, make sure you have run ldconfig if that"
echo "*** is required on your system"
echo "***"
echo "*** If you have an old version installed, it is best to remove it, although"
echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"],
[ echo "*** The test program failed to compile or link. See the file config.log for the"
echo "*** exact error that occured. This usually means liblame was incorrectly installed"
echo "*** or that you have moved liblame since it was installed." ])
CFLAGS="$ac_save_CFLAGS"
LIBS="$ac_save_LIBS"
fi
LAME_CFLAGS=""
LAME_LIBS=""
ifelse([$2], , :, [$2])
fi
AC_DEFINE(HAVE_LAME, 1, [Define if you have liblame.])
use_lame="1"
AC_SUBST(LAME_CFLAGS)
AC_SUBST(LAME_LIBS)
rm -f conf.lametest
])

116
m4/libOggFLAC.m4 Normal file
View File

@@ -0,0 +1,116 @@
# Configure paths for libOggFLAC
# "Inspired" by ogg.m4
dnl AM_PATH_LIBOGGFLAC([ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]])
dnl Test for libOggFLAC, and define LIBOGGFLAC_CFLAGS and LIBOGGFLAC_LIBS
dnl
AC_DEFUN([AM_PATH_LIBOGGFLAC],
[dnl
dnl Get the cflags and libraries
dnl
AC_ARG_WITH(libOggFLAC,
AS_HELP_STRING([--with-libOggFLAC=PFX],
[prefix where libOggFLAC is installed (optional)]),,
libOggFLAC_prefix="")
AC_ARG_WITH(libOggFLAC-libraries,
AS_HELP_STRING([--with-libOggFLAC-libraries=DIR],
[directory where libOggFLAC library is installed (optional)]),,
libOggFLAC_libraries="")
AC_ARG_WITH(libOggFLAC-includes,
AS_HELP_STRING([--with-libOggFLAC-includes=DIR],
[directory where libOggFLAC header files are installed (optional)]),,
libOggFLAC_includes="")
AC_ARG_ENABLE(libOggFLACtest,
AS_HELP_STRING([--disable-libOggFLACtest],
[do not try to compile and run a test libOggFLAC program]),,
enable_libOggFLACtest=yes)
if test "x$libOggFLAC_libraries" != "x" ; then
LIBOGGFLAC_LIBS="-L$libOggFLAC_libraries"
elif test "x$libOggFLAC_prefix" != "x" ; then
LIBOGGFLAC_LIBS="-L$libOggFLAC_prefix/lib"
elif test "x$prefix" != "xNONE" ; then
LIBOGGFLAC_LIBS="-L$libdir"
fi
LIBOGGFLAC_LIBS="$LIBOGGFLAC_LIBS -lOggFLAC -lFLAC -lm"
if test "x$libOggFLAC_includes" != "x" ; then
LIBOGGFLAC_CFLAGS="-I$libOggFLAC_includes"
elif test "x$libOggFLAC_prefix" != "x" ; then
LIBOGGFLAC_CFLAGS="-I$libOggFLAC_prefix/include"
elif test "x$prefix" != "xNONE"; then
LIBOGGFLAC_CFLAGS="-I$prefix/include"
fi
AC_MSG_CHECKING(for libOggFLAC)
no_libOggFLAC=""
if test "x$enable_libOggFLACtest" = "xyes" ; then
ac_save_CFLAGS="$CFLAGS"
ac_save_CXXFLAGS="$CXXFLAGS"
ac_save_LIBS="$LIBS"
CFLAGS="$CFLAGS $LIBOGGFLAC_CFLAGS"
CXXFLAGS="$CXXFLAGS $LIBOGGFLAC_CFLAGS"
LIBS="$LIBS $LIBOGGFLAC_LIBS"
dnl
dnl Now check if the installed libOggFLAC is sufficiently new.
dnl
rm -f conf.libOggFLACtest
AC_TRY_RUN([
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <OggFLAC/stream_decoder.h>
int main ()
{
system("touch conf.libOggFLACtest");
return 0;
}
],, no_libOggFLAC=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"])
CFLAGS="$ac_save_CFLAGS"
LIBS="$ac_save_LIBS"
fi
if test "x$no_libOggFLAC" = "x" ; then
AC_MSG_RESULT(yes)
ifelse([$1], , :, [$1])
else
AC_MSG_RESULT(no)
if test -f conf.libOggFLACtest ; then
:
else
echo "*** Could not run libOggFLAC test program, checking why..."
CFLAGS="$CFLAGS $LIBOGGFLAC_CFLAGS"
LIBS="$LIBS $LIBOGGFLAC_LIBS"
AC_TRY_LINK([
#include <stdio.h>
#include <OggFLAC/stream_decoder.h>
], [ return 0; ],
[ echo "*** The test program compiled, but did not run. This usually means"
echo "*** that the run-time linker is not finding libOggFLAC or finding the wrong"
echo "*** version of libOggFLAC. If it is not finding libOggFLAC, you'll need to set your"
echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point"
echo "*** to the installed location Also, make sure you have run ldconfig if that"
echo "*** is required on your system"
echo "***"
echo "*** If you have an old version installed, it is best to remove it, although"
echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"],
[ echo "*** The test program failed to compile or link. See the file config.log for the"
echo "*** exact error that occured. This usually means libOggFLAC was incorrectly installed"
echo "*** or that you have moved libOggFLAC since it was installed. In the latter case, you"
echo "*** may want to edit the libOggFLAC-config script: $LIBOGGFLAC_CONFIG" ])
CFLAGS="$ac_save_CFLAGS"
LIBS="$ac_save_LIBS"
fi
LIBOGGFLAC_CFLAGS=""
LIBOGGFLAC_LIBS=""
ifelse([$2], , :, [$2])
fi
AC_SUBST(LIBOGGFLAC_CFLAGS)
AC_SUBST(LIBOGGFLAC_LIBS)
rm -f conf.libOggFLACtest
])

View File

@@ -1,14 +0,0 @@
dnl
dnl Usage:
dnl AC_CHECK_LIBWRAP([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
dnl
AC_DEFUN([AC_CHECK_LIBWRAP],[
AC_CHECK_HEADERS([tcpd.h],
AC_CHECK_LIB([wrap], [request_init],
[LIBWRAP_CFLAGS=""
LIBWRAP_LDFLAGS="-lwrap"
$1],
$2),
$2)
])

View File

@@ -1,19 +0,0 @@
AC_DEFUN([results], [
dnl This is a hack to allow "with" names, otherwise "enable".
num=`expr match $1 'with'`
if test "$num" != "0"; then
var="`echo '$'$1`"
else
var="`echo '$'enable_$1`"
fi
echo -n '('
if eval "test x$var = xyes"; then
echo -n '+'
elif test -n "$3" && eval "test x$var = x$3"; then
echo -n '+'
else
echo -n '-'
fi
echo -n "$2) "
])

View File

@@ -1,47 +0,0 @@
#!/usr/bin/env ruby
#
# This script verifies that every source includes config.h first.
# This is very important for consistent Large File Support.
#
def check_file(file)
first = true
file.each_line do |line|
if line =~ /^\#include\s+(\S+)/ then
if $1 == '"config.h"'
unless first
puts "#{file.path}: config.h included too late"
end
else
if first
puts "#{file.path}: config.h missing"
end
end
first = false
end
end
end
def check_path(path)
File.open(path) do |file|
check_file(file)
end
end
if ARGV.empty?
Dir["src/*.c"].each do |path|
check_path(path)
end
Dir["src/*/*.c"].each do |path|
check_path(path)
end
Dir["test/*.c"].each do |path|
check_path(path)
end
else
ARGV.each do |path|
check_path(path)
end
end

View File

@@ -18,7 +18,6 @@ test -x configure || NOCONFIGURE=1 ./autogen.sh
./configure --prefix=$PREFIX/full \
--disable-dependency-tracking --enable-debug --enable-werror \
--enable-un \
--enable-modplug \
--enable-ao --enable-mikmod --enable-mvp
$MAKE install
$MAKE distclean
@@ -48,7 +47,6 @@ $MAKE install
$MAKE distclean
# shout: ogg without mp3
# sndfile instead of modplug
./configure --prefix=$PREFIX/shout_ogg \
--disable-dependency-tracking --disable-debug --enable-werror \
--disable-tcp \
@@ -58,7 +56,6 @@ $MAKE distclean
--enable-shout-ogg --disable-shout-mp3 --disable-lame-encoder \
--disable-ffmpeg --disable-wavpack --disable-mpc --disable-aac \
--disable-flac --enable-vorbis --disable-oggflac --disable-audiofile \
--disable-modplug --enable-sndfile \
--with-zeroconf=no
$MAKE install
$MAKE distclean

View File

@@ -1,185 +0,0 @@
/* compress.c
* Compressor logic
*
* (c)2007 busybee (http://beesbuzz.biz/
* Licensed under the terms of the LGPL. See the file COPYING for details.
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "config.h"
#include "compress.h"
struct Compressor {
//! The compressor's preferences
struct CompressorConfig prefs;
//! History of the peak values
int *peaks;
//! History of the gain values
int *gain;
//! History of clip amounts
int *clipped;
unsigned int pos;
unsigned int bufsz;
};
struct Compressor *Compressor_new(unsigned int history)
{
struct Compressor *obj = malloc(sizeof(struct Compressor));
obj->prefs.target = TARGET;
obj->prefs.maxgain = GAINMAX;
obj->prefs.smooth = GAINSMOOTH;
obj->peaks = obj->gain = obj->clipped = NULL;
obj->bufsz = 0;
obj->pos = 0;
Compressor_setHistory(obj, history);
return obj;
}
void Compressor_delete(struct Compressor *obj)
{
if (obj->peaks)
free(obj->peaks);
if (obj->gain)
free(obj->gain);
if (obj->clipped)
free(obj->clipped);
free(obj);
}
static int *resizeArray(int *data, int newsz, int oldsz)
{
data = realloc(data, newsz*sizeof(int));
if (newsz > oldsz)
memset(data + oldsz, 0, sizeof(int)*(newsz - oldsz));
return data;
}
void Compressor_setHistory(struct Compressor *obj, unsigned int history)
{
if (!history)
history = BUCKETS;
obj->peaks = resizeArray(obj->peaks, history, obj->bufsz);
obj->gain = resizeArray(obj->gain, history, obj->bufsz);
obj->clipped = resizeArray(obj->clipped, history, obj->bufsz);
obj->bufsz = history;
}
struct CompressorConfig *Compressor_getConfig(struct Compressor *obj)
{
return &obj->prefs;
}
void Compressor_Process_int16(struct Compressor *obj, int16_t *audio,
unsigned int count)
{
struct CompressorConfig *prefs = Compressor_getConfig(obj);
int16_t *ap;
unsigned int i;
int *peaks = obj->peaks;
int curGain = obj->gain[obj->pos];
int newGain;
int peakVal = 1;
int peakPos = 0;
int slot = (obj->pos + 1) % obj->bufsz;
int *clipped = obj->clipped + slot;
unsigned int ramp = count;
int delta;
ap = audio;
for (i = 0; i < count; i++)
{
int val = *ap++;
if (val < 0)
val = -val;
if (val > peakVal)
{
peakVal = val;
peakPos = i;
}
}
peaks[slot] = peakVal;
for (i = 0; i < obj->bufsz; i++)
{
if (peaks[i] > peakVal)
{
peakVal = peaks[i];
peakPos = 0;
}
}
//! Determine target gain
newGain = (1 << 10)*prefs->target/peakVal;
//! Adjust the gain with inertia from the previous gain value
newGain = (curGain*((1 << prefs->smooth) - 1) + newGain)
>> prefs->smooth;
//! Make sure it's no more than the maximum gain value
if (newGain > (prefs->maxgain << 10))
newGain = prefs->maxgain << 10;
//! Make sure it's no less than 1:1
if (newGain < (1 << 10))
newGain = 1 << 10;
//! Make sure the adjusted gain won't cause clipping
if ((peakVal*newGain >> 10) > 32767)
{
newGain = (32767 << 10)/peakVal;
//! Truncate the ramp time
ramp = peakPos;
}
//! Record the new gain
obj->gain[slot] = newGain;
if (!ramp)
ramp = 1;
if (!curGain)
curGain = 1 << 10;
delta = (newGain - curGain) / (int)ramp;
ap = audio;
*clipped = 0;
for (i = 0; i < count; i++)
{
int sample;
//! Amplify the sample
sample = *ap*curGain >> 10;
if (sample < -32768)
{
*clipped += -32768 - sample;
sample = -32768;
} else if (sample > 32767)
{
*clipped += sample - 32767;
sample = 32767;
}
*ap++ = sample;
//! Adjust the gain
if (i < ramp)
curGain += delta;
else
curGain = newGain;
}
obj->pos = slot;
}

View File

@@ -1,40 +0,0 @@
/*! compress.h
* interface to audio compression
*
* (c)2007 busybee (http://beesbuzz.biz/)
* Licensed under the terms of the LGPL. See the file COPYING for details.
*/
#ifndef COMPRESS_H
#define COMPRESS_H
#include <stdint.h>
//! Configuration values for the compressor object
struct CompressorConfig {
int target;
int maxgain;
int smooth;
};
struct Compressor;
//! Create a new compressor (use history value of 0 for default)
struct Compressor *Compressor_new(unsigned int history);
//! Delete a compressor
void Compressor_delete(struct Compressor *);
//! Set the history length
void Compressor_setHistory(struct Compressor *, unsigned int history);
//! Get the configuration for a compressor
struct CompressorConfig *Compressor_getConfig(struct Compressor *);
//! Process 16-bit signed data
void Compressor_Process_int16(struct Compressor *, int16_t *data, unsigned int count);
//! TODO: Compressor_Process_int32, Compressor_Process_float, others as needed
//! TODO: functions for getting at the peak/gain/clip history buffers (for monitoring)
#endif

View File

@@ -1,19 +0,0 @@
/* config.h
** Default values for the configuration, and also a few random debug things
*/
#ifndef AC_CONFIG_H
#define AC_CONFIG_H
/*** Version information ***/
#define ACVERSION "2.0"
/*** Default configuration stuff ***/
#define TARGET 16384 /*!< Target level (on a scale of 0-32767) */
#define GAINMAX 32 /*!< The maximum amount to amplify by */
#define GAINSMOOTH 8 /*!< How much inertia ramping has*/
#define BUCKETS 400 /*!< How long of a history to use by default */
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -17,7 +17,6 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h" /* must be first for large file support */
#include "aiff.h"
#include <glib.h>

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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

View File

@@ -1,301 +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.
*/
/**
* single bz2 archive handling (requires libbz2)
*/
#include "config.h"
#include "archive/bz2_archive_plugin.h"
#include "archive_api.h"
#include "input_plugin.h"
#include "refcount.h"
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <glib.h>
#include <bzlib.h>
#ifdef HAVE_OLDER_BZIP2
#define BZ2_bzDecompressInit bzDecompressInit
#define BZ2_bzDecompress bzDecompress
#endif
struct bz2_archive_file {
struct archive_file base;
struct refcount ref;
char *name;
bool reset;
struct input_stream *istream;
};
struct bz2_input_stream {
struct input_stream base;
struct bz2_archive_file *archive;
bool eof;
bz_stream bzstream;
char buffer[5000];
};
static const struct input_plugin bz2_inputplugin;
static inline GQuark
bz2_quark(void)
{
return g_quark_from_static_string("bz2");
}
/* single archive handling allocation helpers */
static bool
bz2_alloc(struct bz2_input_stream *data, GError **error_r)
{
int ret;
data->bzstream.bzalloc = NULL;
data->bzstream.bzfree = NULL;
data->bzstream.opaque = NULL;
data->bzstream.next_in = (void *) data->buffer;
data->bzstream.avail_in = 0;
ret = BZ2_bzDecompressInit(&data->bzstream, 0, 0);
if (ret != BZ_OK) {
g_free(data);
g_set_error(error_r, bz2_quark(), ret,
"BZ2_bzDecompressInit() has failed");
return false;
}
return true;
}
static void
bz2_destroy(struct bz2_input_stream *data)
{
BZ2_bzDecompressEnd(&data->bzstream);
}
/* archive open && listing routine */
static struct archive_file *
bz2_open(const char *pathname, GError **error_r)
{
struct bz2_archive_file *context;
int len;
context = g_malloc(sizeof(*context));
archive_file_init(&context->base, &bz2_archive_plugin);
refcount_init(&context->ref);
//open archive
context->istream = input_stream_open(pathname, error_r);
if (context->istream == NULL) {
g_free(context);
return NULL;
}
context->name = g_path_get_basename(pathname);
//remove suffix
len = strlen(context->name);
if (len > 4) {
context->name[len - 4] = 0; //remove .bz2 suffix
}
return &context->base;
}
static void
bz2_scan_reset(struct archive_file *file)
{
struct bz2_archive_file *context = (struct bz2_archive_file *) file;
context->reset = true;
}
static char *
bz2_scan_next(struct archive_file *file)
{
struct bz2_archive_file *context = (struct bz2_archive_file *) file;
char *name = NULL;
if (context->reset) {
name = context->name;
context->reset = false;
}
return name;
}
static void
bz2_close(struct archive_file *file)
{
struct bz2_archive_file *context = (struct bz2_archive_file *) file;
if (!refcount_dec(&context->ref))
return;
g_free(context->name);
input_stream_close(context->istream);
g_free(context);
}
/* single archive handling */
static struct input_stream *
bz2_open_stream(struct archive_file *file, const char *path, GError **error_r)
{
struct bz2_archive_file *context = (struct bz2_archive_file *) file;
struct bz2_input_stream *bis = g_new(struct bz2_input_stream, 1);
input_stream_init(&bis->base, &bz2_inputplugin, path);
bis->archive = context;
bis->base.ready = true;
bis->base.seekable = false;
if (!bz2_alloc(bis, error_r)) {
input_stream_deinit(&bis->base);
g_free(bis);
return NULL;
}
bis->eof = false;
refcount_inc(&context->ref);
return &bis->base;
}
static void
bz2_is_close(struct input_stream *is)
{
struct bz2_input_stream *bis = (struct bz2_input_stream *)is;
bz2_destroy(bis);
bz2_close(&bis->archive->base);
input_stream_deinit(&bis->base);
g_free(bis);
}
static bool
bz2_fillbuffer(struct bz2_input_stream *bis, GError **error_r)
{
size_t count;
bz_stream *bzstream;
bzstream = &bis->bzstream;
if (bzstream->avail_in > 0)
return true;
count = input_stream_read(bis->archive->istream,
bis->buffer, sizeof(bis->buffer),
error_r);
if (count == 0)
return false;
bzstream->next_in = bis->buffer;
bzstream->avail_in = count;
return true;
}
static size_t
bz2_is_read(struct input_stream *is, void *ptr, size_t length,
GError **error_r)
{
struct bz2_input_stream *bis = (struct bz2_input_stream *)is;
bz_stream *bzstream;
int bz_result;
size_t nbytes = 0;
if (bis->eof)
return 0;
bzstream = &bis->bzstream;
bzstream->next_out = ptr;
bzstream->avail_out = length;
do {
if (!bz2_fillbuffer(bis, error_r))
return 0;
bz_result = BZ2_bzDecompress(bzstream);
if (bz_result == BZ_STREAM_END) {
bis->eof = true;
break;
}
if (bz_result != BZ_OK) {
g_set_error(error_r, bz2_quark(), bz_result,
"BZ2_bzDecompress() has failed");
return 0;
}
} while (bzstream->avail_out == length);
nbytes = length - bzstream->avail_out;
is->offset += nbytes;
return nbytes;
}
static bool
bz2_is_eof(struct input_stream *is)
{
struct bz2_input_stream *bis = (struct bz2_input_stream *)is;
return bis->eof;
}
/* exported structures */
static const char *const bz2_extensions[] = {
"bz2",
NULL
};
static const struct input_plugin bz2_inputplugin = {
.close = bz2_is_close,
.read = bz2_is_read,
.eof = bz2_is_eof,
};
const struct archive_plugin bz2_archive_plugin = {
.name = "bz2",
.open = bz2_open,
.scan_reset = bz2_scan_reset,
.scan_next = bz2_scan_next,
.open_stream = bz2_open_stream,
.close = bz2_close,
.suffixes = bz2_extensions
};

View File

@@ -1,25 +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_ARCHIVE_BZ2_H
#define MPD_ARCHIVE_BZ2_H
extern const struct archive_plugin bz2_archive_plugin;
#endif

284
src/archive/bz2_plugin.c Normal file
View File

@@ -0,0 +1,284 @@
/*
* Copyright (C) 2003-2009 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/**
* single bz2 archive handling (requires libbz2)
*/
#include "archive_api.h"
#include "input_plugin.h"
#include "config.h"
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <glib.h>
#include <bzlib.h>
#ifdef HAVE_OLDER_BZIP2
#define BZ2_bzDecompressInit bzDecompressInit
#define BZ2_bzDecompress bzDecompress
#endif
#define BZ_BUFSIZE 5000
typedef struct {
char *name;
bool reset;
struct input_stream istream;
int last_bz_result;
int last_parent_result;
bz_stream bzstream;
char *buffer;
} bz2_context;
static const struct input_plugin bz2_inputplugin;
/* single archive handling allocation helpers */
static bool
bz2_alloc(bz2_context *data)
{
data->bzstream.bzalloc = NULL;
data->bzstream.bzfree = NULL;
data->bzstream.opaque = NULL;
data->buffer = g_malloc(BZ_BUFSIZE);
data->bzstream.next_in = (void *) data->buffer;
data->bzstream.avail_in = 0;
if (BZ2_bzDecompressInit(&data->bzstream, 0, 0) != BZ_OK) {
g_free(data->buffer);
g_free(data);
return false;
}
data->last_bz_result = BZ_OK;
data->last_parent_result = 0;
return true;
}
static void
bz2_destroy(bz2_context *data)
{
BZ2_bzDecompressEnd(&data->bzstream);
g_free(data->buffer);
}
/* archive open && listing routine */
static struct archive_file *
bz2_open(char * pathname)
{
bz2_context *context;
char *name;
int len;
context = g_malloc(sizeof(bz2_context));
if (!context) {
return NULL;
}
//open archive
if (!input_stream_open(&context->istream, pathname)) {
g_warning("failed to open an bzip2 archive %s\n",pathname);
g_free(context);
return NULL;
}
//capture filename
name = strrchr(pathname, '/');
if (name == NULL) {
g_warning("failed to get bzip2 name from %s\n",pathname);
g_free(context);
return NULL;
}
context->name = g_strdup(name+1);
//remove suffix
len = strlen(context->name);
if (len > 4) {
context->name[len-4] = 0; //remove .bz2 suffix
}
return (struct archive_file *) context;
}
static void
bz2_scan_reset(struct archive_file *file)
{
bz2_context *context = (bz2_context *) file;
context->reset = true;
}
static char *
bz2_scan_next(struct archive_file *file)
{
bz2_context *context = (bz2_context *) file;
char *name = NULL;
if (context->reset) {
name = context->name;
context->reset = false;
}
return name;
}
static void
bz2_close(struct archive_file *file)
{
bz2_context *context = (bz2_context *) file;
g_free(context->name);
input_stream_close(&context->istream);
g_free(context);
}
/* single archive handling */
static bool
bz2_open_stream(struct archive_file *file, struct input_stream *is,
G_GNUC_UNUSED const char *path)
{
bz2_context *context = (bz2_context *) file;
//setup file ops
is->plugin = &bz2_inputplugin;
//insert back reference
is->data = context;
is->seekable = false;
if (!bz2_alloc(context)) {
g_warning("alloc bz2 failed\n");
return false;
}
return true;
}
static void
bz2_is_close(struct input_stream *is)
{
bz2_context *context = (bz2_context *) is->data;
bz2_destroy(context);
is->data = NULL;
bz2_close((struct archive_file *)context);
}
static int
bz2_fillbuffer(bz2_context *context,
size_t numBytes)
{
size_t count;
bz_stream *bzstream;
bzstream = &context->bzstream;
if (bzstream->avail_in > 0)
return 0;
count = input_stream_read(&context->istream,
context->buffer, BZ_BUFSIZE);
if (count == 0) {
if (bzstream->avail_out == numBytes)
return -1;
if (!input_stream_eof(&context->istream))
context->last_parent_result = 1;
} else {
bzstream->next_in = context->buffer;
bzstream->avail_in = count;
}
return 0;
}
static size_t
bz2_is_read(struct input_stream *is, void *ptr, size_t size)
{
bz2_context *context = (bz2_context *) is->data;
bz_stream *bzstream;
int bz_result;
size_t numBytes = size;
size_t bytesRead = 0;
if (context->last_bz_result != BZ_OK)
return 0;
if (context->last_parent_result != 0)
return 0;
bzstream = &context->bzstream;
bzstream->next_out = ptr;
bzstream->avail_out = numBytes;
while (bzstream->avail_out != 0) {
if (bz2_fillbuffer(context, numBytes) != 0)
break;
bz_result = BZ2_bzDecompress(bzstream);
if (context->last_bz_result != BZ_OK
&& bzstream->avail_out == numBytes) {
context->last_bz_result = bz_result;
break;
}
if (bz_result == BZ_STREAM_END) {
context->last_bz_result = bz_result;
break;
}
}
bytesRead = numBytes - bzstream->avail_out;
is->offset += bytesRead;
return bytesRead;
}
static bool
bz2_is_eof(struct input_stream *is)
{
bz2_context *context = (bz2_context *) is->data;
if (context->last_bz_result == BZ_STREAM_END) {
return true;
}
return false;
}
/* exported structures */
static const char *const bz2_extensions[] = {
"bz2",
NULL
};
static const struct input_plugin bz2_inputplugin = {
.close = bz2_is_close,
.read = bz2_is_read,
.eof = bz2_is_eof,
};
const struct archive_plugin bz2_plugin = {
.name = "bz2",
.open = bz2_open,
.scan_reset = bz2_scan_reset,
.scan_next = bz2_scan_next,
.open_stream = bz2_open_stream,
.close = bz2_close,
.suffixes = bz2_extensions
};

View File

@@ -1,287 +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.
*/
/**
* iso archive handling (requires cdio, and iso9660)
*/
#include "config.h"
#include "archive/iso9660_archive_plugin.h"
#include "archive_api.h"
#include "input_plugin.h"
#include "refcount.h"
#include <cdio/cdio.h>
#include <cdio/iso9660.h>
#include <glib.h>
#include <string.h>
#define CEILING(x, y) ((x+(y-1))/y)
struct iso9660_archive_file {
struct archive_file base;
struct refcount ref;
iso9660_t *iso;
GSList *list;
GSList *iter;
};
static const struct input_plugin iso9660_input_plugin;
static inline GQuark
iso9660_quark(void)
{
return g_quark_from_static_string("iso9660");
}
/* archive open && listing routine */
static void
listdir_recur(const char *psz_path, struct iso9660_archive_file *context)
{
iso9660_t *iso = context->iso;
CdioList_t *entlist;
CdioListNode_t *entnode;
iso9660_stat_t *statbuf;
char pathname[4096];
entlist = iso9660_ifs_readdir (iso, psz_path);
if (!entlist) {
return;
}
/* Iterate over the list of nodes that iso9660_ifs_readdir gives */
_CDIO_LIST_FOREACH (entnode, entlist) {
statbuf = (iso9660_stat_t *) _cdio_list_node_data (entnode);
strcpy(pathname, psz_path);
strcat(pathname, statbuf->filename);
if (_STAT_DIR == statbuf->type ) {
if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) {
strcat(pathname, "/");
listdir_recur(pathname, context);
}
} else {
//remove leading /
context->list = g_slist_prepend( context->list,
g_strdup(pathname + 1));
}
}
_cdio_list_free (entlist, true);
}
static struct archive_file *
iso9660_archive_open(const char *pathname, GError **error_r)
{
struct iso9660_archive_file *context =
g_new(struct iso9660_archive_file, 1);
archive_file_init(&context->base, &iso9660_archive_plugin);
refcount_init(&context->ref);
context->list = NULL;
/* open archive */
context->iso = iso9660_open (pathname);
if (context->iso == NULL) {
g_set_error(error_r, iso9660_quark(), 0,
"Failed to open ISO9660 file %s", pathname);
return NULL;
}
listdir_recur("/", context);
return &context->base;
}
static void
iso9660_archive_scan_reset(struct archive_file *file)
{
struct iso9660_archive_file *context =
(struct iso9660_archive_file *)file;
//reset iterator
context->iter = context->list;
}
static char *
iso9660_archive_scan_next(struct archive_file *file)
{
struct iso9660_archive_file *context =
(struct iso9660_archive_file *)file;
char *data = NULL;
if (context->iter != NULL) {
///fetch data and goto next
data = context->iter->data;
context->iter = g_slist_next(context->iter);
}
return data;
}
static void
iso9660_archive_close(struct archive_file *file)
{
struct iso9660_archive_file *context =
(struct iso9660_archive_file *)file;
GSList *tmp;
if (!refcount_dec(&context->ref))
return;
if (context->list) {
//free list
for (tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp))
g_free(tmp->data);
g_slist_free(context->list);
}
//close archive
iso9660_close(context->iso);
g_free(context);
}
/* single archive handling */
struct iso9660_input_stream {
struct input_stream base;
struct iso9660_archive_file *archive;
iso9660_stat_t *statbuf;
size_t max_blocks;
};
static struct input_stream *
iso9660_archive_open_stream(struct archive_file *file,
const char *pathname, GError **error_r)
{
struct iso9660_archive_file *context =
(struct iso9660_archive_file *)file;
struct iso9660_input_stream *iis;
iis = g_new(struct iso9660_input_stream, 1);
input_stream_init(&iis->base, &iso9660_input_plugin, pathname);
iis->archive = context;
iis->statbuf = iso9660_ifs_stat_translate(context->iso, pathname);
if (iis->statbuf == NULL) {
g_free(iis);
g_set_error(error_r, iso9660_quark(), 0,
"not found in the ISO file: %s", pathname);
return NULL;
}
iis->base.ready = true;
//we are not seekable
iis->base.seekable = false;
iis->base.size = iis->statbuf->size;
iis->max_blocks = CEILING(iis->statbuf->size, ISO_BLOCKSIZE);
refcount_inc(&context->ref);
return &iis->base;
}
static void
iso9660_input_close(struct input_stream *is)
{
struct iso9660_input_stream *iis = (struct iso9660_input_stream *)is;
g_free(iis->statbuf);
iso9660_archive_close(&iis->archive->base);
input_stream_deinit(&iis->base);
g_free(iis);
}
static size_t
iso9660_input_read(struct input_stream *is, void *ptr, size_t size, GError **error_r)
{
struct iso9660_input_stream *iis = (struct iso9660_input_stream *)is;
int toread, readed = 0;
int no_blocks, cur_block;
size_t left_bytes = iis->statbuf->size - is->offset;
size = (size * ISO_BLOCKSIZE) / ISO_BLOCKSIZE;
if (left_bytes < size) {
toread = left_bytes;
no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE);
} else {
toread = size;
no_blocks = toread / ISO_BLOCKSIZE;
}
if (no_blocks > 0) {
cur_block = is->offset / ISO_BLOCKSIZE;
readed = iso9660_iso_seek_read (iis->archive->iso, ptr,
iis->statbuf->lsn + cur_block, no_blocks);
if (readed != no_blocks * ISO_BLOCKSIZE) {
g_set_error(error_r, iso9660_quark(), 0,
"error reading ISO file at lsn %lu",
(long unsigned int) cur_block);
return 0;
}
if (left_bytes < size) {
readed = left_bytes;
}
is->offset += readed;
}
return readed;
}
static bool
iso9660_input_eof(struct input_stream *is)
{
return is->offset == is->size;
}
/* exported structures */
static const char *const iso9660_archive_extensions[] = {
"iso",
NULL
};
static const struct input_plugin iso9660_input_plugin = {
.close = iso9660_input_close,
.read = iso9660_input_read,
.eof = iso9660_input_eof,
};
const struct archive_plugin iso9660_archive_plugin = {
.name = "iso",
.open = iso9660_archive_open,
.scan_reset = iso9660_archive_scan_reset,
.scan_next = iso9660_archive_scan_next,
.open_stream = iso9660_archive_open_stream,
.close = iso9660_archive_close,
.suffixes = iso9660_archive_extensions
};

View File

@@ -1,25 +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_ARCHIVE_ISO9660_H
#define MPD_ARCHIVE_ISO9660_H
extern const struct archive_plugin iso9660_archive_plugin;
#endif

239
src/archive/iso_plugin.c Normal file
View File

@@ -0,0 +1,239 @@
/*
* Copyright (C) 2003-2009 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/**
* iso archive handling (requires cdio, and iso9660)
*/
#include "archive_api.h"
#include "input_plugin.h"
#include <cdio/cdio.h>
#include <cdio/iso9660.h>
#include <glib.h>
#include <string.h>
#define CEILING(x, y) ((x+(y-1))/y)
typedef struct {
iso9660_t *iso;
iso9660_stat_t *statbuf;
size_t cur_ofs;
size_t max_blocks;
GSList *list;
GSList *iter;
} iso_context;
static const struct input_plugin iso_inputplugin;
/* archive open && listing routine */
static void
listdir_recur(const char *psz_path, iso_context *context)
{
iso9660_t *iso = context->iso;
CdioList_t *entlist;
CdioListNode_t *entnode;
iso9660_stat_t *statbuf;
char pathname[4096];
entlist = iso9660_ifs_readdir (iso, psz_path);
if (!entlist) {
return;
}
/* Iterate over the list of nodes that iso9660_ifs_readdir gives */
_CDIO_LIST_FOREACH (entnode, entlist) {
statbuf = (iso9660_stat_t *) _cdio_list_node_data (entnode);
strcpy(pathname, psz_path);
strcat(pathname, statbuf->filename);
if (_STAT_DIR == statbuf->type ) {
if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) {
strcat(pathname, "/");
listdir_recur(pathname, context);
}
} else {
//remove leading /
context->list = g_slist_prepend( context->list,
g_strdup(pathname + 1));
}
}
_cdio_list_free (entlist, true);
}
static struct archive_file *
iso_open(char * pathname)
{
iso_context *context = g_malloc(sizeof(iso_context));
context->list = NULL;
/* open archive */
context->iso = iso9660_open (pathname);
if (context->iso == NULL) {
g_warning("iso %s open failed\n", pathname);
return NULL;
}
listdir_recur("/", context);
return (struct archive_file *)context;
}
static void
iso_scan_reset(struct archive_file *file)
{
iso_context *context = (iso_context *) file;
//reset iterator
context->iter = context->list;
}
static char *
iso_scan_next(struct archive_file *file)
{
iso_context *context = (iso_context *) file;
char *data = NULL;
if (context->iter != NULL) {
///fetch data and goto next
data = context->iter->data;
context->iter = g_slist_next(context->iter);
}
return data;
}
static void
iso_close(struct archive_file *file)
{
iso_context *context = (iso_context *) file;
GSList *tmp;
if (context->list) {
//free list
for (tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp))
g_free(tmp->data);
g_slist_free(context->list);
}
//close archive
iso9660_close(context->iso);
g_free(context);
}
/* single archive handling */
static bool
iso_open_stream(struct archive_file *file, struct input_stream *is,
const char *pathname)
{
iso_context *context = (iso_context *) file;
//setup file ops
is->plugin = &iso_inputplugin;
//insert back reference
is->data = context;
//we are not seekable
is->seekable = false;
context->statbuf = iso9660_ifs_stat_translate (context->iso, pathname);
if (context->statbuf == NULL) {
g_warning("file %s not found in iso\n", pathname);
return false;
}
context->cur_ofs = 0;
context->max_blocks = CEILING(context->statbuf->size, ISO_BLOCKSIZE);
return true;
}
static void
iso_is_close(struct input_stream *is)
{
iso_context *context = (iso_context *) is->data;
g_free(context->statbuf);
iso_close((struct archive_file *)context);
}
static size_t
iso_is_read(struct input_stream *is, void *ptr, size_t size)
{
iso_context *context = (iso_context *) is->data;
int toread, readed = 0;
int no_blocks, cur_block;
size_t left_bytes = context->statbuf->size - context->cur_ofs;
size = (size * ISO_BLOCKSIZE) / ISO_BLOCKSIZE;
if (left_bytes < size) {
toread = left_bytes;
no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE);
} else {
toread = size;
no_blocks = toread / ISO_BLOCKSIZE;
}
if (no_blocks > 0) {
cur_block = context->cur_ofs / ISO_BLOCKSIZE;
readed = iso9660_iso_seek_read (context->iso, ptr,
context->statbuf->lsn + cur_block, no_blocks);
if (readed != no_blocks * ISO_BLOCKSIZE) {
g_warning("error reading ISO file at lsn %lu\n",
(long unsigned int) cur_block );
return -1;
}
if (left_bytes < size) {
readed = left_bytes;
}
context->cur_ofs += readed;
}
return readed;
}
static bool
iso_is_eof(struct input_stream *is)
{
iso_context *context = (iso_context *) is->data;
return (context->cur_ofs == context->statbuf->size);
}
/* exported structures */
static const char *const iso_extensions[] = {
"iso",
NULL
};
static const struct input_plugin iso_inputplugin = {
.close = iso_is_close,
.read = iso_is_read,
.eof = iso_is_eof,
};
const struct archive_plugin iso_plugin = {
.name = "iso",
.open = iso_open,
.scan_reset = iso_scan_reset,
.scan_next = iso_scan_next,
.open_stream = iso_open_stream,
.close = iso_close,
.suffixes = iso_extensions
};

196
src/archive/zip_plugin.c Normal file
View File

@@ -0,0 +1,196 @@
/*
* Copyright (C) 2003-2009 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/**
* zip archive handling (requires zziplib)
*/
#include "archive_api.h"
#include "archive_api.h"
#include "input_plugin.h"
#include <zzip/zzip.h>
#include <glib.h>
#include <string.h>
typedef struct {
ZZIP_DIR *dir;
ZZIP_FILE *file;
size_t length;
GSList *list;
GSList *iter;
} zip_context;
static const struct input_plugin zip_inputplugin;
/* archive open && listing routine */
static struct archive_file *
zip_open(char * pathname)
{
zip_context *context = g_malloc(sizeof(zip_context));
ZZIP_DIRENT dirent;
// open archive
context->list = NULL;
context->dir = zzip_dir_open(pathname, NULL);
if (context->dir == NULL) {
g_warning("zipfile %s open failed\n", pathname);
return NULL;
}
while (zzip_dir_read(context->dir, &dirent)) {
//add only files
if (dirent.st_size > 0) {
context->list = g_slist_prepend(context->list,
g_strdup(dirent.d_name));
}
}
return (struct archive_file *)context;
}
static void
zip_scan_reset(struct archive_file *file)
{
zip_context *context = (zip_context *) file;
//reset iterator
context->iter = context->list;
}
static char *
zip_scan_next(struct archive_file *file)
{
zip_context *context = (zip_context *) file;
char *data = NULL;
if (context->iter != NULL) {
///fetch data and goto next
data = context->iter->data;
context->iter = g_slist_next(context->iter);
}
return data;
}
static void
zip_close(struct archive_file *file)
{
zip_context *context = (zip_context *) file;
if (context->list) {
//free list
for (GSList *tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp))
g_free(tmp->data);
g_slist_free(context->list);
}
//close archive
zzip_dir_close (context->dir);
g_free(context);
}
/* single archive handling */
static bool
zip_open_stream(struct archive_file *file, struct input_stream *is,
const char *pathname)
{
zip_context *context = (zip_context *) file;
ZZIP_STAT z_stat;
//setup file ops
is->plugin = &zip_inputplugin;
//insert back reference
is->data = context;
//we are seekable (but its not recommendent to do so)
is->seekable = true;
context->file = zzip_file_open(context->dir, pathname, 0);
if (!context->file) {
g_warning("file %s not found in the zipfile\n", pathname);
return false;
}
zzip_file_stat(context->file, &z_stat);
context->length = z_stat.st_size;
return true;
}
static void
zip_is_close(struct input_stream *is)
{
zip_context *context = (zip_context *) is->data;
zzip_file_close (context->file);
zip_close((struct archive_file *)context);
}
static size_t
zip_is_read(struct input_stream *is, void *ptr, size_t size)
{
zip_context *context = (zip_context *) is->data;
int ret;
ret = zzip_file_read(context->file, ptr, size);
if (ret < 0) {
g_warning("error %d reading zipfile\n", ret);
return 0;
}
return ret;
}
static bool
zip_is_eof(struct input_stream *is)
{
zip_context *context = (zip_context *) is->data;
return ((size_t) zzip_tell(context->file) == context->length);
}
static bool
zip_is_seek(G_GNUC_UNUSED struct input_stream *is,
G_GNUC_UNUSED off_t offset, G_GNUC_UNUSED int whence)
{
zip_context *context = (zip_context *) is->data;
zzip_off_t ofs = zzip_seek(context->file, offset, whence);
if (ofs != -1) {
is->offset = ofs;
return true;
}
return false;
}
/* exported structures */
static const char *const zip_extensions[] = {
"zip",
NULL
};
static const struct input_plugin zip_inputplugin = {
.close = zip_is_close,
.read = zip_is_read,
.eof = zip_is_eof,
.seek = zip_is_seek,
};
const struct archive_plugin zip_plugin = {
.name = "zip",
.open = zip_open,
.scan_reset = zip_scan_reset,
.scan_next = zip_scan_next,
.open_stream = zip_open_stream,
.close = zip_close,
.suffixes = zip_extensions
};

View File

@@ -1,242 +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.
*/
/**
* zip archive handling (requires zziplib)
*/
#include "config.h"
#include "archive/zzip_archive_plugin.h"
#include "archive_api.h"
#include "archive_api.h"
#include "input_plugin.h"
#include "refcount.h"
#include <zzip/zzip.h>
#include <glib.h>
#include <string.h>
struct zzip_archive {
struct archive_file base;
struct refcount ref;
ZZIP_DIR *dir;
GSList *list;
GSList *iter;
};
static const struct input_plugin zzip_input_plugin;
static inline GQuark
zzip_quark(void)
{
return g_quark_from_static_string("zzip");
}
/* archive open && listing routine */
static struct archive_file *
zzip_archive_open(const char *pathname, GError **error_r)
{
struct zzip_archive *context = g_malloc(sizeof(*context));
ZZIP_DIRENT dirent;
archive_file_init(&context->base, &zzip_archive_plugin);
refcount_init(&context->ref);
// open archive
context->list = NULL;
context->dir = zzip_dir_open(pathname, NULL);
if (context->dir == NULL) {
g_set_error(error_r, zzip_quark(), 0,
"Failed to open ZIP file %s", pathname);
return NULL;
}
while (zzip_dir_read(context->dir, &dirent)) {
//add only files
if (dirent.st_size > 0) {
context->list = g_slist_prepend(context->list,
g_strdup(dirent.d_name));
}
}
return &context->base;
}
static void
zzip_archive_scan_reset(struct archive_file *file)
{
struct zzip_archive *context = (struct zzip_archive *) file;
//reset iterator
context->iter = context->list;
}
static char *
zzip_archive_scan_next(struct archive_file *file)
{
struct zzip_archive *context = (struct zzip_archive *) file;
char *data = NULL;
if (context->iter != NULL) {
///fetch data and goto next
data = context->iter->data;
context->iter = g_slist_next(context->iter);
}
return data;
}
static void
zzip_archive_close(struct archive_file *file)
{
struct zzip_archive *context = (struct zzip_archive *) file;
if (!refcount_dec(&context->ref))
return;
if (context->list) {
//free list
for (GSList *tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp))
g_free(tmp->data);
g_slist_free(context->list);
}
//close archive
zzip_dir_close (context->dir);
g_free(context);
}
/* single archive handling */
struct zzip_input_stream {
struct input_stream base;
struct zzip_archive *archive;
ZZIP_FILE *file;
};
static struct input_stream *
zzip_archive_open_stream(struct archive_file *file,
const char *pathname, GError **error_r)
{
struct zzip_archive *context = (struct zzip_archive *) file;
struct zzip_input_stream *zis;
ZZIP_STAT z_stat;
zis = g_new(struct zzip_input_stream, 1);
input_stream_init(&zis->base, &zzip_input_plugin, pathname);
zis->archive = context;
zis->file = zzip_file_open(context->dir, pathname, 0);
if (zis->file == NULL) {
g_free(zis);
g_set_error(error_r, zzip_quark(), 0,
"not found in the ZIP file: %s", pathname);
return NULL;
}
zis->base.ready = true;
//we are seekable (but its not recommendent to do so)
zis->base.seekable = true;
zzip_file_stat(zis->file, &z_stat);
zis->base.size = z_stat.st_size;
refcount_inc(&context->ref);
return &zis->base;
}
static void
zzip_input_close(struct input_stream *is)
{
struct zzip_input_stream *zis = (struct zzip_input_stream *)is;
zzip_file_close(zis->file);
zzip_archive_close(&zis->archive->base);
input_stream_deinit(&zis->base);
g_free(zis);
}
static size_t
zzip_input_read(struct input_stream *is, void *ptr, size_t size,
GError **error_r)
{
struct zzip_input_stream *zis = (struct zzip_input_stream *)is;
int ret;
ret = zzip_file_read(zis->file, ptr, size);
if (ret < 0) {
g_set_error(error_r, zzip_quark(), ret,
"zzip_file_read() has failed");
return 0;
}
is->offset = zzip_tell(zis->file);
return ret;
}
static bool
zzip_input_eof(struct input_stream *is)
{
struct zzip_input_stream *zis = (struct zzip_input_stream *)is;
return (goffset)zzip_tell(zis->file) == is->size;
}
static bool
zzip_input_seek(struct input_stream *is,
goffset offset, int whence, GError **error_r)
{
struct zzip_input_stream *zis = (struct zzip_input_stream *)is;
zzip_off_t ofs = zzip_seek(zis->file, offset, whence);
if (ofs != -1) {
g_set_error(error_r, zzip_quark(), ofs,
"zzip_seek() has failed");
is->offset = ofs;
return true;
}
return false;
}
/* exported structures */
static const char *const zzip_archive_extensions[] = {
"zip",
NULL
};
static const struct input_plugin zzip_input_plugin = {
.close = zzip_input_close,
.read = zzip_input_read,
.eof = zzip_input_eof,
.seek = zzip_input_seek,
};
const struct archive_plugin zzip_archive_plugin = {
.name = "zzip",
.open = zzip_archive_open,
.scan_reset = zzip_archive_scan_reset,
.scan_next = zzip_archive_scan_next,
.open_stream = zzip_archive_open_stream,
.close = zzip_archive_close,
.suffixes = zzip_archive_extensions
};

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -17,9 +17,6 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h" /* must be first for large file support */
#include "archive_api.h"
#include <stdio.h>
#include <string.h>
@@ -29,6 +26,8 @@
#include <errno.h>
#include <glib.h>
#include "archive_api.h"
/**
*
* archive_lookup is used to determine if part of pathname refers to an regular

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -27,11 +27,72 @@
*/
#include "archive_internal.h"
#include "archive_plugin.h"
#include "input_stream.h"
#include <stdbool.h>
struct archive_file;
struct archive_plugin {
const char *name;
/**
* optional, set this to NULL if the archive plugin doesn't
* have/need one this must false if there is an error and
* true otherwise
*/
bool (*init)(void);
/**
* optional, set this to NULL if the archive plugin doesn't
* have/need one
*/
void (*finish)(void);
/**
* tryes to open archive file and associates handle with archive
* returns pointer to handle used is all operations with this archive
* or NULL when opening fails
*/
struct archive_file *(*open)(char * pathname);
/**
* reset routine will move current read index in archive to default
* position and then the filenames from archives can be read
* via scan_next routine
*/
void (*scan_reset)(struct archive_file *);
/**
* the read method will return corresponding files from archive
* (as pathnames) and move read index to next file. When there is no
* next file it return NULL.
*/
char *(*scan_next)(struct archive_file *);
/**
* Opens an input_stream of a file within the archive.
*
* If this function succeeds, then the #input_stream "owns"
* the archive file and will automatically close it.
*
* @param path the path within the archive
*/
bool (*open_stream)(struct archive_file *, struct input_stream *is,
const char *path);
/**
* closes archive file.
*/
void (*close)(struct archive_file *);
/**
* suffixes handled by this plugin.
* last element in these arrays must always be a NULL
*/
const char *const*suffixes;
};
bool archive_lookup(char *pathname, char **archive, char **inpath, char **suffix);
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -21,14 +21,7 @@
#define MPD_ARCHIVE_INTERNAL_H
struct archive_file {
const struct archive_plugin *plugin;
int placeholder;
};
static inline void
archive_file_init(struct archive_file *archive_file,
const struct archive_plugin *plugin)
{
archive_file->plugin = plugin;
}
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -17,44 +17,50 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "archive_list.h"
#include "archive_plugin.h"
#include "archive_api.h"
#include "utils.h"
#include "archive/bz2_archive_plugin.h"
#include "archive/iso9660_archive_plugin.h"
#include "archive/zzip_archive_plugin.h"
#include "config.h"
#include <string.h>
#include <glib.h>
extern const struct archive_plugin bz2_plugin;
extern const struct archive_plugin zip_plugin;
extern const struct archive_plugin iso_plugin;
static const struct archive_plugin *const archive_plugins[] = {
#ifdef HAVE_BZ2
&bz2_archive_plugin,
&bz2_plugin,
#endif
#ifdef HAVE_ZZIP
&zzip_archive_plugin,
#ifdef HAVE_ZIP
&zip_plugin,
#endif
#ifdef HAVE_ISO9660
&iso9660_archive_plugin,
#ifdef HAVE_ISO
&iso_plugin,
#endif
NULL
};
enum {
num_archive_plugins = G_N_ELEMENTS(archive_plugins)-1,
};
/** which plugins have been initialized successfully? */
static bool archive_plugins_enabled[G_N_ELEMENTS(archive_plugins) - 1];
static bool archive_plugins_enabled[num_archive_plugins+1];
const struct archive_plugin *
archive_plugin_from_suffix(const char *suffix)
{
unsigned i;
if (suffix == NULL)
return NULL;
for (unsigned i = 0; archive_plugins[i] != NULL; ++i) {
for (i=0; i < num_archive_plugins; ++i) {
const struct archive_plugin *plugin = archive_plugins[i];
if (archive_plugins_enabled[i] &&
plugin->suffixes != NULL &&
string_array_contains(plugin->suffixes, suffix)) {
stringFoundInStringArray(plugin->suffixes, suffix)) {
++i;
return plugin;
}
@@ -65,7 +71,7 @@ archive_plugin_from_suffix(const char *suffix)
const struct archive_plugin *
archive_plugin_from_name(const char *name)
{
for (unsigned i = 0; archive_plugins[i] != NULL; ++i) {
for (unsigned i = 0; i < num_archive_plugins; ++i) {
const struct archive_plugin *plugin = archive_plugins[i];
if (archive_plugins_enabled[i] &&
strcmp(plugin->name, name) == 0)
@@ -78,7 +84,7 @@ void archive_plugin_print_all_suffixes(FILE * fp)
{
const char *const*suffixes;
for (unsigned i = 0; archive_plugins[i] != NULL; ++i) {
for (unsigned i = 0; i < num_archive_plugins; ++i) {
const struct archive_plugin *plugin = archive_plugins[i];
if (!archive_plugins_enabled[i])
continue;
@@ -95,7 +101,7 @@ void archive_plugin_print_all_suffixes(FILE * fp)
void archive_plugin_init_all(void)
{
for (unsigned i = 0; archive_plugins[i] != NULL; ++i) {
for (unsigned i = 0; i < num_archive_plugins; ++i) {
const struct archive_plugin *plugin = archive_plugins[i];
if (plugin->init == NULL || archive_plugins[i]->init())
archive_plugins_enabled[i] = true;
@@ -104,7 +110,7 @@ void archive_plugin_init_all(void)
void archive_plugin_deinit_all(void)
{
for (unsigned i = 0; archive_plugins[i] != NULL; ++i) {
for (unsigned i = 0; i < num_archive_plugins; ++i) {
const struct archive_plugin *plugin = archive_plugins[i];
if (archive_plugins_enabled[i] && plugin->finish != NULL)
archive_plugins[i]->finish();

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -20,6 +20,8 @@
#ifndef MPD_ARCHIVE_LIST_H
#define MPD_ARCHIVE_LIST_H
#include "archive_api.h"
#include <stdio.h>
struct archive_plugin;

View File

@@ -1,92 +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 "archive_plugin.h"
#include "archive_internal.h"
#include <assert.h>
struct archive_file *
archive_file_open(const struct archive_plugin *plugin, const char *path,
GError **error_r)
{
struct archive_file *file;
assert(plugin != NULL);
assert(plugin->open != NULL);
assert(path != NULL);
assert(error_r == NULL || *error_r == NULL);
file = plugin->open(path, error_r);
if (file != NULL) {
assert(file->plugin != NULL);
assert(file->plugin->close != NULL);
assert(file->plugin->scan_reset != NULL);
assert(file->plugin->scan_next != NULL);
assert(file->plugin->open_stream != NULL);
assert(error_r == NULL || *error_r == NULL);
} else {
assert(error_r == NULL || *error_r != NULL);
}
return file;
}
void
archive_file_close(struct archive_file *file)
{
assert(file != NULL);
assert(file->plugin != NULL);
assert(file->plugin->close != NULL);
file->plugin->close(file);
}
void
archive_file_scan_reset(struct archive_file *file)
{
assert(file != NULL);
assert(file->plugin != NULL);
assert(file->plugin->scan_reset != NULL);
assert(file->plugin->scan_next != NULL);
file->plugin->scan_reset(file);
}
char *
archive_file_scan_next(struct archive_file *file)
{
assert(file != NULL);
assert(file->plugin != NULL);
assert(file->plugin->scan_next != NULL);
return file->plugin->scan_next(file);
}
struct input_stream *
archive_file_open_stream(struct archive_file *file,
const char *path, GError **error_r)
{
assert(file != NULL);
assert(file->plugin != NULL);
assert(file->plugin->open_stream != NULL);
return file->plugin->open_stream(file, path, error_r);
}

View File

@@ -1,107 +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_ARCHIVE_PLUGIN_H
#define MPD_ARCHIVE_PLUGIN_H
#include <glib.h>
#include <stdbool.h>
struct input_stream;
struct archive_file;
struct archive_plugin {
const char *name;
/**
* optional, set this to NULL if the archive plugin doesn't
* have/need one this must false if there is an error and
* true otherwise
*/
bool (*init)(void);
/**
* optional, set this to NULL if the archive plugin doesn't
* have/need one
*/
void (*finish)(void);
/**
* tryes to open archive file and associates handle with archive
* returns pointer to handle used is all operations with this archive
* or NULL when opening fails
*/
struct archive_file *(*open)(const char *path_fs, GError **error_r);
/**
* reset routine will move current read index in archive to default
* position and then the filenames from archives can be read
* via scan_next routine
*/
void (*scan_reset)(struct archive_file *);
/**
* the read method will return corresponding files from archive
* (as pathnames) and move read index to next file. When there is no
* next file it return NULL.
*/
char *(*scan_next)(struct archive_file *);
/**
* Opens an input_stream of a file within the archive.
*
* @param path the path within the archive
* @param error_r location to store the error occuring, or
* NULL to ignore errors
*/
struct input_stream *(*open_stream)(struct archive_file *af,
const char *path,
GError **error_r);
/**
* closes archive file.
*/
void (*close)(struct archive_file *);
/**
* suffixes handled by this plugin.
* last element in these arrays must always be a NULL
*/
const char *const*suffixes;
};
struct archive_file *
archive_file_open(const struct archive_plugin *plugin, const char *path,
GError **error_r);
void
archive_file_close(struct archive_file *file);
void
archive_file_scan_reset(struct archive_file *file);
char *
archive_file_scan_next(struct archive_file *file);
struct input_stream *
archive_file_open_stream(struct archive_file *file,
const char *path, GError **error_r);
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -17,7 +17,6 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "audio.h"
#include "audio_format.h"
#include "audio_parser.h"
@@ -36,8 +35,9 @@ static struct audio_format configured_audio_format;
void getOutputAudioFormat(const struct audio_format *inAudioFormat,
struct audio_format *outAudioFormat)
{
*outAudioFormat = *inAudioFormat;
audio_format_mask_apply(outAudioFormat, &configured_audio_format);
*outAudioFormat = audio_format_defined(&configured_audio_format)
? configured_audio_format
: *inAudioFormat;
}
void initAudioConfig(void)
@@ -46,12 +46,17 @@ void initAudioConfig(void)
GError *error = NULL;
bool ret;
if (param == NULL)
if (NULL == param || NULL == param->value)
return;
ret = audio_format_parse(&configured_audio_format, param->value,
true, &error);
&error);
if (!ret)
g_error("error parsing \"%s\" at line %i: %s",
CONF_AUDIO_OUTPUT_FORMAT, param->line, error->message);
}
void finishAudioConfig(void)
{
audio_format_clear(&configured_audio_format);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -30,4 +30,6 @@ void getOutputAudioFormat(const struct audio_format *inFormat,
/* make sure initPlayerData is called before this function!! */
void initAudioConfig(void);
void finishAudioConfig(void);
#endif

View File

@@ -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 "audio_check.h"
#include "audio_format.h"
#include <assert.h>
bool
audio_check_sample_rate(unsigned long sample_rate, GError **error_r)
{
if (!audio_valid_sample_rate(sample_rate)) {
g_set_error(error_r, audio_format_quark(), 0,
"Invalid sample rate: %lu", sample_rate);
return false;
}
return true;
}
bool
audio_check_sample_format(enum sample_format sample_format, GError **error_r)
{
if (!audio_valid_sample_format(sample_format)) {
g_set_error(error_r, audio_format_quark(), 0,
"Invalid sample format: %u", sample_format);
return false;
}
return true;
}
bool
audio_check_channel_count(unsigned channels, GError **error_r)
{
if (!audio_valid_channel_count(channels)) {
g_set_error(error_r, audio_format_quark(), 0,
"Invalid channel count: %u", channels);
return false;
}
return true;
}
bool
audio_format_init_checked(struct audio_format *af, unsigned long sample_rate,
enum sample_format sample_format, unsigned channels,
GError **error_r)
{
if (audio_check_sample_rate(sample_rate, error_r) &&
audio_check_sample_format(sample_format, error_r) &&
audio_check_channel_count(channels, error_r)) {
audio_format_init(af, sample_rate, sample_format, channels);
assert(audio_format_valid(af));
return true;
} else
return false;
}

View File

@@ -1,54 +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_AUDIO_CHECK_H
#define MPD_AUDIO_CHECK_H
#include "audio_format.h"
#include <glib.h>
#include <stdbool.h>
/**
* The GLib quark used for errors reported by this library.
*/
static inline GQuark
audio_format_quark(void)
{
return g_quark_from_static_string("audio_format");
}
bool
audio_check_sample_rate(unsigned long sample_rate, GError **error_r);
bool
audio_check_sample_format(unsigned sample_format, GError **error_r);
bool
audio_check_channel_count(unsigned sample_format, GError **error_r);
/**
* Wrapper for audio_format_init(), which checks all attributes.
*/
bool
audio_format_init_checked(struct audio_format *af, unsigned long sample_rate,
enum sample_format sample_format, unsigned channels,
GError **error_r);
#endif

View File

@@ -1,72 +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 "audio_format.h"
#include <assert.h>
#include <stdio.h>
#if G_BYTE_ORDER == G_BIG_ENDIAN
#define REVERSE_ENDIAN_SUFFIX "_le"
#else
#define REVERSE_ENDIAN_SUFFIX "_be"
#endif
const char *
sample_format_to_string(enum sample_format format)
{
switch (format) {
case SAMPLE_FORMAT_UNDEFINED:
return "?";
case SAMPLE_FORMAT_S8:
return "8";
case SAMPLE_FORMAT_S16:
return "16";
case SAMPLE_FORMAT_S24:
return "24_3";
case SAMPLE_FORMAT_S24_P32:
return "24";
case SAMPLE_FORMAT_S32:
return "32";
}
/* unreachable */
assert(false);
return "?";
}
const char *
audio_format_to_string(const struct audio_format *af,
struct audio_format_string *s)
{
assert(af != NULL);
assert(s != NULL);
snprintf(s->buffer, sizeof(s->buffer), "%u:%s%s:%u",
af->sample_rate, sample_format_to_string(af->format),
af->reverse_endian ? REVERSE_ENDIAN_SUFFIX : "",
af->channels);
return s->buffer;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -23,122 +23,24 @@
#include <stdint.h>
#include <stdbool.h>
enum sample_format {
SAMPLE_FORMAT_UNDEFINED = 0,
SAMPLE_FORMAT_S8,
SAMPLE_FORMAT_S16,
/**
* Signed 24 bit integer samples, without padding.
*/
SAMPLE_FORMAT_S24,
/**
* Signed 24 bit integer samples, packed in 32 bit integers
* (the most significant byte is filled with the sign bit).
*/
SAMPLE_FORMAT_S24_P32,
SAMPLE_FORMAT_S32,
};
/**
* This structure describes the format of a raw PCM stream.
*/
struct audio_format {
/**
* The sample rate in Hz. A better name for this attribute is
* "frame rate", because technically, you have two samples per
* frame in stereo sound.
*/
uint32_t sample_rate;
/**
* The format samples are stored in. See the #sample_format
* enum for valid values.
*/
uint8_t format;
/**
* The number of channels. Only mono (1) and stereo (2) are
* fully supported currently.
*/
uint8_t bits;
uint8_t channels;
/**
* If zero, then samples are stored in host byte order. If
* nonzero, then samples are stored in the reverse host byte
* order.
*/
uint8_t reverse_endian;
};
/**
* Buffer for audio_format_string().
*/
struct audio_format_string {
char buffer[24];
};
/**
* Clears the #audio_format object, i.e. sets all attributes to an
* undefined (invalid) value.
*/
static inline void audio_format_clear(struct audio_format *af)
{
af->sample_rate = 0;
af->format = SAMPLE_FORMAT_UNDEFINED;
af->bits = 0;
af->channels = 0;
af->reverse_endian = 0;
}
/**
* Initializes an #audio_format object, i.e. sets all
* attributes to valid values.
*/
static inline void audio_format_init(struct audio_format *af,
uint32_t sample_rate,
enum sample_format format, uint8_t channels)
{
af->sample_rate = sample_rate;
af->format = (uint8_t)format;
af->channels = channels;
af->reverse_endian = 0;
}
/**
* Checks whether the specified #audio_format object has a defined
* value.
*/
static inline bool audio_format_defined(const struct audio_format *af)
{
return af->sample_rate != 0;
}
/**
* Checks whether the specified #audio_format object is full, i.e. all
* attributes are defined. This is more complete than
* audio_format_defined(), but slower.
*/
static inline bool
audio_format_fully_defined(const struct audio_format *af)
{
return af->sample_rate != 0 && af->format != SAMPLE_FORMAT_UNDEFINED &&
af->channels != 0;
}
/**
* Checks whether the specified #audio_format object has at least one
* defined value.
*/
static inline bool
audio_format_mask_defined(const struct audio_format *af)
{
return af->sample_rate != 0 || af->format != SAMPLE_FORMAT_UNDEFINED ||
af->channels != 0;
}
/**
* Checks whether the sample rate is valid.
*
@@ -156,21 +58,9 @@ audio_valid_sample_rate(unsigned sample_rate)
* @param bits the number of significant bits per sample
*/
static inline bool
audio_valid_sample_format(enum sample_format format)
audio_valid_sample_format(unsigned bits)
{
switch (format) {
case SAMPLE_FORMAT_S8:
case SAMPLE_FORMAT_S16:
case SAMPLE_FORMAT_S24:
case SAMPLE_FORMAT_S24_P32:
case SAMPLE_FORMAT_S32:
return true;
case SAMPLE_FORMAT_UNDEFINED:
break;
}
return false;
return bits == 16 || bits == 24 || bits == 32 || bits == 8;
}
/**
@@ -189,44 +79,16 @@ audio_valid_channel_count(unsigned channels)
static inline bool audio_format_valid(const struct audio_format *af)
{
return audio_valid_sample_rate(af->sample_rate) &&
audio_valid_sample_format((enum sample_format)af->format) &&
audio_valid_sample_format(af->bits) &&
audio_valid_channel_count(af->channels);
}
/**
* Returns false if the format mask is not valid for playback with
* MPD. This function performs some basic validity checks.
*/
static inline bool audio_format_mask_valid(const struct audio_format *af)
{
return (af->sample_rate == 0 ||
audio_valid_sample_rate(af->sample_rate)) &&
(af->format == SAMPLE_FORMAT_UNDEFINED ||
audio_valid_sample_format((enum sample_format)af->format)) &&
(af->channels == 0 || audio_valid_channel_count(af->channels));
}
static inline bool audio_format_equals(const struct audio_format *a,
const struct audio_format *b)
{
return a->sample_rate == b->sample_rate &&
a->format == b->format &&
a->channels == b->channels &&
a->reverse_endian == b->reverse_endian;
}
static inline void
audio_format_mask_apply(struct audio_format *af,
const struct audio_format *mask)
{
if (mask->sample_rate != 0)
af->sample_rate = mask->sample_rate;
if (mask->format != SAMPLE_FORMAT_UNDEFINED)
af->format = mask->format;
if (mask->channels != 0)
af->channels = mask->channels;
a->bits == b->bits &&
a->channels == b->channels;
}
/**
@@ -234,65 +96,28 @@ audio_format_mask_apply(struct audio_format *af,
*/
static inline unsigned audio_format_sample_size(const struct audio_format *af)
{
switch (af->format) {
case SAMPLE_FORMAT_S8:
if (af->bits <= 8)
return 1;
case SAMPLE_FORMAT_S16:
else if (af->bits <= 16)
return 2;
case SAMPLE_FORMAT_S24:
return 3;
case SAMPLE_FORMAT_S24_P32:
case SAMPLE_FORMAT_S32:
else
return 4;
case SAMPLE_FORMAT_UNDEFINED:
break;
}
return 0;
}
/**
* Returns the size of each full frame in bytes.
*/
static inline unsigned
audio_format_frame_size(const struct audio_format *af)
{
return audio_format_sample_size(af) * af->channels;
}
/**
* Returns the floating point factor which converts a time span to a
* storage size in bytes.
*/
static inline double audio_format_time_to_size(const struct audio_format *af)
{
return af->sample_rate * audio_format_frame_size(af);
}
/**
* Renders a #sample_format enum into a string, e.g. for printing it
* in a log file.
*
* @param format a #sample_format enum value
* @return the string
*/
const char *
sample_format_to_string(enum sample_format format);
/**
* Renders the #audio_format object into a string, e.g. for printing
* it in a log file.
*
* @param af the #audio_format object
* @param s a buffer to print into
* @return the string, or NULL if the #audio_format object is invalid
*/
const char *
audio_format_to_string(const struct audio_format *af,
struct audio_format_string *s);
static inline double audioFormatSizeToTime(const struct audio_format *af)
{
return 1.0 / audio_format_time_to_size(af);
}
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -22,13 +22,9 @@
*
*/
#include "config.h"
#include "audio_parser.h"
#include "audio_format.h"
#include "audio_check.h"
#include <assert.h>
#include <string.h>
#include <stdlib.h>
/**
@@ -40,158 +36,64 @@ audio_parser_quark(void)
return g_quark_from_static_string("audio_parser");
}
static bool
parse_sample_rate(const char *src, bool mask, uint32_t *sample_rate_r,
const char **endptr_r, GError **error_r)
{
unsigned long value;
char *endptr;
if (mask && *src == '*') {
*sample_rate_r = 0;
*endptr_r = src + 1;
return true;
}
value = strtoul(src, &endptr, 10);
if (endptr == src) {
g_set_error(error_r, audio_parser_quark(), 0,
"Failed to parse the sample rate");
return false;
} else if (!audio_check_sample_rate(value, error_r))
return false;
*sample_rate_r = value;
*endptr_r = endptr;
return true;
}
static bool
parse_sample_format(const char *src, bool mask,
enum sample_format *sample_format_r,
const char **endptr_r, GError **error_r)
{
unsigned long value;
char *endptr;
enum sample_format sample_format;
if (mask && *src == '*') {
*sample_format_r = SAMPLE_FORMAT_UNDEFINED;
*endptr_r = src + 1;
return true;
}
value = strtoul(src, &endptr, 10);
if (endptr == src) {
g_set_error(error_r, audio_parser_quark(), 0,
"Failed to parse the sample format");
return false;
}
switch (value) {
case 8:
sample_format = SAMPLE_FORMAT_S8;
break;
case 16:
sample_format = SAMPLE_FORMAT_S16;
break;
case 24:
if (memcmp(endptr, "_3", 2) == 0) {
sample_format = SAMPLE_FORMAT_S24;
endptr += 2;
} else
sample_format = SAMPLE_FORMAT_S24_P32;
break;
case 32:
sample_format = SAMPLE_FORMAT_S32;
break;
default:
g_set_error(error_r, audio_parser_quark(), 0,
"Invalid sample format: %lu", value);
return false;
}
assert(audio_valid_sample_format(sample_format));
*sample_format_r = sample_format;
*endptr_r = endptr;
return true;
}
static bool
parse_channel_count(const char *src, bool mask, uint8_t *channels_r,
const char **endptr_r, GError **error_r)
{
unsigned long value;
char *endptr;
if (mask && *src == '*') {
*channels_r = 0;
*endptr_r = src + 1;
return true;
}
value = strtoul(src, &endptr, 10);
if (endptr == src) {
g_set_error(error_r, audio_parser_quark(), 0,
"Failed to parse the channel count");
return false;
} else if (!audio_check_channel_count(value, error_r))
return false;
*channels_r = value;
*endptr_r = endptr;
return true;
}
bool
audio_format_parse(struct audio_format *dest, const char *src,
bool mask, GError **error_r)
audio_format_parse(struct audio_format *dest, const char *src, GError **error)
{
uint32_t rate;
enum sample_format sample_format;
uint8_t channels;
char *endptr;
unsigned long value;
audio_format_clear(dest);
/* parse sample rate */
if (!parse_sample_rate(src, mask, &rate, &src, error_r))
value = strtoul(src, &endptr, 10);
if (endptr == src) {
g_set_error(error, audio_parser_quark(), 0,
"Sample rate missing");
return false;
if (*src++ != ':') {
g_set_error(error_r, audio_parser_quark(), 0,
} else if (*endptr != ':') {
g_set_error(error, audio_parser_quark(), 0,
"Sample format missing");
return false;
} else if (!audio_valid_sample_rate(value)) {
g_set_error(error, audio_parser_quark(), 0,
"Invalid sample rate: %lu", value);
return false;
}
dest->sample_rate = value;
/* parse sample format */
if (!parse_sample_format(src, mask, &sample_format, &src, error_r))
src = endptr + 1;
value = strtoul(src, &endptr, 10);
if (endptr == src) {
g_set_error(error, audio_parser_quark(), 0,
"Sample format missing");
return false;
if (*src++ != ':') {
g_set_error(error_r, audio_parser_quark(), 0,
} else if (*endptr != ':') {
g_set_error(error, audio_parser_quark(), 0,
"Channel count missing");
return false;
} else if (!audio_valid_sample_format(value)) {
g_set_error(error, audio_parser_quark(), 0,
"Invalid sample format: %lu", value);
return false;
}
dest->bits = value;
/* parse channel count */
if (!parse_channel_count(src, mask, &channels, &src, error_r))
return false;
if (*src != 0) {
g_set_error(error_r, audio_parser_quark(), 0,
"Extra data after channel count: %s", src);
src = endptr + 1;
value = strtoul(src, &endptr, 10);
if (*endptr != 0 || !audio_valid_channel_count(value)) {
g_set_error(error, audio_parser_quark(), 0,
"Invalid channel count: %s", src);
return false;
}
audio_format_init(dest, rate, sample_format, channels);
dest->channels = value;
return true;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -37,13 +37,11 @@ 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 location to store the error occuring, or NULL to
* ignore errors
* @return true on success
*/
bool
audio_format_parse(struct audio_format *dest, const char *src,
bool mask, GError **error_r);
audio_format_parse(struct audio_format *dest, const char *src, GError **error);
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -17,7 +17,6 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "buffer.h"
#include "chunk.h"
#include "poison.h"
@@ -118,9 +117,6 @@ music_buffer_return(struct music_buffer *buffer, struct music_chunk *chunk)
assert(buffer != NULL);
assert(chunk != NULL);
if (chunk->other != NULL)
music_buffer_return(buffer, chunk->other);
g_mutex_lock(buffer->mutex);
music_chunk_free(chunk);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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

133
src/buffer2array.c Normal file
View File

@@ -0,0 +1,133 @@
/*
* Copyright (C) 2003-2009 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "buffer2array.h"
#include <glib.h>
#include <string.h>
int buffer2array(char *buffer, char *array[], const int max)
{
int i = 0;
char *c = buffer;
while (*c != '\0' && i < max) {
if (*c == '\"') {
array[i++] = ++c;
while (*c != '\0') {
if (*c == '\"') {
*(c++) = '\0';
break;
}
else if (*(c++) == '\\' && *c != '\0') {
memmove(c - 1, c, strlen(c) + 1);
}
}
} else {
c = g_strchug(c);
if (*c == '\0')
return i;
array[i++] = c++;
while (!g_ascii_isspace(*c) && *c != '\0')
++c;
}
if (*c == '\0')
return i;
*(c++) = '\0';
c = g_strchug(c);
}
return i;
}
#ifdef UNIT_TEST
#include <stdio.h>
#include <string.h>
#include <assert.h>
int main()
{
char *a[4] = { NULL };
char *b;
int max;
b = strdup("lsinfo \"/some/dir/name \\\"test\\\"\"");
assert(b);
max = buffer2array(b, a, 4);
assert( !strcmp("lsinfo", a[0]) );
assert( !strcmp("/some/dir/name \"test\"", a[1]) );
assert( !a[2] );
b = strdup("lsinfo \"/some/dir/name \\\"test\\\" something else\"");
assert(b);
max = buffer2array(b, a, 4);
assert( !strcmp("lsinfo", a[0]) );
assert( !strcmp("/some/dir/name \"test\" something else", a[1]) );
assert( !a[2] );
b = strdup("lsinfo \"/some/dir\\\\name\"");
assert(b);
max = buffer2array(b, a, 4);
assert( !strcmp("lsinfo", a[0]) );
assert( !strcmp("/some/dir\\name", a[1]) );
assert( !a[2] );
b = strdup("lsinfo \"/some/dir name\"");
assert(b);
max = buffer2array(b, a, 4);
assert( !strcmp("lsinfo", a[0]) );
assert( !strcmp("/some/dir name", a[1]) );
assert( !a[2] );
b = strdup("lsinfo \"\\\"/some/dir\\\"\"");
assert(b);
max = buffer2array(b, a, 4);
assert( !strcmp("lsinfo", a[0]) );
assert( !strcmp("\"/some/dir\"", a[1]) );
assert( !a[2] );
b = strdup("lsinfo \"\\\"/some/dir\\\" x\"");
assert(b);
max = buffer2array(b, a, 4);
assert( !strcmp("lsinfo", a[0]) );
assert( !strcmp("\"/some/dir\" x", a[1]) );
assert( !a[2] );
b = strdup("lsinfo \"single quote\\'d from php magicquotes\"");
assert(b);
max = buffer2array(b, a, 4);
assert( !strcmp("lsinfo", a[0]) );
assert( !strcmp("single quote\'d from php magicquotes", a[1]) );
assert( !a[2] );
b = strdup("lsinfo \"double quote\\\"d from php magicquotes\"");
assert(b);
max = buffer2array(b, a, 4);
assert( !strcmp("lsinfo", a[0]) );
assert( !strcmp("double quote\"d from php magicquotes", a[1]) );
assert( !a[2] );
return 0;
}
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -17,18 +17,15 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef AUTOCONVERT_FILTER_PLUGIN_H
#define AUTOCONVERT_FILTER_PLUGIN_H
#ifndef MPD_BUFFER_2_ARRAY_H
#define MPD_BUFFER_2_ARRAY_H
struct filter;
/**
* Creates a new "autoconvert" filter. When opened, it ensures that
* the input audio format isn't changed. If the underlying filter
* requests a different format, it automatically creates a
* convert_filter.
/* tokenizes up to max elements in buffer (a null-terminated string) and
* stores the result in array (which must be capable of holding up to
* max elements). Tokenization is based on C string quoting rules.
* The arguments buffer and array are modified.
* Returns the number of elements tokenized.
*/
struct filter *
autoconvert_filter_new(struct filter *filter);
int buffer2array(char *buffer, char *array[], const int max);
#endif

View File

@@ -1,47 +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_CHECK_H
#define MPD_CHECK_H
/*
* All sources must include config.h on the first line to ensure that
* Large File Support is configured properly. This header checks
* whether this has happened.
*
* Usage: include this header before you use any of the above types.
* It will stop the compiler if something went wrong.
*
* This is Linux/glibc specific, and only enabled in the debug build,
* so bugs in this headers don't affect users with production builds.
*
*/
#ifndef PACKAGE_VERSION
#error config.h missing
#endif
#if defined(__linux__) && !defined(NDEBUG) && defined(ENABLE_LARGEFILE) && \
defined(_FEATURES_H) && defined(__i386__) && \
!defined(__USE_FILE_OFFSET64)
/* on i386, check if LFS is enabled */
#error config.h was included too late
#endif
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -17,7 +17,6 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "chunk.h"
#include "audio_format.h"
#include "tag.h"
@@ -27,10 +26,8 @@
void
music_chunk_init(struct music_chunk *chunk)
{
chunk->other = NULL;
chunk->length = 0;
chunk->tag = NULL;
chunk->replay_gain_serial = 0;
}
void

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -20,8 +20,6 @@
#ifndef MPD_CHUNK_H
#define MPD_CHUNK_H
#include "replay_gain_info.h"
#ifndef NDEBUG
#include "audio_format.h"
#endif
@@ -44,18 +42,6 @@ struct music_chunk {
/** the next chunk in a linked list */
struct music_chunk *next;
/**
* An optional chunk which should be mixed into this chunk.
* This is used for cross-fading.
*/
struct music_chunk *other;
/**
* The current mix ratio for cross-fading: 1.0 means play 100%
* of this chunk, 0.0 means play 100% of the "other" chunk.
*/
float mix_ratio;
/** number of bytes stored in this chunk */
uint16_t length;
@@ -73,19 +59,6 @@ struct music_chunk {
*/
struct tag *tag;
/**
* Replay gain information associated with this chunk.
* Only valid if the serial is not 0.
*/
struct replay_gain_info replay_gain_info;
/**
* A serial number for checking if replay gain info has
* changed since the last chunk. The magic value 0 indicates
* that there is no replay gain info available.
*/
unsigned replay_gain_serial;
/** the data (probably PCM) */
char data[CHUNK_SIZE];

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -17,8 +17,110 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "client.h"
#include "fifo_buffer.h"
#include "command.h"
#include "conf.h"
#include "listen.h"
#include "socket_util.h"
#include "permission.h"
#include "event_pipe.h"
#include "idle.h"
#include "main.h"
#include "config.h"
#include "client_internal.h"
#include <glib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#ifdef WIN32
#include <ws2tcpip.h>
#include <winsock.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "client"
#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO
static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n";
#define CLIENT_LIST_MODE_BEGIN "command_list_begin"
#define CLIENT_LIST_OK_MODE_BEGIN "command_list_ok_begin"
#define CLIENT_LIST_MODE_END "command_list_end"
#define CLIENT_TIMEOUT_DEFAULT (60)
#define CLIENT_MAX_CONNECTIONS_DEFAULT (10)
#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024)
#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024)
/* set this to zero to indicate we have no possible clients */
static unsigned int client_max_connections; /*CLIENT_MAX_CONNECTIONS_DEFAULT; */
static int client_timeout;
static size_t client_max_command_list_size;
static size_t client_max_output_buffer_size;
struct deferred_buffer {
size_t size;
char data[sizeof(long)];
};
struct client {
GIOChannel *channel;
guint source_id;
/** the buffer for reading lines from the #channel */
struct fifo_buffer *input;
unsigned permission;
/** the uid of the client process, or -1 if unknown */
int uid;
/**
* How long since the last activity from this client?
*/
GTimer *last_activity;
GSList *cmd_list; /* for when in list mode */
int cmd_list_OK; /* print OK after each command execution */
size_t cmd_list_size; /* mem cmd_list consumes */
GQueue *deferred_send; /* for output if client is slow */
size_t deferred_bytes; /* mem deferred_send consumes */
unsigned int num; /* client number */
char send_buf[4096];
size_t send_buf_used; /* bytes used this instance */
/** is this client waiting for an "idle" response? */
bool idle_waiting;
/** idle flags pending on this client, to be sent as soon as
the client enters "idle" */
unsigned idle_flags;
/** idle flags that the client wants to receive */
unsigned idle_subscriptions;
};
static GList *clients;
static unsigned num_clients;
static guint expire_source_id;
static void client_write_deferred(struct client *client);
static void client_write_output(struct client *client);
static void client_manager_expire(void);
static gboolean
client_in_event(GIOChannel *source, GIOCondition condition, gpointer data);
bool client_is_expired(const struct client *client)
{
@@ -39,3 +141,782 @@ void client_set_permission(struct client *client, unsigned permission)
{
client->permission = permission;
}
/**
* An idle event which calls client_manager_expire().
*/
static gboolean
client_manager_expire_event(G_GNUC_UNUSED gpointer data)
{
expire_source_id = 0;
client_manager_expire();
return false;
}
static inline void client_set_expired(struct client *client)
{
if (expire_source_id == 0 && !client_is_expired(client))
/* delayed deletion */
expire_source_id = g_idle_add(client_manager_expire_event,
NULL);
if (client->source_id != 0) {
g_source_remove(client->source_id);
client->source_id = 0;
}
if (client->channel != NULL) {
g_io_channel_unref(client->channel);
client->channel = NULL;
}
}
static void client_init(struct client *client, int fd)
{
static unsigned int next_client_num;
assert(fd >= 0);
client->cmd_list_size = 0;
client->cmd_list_OK = -1;
#ifndef G_OS_WIN32
client->channel = g_io_channel_unix_new(fd);
#else
client->channel = g_io_channel_win32_new_socket(fd);
#endif
/* GLib is responsible for closing the file descriptor */
g_io_channel_set_close_on_unref(client->channel, true);
/* NULL encoding means the stream is binary safe; the MPD
protocol is UTF-8 only, but we are doing this call anyway
to prevent GLib from messing around with the stream */
g_io_channel_set_encoding(client->channel, NULL, NULL);
/* we prefer to do buffering */
g_io_channel_set_buffered(client->channel, false);
client->source_id = g_io_add_watch(client->channel,
G_IO_IN|G_IO_ERR|G_IO_HUP,
client_in_event, client);
client->input = fifo_buffer_new(4096);
client->cmd_list = NULL;
client->deferred_send = g_queue_new();
client->deferred_bytes = 0;
client->num = next_client_num++;
client->send_buf_used = 0;
client->permission = getDefaultPermissions();
(void)write(fd, GREETING, sizeof(GREETING) - 1);
}
static void free_cmd_list(GSList *list)
{
for (GSList *tmp = list; tmp != NULL; tmp = g_slist_next(tmp))
g_free(tmp->data);
g_slist_free(list);
}
static void new_cmd_list_ptr(struct client *client, char *s)
{
client->cmd_list = g_slist_prepend(client->cmd_list, g_strdup(s));
}
static void
deferred_buffer_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
{
struct deferred_buffer *buffer = data;
g_free(buffer);
}
static void client_close(struct client *client)
{
assert(num_clients > 0);
assert(clients != NULL);
clients = g_list_remove(clients, client);
--num_clients;
client_set_expired(client);
g_timer_destroy(client->last_activity);
if (client->cmd_list) {
free_cmd_list(client->cmd_list);
client->cmd_list = NULL;
}
g_queue_foreach(client->deferred_send, deferred_buffer_free, NULL);
g_queue_free(client->deferred_send);
fifo_buffer_free(client->input);
g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
"[%u] closed", client->num);
g_free(client);
}
void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid)
{
struct client *client;
char *remote;
if (num_clients >= client_max_connections) {
g_warning("Max Connections Reached!");
close(fd);
return;
}
client = g_new0(struct client, 1);
clients = g_list_prepend(clients, client);
++num_clients;
client_init(client, fd);
client->uid = uid;
client->last_activity = g_timer_new();
remote = sockaddr_to_string(sa, sa_length, NULL);
g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
"[%u] opened from %s", client->num, remote);
g_free(remote);
}
static int client_process_line(struct client *client, char *line)
{
int ret = 1;
if (strcmp(line, "noidle") == 0) {
if (client->idle_waiting) {
/* send empty idle response and leave idle mode */
client->idle_waiting = false;
command_success(client);
client_write_output(client);
}
/* do nothing if the client wasn't idling: the client
has already received the full idle response from
client_idle_notify(), which he can now evaluate */
return 0;
} else if (client->idle_waiting) {
/* during idle mode, clients must not send anything
except "noidle" */
g_warning("[%u] command \"%s\" during idle",
client->num, line);
return COMMAND_RETURN_CLOSE;
}
if (client->cmd_list_OK >= 0) {
if (strcmp(line, CLIENT_LIST_MODE_END) == 0) {
g_debug("[%u] process command list",
client->num);
/* for scalability reasons, we have prepended
each new command; now we have to reverse it
to restore the correct order */
client->cmd_list = g_slist_reverse(client->cmd_list);
ret = command_process_list(client,
client->cmd_list_OK,
client->cmd_list);
g_debug("[%u] process command "
"list returned %i", client->num, ret);
if (ret == COMMAND_RETURN_CLOSE ||
client_is_expired(client))
return COMMAND_RETURN_CLOSE;
if (ret == 0)
command_success(client);
client_write_output(client);
free_cmd_list(client->cmd_list);
client->cmd_list = NULL;
client->cmd_list_OK = -1;
} else {
size_t len = strlen(line) + 1;
client->cmd_list_size += len;
if (client->cmd_list_size >
client_max_command_list_size) {
g_warning("[%u] command list size (%lu) "
"is larger than the max (%lu)",
client->num,
(unsigned long)client->cmd_list_size,
(unsigned long)client_max_command_list_size);
return COMMAND_RETURN_CLOSE;
} else
new_cmd_list_ptr(client, line);
}
} else {
if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) {
client->cmd_list_OK = 0;
ret = 1;
} else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) {
client->cmd_list_OK = 1;
ret = 1;
} else {
g_debug("[%u] process command \"%s\"",
client->num, line);
ret = command_process(client, line);
g_debug("[%u] command returned %i",
client->num, ret);
if (ret == COMMAND_RETURN_CLOSE ||
client_is_expired(client))
return COMMAND_RETURN_CLOSE;
if (ret == 0)
command_success(client);
client_write_output(client);
}
}
return ret;
}
static char *
client_read_line(struct client *client)
{
const char *p, *newline;
size_t length;
char *line;
p = fifo_buffer_read(client->input, &length);
if (p == NULL)
return NULL;
newline = memchr(p, '\n', length);
if (newline == NULL)
return NULL;
line = g_strndup(p, newline - p);
fifo_buffer_consume(client->input, newline - p + 1);
return g_strchomp(line);
}
static int client_input_received(struct client *client, size_t bytesRead)
{
char *line;
int ret;
fifo_buffer_append(client->input, bytesRead);
/* process all lines */
while ((line = client_read_line(client)) != NULL) {
ret = client_process_line(client, line);
g_free(line);
if (ret == COMMAND_RETURN_KILL ||
ret == COMMAND_RETURN_CLOSE)
return ret;
if (client_is_expired(client))
return COMMAND_RETURN_CLOSE;
}
return 0;
}
static int client_read(struct client *client)
{
char *p;
size_t max_length;
GError *error = NULL;
GIOStatus status;
gsize bytes_read;
assert(client != NULL);
assert(client->channel != NULL);
p = fifo_buffer_write(client->input, &max_length);
if (p == NULL) {
g_warning("[%u] buffer overflow", client->num);
return COMMAND_RETURN_CLOSE;
}
status = g_io_channel_read_chars(client->channel, p, max_length,
&bytes_read, &error);
switch (status) {
case G_IO_STATUS_NORMAL:
return client_input_received(client, bytes_read);
case G_IO_STATUS_AGAIN:
/* try again later, after select() */
return 0;
case G_IO_STATUS_EOF:
/* peer disconnected */
return COMMAND_RETURN_CLOSE;
case G_IO_STATUS_ERROR:
/* I/O error */
g_warning("failed to read from client %d: %s",
client->num, error->message);
g_error_free(error);
return COMMAND_RETURN_CLOSE;
}
/* unreachable */
return COMMAND_RETURN_CLOSE;
}
static gboolean
client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
gpointer data);
static gboolean
client_in_event(G_GNUC_UNUSED GIOChannel *source,
GIOCondition condition,
gpointer data)
{
struct client *client = data;
int ret;
assert(!client_is_expired(client));
if (condition != G_IO_IN) {
client_set_expired(client);
return false;
}
g_timer_start(client->last_activity);
ret = client_read(client);
switch (ret) {
case COMMAND_RETURN_KILL:
client_close(client);
g_main_loop_quit(main_loop);
return false;
case COMMAND_RETURN_CLOSE:
client_close(client);
return false;
}
if (client_is_expired(client)) {
client_close(client);
return false;
}
if (!g_queue_is_empty(client->deferred_send)) {
/* deferred buffers exist: schedule write */
client->source_id = g_io_add_watch(client->channel,
G_IO_OUT|G_IO_ERR|G_IO_HUP,
client_out_event, client);
return false;
}
/* read more */
return true;
}
static gboolean
client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
gpointer data)
{
struct client *client = data;
assert(!client_is_expired(client));
if (condition != G_IO_OUT) {
client_set_expired(client);
return false;
}
client_write_deferred(client);
if (client_is_expired(client)) {
client_close(client);
return false;
}
g_timer_start(client->last_activity);
if (g_queue_is_empty(client->deferred_send)) {
/* done sending deferred buffers exist: schedule
read */
client->source_id = g_io_add_watch(client->channel,
G_IO_IN|G_IO_ERR|G_IO_HUP,
client_in_event, client);
return false;
}
/* write more */
return true;
}
void client_manager_init(void)
{
client_timeout = config_get_positive(CONF_CONN_TIMEOUT,
CLIENT_TIMEOUT_DEFAULT);
client_max_connections =
config_get_positive(CONF_MAX_CONN,
CLIENT_MAX_CONNECTIONS_DEFAULT);
client_max_command_list_size =
config_get_positive(CONF_MAX_COMMAND_LIST_SIZE,
CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024)
* 1024;
client_max_output_buffer_size =
config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE,
CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024)
* 1024;
}
static void client_close_all(void)
{
while (clients != NULL) {
struct client *client = clients->data;
client_close(client);
}
assert(num_clients == 0);
}
void client_manager_deinit(void)
{
client_close_all();
client_max_connections = 0;
if (expire_source_id != 0)
g_source_remove(expire_source_id);
}
static void
client_check_expired_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
{
struct client *client = data;
if (client_is_expired(client)) {
g_debug("[%u] expired", client->num);
client_close(client);
} else if (!client->idle_waiting && /* idle clients
never expire */
(int)g_timer_elapsed(client->last_activity, NULL) >
client_timeout) {
g_debug("[%u] timeout", client->num);
client_close(client);
}
}
static void
client_manager_expire(void)
{
g_list_foreach(clients, client_check_expired_callback, NULL);
}
static size_t
client_write_deferred_buffer(struct client *client,
const struct deferred_buffer *buffer)
{
GError *error = NULL;
GIOStatus status;
gsize bytes_written;
assert(client != NULL);
assert(client->channel != NULL);
assert(buffer != NULL);
status = g_io_channel_write_chars
(client->channel, buffer->data, buffer->size,
&bytes_written, &error);
switch (status) {
case G_IO_STATUS_NORMAL:
return bytes_written;
case G_IO_STATUS_AGAIN:
return 0;
case G_IO_STATUS_EOF:
/* client has disconnected */
client_set_expired(client);
return 0;
case G_IO_STATUS_ERROR:
/* I/O error */
client_set_expired(client);
g_warning("failed to flush buffer for %i: %s",
client->num, error->message);
g_error_free(error);
return 0;
}
/* unreachable */
return 0;
}
static void client_write_deferred(struct client *client)
{
size_t ret;
while (!g_queue_is_empty(client->deferred_send)) {
struct deferred_buffer *buf =
g_queue_peek_head(client->deferred_send);
assert(buf->size > 0);
assert(buf->size <= client->deferred_bytes);
ret = client_write_deferred_buffer(client, buf);
if (ret == 0)
break;
if (ret < buf->size) {
assert(client->deferred_bytes >= (size_t)ret);
client->deferred_bytes -= ret;
buf->size -= ret;
memmove(buf->data, buf->data + ret, buf->size);
break;
} else {
size_t decr = sizeof(*buf) -
sizeof(buf->data) + buf->size;
assert(client->deferred_bytes >= decr);
client->deferred_bytes -= decr;
g_free(buf);
g_queue_pop_head(client->deferred_send);
}
g_timer_start(client->last_activity);
}
if (g_queue_is_empty(client->deferred_send)) {
g_debug("[%u] buffer empty %lu", client->num,
(unsigned long)client->deferred_bytes);
assert(client->deferred_bytes == 0);
}
}
static void client_defer_output(struct client *client,
const void *data, size_t length)
{
size_t alloc;
struct deferred_buffer *buf;
assert(length > 0);
alloc = sizeof(*buf) - sizeof(buf->data) + length;
client->deferred_bytes += alloc;
if (client->deferred_bytes > client_max_output_buffer_size) {
g_warning("[%u] output buffer size (%lu) is "
"larger than the max (%lu)",
client->num,
(unsigned long)client->deferred_bytes,
(unsigned long)client_max_output_buffer_size);
/* cause client to close */
client_set_expired(client);
return;
}
buf = g_malloc(alloc);
buf->size = length;
memcpy(buf->data, data, length);
g_queue_push_tail(client->deferred_send, buf);
}
static void client_write_direct(struct client *client,
const char *data, size_t length)
{
GError *error = NULL;
GIOStatus status;
gsize bytes_written;
assert(client != NULL);
assert(client->channel != NULL);
assert(data != NULL);
assert(length > 0);
assert(g_queue_is_empty(client->deferred_send));
status = g_io_channel_write_chars(client->channel, data, length,
&bytes_written, &error);
switch (status) {
case G_IO_STATUS_NORMAL:
case G_IO_STATUS_AGAIN:
break;
case G_IO_STATUS_EOF:
/* client has disconnected */
client_set_expired(client);
return;
case G_IO_STATUS_ERROR:
/* I/O error */
client_set_expired(client);
g_warning("failed to write to %i: %s",
client->num, error->message);
g_error_free(error);
return;
}
if (bytes_written < length)
client_defer_output(client, data + bytes_written,
length - bytes_written);
if (!g_queue_is_empty(client->deferred_send))
g_debug("[%u] buffer created", client->num);
}
static void client_write_output(struct client *client)
{
if (client_is_expired(client) || !client->send_buf_used)
return;
if (!g_queue_is_empty(client->deferred_send)) {
client_defer_output(client, client->send_buf,
client->send_buf_used);
if (client_is_expired(client))
return;
/* try to flush the deferred buffers now; the current
server command may take too long to finish, and
meanwhile try to feed output to the client,
otherwise it will time out. One reason why
deferring is slow might be that currently each
client_write() allocates a new deferred buffer.
This should be optimized after MPD 0.14. */
client_write_deferred(client);
} else
client_write_direct(client, client->send_buf,
client->send_buf_used);
client->send_buf_used = 0;
}
/**
* Write a block of data to the client.
*/
static void client_write(struct client *client, const char *buffer, size_t buflen)
{
/* if the client is going to be closed, do nothing */
if (client_is_expired(client))
return;
while (buflen > 0 && !client_is_expired(client)) {
size_t copylen;
assert(client->send_buf_used < sizeof(client->send_buf));
copylen = sizeof(client->send_buf) - client->send_buf_used;
if (copylen > buflen)
copylen = buflen;
memcpy(client->send_buf + client->send_buf_used, buffer,
copylen);
buflen -= copylen;
client->send_buf_used += copylen;
buffer += copylen;
if (client->send_buf_used >= sizeof(client->send_buf))
client_write_output(client);
}
}
void client_puts(struct client *client, const char *s)
{
client_write(client, s, strlen(s));
}
void client_vprintf(struct client *client, const char *fmt, va_list args)
{
va_list tmp;
int length;
char *buffer;
va_copy(tmp, args);
length = vsnprintf(NULL, 0, fmt, tmp);
va_end(tmp);
if (length <= 0)
/* wtf.. */
return;
buffer = g_malloc(length + 1);
vsnprintf(buffer, length + 1, fmt, args);
client_write(client, buffer, length);
g_free(buffer);
}
G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
client_vprintf(client, fmt, args);
va_end(args);
}
/**
* Send "idle" response to this client.
*/
static void
client_idle_notify(struct client *client)
{
unsigned flags, i;
const char *const* idle_names;
assert(client->idle_waiting);
assert(client->idle_flags != 0);
flags = client->idle_flags;
client->idle_flags = 0;
client->idle_waiting = false;
idle_names = idle_get_names();
for (i = 0; idle_names[i]; ++i) {
if (flags & (1 << i) & client->idle_subscriptions)
client_printf(client, "changed: %s\n",
idle_names[i]);
}
client_puts(client, "OK\n");
g_timer_start(client->last_activity);
}
static void
client_idle_callback(gpointer data, gpointer user_data)
{
struct client *client = data;
unsigned flags = GPOINTER_TO_UINT(user_data);
if (client_is_expired(client))
return;
client->idle_flags |= flags;
if (client->idle_waiting
&& (client->idle_flags & client->idle_subscriptions)) {
client_idle_notify(client);
client_write_output(client);
}
}
void client_manager_idle_add(unsigned flags)
{
assert(flags != 0);
g_list_foreach(clients, client_idle_callback, GUINT_TO_POINTER(flags));
}
bool client_idle_wait(struct client *client, unsigned flags)
{
assert(!client->idle_waiting);
client->idle_waiting = true;
client->idle_subscriptions = flags;
if (client->idle_flags & client->idle_subscriptions) {
client_idle_notify(client);
return true;
} else
return false;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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

View File

@@ -1,108 +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 "client_internal.h"
#include "main.h"
#include <assert.h>
static gboolean
client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
gpointer data)
{
struct client *client = data;
assert(!client_is_expired(client));
if (condition != G_IO_OUT) {
client_set_expired(client);
return false;
}
client_write_deferred(client);
if (client_is_expired(client)) {
client_close(client);
return false;
}
g_timer_start(client->last_activity);
if (g_queue_is_empty(client->deferred_send)) {
/* done sending deferred buffers exist: schedule
read */
client->source_id = g_io_add_watch(client->channel,
G_IO_IN|G_IO_ERR|G_IO_HUP,
client_in_event, client);
return false;
}
/* write more */
return true;
}
gboolean
client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
gpointer data)
{
struct client *client = data;
enum command_return ret;
assert(!client_is_expired(client));
if (condition != G_IO_IN) {
client_set_expired(client);
return false;
}
g_timer_start(client->last_activity);
ret = client_read(client);
switch (ret) {
case COMMAND_RETURN_OK:
case COMMAND_RETURN_ERROR:
break;
case COMMAND_RETURN_KILL:
client_close(client);
g_main_loop_quit(main_loop);
return false;
case COMMAND_RETURN_CLOSE:
client_close(client);
return false;
}
if (client_is_expired(client)) {
client_close(client);
return false;
}
if (!g_queue_is_empty(client->deferred_send)) {
/* deferred buffers exist: schedule write */
client->source_id = g_io_add_watch(client->channel,
G_IO_OUT|G_IO_ERR|G_IO_HUP,
client_out_event, client);
return false;
}
/* read more */
return true;
}

View File

@@ -1,90 +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 "client_internal.h"
static guint expire_source_id;
void
client_set_expired(struct client *client)
{
if (!client_is_expired(client))
client_schedule_expire();
if (client->source_id != 0) {
g_source_remove(client->source_id);
client->source_id = 0;
}
if (client->channel != NULL) {
g_io_channel_unref(client->channel);
client->channel = NULL;
}
}
static void
client_check_expired_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
{
struct client *client = data;
if (client_is_expired(client)) {
g_debug("[%u] expired", client->num);
client_close(client);
} else if (!client->idle_waiting && /* idle clients
never expire */
(int)g_timer_elapsed(client->last_activity, NULL) >
client_timeout) {
g_debug("[%u] timeout", client->num);
client_close(client);
}
}
static void
client_manager_expire(void)
{
client_list_foreach(client_check_expired_callback, NULL);
}
/**
* An idle event which calls client_manager_expire().
*/
static gboolean
client_manager_expire_event(G_GNUC_UNUSED gpointer data)
{
expire_source_id = 0;
client_manager_expire();
return false;
}
void
client_schedule_expire(void)
{
if (expire_source_id == 0)
/* delayed deletion */
expire_source_id = g_idle_add(client_manager_expire_event,
NULL);
}
void
client_deinit_expire(void)
{
if (expire_source_id != 0)
g_source_remove(expire_source_id);
}

View File

@@ -1,73 +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 "client_internal.h"
#include "conf.h"
#include <assert.h>
#define CLIENT_TIMEOUT_DEFAULT (60)
#define CLIENT_MAX_CONNECTIONS_DEFAULT (10)
#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024)
#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024)
/* set this to zero to indicate we have no possible clients */
unsigned int client_max_connections;
int client_timeout;
size_t client_max_command_list_size;
size_t client_max_output_buffer_size;
void client_manager_init(void)
{
client_timeout = config_get_positive(CONF_CONN_TIMEOUT,
CLIENT_TIMEOUT_DEFAULT);
client_max_connections =
config_get_positive(CONF_MAX_CONN,
CLIENT_MAX_CONNECTIONS_DEFAULT);
client_max_command_list_size =
config_get_positive(CONF_MAX_COMMAND_LIST_SIZE,
CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024)
* 1024;
client_max_output_buffer_size =
config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE,
CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024)
* 1024;
}
static void client_close_all(void)
{
while (!client_list_is_empty()) {
struct client *client = client_list_get_first();
client_close(client);
}
assert(client_list_is_empty());
}
void client_manager_deinit(void)
{
client_close_all();
client_max_connections = 0;
client_deinit_expire();
}

View File

@@ -1,89 +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 "client_internal.h"
#include "idle.h"
#include <assert.h>
/**
* Send "idle" response to this client.
*/
static void
client_idle_notify(struct client *client)
{
unsigned flags, i;
const char *const* idle_names;
assert(client->idle_waiting);
assert(client->idle_flags != 0);
flags = client->idle_flags;
client->idle_flags = 0;
client->idle_waiting = false;
idle_names = idle_get_names();
for (i = 0; idle_names[i]; ++i) {
if (flags & (1 << i) & client->idle_subscriptions)
client_printf(client, "changed: %s\n",
idle_names[i]);
}
client_puts(client, "OK\n");
g_timer_start(client->last_activity);
}
static void
client_idle_callback(gpointer data, gpointer user_data)
{
struct client *client = data;
unsigned flags = GPOINTER_TO_UINT(user_data);
if (client_is_expired(client))
return;
client->idle_flags |= flags;
if (client->idle_waiting
&& (client->idle_flags & client->idle_subscriptions)) {
client_idle_notify(client);
client_write_output(client);
}
}
void client_manager_idle_add(unsigned flags)
{
assert(flags != 0);
client_list_foreach(client_idle_callback, GUINT_TO_POINTER(flags));
}
bool client_idle_wait(struct client *client, unsigned flags)
{
assert(!client->idle_waiting);
client->idle_waiting = true;
client->idle_subscriptions = flags;
if (client->idle_flags & client->idle_subscriptions) {
client_idle_notify(client);
return true;
} else
return false;
}

View File

@@ -1,145 +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_CLIENT_INTERNAL_H
#define MPD_CLIENT_INTERNAL_H
#include "client.h"
#include "command.h"
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "client"
struct deferred_buffer {
size_t size;
char data[sizeof(long)];
};
struct client {
GIOChannel *channel;
guint source_id;
/** the buffer for reading lines from the #channel */
struct fifo_buffer *input;
unsigned permission;
/** the uid of the client process, or -1 if unknown */
int uid;
/**
* How long since the last activity from this client?
*/
GTimer *last_activity;
GSList *cmd_list; /* for when in list mode */
int cmd_list_OK; /* print OK after each command execution */
size_t cmd_list_size; /* mem cmd_list consumes */
GQueue *deferred_send; /* for output if client is slow */
size_t deferred_bytes; /* mem deferred_send consumes */
unsigned int num; /* client number */
char send_buf[16384];
size_t send_buf_used; /* bytes used this instance */
/** is this client waiting for an "idle" response? */
bool idle_waiting;
/** idle flags pending on this client, to be sent as soon as
the client enters "idle" */
unsigned idle_flags;
/** idle flags that the client wants to receive */
unsigned idle_subscriptions;
};
extern unsigned int client_max_connections;
extern int client_timeout;
extern size_t client_max_command_list_size;
extern size_t client_max_output_buffer_size;
bool
client_list_is_empty(void);
bool
client_list_is_full(void);
struct client *
client_list_get_first(void);
void
client_list_add(struct client *client);
void
client_list_foreach(GFunc func, gpointer user_data);
void
client_list_remove(struct client *client);
void
client_close(struct client *client);
static inline void
new_cmd_list_ptr(struct client *client, const char *s)
{
client->cmd_list = g_slist_prepend(client->cmd_list, g_strdup(s));
}
static inline void
free_cmd_list(GSList *list)
{
for (GSList *tmp = list; tmp != NULL; tmp = g_slist_next(tmp))
g_free(tmp->data);
g_slist_free(list);
}
void
client_set_expired(struct client *client);
/**
* Schedule an "expired" check for all clients: permanently delete
* clients which have been set "expired" with client_set_expired().
*/
void
client_schedule_expire(void);
/**
* Removes a scheduled "expired" check.
*/
void
client_deinit_expire(void);
enum command_return
client_read(struct client *client);
enum command_return
client_process_line(struct client *client, char *line);
void
client_write_deferred(struct client *client);
void
client_write_output(struct client *client);
gboolean
client_in_event(GIOChannel *source, GIOCondition condition,
gpointer data);
#endif

View File

@@ -1,69 +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 "client_internal.h"
#include <assert.h>
static GList *clients;
static unsigned num_clients;
bool
client_list_is_empty(void)
{
return num_clients == 0;
}
bool
client_list_is_full(void)
{
return num_clients >= client_max_connections;
}
struct client *
client_list_get_first(void)
{
assert(clients != NULL);
return clients->data;
}
void
client_list_add(struct client *client)
{
clients = g_list_prepend(clients, client);
++num_clients;
}
void
client_list_foreach(GFunc func, gpointer user_data)
{
g_list_foreach(clients, func, user_data);
}
void
client_list_remove(struct client *client)
{
assert(num_clients > 0);
assert(clients != NULL);
clients = g_list_remove(clients, client);
--num_clients;
}

View File

@@ -1,159 +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 "client_internal.h"
#include "fifo_buffer.h"
#include "socket_util.h"
#include "permission.h"
#include <assert.h>
#include <sys/types.h>
#ifdef WIN32
#include <winsock2.h>
#else
#include <sys/socket.h>
#endif
#include <unistd.h>
#ifdef HAVE_LIBWRAP
#include <tcpd.h>
#endif
#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO
static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n";
void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid)
{
static unsigned int next_client_num;
struct client *client;
char *remote;
assert(fd >= 0);
#ifdef HAVE_LIBWRAP
if (sa->sa_family != AF_UNIX) {
char *hostaddr = sockaddr_to_string(sa, sa_length, NULL);
const char *progname = g_get_prgname();
struct request_info req;
request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0);
fromhost(&req);
if (!hosts_access(&req)) {
/* tcp wrappers says no */
g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
"libwrap refused connection (libwrap=%s) from %s",
progname, hostaddr);
g_free(hostaddr);
close(fd);
return;
}
g_free(hostaddr);
}
#endif /* HAVE_WRAP */
if (client_list_is_full()) {
g_warning("Max Connections Reached!");
close(fd);
return;
}
client = g_new0(struct client, 1);
#ifndef G_OS_WIN32
client->channel = g_io_channel_unix_new(fd);
#else
client->channel = g_io_channel_win32_new_socket(fd);
#endif
/* GLib is responsible for closing the file descriptor */
g_io_channel_set_close_on_unref(client->channel, true);
/* NULL encoding means the stream is binary safe; the MPD
protocol is UTF-8 only, but we are doing this call anyway
to prevent GLib from messing around with the stream */
g_io_channel_set_encoding(client->channel, NULL, NULL);
/* we prefer to do buffering */
g_io_channel_set_buffered(client->channel, false);
client->source_id = g_io_add_watch(client->channel,
G_IO_IN|G_IO_ERR|G_IO_HUP,
client_in_event, client);
client->input = fifo_buffer_new(4096);
client->permission = getDefaultPermissions();
client->uid = uid;
client->last_activity = g_timer_new();
client->cmd_list = NULL;
client->cmd_list_OK = -1;
client->cmd_list_size = 0;
client->deferred_send = g_queue_new();
client->deferred_bytes = 0;
client->num = next_client_num++;
client->send_buf_used = 0;
(void)send(fd, GREETING, sizeof(GREETING) - 1, 0);
client_list_add(client);
remote = sockaddr_to_string(sa, sa_length, NULL);
g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
"[%u] opened from %s", client->num, remote);
g_free(remote);
}
static void
deferred_buffer_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
{
struct deferred_buffer *buffer = data;
g_free(buffer);
}
void
client_close(struct client *client)
{
client_list_remove(client);
client_set_expired(client);
g_timer_destroy(client->last_activity);
if (client->cmd_list) {
free_cmd_list(client->cmd_list);
client->cmd_list = NULL;
}
g_queue_foreach(client->deferred_send, deferred_buffer_free, NULL);
g_queue_free(client->deferred_send);
fifo_buffer_free(client->input);
g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
"[%u] closed", client->num);
g_free(client);
}

View File

@@ -1,146 +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 "client_internal.h"
#include <string.h>
#define CLIENT_LIST_MODE_BEGIN "command_list_begin"
#define CLIENT_LIST_OK_MODE_BEGIN "command_list_ok_begin"
#define CLIENT_LIST_MODE_END "command_list_end"
static enum command_return
client_process_command_list(struct client *client, bool list_ok, GSList *list)
{
enum command_return ret = COMMAND_RETURN_OK;
unsigned num = 0;
for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) {
char *cmd = cur->data;
g_debug("command_process_list: process command \"%s\"",
cmd);
ret = command_process(client, num++, cmd);
g_debug("command_process_list: command returned %i", ret);
if (ret != COMMAND_RETURN_OK || client_is_expired(client))
break;
else if (list_ok)
client_puts(client, "list_OK\n");
}
return ret;
}
enum command_return
client_process_line(struct client *client, char *line)
{
enum command_return ret;
if (strcmp(line, "noidle") == 0) {
if (client->idle_waiting) {
/* send empty idle response and leave idle mode */
client->idle_waiting = false;
command_success(client);
client_write_output(client);
}
/* do nothing if the client wasn't idling: the client
has already received the full idle response from
client_idle_notify(), which he can now evaluate */
return COMMAND_RETURN_OK;
} else if (client->idle_waiting) {
/* during idle mode, clients must not send anything
except "noidle" */
g_warning("[%u] command \"%s\" during idle",
client->num, line);
return COMMAND_RETURN_CLOSE;
}
if (client->cmd_list_OK >= 0) {
if (strcmp(line, CLIENT_LIST_MODE_END) == 0) {
g_debug("[%u] process command list",
client->num);
/* for scalability reasons, we have prepended
each new command; now we have to reverse it
to restore the correct order */
client->cmd_list = g_slist_reverse(client->cmd_list);
ret = client_process_command_list(client,
client->cmd_list_OK,
client->cmd_list);
g_debug("[%u] process command "
"list returned %i", client->num, ret);
if (ret == COMMAND_RETURN_CLOSE ||
client_is_expired(client))
return COMMAND_RETURN_CLOSE;
if (ret == COMMAND_RETURN_OK)
command_success(client);
client_write_output(client);
free_cmd_list(client->cmd_list);
client->cmd_list = NULL;
client->cmd_list_OK = -1;
} else {
size_t len = strlen(line) + 1;
client->cmd_list_size += len;
if (client->cmd_list_size >
client_max_command_list_size) {
g_warning("[%u] command list size (%lu) "
"is larger than the max (%lu)",
client->num,
(unsigned long)client->cmd_list_size,
(unsigned long)client_max_command_list_size);
return COMMAND_RETURN_CLOSE;
}
new_cmd_list_ptr(client, line);
ret = COMMAND_RETURN_OK;
}
} else {
if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) {
client->cmd_list_OK = 0;
ret = COMMAND_RETURN_OK;
} else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) {
client->cmd_list_OK = 1;
ret = COMMAND_RETURN_OK;
} else {
g_debug("[%u] process command \"%s\"",
client->num, line);
ret = command_process(client, 0, line);
g_debug("[%u] command returned %i",
client->num, ret);
if (ret == COMMAND_RETURN_CLOSE ||
client_is_expired(client))
return COMMAND_RETURN_CLOSE;
if (ret == COMMAND_RETURN_OK)
command_success(client);
client_write_output(client);
}
}
return ret;
}

View File

@@ -1,113 +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 "client_internal.h"
#include "fifo_buffer.h"
#include <assert.h>
#include <string.h>
static char *
client_read_line(struct client *client)
{
const char *p, *newline;
size_t length;
char *line;
p = fifo_buffer_read(client->input, &length);
if (p == NULL)
return NULL;
newline = memchr(p, '\n', length);
if (newline == NULL)
return NULL;
line = g_strndup(p, newline - p);
fifo_buffer_consume(client->input, newline - p + 1);
return g_strchomp(line);
}
static enum command_return
client_input_received(struct client *client, size_t bytesRead)
{
char *line;
fifo_buffer_append(client->input, bytesRead);
/* process all lines */
while ((line = client_read_line(client)) != NULL) {
enum command_return ret = client_process_line(client, line);
g_free(line);
if (ret == COMMAND_RETURN_KILL ||
ret == COMMAND_RETURN_CLOSE)
return ret;
if (client_is_expired(client))
return COMMAND_RETURN_CLOSE;
}
return COMMAND_RETURN_OK;
}
enum command_return
client_read(struct client *client)
{
char *p;
size_t max_length;
GError *error = NULL;
GIOStatus status;
gsize bytes_read;
assert(client != NULL);
assert(client->channel != NULL);
p = fifo_buffer_write(client->input, &max_length);
if (p == NULL) {
g_warning("[%u] buffer overflow", client->num);
return COMMAND_RETURN_CLOSE;
}
status = g_io_channel_read_chars(client->channel, p, max_length,
&bytes_read, &error);
switch (status) {
case G_IO_STATUS_NORMAL:
return client_input_received(client, bytes_read);
case G_IO_STATUS_AGAIN:
/* try again later, after select() */
return COMMAND_RETURN_OK;
case G_IO_STATUS_EOF:
/* peer disconnected */
return COMMAND_RETURN_CLOSE;
case G_IO_STATUS_ERROR:
/* I/O error */
g_warning("failed to read from client %d: %s",
client->num, error->message);
g_error_free(error);
return COMMAND_RETURN_CLOSE;
}
/* unreachable */
return COMMAND_RETURN_CLOSE;
}

View File

@@ -1,284 +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 "client_internal.h"
#include <assert.h>
#include <string.h>
#include <stdio.h>
static size_t
client_write_deferred_buffer(struct client *client,
const struct deferred_buffer *buffer)
{
GError *error = NULL;
GIOStatus status;
gsize bytes_written;
assert(client != NULL);
assert(client->channel != NULL);
assert(buffer != NULL);
status = g_io_channel_write_chars
(client->channel, buffer->data, buffer->size,
&bytes_written, &error);
switch (status) {
case G_IO_STATUS_NORMAL:
return bytes_written;
case G_IO_STATUS_AGAIN:
return 0;
case G_IO_STATUS_EOF:
/* client has disconnected */
client_set_expired(client);
return 0;
case G_IO_STATUS_ERROR:
/* I/O error */
client_set_expired(client);
g_warning("failed to flush buffer for %i: %s",
client->num, error->message);
g_error_free(error);
return 0;
}
/* unreachable */
return 0;
}
void
client_write_deferred(struct client *client)
{
size_t ret;
while (!g_queue_is_empty(client->deferred_send)) {
struct deferred_buffer *buf =
g_queue_peek_head(client->deferred_send);
assert(buf->size > 0);
assert(buf->size <= client->deferred_bytes);
ret = client_write_deferred_buffer(client, buf);
if (ret == 0)
break;
if (ret < buf->size) {
assert(client->deferred_bytes >= (size_t)ret);
client->deferred_bytes -= ret;
buf->size -= ret;
memmove(buf->data, buf->data + ret, buf->size);
break;
} else {
size_t decr = sizeof(*buf) -
sizeof(buf->data) + buf->size;
assert(client->deferred_bytes >= decr);
client->deferred_bytes -= decr;
g_free(buf);
g_queue_pop_head(client->deferred_send);
}
g_timer_start(client->last_activity);
}
if (g_queue_is_empty(client->deferred_send)) {
g_debug("[%u] buffer empty %lu", client->num,
(unsigned long)client->deferred_bytes);
assert(client->deferred_bytes == 0);
}
}
static void client_defer_output(struct client *client,
const void *data, size_t length)
{
size_t alloc;
struct deferred_buffer *buf;
assert(length > 0);
alloc = sizeof(*buf) - sizeof(buf->data) + length;
client->deferred_bytes += alloc;
if (client->deferred_bytes > client_max_output_buffer_size) {
g_warning("[%u] output buffer size (%lu) is "
"larger than the max (%lu)",
client->num,
(unsigned long)client->deferred_bytes,
(unsigned long)client_max_output_buffer_size);
/* cause client to close */
client_set_expired(client);
return;
}
buf = g_malloc(alloc);
buf->size = length;
memcpy(buf->data, data, length);
g_queue_push_tail(client->deferred_send, buf);
}
static void client_write_direct(struct client *client,
const char *data, size_t length)
{
GError *error = NULL;
GIOStatus status;
gsize bytes_written;
assert(client != NULL);
assert(client->channel != NULL);
assert(data != NULL);
assert(length > 0);
assert(g_queue_is_empty(client->deferred_send));
status = g_io_channel_write_chars(client->channel, data, length,
&bytes_written, &error);
switch (status) {
case G_IO_STATUS_NORMAL:
case G_IO_STATUS_AGAIN:
break;
case G_IO_STATUS_EOF:
/* client has disconnected */
client_set_expired(client);
return;
case G_IO_STATUS_ERROR:
/* I/O error */
client_set_expired(client);
g_warning("failed to write to %i: %s",
client->num, error->message);
g_error_free(error);
return;
}
if (bytes_written < length)
client_defer_output(client, data + bytes_written,
length - bytes_written);
if (!g_queue_is_empty(client->deferred_send))
g_debug("[%u] buffer created", client->num);
}
void
client_write_output(struct client *client)
{
if (client_is_expired(client) || !client->send_buf_used)
return;
if (!g_queue_is_empty(client->deferred_send)) {
client_defer_output(client, client->send_buf,
client->send_buf_used);
if (client_is_expired(client))
return;
/* try to flush the deferred buffers now; the current
server command may take too long to finish, and
meanwhile try to feed output to the client,
otherwise it will time out. One reason why
deferring is slow might be that currently each
client_write() allocates a new deferred buffer.
This should be optimized after MPD 0.14. */
client_write_deferred(client);
} else
client_write_direct(client, client->send_buf,
client->send_buf_used);
client->send_buf_used = 0;
}
/**
* Write a block of data to the client.
*/
static void client_write(struct client *client, const char *buffer, size_t buflen)
{
/* if the client is going to be closed, do nothing */
if (client_is_expired(client))
return;
while (buflen > 0 && !client_is_expired(client)) {
size_t copylen;
assert(client->send_buf_used < sizeof(client->send_buf));
copylen = sizeof(client->send_buf) - client->send_buf_used;
if (copylen > buflen)
copylen = buflen;
memcpy(client->send_buf + client->send_buf_used, buffer,
copylen);
buflen -= copylen;
client->send_buf_used += copylen;
buffer += copylen;
if (client->send_buf_used >= sizeof(client->send_buf))
client_write_output(client);
}
}
void client_puts(struct client *client, const char *s)
{
client_write(client, s, strlen(s));
}
void client_vprintf(struct client *client, const char *fmt, va_list args)
{
#ifndef G_OS_WIN32
va_list tmp;
int length;
char *buffer;
va_copy(tmp, args);
length = vsnprintf(NULL, 0, fmt, tmp);
va_end(tmp);
if (length <= 0)
/* wtf.. */
return;
buffer = g_malloc(length + 1);
vsnprintf(buffer, length + 1, fmt, args);
client_write(client, buffer, length);
g_free(buffer);
#else
/* On mingw32, snprintf() expects a 64 bit integer instead of
a "long int" for "%li". This is not consistent with our
expectation, so we're using plain sprintf() here, hoping
the static buffer is large enough. Sorry for this hack,
but WIN32 development is so painful, I'm not in the mood to
do it properly now. */
static char buffer[4096];
vsprintf(buffer, fmt, args);
client_write(client, buffer, strlen(buffer));
#endif
}
G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
client_vprintf(client, fmt, args);
va_end(args);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -17,20 +17,15 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "cmdline.h"
#include "path.h"
#include "log.h"
#include "conf.h"
#include "decoder_list.h"
#include "decoder_plugin.h"
#include "config.h"
#include "output_list.h"
#include "ls.h"
#ifdef ENABLE_ENCODER
#include "encoder_list.h"
#endif
#ifdef ENABLE_ARCHIVE
#include "archive_list.h"
#endif
@@ -40,37 +35,9 @@
#include <stdio.h>
#include <stdlib.h>
#ifdef G_OS_WIN32
#define CONFIG_FILE_LOCATION "\\mpd\\mpd.conf"
#else /* G_OS_WIN32 */
#define SYSTEM_CONFIG_FILE_LOCATION "/etc/mpd.conf"
#define USER_CONFIG_FILE_LOCATION1 ".mpdconf"
#define USER_CONFIG_FILE_LOCATION2 ".mpd/mpd.conf"
#endif
static GQuark
cmdline_quark(void)
{
return g_quark_from_static_string("cmdline");
}
static void
print_all_decoders(FILE *fp)
{
for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) {
const struct decoder_plugin *plugin = decoder_plugins[i];
const char *const*suffixes;
fprintf(fp, "[%s]", plugin->name);
for (suffixes = plugin->suffixes;
suffixes != NULL && *suffixes != NULL;
++suffixes) {
fprintf(fp, " %s", *suffixes);
}
fprintf(fp, "\n");
}
}
G_GNUC_NORETURN
static void version(void)
@@ -78,25 +45,19 @@ 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 Max Kellermann <max@duempel.org>\n"
"This is free software; see the source for copying conditions. There is NO\n"
"warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
"\n"
"Supported decoders:\n");
print_all_decoders(stdout);
decoder_plugin_init_all();
decoder_plugin_print_all_decoders(stdout);
puts("\n"
"Supported outputs:\n");
audio_output_plugin_print_all_types(stdout);
#ifdef ENABLE_ENCODER
puts("\n"
"Supported encoders:\n");
encoder_plugin_print_all_types(stdout);
#endif
#ifdef ENABLE_ARCHIVE
puts("\n"
"Supported archives:\n");
@@ -111,29 +72,31 @@ static void version(void)
exit(EXIT_SUCCESS);
}
#if GLIB_CHECK_VERSION(2,12,0)
static const char *summary =
"Music Player Daemon - a daemon for playing music.";
#endif
bool
parse_cmdline(int argc, char **argv, struct options *options,
GError **error_r)
void parseOptions(int argc, char **argv, Options *options)
{
GError *error = NULL;
GOptionContext *context;
bool ret;
static gboolean option_version,
option_no_daemon,
option_create_db, option_no_create_db, option_no_daemon,
option_no_config;
const GOptionEntry entries[] = {
{ "create-db", 0, 0, G_OPTION_ARG_NONE, &option_create_db,
"force (re)creation of database", NULL },
{ "kill", 0, 0, G_OPTION_ARG_NONE, &options->kill,
"kill the currently running mpd session", NULL },
{ "no-config", 0, 0, G_OPTION_ARG_NONE, &option_no_config,
"don't read from config", NULL },
{ "no-create-db", 0, 0, G_OPTION_ARG_NONE, &option_no_create_db,
"don't create database, even if it doesn't exist", NULL },
{ "no-daemon", 0, 0, G_OPTION_ARG_NONE, &option_no_daemon,
"don't detach from console", NULL },
{ "stdout", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr,
NULL, NULL },
{ "stderr", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr,
{ "stdout", 0, 0, G_OPTION_ARG_NONE, &options->stdOutput,
"print messages to stderr", NULL },
{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &options->verbose,
"verbose logging", NULL },
@@ -144,13 +107,16 @@ parse_cmdline(int argc, char **argv, struct options *options,
options->kill = false;
options->daemon = true;
options->log_stderr = false;
options->stdOutput = false;
options->verbose = false;
options->createDB = 0;
context = g_option_context_new("[path/to/mpd.conf]");
g_option_context_add_main_entries(context, entries, NULL);
#if GLIB_CHECK_VERSION(2,12,0)
g_option_context_set_summary(context, summary);
#endif
ret = g_option_context_parse(context, &argc, &argv, &error);
g_option_context_free(context);
@@ -167,71 +133,39 @@ parse_cmdline(int argc, char **argv, struct options *options,
parser can use it already */
log_early_init(options->verbose);
if (option_create_db && option_no_create_db)
g_error("Cannot use both --create-db and --no-create-db\n");
if (option_no_create_db)
options->createDB = -1;
else if (option_create_db)
options->createDB = 1;
options->daemon = !option_no_daemon;
if (option_no_config) {
g_debug("Ignoring config, using daemon defaults\n");
return true;
} else if (argc <= 1) {
/* default configuration file path */
char *path1;
#ifdef G_OS_WIN32
path1 = g_build_filename(g_get_user_config_dir(),
CONFIG_FILE_LOCATION, NULL);
if (g_file_test(path1, G_FILE_TEST_IS_REGULAR))
ret = config_read_file(path1, error_r);
else {
int i = 0;
char *system_path = NULL;
const char * const *system_config_dirs;
system_config_dirs = g_get_system_config_dirs();
while(system_config_dirs[i] != NULL) {
system_path = g_build_filename(system_config_dirs[i],
CONFIG_FILE_LOCATION,
NULL);
if(g_file_test(system_path,
G_FILE_TEST_IS_REGULAR)) {
ret = config_read_file(system_path,error_r);
g_free(system_path);
g_free(&system_config_dirs);
break;
}
++i;;
}
g_free(system_path);
g_free(&system_config_dirs);
}
#else /* G_OS_WIN32 */
char *path2;
path1 = g_build_filename(g_get_home_dir(),
USER_CONFIG_FILE_LOCATION1, NULL);
path2 = g_build_filename(g_get_home_dir(),
USER_CONFIG_FILE_LOCATION2, NULL);
if (g_file_test(path1, G_FILE_TEST_IS_REGULAR))
ret = config_read_file(path1, error_r);
config_read_file(path1);
else if (g_file_test(path2, G_FILE_TEST_IS_REGULAR))
ret = config_read_file(path2, error_r);
config_read_file(path2);
else if (g_file_test(SYSTEM_CONFIG_FILE_LOCATION,
G_FILE_TEST_IS_REGULAR))
ret = config_read_file(SYSTEM_CONFIG_FILE_LOCATION,
error_r);
#endif
config_read_file(SYSTEM_CONFIG_FILE_LOCATION);
g_free(path1);
#ifndef G_OS_WIN32
g_free(path2);
#endif
return ret;
} else if (argc == 2) {
/* specified configuration file */
return config_read_file(argv[1], error_r);
} else {
g_set_error(error_r, cmdline_quark(), 0,
"too many arguments");
return false;
}
config_read_file(argv[1]);
} else
g_error("too many arguments");
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -22,17 +22,14 @@
#include <glib.h>
#include <stdbool.h>
struct options {
typedef struct _Options {
gboolean kill;
gboolean daemon;
gboolean log_stderr;
gboolean stdOutput;
gboolean verbose;
};
int createDB;
} Options;
bool
parse_cmdline(int argc, char **argv, struct options *options,
GError **error_r);
void parseOptions(int argc, char **argv, Options *options);
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -17,17 +17,14 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "command.h"
#include "player_control.h"
#include "playlist.h"
#include "playlist_print.h"
#include "playlist_save.h"
#include "playlist_queue.h"
#include "queue_print.h"
#include "ls.h"
#include "uri.h"
#include "decoder_print.h"
#include "directory.h"
#include "directory_print.h"
#include "database.h"
@@ -35,7 +32,7 @@
#include "volume.h"
#include "stats.h"
#include "permission.h"
#include "tokenizer.h"
#include "buffer2array.h"
#include "stored_playlist.h"
#include "ack.h"
#include "output_command.h"
@@ -46,8 +43,8 @@
#include "client.h"
#include "tag_print.h"
#include "path.h"
#include "replay_gain_config.h"
#include "idle.h"
#include "config.h"
#ifdef ENABLE_SQLITE
#include "sticker.h"
@@ -61,6 +58,7 @@
#include <stdlib.h>
#include <errno.h>
#define COMMAND_STATUS_VOLUME "volume"
#define COMMAND_STATUS_STATE "state"
#define COMMAND_STATUS_REPEAT "repeat"
#define COMMAND_STATUS_SINGLE "single"
@@ -76,8 +74,6 @@
#define COMMAND_STATUS_BITRATE "bitrate"
#define COMMAND_STATUS_ERROR "error"
#define COMMAND_STATUS_CROSSFADE "xfade"
#define COMMAND_STATUS_MIXRAMPDB "mixrampdb"
#define COMMAND_STATUS_MIXRAMPDELAY "mixrampdelay"
#define COMMAND_STATUS_AUDIO "audio"
#define COMMAND_STATUS_UPDATING_DB "updating_db"
@@ -170,8 +166,8 @@ check_int(struct client *client, int *value_r,
return false;
}
#if G_MAXLONG > G_MAXINT
if (value < G_MININT || value > G_MAXINT) {
#if LONG_MAX > INT_MAX
if (value < INT_MIN || value > INT_MAX) {
command_error(client, ACK_ERROR_ARG,
"Number too large: %s", s);
return false;
@@ -202,7 +198,7 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
/* compatibility with older MPD versions: specifying
"-1" makes MPD display the whole list */
*value_r1 = 0;
*value_r2 = G_MAXUINT;
*value_r2 = UINT_MAX;
return true;
}
@@ -212,8 +208,8 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
return false;
}
#if G_MAXLONG > G_MAXUINT
if (value > G_MAXUINT) {
#if LONG_MAX > UINT_MAX
if (value > UINT_MAX) {
command_error(client, ACK_ERROR_ARG,
"Number too large: %s", s);
return false;
@@ -224,7 +220,7 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
if (*test == ':') {
value = strtol(++test, &test2, 10);
if (*test2 != '\0') {
if (*test2 != '\0' || test == test2) {
va_list args;
va_start(args, fmt);
command_error_v(client, ACK_ERROR_ARG, fmt, args);
@@ -232,17 +228,14 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
return false;
}
if (test == test2)
value = G_MAXUINT;
if (value < 0) {
command_error(client, ACK_ERROR_ARG,
"Number is negative: %s", s);
return false;
}
#if G_MAXLONG > G_MAXUINT
if (value > G_MAXUINT) {
#if LONG_MAX > UINT_MAX
if (value > UINT_MAX) {
command_error(client, ACK_ERROR_ARG,
"Number too large: %s", s);
return false;
@@ -269,7 +262,7 @@ check_unsigned(struct client *client, unsigned *value_r, const char *s)
return false;
}
if (value > G_MAXUINT) {
if (value > UINT_MAX) {
command_error(client, ACK_ERROR_ARG,
"Number too large: %s", s);
return false;
@@ -296,23 +289,6 @@ check_bool(struct client *client, bool *value_r, const char *s)
return true;
}
static bool
check_float(struct client *client, float *value_r, const char *s)
{
float value;
char *endptr;
value = strtof(s, &endptr);
if (*endptr != 0 && endptr == s) {
command_error(client, ACK_ERROR_ARG,
"Float expected: %s", s);
return false;
}
*value_r = value;
return true;
}
static enum command_return
print_playlist_result(struct client *client,
enum playlist_result result)
@@ -387,12 +363,10 @@ print_spl_list(struct client *client, GPtrArray *list)
client_printf(client, "playlist: %s\n", playlist->name);
t = playlist->mtime;
strftime(timestamp, sizeof(timestamp),
#ifdef G_OS_WIN32
"%Y-%m-%dT%H:%M:%SZ",
strftime(timestamp, sizeof(timestamp), "%FT%TZ",
#ifdef WIN32
gmtime(&t)
#else
"%FT%TZ",
gmtime_r(&t, &tm)
#endif
);
@@ -410,14 +384,6 @@ handle_urlhandlers(struct client *client,
return COMMAND_RETURN_OK;
}
static enum command_return
handle_decoders(struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
decoder_list_print(client);
return COMMAND_RETURN_OK;
}
static enum command_return
handle_tagtypes(struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
@@ -434,7 +400,7 @@ handle_play(struct client *client, int argc, char *argv[])
if (argc == 2 && !check_int(client, &song, argv[1], need_positive))
return COMMAND_RETURN_ERROR;
result = playlist_play(&g_playlist, song);
result = playPlaylist(&g_playlist, song);
return print_playlist_result(client, result);
}
@@ -447,7 +413,7 @@ handle_playid(struct client *client, int argc, char *argv[])
if (argc == 2 && !check_int(client, &id, argv[1], need_positive))
return COMMAND_RETURN_ERROR;
result = playlist_play_id(&g_playlist, id);
result = playPlaylistById(&g_playlist, id);
return print_playlist_result(client, result);
}
@@ -455,7 +421,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);
stopPlaylist(&g_playlist);
return COMMAND_RETURN_OK;
}
@@ -475,11 +441,11 @@ handle_pause(struct client *client,
bool pause_flag;
if (!check_bool(client, &pause_flag, argv[1]))
return COMMAND_RETURN_ERROR;
playerSetPause(pause_flag);
return COMMAND_RETURN_OK;
}
pc_set_pause(pause_flag);
} else
pc_pause();
playerPause();
return COMMAND_RETURN_OK;
}
@@ -488,14 +454,10 @@ handle_status(struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
const char *state = NULL;
struct player_status player_status;
int updateJobId;
char *error;
int song;
pc_get_status(&player_status);
switch (player_status.state) {
switch (getPlayerState()) {
case PLAYER_STATE_STOP:
state = "stop";
break;
@@ -508,7 +470,7 @@ handle_status(struct client *client,
}
client_printf(client,
"volume: %i\n"
COMMAND_STATUS_VOLUME ": %i\n"
COMMAND_STATUS_REPEAT ": %i\n"
COMMAND_STATUS_RANDOM ": %i\n"
COMMAND_STATUS_SINGLE ": %i\n"
@@ -516,43 +478,34 @@ handle_status(struct client *client,
COMMAND_STATUS_PLAYLIST ": %li\n"
COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n"
COMMAND_STATUS_CROSSFADE ": %i\n"
COMMAND_STATUS_MIXRAMPDB ": %f\n"
COMMAND_STATUS_MIXRAMPDELAY ": %f\n"
COMMAND_STATUS_STATE ": %s\n",
volume_level_get(),
playlist_get_repeat(&g_playlist),
playlist_get_random(&g_playlist),
playlist_get_single(&g_playlist),
playlist_get_consume(&g_playlist),
playlist_get_version(&g_playlist),
playlist_get_length(&g_playlist),
(int)(pc_get_cross_fade() + 0.5),
pc_get_mixramp_db(),
pc_get_mixramp_delay(),
getPlaylistRepeatStatus(&g_playlist),
getPlaylistRandomStatus(&g_playlist),
getPlaylistSingleStatus(&g_playlist),
getPlaylistConsumeStatus(&g_playlist),
getPlaylistVersion(&g_playlist),
getPlaylistLength(&g_playlist),
(int)(getPlayerCrossFade() + 0.5),
state);
song = playlist_get_current_song(&g_playlist);
song = getPlaylistCurrentSong(&g_playlist);
if (song >= 0) {
client_printf(client,
COMMAND_STATUS_SONG ": %i\n"
COMMAND_STATUS_SONGID ": %u\n",
song, playlist_get_song_id(&g_playlist, song));
song, getPlaylistSongId(&g_playlist, song));
}
if (player_status.state != PLAYER_STATE_STOP) {
struct audio_format_string af_string;
if (getPlayerState() != PLAYER_STATE_STOP) {
const struct audio_format *af = player_get_audio_format();
client_printf(client,
COMMAND_STATUS_TIME ": %i:%i\n"
"elapsed: %1.3f\n"
COMMAND_STATUS_BITRATE ": %u\n"
COMMAND_STATUS_AUDIO ": %s\n",
(int)(player_status.elapsed_time + 0.5),
(int)(player_status.total_time + 0.5),
player_status.elapsed_time,
player_status.bit_rate,
audio_format_to_string(&player_status.audio_format,
&af_string));
COMMAND_STATUS_BITRATE ": %li\n"
COMMAND_STATUS_AUDIO ": %u:%u:%u\n",
getPlayerElapsedTime(), getPlayerTotalTime(),
getPlayerBitRate(),
af->sample_rate, af->bits, af->channels);
}
if ((updateJobId = isUpdatingDB())) {
@@ -561,20 +514,18 @@ handle_status(struct client *client,
updateJobId);
}
error = pc_get_error_message();
if (error != NULL) {
if (getPlayerError() != PLAYER_ERROR_NOERROR) {
client_printf(client,
COMMAND_STATUS_ERROR ": %s\n",
error);
g_free(error);
getPlayerErrorStr());
}
song = playlist_get_next_song(&g_playlist);
song = getPlaylistNextSong(&g_playlist);
if (song >= 0) {
client_printf(client,
COMMAND_STATUS_NEXTSONG ": %i\n"
COMMAND_STATUS_NEXTSONGID ": %u\n",
song, playlist_get_song_id(&g_playlist, song));
song, getPlaylistSongId(&g_playlist, song));
}
return COMMAND_RETURN_OK;
@@ -618,7 +569,7 @@ 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 = addToPlaylist(&g_playlist, uri, NULL);
return print_playlist_result(client, result);
}
@@ -654,7 +605,7 @@ handle_addid(struct client *client, int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
result = playlist_append_uri(&g_playlist, uri, &added_id);
result = addToPlaylist(&g_playlist, uri, &added_id);
}
if (result != PLAYLIST_RESULT_SUCCESS)
@@ -664,11 +615,11 @@ handle_addid(struct client *client, int argc, char *argv[])
int to;
if (!check_int(client, &to, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
result = playlist_move_id(&g_playlist, added_id, to);
result = moveSongInPlaylistById(&g_playlist, added_id, to);
if (result != PLAYLIST_RESULT_SUCCESS) {
enum command_return ret =
print_playlist_result(client, result);
playlist_delete_id(&g_playlist, added_id);
deleteFromPlaylistById(&g_playlist, added_id);
return ret;
}
}
@@ -680,13 +631,13 @@ handle_addid(struct client *client, int argc, char *argv[])
static enum command_return
handle_delete(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
unsigned start, end;
int song;
enum playlist_result result;
if (!check_range(client, &start, &end, argv[1], need_range))
if (!check_int(client, &song, argv[1], need_positive))
return COMMAND_RETURN_ERROR;
result = playlist_delete_range(&g_playlist, start, end);
result = deleteFromPlaylist(&g_playlist, song);
return print_playlist_result(client, result);
}
@@ -699,7 +650,7 @@ handle_deleteid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_int(client, &id, argv[1], need_positive))
return COMMAND_RETURN_ERROR;
result = playlist_delete_id(&g_playlist, id);
result = deleteFromPlaylistById(&g_playlist, id);
return print_playlist_result(client, result);
}
@@ -720,7 +671,7 @@ handle_shuffle(G_GNUC_UNUSED struct client *client,
argv[1], need_range))
return COMMAND_RETURN_ERROR;
playlist_shuffle(&g_playlist, start, end);
shufflePlaylist(&g_playlist, start, end);
return COMMAND_RETURN_OK;
}
@@ -728,7 +679,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);
clearPlaylist(&g_playlist);
return COMMAND_RETURN_OK;
}
@@ -747,10 +698,6 @@ handle_load(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
enum playlist_result result;
result = playlist_open_into_queue(argv[1], &g_playlist);
if (result != PLAYLIST_RESULT_NO_SUCH_LIST)
return result;
result = playlist_load_spl(&g_playlist, argv[1]);
return print_playlist_result(client, result);
}
@@ -758,9 +705,6 @@ handle_load(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
static enum command_return
handle_listplaylist(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
if (playlist_file_print(client, argv[1], false))
return COMMAND_RETURN_OK;
bool ret;
ret = spl_print(client, argv[1], false);
@@ -776,9 +720,6 @@ static enum command_return
handle_listplaylistinfo(struct client *client,
G_GNUC_UNUSED int argc, char *argv[])
{
if (playlist_file_print(client, argv[1], true))
return COMMAND_RETURN_OK;
bool ret;
ret = spl_print(client, argv[1], true);
@@ -867,7 +808,7 @@ handle_plchangesposid(struct client *client, G_GNUC_UNUSED int argc, char *argv[
static enum command_return
handle_playlistinfo(struct client *client, int argc, char *argv[])
{
unsigned start = 0, end = G_MAXUINT;
unsigned start = 0, end = UINT_MAX;
bool ret;
if (argc == 2 && !check_range(client, &start, &end,
@@ -896,7 +837,7 @@ handle_playlistid(struct client *client, int argc, char *argv[])
return print_playlist_result(client,
PLAYLIST_RESULT_NO_SUCH_SONG);
} else {
playlist_print_info(client, &g_playlist, 0, G_MAXUINT);
playlist_print_info(client, &g_playlist, 0, UINT_MAX);
}
return COMMAND_RETURN_OK;
@@ -927,30 +868,6 @@ handle_find(struct client *client, int argc, char *argv[])
return ret;
}
static enum command_return
handle_findadd(struct client *client, int argc, char *argv[])
{
int ret;
struct locate_item_list *list =
locate_item_list_parse(argv + 1, argc - 1);
if (list == NULL || list->length == 0) {
if (list != NULL)
locate_item_list_free(list);
command_error(client, ACK_ERROR_ARG, "incorrect arguments");
return COMMAND_RETURN_ERROR;
}
ret = findAddIn(client, NULL, list);
if (ret == -1)
command_error(client, ACK_ERROR_NO_EXIST,
"directory or file not found");
locate_item_list_free(list);
return ret;
}
static enum command_return
handle_search(struct client *client, int argc, char *argv[])
{
@@ -1076,52 +993,14 @@ handle_playlistmove(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
static enum command_return
handle_update(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
const char *path = NULL;
char *path = NULL;
unsigned ret;
assert(argc <= 2);
if (argc == 2) {
path = argv[1];
if (argc == 2)
path = g_strdup(argv[1]);
if (*path == 0 || strcmp(path, "/") == 0)
/* backwards compatibility with MPD 0.15 */
path = NULL;
else if (!uri_safe_local(path)) {
command_error(client, ACK_ERROR_ARG,
"Malformed path");
return COMMAND_RETURN_ERROR;
}
}
ret = update_enqueue(path, false);
if (ret > 0) {
client_printf(client, "updating_db: %i\n", ret);
return COMMAND_RETURN_OK;
} else {
command_error(client, ACK_ERROR_UPDATE_ALREADY,
"already updating");
return COMMAND_RETURN_ERROR;
}
}
static enum command_return
handle_rescan(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
const char *path = NULL;
unsigned ret;
assert(argc <= 2);
if (argc == 2) {
path = argv[1];
if (!uri_safe_local(path)) {
command_error(client, ACK_ERROR_ARG,
"Malformed path");
return COMMAND_RETURN_ERROR;
}
}
ret = update_enqueue(path, true);
ret = directory_update_init(path);
if (ret > 0) {
client_printf(client, "updating_db: %i\n", ret);
return COMMAND_RETURN_OK;
@@ -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);
nextSongInPlaylist(&g_playlist);
g_playlist.queue.single = single;
return COMMAND_RETURN_OK;
@@ -1151,7 +1030,7 @@ 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);
previousSongInPlaylist(&g_playlist);
return COMMAND_RETURN_OK;
}
@@ -1172,6 +1051,25 @@ handle_listall(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return ret;
}
static enum command_return
handle_volume(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
int change;
bool success;
if (!check_int(client, &change, argv[1], need_integer))
return COMMAND_RETURN_ERROR;
success = volume_level_change(change, true);
if (!success) {
command_error(client, ACK_ERROR_SYSTEM,
"problems setting volume");
return COMMAND_RETURN_ERROR;
}
return COMMAND_RETURN_OK;
}
static enum command_return
handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
@@ -1181,12 +1079,7 @@ handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_int(client, &level, argv[1], need_integer))
return COMMAND_RETURN_ERROR;
if (level < 0 || level > 100) {
command_error(client, ACK_ERROR_ARG, "Invalid volume value");
return COMMAND_RETURN_ERROR;
}
success = volume_level_change(level);
success = volume_level_change(level, 0);
if (!success) {
command_error(client, ACK_ERROR_SYSTEM,
"problems setting volume");
@@ -1210,7 +1103,7 @@ handle_repeat(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
playlist_set_repeat(&g_playlist, status);
setPlaylistRepeatStatus(&g_playlist, status);
return COMMAND_RETURN_OK;
}
@@ -1228,7 +1121,7 @@ handle_single(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
playlist_set_single(&g_playlist, status);
setPlaylistSingleStatus(&g_playlist, status);
return COMMAND_RETURN_OK;
}
@@ -1246,7 +1139,7 @@ handle_consume(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
playlist_set_consume(&g_playlist, status);
setPlaylistConsumeStatus(&g_playlist, status);
return COMMAND_RETURN_OK;
}
@@ -1264,7 +1157,7 @@ handle_random(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
playlist_set_random(&g_playlist, status);
setPlaylistRandomStatus(&g_playlist, status);
return COMMAND_RETURN_OK;
}
@@ -1279,7 +1172,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();
clearPlayerError();
return COMMAND_RETURN_OK;
}
@@ -1303,17 +1196,17 @@ handle_list(struct client *client, int argc, char *argv[])
/* for compatibility with < 0.12.0 */
if (argc == 3) {
if (tagType != TAG_ALBUM) {
if (tagType != TAG_ITEM_ALBUM) {
command_error(client, ACK_ERROR_ARG,
"should be \"%s\" for 3 arguments",
tag_item_names[TAG_ALBUM]);
tag_item_names[TAG_ITEM_ALBUM]);
return COMMAND_RETURN_ERROR;
}
locate_item_list_parse(argv + 1, argc - 1);
conditionals = locate_item_list_new(1);
conditionals->items[0].tag = TAG_ARTIST;
conditionals->items[0].tag = TAG_ITEM_ARTIST;
conditionals->items[0].needle = g_strdup(argv[2]);
} else {
conditionals =
@@ -1348,7 +1241,7 @@ handle_move(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
if (!check_int(client, &to, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
result = playlist_move_range(&g_playlist, start, end, to);
result = moveSongRangeInPlaylist(&g_playlist, start, end, to);
return print_playlist_result(client, result);
}
@@ -1362,7 +1255,7 @@ handle_moveid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
if (!check_int(client, &to, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
result = playlist_move_id(&g_playlist, id, to);
result = moveSongInPlaylistById(&g_playlist, id, to);
return print_playlist_result(client, result);
}
@@ -1376,7 +1269,7 @@ handle_swap(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
if (!check_int(client, &song2, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
result = playlist_swap_songs(&g_playlist, song1, song2);
result = swapSongsInPlaylist(&g_playlist, song1, song2);
return print_playlist_result(client, result);
}
@@ -1390,7 +1283,7 @@ handle_swapid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
if (!check_int(client, &id2, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
result = playlist_swap_songs_id(&g_playlist, id1, id2);
result = swapSongsInPlaylistById(&g_playlist, id1, id2);
return print_playlist_result(client, result);
}
@@ -1405,7 +1298,7 @@ handle_seek(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_int(client, &seek_time, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
result = playlist_seek_song(&g_playlist, song, seek_time);
result = seekSongInPlaylist(&g_playlist, song, seek_time);
return print_playlist_result(client, result);
}
@@ -1420,7 +1313,7 @@ handle_seekid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_int(client, &seek_time, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
result = playlist_seek_song_id(&g_playlist, id, seek_time);
result = seekSongInPlaylistById(&g_playlist, id, seek_time);
return print_playlist_result(client, result);
}
@@ -1470,31 +1363,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);
return COMMAND_RETURN_OK;
}
static enum command_return
handle_mixrampdb(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
float db;
if (!check_float(client, &db, argv[1]))
return COMMAND_RETURN_ERROR;
pc_set_mixramp_db(db);
return COMMAND_RETURN_OK;
}
static enum command_return
handle_mixrampdelay(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
float delay_secs;
if (!check_float(client, &delay_secs, argv[1]))
return COMMAND_RETURN_ERROR;
pc_set_mixramp_delay(delay_secs);
setPlayerCrossFade(xfade_time);
return COMMAND_RETURN_OK;
}
@@ -1607,28 +1476,6 @@ handle_listplaylists(struct client *client,
return COMMAND_RETURN_OK;
}
static enum command_return
handle_replay_gain_mode(struct client *client,
G_GNUC_UNUSED int argc, char *argv[])
{
if (!replay_gain_set_mode_string(argv[1])) {
command_error(client, ACK_ERROR_ARG,
"Unrecognized replay gain mode");
return COMMAND_RETURN_ERROR;
}
return COMMAND_RETURN_OK;
}
static enum command_return
handle_replay_gain_status(struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
client_printf(client, "replay_gain_mode: %s\n",
replay_gain_get_mode_string());
return COMMAND_RETURN_OK;
}
static enum command_return
handle_idle(struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
@@ -1672,14 +1519,13 @@ sticker_song_find_print_cb(struct song *song, const char *value,
{
struct sticker_song_find_data *data = user_data;
song_print_uri(data->client, song);
song_print_url(data->client, song);
sticker_print_value(data->client, data->name, value);
}
static enum command_return
handle_sticker_song(struct client *client, int argc, char *argv[])
{
/* get song song_id key */
if (argc == 5 && strcmp(argv[1], "get") == 0) {
struct song *song;
char *value;
@@ -1702,7 +1548,6 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
g_free(value);
return COMMAND_RETURN_OK;
/* list song song_id */
} else if (argc == 4 && strcmp(argv[1], "list") == 0) {
struct song *song;
struct sticker *sticker;
@@ -1725,7 +1570,6 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
sticker_free(sticker);
return COMMAND_RETURN_OK;
/* set song song_id id key */
} else if (argc == 6 && strcmp(argv[1], "set") == 0) {
struct song *song;
bool ret;
@@ -1745,7 +1589,6 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
}
return COMMAND_RETURN_OK;
/* delete song song_id [key] */
} else if ((argc == 4 || argc == 5) &&
strcmp(argv[1], "delete") == 0) {
struct song *song;
@@ -1768,7 +1611,6 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
}
return COMMAND_RETURN_OK;
/* find song dir key */
} else if (argc == 5 && strcmp(argv[1], "find") == 0) {
/* "sticker find song a/directory name" */
struct directory *directory;
@@ -1837,13 +1679,11 @@ static const struct command commands[] = {
{ "count", PERMISSION_READ, 2, -1, handle_count },
{ "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade },
{ "currentsong", PERMISSION_READ, 0, 0, handle_currentsong },
{ "decoders", PERMISSION_READ, 0, 0, handle_decoders },
{ "delete", PERMISSION_CONTROL, 1, 1, handle_delete },
{ "deleteid", PERMISSION_CONTROL, 1, 1, handle_deleteid },
{ "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput },
{ "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput },
{ "find", PERMISSION_READ, 2, -1, handle_find },
{ "findadd", PERMISSION_READ, 2, -1, handle_findadd},
{ "idle", PERMISSION_READ, 0, -1, handle_idle },
{ "kill", PERMISSION_ADMIN, -1, -1, handle_kill },
{ "list", PERMISSION_READ, 1, -1, handle_list },
@@ -1854,8 +1694,6 @@ static const struct command commands[] = {
{ "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists },
{ "load", PERMISSION_ADD, 1, 1, handle_load },
{ "lsinfo", PERMISSION_READ, 0, 1, handle_lsinfo },
{ "mixrampdb", PERMISSION_CONTROL, 1, 1, handle_mixrampdb },
{ "mixrampdelay", PERMISSION_CONTROL, 1, 1, handle_mixrampdelay },
{ "move", PERMISSION_CONTROL, 2, 2, handle_move },
{ "moveid", PERMISSION_CONTROL, 2, 2, handle_moveid },
{ "next", PERMISSION_CONTROL, 0, 0, handle_next },
@@ -1881,11 +1719,6 @@ static const struct command commands[] = {
{ "random", PERMISSION_CONTROL, 1, 1, handle_random },
{ "rename", PERMISSION_CONTROL, 2, 2, handle_rename },
{ "repeat", PERMISSION_CONTROL, 1, 1, handle_repeat },
{ "replay_gain_mode", PERMISSION_CONTROL, 1, 1,
handle_replay_gain_mode },
{ "replay_gain_status", PERMISSION_READ, 0, 0,
handle_replay_gain_status },
{ "rescan", PERMISSION_ADMIN, 0, 1, handle_rescan },
{ "rm", PERMISSION_CONTROL, 1, 1, handle_rm },
{ "save", PERMISSION_CONTROL, 1, 1, handle_save },
{ "search", PERMISSION_READ, 2, -1, handle_search },
@@ -1905,6 +1738,7 @@ static const struct command commands[] = {
{ "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes },
{ "update", PERMISSION_ADMIN, 0, 1, handle_update },
{ "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers },
{ "volume", PERMISSION_CONTROL, 1, 1, handle_volume },
};
static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]);
@@ -2058,63 +1892,15 @@ command_checked_lookup(struct client *client, unsigned permission,
}
enum command_return
command_process(struct client *client, unsigned num, char *line)
command_process(struct client *client, char *commandString)
{
GError *error = NULL;
int argc;
char *argv[COMMAND_ARGV_MAX] = { NULL };
const struct command *cmd;
enum command_return ret = COMMAND_RETURN_ERROR;
command_list_num = num;
/* get the command name (first word on the line) */
argv[0] = tokenizer_next_word(&line, &error);
if (argv[0] == NULL) {
current_command = "";
if (*line == 0)
command_error(client, ACK_ERROR_UNKNOWN,
"No command given");
else {
command_error(client, ACK_ERROR_UNKNOWN,
"%s", error->message);
g_error_free(error);
}
current_command = NULL;
return COMMAND_RETURN_ERROR;
}
argc = 1;
/* now parse the arguments (quoted or unquoted) */
while (argc < (int)G_N_ELEMENTS(argv) &&
(argv[argc] =
tokenizer_next_param(&line, &error)) != NULL)
++argc;
/* some error checks; we have to set current_command because
command_error() expects it to be set */
current_command = argv[0];
if (argc >= (int)G_N_ELEMENTS(argv)) {
command_error(client, ACK_ERROR_ARG, "Too many arguments");
current_command = NULL;
return COMMAND_RETURN_ERROR;
}
if (*line != 0) {
command_error(client, ACK_ERROR_ARG,
"%s", error->message);
current_command = NULL;
g_error_free(error);
return COMMAND_RETURN_ERROR;
}
/* look up and invoke the command handler */
if (!(argc = buffer2array(commandString, argv, COMMAND_ARGV_MAX)))
return COMMAND_RETURN_OK;
cmd = command_checked_lookup(client, client_get_permission(client),
argc, argv);
@@ -2122,7 +1908,32 @@ command_process(struct client *client, unsigned num, char *line)
ret = cmd->handler(client, argc, argv);
current_command = NULL;
command_list_num = 0;
return ret;
}
enum command_return
command_process_list(struct client *client,
bool list_ok, GSList *list)
{
enum command_return ret = COMMAND_RETURN_OK;
command_list_num = 0;
for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) {
char *cmd = cur->data;
g_debug("command_process_list: process command \"%s\"",
cmd);
ret = command_process(client, cmd);
g_debug("command_process_list: command returned %i", ret);
if (ret != COMMAND_RETURN_OK || client_is_expired(client))
break;
else if (list_ok)
client_puts(client, "list_OK\n");
command_list_num++;
}
command_list_num = 0;
return ret;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -39,7 +39,11 @@ void command_init(void);
void command_finish(void);
enum command_return
command_process(struct client *client, unsigned num, char *line);
command_process_list(struct client *client,
bool list_ok, GSList *list);
enum command_return
command_process(struct client *client, char *commandString);
void command_success(struct client *client);

410
src/compress.c Normal file
View File

@@ -0,0 +1,410 @@
/*
* Copyright (C) 2003-2009 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*
* Imported from AudioCompress by J. Shagam <fluffy@beesbuzz.biz>
*/
#include "compress.h"
#include <glib.h>
#include <stdint.h>
#include <string.h>
#ifdef USE_X
#include <X11/Xlib.h>
#include <X11/Xutil.h>
static Display *display;
static Window window;
static Visual *visual;
static int screen;
static GC blackGC, whiteGC, blueGC, yellowGC, dkyellowGC, redGC;
#endif
static int *peaks;
static int gainCurrent, gainTarget;
static struct {
int show_mon;
int anticlip;
int target;
int gainmax;
int gainsmooth;
unsigned buckets;
} prefs;
#ifdef USE_X
static int mon_init;
#endif
void CompressCfg(int show_mon, int anticlip, int target, int gainmax,
int gainsmooth, unsigned buckets)
{
static unsigned lastsize;
prefs.show_mon = show_mon;
prefs.anticlip = anticlip;
prefs.target = target;
prefs.gainmax = gainmax;
prefs.gainsmooth = gainsmooth;
prefs.buckets = buckets;
/* Allocate the peak structure */
peaks = g_realloc(peaks, sizeof(int)*prefs.buckets);
if (prefs.buckets > lastsize)
memset(peaks + lastsize, 0, sizeof(int)*(prefs.buckets
- lastsize));
lastsize = prefs.buckets;
#ifdef USE_X
/* Configure the monitor window if needed */
if (show_mon && !mon_init)
{
display = XOpenDisplay(getenv("DISPLAY"));
/* We really shouldn't try to init X if there's no X */
if (!display)
{
fprintf(stderr,
"X not detected; disabling monitor window\n");
show_mon = prefs.show_mon = 0;
}
}
if (show_mon && !mon_init)
{
XGCValues gcv;
XColor col;
gainCurrent = gainTarget = (1 << GAINSHIFT);
screen = DefaultScreen(display);
visual = DefaultVisual(display, screen);
window = XCreateSimpleWindow(display,
RootWindow(display, screen),
0, 0, prefs.buckets, 128 + 8, 0,
BlackPixel(display, screen),
WhitePixel(display, screen));
XStoreName(display, window, "AudioCompress monitor");
gcv.foreground = BlackPixel(display, screen);
blackGC = XCreateGC(display, window, GCForeground, &gcv);
gcv.foreground = WhitePixel(display, screen);
whiteGC = XCreateGC(display, window, GCForeground, &gcv);
col.red = 0;
col.green = 0;
col.blue = 65535;
XAllocColor(display, DefaultColormap(display, screen), &col);
gcv.foreground = col.pixel;
blueGC = XCreateGC(display, window, GCForeground, &gcv);
col.red = 65535;
col.green = 65535;
col.blue = 0;
XAllocColor(display, DefaultColormap(display, screen), &col);
gcv.foreground = col.pixel;
yellowGC = XCreateGC(display, window, GCForeground, &gcv);
col.red = 32767;
col.green = 32767;
col.blue = 0;
XAllocColor(display, DefaultColormap(display, screen), &col);
gcv.foreground = col.pixel;
dkyellowGC = XCreateGC(display, window, GCForeground, &gcv);
col.red = 65535;
col.green = 0;
col.blue = 0;
XAllocColor(display, DefaultColormap(display, screen), &col);
gcv.foreground = col.pixel;
redGC = XCreateGC(display, window, GCForeground, &gcv);
mon_init = 1;
}
if (mon_init)
{
if (show_mon)
XMapWindow(display, window);
else
XUnmapWindow(display, window);
XResizeWindow(display, window, prefs.buckets, 128 + 8);
XFlush(display);
}
#endif
}
void CompressFree(void)
{
#ifdef USE_X
if (mon_init)
{
XFreeGC(display, blackGC);
XFreeGC(display, whiteGC);
XFreeGC(display, blueGC);
XFreeGC(display, yellowGC);
XFreeGC(display, dkyellowGC);
XFreeGC(display, redGC);
XDestroyWindow(display, window);
XCloseDisplay(display);
}
#endif
g_free(peaks);
}
void CompressDo(void *data, unsigned int length)
{
int16_t *audio = (int16_t *)data, *ap;
int peak;
unsigned int i, pos;
int gr, gf, gn;
static int pn = -1;
#ifdef STATS
static int clip;
#endif
static int clipped;
if (!peaks)
return;
if (pn == -1)
{
for (i = 0; i < prefs.buckets; i++)
peaks[i] = 0;
}
pn = (pn + 1)%prefs.buckets;
#ifdef DEBUG
fprintf(stderr, "modifyNative16(0x%08x, %d)\n",(unsigned int)data,
length);
#endif
/* Determine peak's value and position */
peak = 1;
pos = 0;
#ifdef DEBUG
fprintf(stderr, "finding peak(b=%d)\n", pn);
#endif
ap = audio;
for (i = 0; i < length/2; i++)
{
int val = *ap;
if (val > peak)
{
peak = val;
pos = i;
} else if (-val > peak)
{
peak = -val;
pos = i;
}
ap++;
}
peaks[pn] = peak;
/* Only draw if needed, of course */
#ifdef USE_X
if (prefs.show_mon)
{
/* current amplitude */
XDrawLine(display, window, whiteGC,
pn, 0,
pn,
127 -
(peaks[pn]*gainCurrent >> (GAINSHIFT + 8)));
/* amplification */
XDrawLine(display, window, yellowGC,
pn,
127 - (peaks[pn]*gainCurrent
>> (GAINSHIFT + 8)),
pn, 127);
/* peak */
XDrawLine(display, window, blackGC,
pn, 127 - (peaks[pn] >> 8), pn, 127);
/* clip indicator */
if (clipped)
XDrawLine(display, window, redGC,
(pn + prefs.buckets - 1)%prefs.buckets,
126 - clipped/(length*512),
(pn + prefs.buckets - 1)%prefs.buckets,
127);
clipped = 0;
/* target line */
/* XDrawPoint(display, window, redGC, */
/* pn, 127 - TARGET/256); */
/* amplification edge */
XDrawLine(display, window, dkyellowGC,
pn,
127 - (peaks[pn]*gainCurrent
>> (GAINSHIFT + 8)),
pn - 1,
127 -
(peaks[(pn + prefs.buckets
- 1)%prefs.buckets]*gainCurrent
>> (GAINSHIFT + 8)));
}
#endif
for (i = 0; i < prefs.buckets; i++)
{
if (peaks[i] > peak)
{
peak = peaks[i];
pos = 0;
}
}
/* Determine target gain */
gn = (1 << GAINSHIFT)*prefs.target/peak;
if (gn <(1 << GAINSHIFT))
gn = 1 << GAINSHIFT;
gainTarget = (gainTarget *((1 << prefs.gainsmooth) - 1) + gn)
>> prefs.gainsmooth;
/* Give it an extra insignifigant nudge to counteract possible
** rounding error
*/
if (gn < gainTarget)
gainTarget--;
else if (gn > gainTarget)
gainTarget++;
if (gainTarget > prefs.gainmax << GAINSHIFT)
gainTarget = prefs.gainmax << GAINSHIFT;
#ifdef USE_X
if (prefs.show_mon)
{
int x;
/* peak*gain */
XDrawPoint(display, window, redGC,
pn,
127 - (peak*gainCurrent
>> (GAINSHIFT + 8)));
/* gain indicator */
XFillRectangle(display, window, whiteGC, 0, 128,
prefs.buckets, 8);
x = (gainTarget - (1 << GAINSHIFT))*prefs.buckets
/ ((prefs.gainmax - 1) << GAINSHIFT);
XDrawLine(display, window, redGC, x,
128, x, 128 + 8);
x = (gn - (1 << GAINSHIFT))*prefs.buckets
/ ((prefs.gainmax - 1) << GAINSHIFT);
XDrawLine(display, window, blackGC,
x, 132 - 1,
x, 132 + 1);
/* blue peak line */
XDrawLine(display, window, blueGC,
0, 127 - (peak >> 8), prefs.buckets,
127 - (peak >> 8));
XFlush(display);
XDrawLine(display, window, whiteGC,
0, 127 - (peak >> 8), prefs.buckets,
127 - (peak >> 8));
}
#endif
/* See if a peak is going to clip */
gn = (1 << GAINSHIFT)*32768/peak;
if (gn < gainTarget)
{
gainTarget = gn;
if (prefs.anticlip)
pos = 0;
} else
{
/* We're ramping up, so draw it out over the whole frame */
pos = length;
}
/* Determine gain rate necessary to make target */
if (!pos)
pos = 1;
gr = ((gainTarget - gainCurrent) << 16)/(int)pos;
/* Do the shiznit */
gf = gainCurrent << 16;
#ifdef STATS
fprintf(stderr, "\rgain = %2.2f%+.2e ",
gainCurrent*1.0/(1 << GAINSHIFT),
(gainTarget - gainCurrent)*1.0/(1 << GAINSHIFT));
#endif
ap = audio;
for (i = 0; i < length/2; i++)
{
int sample;
/* Interpolate the gain */
gainCurrent = gf >> 16;
if (i < pos)
gf += gr;
else if (i == pos)
gf = gainTarget << 16;
/* Amplify */
sample = (*ap)*gainCurrent >> GAINSHIFT;
if (sample < -32768)
{
#ifdef STATS
clip++;
#endif
clipped += -32768 - sample;
sample = -32768;
} else if (sample > 32767)
{
#ifdef STATS
clip++;
#endif
clipped += sample - 32767;
sample = 32767;
}
*ap++ = sample;
}
#ifdef STATS
fprintf(stderr, "clip %d b%-3d ", clip, pn);
#endif
#ifdef DEBUG
fprintf(stderr, "\ndone\n");
#endif
}

48
src/compress.h Normal file
View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2003-2009 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*
* Imported from AudioCompress by J. Shagam <fluffy@beesbuzz.biz>
*/
#ifndef MPD_COMPRESS_H
#define MPD_COMPRESS_H
/* These are copied from the AudioCompress config.h, mainly because CompressDo
* needs GAINSHIFT defined. The rest are here so they can be used as defaults
* to pass to CompressCfg(). -- jat */
#define ANTICLIP 0 /* Strict clipping protection */
#define TARGET 25000 /* Target level */
#define GAINMAX 32 /* The maximum amount to amplify by */
#define GAINSHIFT 10 /* How fine-grained the gain is */
#define GAINSMOOTH 8 /* How much inertia ramping has*/
#define BUCKETS 400 /* How long of a history to store */
void CompressCfg(int monitor,
int anticlip,
int target,
int maxgain,
int smooth,
unsigned buckets);
void CompressDo(void *data, unsigned int numSamples);
void CompressFree(void);
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -17,12 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "conf.h"
#include "utils.h"
#include "tokenizer.h"
#include "buffer2array.h"
#include "path.h"
#include "glib_compat.h"
#include <glib.h>
@@ -38,84 +36,37 @@
#define MAX_STRING_SIZE MPD_PATH_MAX+80
#define CONF_COMMENT '#'
#define CONF_BLOCK_BEGIN "{"
#define CONF_BLOCK_END "}"
#define CONF_REPEATABLE_MASK 0x01
#define CONF_BLOCK_MASK 0x02
#define CONF_LINE_TOKEN_MAX 3
struct config_entry {
const char *const name;
const bool repeatable;
const bool block;
const char *name;
unsigned char mask;
GSList *params;
};
static struct config_entry config_entries[] = {
{ .name = CONF_MUSIC_DIR, false, false },
{ .name = CONF_PLAYLIST_DIR, false, false },
{ .name = CONF_FOLLOW_INSIDE_SYMLINKS, false, false },
{ .name = CONF_FOLLOW_OUTSIDE_SYMLINKS, false, false },
{ .name = CONF_DB_FILE, false, false },
{ .name = CONF_STICKER_FILE, false, false },
{ .name = CONF_LOG_FILE, false, false },
{ .name = CONF_PID_FILE, false, false },
{ .name = CONF_STATE_FILE, false, false },
{ .name = CONF_USER, false, false },
{ .name = CONF_GROUP, false, false },
{ .name = CONF_BIND_TO_ADDRESS, true, false },
{ .name = CONF_PORT, false, false },
{ .name = CONF_LOG_LEVEL, false, false },
{ .name = CONF_ZEROCONF_NAME, false, false },
{ .name = CONF_ZEROCONF_ENABLED, false, false },
{ .name = CONF_PASSWORD, true, false },
{ .name = CONF_DEFAULT_PERMS, false, false },
{ .name = CONF_AUDIO_OUTPUT, true, true },
{ .name = CONF_AUDIO_OUTPUT_FORMAT, false, false },
{ .name = CONF_MIXER_TYPE, false, false },
{ .name = CONF_REPLAYGAIN, false, false },
{ .name = CONF_REPLAYGAIN_PREAMP, false, false },
{ .name = CONF_REPLAYGAIN_MISSING_PREAMP, false, false },
{ .name = CONF_REPLAYGAIN_LIMIT, false, false },
{ .name = CONF_VOLUME_NORMALIZATION, false, false },
{ .name = CONF_SAMPLERATE_CONVERTER, false, false },
{ .name = CONF_AUDIO_BUFFER_SIZE, false, false },
{ .name = CONF_BUFFER_BEFORE_PLAY, false, false },
{ .name = CONF_HTTP_PROXY_HOST, false, false },
{ .name = CONF_HTTP_PROXY_PORT, false, false },
{ .name = CONF_HTTP_PROXY_USER, false, false },
{ .name = CONF_HTTP_PROXY_PASSWORD, false, false },
{ .name = CONF_CONN_TIMEOUT, false, false },
{ .name = CONF_MAX_CONN, false, false },
{ .name = CONF_MAX_PLAYLIST_LENGTH, false, false },
{ .name = CONF_MAX_COMMAND_LIST_SIZE, false, false },
{ .name = CONF_MAX_OUTPUT_BUFFER_SIZE, false, false },
{ .name = CONF_FS_CHARSET, false, false },
{ .name = CONF_ID3V1_ENCODING, false, false },
{ .name = CONF_METADATA_TO_USE, false, false },
{ .name = CONF_SAVE_ABSOLUTE_PATHS, false, false },
{ .name = CONF_DECODER, true, true },
{ .name = CONF_INPUT, true, true },
{ .name = CONF_GAPLESS_MP3_PLAYBACK, false, false },
{ .name = CONF_PLAYLIST_PLUGIN, true, true },
{ .name = CONF_AUTO_UPDATE, false, false },
{ .name = CONF_AUTO_UPDATE_DEPTH, false, false },
{ .name = "filter", true, true },
};
static GSList *config_entries;
static bool
get_bool(const char *value, bool *value_r)
static int get_bool(const char *value)
{
const char **x;
static const char *t[] = { "yes", "true", "1", NULL };
static const char *f[] = { "no", "false", "0", NULL };
if (string_array_contains(t, value)) {
*value_r = true;
return true;
for (x = t; *x; x++) {
if (!g_ascii_strcasecmp(*x, value))
return 1;
}
if (string_array_contains(f, value)) {
*value_r = false;
return true;
for (x = f; *x; x++) {
if (!g_ascii_strcasecmp(*x, value))
return 0;
}
return false;
return CONF_BOOL_INVALID;
}
struct config_param *
@@ -132,14 +83,15 @@ config_new_param(const char *value, int line)
ret->num_block_params = 0;
ret->block_params = NULL;
ret->used = false;
return ret;
}
static void
config_param_free(struct config_param *param)
config_param_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
{
struct config_param *param = data;
g_free(param->value);
for (unsigned i = 0; i < param->num_block_params; i++) {
@@ -153,19 +105,42 @@ config_param_free(struct config_param *param)
g_free(param);
}
static void
config_param_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
static struct config_entry *
newConfigEntry(const char *name, int repeatable, int block)
{
struct config_param *param = data;
struct config_entry *ret = g_new(struct config_entry, 1);
config_param_free(param);
ret->name = name;
ret->mask = 0;
ret->params = NULL;
if (repeatable)
ret->mask |= CONF_REPEATABLE_MASK;
if (block)
ret->mask |= CONF_BLOCK_MASK;
return ret;
}
static void
config_entry_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
{
struct config_entry *entry = data;
g_slist_foreach(entry->params, config_param_free, NULL);
g_slist_free(entry->params);
g_free(entry);
}
static struct config_entry *
config_entry_get(const char *name)
{
for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) {
struct config_entry *entry = &config_entries[i];
GSList *list;
for (list = config_entries; list != NULL;
list = g_slist_next(list)) {
struct config_entry *entry = list->data;
if (strcmp(entry->name, name) == 0)
return entry;
}
@@ -173,65 +148,82 @@ config_entry_get(const char *name)
return NULL;
}
static void registerConfigParam(const char *name, int repeatable, int block)
{
struct config_entry *entry;
entry = config_entry_get(name);
if (entry != NULL)
g_error("config parameter \"%s\" already registered\n", name);
entry = newConfigEntry(name, repeatable, block);
config_entries = g_slist_prepend(config_entries, entry);
}
void config_global_finish(void)
{
for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) {
struct config_entry *entry = &config_entries[i];
g_slist_foreach(entry->params,
config_param_free_callback, NULL);
g_slist_free(entry->params);
}
g_slist_foreach(config_entries, config_entry_free, NULL);
g_slist_free(config_entries);
}
void config_global_init(void)
{
config_entries = NULL;
/* registerConfigParam(name, repeatable, block); */
registerConfigParam(CONF_MUSIC_DIR, 0, 0);
registerConfigParam(CONF_PLAYLIST_DIR, 0, 0);
registerConfigParam(CONF_FOLLOW_INSIDE_SYMLINKS, 0, 0);
registerConfigParam(CONF_FOLLOW_OUTSIDE_SYMLINKS, 0, 0);
registerConfigParam(CONF_DB_FILE, 0, 0);
registerConfigParam(CONF_STICKER_FILE, false, false);
registerConfigParam(CONF_LOG_FILE, 0, 0);
registerConfigParam(CONF_ERROR_FILE, 0, 0);
registerConfigParam(CONF_PID_FILE, 0, 0);
registerConfigParam(CONF_STATE_FILE, 0, 0);
registerConfigParam(CONF_USER, 0, 0);
registerConfigParam(CONF_BIND_TO_ADDRESS, 1, 0);
registerConfigParam(CONF_PORT, 0, 0);
registerConfigParam(CONF_LOG_LEVEL, 0, 0);
registerConfigParam(CONF_ZEROCONF_NAME, 0, 0);
registerConfigParam(CONF_ZEROCONF_ENABLED, 0, 0);
registerConfigParam(CONF_PASSWORD, 1, 0);
registerConfigParam(CONF_DEFAULT_PERMS, 0, 0);
registerConfigParam(CONF_AUDIO_OUTPUT, 1, 1);
registerConfigParam(CONF_AUDIO_OUTPUT_FORMAT, 0, 0);
registerConfigParam(CONF_MIXER_TYPE, 0, 0);
registerConfigParam(CONF_MIXER_DEVICE, 0, 0);
registerConfigParam(CONF_MIXER_CONTROL, 0, 0);
registerConfigParam(CONF_REPLAYGAIN, 0, 0);
registerConfigParam(CONF_REPLAYGAIN_PREAMP, 0, 0);
registerConfigParam(CONF_VOLUME_NORMALIZATION, 0, 0);
registerConfigParam(CONF_SAMPLERATE_CONVERTER, 0, 0);
registerConfigParam(CONF_AUDIO_BUFFER_SIZE, 0, 0);
registerConfigParam(CONF_BUFFER_BEFORE_PLAY, 0, 0);
registerConfigParam(CONF_HTTP_PROXY_HOST, 0, 0);
registerConfigParam(CONF_HTTP_PROXY_PORT, 0, 0);
registerConfigParam(CONF_HTTP_PROXY_USER, 0, 0);
registerConfigParam(CONF_HTTP_PROXY_PASSWORD, 0, 0);
registerConfigParam(CONF_CONN_TIMEOUT, 0, 0);
registerConfigParam(CONF_MAX_CONN, 0, 0);
registerConfigParam(CONF_MAX_PLAYLIST_LENGTH, 0, 0);
registerConfigParam(CONF_MAX_COMMAND_LIST_SIZE, 0, 0);
registerConfigParam(CONF_MAX_OUTPUT_BUFFER_SIZE, 0, 0);
registerConfigParam(CONF_FS_CHARSET, 0, 0);
registerConfigParam(CONF_ID3V1_ENCODING, 0, 0);
registerConfigParam(CONF_METADATA_TO_USE, 0, 0);
registerConfigParam(CONF_SAVE_ABSOLUTE_PATHS, 0, 0);
registerConfigParam(CONF_DECODER, true, true);
registerConfigParam(CONF_INPUT, true, true);
registerConfigParam(CONF_GAPLESS_MP3_PLAYBACK, 0, 0);
}
static void
config_param_check(gpointer data, G_GNUC_UNUSED gpointer user_data)
{
struct config_param *param = data;
if (!param->used)
/* this whole config_param was not queried at all -
the feature might be disabled at compile time?
Silently ignore it here. */
return;
for (unsigned i = 0; i < param->num_block_params; i++) {
struct block_param *bp = &param->block_params[i];
if (!bp->used)
g_warning("option '%s' on line %i was not recognized",
bp->name, bp->line);
}
}
void config_global_check(void)
{
for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) {
struct config_entry *entry = &config_entries[i];
g_slist_foreach(entry->params, config_param_check, NULL);
}
}
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;
}
param->num_block_params++;
param->block_params = g_realloc(param->block_params,
@@ -243,97 +235,67 @@ config_add_block_param(struct config_param * param, const char *name,
bp->name = g_strdup(name);
bp->value = g_strdup(value);
bp->line = line;
bp->used = false;
return true;
}
static struct config_param *
config_read_block(FILE *fp, int *count, char *string, GError **error_r)
config_read_block(FILE *fp, int *count, char *string)
{
struct config_param *ret = config_new_param(NULL, *count);
GError *error = NULL;
bool success;
while (true) {
char *line;
const char *name, *value;
int i;
int numberOfArgs;
int argsMinusComment;
line = fgets(string, MAX_STRING_SIZE, fp);
if (line == NULL) {
config_param_free(ret);
g_set_error(error_r, config_quark(), 0,
"Expected '}' before end-of-file");
return NULL;
}
while (fgets(string, MAX_STRING_SIZE, fp)) {
char *array[CONF_LINE_TOKEN_MAX] = { NULL };
(*count)++;
line = g_strchug(line);
if (*line == 0 || *line == CONF_COMMENT)
numberOfArgs = buffer2array(string, array, CONF_LINE_TOKEN_MAX);
for (i = 0; i < numberOfArgs; i++) {
if (array[i][0] == CONF_COMMENT)
break;
}
argsMinusComment = i;
if (0 == argsMinusComment) {
continue;
if (*line == '}') {
/* end of this block; return from the function
(and from this "while" loop) */
line = g_strchug(line + 1);
if (*line != 0 && *line != CONF_COMMENT) {
config_param_free(ret);
g_set_error(error_r, config_quark(), 0,
"line %i: Unknown tokens after '}'",
*count);
return false;
}
return ret;
}
/* parse name and value */
name = tokenizer_next_word(&line, &error);
if (name == NULL) {
assert(*line != 0);
config_param_free(ret);
g_propagate_prefixed_error(error_r, error,
"line %i: ", *count);
return NULL;
if (1 == argsMinusComment &&
0 == strcmp(array[0], CONF_BLOCK_END)) {
break;
}
value = tokenizer_next_string(&line, &error);
if (value == NULL) {
config_param_free(ret);
if (*line == 0)
g_set_error(error_r, config_quark(), 0,
"line %i: Value missing", *count);
else
g_propagate_prefixed_error(error_r, error,
"line %i: ",
*count);
return NULL;
if (2 != argsMinusComment) {
g_error("improperly formatted config file at line %i:"
" %s\n", *count, string);
}
if (*line != 0 && *line != CONF_COMMENT) {
config_param_free(ret);
g_set_error(error_r, config_quark(), 0,
"line %i: Unknown tokens after value",
*count);
return NULL;
if (0 == strcmp(array[0], CONF_BLOCK_BEGIN) ||
0 == strcmp(array[1], CONF_BLOCK_BEGIN) ||
0 == strcmp(array[0], CONF_BLOCK_END) ||
0 == strcmp(array[1], CONF_BLOCK_END)) {
g_error("improperly formatted config file at line %i: %s "
"in block beginning at line %i\n",
*count, string, ret->line);
}
success = config_add_block_param(ret, name, value, *count,
error_r);
if (!success) {
config_param_free(ret);
return false;
}
config_add_block_param(ret, array[0], array[1], *count);
}
return ret;
}
bool
config_read_file(const char *file, GError **error_r)
void config_read_file(const char *file)
{
FILE *fp;
char string[MAX_STRING_SIZE + 1];
int i;
int numberOfArgs;
int argsMinusComment;
int count = 0;
struct config_entry *entry;
struct config_param *param;
@@ -341,110 +303,67 @@ config_read_file(const char *file, GError **error_r)
g_debug("loading file %s", file);
if (!(fp = fopen(file, "r"))) {
g_set_error(error_r, config_quark(), errno,
"Failed to open %s: %s",
file, strerror(errno));
return false;
g_error("problems opening file %s for reading: %s\n",
file, strerror(errno));
}
while (fgets(string, MAX_STRING_SIZE, fp)) {
char *line;
const char *name, *value;
GError *error = NULL;
char *array[CONF_LINE_TOKEN_MAX] = { NULL };
count++;
line = g_strchug(string);
if (*line == 0 || *line == CONF_COMMENT)
numberOfArgs = buffer2array(string, array, CONF_LINE_TOKEN_MAX);
for (i = 0; i < numberOfArgs; i++) {
if (array[i][0] == CONF_COMMENT)
break;
}
argsMinusComment = i;
if (0 == argsMinusComment) {
continue;
/* the first token in each line is the name, followed
by either the value or '{' */
name = tokenizer_next_word(&line, &error);
if (name == NULL) {
assert(*line != 0);
g_propagate_prefixed_error(error_r, error,
"line %i: ", count);
return false;
}
/* get the definition of that option, and check the
"repeatable" flag */
entry = config_entry_get(name);
if (entry == NULL) {
g_set_error(error_r, config_quark(), 0,
"unrecognized parameter in config file at "
"line %i: %s\n", count, name);
return false;
if (2 != argsMinusComment) {
g_error("improperly formatted config file at line %i:"
" %s\n", count, string);
}
if (entry->params != NULL && !entry->repeatable) {
entry = config_entry_get(array[0]);
if (entry == NULL)
g_error("unrecognized parameter in config file at "
"line %i: %s\n", count, string);
if (!(entry->mask & CONF_REPEATABLE_MASK) &&
entry->params != NULL) {
param = entry->params->data;
g_set_error(error_r, config_quark(), 0,
"config parameter \"%s\" is first defined "
"on line %i and redefined on line %i\n",
name, param->line, count);
return false;
g_error("config parameter \"%s\" is first defined on "
"line %i and redefined on line %i\n",
array[0], param->line, count);
}
/* now parse the block or the value */
if (entry->block) {
/* it's a block, call config_read_block() */
if (*line != '{') {
g_set_error(error_r, config_quark(), 0,
"line %i: '{' expected", count);
return false;
if (entry->mask & CONF_BLOCK_MASK) {
if (0 != strcmp(array[1], CONF_BLOCK_BEGIN)) {
g_error("improperly formatted config file at "
"line %i: %s\n", count, string);
}
line = g_strchug(line + 1);
if (*line != 0 && *line != CONF_COMMENT) {
g_set_error(error_r, config_quark(), 0,
"line %i: Unknown tokens after '{'",
count);
return false;
}
param = config_read_block(fp, &count, string, error_r);
if (param == NULL)
return false;
} else {
/* a string value */
value = tokenizer_next_string(&line, &error);
if (value == NULL) {
if (*line == 0)
g_set_error(error_r, config_quark(), 0,
"line %i: Value missing",
count);
else {
g_set_error(error_r, config_quark(), 0,
"line %i: %s", count,
error->message);
g_error_free(error);
}
return false;
}
if (*line != 0 && *line != CONF_COMMENT) {
g_set_error(error_r, config_quark(), 0,
"line %i: Unknown tokens after value",
count);
return false;
}
param = config_new_param(value, count);
}
param = config_read_block(fp, &count, string);
} else
param = config_new_param(array[1], count);
entry->params = g_slist_append(entry->params, param);
}
fclose(fp);
}
return true;
void
config_add_param(const char *name, struct config_param *param)
{
struct config_entry *entry = config_entry_get(name);
assert(entry != NULL);
entry->params = g_slist_append(entry->params, param);
}
struct config_param *
@@ -472,7 +391,7 @@ config_get_next_param(const char *name, const struct config_param * last)
return NULL;
param = node->data;
param->used = true;
return param;
}
@@ -505,23 +424,6 @@ config_get_path(const char *name)
return param->value = path;
}
unsigned
config_get_unsigned(const char *name, unsigned default_value)
{
const struct config_param *param = config_get_param(name);
long value;
char *endptr;
if (param == NULL)
return default_value;
value = strtol(param->value, &endptr, 0);
if (*endptr != 0 || value < 0)
g_error("Not a valid non-negative number in line %i", param->line);
return (unsigned)value;
}
unsigned
config_get_positive(const char *name, unsigned default_value)
{
@@ -545,35 +447,43 @@ config_get_positive(const char *name, unsigned default_value)
struct block_param *
config_get_block_param(const struct config_param * param, const char *name)
{
struct block_param *ret = NULL;
if (param == NULL)
return NULL;
for (unsigned i = 0; i < param->num_block_params; i++) {
if (0 == strcmp(name, param->block_params[i].name)) {
struct block_param *bp = &param->block_params[i];
bp->used = true;
return bp;
if (ret) {
g_warning("\"%s\" first defined on line %i, and "
"redefined on line %i\n", name,
ret->line, param->block_params[i].line);
}
ret = param->block_params + i;
}
}
return NULL;
return ret;
}
bool config_get_bool(const char *name, bool default_value)
{
const struct config_param *param = config_get_param(name);
bool success, value;
int value;
if (param == NULL)
return default_value;
success = get_bool(param->value, &value);
if (!success)
value = get_bool(param->value);
if (value == CONF_BOOL_INVALID)
g_error("%s is not a boolean value (yes, true, 1) or "
"(no, false, 0) on line %i\n",
name, param->line);
return value;
if (value == CONF_BOOL_UNSET)
return default_value;
return !!value;
}
const char *
@@ -614,16 +524,19 @@ 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);
bool success, value;
int value;
if (bp == NULL)
return default_value;
success = get_bool(bp->value, &value);
if (!success)
value = get_bool(bp->value);
if (value == CONF_BOOL_INVALID)
g_error("%s is not a boolean value (yes, true, 1) or "
"(no, false, 0) on line %i\n",
name, bp->line);
return value;
if (value == CONF_BOOL_UNSET)
return default_value;
return !!value;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -30,10 +30,10 @@
#define CONF_DB_FILE "db_file"
#define CONF_STICKER_FILE "sticker_file"
#define CONF_LOG_FILE "log_file"
#define CONF_ERROR_FILE "error_file"
#define CONF_PID_FILE "pid_file"
#define CONF_STATE_FILE "state_file"
#define CONF_USER "user"
#define CONF_GROUP "group"
#define CONF_BIND_TO_ADDRESS "bind_to_address"
#define CONF_PORT "port"
#define CONF_LOG_LEVEL "log_level"
@@ -42,13 +42,12 @@
#define CONF_PASSWORD "password"
#define CONF_DEFAULT_PERMS "default_permissions"
#define CONF_AUDIO_OUTPUT "audio_output"
#define CONF_AUDIO_FILTER "filter"
#define CONF_AUDIO_OUTPUT_FORMAT "audio_output_format"
#define CONF_MIXER_TYPE "mixer_type"
#define CONF_MIXER_DEVICE "mixer_device"
#define CONF_MIXER_CONTROL "mixer_control"
#define CONF_REPLAYGAIN "replaygain"
#define CONF_REPLAYGAIN_PREAMP "replaygain_preamp"
#define CONF_REPLAYGAIN_MISSING_PREAMP "replaygain_missing_preamp"
#define CONF_REPLAYGAIN_LIMIT "replaygain_limit"
#define CONF_VOLUME_NORMALIZATION "volume_normalization"
#define CONF_SAMPLERATE_CONVERTER "samplerate_converter"
#define CONF_AUDIO_BUFFER_SIZE "audio_buffer_size"
@@ -69,25 +68,17 @@
#define CONF_DECODER "decoder"
#define CONF_INPUT "input"
#define CONF_GAPLESS_MP3_PLAYBACK "gapless_mp3_playback"
#define CONF_PLAYLIST_PLUGIN "playlist_plugin"
#define CONF_AUTO_UPDATE "auto_update"
#define CONF_AUTO_UPDATE_DEPTH "auto_update_depth"
#define CONF_BOOL_UNSET -1
#define CONF_BOOL_INVALID -2
#define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16)
#define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false
#define MAX_FILTER_CHAIN_LENGTH 255
struct block_param {
char *name;
char *value;
int line;
/**
* This flag is false when nobody has queried the value of
* this option yet.
*/
bool used;
};
struct config_param {
@@ -96,57 +87,31 @@ struct config_param {
struct block_param *block_params;
unsigned num_block_params;
/**
* This flag is false when nobody has queried the value of
* this option yet.
*/
bool used;
};
/**
* A GQuark for GError instances, resulting from malformed
* configuration.
*/
static inline GQuark
config_quark(void)
{
return g_quark_from_static_string("config");
}
void config_global_init(void);
void config_global_finish(void);
/**
* Call this function after all configuration has been evaluated. It
* checks for unused parameters, and logs warnings.
*/
void config_global_check(void);
void config_read_file(const char *file);
bool
config_read_file(const char *file, GError **error_r);
/**
* Adds a new configuration parameter. The name must be registered
* with registerConfigParam().
*/
void
config_add_param(const char *name, struct config_param *param);
/* don't free the returned value
set _last_ to NULL to get first entry */
G_GNUC_PURE
struct config_param *
config_get_next_param(const char *name, const struct config_param *last);
G_GNUC_PURE
static inline struct config_param *
config_get_param(const char *name)
{
return config_get_next_param(name, NULL);
}
/* Note on G_GNUC_PURE: Some of the functions declared pure are not
really pure in strict sense. They have side effect such that they
validate parameter's value and signal an error if it's invalid.
However, if the argument was already validated or we don't care
about the argument at all, this may be ignored so in the end, we
should be fine with calling those functions pure. */
G_GNUC_PURE
const char *
config_get_string(const char *name, const char *default_value);
@@ -155,31 +120,17 @@ config_get_string(const char *name, const char *default_value);
* absolute path. If there is a tilde prefix, it is expanded. Aborts
* MPD if the path is not a valid absolute path.
*/
/* We lie here really. This function is not pure as it has side
effects -- it parse the value and creates new string freeing
previous one. However, because this works the very same way each
time (ie. from the outside it appears as if function had no side
effects) we should be in the clear declaring it pure. */
G_GNUC_PURE
const char *
config_get_path(const char *name);
G_GNUC_PURE
unsigned
config_get_unsigned(const char *name, unsigned default_value);
G_GNUC_PURE
unsigned
config_get_positive(const char *name, unsigned default_value);
G_GNUC_PURE
struct block_param *
config_get_block_param(const struct config_param *param, const char *name);
G_GNUC_PURE
bool config_get_bool(const char *name, bool default_value);
G_GNUC_PURE
const char *
config_get_block_string(const struct config_param *param, const char *name,
const char *default_value);
@@ -191,12 +142,10 @@ config_dup_block_string(const struct config_param *param, const char *name,
return g_strdup(config_get_block_string(param, name, default_value));
}
G_GNUC_PURE
unsigned
config_get_block_unsigned(const struct config_param *param, const char *name,
unsigned default_value);
G_GNUC_PURE
bool
config_get_block_bool(const struct config_param *param, const char *name,
bool default_value);
@@ -204,8 +153,8 @@ config_get_block_bool(const struct config_param *param, const char *name,
struct config_param *
config_new_param(const char *value, int line);
bool
config_add_block_param(struct config_param * param, const char *name,
const char *value, int line, GError **error_r);
void
config_add_block_param(struct config_param *param, const char *name,
const char *value, int line);
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -17,7 +17,6 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "crossfade.h"
#include "pcm_mix.h"
#include "chunk.h"
@@ -26,112 +25,73 @@
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <glib.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "crossfade"
#ifdef G_OS_WIN32
static char *
strtok_r(char *str, const char *delim, G_GNUC_UNUSED char **saveptr)
{
return strtok(str, delim);
}
#endif
static float mixramp_interpolate(char *ramp_list, float required_db)
{
float db, secs, last_db = nan(""), last_secs = 0;
char *ramp_str, *save_str = NULL;
/* ramp_list is a string of pairs of dBs and seconds that describe the
* volume profile. Delimiters are semi-colons between pairs and spaces
* between the dB and seconds of a pair.
* The dB values must be monotonically increasing for this to work. */
while (1) {
/* Parse the dB tokens out of the input string. */
ramp_str = strtok_r(ramp_list, " ", &save_str);
/* Tell strtok to continue next time round. */
ramp_list = NULL;
/* Parse the dB value. */
if (NULL == ramp_str) {
return nan("");
}
db = (float)atof(ramp_str);
/* Parse the time. */
ramp_str = strtok_r(NULL, ";", &save_str);
if (NULL == ramp_str) {
return nan("");
}
secs = (float)atof(ramp_str);
/* Check for exact match. */
if (db == required_db) {
return secs;
}
/* Save if too quiet. */
if (db < required_db) {
last_db = db;
last_secs = secs;
continue;
}
/* If required db < any stored value, use the least. */
if (isnan(last_db)) {
return secs;
}
/* Finally, interpolate linearly. */
secs = last_secs + (required_db - last_db) * (secs - last_secs) / (db - last_db);
return secs;
}
}
unsigned cross_fade_calc(float duration, float total_time,
float mixramp_db, float mixramp_delay,
float replay_gain_db, float replay_gain_prev_db,
char *mixramp_start, char *mixramp_prev_end,
const struct audio_format *af,
const struct audio_format *old_format,
unsigned max_chunks)
{
unsigned int chunks = 0;
float chunks_f;
float mixramp_overlap;
unsigned int chunks;
if (duration < 0 || duration >= total_time ||
if (duration <= 0 || duration >= total_time ||
/* we can't crossfade when the audio formats are different */
!audio_format_equals(af, old_format))
return 0;
assert(duration >= 0);
assert(audio_format_valid(af));
assert(duration > 0);
assert(af->bits > 0);
assert(af->channels > 0);
assert(af->sample_rate > 0);
chunks_f = (float)audio_format_time_to_size(af) / (float)CHUNK_SIZE;
chunks = audio_format_time_to_size(af) / CHUNK_SIZE;
chunks = (chunks * duration + 0.5);
if (isnan(mixramp_delay) || !(mixramp_start) || !(mixramp_prev_end)) {
chunks = (chunks_f * duration + 0.5);
} else {
/* Calculate mixramp overlap. */
mixramp_overlap = mixramp_interpolate(mixramp_start, mixramp_db - replay_gain_db)
+ mixramp_interpolate(mixramp_prev_end, mixramp_db - replay_gain_prev_db);
if (!isnan(mixramp_overlap) && (mixramp_delay <= mixramp_overlap)) {
chunks = (chunks_f * (mixramp_overlap - mixramp_delay));
g_debug("will overlap %d chunks, %fs", chunks,
mixramp_overlap - mixramp_delay);
}
}
if (chunks > max_chunks) {
if (chunks > max_chunks)
chunks = max_chunks;
g_warning("audio_buffer_size too small for computed MixRamp overlap");
}
return chunks;
}
void cross_fade_apply(struct music_chunk *a, const struct music_chunk *b,
const struct audio_format *format,
unsigned int current_chunk, unsigned int num_chunks)
{
size_t size;
assert(a != NULL);
assert(b != NULL);
assert(a->length == 0 || b->length == 0 ||
audio_format_equals(&a->audio_format, &b->audio_format));
assert(current_chunk <= num_chunks);
if (a->tag == NULL && b->tag != NULL)
/* merge the tag into the destination chunk */
a->tag = tag_dup(b->tag);
size = b->length > a->length
? a->length
: b->length;
pcm_mix(a->data,
b->data,
size,
format,
((float)current_chunk) / num_chunks);
if (b->length > a->length) {
/* the second buffer is larger than the first one:
there is unmixed rest at the end. Copy it over.
The output buffer API guarantees that there is
enough room in a->data. */
#ifndef NDEBUG
if (a->length == 0)
a->audio_format = b->audio_format;
#endif
memcpy(a->data + a->length,
b->data + a->length,
b->length - a->length);
a->length = b->length;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -28,12 +28,6 @@ struct music_chunk;
*
* @param duration the requested crossfade duration
* @param total_time total_time the duration of the new song
* @param mixramp_db the current mixramp_db setting
* @param mixramp_delay the current mixramp_delay setting
* @param replay_gain_db the ReplayGain adjustment used for this song
* @param replay_gain_prev_db the ReplayGain adjustment used on the last song
* @param mixramp_start the next songs mixramp_start tag
* @param mixramp_prev_end the last songs mixramp_end setting
* @param af the audio format of the new song
* @param old_format the audio format of the current song
* @param max_chunks the maximum number of chunks
@@ -41,11 +35,22 @@ struct music_chunk;
* should be disabled for this song change
*/
unsigned cross_fade_calc(float duration, float total_time,
float mixramp_db, float mixramp_delay,
float replay_gain_db, float replay_gain_prev_db,
char *mixramp_start, char *mixramp_prev_end,
const struct audio_format *af,
const struct audio_format *old_format,
unsigned max_chunks);
/**
* Applies cross fading to two chunks, i.e. mixes these chunks.
* Internally, this calls pcm_mix().
*
* @param a the chunk in the current song (and the destination chunk)
* @param b the according chunk in the new song
* @param format the audio format of both chunks (must be the same)
* @param current_chunk the relative index of the current chunk
* @param num_chunks the number of chunks used for cross fading
*/
void cross_fade_apply(struct music_chunk *a, const struct music_chunk *b,
const struct audio_format *format,
unsigned int current_chunk, unsigned int num_chunks);
#endif

View File

@@ -1,78 +1,76 @@
#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)
static struct tag*
cue_tag_cd(struct Cdtext* cdtext, struct Rem* rem)
{
struct tag *tag;
char *tmp;
char* tmp = NULL;
struct tag* tag = NULL;
assert(cdtext != NULL);
//if (cdtext == NULL)
//return NULL;
tag = tag_new();
tag_begin_add(tag);
/* TAG_ALBUM_ARTIST */
{ /* TAG_ITEM_ALBUM_ARTIST */
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
tag_add_item(tag, TAG_ALBUM_ARTIST, tmp);
tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL)
tag_add_item(tag, TAG_ALBUM_ARTIST, tmp);
tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
tag_add_item(tag, TAG_ALBUM_ARTIST, tmp);
tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL)
tag_add_item(tag, TAG_ALBUM_ARTIST, tmp);
tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp);
/* TAG_ITEM_ALBUM_ARTIST */ }
/* TAG_ARTIST */
{ /* TAG_ITEM_ARTIST */
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
tag_add_item(tag, TAG_ARTIST, tmp);
tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL)
tag_add_item(tag, TAG_ARTIST, tmp);
tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
tag_add_item(tag, TAG_ARTIST, tmp);
tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL)
tag_add_item(tag, TAG_ARTIST, tmp);
tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
/* TAG_ITEM_ARTIST */ }
/* TAG_PERFORMER */
/* TAG_ITEM_PERFORMER */
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
tag_add_item(tag, TAG_PERFORMER, tmp);
tag_add_item(tag, TAG_ITEM_PERFORMER, tmp);
/* TAG_COMPOSER */
/* TAG_ITEM_COMPOSER */
if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
tag_add_item(tag, TAG_COMPOSER, tmp);
tag_add_item(tag, TAG_ITEM_COMPOSER, tmp);
/* TAG_ALBUM */
/* TAG_ITEM_ALBUM */
if ((tmp = cdtext_get(PTI_TITLE, cdtext)) != NULL)
tag_add_item(tag, TAG_ALBUM, tmp);
tag_add_item(tag, TAG_ITEM_ALBUM, tmp);
/* TAG_GENRE */
/* TAG_ITEM_GENRE */
if ((tmp = cdtext_get(PTI_GENRE, cdtext)) != NULL)
tag_add_item(tag, TAG_GENRE, tmp);
tag_add_item(tag, TAG_ITEM_GENRE, tmp);
/* TAG_DATE */
/* TAG_ITEM_DATE */
if ((tmp = rem_get(REM_DATE, rem)) != NULL)
tag_add_item(tag, TAG_DATE, tmp);
tag_add_item(tag, TAG_ITEM_DATE, tmp);
/* TAG_COMMENT */
/* TAG_ITEM_COMMENT */
if ((tmp = cdtext_get(PTI_MESSAGE, cdtext)) != NULL)
tag_add_item(tag, TAG_COMMENT, tmp);
tag_add_item(tag, TAG_ITEM_COMMENT, tmp);
/* TAG_DISC */
/* TAG_ITEM_DISC */
if ((tmp = cdtext_get(PTI_DISC_ID, cdtext)) != NULL)
tag_add_item(tag, TAG_DISC, tmp);
tag_add_item(tag, TAG_ITEM_DISC, tmp);
/* stream name, usually empty
* tag_add_item(tag, TAG_NAME,);
* tag_add_item(tag, TAG_ITEM_NAME,);
*/
/* REM MUSICBRAINZ entry?
@@ -84,152 +82,175 @@ cue_tag_cd(struct Cdtext *cdtext, struct Rem *rem)
tag_end_add(tag);
if (tag_is_empty(tag)) {
tag_free(tag);
return NULL;
if (tag != NULL)
{
if (tag_is_empty(tag))
{
tag_free(tag);
return NULL;
}
else
return tag;
}
return tag;
else
return NULL;
}
static struct tag *
cue_tag_track(struct Cdtext *cdtext, struct Rem *rem)
static struct tag*
cue_tag_track(struct Cdtext* cdtext, struct Rem* rem)
{
struct tag *tag;
char *tmp;
char* tmp = NULL;
struct tag* tag = NULL;
assert(cdtext != NULL);
//if (cdtext == NULL)
//return NULL;
tag = tag_new();
tag_begin_add(tag);
/* TAG_ARTIST */
{ /* TAG_ITEM_ARTIST */
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
tag_add_item(tag, TAG_ARTIST, tmp);
tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL)
tag_add_item(tag, TAG_ARTIST, tmp);
tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
tag_add_item(tag, TAG_ARTIST, tmp);
tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL)
tag_add_item(tag, TAG_ARTIST, tmp);
tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
/* TAG_ITEM_ARTIST */ }
/* TAG_TITLE */
/* TAG_ITEM_TITLE */
if ((tmp = cdtext_get(PTI_TITLE, cdtext)) != NULL)
tag_add_item(tag, TAG_TITLE, tmp);
tag_add_item(tag, TAG_ITEM_TITLE, tmp);
/* TAG_GENRE */
/* TAG_ITEM_GENRE */
if ((tmp = cdtext_get(PTI_GENRE, cdtext)) != NULL)
tag_add_item(tag, TAG_GENRE, tmp);
tag_add_item(tag, TAG_ITEM_GENRE, tmp);
/* TAG_DATE */
/* TAG_ITEM_DATE */
if ((tmp = rem_get(REM_DATE, rem)) != NULL)
tag_add_item(tag, TAG_DATE, tmp);
tag_add_item(tag, TAG_ITEM_DATE, tmp);
/* TAG_COMPOSER */
/* TAG_ITEM_COMPOSER */
if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
tag_add_item(tag, TAG_COMPOSER, tmp);
tag_add_item(tag, TAG_ITEM_COMPOSER, tmp);
/* TAG_PERFORMER */
/* TAG_ITEM_PERFORMER */
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
tag_add_item(tag, TAG_PERFORMER, tmp);
tag_add_item(tag, TAG_ITEM_PERFORMER, tmp);
/* TAG_COMMENT */
/* TAG_ITEM_COMMENT */
if ((tmp = cdtext_get(PTI_MESSAGE, cdtext)) != NULL)
tag_add_item(tag, TAG_COMMENT, tmp);
tag_add_item(tag, TAG_ITEM_COMMENT, tmp);
/* TAG_DISC */
/* TAG_ITEM_DISC */
if ((tmp = cdtext_get(PTI_DISC_ID, cdtext)) != NULL)
tag_add_item(tag, TAG_DISC, tmp);
tag_add_item(tag, TAG_ITEM_DISC, tmp);
tag_end_add(tag);
if (tag_is_empty(tag)) {
tag_free(tag);
if (tag != NULL)
{
if (tag_is_empty(tag))
{
tag_free(tag);
return NULL;
}
else
return tag;
}
else
return NULL;
}
struct tag*
cue_tag_file( FILE* fp,
const unsigned int tnum)
{
struct tag* cd_tag = NULL;
struct tag* track_tag = NULL;
struct Cd* cd = NULL;
if (tnum > 256)
return NULL;
if (fp == NULL)
return NULL;
else
cd = cue_parse_file(fp);
if (cd == NULL)
return NULL;
else
{
/* tag from CDtext info */
cd_tag = cue_tag_cd( cd_get_cdtext(cd),
cd_get_rem(cd));
/* tag from TRACKtext info */
track_tag = cue_tag_track( track_get_cdtext( cd_get_track(cd, tnum)),
track_get_rem( cd_get_track(cd, tnum)));
cd_delete(cd);
}
return tag;
return tag_merge_replace(cd_tag, track_tag);
}
struct tag *
cue_tag(struct Cd *cd, unsigned tnum)
struct tag*
cue_tag_string( char* str,
const unsigned int 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);
struct tag* cd_tag = NULL;
struct tag* track_tag = NULL;
struct tag* merge_tag = NULL;
struct Cd* cd = NULL;
if (tnum > 256)
return NULL;
cd = cue_parse_file(fp);
if (str == NULL)
return NULL;
else
cd = cue_parse_string(str);
if (cd == NULL)
return NULL;
else
{
/* tag from CDtext info */
cd_tag = cue_tag_cd( cd_get_cdtext(cd),
cd_get_rem(cd));
tag = cue_tag(cd, tnum);
cd_delete(cd);
/* tag from TRACKtext info */
track_tag = cue_tag_track( track_get_cdtext( cd_get_track(cd, tnum)),
track_get_rem( cd_get_track(cd, tnum)));
return tag;
}
cd_delete(cd);
}
struct tag *
cue_tag_string(const char *str, unsigned tnum)
{
struct Cd *cd;
struct tag *tag;
if ((cd_tag != NULL) && (track_tag != NULL))
{
merge_tag = tag_merge(cd_tag, track_tag);
tag_free(cd_tag);
tag_free(track_tag);
return merge_tag;
}
assert(str != NULL);
else if (cd_tag != NULL)
{
return cd_tag;
}
if (tnum > 256)
else if (track_tag != NULL)
{
return track_tag;
}
else
return NULL;
cd = cue_parse_string(str);
if (cd == NULL)
return NULL;
tag = cue_tag(cd, tnum);
cd_delete(cd);
return tag;
}

View File

@@ -1,23 +1,20 @@
#ifndef MPD_CUE_TAG_H
#define MPD_CUE_TAG_H
#include "check.h"
#include "config.h"
#ifdef HAVE_CUE /* libcue */
#include <stdio.h>
#include <libcue/libcue.h>
#include "../tag.h"
struct tag;
struct Cd;
struct tag*
cue_tag_file( FILE*,
const unsigned int);
struct tag *
cue_tag(struct Cd *cd, unsigned tnum);
struct tag *
cue_tag_file(FILE *file, unsigned tnum);
struct tag *
cue_tag_string(const char *str, unsigned tnum);
struct tag*
cue_tag_string( char*,
const unsigned int);
#endif /* libcue */
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -17,7 +17,6 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "daemon.h"
#include <glib.h>
@@ -46,21 +45,20 @@
static char *user_name;
/** the Unix user id which MPD runs as */
static uid_t user_uid = (uid_t)-1;
static uid_t user_uid;
/** the Unix group id which MPD runs as */
static gid_t user_gid = (pid_t)-1;
static gid_t user_gid;
/** the absolute path of the pidfile */
static char *pidfile;
/* whether "group" conf. option was given */
static bool had_group = false;
#endif
void
daemonize_kill(void)
{
#ifndef WIN32
FILE *fp;
int pid, ret;
@@ -84,34 +82,41 @@ daemonize_kill(void)
pid, g_strerror(errno));
exit(EXIT_SUCCESS);
#else
g_error("--kill is not available on WIN32");
#endif
}
void
daemonize_close_stdin(void)
{
close(STDIN_FILENO);
open("/dev/null", O_RDONLY);
int fd = open("/dev/null", O_RDONLY);
if (fd < 0)
close(STDIN_FILENO);
else if (fd != STDIN_FILENO) {
dup2(fd, STDIN_FILENO);
close(fd);
}
}
void
daemonize_set_user(void)
{
#ifndef WIN32
if (user_name == NULL)
return;
/* set gid */
if (user_gid != (gid_t)-1 && user_gid != getgid()) {
if (setgid(user_gid) == -1) {
g_error("cannot setgid to %d: %s",
(int)user_gid, g_strerror(errno));
}
/* get uid */
if (setgid(user_gid) == -1) {
g_error("cannot setgid for user \"%s\": %s",
user_name, g_strerror(errno));
}
#ifdef _BSD_SOURCE
/* init suplementary groups
* (must be done before we change our uid)
*/
if (!had_group && initgroups(user_name, user_gid) == -1) {
if (initgroups(user_name, user_gid) == -1) {
g_warning("cannot init supplementary groups "
"of user \"%s\": %s",
user_name, g_strerror(errno));
@@ -119,38 +124,32 @@ daemonize_set_user(void)
#endif
/* set uid */
if (user_uid != (uid_t)-1 && user_uid != getuid() &&
setuid(user_uid) == -1) {
if (setuid(user_uid) == -1) {
g_error("cannot change to uid of user \"%s\": %s",
user_name, g_strerror(errno));
}
#endif
}
#ifndef G_OS_WIN32
static void
daemonize_detach(void)
{
pid_t pid;
/* flush all file handles before duplicating the buffers */
fflush(NULL);
#ifdef HAVE_DAEMON
if (daemon(0, 1))
g_error("daemon() failed: %s", g_strerror(errno));
#elif defined(HAVE_FORK)
/* detach from parent process */
switch (fork()) {
case -1:
pid = fork();
if (pid < 0)
g_error("fork() failed: %s", g_strerror(errno));
case 0:
break;
default:
if (pid > 0)
/* exit the parent process */
_exit(EXIT_SUCCESS);
}
/* release the current working directory */
@@ -161,16 +160,14 @@ daemonize_detach(void)
setsid();
#else
g_error("no support for daemonizing");
#endif
g_debug("daemonized!");
}
#endif
void
daemonize(bool detach)
{
#ifndef WIN32
FILE *fp = NULL;
if (pidfile != NULL) {
@@ -192,45 +189,47 @@ daemonize(bool detach)
fprintf(fp, "%lu\n", (unsigned long)getpid());
fclose(fp);
}
#else
/* no daemonization on WIN32 */
(void)detach;
#endif
}
void
daemonize_init(const char *user, const char *group, const char *_pidfile)
daemonize_init(const char *user, const char *_pidfile)
{
if (user) {
struct passwd *pwd = getpwnam(user);
if (!pwd)
g_error("no such user \"%s\"", user);
#ifndef WIN32
if (user != NULL && strcmp(user, g_get_user_name()) != 0) {
struct passwd *pwd;
user_name = g_strdup(user);
pwd = getpwnam(user_name);
if (pwd == NULL)
g_error("no such user \"%s\"", user_name);
user_uid = pwd->pw_uid;
user_gid = pwd->pw_gid;
user_name = g_strdup(user);
/* this is needed by libs such as arts */
g_setenv("HOME", pwd->pw_dir, true);
}
if (group) {
struct group *grp = grp = getgrnam(group);
if (!grp)
g_error("no such group \"%s\"", group);
user_gid = grp->gr_gid;
had_group = true;
}
pidfile = g_strdup(_pidfile);
#else
(void)user;
(void)_pidfile;
#endif
}
void
daemonize_finish(void)
{
#ifndef WIN32
if (pidfile != NULL)
unlink(pidfile);
g_free(user_name);
g_free(pidfile);
}
#endif
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -22,68 +22,32 @@
#include <stdbool.h>
#ifndef WIN32
void
daemonize_init(const char *user, const char *group, const char *pidfile);
#else
static inline void
daemonize_init(const char *user, const char *group, const char *pidfile)
{ (void)user; (void)group; (void)pidfile; }
#endif
daemonize_init(const char *user, const char *pidfile);
#ifndef WIN32
void
daemonize_finish(void);
#else
static inline void
daemonize_finish(void)
{ /* nop */ }
#endif
/**
* Kill the MPD which is currently running, pid determined from the
* pid file.
*/
#ifndef WIN32
void
daemonize_kill(void);
#else
#include <glib.h>
static inline void
daemonize_kill(void)
{ g_error("--kill is not available on WIN32"); }
#endif
/**
* Close stdin (fd 0) and re-open it as /dev/null.
*/
#ifndef WIN32
void
daemonize_close_stdin(void);
#else
static inline void
daemonize_close_stdin(void) {}
#endif
/**
* Change to the configured Unix user.
*/
#ifndef WIN32
void
daemonize_set_user(void);
#else
static inline void
daemonize_set_user(void)
{ /* nop */ }
#endif
#ifndef WIN32
void
daemonize(bool detach);
#else
static inline void
daemonize(bool detach)
{ (void)detach; }
#endif
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -17,16 +17,13 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "database.h"
#include "directory.h"
#include "directory_save.h"
#include "song.h"
#include "path.h"
#include "stats.h"
#include "text_file.h"
#include "tag.h"
#include "tag_internal.h"
#include "config.h"
#include <glib.h>
@@ -43,14 +40,8 @@
#define DIRECTORY_INFO_BEGIN "info_begin"
#define DIRECTORY_INFO_END "info_end"
#define DB_FORMAT_PREFIX "format: "
#define DIRECTORY_MPD_VERSION "mpd_version: "
#define DIRECTORY_FS_CHARSET "fs_charset: "
#define DB_TAG_PREFIX "tag: "
enum {
DB_FORMAT = 1,
};
static char *database_path;
@@ -239,27 +230,20 @@ db_save(void)
return false;
}
/* block signals when writing the db so we don't get a corrupted db */
fprintf(fp, "%s\n", DIRECTORY_INFO_BEGIN);
fprintf(fp, DB_FORMAT_PREFIX "%u\n", DB_FORMAT);
fprintf(fp, "%s%s\n", DIRECTORY_MPD_VERSION, VERSION);
fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET, path_get_fs_charset());
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
if (!ignore_tag_items[i])
fprintf(fp, DB_TAG_PREFIX "%s\n", tag_item_names[i]);
fprintf(fp, "%s\n", DIRECTORY_INFO_END);
directory_save(fp, music_root);
if (ferror(fp)) {
if (directory_save(fp, music_root) < 0) {
g_warning("Failed to write to database file: %s",
strerror(errno));
fclose(fp);
while (fclose(fp) && errno == EINTR);
return false;
}
fclose(fp);
while (fclose(fp) && errno == EINTR);
if (stat(database_path, &st) == 0)
database_mtime = st.st_mtime;
@@ -272,64 +256,64 @@ db_load(GError **error)
{
FILE *fp = NULL;
struct stat st;
GString *buffer = g_string_sized_new(1024);
char *line;
int format = 0;
char buffer[100];
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 (!music_root)
music_root = directory_new("", NULL);
while (!(fp = fopen(database_path, "r")) && errno == EINTR) ;
if (fp == NULL) {
g_set_error(error, db_quark(), errno,
"Failed to open database file \"%s\": %s",
database_path, strerror(errno));
g_string_free(buffer, true);
return false;
}
/* get initial info */
line = read_text_line(fp, buffer);
if (line == NULL || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) {
if (!fgets(buffer, sizeof(buffer), fp)) {
fclose(fp);
g_set_error(error, db_quark(), 0, "Database corrupted");
g_string_free(buffer, true);
g_set_error(error, db_quark(), 0, "Unexpected end of file");
return false;
}
memset(tags, false, sizeof(tags));
g_strchomp(buffer);
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 (0 != strcmp(DIRECTORY_INFO_BEGIN, buffer)) {
fclose(fp);
g_set_error(error, db_quark(), 0, "Database corrupted");
return false;
}
while (fgets(buffer, sizeof(buffer), fp) &&
!g_str_has_prefix(buffer, DIRECTORY_INFO_END)) {
g_strchomp(buffer);
if (g_str_has_prefix(buffer, 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)) {
} else if (g_str_has_prefix(buffer, 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;
new_charset = &(buffer[strlen(DIRECTORY_FS_CHARSET)]);
old_charset = path_get_fs_charset();
if (old_charset != NULL
&& strcmp(new_charset, old_charset)) {
@@ -339,51 +323,20 @@ db_load(GError **error)
"\"%s\" instead of \"%s\"; "
"discarding database file",
new_charset, old_charset);
g_string_free(buffer, true);
return false;
}
} else if (g_str_has_prefix(line, DB_TAG_PREFIX)) {
const char *name = line + sizeof(DB_TAG_PREFIX) - 1;
enum tag_type tag = tag_name_parse(name);
if (tag == TAG_NUM_OF_ITEM_TYPES) {
g_set_error(error, db_quark(), 0,
"Unrecognized tag '%s', "
"discarding database file",
name);
return false;
}
tags[tag] = true;
} else {
fclose(fp);
g_set_error(error, db_quark(), 0,
"Malformed line: %s", 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");
"Malformed line: %s", buffer);
return false;
}
}
g_debug("reading DB");
success = directory_load(fp, music_root, buffer, error);
g_string_free(buffer, true);
fclose(fp);
success = directory_load(fp, music_root, error);
while (fclose(fp) && errno == EINTR) ;
if (!success)
return false;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -17,7 +17,6 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "dbUtils.h"
#include "locate.h"
#include "directory.h"
@@ -60,7 +59,7 @@ static int
printSongInDirectory(struct song *song, G_GNUC_UNUSED void *data)
{
struct client *client = data;
song_print_uri(client, song);
song_print_url(client, song);
return 0;
}
@@ -75,7 +74,7 @@ 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 song_print_info(data->client, song);
return 0;
}
@@ -105,7 +104,7 @@ findInDirectory(struct song *song, void *_data)
struct search_data *data = _data;
if (locate_song_match(song, data->criteria))
song_print_info(data->client, song);
return song_print_info(data->client, song);
return 0;
}
@@ -135,7 +134,8 @@ searchStatsInDirectory(struct song *song, void *data)
if (locate_song_match(song, stats->criteria)) {
stats->numberOfSongs++;
stats->playTime += song_get_duration(song);
if (song->tag->time > 0)
stats->playTime += song->tag->time;
}
return 0;
@@ -168,7 +168,7 @@ int printAllIn(struct client *client, const char *name)
static int
directoryAddSongToPlaylist(struct song *song, G_GNUC_UNUSED void *data)
{
return playlist_append_song(&g_playlist, song, NULL);
return addSongToPlaylist(&g_playlist, song, NULL);
}
struct add_data {
@@ -199,28 +199,6 @@ int addAllInToStoredPlaylist(const char *name, const char *utf8file)
return db_walk(name, directoryAddSongToStoredPlaylist, NULL, &data);
}
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)
{
@@ -259,7 +237,7 @@ visitTag(struct client *client, struct strset *set,
bool found = false;
if (tagType == LOCATE_TAG_FILE_TYPE) {
song_print_uri(client, song);
song_print_url(client, song);
return;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -39,10 +39,6 @@ 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);
int
searchStatsForSongsIn(struct client *client, const char *name,
const struct locate_item_list *criteria);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -21,11 +21,7 @@
* Common data structures and functions used by FLAC and OggFLAC
*/
#include "config.h"
#include "_flac_common.h"
#include "flac_metadata.h"
#include "flac_pcm.h"
#include "audio_check.h"
#include <glib.h>
@@ -35,104 +31,186 @@ void
flac_data_init(struct flac_data *data, struct decoder * decoder,
struct input_stream *input_stream)
{
pcm_buffer_init(&data->buffer);
data->unsupported = false;
data->initialized = false;
data->total_frames = 0;
data->first_frame = 0;
data->next_frame = 0;
data->time = 0;
data->position = 0;
data->bit_rate = 0;
data->decoder = decoder;
data->input_stream = input_stream;
data->replay_gain_info = NULL;
data->tag = NULL;
}
void
flac_data_deinit(struct flac_data *data)
static void
flac_find_float_comment(const FLAC__StreamMetadata *block,
const char *cmnt, float *fl, bool *found_r)
{
pcm_buffer_deinit(&data->buffer);
int offset;
size_t pos;
int len;
unsigned char tmp, *p;
if (data->tag != NULL)
tag_free(data->tag);
}
offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0,
cmnt);
if (offset < 0)
return;
static enum sample_format
flac_sample_format(unsigned bits_per_sample)
{
switch (bits_per_sample) {
case 8:
return SAMPLE_FORMAT_S8;
pos = strlen(cmnt) + 1; /* 1 is for '=' */
len = block->data.vorbis_comment.comments[offset].length - pos;
if (len <= 0)
return;
case 16:
return SAMPLE_FORMAT_S16;
p = &block->data.vorbis_comment.comments[offset].entry[pos];
tmp = p[len];
p[len] = '\0';
*fl = (float)atof((char *)p);
p[len] = tmp;
case 24:
return SAMPLE_FORMAT_S24_P32;
case 32:
return SAMPLE_FORMAT_S32;
default:
return SAMPLE_FORMAT_UNDEFINED;
}
*found_r = true;
}
static void
flac_got_stream_info(struct flac_data *data,
const FLAC__StreamMetadata_StreamInfo *stream_info)
flac_parse_replay_gain(const FLAC__StreamMetadata *block,
struct flac_data *data)
{
if (data->initialized || data->unsupported)
return;
bool found = false;
GError *error = NULL;
if (!audio_format_init_checked(&data->audio_format,
stream_info->sample_rate,
flac_sample_format(stream_info->bits_per_sample),
stream_info->channels, &error)) {
g_warning("%s", error->message);
g_error_free(error);
data->unsupported = true;
return;
if (data->replay_gain_info)
replay_gain_info_free(data->replay_gain_info);
data->replay_gain_info = replay_gain_info_new();
flac_find_float_comment(block, "replaygain_album_gain",
&data->replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain,
&found);
flac_find_float_comment(block, "replaygain_album_peak",
&data->replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak,
&found);
flac_find_float_comment(block, "replaygain_track_gain",
&data->replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain,
&found);
flac_find_float_comment(block, "replaygain_track_peak",
&data->replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak,
&found);
if (!found) {
replay_gain_info_free(data->replay_gain_info);
data->replay_gain_info = NULL;
}
}
/**
* Checks if the specified name matches the entry's name, and if yes,
* returns the comment value (not null-temrinated).
*/
static const char *
flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
const char *name, const char *char_tnum, size_t *length_r)
{
size_t name_length = strlen(name);
size_t char_tnum_length = 0;
const char *comment = (const char*)entry->entry;
if (entry->length <= name_length ||
g_ascii_strncasecmp(comment, name, name_length) != 0)
return NULL;
if (char_tnum != NULL) {
char_tnum_length = strlen(char_tnum);
if (entry->length > name_length + char_tnum_length + 2 &&
comment[name_length] == '[' &&
g_ascii_strncasecmp(comment + name_length + 1,
char_tnum, char_tnum_length) == 0 &&
comment[name_length + char_tnum_length + 1] == ']')
name_length = name_length + char_tnum_length + 2;
else if (entry->length > name_length + char_tnum_length &&
g_ascii_strncasecmp(comment + name_length,
char_tnum, char_tnum_length) == 0)
name_length = name_length + char_tnum_length;
}
data->frame_size = audio_format_frame_size(&data->audio_format);
if (comment[name_length] == '=') {
*length_r = entry->length - name_length - 1;
return comment + name_length + 1;
}
if (data->total_frames == 0)
data->total_frames = stream_info->total_samples;
return NULL;
}
data->initialized = true;
/**
* Check if the comment's name equals the passed name, and if so, copy
* the comment value into the tag.
*/
static bool
flac_copy_comment(struct tag *tag,
const FLAC__StreamMetadata_VorbisComment_Entry *entry,
const char *name, enum tag_type tag_type,
const char *char_tnum)
{
const char *value;
size_t value_length;
value = flac_comment_value(entry, name, char_tnum, &value_length);
if (value != NULL) {
tag_add_item_n(tag, tag_type, value, value_length);
return true;
}
return false;
}
/* tracknumber is used in VCs, MPD uses "track" ..., all the other
* tag names match */
static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber";
static const char *VORBIS_COMMENT_DISC_KEY = "discnumber";
static void
flac_parse_comment(struct tag *tag, const char *char_tnum,
const FLAC__StreamMetadata_VorbisComment_Entry *entry)
{
assert(tag != NULL);
if (flac_copy_comment(tag, entry, VORBIS_COMMENT_TRACK_KEY,
TAG_ITEM_TRACK, char_tnum) ||
flac_copy_comment(tag, entry, VORBIS_COMMENT_DISC_KEY,
TAG_ITEM_DISC, char_tnum) ||
flac_copy_comment(tag, entry, "album artist",
TAG_ITEM_ALBUM_ARTIST, char_tnum))
return;
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
if (flac_copy_comment(tag, entry,
tag_item_names[i], i, char_tnum))
return;
}
void
flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum,
const FLAC__StreamMetadata *block)
{
FLAC__StreamMetadata_VorbisComment_Entry *comments =
block->data.vorbis_comment.comments;
for (unsigned i = block->data.vorbis_comment.num_comments; i > 0; --i)
flac_parse_comment(tag, char_tnum, comments++);
}
void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
struct flac_data *data)
{
if (data->unsupported)
return;
struct replay_gain_info rgi;
char *mixramp_start;
char *mixramp_end;
float replay_gain_db = 0;
const FLAC__StreamMetadata_StreamInfo *si = &(block->data.stream_info);
switch (block->type) {
case FLAC__METADATA_TYPE_STREAMINFO:
flac_got_stream_info(data, &block->data.stream_info);
data->audio_format.bits = (int8_t)si->bits_per_sample;
data->audio_format.sample_rate = si->sample_rate;
data->audio_format.channels = (int8_t)si->channels;
data->total_time = ((float)si->total_samples) / (si->sample_rate);
break;
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
if (flac_parse_replay_gain(&rgi, block))
replay_gain_db = decoder_replay_gain(data->decoder, &rgi);
if (flac_parse_mixramp(&mixramp_start, &mixramp_end, block)) {
g_debug("setting mixramp_tags");
decoder_mixramp(data->decoder, replay_gain_db,
mixramp_start, mixramp_end);
}
flac_parse_replay_gain(block, data);
if (data->tag != NULL)
flac_vorbis_comments_to_tag(data->tag, NULL,
&block->data.vorbis_comment);
flac_vorbis_comments_to_tag(data->tag, NULL, block);
default:
break;
@@ -161,82 +239,187 @@ void flac_error_common_cb(const char *plugin,
}
}
/**
* This function attempts to call decoder_initialized() in case there
* was no STREAMINFO block. This is allowed for nonseekable streams,
* where the server sends us only a part of the file, without
* providing the STREAMINFO block from the beginning of the file
* (e.g. when seeking with SqueezeBox Server).
*/
static bool
flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header)
static void flac_convert_stereo16(int16_t *dest,
const FLAC__int32 * const buf[],
unsigned int position, unsigned int end)
{
if (data->unsupported)
return false;
GError *error = NULL;
if (!audio_format_init_checked(&data->audio_format,
header->sample_rate,
flac_sample_format(header->bits_per_sample),
header->channels, &error)) {
g_warning("%s", error->message);
g_error_free(error);
data->unsupported = true;
return false;
for (; position < end; ++position) {
*dest++ = buf[0][position];
*dest++ = buf[1][position];
}
}
data->frame_size = audio_format_frame_size(&data->audio_format);
static void
flac_convert_16(int16_t *dest,
unsigned int num_channels,
const FLAC__int32 * const buf[],
unsigned int position, unsigned int end)
{
unsigned int c_chan;
decoder_initialized(data->decoder, &data->audio_format,
data->input_stream->seekable,
(float)data->total_frames /
(float)data->audio_format.sample_rate);
for (; position < end; ++position)
for (c_chan = 0; c_chan < num_channels; c_chan++)
*dest++ = buf[c_chan][position];
}
data->initialized = true;
/**
* Note: this function also handles 24 bit files!
*/
static void
flac_convert_32(int32_t *dest,
unsigned int num_channels,
const FLAC__int32 * const buf[],
unsigned int position, unsigned int end)
{
unsigned int c_chan;
return true;
for (; position < end; ++position)
for (c_chan = 0; c_chan < num_channels; c_chan++)
*dest++ = buf[c_chan][position];
}
static void
flac_convert_8(int8_t *dest,
unsigned int num_channels,
const FLAC__int32 * const buf[],
unsigned int position, unsigned int end)
{
unsigned int c_chan;
for (; position < end; ++position)
for (c_chan = 0; c_chan < num_channels; c_chan++)
*dest++ = buf[c_chan][position];
}
static void flac_convert(unsigned char *dest,
unsigned int num_channels,
unsigned int bytes_per_sample,
const FLAC__int32 * const buf[],
unsigned int position, unsigned int end)
{
switch (bytes_per_sample) {
case 2:
if (num_channels == 2)
flac_convert_stereo16((int16_t*)dest, buf,
position, end);
else
flac_convert_16((int16_t*)dest, num_channels, buf,
position, end);
break;
case 4:
flac_convert_32((int32_t*)dest, num_channels, buf,
position, end);
break;
case 1:
flac_convert_8((int8_t*)dest, num_channels, buf,
position, end);
break;
}
}
FLAC__StreamDecoderWriteStatus
flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
const FLAC__int32 *const buf[],
FLAC__uint64 nbytes)
const FLAC__int32 *const buf[])
{
unsigned int c_samp;
const unsigned int num_channels = frame->header.channels;
const unsigned int bytes_per_sample =
audio_format_sample_size(&data->audio_format);
const unsigned int bytes_per_channel =
bytes_per_sample * frame->header.channels;
const unsigned int max_samples = FLAC_CHUNK_SIZE / bytes_per_channel;
unsigned int num_samples;
enum decoder_command cmd;
void *buffer;
unsigned bit_rate;
if (!data->initialized && !flac_got_first_frame(data, &frame->header))
if (bytes_per_sample != 1 && bytes_per_sample != 2 &&
bytes_per_sample != 4)
/* exotic unsupported bit rate */
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
size_t buffer_size = frame->header.blocksize * data->frame_size;
buffer = pcm_buffer_get(&data->buffer, buffer_size);
for (c_samp = 0; c_samp < frame->header.blocksize;
c_samp += num_samples) {
num_samples = frame->header.blocksize - c_samp;
if (num_samples > max_samples)
num_samples = max_samples;
flac_convert(buffer, frame->header.channels,
data->audio_format.format, buf,
0, frame->header.blocksize);
flac_convert(data->chunk,
num_channels, bytes_per_sample, buf,
c_samp, c_samp + num_samples);
if (nbytes > 0)
bit_rate = nbytes * 8 * frame->header.sample_rate /
(1000 * frame->header.blocksize);
else
bit_rate = 0;
cmd = decoder_data(data->decoder, data->input_stream,
data->chunk,
num_samples * bytes_per_channel,
data->time, data->bit_rate,
data->replay_gain_info);
switch (cmd) {
case DECODE_COMMAND_NONE:
case DECODE_COMMAND_START:
break;
cmd = decoder_data(data->decoder, data->input_stream,
buffer, buffer_size,
bit_rate);
data->next_frame += frame->header.blocksize;
switch (cmd) {
case DECODE_COMMAND_NONE:
case DECODE_COMMAND_START:
break;
case DECODE_COMMAND_STOP:
return
FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
case DECODE_COMMAND_STOP:
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
case DECODE_COMMAND_SEEK:
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
case DECODE_COMMAND_SEEK:
return
FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
}
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
char*
flac_cue_track( const char* pathname,
const unsigned int tnum)
{
FLAC__bool success;
FLAC__StreamMetadata* cs;
success = FLAC__metadata_get_cuesheet(pathname, &cs);
if (!success)
return NULL;
assert(cs != NULL);
if (cs->data.cue_sheet.num_tracks <= 1)
{
FLAC__metadata_object_delete(cs);
return NULL;
}
if (tnum > 0 && tnum < cs->data.cue_sheet.num_tracks)
{
char* track = g_strdup_printf("track_%03u.flac", tnum);
FLAC__metadata_object_delete(cs);
return track;
}
else
{
FLAC__metadata_object_delete(cs);
return NULL;
}
}
unsigned int
flac_vtrack_tnum(const char* fname)
{
/* find last occurrence of '_' in fname
* which is hopefully something like track_xxx.flac
* another/better way would be to use tag struct
*/
char* ptr = strrchr(fname, '_');
if (ptr == NULL)
return 0;
// copy ascii tracknumber to int
return (unsigned int)strtol(++ptr, NULL, 10);
}
#endif /* FLAC_API_VERSION_CURRENT >= 7 */

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -24,62 +24,136 @@
#ifndef MPD_FLAC_COMMON_H
#define MPD_FLAC_COMMON_H
#include "decoder_api.h"
#include "pcm_buffer.h"
#include "../decoder_api.h"
#include "config.h"
#include <glib.h>
#include <FLAC/stream_decoder.h>
#include <FLAC/metadata.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "flac"
#include <FLAC/export.h>
#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
# include <FLAC/seekable_stream_decoder.h>
# define flac_decoder FLAC__SeekableStreamDecoder
# define flac_new() FLAC__seekable_stream_decoder_new()
# define flac_ogg_init(a,b,c,d,e,f,g,h,i,j) (0)
# define flac_get_decode_position(x,y) \
FLAC__seekable_stream_decoder_get_decode_position(x,y)
# define flac_get_state(x) FLAC__seekable_stream_decoder_get_state(x)
# define flac_process_single(x) FLAC__seekable_stream_decoder_process_single(x)
# define flac_process_metadata(x) \
FLAC__seekable_stream_decoder_process_until_end_of_metadata(x)
# define flac_seek_absolute(x,y) \
FLAC__seekable_stream_decoder_seek_absolute(x,y)
# define flac_finish(x) FLAC__seekable_stream_decoder_finish(x)
# define flac_delete(x) FLAC__seekable_stream_decoder_delete(x)
# define flac_decoder_eof FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM
typedef unsigned flac_read_status_size_t;
# define flac_read_status FLAC__SeekableStreamDecoderReadStatus
# define flac_read_status_continue \
FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK
# define flac_read_status_eof FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK
# define flac_read_status_abort \
FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR
# define flac_seek_status FLAC__SeekableStreamDecoderSeekStatus
# define flac_seek_status_ok FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK
# define flac_seek_status_error FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR
# define flac_tell_status FLAC__SeekableStreamDecoderTellStatus
# define flac_tell_status_ok FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK
# define flac_tell_status_error \
FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR
# define flac_tell_status_unsupported \
FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR
# define flac_length_status FLAC__SeekableStreamDecoderLengthStatus
# define flac_length_status_ok FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK
# define flac_length_status_error \
FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR
# define flac_length_status_unsupported \
FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR
# ifdef HAVE_OGGFLAC
# include <OggFLAC/seekable_stream_decoder.h>
# endif
#else /* FLAC_API_VERSION_CURRENT > 7 */
/*
* OggFLAC support is handled by our flac_plugin already, and
* thus we *can* always have it if libFLAC was compiled with it
*/
# include "_ogg_common.h"
# include <FLAC/stream_decoder.h>
# define flac_decoder FLAC__StreamDecoder
# define flac_new() FLAC__stream_decoder_new()
# define flac_init(a,b,c,d,e,f,g,h,i,j) \
(FLAC__stream_decoder_init_stream(a,b,c,d,e,f,g,h,i,j) \
== FLAC__STREAM_DECODER_INIT_STATUS_OK)
# define flac_ogg_init(a,b,c,d,e,f,g,h,i,j) \
(FLAC__stream_decoder_init_ogg_stream(a,b,c,d,e,f,g,h,i,j) \
== FLAC__STREAM_DECODER_INIT_STATUS_OK)
# define flac_get_decode_position(x,y) \
FLAC__stream_decoder_get_decode_position(x,y)
# define flac_get_state(x) FLAC__stream_decoder_get_state(x)
# define flac_process_single(x) FLAC__stream_decoder_process_single(x)
# define flac_process_metadata(x) \
FLAC__stream_decoder_process_until_end_of_metadata(x)
# define flac_seek_absolute(x,y) FLAC__stream_decoder_seek_absolute(x,y)
# define flac_finish(x) FLAC__stream_decoder_finish(x)
# define flac_delete(x) FLAC__stream_decoder_delete(x)
# define flac_decoder_eof FLAC__STREAM_DECODER_END_OF_STREAM
typedef size_t flac_read_status_size_t;
# define flac_read_status FLAC__StreamDecoderReadStatus
# define flac_read_status_continue \
FLAC__STREAM_DECODER_READ_STATUS_CONTINUE
# define flac_read_status_eof FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM
# define flac_read_status_abort FLAC__STREAM_DECODER_READ_STATUS_ABORT
# define flac_seek_status FLAC__StreamDecoderSeekStatus
# define flac_seek_status_ok FLAC__STREAM_DECODER_SEEK_STATUS_OK
# define flac_seek_status_error FLAC__STREAM_DECODER_SEEK_STATUS_ERROR
# define flac_seek_status_unsupported \
FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED
# define flac_tell_status FLAC__StreamDecoderTellStatus
# define flac_tell_status_ok FLAC__STREAM_DECODER_TELL_STATUS_OK
# define flac_tell_status_error FLAC__STREAM_DECODER_TELL_STATUS_ERROR
# define flac_tell_status_unsupported \
FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED
# define flac_length_status FLAC__StreamDecoderLengthStatus
# define flac_length_status_ok FLAC__STREAM_DECODER_LENGTH_STATUS_OK
# define flac_length_status_error FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR
# define flac_length_status_unsupported \
FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
#include <FLAC/metadata.h>
#define FLAC_CHUNK_SIZE 4080
struct flac_data {
struct pcm_buffer buffer;
/**
* The size of one frame in the output buffer.
*/
unsigned frame_size;
/**
* Has decoder_initialized() been called yet?
*/
bool initialized;
/**
* Does the FLAC file contain an unsupported audio format?
*/
bool unsupported;
/**
* The validated audio format of the FLAC file. This
* attribute is defined if "initialized" is true.
*/
unsigned char chunk[FLAC_CHUNK_SIZE];
float time;
unsigned int bit_rate;
struct audio_format audio_format;
/**
* The total number of frames in this song. The decoder
* plugin may initialize this attribute to override the value
* provided by libFLAC (e.g. for sub songs from a CUE sheet).
*/
FLAC__uint64 total_frames;
/**
* The number of the first frame in this song. This is only
* non-zero if playing sub songs from a CUE sheet.
*/
FLAC__uint64 first_frame;
/**
* The number of the next frame which is going to be decoded.
*/
FLAC__uint64 next_frame;
float total_time;
FLAC__uint64 position;
struct decoder *decoder;
struct input_stream *input_stream;
struct replay_gain_info *replay_gain_info;
struct tag *tag;
};
@@ -88,9 +162,6 @@ void
flac_data_init(struct flac_data *data, struct decoder * decoder,
struct input_stream *input_stream);
void
flac_data_deinit(struct flac_data *data);
void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
struct flac_data *data);
@@ -98,9 +169,23 @@ void flac_error_common_cb(const char *plugin,
FLAC__StreamDecoderErrorStatus status,
struct flac_data *data);
void
flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum,
const FLAC__StreamMetadata *block);
FLAC__StreamDecoderWriteStatus
flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
const FLAC__int32 *const buf[],
FLAC__uint64 nbytes);
const FLAC__int32 *const buf[]);
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
char*
flac_cue_track( const char* pathname,
const unsigned int tnum);
unsigned int
flac_vtrack_tnum( const char*);
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
#endif /* _FLAC_COMMON_H */

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -21,8 +21,8 @@
* Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC)
*/
#include "config.h"
#include "_ogg_common.h"
#include "../utils.h"
ogg_stream_type ogg_stream_type_detect(struct input_stream *inStream)
{

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -24,7 +24,7 @@
#ifndef MPD_OGG_COMMON_H
#define MPD_OGG_COMMON_H
#include "decoder_api.h"
#include "../decoder_api.h"
typedef enum _ogg_stream_type { VORBIS, FLAC } ogg_stream_type;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -17,9 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "decoder_api.h"
#include "audio_check.h"
#include "../decoder_api.h"
#include <audiofile.h>
#include <af_vfs.h>
@@ -47,20 +45,10 @@ static int audiofile_get_duration(const char *file)
}
static ssize_t
audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length)
audiofile_file_read(AFvirtualfile *vfile, void *data, size_t nbytes)
{
struct input_stream *is = (struct input_stream *) vfile->closure;
GError *error = NULL;
size_t nbytes;
nbytes = input_stream_read(is, data, length, &error);
if (nbytes == 0 && error != NULL) {
g_warning("%s", error->message);
g_error_free(error);
return -1;
}
return nbytes;
return input_stream_read(is, data, nbytes);
}
static long
@@ -90,7 +78,7 @@ audiofile_file_seek(AFvirtualfile *vfile, long offset, int is_relative)
{
struct input_stream *is = (struct input_stream *) vfile->closure;
int whence = (is_relative ? SEEK_CUR : SEEK_SET);
if (input_stream_seek(is, offset, whence, NULL)) {
if (input_stream_seek(is, offset, whence)) {
return is->offset;
} else {
return -1;
@@ -111,56 +99,17 @@ setup_virtual_fops(struct input_stream *stream)
return vf;
}
static enum sample_format
audiofile_bits_to_sample_format(int bits)
{
switch (bits) {
case 8:
return SAMPLE_FORMAT_S8;
case 16:
return SAMPLE_FORMAT_S16;
case 24:
return SAMPLE_FORMAT_S24_P32;
case 32:
return SAMPLE_FORMAT_S32;
}
return SAMPLE_FORMAT_UNDEFINED;
}
static enum sample_format
audiofile_setup_sample_format(AFfilehandle af_fp)
{
int fs, bits;
afGetSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
if (!audio_valid_sample_format(audiofile_bits_to_sample_format(bits))) {
g_debug("input file has %d bit samples, converting to 16",
bits);
bits = 16;
}
afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK,
AF_SAMPFMT_TWOSCOMP, bits);
afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
return audiofile_bits_to_sample_format(bits);
}
static void
audiofile_stream_decode(struct decoder *decoder, struct input_stream *is)
{
GError *error = NULL;
AFvirtualfile *vf;
int fs, frame_count;
AFfilehandle af_fp;
int bits;
struct audio_format audio_format;
float total_time;
uint16_t bit_rate;
int ret;
int ret, current = 0;
char chunk[CHUNK_SIZE];
enum decoder_command cmd;
@@ -177,13 +126,26 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is)
return;
}
if (!audio_format_init_checked(&audio_format,
afGetRate(af_fp, AF_DEFAULT_TRACK),
audiofile_setup_sample_format(af_fp),
afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK),
&error)) {
g_warning("%s", error->message);
g_error_free(error);
afGetSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
if (!audio_valid_sample_format(bits)) {
g_debug("input file has %d bit samples, converting to 16",
bits);
bits = 16;
}
afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK,
AF_SAMPFMT_TWOSCOMP, bits);
afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
audio_format.bits = (uint8_t)bits;
audio_format.sample_rate =
(unsigned int)afGetRate(af_fp, AF_DEFAULT_TRACK);
audio_format.channels =
(uint8_t)afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK);
if (!audio_format_valid(&audio_format)) {
g_warning("Invalid audio format: %u:%u:%u\n",
audio_format.sample_rate, audio_format.bits,
audio_format.channels);
afCloseFile(af_fp);
return;
}
@@ -204,14 +166,17 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is)
if (ret <= 0)
break;
current += ret;
cmd = decoder_data(decoder, NULL,
chunk, ret * fs,
bit_rate);
(float)current /
(float)audio_format.sample_rate,
bit_rate, NULL);
if (cmd == DECODE_COMMAND_SEEK) {
AFframecount frame = decoder_seek_where(decoder) *
current = decoder_seek_where(decoder) *
audio_format.sample_rate;
afSeekFrame(af_fp, AF_DEFAULT_TRACK, frame);
afSeekFrame(af_fp, AF_DEFAULT_TRACK, current);
decoder_command_finished(decoder);
cmd = DECODE_COMMAND_NONE;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -17,10 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "decoder_api.h"
#include "../decoder_api.h"
#include "decoder_buffer.h"
#include "audio_check.h"
#include "config.h"
#define AAC_MAX_CHANNELS 6
@@ -37,15 +36,6 @@ static const unsigned adts_sample_rates[] =
16000, 12000, 11025, 8000, 7350, 0, 0, 0
};
/**
* The GLib quark used for errors reported by this plugin.
*/
static inline GQuark
faad_decoder_quark(void)
{
return g_quark_from_static_string("faad");
}
/**
* Check whether the buffer head is an AAC frame, and return the frame
* length. Returns 0 if it is not a frame.
@@ -205,7 +195,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_seek(is, tagsize, SEEK_SET);
data = decoder_buffer_read(buffer, &length);
if (data != NULL)
@@ -242,7 +232,7 @@ faad_song_duration(struct decoder_buffer *buffer, struct input_stream *is)
*/
static bool
faad_decoder_init(faacDecHandle decoder, struct decoder_buffer *buffer,
struct audio_format *audio_format, GError **error_r)
struct audio_format *audio_format)
{
union {
/* deconst hack for libfaad */
@@ -257,33 +247,32 @@ faad_decoder_init(faacDecHandle decoder, struct decoder_buffer *buffer,
/* neaacdec.h declares all arguments as "unsigned long", but
internally expects uint32_t pointers. To avoid gcc
warnings, use this workaround. */
unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate;
unsigned long *sample_rate_r = (unsigned long *)(void *)&sample_rate;
#else
uint32_t *sample_rate_p = &sample_rate;
uint32_t *sample_rate_r = &sample_rate;
#endif
u.in = decoder_buffer_read(buffer, &length);
if (u.in == NULL) {
g_set_error(error_r, faad_decoder_quark(), 0,
"Empty file");
if (u.in == NULL)
return false;
}
nbytes = faacDecInit(decoder, u.out,
#ifdef HAVE_FAAD_BUFLEN_FUNCS
length,
#endif
sample_rate_p, &channels);
if (nbytes < 0) {
g_set_error(error_r, faad_decoder_quark(), 0,
"Not an AAC stream");
sample_rate_r, &channels);
if (nbytes < 0)
return false;
}
decoder_buffer_consume(buffer, nbytes);
return audio_format_init_checked(audio_format, sample_rate,
SAMPLE_FORMAT_S16, channels, error_r);
*audio_format = (struct audio_format){
.bits = 16,
.channels = channels,
.sample_rate = sample_rate,
};
return true;
}
/**
@@ -322,16 +311,20 @@ faad_decoder_decode(faacDecHandle decoder, struct decoder_buffer *buffer,
* file is invalid.
*/
static float
faad_get_file_time_float(struct input_stream *is)
faad_get_file_time_float(const char *file)
{
struct decoder_buffer *buffer;
float length;
faacDecHandle decoder;
faacDecConfigurationPtr config;
struct input_stream is;
buffer = decoder_buffer_new(NULL, is,
if (!input_stream_open(&is, file))
return -1;
buffer = decoder_buffer_new(NULL, &is,
FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
length = faad_song_duration(buffer, is);
length = faad_song_duration(buffer, &is);
if (length < 0) {
bool ret;
@@ -345,14 +338,15 @@ faad_get_file_time_float(struct input_stream *is)
decoder_buffer_fill(buffer);
ret = faad_decoder_init(decoder, buffer, &audio_format, NULL);
if (ret)
ret = faad_decoder_init(decoder, buffer, &audio_format);
if (ret && audio_format_valid(&audio_format))
length = 0;
faacDecClose(decoder);
}
decoder_buffer_free(buffer);
input_stream_close(&is);
return length;
}
@@ -363,12 +357,12 @@ faad_get_file_time_float(struct input_stream *is)
* file is invalid.
*/
static int
faad_get_file_time(struct input_stream *is)
faad_get_file_time(const char *file)
{
int file_time = -1;
float length;
if ((length = faad_get_file_time_float(is)) >= 0)
if ((length = faad_get_file_time_float(file)) >= 0)
file_time = length + 0.5;
return file_time;
@@ -377,7 +371,7 @@ faad_get_file_time(struct input_stream *is)
static void
faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
{
GError *error = NULL;
float file_time;
float total_time = 0;
faacDecHandle decoder;
struct audio_format audio_format;
@@ -414,10 +408,15 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
/* initialize it */
ret = faad_decoder_init(decoder, buffer, &audio_format, &error);
ret = faad_decoder_init(decoder, buffer, &audio_format);
if (!ret) {
g_warning("%s", error->message);
g_error_free(error);
g_warning("Error not a AAC stream.\n");
faacDecClose(decoder);
return;
}
if (!audio_format_valid(&audio_format)) {
g_warning("invalid audio format\n");
faacDecClose(decoder);
return;
}
@@ -428,6 +427,8 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
/* the decoder loop */
file_time = 0.0;
do {
size_t frame_size;
const void *decoded;
@@ -473,13 +474,16 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
bit_rate = frame_info.bytesconsumed * 8.0 *
frame_info.channels * audio_format.sample_rate /
frame_info.samples / 1000 + 0.5;
file_time +=
(float)(frame_info.samples) / frame_info.channels /
audio_format.sample_rate;
}
/* send PCM samples to MPD */
cmd = decoder_data(mpd_decoder, is, decoded,
(size_t)frame_info.samples * 2,
bit_rate);
(size_t)frame_info.samples * 2, file_time,
bit_rate, NULL);
} while (cmd != DECODE_COMMAND_STOP);
/* cleanup */
@@ -488,13 +492,15 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
}
static struct tag *
faad_stream_tag(struct input_stream *is)
faad_tag_dup(const char *file)
{
int file_time = faad_get_file_time(is);
int file_time = faad_get_file_time(file);
struct tag *tag;
if (file_time < 0)
if (file_time < 0) {
g_debug("Failed to get total song time from: %s", file);
return NULL;
}
tag = tag_new();
tag->time = file_time;
@@ -509,7 +515,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,
.tag_dup = faad_tag_dup,
.suffixes = faad_suffixes,
.mime_types = faad_mime_types,
};

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -17,9 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "../decoder_api.h"
#include "config.h"
#include "decoder_api.h"
#include "audio_check.h"
#include <glib.h>
@@ -40,46 +39,19 @@
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
#include <libavutil/log.h>
#endif
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "ffmpeg"
#ifndef OLD_FFMPEG_INCLUDES
static GLogLevelFlags
level_ffmpeg_to_glib(int level)
{
if (level <= AV_LOG_FATAL)
return G_LOG_LEVEL_CRITICAL;
if (level <= AV_LOG_ERROR)
return G_LOG_LEVEL_WARNING;
if (level <= AV_LOG_INFO)
return G_LOG_LEVEL_MESSAGE;
return G_LOG_LEVEL_DEBUG;
}
static void
mpd_ffmpeg_log_callback(G_GNUC_UNUSED void *ptr, int level,
const char *fmt, va_list vl)
{
const AVClass * cls = NULL;
if (ptr != NULL)
cls = *(const AVClass *const*)ptr;
if (cls != NULL) {
char *domain = g_strconcat(G_LOG_DOMAIN, "/", cls->item_name(ptr), NULL);
g_logv(domain, level_ffmpeg_to_glib(level), fmt, vl);
g_free(domain);
}
}
#endif /* !OLD_FFMPEG_INCLUDES */
struct ffmpeg_context {
int audio_stream;
AVFormatContext *format_context;
AVCodecContext *codec_context;
struct decoder *decoder;
struct input_stream *input;
struct tag *tag;
};
struct mpd_ffmpeg_stream {
struct decoder *decoder;
@@ -107,7 +79,7 @@ mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence)
if (whence == AVSEEK_SIZE)
return stream->input->size;
ret = input_stream_seek(stream->input, pos, whence, NULL);
ret = input_stream_seek(stream->input, pos, whence);
if (!ret)
return -1;
@@ -143,10 +115,6 @@ 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;
}
@@ -162,6 +130,125 @@ ffmpeg_find_audio_stream(const AVFormatContext *format_context)
return -1;
}
static AVInputFormat *
ffmpeg_probe(struct decoder *decoder, struct input_stream *is,
const char *uri)
{
enum {
BUFFER_SIZE = 16384,
PADDING = 16,
};
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)) {
g_free(buffer);
return NULL;
}
/* some ffmpeg parsers (e.g. ac3_parser.c) read a few bytes
beyond the declared buffer limit, which makes valgrind
angry; this workaround removes some padding from the buffer
size */
nbytes -= PADDING;
AVProbeData avpd = {
.buf = buffer,
.buf_size = nbytes,
.filename = uri,
};
AVInputFormat *format = av_probe_input_format(&avpd, true);
g_free(buffer);
return format;
}
static bool
ffmpeg_helper(const char *uri,
struct decoder *decoder, struct input_stream *input,
bool (*callback)(struct ffmpeg_context *ctx),
struct ffmpeg_context *ctx)
{
AVInputFormat *input_format = ffmpeg_probe(decoder, input, uri);
if (input_format == NULL)
return false;
g_debug("detected input format '%s' (%s)",
input_format->name, input_format->long_name);
struct mpd_ffmpeg_stream *stream =
mpd_ffmpeg_stream_open(decoder, input);
if (stream == NULL) {
g_warning("Failed to open stream");
return false;
}
AVFormatContext *format_context;
AVCodecContext *codec_context;
AVCodec *codec;
int audio_stream;
bool ret;
//ffmpeg works with ours "fileops" helper
if (av_open_input_stream(&format_context, stream->io, uri,
input_format, NULL) != 0) {
g_warning("Open failed\n");
mpd_ffmpeg_stream_close(stream);
return false;
}
if (av_find_stream_info(format_context)<0) {
g_warning("Couldn't find stream info\n");
av_close_input_stream(format_context);
mpd_ffmpeg_stream_close(stream);
return false;
}
audio_stream = ffmpeg_find_audio_stream(format_context);
if (audio_stream == -1) {
g_warning("No audio stream inside\n");
av_close_input_stream(format_context);
mpd_ffmpeg_stream_close(stream);
return false;
}
codec_context = format_context->streams[audio_stream]->codec;
if (codec_context->codec_name[0] != 0)
g_debug("codec '%s'", codec_context->codec_name);
codec = avcodec_find_decoder(codec_context->codec_id);
if (!codec) {
g_warning("Unsupported audio codec\n");
av_close_input_stream(format_context);
mpd_ffmpeg_stream_close(stream);
return false;
}
if (avcodec_open(codec_context, codec)<0) {
g_warning("Could not open codec\n");
av_close_input_stream(format_context);
mpd_ffmpeg_stream_close(stream);
return false;
}
if (callback) {
ctx->audio_stream = audio_stream;
ctx->format_context = format_context;
ctx->codec_context = codec_context;
ret = callback(ctx);
} else
ret = true;
avcodec_close(codec_context);
av_close_input_stream(format_context);
mpd_ffmpeg_stream_close(stream);
return ret;
}
/**
* On some platforms, libavcodec wants the output buffer aligned to 16
* bytes (because it uses SSE/Altivec internally). This function
@@ -184,6 +271,7 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
const AVRational *time_base)
{
enum decoder_command cmd = DECODE_COMMAND_NONE;
int position;
uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16];
int16_t *aligned_buffer;
size_t buffer_size;
@@ -191,11 +279,6 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
uint8_t *packet_data;
int packet_size;
if (packet->pts != (int64_t)AV_NOPTS_VALUE)
decoder_timestamp(decoder,
av_rescale_q(packet->pts, *time_base,
(AVRational){1, 1}));
packet_data = packet->data;
packet_size = packet->size;
@@ -220,163 +303,65 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
if (audio_size <= 0)
continue;
position = packet->pts != (int64_t)AV_NOPTS_VALUE
? av_rescale_q(packet->pts, *time_base,
(AVRational){1, 1})
: 0;
cmd = decoder_data(decoder, is,
aligned_buffer, audio_size,
codec_context->bit_rate / 1000);
position,
codec_context->bit_rate / 1000, NULL);
}
return cmd;
}
static enum sample_format
ffmpeg_sample_format(G_GNUC_UNUSED const AVCodecContext *codec_context)
static bool
ffmpeg_decode_internal(struct ffmpeg_context *ctx)
{
struct decoder *decoder = ctx->decoder;
AVCodecContext *codec_context = ctx->codec_context;
AVFormatContext *format_context = ctx->format_context;
AVPacket packet;
struct audio_format audio_format;
enum decoder_command cmd;
int total_time;
total_time = 0;
#if LIBAVCODEC_VERSION_INT >= ((51<<16)+(41<<8)+0)
int bits = (uint8_t) av_get_bits_per_sample_format(codec_context->sample_fmt);
/* XXX implement & test other sample formats */
switch (bits) {
case 16:
return SAMPLE_FORMAT_S16;
}
return SAMPLE_FORMAT_UNDEFINED;
audio_format.bits = (uint8_t) av_get_bits_per_sample_format(codec_context->sample_fmt);
#else
/* XXX fixme 16-bit for older ffmpeg (13 Aug 2007) */
return SAMPLE_FORMAT_S16;
audio_format.bits = (uint8_t) 16;
#endif
}
audio_format.sample_rate = (unsigned int)codec_context->sample_rate;
audio_format.channels = codec_context->channels;
static AVInputFormat *
ffmpeg_probe(struct decoder *decoder, struct input_stream *is)
{
enum {
BUFFER_SIZE = 16384,
PADDING = 16,
};
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)) {
g_free(buffer);
return NULL;
if (!audio_format_valid(&audio_format)) {
g_warning("Invalid audio format: %u:%u:%u\n",
audio_format.sample_rate, audio_format.bits,
audio_format.channels);
return false;
}
/* some ffmpeg parsers (e.g. ac3_parser.c) read a few bytes
beyond the declared buffer limit, which makes valgrind
angry; this workaround removes some padding from the buffer
size */
nbytes -= PADDING;
AVProbeData avpd = {
.buf = buffer,
.buf_size = nbytes,
.filename = is->uri,
};
AVInputFormat *format = av_probe_input_format(&avpd, true);
g_free(buffer);
return format;
}
static void
ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
{
AVInputFormat *input_format = ffmpeg_probe(decoder, input);
if (input_format == NULL)
return;
g_debug("detected input format '%s' (%s)",
input_format->name, input_format->long_name);
struct mpd_ffmpeg_stream *stream =
mpd_ffmpeg_stream_open(decoder, input);
if (stream == NULL) {
g_warning("Failed to open stream");
return;
//there is some problem with this on some demux (mp3 at least)
if (format_context->duration != (int64_t)AV_NOPTS_VALUE) {
total_time = format_context->duration / AV_TIME_BASE;
}
AVFormatContext *format_context;
AVCodecContext *codec_context;
AVCodec *codec;
int audio_stream;
//ffmpeg works with ours "fileops" helper
if (av_open_input_stream(&format_context, stream->io, input->uri,
input_format, NULL) != 0) {
g_warning("Open failed\n");
mpd_ffmpeg_stream_close(stream);
return;
}
if (av_find_stream_info(format_context)<0) {
g_warning("Couldn't find stream info\n");
av_close_input_stream(format_context);
mpd_ffmpeg_stream_close(stream);
return;
}
audio_stream = ffmpeg_find_audio_stream(format_context);
if (audio_stream == -1) {
g_warning("No audio stream inside\n");
av_close_input_stream(format_context);
mpd_ffmpeg_stream_close(stream);
return;
}
codec_context = format_context->streams[audio_stream]->codec;
if (codec_context->codec_name[0] != 0)
g_debug("codec '%s'", codec_context->codec_name);
codec = avcodec_find_decoder(codec_context->codec_id);
if (!codec) {
g_warning("Unsupported audio codec\n");
av_close_input_stream(format_context);
mpd_ffmpeg_stream_close(stream);
return;
}
if (avcodec_open(codec_context, codec)<0) {
g_warning("Could not open codec\n");
av_close_input_stream(format_context);
mpd_ffmpeg_stream_close(stream);
return;
}
GError *error = NULL;
struct audio_format audio_format;
if (!audio_format_init_checked(&audio_format,
codec_context->sample_rate,
ffmpeg_sample_format(codec_context),
codec_context->channels, &error)) {
g_warning("%s", error->message);
g_error_free(error);
avcodec_close(codec_context);
av_close_input_stream(format_context);
mpd_ffmpeg_stream_close(stream);
return;
}
int total_time = format_context->duration != (int64_t)AV_NOPTS_VALUE
? format_context->duration / AV_TIME_BASE
: 0;
decoder_initialized(decoder, &audio_format,
input->seekable, total_time);
ctx->input->seekable, total_time);
enum decoder_command cmd;
do {
AVPacket packet;
if (av_read_frame(format_context, &packet) < 0)
/* end of file */
break;
if (packet.stream_index == audio_stream)
cmd = ffmpeg_send_packet(decoder, input,
if (packet.stream_index == ctx->audio_stream)
cmd = ffmpeg_send_packet(decoder, ctx->input,
&packet, codec_context,
&format_context->streams[audio_stream]->time_base);
&format_context->streams[ctx->audio_stream]->time_base);
else
cmd = decoder_get_command(decoder);
@@ -393,121 +378,115 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
}
} while (cmd != DECODE_COMMAND_STOP);
avcodec_close(codec_context);
av_close_input_stream(format_context);
mpd_ffmpeg_stream_close(stream);
return true;
}
static void
ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
{
struct ffmpeg_context ctx;
ctx.input = input;
ctx.decoder = decoder;
char *uri = decoder_get_uri(decoder);
ffmpeg_helper(uri, decoder, input,
ffmpeg_decode_internal, &ctx);
g_free(uri);
}
#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[] = {
{ TAG_TITLE, "title" },
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(50<<8))
{ TAG_ARTIST, "artist" },
{ TAG_DATE, "date" },
#else
{ TAG_ARTIST, "author" },
{ TAG_DATE, "year" },
#endif
{ TAG_ALBUM, "album" },
{ TAG_COMMENT, "comment" },
{ TAG_GENRE, "genre" },
{ TAG_TRACK, "track" },
{ TAG_ARTIST_SORT, "author-sort" },
{ TAG_ALBUM_ARTIST, "album_artist" },
{ TAG_ALBUM_ARTIST_SORT, "album_artist-sort" },
{ TAG_COMPOSER, "composer" },
{ TAG_PERFORMER, "performer" },
{ TAG_DISC, "disc" },
};
static bool
ffmpeg_copy_metadata(struct tag *tag, AVMetadata *m,
const ffmpeg_tag_map tag_map)
enum tag_type type, const char *name)
{
AVMetadataTag *mt = NULL;
while ((mt = av_metadata_get(m, tag_map.name, mt, 0)) != NULL)
tag_add_item(tag, tag_map.type, mt->value);
AVMetadataTag *mt = av_metadata_get(m, name, NULL, 0);
if (mt != NULL)
tag_add_item(tag, type, mt->value);
return mt != NULL;
}
#endif
//no tag reading in ffmpeg, check if playable
static struct tag *
ffmpeg_stream_tag(struct input_stream *is)
static bool ffmpeg_tag_internal(struct ffmpeg_context *ctx)
{
AVInputFormat *input_format = ffmpeg_probe(NULL, is);
if (input_format == NULL)
return NULL;
struct tag *tag = (struct tag *) ctx->tag;
AVFormatContext *f = ctx->format_context;
struct mpd_ffmpeg_stream *stream = mpd_ffmpeg_stream_open(NULL, is);
if (stream == NULL)
return NULL;
AVFormatContext *f;
if (av_open_input_stream(&f, stream->io, is->uri,
input_format, NULL) != 0) {
mpd_ffmpeg_stream_close(stream);
return NULL;
}
if (av_find_stream_info(f) < 0) {
av_close_input_stream(f);
mpd_ffmpeg_stream_close(stream);
return NULL;
}
struct tag *tag = tag_new();
tag->time = f->duration != (int64_t)AV_NOPTS_VALUE
? f->duration / AV_TIME_BASE
: 0;
tag->time = 0;
if (f->duration != (int64_t)AV_NOPTS_VALUE)
tag->time = f->duration / AV_TIME_BASE;
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0)
av_metadata_conv(f, NULL, f->iformat->metadata_conv);
for (unsigned i = 0; i < sizeof(ffmpeg_tag_maps)/sizeof(ffmpeg_tag_map); i++) {
int idx = ffmpeg_find_audio_stream(f);
ffmpeg_copy_metadata(tag, f->metadata, ffmpeg_tag_maps[i]);
if (idx >= 0)
ffmpeg_copy_metadata(tag, f->streams[idx]->metadata, ffmpeg_tag_maps[i]);
}
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TITLE, "title");
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(50<<8))
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ARTIST, "artist");
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DATE, "date");
#else
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ARTIST, "author");
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DATE, "year");
#endif
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ALBUM, "album");
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_COMMENT, "comment");
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_GENRE, "genre");
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TRACK, "track");
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ALBUM_ARTIST, "album_artist");
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_COMPOSER, "composer");
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_PERFORMER, "performer");
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DISC, "disc");
#else
if (f->author[0])
tag_add_item(tag, TAG_ARTIST, f->author);
tag_add_item(tag, TAG_ITEM_ARTIST, f->author);
if (f->title[0])
tag_add_item(tag, TAG_TITLE, f->title);
tag_add_item(tag, TAG_ITEM_TITLE, f->title);
if (f->album[0])
tag_add_item(tag, TAG_ALBUM, f->album);
tag_add_item(tag, TAG_ITEM_ALBUM, f->album);
if (f->track > 0) {
char buffer[16];
snprintf(buffer, sizeof(buffer), "%d", f->track);
tag_add_item(tag, TAG_TRACK, buffer);
tag_add_item(tag, TAG_ITEM_TRACK, buffer);
}
if (f->comment[0])
tag_add_item(tag, TAG_COMMENT, f->comment);
tag_add_item(tag, TAG_ITEM_COMMENT, f->comment);
if (f->genre[0])
tag_add_item(tag, TAG_GENRE, f->genre);
tag_add_item(tag, TAG_ITEM_GENRE, f->genre);
if (f->year > 0) {
char buffer[16];
snprintf(buffer, sizeof(buffer), "%d", f->year);
tag_add_item(tag, TAG_DATE, buffer);
tag_add_item(tag, TAG_ITEM_DATE, buffer);
}
#endif
return true;
}
av_close_input_stream(f);
mpd_ffmpeg_stream_close(stream);
//no tag reading in ffmpeg, check if playable
static struct tag *ffmpeg_tag(const char *file)
{
struct input_stream input;
struct ffmpeg_context ctx;
bool ret;
return tag;
if (!input_stream_open(&input, file)) {
g_warning("failed to open %s\n", file);
return NULL;
}
ctx.decoder = NULL;
ctx.tag = tag_new();
ret = ffmpeg_helper(file, NULL, &input, ffmpeg_tag_internal, &ctx);
if (!ret) {
tag_free(ctx.tag);
ctx.tag = NULL;
}
input_stream_close(&input);
return ctx.tag;
}
/**
@@ -522,7 +501,9 @@ static const char *const ffmpeg_suffixes[] = {
"atrac", "au", "aud", "avi", "avm2", "avs", "bap", "bfi", "c93", "cak",
"cin", "cmv", "cpk", "daud", "dct", "divx", "dts", "dv", "dvd", "dxa",
"eac3", "film", "flac", "flc", "fli", "fll", "flx", "flv", "g726",
"gsm", "gxf", "iss", "m1v", "m2v", "m2t", "m2ts", "m4a", "m4v", "mad",
"gsm", "gxf", "iss", "m1v", "m2v", "m2t", "m2ts",
"m4a", "m4b", "m4v",
"mad",
"mj2", "mjpeg", "mjpg", "mka", "mkv", "mlp", "mm", "mmf", "mov", "mp+",
"mp1", "mp2", "mp3", "mp4", "mpc", "mpeg", "mpg", "mpga", "mpp", "mpu",
"mve", "mvi", "mxf", "nc", "nsv", "nut", "nuv", "oga", "ogm", "ogv",
@@ -611,12 +592,6 @@ static const char *const ffmpeg_mime_types[] = {
"video/x-vid",
"video/x-wmv",
"video/x-xvid",
/* special value for the "ffmpeg" input plugin: all streams by
the "ffmpeg" input plugin shall be decoded by this
plugin */
"audio/x-mpd-ffmpeg",
NULL
};
@@ -624,7 +599,7 @@ const struct decoder_plugin ffmpeg_decoder_plugin = {
.name = "ffmpeg",
.init = ffmpeg_init,
.stream_decode = ffmpeg_decode,
.stream_tag = ffmpeg_stream_tag,
.tag_dup = ffmpeg_tag,
.suffixes = ffmpeg_suffixes,
.mime_types = ffmpeg_mime_types
};

View File

@@ -1,114 +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.
*/
/*
* Common data structures and functions used by FLAC and OggFLAC
*/
#ifndef MPD_FLAC_COMPAT_H
#define MPD_FLAC_COMPAT_H
#include <FLAC/export.h>
#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
# include <FLAC/seekable_stream_decoder.h>
/* starting with libFLAC 1.1.3, the SeekableStreamDecoder has been
merged into the StreamDecoder. The following macros try to emulate
the new API for libFLAC 1.1.2 by mapping MPD's StreamDecoder calls
to the old SeekableStreamDecoder API. */
#define FLAC__StreamDecoder FLAC__SeekableStreamDecoder
#define FLAC__stream_decoder_new FLAC__seekable_stream_decoder_new
#define FLAC__stream_decoder_get_decode_position FLAC__seekable_stream_decoder_get_decode_position
#define FLAC__stream_decoder_get_state FLAC__seekable_stream_decoder_get_state
#define FLAC__stream_decoder_process_single FLAC__seekable_stream_decoder_process_single
#define FLAC__stream_decoder_process_until_end_of_metadata FLAC__seekable_stream_decoder_process_until_end_of_metadata
#define FLAC__stream_decoder_seek_absolute FLAC__seekable_stream_decoder_seek_absolute
#define FLAC__stream_decoder_finish FLAC__seekable_stream_decoder_finish
#define FLAC__stream_decoder_delete FLAC__seekable_stream_decoder_delete
#define FLAC__STREAM_DECODER_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM
typedef unsigned flac_read_status_size_t;
#define FLAC__StreamDecoderReadStatus FLAC__SeekableStreamDecoderReadStatus
#define FLAC__STREAM_DECODER_READ_STATUS_CONTINUE FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK
#define FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK
#define FLAC__STREAM_DECODER_READ_STATUS_ABORT FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR
#define FLAC__StreamDecoderSeekStatus FLAC__SeekableStreamDecoderSeekStatus
#define FLAC__STREAM_DECODER_SEEK_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK
#define FLAC__STREAM_DECODER_SEEK_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR
#define FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR
#define FLAC__StreamDecoderTellStatus FLAC__SeekableStreamDecoderTellStatus
#define FLAC__STREAM_DECODER_TELL_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK
#define FLAC__STREAM_DECODER_TELL_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR
#define FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR
#define FLAC__StreamDecoderLengthStatus FLAC__SeekableStreamDecoderLengthStatus
#define FLAC__STREAM_DECODER_LENGTH_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK
#define FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR
#define FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR
typedef enum {
FLAC__STREAM_DECODER_INIT_STATUS_OK,
FLAC__STREAM_DECODER_INIT_STATUS_ERROR,
} FLAC__StreamDecoderInitStatus;
static inline FLAC__StreamDecoderInitStatus
FLAC__stream_decoder_init_stream(FLAC__SeekableStreamDecoder *decoder,
FLAC__SeekableStreamDecoderReadCallback read_cb,
FLAC__SeekableStreamDecoderSeekCallback seek_cb,
FLAC__SeekableStreamDecoderTellCallback tell_cb,
FLAC__SeekableStreamDecoderLengthCallback length_cb,
FLAC__SeekableStreamDecoderEofCallback eof_cb,
FLAC__SeekableStreamDecoderWriteCallback write_cb,
FLAC__SeekableStreamDecoderMetadataCallback metadata_cb,
FLAC__SeekableStreamDecoderErrorCallback error_cb,
void *data)
{
return FLAC__seekable_stream_decoder_set_read_callback(decoder, read_cb) &&
FLAC__seekable_stream_decoder_set_seek_callback(decoder, seek_cb) &&
FLAC__seekable_stream_decoder_set_tell_callback(decoder, tell_cb) &&
FLAC__seekable_stream_decoder_set_length_callback(decoder, length_cb) &&
FLAC__seekable_stream_decoder_set_eof_callback(decoder, eof_cb) &&
FLAC__seekable_stream_decoder_set_write_callback(decoder, write_cb) &&
FLAC__seekable_stream_decoder_set_metadata_callback(decoder, metadata_cb) &&
FLAC__seekable_stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT) &&
FLAC__seekable_stream_decoder_set_error_callback(decoder, error_cb) &&
FLAC__seekable_stream_decoder_set_client_data(decoder, data) &&
FLAC__seekable_stream_decoder_init(decoder) == FLAC__SEEKABLE_STREAM_DECODER_OK
? FLAC__STREAM_DECODER_INIT_STATUS_OK
: FLAC__STREAM_DECODER_INIT_STATUS_ERROR;
}
#else /* FLAC_API_VERSION_CURRENT > 7 */
# include <FLAC/stream_decoder.h>
# define flac_init(a,b,c,d,e,f,g,h,i,j) \
(FLAC__stream_decoder_init_stream(a,b,c,d,e,f,g,h,i,j) \
== FLAC__STREAM_DECODER_INIT_STATUS_OK)
typedef size_t flac_read_status_size_t;
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
#endif /* _FLAC_COMMON_H */

View File

@@ -1,497 +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" /* must be first for large file support */
#include "_flac_common.h"
#include "flac_compat.h"
#include "flac_metadata.h"
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
#include "_ogg_common.h"
#endif
#include <glib.h>
#include <assert.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
/* this code was based on flac123, from flac-tools */
static FLAC__StreamDecoderReadStatus
flac_read_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
FLAC__byte buf[], flac_read_status_size_t *bytes,
void *fdata)
{
struct flac_data *data = fdata;
size_t r;
r = decoder_read(data->decoder, data->input_stream,
(void *)buf, *bytes);
*bytes = r;
if (r == 0) {
if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE ||
input_stream_eof(data->input_stream))
return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
else
return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
}
return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
}
static FLAC__StreamDecoderSeekStatus
flac_seek_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
FLAC__uint64 offset, void *fdata)
{
struct flac_data *data = (struct flac_data *) fdata;
if (!data->input_stream->seekable)
return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED;
if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL))
return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
}
static FLAC__StreamDecoderTellStatus
flac_tell_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
FLAC__uint64 * offset, void *fdata)
{
struct flac_data *data = (struct flac_data *) fdata;
if (!data->input_stream->seekable)
return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED;
*offset = (long)(data->input_stream->offset);
return FLAC__STREAM_DECODER_TELL_STATUS_OK;
}
static FLAC__StreamDecoderLengthStatus
flac_length_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
FLAC__uint64 * length, void *fdata)
{
struct flac_data *data = (struct flac_data *) fdata;
if (data->input_stream->size < 0)
return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED;
*length = (size_t) (data->input_stream->size);
return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
}
static FLAC__bool
flac_eof_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, void *fdata)
{
struct flac_data *data = (struct flac_data *) fdata;
return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE &&
decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) ||
input_stream_eof(data->input_stream);
}
static void
flac_error_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
FLAC__StreamDecoderErrorStatus status, void *fdata)
{
flac_error_common_cb("flac", status, (struct flac_data *) fdata);
}
#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
static void flacPrintErroredState(FLAC__SeekableStreamDecoderState state)
{
const char *str = ""; /* "" to silence compiler warning */
switch (state) {
case FLAC__SEEKABLE_STREAM_DECODER_OK:
case FLAC__SEEKABLE_STREAM_DECODER_SEEKING:
case FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM:
return;
case FLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
str = "allocation error";
break;
case FLAC__SEEKABLE_STREAM_DECODER_READ_ERROR:
str = "read error";
break;
case FLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR:
str = "seek error";
break;
case FLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR:
str = "seekable stream error";
break;
case FLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED:
str = "decoder already initialized";
break;
case FLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK:
str = "invalid callback";
break;
case FLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED:
str = "decoder uninitialized";
}
g_warning("%s\n", str);
}
#else /* FLAC_API_VERSION_CURRENT >= 7 */
static void flacPrintErroredState(FLAC__StreamDecoderState state)
{
const char *str = ""; /* "" to silence compiler warning */
switch (state) {
case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA:
case FLAC__STREAM_DECODER_READ_METADATA:
case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC:
case FLAC__STREAM_DECODER_READ_FRAME:
case FLAC__STREAM_DECODER_END_OF_STREAM:
return;
case FLAC__STREAM_DECODER_OGG_ERROR:
str = "error in the Ogg layer";
break;
case FLAC__STREAM_DECODER_SEEK_ERROR:
str = "seek error";
break;
case FLAC__STREAM_DECODER_ABORTED:
str = "decoder aborted by read";
break;
case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
str = "allocation error";
break;
case FLAC__STREAM_DECODER_UNINITIALIZED:
str = "decoder uninitialized";
}
g_warning("%s\n", str);
}
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
static void flacMetadata(G_GNUC_UNUSED const FLAC__StreamDecoder * dec,
const FLAC__StreamMetadata * block, void *vdata)
{
flac_metadata_common_cb(block, (struct flac_data *) vdata);
}
static FLAC__StreamDecoderWriteStatus
flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame,
const FLAC__int32 *const buf[], void *vdata)
{
struct flac_data *data = (struct flac_data *) vdata;
FLAC__uint64 nbytes = 0;
if (FLAC__stream_decoder_get_decode_position(dec, &nbytes)) {
if (data->position > 0 && nbytes > data->position) {
nbytes -= data->position;
data->position += nbytes;
} else {
data->position = nbytes;
nbytes = 0;
}
} else
nbytes = 0;
return flac_common_write(data, frame, buf, nbytes);
}
static struct tag *
flac_tag_dup(const char *file)
{
return flac_tag_load(file, NULL);
}
/**
* Some glue code around FLAC__stream_decoder_new().
*/
static FLAC__StreamDecoder *
flac_decoder_new(void)
{
FLAC__StreamDecoder *sd = FLAC__stream_decoder_new();
if (sd == NULL) {
g_warning("FLAC__stream_decoder_new() failed");
return NULL;
}
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT))
g_debug("FLAC__stream_decoder_set_metadata_respond() has failed");
#endif
return sd;
}
static bool
flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd,
FLAC__uint64 duration)
{
data->total_frames = duration;
if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) {
g_warning("problem reading metadata");
return false;
}
if (data->initialized) {
/* done */
decoder_initialized(data->decoder, &data->audio_format,
data->input_stream->seekable,
(float)data->total_frames /
(float)data->audio_format.sample_rate);
return true;
}
if (data->input_stream->seekable)
/* allow the workaround below only for nonseekable
streams*/
return false;
/* no stream_info packet found; try to initialize the decoder
from the first frame header */
FLAC__stream_decoder_process_single(sd);
return data->initialized;
}
static void
flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec,
FLAC__uint64 t_start, FLAC__uint64 t_end)
{
struct decoder *decoder = data->decoder;
enum decoder_command cmd;
data->first_frame = t_start;
while (true) {
if (data->tag != NULL && !tag_is_empty(data->tag)) {
cmd = decoder_tag(data->decoder, data->input_stream,
data->tag);
tag_free(data->tag);
data->tag = tag_new();
} else
cmd = decoder_get_command(decoder);
if (cmd == DECODE_COMMAND_SEEK) {
FLAC__uint64 seek_sample = t_start +
decoder_seek_where(decoder) *
data->audio_format.sample_rate;
if (seek_sample >= t_start &&
(t_end == 0 || seek_sample <= t_end) &&
FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) {
data->next_frame = seek_sample;
data->position = 0;
decoder_command_finished(decoder);
} else
decoder_seek_error(decoder);
} else if (cmd == DECODE_COMMAND_STOP ||
FLAC__stream_decoder_get_state(flac_dec) == FLAC__STREAM_DECODER_END_OF_STREAM)
break;
if (t_end != 0 && data->next_frame >= t_end)
/* end of this sub track */
break;
if (!FLAC__stream_decoder_process_single(flac_dec)) {
cmd = decoder_get_command(decoder);
if (cmd != DECODE_COMMAND_SEEK)
break;
}
}
if (cmd != DECODE_COMMAND_STOP) {
flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec));
FLAC__stream_decoder_finish(flac_dec);
}
}
static void
flac_decode_internal(struct decoder * decoder,
struct input_stream *input_stream,
bool is_ogg)
{
FLAC__StreamDecoder *flac_dec;
struct flac_data data;
const char *err = NULL;
flac_dec = flac_decoder_new();
if (flac_dec == NULL)
return;
flac_data_init(&data, decoder, input_stream);
data.tag = tag_new();
if (is_ogg) {
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
FLAC__StreamDecoderInitStatus status =
FLAC__stream_decoder_init_ogg_stream(flac_dec,
flac_read_cb,
flac_seek_cb,
flac_tell_cb,
flac_length_cb,
flac_eof_cb,
flac_write_cb,
flacMetadata,
flac_error_cb,
(void *)&data);
if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
err = "doing Ogg init()";
goto fail;
}
#else
goto fail;
#endif
} else {
FLAC__StreamDecoderInitStatus status =
FLAC__stream_decoder_init_stream(flac_dec,
flac_read_cb,
flac_seek_cb,
flac_tell_cb,
flac_length_cb,
flac_eof_cb,
flac_write_cb,
flacMetadata,
flac_error_cb,
(void *)&data);
if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
err = "doing init()";
goto fail;
}
}
if (!flac_decoder_initialize(&data, flac_dec, 0)) {
flac_data_deinit(&data);
FLAC__stream_decoder_delete(flac_dec);
return;
}
flac_decoder_loop(&data, flac_dec, 0, 0);
fail:
flac_data_deinit(&data);
FLAC__stream_decoder_delete(flac_dec);
if (err)
g_warning("%s\n", err);
}
static void
flac_decode(struct decoder * decoder, struct input_stream *input_stream)
{
flac_decode_internal(decoder, input_stream, false);
}
#ifndef HAVE_OGGFLAC
static bool
oggflac_init(G_GNUC_UNUSED const struct config_param *param)
{
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
return !!FLAC_API_SUPPORTS_OGG_FLAC;
#else
/* disable oggflac when libflac is too old */
return false;
#endif
}
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
static struct tag *
oggflac_tag_dup(const char *file)
{
struct tag *ret = NULL;
FLAC__Metadata_Iterator *it;
FLAC__StreamMetadata *block;
FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new();
if (!(FLAC__metadata_chain_read_ogg(chain, file)))
goto out;
it = FLAC__metadata_iterator_new();
FLAC__metadata_iterator_init(it, chain);
ret = tag_new();
do {
if (!(block = FLAC__metadata_iterator_get_block(it)))
break;
flac_tag_apply_metadata(ret, NULL, block);
} while (FLAC__metadata_iterator_next(it));
FLAC__metadata_iterator_delete(it);
if (!tag_is_defined(ret)) {
tag_free(ret);
ret = NULL;
}
out:
FLAC__metadata_chain_delete(chain);
return ret;
}
static void
oggflac_decode(struct decoder *decoder, struct input_stream *input_stream)
{
if (ogg_stream_type_detect(input_stream) != FLAC)
return;
/* rewind the stream, because ogg_stream_type_detect() has
moved it */
input_stream_seek(input_stream, 0, SEEK_SET, NULL);
flac_decode_internal(decoder, input_stream, true);
}
static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL };
static const char *const oggflac_mime_types[] = {
"application/ogg",
"application/x-ogg",
"audio/ogg",
"audio/x-flac+ogg",
"audio/x-ogg",
NULL
};
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
const struct decoder_plugin oggflac_decoder_plugin = {
.name = "oggflac",
.init = oggflac_init,
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
.stream_decode = oggflac_decode,
.tag_dup = oggflac_tag_dup,
.suffixes = oggflac_suffixes,
.mime_types = oggflac_mime_types
#endif
};
#endif /* HAVE_OGGFLAC */
static const char *const flac_suffixes[] = { "flac", NULL };
static const char *const flac_mime_types[] = {
"application/flac",
"application/x-flac",
"audio/flac",
"audio/x-flac",
NULL
};
const struct decoder_plugin flac_decoder_plugin = {
.name = "flac",
.stream_decode = flac_decode,
.tag_dup = flac_tag_dup,
.suffixes = flac_suffixes,
.mime_types = flac_mime_types,
};

View File

@@ -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 "flac_metadata.h"
#include "replay_gain_info.h"
#include "tag.h"
#include <glib.h>
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
static bool
flac_find_float_comment(const FLAC__StreamMetadata *block,
const char *cmnt, float *fl)
{
int offset;
size_t pos;
int len;
unsigned char tmp, *p;
offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0,
cmnt);
if (offset < 0)
return false;
pos = strlen(cmnt) + 1; /* 1 is for '=' */
len = block->data.vorbis_comment.comments[offset].length - pos;
if (len <= 0)
return false;
p = &block->data.vorbis_comment.comments[offset].entry[pos];
tmp = p[len];
p[len] = '\0';
*fl = (float)atof((char *)p);
p[len] = tmp;
return true;
}
bool
flac_parse_replay_gain(struct replay_gain_info *rgi,
const FLAC__StreamMetadata *block)
{
bool found = false;
replay_gain_info_init(rgi);
if (flac_find_float_comment(block, "replaygain_album_gain",
&rgi->tuples[REPLAY_GAIN_ALBUM].gain))
found = true;
if (flac_find_float_comment(block, "replaygain_album_peak",
&rgi->tuples[REPLAY_GAIN_ALBUM].peak))
found = true;
if (flac_find_float_comment(block, "replaygain_track_gain",
&rgi->tuples[REPLAY_GAIN_TRACK].gain))
found = true;
if (flac_find_float_comment(block, "replaygain_track_peak",
&rgi->tuples[REPLAY_GAIN_TRACK].peak))
found = true;
return found;
}
static bool
flac_find_string_comment(const FLAC__StreamMetadata *block,
const char *cmnt, char **str)
{
int offset;
size_t pos;
int len;
unsigned char tmp, *p;
*str = NULL;
offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0,
cmnt);
if (offset < 0)
return false;
pos = strlen(cmnt) + 1; /* 1 is for '=' */
len = block->data.vorbis_comment.comments[offset].length - pos;
if (len <= 0)
return false;
p = &block->data.vorbis_comment.comments[offset].entry[pos];
tmp = p[len];
p[len] = '\0';
*str = strdup((char *)p);
p[len] = tmp;
return true;
}
bool
flac_parse_mixramp(char **mixramp_start, char **mixramp_end,
const FLAC__StreamMetadata *block)
{
bool found = false;
if (flac_find_string_comment(block, "mixramp_start", mixramp_start))
found = true;
if (flac_find_string_comment(block, "mixramp_end", mixramp_end))
found = true;
return found;
}
/**
* Checks if the specified name matches the entry's name, and if yes,
* returns the comment value (not null-temrinated).
*/
static const char *
flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
const char *name, const char *char_tnum, size_t *length_r)
{
size_t name_length = strlen(name);
size_t char_tnum_length = 0;
const char *comment = (const char*)entry->entry;
if (entry->length <= name_length ||
g_ascii_strncasecmp(comment, name, name_length) != 0)
return NULL;
if (char_tnum != NULL) {
char_tnum_length = strlen(char_tnum);
if (entry->length > name_length + char_tnum_length + 2 &&
comment[name_length] == '[' &&
g_ascii_strncasecmp(comment + name_length + 1,
char_tnum, char_tnum_length) == 0 &&
comment[name_length + char_tnum_length + 1] == ']')
name_length = name_length + char_tnum_length + 2;
else if (entry->length > name_length + char_tnum_length &&
g_ascii_strncasecmp(comment + name_length,
char_tnum, char_tnum_length) == 0)
name_length = name_length + char_tnum_length;
}
if (comment[name_length] == '=') {
*length_r = entry->length - name_length - 1;
return comment + name_length + 1;
}
return NULL;
}
/**
* Check if the comment's name equals the passed name, and if so, copy
* the comment value into the tag.
*/
static bool
flac_copy_comment(struct tag *tag,
const FLAC__StreamMetadata_VorbisComment_Entry *entry,
const char *name, enum tag_type tag_type,
const char *char_tnum)
{
const char *value;
size_t value_length;
value = flac_comment_value(entry, name, char_tnum, &value_length);
if (value != NULL) {
tag_add_item_n(tag, tag_type, value, value_length);
return true;
}
return false;
}
/* tracknumber is used in VCs, MPD uses "track" ..., all the other
* tag names match */
static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber";
static const char *VORBIS_COMMENT_DISC_KEY = "discnumber";
static void
flac_parse_comment(struct tag *tag, const char *char_tnum,
const FLAC__StreamMetadata_VorbisComment_Entry *entry)
{
assert(tag != NULL);
if (flac_copy_comment(tag, entry, VORBIS_COMMENT_TRACK_KEY,
TAG_TRACK, char_tnum) ||
flac_copy_comment(tag, entry, VORBIS_COMMENT_DISC_KEY,
TAG_DISC, char_tnum) ||
flac_copy_comment(tag, entry, "album artist",
TAG_ALBUM_ARTIST, char_tnum))
return;
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
if (flac_copy_comment(tag, entry,
tag_item_names[i], i, char_tnum))
return;
}
void
flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum,
const FLAC__StreamMetadata_VorbisComment *comment)
{
for (unsigned i = 0; i < comment->num_comments; ++i)
flac_parse_comment(tag, char_tnum, &comment->comments[i]);
}
void
flac_tag_apply_metadata(struct tag *tag, const char *track,
const FLAC__StreamMetadata *block)
{
switch (block->type) {
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
flac_vorbis_comments_to_tag(tag, track,
&block->data.vorbis_comment);
break;
case FLAC__METADATA_TYPE_STREAMINFO:
tag->time = flac_duration(&block->data.stream_info);
break;
default:
break;
}
}
struct tag *
flac_tag_load(const char *file, const char *char_tnum)
{
struct tag *tag;
FLAC__Metadata_SimpleIterator *it;
FLAC__StreamMetadata *block = NULL;
it = FLAC__metadata_simple_iterator_new();
if (!FLAC__metadata_simple_iterator_init(it, file, 1, 0)) {
const char *err;
FLAC_API FLAC__Metadata_SimpleIteratorStatus s;
s = FLAC__metadata_simple_iterator_status(it);
switch (s) { /* slightly more human-friendly messages: */
case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ILLEGAL_INPUT:
err = "illegal input";
break;
case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ERROR_OPENING_FILE:
err = "error opening file";
break;
case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_NOT_A_FLAC_FILE:
err = "not a FLAC file";
break;
default:
err = FLAC__Metadata_SimpleIteratorStatusString[s];
}
g_debug("Reading '%s' metadata gave the following error: %s\n",
file, err);
FLAC__metadata_simple_iterator_delete(it);
return NULL;
}
tag = tag_new();
do {
block = FLAC__metadata_simple_iterator_get_block(it);
if (!block)
break;
flac_tag_apply_metadata(tag, char_tnum, block);
FLAC__metadata_object_delete(block);
} while (FLAC__metadata_simple_iterator_next(it));
FLAC__metadata_simple_iterator_delete(it);
if (!tag_is_defined(tag)) {
tag_free(tag);
tag = NULL;
}
return tag;
}

View File

@@ -1,55 +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_FLAC_METADATA_H
#define MPD_FLAC_METADATA_H
#include <stdbool.h>
#include <FLAC/metadata.h>
struct tag;
struct replay_gain_info;
static inline unsigned
flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info)
{
return (stream_info->total_samples + stream_info->sample_rate - 1) /
stream_info->sample_rate;
}
bool
flac_parse_replay_gain(struct replay_gain_info *rgi,
const FLAC__StreamMetadata *block);
bool
flac_parse_mixramp(char **mixramp_start, char **mixramp_end,
const FLAC__StreamMetadata *block);
void
flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum,
const FLAC__StreamMetadata_VorbisComment *comment);
void
flac_tag_apply_metadata(struct tag *tag, const char *track,
const FLAC__StreamMetadata *block);
struct tag *
flac_tag_load(const char *file, const char *char_tnum);
#endif

View File

@@ -1,109 +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 "flac_pcm.h"
#include <assert.h>
static void flac_convert_stereo16(int16_t *dest,
const FLAC__int32 * const buf[],
unsigned int position, unsigned int end)
{
for (; position < end; ++position) {
*dest++ = buf[0][position];
*dest++ = buf[1][position];
}
}
static void
flac_convert_16(int16_t *dest,
unsigned int num_channels,
const FLAC__int32 * const buf[],
unsigned int position, unsigned int end)
{
unsigned int c_chan;
for (; position < end; ++position)
for (c_chan = 0; c_chan < num_channels; c_chan++)
*dest++ = buf[c_chan][position];
}
/**
* Note: this function also handles 24 bit files!
*/
static void
flac_convert_32(int32_t *dest,
unsigned int num_channels,
const FLAC__int32 * const buf[],
unsigned int position, unsigned int end)
{
unsigned int c_chan;
for (; position < end; ++position)
for (c_chan = 0; c_chan < num_channels; c_chan++)
*dest++ = buf[c_chan][position];
}
static void
flac_convert_8(int8_t *dest,
unsigned int num_channels,
const FLAC__int32 * const buf[],
unsigned int position, unsigned int end)
{
unsigned int c_chan;
for (; position < end; ++position)
for (c_chan = 0; c_chan < num_channels; c_chan++)
*dest++ = buf[c_chan][position];
}
void
flac_convert(void *dest,
unsigned int num_channels, enum sample_format sample_format,
const FLAC__int32 *const buf[],
unsigned int position, unsigned int end)
{
switch (sample_format) {
case SAMPLE_FORMAT_S16:
if (num_channels == 2)
flac_convert_stereo16((int16_t*)dest, buf,
position, end);
else
flac_convert_16((int16_t*)dest, num_channels, buf,
position, end);
break;
case SAMPLE_FORMAT_S24_P32:
case SAMPLE_FORMAT_S32:
flac_convert_32((int32_t*)dest, num_channels, buf,
position, end);
break;
case SAMPLE_FORMAT_S8:
flac_convert_8((int8_t*)dest, num_channels, buf,
position, end);
break;
case SAMPLE_FORMAT_S24:
case SAMPLE_FORMAT_UNDEFINED:
/* unreachable */
assert(false);
}
}

918
src/decoder/flac_plugin.c Normal file
View File

@@ -0,0 +1,918 @@
/*
* Copyright (C) 2003-2009 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "_flac_common.h"
#include <glib.h>
#include <assert.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#ifdef HAVE_CUE /* libcue */
#include "../cue/cue_tag.h"
#endif
/* this code was based on flac123, from flac-tools */
static flac_read_status
flac_read_cb(G_GNUC_UNUSED const flac_decoder *fd,
FLAC__byte buf[], flac_read_status_size_t *bytes,
void *fdata)
{
struct flac_data *data = fdata;
size_t r;
r = decoder_read(data->decoder, data->input_stream,
(void *)buf, *bytes);
*bytes = r;
if (r == 0) {
if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE ||
input_stream_eof(data->input_stream))
return flac_read_status_eof;
else
return flac_read_status_abort;
}
return flac_read_status_continue;
}
static flac_seek_status
flac_seek_cb(G_GNUC_UNUSED const flac_decoder *fd,
FLAC__uint64 offset, void *fdata)
{
struct flac_data *data = (struct flac_data *) fdata;
if (!input_stream_seek(data->input_stream, offset, SEEK_SET))
return flac_seek_status_error;
return flac_seek_status_ok;
}
static flac_tell_status
flac_tell_cb(G_GNUC_UNUSED const flac_decoder *fd,
FLAC__uint64 * offset, void *fdata)
{
struct flac_data *data = (struct flac_data *) fdata;
*offset = (long)(data->input_stream->offset);
return flac_tell_status_ok;
}
static flac_length_status
flac_length_cb(G_GNUC_UNUSED const flac_decoder *fd,
FLAC__uint64 * length, void *fdata)
{
struct flac_data *data = (struct flac_data *) fdata;
if (data->input_stream->size < 0)
return flac_length_status_unsupported;
*length = (size_t) (data->input_stream->size);
return flac_length_status_ok;
}
static FLAC__bool
flac_eof_cb(G_GNUC_UNUSED const flac_decoder *fd, void *fdata)
{
struct flac_data *data = (struct flac_data *) fdata;
return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE &&
decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) ||
input_stream_eof(data->input_stream);
}
static void
flac_error_cb(G_GNUC_UNUSED const flac_decoder *fd,
FLAC__StreamDecoderErrorStatus status, void *fdata)
{
flac_error_common_cb("flac", status, (struct flac_data *) fdata);
}
#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
static void flacPrintErroredState(FLAC__SeekableStreamDecoderState state)
{
const char *str = ""; /* "" to silence compiler warning */
switch (state) {
case FLAC__SEEKABLE_STREAM_DECODER_OK:
case FLAC__SEEKABLE_STREAM_DECODER_SEEKING:
case FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM:
return;
case FLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
str = "allocation error";
break;
case FLAC__SEEKABLE_STREAM_DECODER_READ_ERROR:
str = "read error";
break;
case FLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR:
str = "seek error";
break;
case FLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR:
str = "seekable stream error";
break;
case FLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED:
str = "decoder already initialized";
break;
case FLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK:
str = "invalid callback";
break;
case FLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED:
str = "decoder uninitialized";
}
g_warning("%s\n", str);
}
static bool
flac_init(FLAC__SeekableStreamDecoder *dec,
FLAC__SeekableStreamDecoderReadCallback read_cb,
FLAC__SeekableStreamDecoderSeekCallback seek_cb,
FLAC__SeekableStreamDecoderTellCallback tell_cb,
FLAC__SeekableStreamDecoderLengthCallback length_cb,
FLAC__SeekableStreamDecoderEofCallback eof_cb,
FLAC__SeekableStreamDecoderWriteCallback write_cb,
FLAC__SeekableStreamDecoderMetadataCallback metadata_cb,
FLAC__SeekableStreamDecoderErrorCallback error_cb,
void *data)
{
return FLAC__seekable_stream_decoder_set_read_callback(dec, read_cb) &&
FLAC__seekable_stream_decoder_set_seek_callback(dec, seek_cb) &&
FLAC__seekable_stream_decoder_set_tell_callback(dec, tell_cb) &&
FLAC__seekable_stream_decoder_set_length_callback(dec, length_cb) &&
FLAC__seekable_stream_decoder_set_eof_callback(dec, eof_cb) &&
FLAC__seekable_stream_decoder_set_write_callback(dec, write_cb) &&
FLAC__seekable_stream_decoder_set_metadata_callback(dec, metadata_cb) &&
FLAC__seekable_stream_decoder_set_metadata_respond(dec, FLAC__METADATA_TYPE_VORBIS_COMMENT) &&
FLAC__seekable_stream_decoder_set_error_callback(dec, error_cb) &&
FLAC__seekable_stream_decoder_set_client_data(dec, data) &&
FLAC__seekable_stream_decoder_init(dec) == FLAC__SEEKABLE_STREAM_DECODER_OK;
}
#else /* FLAC_API_VERSION_CURRENT >= 7 */
static void flacPrintErroredState(FLAC__StreamDecoderState state)
{
const char *str = ""; /* "" to silence compiler warning */
switch (state) {
case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA:
case FLAC__STREAM_DECODER_READ_METADATA:
case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC:
case FLAC__STREAM_DECODER_READ_FRAME:
case FLAC__STREAM_DECODER_END_OF_STREAM:
return;
case FLAC__STREAM_DECODER_OGG_ERROR:
str = "error in the Ogg layer";
break;
case FLAC__STREAM_DECODER_SEEK_ERROR:
str = "seek error";
break;
case FLAC__STREAM_DECODER_ABORTED:
str = "decoder aborted by read";
break;
case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
str = "allocation error";
break;
case FLAC__STREAM_DECODER_UNINITIALIZED:
str = "decoder uninitialized";
}
g_warning("%s\n", str);
}
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
static void flacMetadata(G_GNUC_UNUSED const flac_decoder * dec,
const FLAC__StreamMetadata * block, void *vdata)
{
flac_metadata_common_cb(block, (struct flac_data *) vdata);
}
static FLAC__StreamDecoderWriteStatus
flac_write_cb(const flac_decoder *dec, const FLAC__Frame *frame,
const FLAC__int32 *const buf[], void *vdata)
{
FLAC__uint32 samples = frame->header.blocksize;
struct flac_data *data = (struct flac_data *) vdata;
float timeChange;
FLAC__uint64 newPosition = 0;
timeChange = ((float)samples) / frame->header.sample_rate;
data->time += timeChange;
flac_get_decode_position(dec, &newPosition);
if (data->position && newPosition >= data->position) {
assert(timeChange >= 0);
data->bit_rate =
((newPosition - data->position) * 8.0 / timeChange)
/ 1000 + 0.5;
}
data->position = newPosition;
return flac_common_write(data, frame, buf);
}
static struct tag *
flac_tag_load(const char *file, const char *char_tnum)
{
struct tag *tag;
FLAC__Metadata_SimpleIterator *it;
FLAC__StreamMetadata *block = NULL;
it = FLAC__metadata_simple_iterator_new();
if (!FLAC__metadata_simple_iterator_init(it, file, 1, 0)) {
const char *err;
FLAC_API FLAC__Metadata_SimpleIteratorStatus s;
s = FLAC__metadata_simple_iterator_status(it);
switch (s) { /* slightly more human-friendly messages: */
case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ILLEGAL_INPUT:
err = "illegal input";
break;
case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ERROR_OPENING_FILE:
err = "error opening file";
break;
case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_NOT_A_FLAC_FILE:
err = "not a FLAC file";
break;
default:
err = FLAC__Metadata_SimpleIteratorStatusString[s];
}
g_debug("Reading '%s' metadata gave the following error: %s\n",
file, err);
FLAC__metadata_simple_iterator_delete(it);
return NULL;
}
tag = tag_new();
do {
block = FLAC__metadata_simple_iterator_get_block(it);
if (!block)
break;
if (block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) {
flac_vorbis_comments_to_tag(tag, char_tnum, block);
} else if (block->type == FLAC__METADATA_TYPE_STREAMINFO) {
tag->time = ((float)block->data.stream_info.total_samples) /
block->data.stream_info.sample_rate + 0.5;
}
FLAC__metadata_object_delete(block);
} while (FLAC__metadata_simple_iterator_next(it));
FLAC__metadata_simple_iterator_delete(it);
if (!tag_is_defined(tag)) {
tag_free(tag);
tag = NULL;
}
return tag;
}
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
static struct tag *
flac_cue_tag_load(const char *file)
{
struct tag* tag = NULL;
char* char_tnum = NULL;
char* ptr = NULL;
unsigned int tnum = 0;
unsigned int sample_rate = 0;
FLAC__uint64 track_time = 0;
#ifdef HAVE_CUE /* libcue */
FLAC__StreamMetadata* vc;
#endif /* libcue */
FLAC__StreamMetadata* si = FLAC__metadata_object_new(FLAC__METADATA_TYPE_STREAMINFO);
FLAC__StreamMetadata* cs;
tnum = flac_vtrack_tnum(file);
char_tnum = g_strdup_printf("%u", tnum);
ptr = strrchr(file, '/');
*ptr = '\0';
#ifdef HAVE_CUE /* libcue */
if (FLAC__metadata_get_tags(file, &vc))
{
for (unsigned i = 0; i < vc->data.vorbis_comment.num_comments;
i++)
{
if ((ptr = (char*)vc->data.vorbis_comment.comments[i].entry) != NULL)
{
if (g_ascii_strncasecmp(ptr, "cuesheet", 8) == 0)
{
while (*(++ptr) != '=');
tag = cue_tag_string( ++ptr,
tnum);
}
}
}
FLAC__metadata_object_delete(vc);
}
#endif /* libcue */
if (tag == NULL)
tag = flac_tag_load(file, char_tnum);
if (char_tnum != NULL)
{
tag_add_item( tag,
TAG_ITEM_TRACK,
char_tnum);
g_free(char_tnum);
}
if (FLAC__metadata_get_streaminfo(file, si))
{
sample_rate = si->data.stream_info.sample_rate;
FLAC__metadata_object_delete(si);
}
if (FLAC__metadata_get_cuesheet(file, &cs))
{
if (cs->data.cue_sheet.tracks != NULL
&& (tnum <= cs->data.cue_sheet.num_tracks - 1))
{
track_time = cs->data.cue_sheet.tracks[tnum].offset
- cs->data.cue_sheet.tracks[tnum - 1].offset;
}
FLAC__metadata_object_delete(cs);
}
if (sample_rate != 0)
{
tag->time = (unsigned int)(track_time/sample_rate);
}
return tag;
}
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
static struct tag *
flac_tag_dup(const char *file)
{
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
struct stat st;
if (stat(file, &st) < 0)
return flac_cue_tag_load(file);
else
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
return flac_tag_load(file, NULL);
}
static void
flac_decode_internal(struct decoder * decoder,
struct input_stream *input_stream,
bool is_ogg)
{
flac_decoder *flac_dec;
struct flac_data data;
enum decoder_command cmd;
const char *err = NULL;
if (!(flac_dec = flac_new()))
return;
flac_data_init(&data, decoder, input_stream);
data.tag = tag_new();
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
if(!FLAC__stream_decoder_set_metadata_respond(flac_dec, FLAC__METADATA_TYPE_VORBIS_COMMENT))
{
g_debug("Failed to set metadata respond\n");
}
#endif
if (is_ogg) {
if (!flac_ogg_init(flac_dec, flac_read_cb,
flac_seek_cb, flac_tell_cb,
flac_length_cb, flac_eof_cb,
flac_write_cb, flacMetadata,
flac_error_cb, (void *)&data)) {
err = "doing Ogg init()";
goto fail;
}
} else {
if (!flac_init(flac_dec, flac_read_cb,
flac_seek_cb, flac_tell_cb,
flac_length_cb, flac_eof_cb,
flac_write_cb, flacMetadata,
flac_error_cb, (void *)&data)) {
err = "doing init()";
goto fail;
}
}
if (!flac_process_metadata(flac_dec)) {
err = "problem reading metadata";
goto fail;
}
if (!audio_format_valid(&data.audio_format)) {
g_warning("Invalid audio format: %u:%u:%u\n",
data.audio_format.sample_rate,
data.audio_format.bits,
data.audio_format.channels);
goto fail;
}
decoder_initialized(decoder, &data.audio_format,
input_stream->seekable, data.total_time);
while (true) {
if (!tag_is_empty(data.tag)) {
cmd = decoder_tag(decoder, input_stream, data.tag);
tag_free(data.tag);
data.tag = tag_new();
} else
cmd = decoder_get_command(decoder);
if (cmd == DECODE_COMMAND_SEEK) {
FLAC__uint64 seek_sample = decoder_seek_where(decoder) *
data.audio_format.sample_rate + 0.5;
if (flac_seek_absolute(flac_dec, seek_sample)) {
data.time = ((float)seek_sample) /
data.audio_format.sample_rate;
data.position = 0;
decoder_command_finished(decoder);
} else
decoder_seek_error(decoder);
} else if (cmd == DECODE_COMMAND_STOP ||
flac_get_state(flac_dec) == flac_decoder_eof)
break;
if (!flac_process_single(flac_dec)) {
cmd = decoder_get_command(decoder);
if (cmd != DECODE_COMMAND_SEEK)
break;
}
}
if (cmd != DECODE_COMMAND_STOP) {
flacPrintErroredState(flac_get_state(flac_dec));
flac_finish(flac_dec);
}
fail:
if (data.replay_gain_info)
replay_gain_info_free(data.replay_gain_info);
tag_free(data.tag);
if (flac_dec)
flac_delete(flac_dec);
if (err)
g_warning("%s\n", err);
}
static void
flac_decode(struct decoder * decoder, struct input_stream *input_stream)
{
flac_decode_internal(decoder, input_stream, false);
}
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
/**
* @brief Decode a flac file with embedded cue sheets
* @param const char* fname filename on fs
*/
static void
flac_container_decode(struct decoder* decoder,
const char* fname,
bool is_ogg)
{
unsigned int tnum = 0;
FLAC__uint64 t_start = 0;
FLAC__uint64 t_end = 0;
FLAC__uint64 track_time = 0;
FLAC__StreamMetadata* cs = NULL;
flac_decoder *flac_dec;
struct flac_data data;
const char *err = NULL;
char* pathname = g_strdup(fname);
char* slash = strrchr(pathname, '/');
*slash = '\0';
tnum = flac_vtrack_tnum(fname);
cs = FLAC__metadata_object_new(FLAC__METADATA_TYPE_CUESHEET);
FLAC__metadata_get_cuesheet(pathname, &cs);
if (cs != NULL)
{
if (cs->data.cue_sheet.tracks != NULL
&& (tnum <= cs->data.cue_sheet.num_tracks - 1))
{
t_start = cs->data.cue_sheet.tracks[tnum - 1].offset;
t_end = cs->data.cue_sheet.tracks[tnum].offset;
track_time = cs->data.cue_sheet.tracks[tnum].offset
- cs->data.cue_sheet.tracks[tnum - 1].offset;
}
FLAC__metadata_object_delete(cs);
}
else
{
g_free(pathname);
return;
}
if (!(flac_dec = flac_new()))
{
g_free(pathname);
return;
}
flac_data_init(&data, decoder, NULL);
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
if(!FLAC__stream_decoder_set_metadata_respond(flac_dec, FLAC__METADATA_TYPE_VORBIS_COMMENT))
{
g_debug("Failed to set metadata respond\n");
}
#endif
if (is_ogg)
{
if (FLAC__stream_decoder_init_ogg_file( flac_dec,
pathname,
flac_write_cb,
flacMetadata,
flac_error_cb,
(void*) &data )
!= FLAC__STREAM_DECODER_INIT_STATUS_OK )
{
err = "doing Ogg init()";
goto fail;
}
}
else
{
if (FLAC__stream_decoder_init_file( flac_dec,
pathname,
flac_write_cb,
flacMetadata,
flac_error_cb,
(void*) &data )
!= FLAC__STREAM_DECODER_INIT_STATUS_OK )
{
err = "doing init()";
goto fail;
}
}
if (!flac_process_metadata(flac_dec))
{
err = "problem reading metadata";
goto fail;
}
if (!audio_format_valid(&data.audio_format))
{
g_warning("Invalid audio format: %u:%u:%u\n",
data.audio_format.sample_rate,
data.audio_format.bits,
data.audio_format.channels);
goto fail;
}
// set track time (order is important: after stream init)
data.total_time = ((float)track_time / (float)data.audio_format.sample_rate);
data.position = 0;
decoder_initialized(decoder, &data.audio_format,
true, data.total_time);
// seek to song start (order is important: after decoder init)
flac_seek_absolute(flac_dec, (FLAC__uint64)t_start);
while (true)
{
if (!flac_process_single(flac_dec))
break;
// we only need to break at the end of track if we are in "cue mode"
if (data.time >= data.total_time)
{
flacPrintErroredState(flac_get_state(flac_dec));
flac_finish(flac_dec);
}
if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK)
{
FLAC__uint64 seek_sample = t_start +
(decoder_seek_where(decoder) * data.audio_format.sample_rate);
if (seek_sample >= t_start && seek_sample <= t_end &&
flac_seek_absolute(flac_dec, (FLAC__uint64)seek_sample)) {
data.time = (float)(seek_sample - t_start) /
data.audio_format.sample_rate;
data.position = 0;
decoder_command_finished(decoder);
} else
decoder_seek_error(decoder);
}
else if (flac_get_state(flac_dec) == flac_decoder_eof)
break;
}
if (decoder_get_command(decoder) != DECODE_COMMAND_STOP)
{
flacPrintErroredState(flac_get_state(flac_dec));
flac_finish(flac_dec);
}
fail:
if (pathname)
g_free(pathname);
if (data.replay_gain_info)
replay_gain_info_free(data.replay_gain_info);
if (flac_dec)
flac_delete(flac_dec);
if (err)
g_warning("%s\n", err);
}
/**
* @brief Open a flac file for decoding
* @param const char* fname filename on fs
*/
static void
flac_filedecode_internal(struct decoder* decoder,
const char* fname,
bool is_ogg)
{
flac_decoder *flac_dec;
struct flac_data data;
const char *err = NULL;
unsigned int flac_err_state = 0;
if (!(flac_dec = flac_new()))
return;
flac_data_init(&data, decoder, NULL);
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
if(!FLAC__stream_decoder_set_metadata_respond(flac_dec, FLAC__METADATA_TYPE_VORBIS_COMMENT))
{
g_debug("Failed to set metadata respond\n");
}
#endif
if (is_ogg)
{
if ( (flac_err_state = FLAC__stream_decoder_init_ogg_file( flac_dec,
fname,
flac_write_cb,
flacMetadata,
flac_error_cb,
(void*) &data ))
== FLAC__STREAM_DECODER_INIT_STATUS_ERROR_OPENING_FILE)
{
flac_container_decode(decoder, fname, is_ogg);
}
else if (flac_err_state != FLAC__STREAM_DECODER_INIT_STATUS_OK)
{
err = "doing Ogg init()";
goto fail;
}
}
else
{
if ( (flac_err_state = FLAC__stream_decoder_init_file( flac_dec,
fname,
flac_write_cb,
flacMetadata,
flac_error_cb,
(void*) &data ))
== FLAC__STREAM_DECODER_INIT_STATUS_ERROR_OPENING_FILE)
{
flac_container_decode(decoder, fname, is_ogg);
}
else if (flac_err_state != FLAC__STREAM_DECODER_INIT_STATUS_OK)
{
err = "doing init()";
goto fail;
}
}
if (!flac_process_metadata(flac_dec))
{
err = "problem reading metadata";
goto fail;
}
if (!audio_format_valid(&data.audio_format))
{
g_warning("Invalid audio format: %u:%u:%u\n",
data.audio_format.sample_rate,
data.audio_format.bits,
data.audio_format.channels);
goto fail;
}
decoder_initialized(decoder, &data.audio_format,
true, data.total_time);
while (true)
{
if (!flac_process_single(flac_dec))
break;
if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK)
{
FLAC__uint64 seek_sample = decoder_seek_where(decoder) *
data.audio_format.sample_rate + 0.5;
if (flac_seek_absolute(flac_dec, seek_sample))
{
data.time = ((float)seek_sample) /
data.audio_format.sample_rate;
data.position = 0;
decoder_command_finished(decoder);
}
else
decoder_seek_error(decoder);
}
else if (flac_get_state(flac_dec) == flac_decoder_eof)
break;
}
if (decoder_get_command(decoder) != DECODE_COMMAND_STOP)
{
flacPrintErroredState(flac_get_state(flac_dec));
flac_finish(flac_dec);
}
fail:
if (data.replay_gain_info)
replay_gain_info_free(data.replay_gain_info);
if (flac_dec)
flac_delete(flac_dec);
if (err)
g_warning("%s\n", err);
}
/**
* @brief wrapper function for
* flac_filedecode_internal method
* for decoding without ogg
*/
static void
flac_filedecode(struct decoder *decoder, const char *fname)
{
struct stat st;
if (stat(fname, &st) < 0) {
flac_container_decode(decoder, fname, false);
} else
flac_filedecode_internal(decoder, fname, false);
}
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
#ifndef HAVE_OGGFLAC
static bool
oggflac_init(G_GNUC_UNUSED const struct config_param *param)
{
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
return !!FLAC_API_SUPPORTS_OGG_FLAC;
#else
/* disable oggflac when libflac is too old */
return false;
#endif
}
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
static struct tag *
oggflac_tag_dup(const char *file)
{
struct tag *ret = NULL;
FLAC__Metadata_Iterator *it;
FLAC__StreamMetadata *block;
FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new();
if (!(FLAC__metadata_chain_read_ogg(chain, file)))
goto out;
it = FLAC__metadata_iterator_new();
FLAC__metadata_iterator_init(it, chain);
ret = tag_new();
do {
if (!(block = FLAC__metadata_iterator_get_block(it)))
break;
if (block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) {
flac_vorbis_comments_to_tag(ret, NULL, block);
} else if (block->type == FLAC__METADATA_TYPE_STREAMINFO) {
ret->time = ((float)block->data.stream_info.
total_samples) /
block->data.stream_info.sample_rate + 0.5;
}
} while (FLAC__metadata_iterator_next(it));
FLAC__metadata_iterator_delete(it);
if (!tag_is_defined(ret)) {
tag_free(ret);
ret = NULL;
}
out:
FLAC__metadata_chain_delete(chain);
return ret;
}
static void
oggflac_decode(struct decoder *decoder, struct input_stream *input_stream)
{
if (ogg_stream_type_detect(input_stream) != FLAC)
return;
/* rewind the stream, because ogg_stream_type_detect() has
moved it */
input_stream_seek(input_stream, 0, SEEK_SET);
flac_decode_internal(decoder, input_stream, true);
}
static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL };
static const char *const oggflac_mime_types[] = {
"application/ogg",
"application/x-ogg",
"audio/ogg",
"audio/x-flac+ogg",
"audio/x-ogg",
NULL
};
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
const struct decoder_plugin oggflac_decoder_plugin = {
.name = "oggflac",
.init = oggflac_init,
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
.stream_decode = oggflac_decode,
.tag_dup = oggflac_tag_dup,
.suffixes = oggflac_suffixes,
.mime_types = oggflac_mime_types
#endif
};
#endif /* HAVE_OGGFLAC */
static const char *const flac_suffixes[] = { "flac", NULL };
static const char *const flac_mime_types[] = {
"application/flac",
"application/x-flac",
"audio/flac",
"audio/x-flac",
NULL
};
const struct decoder_plugin flac_decoder_plugin = {
.name = "flac",
.stream_decode = flac_decode,
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
.file_decode = flac_filedecode,
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
.tag_dup = flac_tag_dup,
.suffixes = flac_suffixes,
.mime_types = flac_mime_types,
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
.container_scan = flac_cue_track,
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
};

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2003-2010 The Music Player Daemon Project
* 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
@@ -26,10 +26,9 @@
*
*/
#include "config.h"
#include "decoder_api.h"
#include "timer.h"
#include "conf.h"
#include "../decoder_api.h"
#include "../timer.h"
#include "../conf.h"
#include <glib.h>
@@ -88,7 +87,7 @@ fluidsynth_file_decode(struct decoder *decoder, const char *path_fs)
{
static const struct audio_format audio_format = {
.sample_rate = 48000,
.format = SAMPLE_FORMAT_S16,
.bits = 16,
.channels = 2,
};
char setting_sample_rate[] = "synth.sample-rate";
@@ -204,7 +203,7 @@ fluidsynth_file_decode(struct decoder *decoder, const char *path_fs)
break;
cmd = decoder_data(decoder, NULL, buffer, sizeof(buffer),
0);
0, 0, NULL);
} while (cmd == DECODE_COMMAND_NONE);
/* clean up */

Some files were not shown because too many files have changed in this diff Show More