Commit c38e9e6d authored by Alexey Morsov's avatar Alexey Morsov

Merge commit 'v0.17.0-git050212' into alt

parents 41bd98a5 e15b4f40
......@@ -34,11 +34,13 @@ missing
mkinstalldirs
mpd
mpd.exe
mpd.service
stamp-h1
tags
*~
.#*
.stgit*
src/dsd2pcm/dsd2pcm
doc/doxygen.conf
doc/protocol.html
doc/protocol
......@@ -62,3 +64,6 @@ test/tmp
test/run_inotify
test/test_queue_priority
test/run_ntp_server
test/run_resolver
test/run_tcp_connect
test/test_pcm
ACLOCAL_AMFLAGS = -I m4
AUTOMAKE_OPTIONS = foreign 1.10 dist-bzip2 subdir-objects
AUTOMAKE_OPTIONS = foreign 1.11 dist-bzip2 subdir-objects
AM_CPPFLAGS += -I$(srcdir)/src $(GLIB_CFLAGS)
......@@ -7,31 +7,33 @@ AM_CPPFLAGS += -DSYSTEM_CONFIG_FILE_LOCATION='"$(sysconfdir)/mpd.conf"'
bin_PROGRAMS = src/mpd
noinst_LIBRARIES =
noinst_LIBRARIES = \
libpcm.a \
libtag.a \
libinput.a \
libplaylist_plugins.a \
libdecoder_plugins.a \
libfilter_plugins.a \
libmixer_plugins.a \
liboutput_plugins.a
src_mpd_CFLAGS = $(AM_CFLAGS) $(MPD_CFLAGS)
src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \
$(AVAHI_CFLAGS) \
$(LIBWRAP_CFLAGS) \
$(SQLITE_CFLAGS) \
$(ARCHIVE_CFLAGS) \
$(INPUT_CFLAGS) \
$(TAG_CFLAGS) \
$(DECODER_CFLAGS) \
$(ENCODER_CFLAGS) \
$(FILTER_CFLAGS) \
$(OUTPUT_CFLAGS)
src_mpd_LDADD = $(MPD_LIBS) \
$(SQLITE_CFLAGS)
src_mpd_LDADD = \
$(PLAYLIST_LIBS) \
$(AVAHI_LIBS) \
$(LIBWRAP_LDFLAGS) \
$(SQLITE_LIBS) \
$(ARCHIVE_LIBS) \
$(DECODER_LIBS) \
$(INPUT_LIBS) \
$(ARCHIVE_LIBS) \
$(TAG_LIBS) \
$(DECODER_LIBS) \
$(ENCODER_LIBS) \
$(OUTPUT_LIBS) \
$(FILTER_LIBS) \
$(ENCODER_LIBS) \
$(MIXER_LIBS) \
$(GLIB_LIBS)
mpd_headers = \
......@@ -39,7 +41,6 @@ mpd_headers = \
src/notify.h \
src/ack.h \
src/ape.h \
src/audio.h \
src/audio_format.h \
src/audio_check.h \
src/audio_parser.h \
......@@ -83,14 +84,12 @@ mpd_headers = \
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 \
......@@ -144,19 +143,6 @@ mpd_headers = \
src/output/httpd_client.h \
src/output/httpd_internal.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 \
src/player_control.h \
......@@ -199,7 +185,7 @@ mpd_headers = \
src/song_print.h \
src/song_save.h \
src/song_sticker.h \
src/songvec.h \
src/song_sort.c src/song_sort.h \
src/socket_util.h \
src/state_file.h \
src/stats.h \
......@@ -237,18 +223,14 @@ mpd_headers = \
src_mpd_SOURCES = \
$(mpd_headers) \
$(ARCHIVE_SRC) \
$(INPUT_SRC) \
$(PLAYLIST_SRC) \
$(TAG_SRC) \
$(DECODER_SRC) \
$(ENCODER_SRC) \
$(OUTPUT_API_SRC) $(OUTPUT_SRC) \
$(MIXER_API_SRC) $(MIXER_SRC) \
$(FILTER_SRC) \
$(OUTPUT_API_SRC) \
$(MIXER_API_SRC) \
src/util/list.h \
src/util/list_sort.c src/util/list_sort.h \
src/glib_socket.h \
src/notify.c \
src/audio.c \
src/audio_config.c src/audio_config.h \
src/audio_check.c \
src/audio_format.c \
src/audio_parser.c \
......@@ -267,16 +249,17 @@ src_mpd_SOURCES = \
src/directory_save.c \
src/database.c \
src/db_error.h \
src/db_lock.c src/db_lock.h \
src/db_save.c src/db_save.h \
src/db_print.c src/db_print.h \
src/db_plugin.h \
src/db_visitor.h \
src/db_selection.h \
src/db/simple_db_plugin.c src/db/simple_db_plugin.h \
src/dirvec.c \
src/exclude.c \
src/fd_util.c \
src/fifo_buffer.c \
src/fifo_buffer.c src/fifo_buffer.h \
src/growing_fifo.c src/growing_fifo.h \
src/filter_config.c \
src/filter_plugin.c \
src/filter_registry.c \
......@@ -299,6 +282,7 @@ src_mpd_SOURCES = \
src/client_message.c \
src/client_subscribe.h \
src/client_subscribe.c \
src/tcp_connect.c src/tcp_connect.h \
src/tcp_socket.c src/tcp_socket.h \
src/udp_server.c src/udp_server.h \
src/server_socket.c \
......@@ -317,16 +301,6 @@ src_mpd_SOURCES = \
src/path.c \
src/mapper.c \
src/page.c \
src/pcm_convert.c \
src/pcm_volume.c \
src/pcm_mix.c \
src/pcm_byteswap.c \
src/pcm_channels.c \
src/pcm_pack.c \
src/pcm_format.c \
src/pcm_resample.c \
src/pcm_resample_fallback.c \
src/pcm_dither.c \
src/permission.c \
src/player_thread.c \
src/player_control.c \
......@@ -353,7 +327,6 @@ src_mpd_SOURCES = \
src/song_update.c \
src/song_print.c \
src/song_save.c \
src/songvec.c \
src/resolver.c src/resolver.h \
src/socket_util.c \
src/state_file.c \
......@@ -393,69 +366,104 @@ src_mpd_SOURCES += \
src/song_sticker.c
endif
FILTER_CFLAGS = \
# PCM library
libpcm_a_SOURCES = \
src/pcm_buffer.c src/pcm_buffer.h \
src/pcm_convert.c src/pcm_convert.h \
src/pcm_volume.c src/pcm_volume.h \
src/pcm_mix.c src/pcm_mix.h \
src/pcm_byteswap.c src/pcm_byteswap.h \
src/pcm_channels.c src/pcm_channels.h \
src/pcm_pack.c src/pcm_pack.h \
src/pcm_format.c src/pcm_format.h \
src/pcm_resample.c src/pcm_resample.h \
src/pcm_resample_fallback.c \
src/pcm_resample_internal.h \
src/pcm_dither.c src/pcm_dither.h \
src/pcm_prng.h \
src/pcm_utils.h
libpcm_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(SAMPLERATE_CFLAGS)
FILTER_LIBS = \
PCM_LIBS = \
libpcm.a \
$(SAMPLERATE_LIBS)
if HAVE_LIBSAMPLERATE
src_mpd_SOURCES += src/pcm_resample_libsamplerate.c
libpcm_a_SOURCES += src/pcm_resample_libsamplerate.c
endif
# archive plugins
ARCHIVE_CFLAGS = \
if ENABLE_ARCHIVE
noinst_LIBRARIES += libarchive.a
libarchive_a_SOURCES = \
src/archive_api.c \
src/archive_list.c \
src/archive_plugin.c \
src/input/archive_input_plugin.c
libarchive_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(BZ2_CFLAGS) \
$(ISO9660_CFLAGS) \
$(ZZIP_CFLAGS)
ARCHIVE_LIBS = \
libarchive.a \
$(BZ2_LIBS) \
$(ISO9660_LIBS) \
$(ZZIP_LIBS)
ARCHIVE_SRC =
if HAVE_BZ2
ARCHIVE_SRC += src/archive/bz2_archive_plugin.c
libarchive_a_SOURCES += src/archive/bz2_archive_plugin.c
endif
if HAVE_ZZIP
ARCHIVE_SRC += src/archive/zzip_archive_plugin.c
libarchive_a_SOURCES += src/archive/zzip_archive_plugin.c
endif
if HAVE_ISO9660
ARCHIVE_SRC += src/archive/iso9660_archive_plugin.c
libarchive_a_SOURCES += src/archive/iso9660_archive_plugin.c
endif
if ENABLE_ARCHIVE
ARCHIVE_SRC += \
src/archive_api.c \
src/archive_list.c \
src/archive_plugin.c \
src/input/archive_input_plugin.c
else
ARCHIVE_LIBS =
endif
# tag plugins
TAG_CFLAGS = \
libtag_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(ID3TAG_CFLAGS)
TAG_LIBS = \
libtag.a \
$(ID3TAG_LIBS)
TAG_SRC = \
libtag_a_SOURCES =\
src/ape.c \
src/replay_gain_ape.c \
src/tag_ape.c
if HAVE_ID3TAG
TAG_SRC += src/tag_id3.c \
libtag_a_SOURCES += \
src/tag_id3.c \
src/tag_rva2.c \
src/riff.c src/aiff.c
endif
# decoder plugins
DECODER_CFLAGS = \
libdecoder_plugins_a_SOURCES = \
src/decoder/pcm_decoder_plugin.c \
src/decoder/dsdiff_decoder_plugin.c \
src/decoder/dsdiff_decoder_plugin.h \
src/dsd2pcm/dsd2pcm.c src/dsd2pcm/dsd2pcm.h \
src/decoder_buffer.c \
src/decoder_plugin.c \
src/decoder_list.c
libdecoder_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(VORBIS_CFLAGS) $(TREMOR_CFLAGS) \
$(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \
$(SNDFILE_CFLAGS) \
......@@ -469,9 +477,12 @@ DECODER_CFLAGS = \
$(MAD_CFLAGS) \
$(MPG123_CFLAGS) \
$(FFMPEG_CFLAGS) \
$(MPCDEC_CFLAGS) \
$(FAAD_CFLAGS) \
$(CUE_CFLAGS)
DECODER_LIBS = \
libdecoder_plugins.a \
$(VORBIS_LIBS) $(TREMOR_LIBS) \
$(FLAC_LIBS) \
$(SNDFILE_LIBS) \
......@@ -485,63 +496,61 @@ DECODER_LIBS = \
$(MPG123_LIBS) \
$(MP4FF_LIBS) \
$(FFMPEG_LIBS) \
$(MPCDEC_LIBS) \
$(FAAD_LIBS) \
$(CUE_LIBS)
DECODER_SRC = \
src/decoder/pcm_decoder_plugin.c \
src/decoder_buffer.c \
src/decoder_plugin.c \
src/decoder_list.c
DECODER_SRC =
if HAVE_MAD
DECODER_SRC += src/decoder/mad_decoder_plugin.c
libdecoder_plugins_a_SOURCES += src/decoder/mad_decoder_plugin.c
endif
if HAVE_MPG123
DECODER_SRC += src/decoder/mpg123_decoder_plugin.c
libdecoder_plugins_a_SOURCES += src/decoder/mpg123_decoder_plugin.c
endif
if HAVE_MPCDEC
DECODER_SRC += src/decoder/mpcdec_decoder_plugin.c
libdecoder_plugins_a_SOURCES += src/decoder/mpcdec_decoder_plugin.c
endif
if HAVE_WAVPACK
DECODER_SRC += src/decoder/wavpack_decoder_plugin.c
libdecoder_plugins_a_SOURCES += src/decoder/wavpack_decoder_plugin.c
endif
if HAVE_FAAD
DECODER_SRC += src/decoder/faad_decoder_plugin.c
libdecoder_plugins_a_SOURCES += src/decoder/faad_decoder_plugin.c
endif
if HAVE_MP4
DECODER_SRC += src/decoder/mp4ff_decoder_plugin.c
libdecoder_plugins_a_SOURCES += src/decoder/mp4ff_decoder_plugin.c
endif
if HAVE_OGG_COMMON
DECODER_SRC += src/decoder/_ogg_common.c
libdecoder_plugins_a_SOURCES += src/decoder/_ogg_common.c
endif
if HAVE_FLAC_COMMON
DECODER_SRC += \
libdecoder_plugins_a_SOURCES += \
src/decoder/flac_metadata.c \
src/decoder/flac_pcm.c \
src/decoder/_flac_common.c
endif
if ENABLE_VORBIS_DECODER
DECODER_SRC += src/decoder/vorbis_decoder_plugin.c
libdecoder_plugins_a_SOURCES += src/decoder/vorbis_decoder_plugin.c
endif
if HAVE_FLAC
DECODER_SRC += src/decoder/flac_decoder_plugin.c
libdecoder_plugins_a_SOURCES += src/decoder/flac_decoder_plugin.c
endif
if HAVE_AUDIOFILE
DECODER_SRC += src/decoder/audiofile_decoder_plugin.c
libdecoder_plugins_a_SOURCES += src/decoder/audiofile_decoder_plugin.c
endif
if ENABLE_MIKMOD_DECODER
DECODER_SRC += src/decoder/mikmod_decoder_plugin.c
libdecoder_plugins_a_SOURCES += src/decoder/mikmod_decoder_plugin.c
endif
if HAVE_MODPLUG
......@@ -553,68 +562,76 @@ DECODER_LIBS += libmodplug_decoder_plugin.a $(MODPLUG_LIBS)
endif
if ENABLE_SIDPLAY
DECODER_SRC += src/decoder/sidplay_decoder_plugin.cxx
libdecoder_plugins_a_SOURCES += src/decoder/sidplay_decoder_plugin.cxx
DECODER_SRC += src/dummy.cxx
endif
if ENABLE_FLUIDSYNTH
DECODER_SRC += src/decoder/fluidsynth_decoder_plugin.c
libdecoder_plugins_a_SOURCES += src/decoder/fluidsynth_decoder_plugin.c
endif
if ENABLE_WILDMIDI
DECODER_SRC += src/decoder/wildmidi_decoder_plugin.c
libdecoder_plugins_a_SOURCES += src/decoder/wildmidi_decoder_plugin.c
endif
if HAVE_FFMPEG
DECODER_SRC += src/decoder/ffmpeg_decoder_plugin.c
libdecoder_plugins_a_SOURCES += src/decoder/ffmpeg_decoder_plugin.c
endif
if ENABLE_SNDFILE
DECODER_SRC += src/decoder/sndfile_decoder_plugin.c
libdecoder_plugins_a_SOURCES += src/decoder/sndfile_decoder_plugin.c
endif
if HAVE_GME
DECODER_SRC += src/decoder/gme_decoder_plugin.c
libdecoder_plugins_a_SOURCES += src/decoder/gme_decoder_plugin.c
endif
# encoder plugins
ENCODER_CFLAGS = \
if ENABLE_ENCODER
noinst_LIBRARIES += libencoder_plugins.a
libencoder_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(LAME_CFLAGS) \
$(TWOLAME_CFLAGS) \
$(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \
$(VORBISENC_CFLAGS)
ENCODER_LIBS = \
libencoder_plugins.a \
$(LAME_LIBS) \
$(TWOLAME_LIBS) \
$(FLAC_LIBS) \
$(VORBISENC_LIBS)
ENCODER_SRC =
libencoder_plugins_a_SOURCES =
if ENABLE_ENCODER
ENCODER_SRC += src/encoder_list.c
ENCODER_SRC += src/encoder/null_encoder.c
libencoder_plugins_a_SOURCES += src/encoder_list.c
libencoder_plugins_a_SOURCES += src/encoder/null_encoder.c
if ENABLE_WAVE_ENCODER
ENCODER_SRC += src/encoder/wave_encoder.c
libencoder_plugins_a_SOURCES += src/encoder/wave_encoder.c
endif
if ENABLE_VORBIS_ENCODER
ENCODER_SRC += src/encoder/vorbis_encoder.c
libencoder_plugins_a_SOURCES += src/encoder/vorbis_encoder.c
endif
if ENABLE_LAME_ENCODER
ENCODER_SRC += src/encoder/lame_encoder.c
libencoder_plugins_a_SOURCES += src/encoder/lame_encoder.c
endif
if ENABLE_TWOLAME_ENCODER
ENCODER_SRC += src/encoder/twolame_encoder.c
libencoder_plugins_a_SOURCES += src/encoder/twolame_encoder.c
endif
if ENABLE_FLAC_ENCODER
ENCODER_SRC += src/encoder/flac_encoder.c
libencoder_plugins_a_SOURCES += src/encoder/flac_encoder.c
endif
else
ENCODER_LIBS =
endif
......@@ -630,65 +647,64 @@ src_mpd_SOURCES += src/zeroconf-bonjour.c
endif
endif
if HAVE_CUE
DECODER_SRC += src/cue/cue_tag.c
endif
#
# input plugins
#
INPUT_CFLAGS = \
libinput_a_SOURCES = \
src/input_init.c \
src/input_registry.c \
src/input_stream.c \
src/input_internal.c src/input_internal.h \
src/input/rewind_input_plugin.c \
src/input/file_input_plugin.c
libinput_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(CURL_CFLAGS) \
$(SOUP_CFLAGS) \
$(CDIO_PARANOIA_CFLAGS) \
$(FFMPEG_CFLAGS) \
$(DESPOTIFY_CFLAGS) \
$(MMS_CFLAGS)
INPUT_LIBS = \
libinput.a \
$(CURL_LIBS) \
$(SOUP_LIBS) \
$(CDIO_PARANOIA_LIBS) \
$(FFMPEG_LIBS) \
$(DESPOTIFY_LIBS) \
$(MMS_LIBS)
INPUT_SRC = \
src/input_init.c \
src/input_registry.c \
src/input_stream.c \
src/input_internal.c src/input_internal.h \
src/input/rewind_input_plugin.c \
src/input/file_input_plugin.c
if ENABLE_CURL
INPUT_SRC += src/input/curl_input_plugin.c \
libinput_a_SOURCES += src/input/curl_input_plugin.c \
src/icy_metadata.c
endif
if ENABLE_SOUP
INPUT_SRC += \
libinput_a_SOURCES += \
src/input/soup_input_plugin.c \
src/input/soup_input_plugin.h
endif
if ENABLE_CDIO_PARANOIA
INPUT_SRC += src/input/cdio_paranoia_input_plugin.c
libinput_a_SOURCES += src/input/cdio_paranoia_input_plugin.c
endif
if HAVE_FFMPEG
INPUT_SRC += src/input/ffmpeg_input_plugin.c
libinput_a_SOURCES += src/input/ffmpeg_input_plugin.c
endif
if ENABLE_MMS
INPUT_SRC += src/input/mms_input_plugin.c
libinput_a_SOURCES += src/input/mms_input_plugin.c
endif
if ENABLE_DESPOTIFY
INPUT_SRC += src/input/despotify_input_plugin.c
libinput_a_SOURCES += src/input/despotify_input_plugin.c
endif
OUTPUT_CFLAGS = \
liboutput_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(AO_CFLAGS) \
$(ALSA_CFLAGS) \
$(FFADO_CFLAGS) \
......@@ -699,6 +715,7 @@ OUTPUT_CFLAGS = \
$(SHOUT_CFLAGS)
OUTPUT_LIBS = \
liboutput_plugins.a \
$(LIBWRAP_LDFLAGS) \
$(AO_LIBS) \
$(ALSA_LIBS) \
......@@ -721,105 +738,112 @@ OUTPUT_API_SRC = \
src/output_finish.c \
src/output_init.c
OUTPUT_SRC = \
liboutput_plugins_a_SOURCES = \
src/output/null_output_plugin.c
MIXER_LIBS = \
libmixer_plugins.a \
$(PULSE_LIBS)
MIXER_API_SRC = \
src/mixer_control.c \
src/mixer_type.c \
src/mixer_all.c \
src/mixer_api.c
MIXER_SRC = \
libmixer_plugins_a_SOURCES = \
src/mixer/software_mixer_plugin.c
libmixer_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(ALSA_CFLAGS) \
$(PULSE_CFLAGS)
if HAVE_ALSA
OUTPUT_SRC += \
liboutput_plugins_a_SOURCES += \
src/output/alsa_output_plugin.c src/output/alsa_output_plugin.h
MIXER_SRC += src/mixer/alsa_mixer_plugin.c
libmixer_plugins_a_SOURCES += src/mixer/alsa_mixer_plugin.c
endif
if HAVE_ROAR
OUTPUT_SRC += \
liboutput_plugins_a_SOURCES += \
src/output/roar_output_plugin.c src/output/roar_output_plugin.h
MIXER_SRC += src/mixer/roar_mixer_plugin.c
libmixer_plugins_a_SOURCES += src/mixer/roar_mixer_plugin.c
endif
if ENABLE_FFADO_OUTPUT
OUTPUT_SRC += src/output/ffado_output_plugin.c
liboutput_plugins_a_SOURCES += src/output/ffado_output_plugin.c
endif
if HAVE_AO
OUTPUT_SRC += src/output/ao_output_plugin.c
liboutput_plugins_a_SOURCES += src/output/ao_output_plugin.c
endif
if HAVE_FIFO
OUTPUT_SRC += src/output/fifo_output_plugin.c
liboutput_plugins_a_SOURCES += src/output/fifo_output_plugin.c
endif
if ENABLE_PIPE_OUTPUT
OUTPUT_SRC += src/output/pipe_output_plugin.c
liboutput_plugins_a_SOURCES += src/output/pipe_output_plugin.c
endif
if HAVE_JACK
OUTPUT_SRC += src/output/jack_output_plugin.c
liboutput_plugins_a_SOURCES += src/output/jack_output_plugin.c
endif
if HAVE_MVP
OUTPUT_SRC += src/output/mvp_output_plugin.c
liboutput_plugins_a_SOURCES += src/output/mvp_output_plugin.c
endif
if HAVE_OSS
OUTPUT_SRC += src/output/oss_output_plugin.c
MIXER_SRC += src/mixer/oss_mixer_plugin.c
liboutput_plugins_a_SOURCES += src/output/oss_output_plugin.c
libmixer_plugins_a_SOURCES += src/mixer/oss_mixer_plugin.c
endif
if HAVE_OPENAL
OUTPUT_SRC += src/output/openal_output_plugin.c
liboutput_plugins_a_SOURCES += src/output/openal_output_plugin.c
endif
if HAVE_OSX
OUTPUT_SRC += src/output/osx_output_plugin.c
liboutput_plugins_a_SOURCES += src/output/osx_output_plugin.c
endif
if ENABLE_RAOP_OUTPUT
OUTPUT_SRC += \
liboutput_plugins_a_SOURCES += \
src/ntp_server.c src/ntp_server.h \
src/rtsp_client.c src/rtsp_client.h \
src/output/raop_output_plugin.c
MIXER_SRC += src/mixer/raop_mixer_plugin.c
libmixer_plugins_a_SOURCES += src/mixer/raop_mixer_plugin.c
OUTPUT_LIBS += $(OPENSSL_LIBS)
endif
if HAVE_PULSE
OUTPUT_SRC += \
liboutput_plugins_a_SOURCES += \
src/output/pulse_output_plugin.c src/output/pulse_output_plugin.h
MIXER_SRC += src/mixer/pulse_mixer_plugin.c
libmixer_plugins_a_SOURCES += src/mixer/pulse_mixer_plugin.c
endif
if HAVE_SHOUT
OUTPUT_SRC += src/output/shout_output_plugin.c
liboutput_plugins_a_SOURCES += src/output/shout_output_plugin.c
endif
if ENABLE_RECORDER_OUTPUT
OUTPUT_SRC += src/output/recorder_output_plugin.c
liboutput_plugins_a_SOURCES += src/output/recorder_output_plugin.c
endif
if ENABLE_HTTPD_OUTPUT
OUTPUT_SRC += \
liboutput_plugins_a_SOURCES += \
src/icy_server.c \
src/output/httpd_client.c \
src/output/httpd_output_plugin.c
endif
if ENABLE_SOLARIS_OUTPUT
OUTPUT_SRC += src/output/solaris_output_plugin.c
liboutput_plugins_a_SOURCES += src/output/solaris_output_plugin.c
endif
if ENABLE_WINMM_OUTPUT
OUTPUT_SRC += \
liboutput_plugins_a_SOURCES += \
src/output/winmm_output_plugin.c src/output/winmm_output_plugin.h
MIXER_SRC += src/mixer/winmm_mixer_plugin.c
libmixer_plugins_a_SOURCES += src/mixer/winmm_mixer_plugin.c
endif
......@@ -827,7 +851,7 @@ endif
# Playlist plugins
#
PLAYLIST_SRC = \
libplaylist_plugins_a_SOURCES = \
src/playlist/extm3u_playlist_plugin.c \
src/playlist/m3u_playlist_plugin.c \
src/playlist/pls_playlist_plugin.c \
......@@ -835,21 +859,29 @@ PLAYLIST_SRC = \
src/playlist/asx_playlist_plugin.c \
src/playlist/rss_playlist_plugin.c \
src/playlist_list.c
libplaylist_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \
$(CUE_CFLAGS)
PLAYLIST_LIBS = \
libplaylist_plugins.a \
$(FLAC_LIBS)
if ENABLE_LASTFM
PLAYLIST_SRC += src/playlist/lastfm_playlist_plugin.c
libplaylist_plugins_a_SOURCES += src/playlist/lastfm_playlist_plugin.c
endif
if ENABLE_DESPOTIFY
PLAYLIST_SRC += src/playlist/despotify_playlist_plugin.c
libplaylist_plugins_a_SOURCES += src/playlist/despotify_playlist_plugin.c
endif
if HAVE_CUE
PLAYLIST_SRC += src/playlist/cue_playlist_plugin.c
libplaylist_plugins_a_SOURCES += src/playlist/cue_playlist_plugin.c
libplaylist_plugins_a_SOURCES += src/cue/cue_tag.c
endif
if HAVE_FLAC
PLAYLIST_SRC += src/playlist/flac_playlist_plugin.c
libplaylist_plugins_a_SOURCES += src/playlist/flac_playlist_plugin.c
endif
......@@ -857,7 +889,7 @@ endif
# Filter plugins
#
FILTER_SRC = \
libfilter_plugins_a_SOURCES = \
src/filter/null_filter_plugin.c \
src/filter/chain_filter_plugin.c \
src/filter/autoconvert_filter_plugin.c \
......@@ -867,6 +899,19 @@ FILTER_SRC = \
src/filter/replay_gain_filter_plugin.c \
src/filter/volume_filter_plugin.c
FILTER_LIBS = \
libfilter_plugins.a \
$(PCM_LIBS)
#
# systemd unit
#
if HAVE_SYSTEMD
systemdsystemunit_DATA = \
mpd.service
endif
#
# Sparse code analysis
......@@ -896,6 +941,7 @@ sparse-check:
if ENABLE_TEST
C_TESTS = \
test/test_pcm \
test/test_queue_priority
TESTS = $(C_TESTS)
......@@ -903,6 +949,8 @@ TESTS = $(C_TESTS)
noinst_PROGRAMS = \
$(C_TESTS) \
test/read_conf \
test/run_resolver \
test/run_tcp_connect \
test/run_input \
test/dump_playlist \
test/run_decoder \
......@@ -919,39 +967,41 @@ if HAVE_ALSA
noinst_PROGRAMS += test/read_mixer
endif
test_read_conf_CPPFLAGS = $(AM_CPPFLAGS) \
$(GLIB_CFLAGS)
test_read_conf_LDADD = $(MPD_LIBS) \
test_read_conf_LDADD = \
$(GLIB_LIBS)
test_read_conf_SOURCES = test/read_conf.c \
src/conf.c src/tokenizer.c src/utils.c src/string_util.c
test_run_input_CPPFLAGS = $(AM_CPPFLAGS) \
$(ARCHIVE_CFLAGS) \
$(INPUT_CFLAGS)
test_run_input_LDADD = $(MPD_LIBS) \
$(ARCHIVE_LIBS) \
test_run_resolver_LDADD = \
$(GLIB_LIBS)
test_run_resolver_SOURCES = test/run_resolver.c \
src/resolver.c
test_run_tcp_connect_LDADD = \
$(GLIB_LIBS)
test_run_tcp_connect_SOURCES = test/run_tcp_connect.c \
src/io_thread.c src/io_thread.h \
src/fd_util.c \
src/resolver.c \
src/tcp_connect.c
test_run_input_LDADD = \
$(INPUT_LIBS) \
$(ARCHIVE_LIBS) \
$(GLIB_LIBS)
test_run_input_SOURCES = test/run_input.c \
test/stdbin.h \
src/io_thread.c src/io_thread.h \
src/conf.c src/tokenizer.c src/utils.c src/string_util.c\
src/tag.c src/tag_pool.c src/tag_save.c \
src/fd_util.c \
$(ARCHIVE_SRC) \
$(INPUT_SRC)
src/fd_util.c
test_dump_playlist_CPPFLAGS = $(AM_CPPFLAGS) \
$(CUE_CFLAGS) \
$(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \
$(ARCHIVE_CFLAGS) \
$(INPUT_CFLAGS)
test_dump_playlist_LDADD = $(MPD_LIBS) \
test_dump_playlist_LDADD = \
$(PLAYLIST_LIBS) \
$(CUE_LIBS) \
$(FLAC_LIBS) \
$(ARCHIVE_LIBS) \
$(INPUT_LIBS) \
$(ARCHIVE_LIBS) \
$(GLIB_LIBS)
test_dump_playlist_SOURCES = test/dump_playlist.c \
src/io_thread.c src/io_thread.h \
......@@ -959,14 +1009,7 @@ test_dump_playlist_SOURCES = test/dump_playlist.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
src/fd_util.c
if HAVE_FLAC
test_dump_playlist_SOURCES += \
......@@ -974,14 +1017,12 @@ test_dump_playlist_SOURCES += \
src/decoder/flac_metadata.c
endif
test_run_decoder_CPPFLAGS = $(AM_CPPFLAGS) \
$(TAG_CFLAGS) \
$(ARCHIVE_CFLAGS) \
$(INPUT_CFLAGS) $(DECODER_CFLAGS)
test_run_decoder_LDADD = $(MPD_LIBS) \
$(TAG_LIBS) \
test_run_decoder_LDADD = \
$(DECODER_LIBS) \
libpcm.a \
$(INPUT_LIBS) \
$(ARCHIVE_LIBS) \
$(INPUT_LIBS) $(DECODER_LIBS) \
$(TAG_LIBS) \
$(GLIB_LIBS)
test_run_decoder_SOURCES = test/run_decoder.c \
test/stdbin.h \
......@@ -999,14 +1040,12 @@ test_run_decoder_SOURCES = test/run_decoder.c \
$(TAG_SRC) \
$(DECODER_SRC)
test_read_tags_CPPFLAGS = $(AM_CPPFLAGS) \
$(TAG_CFLAGS) \
$(ARCHIVE_CFLAGS) \
$(INPUT_CFLAGS) $(DECODER_CFLAGS)
test_read_tags_LDADD = $(MPD_LIBS) \
$(TAG_LIBS) \
test_read_tags_LDADD = \
$(DECODER_LIBS) \
libpcm.a \
$(INPUT_LIBS) \
$(ARCHIVE_LIBS) \
$(INPUT_LIBS) $(DECODER_LIBS) \
$(TAG_LIBS) \
$(GLIB_LIBS)
test_read_tags_SOURCES = test/read_tags.c \
src/io_thread.c src/io_thread.h \
......@@ -1017,13 +1056,9 @@ test_read_tags_SOURCES = test/read_tags.c \
src/fd_util.c \
src/audio_check.c \
src/timer.c \
$(ARCHIVE_SRC) \
$(INPUT_SRC) \
$(TAG_SRC) \
$(DECODER_SRC)
test_run_ntp_server_CPPFLAGS = $(AM_CPPFLAGS)
test_run_ntp_server_LDADD = $(MPD_LIBS) \
test_run_ntp_server_LDADD = \
$(GLIB_LIBS)
test_run_ntp_server_SOURCES = test/run_ntp_server.c \
test/signals.c test/signals.h \
......@@ -1031,30 +1066,20 @@ test_run_ntp_server_SOURCES = test/run_ntp_server.c \
src/udp_server.c src/udp_server.h \
src/ntp_server.c src/ntp_server.h
test_run_filter_CPPFLAGS = $(AM_CPPFLAGS)
test_run_filter_LDADD = $(MPD_LIBS) \
$(SAMPLERATE_LIBS) \
test_run_filter_LDADD = \
$(FILTER_LIBS) \
$(GLIB_LIBS)
test_run_filter_SOURCES = test/run_filter.c \
test/stdbin.h \
src/filter_plugin.c \
src/filter_registry.c \
src/conf.c src/tokenizer.c src/utils.c src/string_util.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
src/AudioCompress/compress.c
if ENABLE_DESPOTIFY
test_read_tags_SOURCES += \
......@@ -1071,26 +1096,26 @@ if ENABLE_ENCODER
noinst_PROGRAMS += test/run_encoder
test_run_encoder_SOURCES = test/run_encoder.c \
test/stdbin.h \
src/fifo_buffer.c src/growing_fifo.c \
src/conf.c src/tokenizer.c \
src/utils.c src/string_util.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_CPPFLAGS = $(AM_CPPFLAGS) \
$(ENCODER_CFLAGS)
test_run_encoder_LDADD = $(MPD_LIBS) \
src/audio_parser.c
test_run_encoder_LDADD = \
$(ENCODER_LIBS) \
libpcm.a \
$(TAG_LIBS) \
$(GLIB_LIBS)
endif
test_software_volume_SOURCES = test/software_volume.c \
test/stdbin.h \
src/audio_check.c \
src/audio_parser.c \
src/pcm_volume.c
src/audio_parser.c
test_software_volume_LDADD = \
$(PCM_LIBS) \
$(GLIB_LIBS)
test_run_normalize_SOURCES = test/run_normalize.c \
......@@ -1105,31 +1130,16 @@ 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)
src/audio_parser.c
test_run_convert_LDADD = \
$(SAMPLERATE_LIBS) \
$(PCM_LIBS) \
$(GLIB_LIBS)
if HAVE_LIBSAMPLERATE
test_run_convert_SOURCES += src/pcm_resample_libsamplerate.c
endif
test_run_output_CFLAGS = $(AM_CFLAGS) $(MPD_CFLAGS)
test_run_output_CPPFLAGS = $(AM_CPPFLAGS) \
$(ENCODER_CFLAGS) \
$(OUTPUT_CFLAGS)
test_run_output_LDADD = $(MPD_LIBS) \
$(ENCODER_LIBS) \
$(OUTPUT_LIBS) \
$(ENCODER_LIBS) \
libmixer_plugins.a \
$(FILTER_LIBS) \
$(GLIB_LIBS)
test_run_output_SOURCES = test/run_output.c \
test/stdbin.h \
......@@ -1142,35 +1152,26 @@ test_run_output_SOURCES = test/run_output.c \
src/audio_parser.c \
src/timer.c \
src/tag.c src/tag_pool.c \
src/fifo_buffer.c \
src/fifo_buffer.c src/growing_fifo.c \
src/page.c \
src/socket_util.c \
src/resolver.c \
src/output_init.c src/output_finish.c src/output_list.c \
src/output_plugin.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_plugin.c \
src/filter_config.c \
src/filter/autoconvert_filter_plugin.c \
src/filter/convert_filter_plugin.c \
src/filter/replay_gain_filter_plugin.c \
src/filter/normalize_filter_plugin.c \
src/filter/volume_filter_plugin.c \
src/pcm_volume.c \
src/AudioCompress/compress.c \
src/replay_gain_info.c \
src/replay_gain_config.c \
src/fd_util.c \
src/server_socket.c \
$(OUTPUT_SRC)
src/server_socket.c
test_read_mixer_CPPFLAGS = $(AM_CPPFLAGS) \
$(OUTPUT_CFLAGS)
test_read_mixer_LDADD = $(MPD_LIBS) \
test_read_mixer_LDADD = \
libpcm.a \
libmixer_plugins.a \
$(OUTPUT_LIBS) \
$(GLIB_LIBS)
test_read_mixer_SOURCES = test/read_mixer.c \
......@@ -1178,8 +1179,7 @@ test_read_mixer_SOURCES = test/read_mixer.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)
src/fd_util.c
if ENABLE_BZIP2_TEST
TESTS += test/test_archive_bzip2.sh
......@@ -1202,12 +1202,33 @@ test_run_inotify_SOURCES = test/run_inotify.c \
test_run_inotify_LDADD = $(GLIB_LIBS)
endif
test_test_pcm_SOURCES = \
test/test_glib_compat.h \
test/test_pcm_dither.c \
test/test_pcm_pack.c \
test/test_pcm_channels.c \
test/test_pcm_byteswap.c \
test/test_pcm_all.h \
test/test_pcm_main.c
test_test_pcm_LDADD = \
$(PCM_LIBS) \
$(GLIB_LIBS)
test_test_queue_priority_SOURCES = \
src/queue.c \
test/test_queue_priority.c
test_test_queue_priority_LDADD = \
$(GLIB_LIBS)
if HAVE_CXX
noinst_PROGRAMS += src/dsd2pcm/dsd2pcm
src_dsd2pcm_dsd2pcm_SOURCES = \
src/dsd2pcm/dsd2pcm.c src/dsd2pcm/dsd2pcm.h \
src/dsd2pcm/noiseshape.c src/dsd2pcm/noiseshape.h \
src/dsd2pcm/main.cpp
endif
endif
......
......@@ -2,27 +2,66 @@ ver 0.17 (2011/??/??)
* protocol:
- support client-to-client communication
- "update" and "rescan" need only "CONTROL" permission
- new command "seekcur" for simpler seeking within current song
* input:
- cdio_paranoia: new input plugin to play audio CDs
- curl: enable CURLOPT_NETRC
- curl: non-blocking I/O
- soup: new input plugin based on libsoup
- ffmpeg: support libavformat 0.7
* decoder:
- mpg123: implement seeking
- ffmpeg: drop support for pre-0.5 ffmpeg
- ffmpeg: support libavformat 0.7
- oggflac: delete this obsolete plugin
- dsdiff: new decoder plugin
* output:
- httpd: support for streaming to a DLNA client
- openal: improve buffer cancellation
- osx: allow user to specify other audio devices
- osx: implement 32 bit playback
- raop: new output plugin
- shout: add possibility to set url
- roar: new output plugin for RoarAudio
- winmm: fail if wrong device specified instead of using default device
* mixer:
- alsa: listen for external volume changes
* playlist:
- allow references to songs outside the music directory
* state_file: add option "restore_paused"
* cue: show CUE track numbers
* allow port specification in "bind_to_address" settings
* support floating point samples
ver 0.16.7 (2012/02/04)
* input:
- ffmpeg: support libavformat 0.7
* decoder:
- ffmpeg: support libavformat 0.8, libavcodec 0.9
- ffmpeg: support all MPD tags
* output:
- httpd: fix excessive buffering
- openal: force 16 bit playback, as 8 bit doesn't work
- osx: remove sleep call from render callback
- osx: clear render buffer when there's not enough data
* fix moving after current song
ver 0.16.6 (2011/12/01)
* decoder:
- fix assertion failure when resuming streams
- ffmpeg: work around bogus channel count
* encoder:
- flac, null, wave: fix buffer corruption bug
- wave: support packed 24 bit samples
* mapper: fix the bogus "not a directory" error message
* mapper: check "x" and "r" permissions on music directory
* log: print reason for failure
* event_pipe: fix WIN32 regression
* define WINVER in ./configure
* WIN32: autodetect filesystem encoding
ver 0.16.5 (2010/??/??)
ver 0.16.5 (2011/10/09)
* configure.ac
- disable assertions in the non-debugging build
- show solaris plugin result correctly
......@@ -33,7 +72,17 @@ ver 0.16.5 (2010/??/??)
* decoder:
- ffmpeg: higher precision timestamps
- ffmpeg: don't require key frame for seeking
- fix CUE track seeking
* output:
- openal: auto-fallback to mono if channel count is unsupported
* player:
- make seeking to CUE track more reliable
- the "seek" command works when MPD is stopped
- restore song position from state file (bug fix)
- fix crash that sometimes occurred when audio device fails on startup
- fix absolute path support in playlists
* WIN32: close sockets properly
* install systemd service file if systemd is available
ver 0.16.4 (2011/09/01)
......
......@@ -16,7 +16,7 @@ if test -n "$AM_FORCE_VERSION"
then
AM_VERSIONS="$AM_FORCE_VERSION"
else
AM_VERSIONS='1.11 1.10'
AM_VERSIONS='1.11'
fi
if test -n "$AC_FORCE_VERSION"
then
......
AC_PREREQ(2.60)
AC_INIT(mpd, 0.17~git, musicpd-dev-team@lists.sourceforge.net)
AC_CONFIG_SRCDIR([src/main.c])
AM_INIT_AUTOMAKE([foreign 1.10 dist-bzip2 subdir-objects])
AM_CONFIG_HEADER(config.h)
AM_INIT_AUTOMAKE([foreign 1.11 dist-bzip2 subdir-objects])
AM_SILENT_RULES
AC_CONFIG_HEADERS(config.h)
AC_CONFIG_MACRO_DIR([m4])
AC_DEFINE(PROTOCOL_VERSION, "0.17.0", [The MPD protocol version])
......@@ -29,10 +30,26 @@ if test x$CXX = xg++; then
HAVE_CXX=no
fi
fi
AM_CONDITIONAL(HAVE_CXX, test x$HAVE_CXX = xyes)
AC_PROG_INSTALL
AC_PROG_MAKE_SET
PKG_PROG_PKG_CONFIG
AC_ARG_WITH([systemdsystemunitdir],
AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files]),
[], [with_systemdsystemunitdir=no])
if test "x$with_systemdsystemunitdir" = xyes; then
AC_MSG_CHECKING(for systemd)
with_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)
if test -z "$with_systemdsystemunitdir"; then
AC_MSG_ERROR([Failed to detect systemd])
fi
AC_MSG_RESULT([$with_systemdsystemunitdir])
fi
if test "x$with_systemdsystemunitdir" != xno; then
AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])
fi
AM_CONDITIONAL(HAVE_SYSTEMD, [test -n "$with_systemdsystemunitdir" -a "x$with_systemdsystemunitdir" != xno ])
dnl ---------------------------------------------------------------------------
dnl Declare Variables
......@@ -41,11 +58,6 @@ AC_SUBST(AM_CPPFLAGS,"")
AC_SUBST(AM_CFLAGS,"")
AC_SUBST(AM_CXXFLAGS,"")
AC_SUBST(MPD_LIBS)
AC_SUBST(MPD_CFLAGS)
MPD_LIBS=""
MPD_CFLAGS=""
dnl ---------------------------------------------------------------------------
dnl OS Specific Defaults
dnl ---------------------------------------------------------------------------
......@@ -53,7 +65,8 @@ AC_CANONICAL_HOST
case "$host_os" in
mingw32* | windows*)
MPD_LIBS="$MPD_LIBS -lws2_32"
AM_CPPFLAGS="$AM_CPPFLAGS -DWINVER=0x0501"
LIBS="$LIBS -lws2_32"
;;
esac
......@@ -96,22 +109,18 @@ fi
dnl ---------------------------------------------------------------------------
dnl Header/Library Checks
dnl ---------------------------------------------------------------------------
AC_CHECK_FUNCS(daemon fork syslog)
if test $ac_cv_func_syslog = no; then
# syslog is not in the default libraries. See if it's in some other.
for lib in bsd socket inet; do
AC_CHECK_LIB($lib, syslog,
[AC_DEFINE(HAVE_SYSLOG)
LIBS="$LIBS -l$lib"; break])
done
fi
AC_CHECK_FUNCS(daemon fork)
AC_CHECK_LIB(socket,socket,MPD_LIBS="$MPD_LIBS -lsocket",)
AC_CHECK_LIB(nsl,gethostbyname,MPD_LIBS="$MPD_LIBS -lnsl",)
AC_SEARCH_LIBS([syslog], [bsd socket inet],
[AC_DEFINE(HAVE_SYSLOG, 1, [Define if syslog() is available])])
AC_SEARCH_LIBS([socket], [socket])
AC_SEARCH_LIBS([gethostbyname], [nsl])
AC_CHECK_FUNCS(pipe2 accept4)
AC_CHECK_LIB(m,exp,MPD_LIBS="$MPD_LIBS -lm",)
AC_SEARCH_LIBS([exp], [m],,
[AC_MSG_ERROR([exp() not found])])
AC_CHECK_HEADERS(locale.h)
AC_CHECK_HEADERS(valgrind/memcheck.h)
......@@ -202,11 +211,6 @@ AC_ARG_ENABLE(gme,
[enable Blargg's game music emulator plugin]),,
enable_gme=auto)
AC_ARG_ENABLE(gprof,
AS_HELP_STRING([--enable-gprof],
[enable profiling via gprof (default: disabled)]),,
enable_gprof=no)
AC_ARG_ENABLE(httpd-output,
AS_HELP_STRING([--enable-httpd-output],
[enables the HTTP server output]),,
......@@ -219,7 +223,7 @@ AC_ARG_ENABLE(raop-output,
AC_ARG_ENABLE(id3,
AS_HELP_STRING([--enable-id3],
[disable id3 support]),,
[enable id3 support]),,
enable_id3=auto)
AC_ARG_ENABLE(inotify,
......@@ -436,6 +440,11 @@ dnl ---------------------------------------------------------------------------
PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.12 gthread-2.0],,
[AC_MSG_ERROR([GLib 2.12 is required])])
if test x$GCC = xyes; then
# suppress warnings in the GLib headers
GLIB_CFLAGS=`echo $GLIB_CFLAGS |sed -e 's,-I/,-isystem /,g'`
fi
dnl ---------------------------------------------------------------------------
dnl Protocol Options
dnl ---------------------------------------------------------------------------
......@@ -450,7 +459,11 @@ if test x$enable_ipv6 = xyes; then
AC_EGREP_CPP([AP_maGiC_VALUE],
[
#include <sys/types.h>
#ifdef WIN32
#include <winsock2.h>
#else
#include <sys/socket.h>
#endif
#include <netdb.h>
#ifdef PF_INET6
#ifdef AF_INET6
......@@ -580,8 +593,7 @@ if test x$with_zeroconf != xno; then
if test x$with_zeroconf = xbonjour || test x$with_zeroconf = xauto; then
AC_CHECK_HEADER(dns_sd.h,
[enable_bonjour=yes;AC_DEFINE([HAVE_BONJOUR], 1, [Define to enable Bonjour Zeroconf support])])
AC_CHECK_LIB(dns_sd, DNSServiceRegister,
MPD_LIBS="$MPD_LIBS -ldns_sd")
AC_CHECK_LIB([dns_sd], [DNSServiceRegister])
fi
if test x$enable_bonjour = xyes; then
......@@ -671,7 +683,6 @@ MPD_AUTO_PKG(despotify, DESPOTIFY, [despotify],
[Despotify support], [despotify not found])
if test x$enable_despotify = xyes; then
AC_DEFINE(ENABLE_DESPOTIFY, 1, [Define when despotify is enabled])
MPD_LIBS="$MPD_LIBS $DESPOTIFY_LIBS"
fi
AM_CONDITIONAL(ENABLE_DESPOTIFY, test x$enable_despotify = xyes)
......@@ -716,9 +727,10 @@ AM_CONDITIONAL(ENABLE_ISO9660_TEST, test x$MKISOFS != xno)
dnl ---------------------------------- libbz2 ---------------------------------
if test x$enable_bzip2 = xyes; then
AC_CHECK_LIB(bz2, BZ2_bzDecompressInit,
[MPD_LIBS="$MPD_LIBS -lbz2"],
[BZ2_LIBS="-lbz2"],
[AC_MSG_ERROR([libbz2 not found])])
fi
AC_SUBST(BZ2_LIBS)
AM_CONDITIONAL(HAVE_BZ2, test x$enable_bzip2 = xyes)
if test x$enable_bzip2 = xyes; then
......@@ -879,52 +891,32 @@ AM_CONDITIONAL(ENABLE_SNDFILE, test x$enable_sndfile = xyes)
dnl --------------------------------- musepack --------------------------------
if test x$enable_mpc = xyes; then
if test "x$mpcdec_libraries" != "x" ; then
MPCDEC_LIBS="-L$mpcdec_libraries"
elif test "x$mpcdec_prefix" != "x" ; then
MPCDEC_LIBS="-L$mpcdec_prefix/lib"
fi
MPCDEC_LIBS="$MPCDEC_LIBS -lmpcdec"
if test "x$mpcdec_includes" != "x" ; then
MPCDEC_CFLAGS="-I$mpcdec_includes"
elif test "x$mpcdec_prefix" != "x" ; then
MPCDEC_CFLAGS="-I$mpcdec_prefix/include"
fi
oldcflags=$CFLAGS
oldlibs=$LIBS
oldcppflags=$CPPFLAGS
CFLAGS="$CFLAGS $MPD_CFLAGS $MPCDEC_CFLAGS -I."
LIBS="$LIBS $MPD_LIBS $MPCDEC_LIBS"
CPPFLAGS=$CFLAGS
AC_CHECK_HEADER(mpc/mpcdec.h,
old_mpcdec=no,
[AC_CHECK_HEADER(mpcdec/mpcdec.h,
old_mpcdec=yes,
enable_mpc=no)])
if test x$enable_mpc = xyes; then
AC_CHECK_LIB(mpcdec,main,
[MPD_LIBS="$MPD_LIBS $MPCDEC_LIBS";
MPD_CFLAGS="$MPD_CFLAGS $MPCDEC_CFLAGS";],
AC_CHECK_LIB(mpcdec,main,
MPCDEC_LIBS="$MPCDEC_LIBS -lmpcdec",
enable_mpc=no)
fi
CFLAGS=$oldcflags
LIBS=$oldlibs
CPPFLAGS=$oldcppflags
if test x$enable_mpc = xyes; then
AC_DEFINE(HAVE_MPCDEC,1,
[Define to use libmpcdec for MPC decoding])
if test x$old_mpcdec = xyes; then
AC_DEFINE(MPC_IS_OLD_API, 1,
[Define if an old pre-SV8 libmpcdec is used])
fi
AC_CHECK_HEADER([mpc/mpcdec.h],
[AC_DEFINE(HAVE_MPCDEC,1,
[Define to use libmpcdec for MPC decoding])],
[AC_CHECK_HEADER(mpcdec/mpcdec.h,
[AC_DEFINE(MPC_IS_OLD_API, 1,
[Define if an old pre-SV8 libmpcdec is used])]
)]
)
else
AC_MSG_WARN([mpcdec lib needed for MPC support -- disabling MPC support])
fi
CFLAGS=$oldcflags
LIBS=$oldlibs
CPPFLAGS=$oldcppflags
fi
AC_SUBST(MPCDEC_LIBS)
AC_SUBST(MPCDEC_CFLAGS)
AM_CONDITIONAL(HAVE_MPCDEC, test x$enable_mpc = xyes)
dnl -------------------------------- Ogg Tremor -------------------------------
......@@ -993,19 +985,18 @@ MPD_AUTO_PRE(sidplay, [sidplay decoder plugin], [No C++ compiler found])
if test x$enable_sidplay != xno; then
# we're not using pkg-config here
# because libsidplay2's .pc file requires libtool
AC_HAVE_LIBRARY(sidplay2, [found_sidplay=yes], [found_sidplay=no])
AC_CHECK_LIB([sidplay2],[main],[found_sidplay=yes],[found_sidplay=no],[])
MPD_AUTO_PRE(sidplay, [sidplay decoder plugin],
[libsidplay2 not found])
fi
if test x$enable_sidplay != xno; then
# can't use AC_HAVE_LIBRARY here, because the dash in the
# library name triggers an autoconf bug
AC_CHECK_LIB(resid-builder, main,
AC_CHECK_LIB([resid-builder], [main],
[found_sidplay=yes], [found_sidplay=no])
if test x$found_sidplay = xyes; then
AC_HAVE_LIBRARY(sidutils,, [found_sidplay=no])
AC_CHECK_LIB([sidutils],[main],[],[found_sidplay=no],[])
fi
MPD_AUTO_RESULT(sidplay, [sidplay decoder plugin],
......@@ -1324,7 +1315,7 @@ enable_osx=no
case "$host_os" in
darwin*)
AC_DEFINE(HAVE_OSX, 1, [Define for compiling OS X support])
MPD_LIBS="$MPD_LIBS -framework AudioUnit -framework CoreAudio -framework CoreServices"
LIBS="$LIBS -framework AudioUnit -framework CoreAudio -framework CoreServices"
enable_osx=yes ;;
esac
......@@ -1418,7 +1409,7 @@ case "$host_os" in
mingw32* | windows*)
AC_DEFINE(ENABLE_WINMM_OUTPUT, 1, [Define to enable WinMM support])
enable_winmm_output=yes
MPD_LIBS="$MPD_LIBS -lwinmm"
LIBS="$LIBS -lwinmm"
;;
*)
......@@ -1500,12 +1491,6 @@ then
MPD_CHECK_FLAG([-pedantic])
fi
dnl ------------------------------ gprof profiler -----------------------------
if test "x$enable_gprof" = xyes; then
MPD_CFLAGS="$MPD_CFLAGS -pg"
MPD_LIBS="$MPD_LIBS -pg"
fi
dnl ---------------------------- warnings as errors ---------------------------
if test "x$enable_werror" = xyes; then
AM_CFLAGS="$AM_CFLAGS -Werror -pedantic-errors"
......@@ -1567,23 +1552,23 @@ results(id3,[ID3])
printf '\nPlayback support:\n\t'
results(alsa,ALSA)
results(roar,ROAR)
results(ffado,FFADO)
results(fifo,FIFO)
results(recorder_output,[File Recorder])
results(httpd_output,[HTTP Daemon])
results(raop_output, [RAOP])
results(jack,[JACK])
printf '\n\t'
results(ao,[libao])
results(mvp, [Media MVP])
results(oss,[OSS])
printf '\n\t'
results(openal,[OpenAL])
results(osx, [OS X])
results(pipe_output, [Pipeline])
printf '\n\t'
results(pulse, [PulseAudio])
results(mvp, [Media MVP])
results(raop_output, [RAOP])
results(roar,[ROAR])
results(shout, [SHOUTcast])
printf '\n\t'
results(solaris_output, [Solaris])
results(winmm_output, [WinMM])
......@@ -1600,12 +1585,12 @@ if
fi
printf '\nStreaming support:\n\t'
results(cdio_paranoia, [CDIO_PARANOIA])
results(curl,[CURL])
results(soup, [SOUP])
results(despotify,[Despotify])
results(lastfm,[Last.FM])
results(mms,[MMS])
results(cdio_paranoia, [CDIO_PARANOIA])
results(despotify,[Despotify])
results(soup, [SOUP])
printf '\n\n##########################################\n\n'
......@@ -1614,7 +1599,9 @@ echo 'Generating files needed for compilation'
dnl ---------------------------------------------------------------------------
dnl Generate files
dnl ---------------------------------------------------------------------------
AC_OUTPUT(Makefile)
AC_OUTPUT(doc/doxygen.conf)
AC_CONFIG_FILES(Makefile)
AC_CONFIG_FILES(doc/doxygen.conf)
AC_CONFIG_FILES(mpd.service)
AC_OUTPUT
echo 'MPD is ready for compilation, type "make" to begin.'
......@@ -481,7 +481,7 @@ FILE_VERSION_FILTER =
# The QUIET tag can be used to turn on/off the messages that are generated
# by doxygen. Possible values are YES and NO. If left blank NO is used.
QUIET = NO
QUIET = YES
# The WARNINGS tag can be used to turn on/off the warning messages that are
# generated by doxygen. Possible values are YES and NO. If left blank
......
......@@ -83,6 +83,10 @@ This specifies which address mpd binds to and listens on. Multiple
bind_to_address parameters may be specified. The default is "any", which binds
to all available addresses.
You can set a port that is different from the global port setting,
e.g. "localhost:6602". IPv6 addresses must be enclosed in square
brackets if you want to configure a port, e.g. "[::1]:6602".
To bind to a Unix domain socket, specify an absolute path. For a
system-wide MPD, we suggest the path "\fB/var/run/mpd/socket\fP".
.TP
......
......@@ -737,7 +737,11 @@
Sets the replay gain mode. One of
<parameter>off</parameter>,
<parameter>track</parameter>,
<parameter>album</parameter>.
<parameter>album</parameter>,
<parameter>auto</parameter><footnote
id="replay_gain_auto_since_0_16">
<simpara>added in MPD 0.16</simpara>
</footnote>.
</para>
<para>
Changing the mode during playback may take several
......@@ -874,6 +878,23 @@
</para>
</listitem>
</varlistentry>
<varlistentry id="command_seekcur">
<term>
<cmdsynopsis>
<command>seekcur</command>
<arg choice="req"><replaceable>TIME</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
<para>
Seeks to the position <varname>TIME</varname> within the
current song. If prefixed by '+' or '-', then the time
is relative to the current playing position.
</para>
</listitem>
</varlistentry>
<varlistentry id="command_stop">
<term>
<cmdsynopsis>
......@@ -1781,6 +1802,7 @@ OK
<term>
<cmdsynopsis>
<command>disableoutput</command>
<arg choice="req"><replaceable>ID</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
......@@ -1793,6 +1815,7 @@ OK
<term>
<cmdsynopsis>
<command>enableoutput</command>
<arg choice="req"><replaceable>ID</replaceable></arg>
</cmdsynopsis>
</term>
<listitem>
......
......@@ -332,7 +332,8 @@ cd mpd-version</programlisting>
<varname>24_3</varname> (signed 24 bit integer
samples, no padding, 3 bytes per sample),
<varname>32</varname> (signed 32 bit integer
samples).
samples), <varname>f</varname> (32 bit floating
point, -1.0 to 1.0).
</para>
</entry>
</row>
......@@ -737,6 +738,37 @@ cd mpd-version</programlisting>
<title>Decoder plugins</title>
<section>
<title><varname>dsdiff</varname></title>
<para>
Decodes DFF files containing DSDIFF data (e.g. SACD rips).
</para>
<informaltable>
<tgroup cols="2">
<thead>
<row>
<entry>Setting</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>
<varname>lsbitfirst</varname>
<parameter>yes|no</parameter>
</entry>
<entry>
Decode the least significant bit first. Default is
"no".
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</section>
<section>
<title><varname>mikmod</varname></title>
<para>
......
......@@ -39,8 +39,8 @@ if test x$enable_aac = xyes; then
oldcflags=$CFLAGS
oldlibs=$LIBS
oldcppflags=$CPPFLAGS
CFLAGS="$CFLAGS $MPD_CFLAGS $FAAD_CFLAGS -I."
LIBS="$LIBS $MPD_LIBS $FAAD_LIBS"
CFLAGS="$CFLAGS $FAAD_CFLAGS -I."
LIBS="$LIBS $FAAD_LIBS"
CPPFLAGS=$CFLAGS
AC_CHECK_HEADER(faad.h,,enable_aac=no)
if test x$enable_aac = xyes; then
......@@ -50,10 +50,10 @@ if test x$enable_aac = xyes; then
AC_CHECK_DECL(faacDecInit2,,enable_aac=no,[#include <faad.h>])
fi
if test x$enable_aac = xyes; then
AC_CHECK_LIB(faad,faacDecInit2,[MPD_LIBS="$MPD_LIBS $FAAD_LIBS";MPD_CFLAGS="$MPD_CFLAGS $FAAD_CFLAGS"],enable_aac=no)
AC_CHECK_LIB(faad,faacDecInit2,,enable_aac=no)
if test x$enable_aac = xno; then
enable_aac=yes
AC_CHECK_LIB(faad,NeAACDecInit2,[MPD_LIBS="$MPD_LIBS $FAAD_LIBS";MPD_CFLAGS="$MPD_CFLAGS $FAAD_CFLAGS"],enable_aac=no)
AC_CHECK_LIB(faad,NeAACDecInit2,,enable_aac=no)
fi
fi
if test x$enable_aac = xyes; then
......@@ -131,8 +131,8 @@ if test x$enable_aac = xyes; then
oldcflags=$CFLAGS
oldlibs=$LIBS
oldcppflags=$CPPFLAGS
CFLAGS="$CFLAGS $MPD_CFLAGS $FAAD_CFLAGS -Werror"
LIBS="$LIBS $MPD_LIBS $FAAD_LIBS"
CFLAGS="$CFLAGS $FAAD_CFLAGS -Werror"
LIBS="$LIBS $FAAD_LIBS"
CPPFLAGS=$CFLAGS
AC_MSG_CHECKING(for broken libfaad headers)
......@@ -188,5 +188,11 @@ if test x$enable_aac = xyes; then
CPPFLAGS=$oldcppflags
else
enable_mp4=no
FAAD_CFLAGS=""
FAAD_LIBS=""
fi
AC_SUBST(FAAD_CFLAGS)
AC_SUBST(FAAD_LIBS)
])
......@@ -20,7 +20,7 @@ AC_DEFUN([STRUCT_UCRED],[
mpd_cv_have_struct_ucred=no)
if test x$mpd_cv_have_struct_ucred = xyes; then
# :(
MPD_CFLAGS="$MPD_CFLAGS -D_GNU_SOURCE"
CFLAGS="$CFLAGS -D_GNU_SOURCE"
fi
fi
])
......
[Unit]
Description=Music Player Daemon
After=sound.target
[Service]
ExecStart=@prefix@/bin/mpd --no-daemon
[Install]
WantedBy=multi-user.target
......@@ -28,6 +28,7 @@
/**
* The GLib quark used for errors reported by this library.
*/
G_GNUC_CONST
static inline GQuark
audio_format_quark(void)
{
......
......@@ -18,7 +18,7 @@
*/
#include "config.h"
#include "audio.h"
#include "audio_config.h"
#include "audio_format.h"
#include "audio_parser.h"
#include "output_internal.h"
......
......@@ -17,8 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_AUDIO_H
#define MPD_AUDIO_H
#ifndef MPD_AUDIO_CONFIG_H
#define MPD_AUDIO_CONFIG_H
#include <stdbool.h>
......
......@@ -28,6 +28,25 @@
#define REVERSE_ENDIAN_SUFFIX "_be"
#endif
void
audio_format_mask_apply(struct audio_format *af,
const struct audio_format *mask)
{
assert(audio_format_valid(af));
assert(audio_format_mask_valid(mask));
if (mask->sample_rate != 0)
af->sample_rate = mask->sample_rate;
if (mask->format != SAMPLE_FORMAT_UNDEFINED)
af->format = mask->format;
if (mask->channels != 0)
af->channels = mask->channels;
assert(audio_format_valid(af));
}
const char *
sample_format_to_string(enum sample_format format)
{
......@@ -49,6 +68,9 @@ sample_format_to_string(enum sample_format format)
case SAMPLE_FORMAT_S32:
return "32";
case SAMPLE_FORMAT_FLOAT:
return "f";
}
/* unreachable */
......
......@@ -20,6 +20,7 @@
#ifndef MPD_AUDIO_FORMAT_H
#define MPD_AUDIO_FORMAT_H
#include <glib.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
......@@ -42,8 +43,16 @@ enum sample_format {
SAMPLE_FORMAT_S24_P32,
SAMPLE_FORMAT_S32,
/**
* 32 bit floating point samples in the host's format. The
* range is -1.0f to +1.0f.
*/
SAMPLE_FORMAT_FLOAT,
};
static const unsigned MAX_CHANNELS = 8;
/**
* This structure describes the format of a raw PCM stream.
*/
......@@ -72,7 +81,7 @@ struct audio_format {
* nonzero, then samples are stored in the reverse host byte
* order.
*/
uint8_t reverse_endian;
bool reverse_endian;
};
/**
......@@ -91,7 +100,7 @@ static inline void audio_format_clear(struct audio_format *af)
af->sample_rate = 0;
af->format = SAMPLE_FORMAT_UNDEFINED;
af->channels = 0;
af->reverse_endian = 0;
af->reverse_endian = false;
}
/**
......@@ -105,7 +114,7 @@ static inline void audio_format_init(struct audio_format *af,
af->sample_rate = sample_rate;
af->format = (uint8_t)format;
af->channels = channels;
af->reverse_endian = 0;
af->reverse_endian = false;
}
/**
......@@ -165,6 +174,7 @@ audio_valid_sample_format(enum sample_format format)
case SAMPLE_FORMAT_S24:
case SAMPLE_FORMAT_S24_P32:
case SAMPLE_FORMAT_S32:
case SAMPLE_FORMAT_FLOAT:
return true;
case SAMPLE_FORMAT_UNDEFINED:
......@@ -180,13 +190,14 @@ audio_valid_sample_format(enum sample_format format)
static inline bool
audio_valid_channel_count(unsigned channels)
{
return channels >= 1 && channels <= 8;
return channels >= 1 && channels <= MAX_CHANNELS;
}
/**
* Returns false if the format is not valid for playback with MPD.
* This function performs some basic validity checks.
*/
G_GNUC_PURE
static inline bool audio_format_valid(const struct audio_format *af)
{
return audio_valid_sample_rate(af->sample_rate) &&
......@@ -198,6 +209,7 @@ static inline bool audio_format_valid(const struct audio_format *af)
* Returns false if the format mask is not valid for playback with
* MPD. This function performs some basic validity checks.
*/
G_GNUC_PURE
static inline bool audio_format_mask_valid(const struct audio_format *af)
{
return (af->sample_rate == 0 ||
......@@ -216,31 +228,15 @@ static inline bool audio_format_equals(const struct audio_format *a,
a->reverse_endian == b->reverse_endian;
}
static inline void
void
audio_format_mask_apply(struct audio_format *af,
const struct audio_format *mask)
{
assert(audio_format_valid(af));
assert(audio_format_mask_valid(mask));
if (mask->sample_rate != 0)
af->sample_rate = mask->sample_rate;
const struct audio_format *mask);
if (mask->format != SAMPLE_FORMAT_UNDEFINED)
af->format = mask->format;
if (mask->channels != 0)
af->channels = mask->channels;
assert(audio_format_valid(af));
}
/**
* Returns the size of each (mono) sample in bytes.
*/
static inline unsigned audio_format_sample_size(const struct audio_format *af)
G_GNUC_CONST
static inline unsigned
sample_format_size(enum sample_format format)
{
switch (af->format) {
switch (format) {
case SAMPLE_FORMAT_S8:
return 1;
......@@ -252,18 +248,30 @@ static inline unsigned audio_format_sample_size(const struct audio_format *af)
case SAMPLE_FORMAT_S24_P32:
case SAMPLE_FORMAT_S32:
case SAMPLE_FORMAT_FLOAT:
return 4;
case SAMPLE_FORMAT_UNDEFINED:
break;
return 0;
}
assert(false);
return 0;
}
/**
* Returns the size of each (mono) sample in bytes.
*/
G_GNUC_PURE
static inline unsigned audio_format_sample_size(const struct audio_format *af)
{
return sample_format_size((enum sample_format)af->format);
}
/**
* Returns the size of each full frame in bytes.
*/
G_GNUC_PURE
static inline unsigned
audio_format_frame_size(const struct audio_format *af)
{
......@@ -274,6 +282,7 @@ audio_format_frame_size(const struct audio_format *af)
* Returns the floating point factor which converts a time span to a
* storage size in bytes.
*/
G_GNUC_PURE
static inline double audio_format_time_to_size(const struct audio_format *af)
{
return af->sample_rate * audio_format_frame_size(af);
......@@ -286,6 +295,7 @@ static inline double audio_format_time_to_size(const struct audio_format *af)
* @param format a #sample_format enum value
* @return the string
*/
G_GNUC_PURE G_GNUC_MALLOC
const char *
sample_format_to_string(enum sample_format format);
......@@ -297,6 +307,7 @@ sample_format_to_string(enum sample_format format);
* @param s a buffer to print into
* @return the string, or NULL if the #audio_format object is invalid
*/
G_GNUC_PURE G_GNUC_MALLOC
const char *
audio_format_to_string(const struct audio_format *af,
struct audio_format_string *s);
......
......@@ -81,6 +81,12 @@ parse_sample_format(const char *src, bool mask,
return true;
}
if (*src == 'f') {
*sample_format_r = SAMPLE_FORMAT_FLOAT;
*endptr_r = src + 1;
return true;
}
value = strtoul(src, &endptr, 10);
if (endptr == src) {
g_set_error(error_r, audio_parser_quark(), 0,
......
......@@ -59,7 +59,7 @@ void
client_message_copy(struct client_message *dest,
const struct client_message *src);
G_GNUC_MALLOC G_GNUC_PURE
G_GNUC_MALLOC
struct client_message *
client_message_dup(const struct client_message *src);
......
......@@ -194,8 +194,6 @@ parse_cmdline(int argc, char **argv, struct options *options,
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;;
......
......@@ -45,6 +45,7 @@
#include "db_error.h"
#include "db_print.h"
#include "db_selection.h"
#include "db_lock.h"
#include "tag.h"
#include "client.h"
#include "client_idle.h"
......@@ -1532,6 +1533,21 @@ handle_seekid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
}
static enum command_return
handle_seekcur(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
const char *p = argv[1];
bool relative = *p == '+' || *p == '-';
int seek_time;
if (!check_int(client, &seek_time, p, check_integer, p))
return COMMAND_RETURN_ERROR;
enum playlist_result result =
playlist_seek_current(&g_playlist, client->player_control,
seek_time, relative);
return print_playlist_result(client, result);
}
static enum command_return
handle_listallinfo(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
const char *directory = "";
......@@ -1877,8 +1893,10 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
.name = argv[4],
};
db_lock();
directory = db_get_directory(argv[3]);
if (directory == NULL) {
db_unlock();
command_error(client, ACK_ERROR_NO_EXIST,
"no such directory");
return COMMAND_RETURN_ERROR;
......@@ -1886,6 +1904,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
success = sticker_song_find(directory, data.name,
sticker_song_find_print_cb, &data);
db_unlock();
if (!success) {
command_error(client, ACK_ERROR_SYSTEM,
"failed to set search sticker database");
......@@ -2159,6 +2178,7 @@ static const struct command commands[] = {
{ "save", PERMISSION_CONTROL, 1, 1, handle_save },
{ "search", PERMISSION_READ, 2, -1, handle_search },
{ "seek", PERMISSION_CONTROL, 2, 2, handle_seek },
{ "seekcur", PERMISSION_CONTROL, 1, 1, handle_seekcur },
{ "seekid", PERMISSION_CONTROL, 2, 2, handle_seekid },
{ "sendmessage", PERMISSION_CONTROL, 2, 2, handle_send_message },
{ "setvol", PERMISSION_CONTROL, 1, 1, handle_setvol },
......
......@@ -19,7 +19,6 @@
#include "config.h"
#include "crossfade.h"
#include "pcm_mix.h"
#include "chunk.h"
#include "audio_format.h"
#include "tag.h"
......
......@@ -92,7 +92,9 @@ db_get_directory(const char *name)
if (name == NULL)
return music_root;
return directory_lookup_directory(music_root, name);
struct directory *directory =
directory_lookup_directory(music_root, name);
return directory;
}
struct song *
......
......@@ -50,6 +50,9 @@ db_finish(void);
struct directory *
db_get_root(void);
/**
* Caller must lock the #db_mutex.
*/
gcc_nonnull(1)
struct directory *
db_get_directory(const char *name);
......
......@@ -24,6 +24,7 @@
#include "db_selection.h"
#include "db_visitor.h"
#include "db_save.h"
#include "db_lock.h"
#include "conf.h"
#include "glib_compat.h"
#include "directory.h"
......@@ -58,7 +59,11 @@ simple_db_lookup_directory(const struct simple_db *db, const char *uri)
assert(db->root != NULL);
assert(uri != NULL);
return directory_lookup_directory(db->root, uri);
db_lock();
struct directory *directory =
directory_lookup_directory(db->root, uri);
db_unlock();
return directory;
}
static struct db *
......@@ -68,7 +73,7 @@ simple_db_init(const struct config_param *param, GError **error_r)
db_base_init(&db->base, &simple_db_plugin);
GError *error = NULL;
db->path = config_dup_block_path(param, "path", error_r);
db->path = config_dup_block_path(param, "path", &error);
if (db->path == NULL) {
g_free(db);
if (error != NULL)
......@@ -199,7 +204,7 @@ simple_db_open(struct db *_db, G_GNUC_UNUSED GError **error_r)
{
struct simple_db *db = (struct simple_db *)_db;
db->root = directory_new("", NULL);
db->root = directory_new_root();
db->mtime = 0;
GError *error = NULL;
......@@ -212,7 +217,7 @@ simple_db_open(struct db *_db, G_GNUC_UNUSED GError **error_r)
if (!simple_db_check(db, error_r))
return false;
db->root = directory_new("", NULL);
db->root = directory_new_root();
}
return true;
......@@ -235,7 +240,9 @@ simple_db_get_song(struct db *_db, const char *uri, GError **error_r)
assert(db->root != NULL);
db_lock();
struct song *song = directory_lookup_song(db->root, uri);
db_unlock();
if (song == NULL)
g_set_error(error_r, db_quark(), DB_NOT_FOUND,
"No such song: %s", uri);
......@@ -266,8 +273,11 @@ simple_db_visit(struct db *_db, const struct db_selection *selection,
!visitor->directory(directory, ctx, error_r))
return false;
return directory_walk(directory, selection->recursive,
visitor, ctx, error_r);
db_lock();
bool ret = directory_walk(directory, selection->recursive,
visitor, ctx, error_r);
db_unlock();
return ret;
}
const struct db_plugin simple_db_plugin = {
......@@ -297,13 +307,16 @@ simple_db_save(struct db *_db, GError **error_r)
struct simple_db *db = (struct simple_db *)_db;
struct directory *music_root = db->root;
db_lock();
g_debug("removing empty directories from DB");
directory_prune_empty(music_root);
g_debug("sorting DB");
directory_sort(music_root);
db_unlock();
g_debug("writing DB");
FILE *fp = fopen(db->path, "w");
......
......@@ -17,37 +17,11 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_DIRVEC_H
#define MPD_DIRVEC_H
#include "config.h"
#include "db_lock.h"
#include <stddef.h>
GStaticMutex db_mutex = G_STATIC_MUTEX_INIT;
struct dirvec {
struct directory **base;
size_t nr;
};
void dirvec_init(void);
void dirvec_deinit(void);
void dirvec_sort(struct dirvec *dv);
struct directory *dirvec_find(const struct dirvec *dv, const char *path);
int dirvec_delete(struct dirvec *dv, struct directory *del);
void dirvec_add(struct dirvec *dv, struct directory *add);
static inline void
dirvec_clear(struct dirvec *dv)
{
dv->nr = 0;
}
void dirvec_destroy(struct dirvec *dv);
int dirvec_for_each(const struct dirvec *dv,
int (*fn)(struct directory *, void *), void *arg);
#endif /* DIRVEC_H */
#ifndef NDEBUG
GThread *db_mutex_holder;
#endif
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/** \file
*
* Support for locking data structures from the database, for safe
* multi-threading.
*/
#ifndef MPD_DB_LOCK_H
#define MPD_DB_LOCK_H
#include "check.h"
#include <glib.h>
#include <assert.h>
#include <stdbool.h>
extern GStaticMutex db_mutex;
#ifndef NDEBUG
extern GThread *db_mutex_holder;
/**
* Does the current thread hold the database lock?
*/
G_GNUC_PURE
static inline bool
holding_db_lock(void)
{
return db_mutex_holder == g_thread_self();
}
#endif
/**
* Obtain the global database lock. This is needed before
* dereferencing a #song or #directory. It is not recursive.
*/
static inline void
db_lock(void)
{
assert(!holding_db_lock());
g_static_mutex_lock(&db_mutex);
assert(db_mutex_holder == NULL);
#ifndef NDEBUG
db_mutex_holder = g_thread_self();
#endif
}
/**
* Release the global database lock.
*/
static inline void
db_unlock(void)
{
assert(holding_db_lock());
#ifndef NDEBUG
db_mutex_holder = NULL;
#endif
g_static_mutex_unlock(&db_mutex);
}
#endif
......@@ -73,21 +73,35 @@ print_visitor_song_info(struct song *song, void *data,
return true;
}
static void
print_playlist_in_directory(struct client *client,
const struct directory *directory,
const char *name_utf8)
{
if (directory_is_root(directory))
client_printf(client, "playlist: %s\n", name_utf8);
else
client_printf(client, "playlist: %s/%s\n",
directory_get_path(directory), name_utf8);
}
static bool
print_visitor_playlist(const struct playlist_metadata *playlist, void *ctx,
print_visitor_playlist(const struct playlist_metadata *playlist,
const struct directory *directory, void *ctx,
G_GNUC_UNUSED GError **error_r)
{
struct client *client = ctx;
client_printf(client, "playlist: %s\n", playlist->name);
print_playlist_in_directory(client, directory, playlist->name);
return true;
}
static bool
print_visitor_playlist_info(const struct playlist_metadata *playlist,
const struct directory *directory,
void *ctx, G_GNUC_UNUSED GError **error_r)
{
struct client *client = ctx;
client_printf(client, "playlist: %s\n", playlist->name);
print_playlist_in_directory(client, directory, playlist->name);
#ifndef G_OS_WIN32
struct tm tm;
......
......@@ -19,6 +19,7 @@
#include "config.h"
#include "db_save.h"
#include "db_lock.h"
#include "directory.h"
#include "directory_save.h"
#include "song.h"
......@@ -169,7 +170,9 @@ db_load_internal(FILE *fp, struct directory *music_root, GError **error)
g_debug("reading DB");
db_lock();
success = directory_load(fp, music_root, buffer, error);
db_unlock();
g_string_free(buffer, true);
return success;
......
......@@ -43,9 +43,11 @@ struct db_visitor {
/**
* Visit a playlist. Optional method.
*
* @param directory the directory the playlist resides in
* @return true to continue the operation, false on error (set error_r)
*/
bool (*playlist)(const struct playlist_metadata *playlist, void *ctx,
bool (*playlist)(const struct playlist_metadata *playlist,
const struct directory *directory, void *ctx,
GError **error_r);
};
......
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/* \file
*
* This plugin decodes DSDIFF data (SACD) embedded in DFF files. It
* was modeled after the specification found here:
* http://www.sonicstudio.com/pdf/dsd/DSDIFF_1.5_Spec.pdf
*/
#include "config.h"
#include "dsdiff_decoder_plugin.h"
#include "decoder_api.h"
#include "audio_check.h"
#include "dsd2pcm/dsd2pcm.h"
#include <unistd.h>
#include <stdio.h> /* for SEEK_SET, SEEK_CUR */
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "dsdiff"
struct dsdiff_id {
char value[4];
};
struct dsdiff_header {
struct dsdiff_id id;
uint32_t size_high, size_low;
struct dsdiff_id format;
};
struct dsdiff_chunk_header {
struct dsdiff_id id;
uint32_t size_high, size_low;
};
struct dsdiff_metadata {
unsigned sample_rate, channels;
};
static bool lsbitfirst;
static bool
dsdiff_init(const struct config_param *param)
{
lsbitfirst = config_get_block_bool(param, "lsbitfirst", false);
return true;
}
static bool
dsdiff_id_equals(const struct dsdiff_id *id, const char *s)
{
assert(id != NULL);
assert(s != NULL);
assert(strlen(s) == sizeof(id->value));
return memcmp(id->value, s, sizeof(id->value)) == 0;
}
/**
* Read the "size" attribute from the specified header, converting it
* to the host byte order if needed.
*/
G_GNUC_CONST
static uint64_t
dsdiff_chunk_size(const struct dsdiff_chunk_header *header)
{
return (((uint64_t)GUINT32_FROM_BE(header->size_high)) << 32) |
((uint64_t)GUINT32_FROM_BE(header->size_low));
}
static bool
dsdiff_read(struct decoder *decoder, struct input_stream *is,
void *data, size_t length)
{
size_t nbytes = decoder_read(decoder, is, data, length);
return nbytes == length;
}
static bool
dsdiff_read_id(struct decoder *decoder, struct input_stream *is,
struct dsdiff_id *id)
{
return dsdiff_read(decoder, is, id, sizeof(*id));
}
static bool
dsdiff_read_chunk_header(struct decoder *decoder, struct input_stream *is,
struct dsdiff_chunk_header *header)
{
return dsdiff_read(decoder, is, header, sizeof(*header));
}
static bool
dsdiff_read_payload(struct decoder *decoder, struct input_stream *is,
const struct dsdiff_chunk_header *header,
void *data, size_t length)
{
uint64_t size = dsdiff_chunk_size(header);
if (size != (uint64_t)length)
return false;
size_t nbytes = decoder_read(decoder, is, data, length);
return nbytes == length;
}
/**
* Skip the #input_stream to the specified offset.
*/
static bool
dsdiff_skip_to(struct decoder *decoder, struct input_stream *is,
goffset offset)
{
if (is->seekable)
return input_stream_seek(is, offset, SEEK_SET, NULL);
if (is->offset > offset)
return false;
char buffer[8192];
while (is->offset < offset) {
size_t length = sizeof(buffer);
if (offset - is->offset < (goffset)length)
length = offset - is->offset;
size_t nbytes = decoder_read(decoder, is, buffer, length);
if (nbytes == 0)
return false;
}
assert(is->offset == offset);
return true;
}
/**
* Skip some bytes from the #input_stream.
*/
static bool
dsdiff_skip(struct decoder *decoder, struct input_stream *is,
goffset delta)
{
assert(delta >= 0);
if (delta == 0)
return true;
if (is->seekable)
return input_stream_seek(is, delta, SEEK_CUR, NULL);
char buffer[8192];
while (delta > 0) {
size_t length = sizeof(buffer);
if ((goffset)length > delta)
length = delta;
size_t nbytes = decoder_read(decoder, is, buffer, length);
if (nbytes == 0)
return false;
delta -= nbytes;
}
return true;
}
/**
* Read and parse a "SND" chunk inside "PROP".
*/
static bool
dsdiff_read_prop_snd(struct decoder *decoder, struct input_stream *is,
struct dsdiff_metadata *metadata,
goffset end_offset)
{
struct dsdiff_chunk_header header;
while ((goffset)(is->offset + sizeof(header)) <= end_offset) {
if (!dsdiff_read_chunk_header(decoder, is, &header))
return false;
goffset chunk_end_offset =
is->offset + dsdiff_chunk_size(&header);
if (chunk_end_offset > end_offset)
return false;
if (dsdiff_id_equals(&header.id, "FS ")) {
uint32_t sample_rate;
if (!dsdiff_read_payload(decoder, is, &header,
&sample_rate,
sizeof(sample_rate)))
return false;
metadata->sample_rate = GUINT32_FROM_BE(sample_rate);
} else if (dsdiff_id_equals(&header.id, "CHNL")) {
uint16_t channels;
if (dsdiff_chunk_size(&header) < sizeof(channels) ||
!dsdiff_read(decoder, is,
&channels, sizeof(channels)) ||
!dsdiff_skip_to(decoder, is, chunk_end_offset))
return false;
metadata->channels = GUINT16_FROM_BE(channels);
} else if (dsdiff_id_equals(&header.id, "CMPR")) {
struct dsdiff_id type;
if (dsdiff_chunk_size(&header) < sizeof(type) ||
!dsdiff_read(decoder, is,
&type, sizeof(type)) ||
!dsdiff_skip_to(decoder, is, chunk_end_offset))
return false;
if (!dsdiff_id_equals(&type, "DSD "))
/* only uincompressed DSD audio data
is implemented */
return false;
} else {
/* ignore unknown chunk */
if (!dsdiff_skip_to(decoder, is, chunk_end_offset))
return false;
}
}
return is->offset == end_offset;
}
/**
* Read and parse a "PROP" chunk.
*/
static bool
dsdiff_read_prop(struct decoder *decoder, struct input_stream *is,
struct dsdiff_metadata *metadata,
const struct dsdiff_chunk_header *prop_header)
{
uint64_t prop_size = dsdiff_chunk_size(prop_header);
goffset end_offset = is->offset + prop_size;
struct dsdiff_id prop_id;
if (prop_size < sizeof(prop_id) ||
!dsdiff_read_id(decoder, is, &prop_id))
return false;
if (dsdiff_id_equals(&prop_id, "SND "))
return dsdiff_read_prop_snd(decoder, is, metadata, end_offset);
else
/* ignore unknown PROP chunk */
return dsdiff_skip_to(decoder, is, end_offset);
}
/**
* Read and parse all metadata chunks at the beginning. Stop when the
* first "DSD" chunk is seen, and return its header in the
* "chunk_header" parameter.
*/
static bool
dsdiff_read_metadata(struct decoder *decoder, struct input_stream *is,
struct dsdiff_metadata *metadata,
struct dsdiff_chunk_header *chunk_header)
{
struct dsdiff_header header;
if (!dsdiff_read(decoder, is, &header, sizeof(header)) ||
!dsdiff_id_equals(&header.id, "FRM8") ||
!dsdiff_id_equals(&header.format, "DSD "))
return false;
while (true) {
if (!dsdiff_read_chunk_header(decoder, is, chunk_header))
return false;
if (dsdiff_id_equals(&chunk_header->id, "PROP")) {
if (!dsdiff_read_prop(decoder, is, metadata,
chunk_header))
return false;
} else if (dsdiff_id_equals(&chunk_header->id, "DSD ")) {
/* done with metadata */
return true;
} else {
/* ignore unknown chunk */
uint64_t chunk_size = dsdiff_chunk_size(chunk_header);
goffset chunk_end_offset = is->offset + chunk_size;
if (!dsdiff_skip_to(decoder, is, chunk_end_offset))
return false;
}
}
}
/**
* Decode one "DSD" chunk.
*/
static bool
dsdiff_decode_chunk(struct decoder *decoder, struct input_stream *is,
unsigned channels,
dsd2pcm_ctx **dsd2pcm, uint64_t chunk_size)
{
uint8_t buffer[8192];
const size_t sample_size = sizeof(buffer[0]);
const size_t frame_size = channels * sample_size;
const unsigned buffer_frames = sizeof(buffer) / frame_size;
const unsigned buffer_samples = buffer_frames * frame_size;
const size_t buffer_size = buffer_samples * sample_size;
float f_buffer[G_N_ELEMENTS(buffer)];
while (chunk_size > 0) {
/* see how much aligned data from the remaining chunk
fits into the local buffer */
unsigned now_frames = buffer_frames;
size_t now_size = buffer_size;
unsigned now_samples = buffer_samples;
if (chunk_size < (uint64_t)now_size) {
now_frames = (unsigned)chunk_size / frame_size;
now_size = now_frames * frame_size;
now_samples = now_frames * channels;
}
size_t nbytes = decoder_read(decoder, is, buffer, now_size);
if (nbytes != now_size)
return false;
chunk_size -= nbytes;
/* invoke the dsp2pcm library, once for each
channel */
for (unsigned c = 0; c < channels; ++c)
dsd2pcm_translate(dsd2pcm[c], now_frames,
buffer + c, channels,
lsbitfirst, f_buffer + c, channels);
/* convert to integer and submit to the decoder API */
enum decoder_command cmd =
decoder_data(decoder, is, f_buffer,
now_samples * sizeof(f_buffer[0]),
0);
switch (cmd) {
case DECODE_COMMAND_NONE:
break;
case DECODE_COMMAND_START:
case DECODE_COMMAND_STOP:
return false;
case DECODE_COMMAND_SEEK:
/* not implemented yet */
decoder_seek_error(decoder);
break;
}
}
return dsdiff_skip(decoder, is, chunk_size);
}
static void
dsdiff_stream_decode(struct decoder *decoder, struct input_stream *is)
{
struct dsdiff_metadata metadata = {
.sample_rate = 0,
.channels = 0,
};
struct dsdiff_chunk_header chunk_header;
if (!dsdiff_read_metadata(decoder, is, &metadata, &chunk_header))
return;
GError *error = NULL;
struct audio_format audio_format;
if (!audio_format_init_checked(&audio_format, metadata.sample_rate / 8,
SAMPLE_FORMAT_FLOAT,
metadata.channels, &error)) {
g_warning("%s", error->message);
g_error_free(error);
return;
}
/* initialize the dsd2pcm library */
dsd2pcm_ctx *dsd2pcm[MAX_CHANNELS];
for (unsigned i = 0; i < metadata.channels; ++i) {
dsd2pcm[i] = dsd2pcm_init();
if (dsd2pcm[i] == NULL) {
for (unsigned j = 0; j < i; ++j)
dsd2pcm_destroy(dsd2pcm[j]);
return;
}
}
/* success: file was recognized */
decoder_initialized(decoder, &audio_format, false, -1);
/* every iteration of the following loop decodes one "DSD"
chunk */
while (true) {
uint64_t chunk_size = dsdiff_chunk_size(&chunk_header);
if (dsdiff_id_equals(&chunk_header.id, "DSD ")) {
if (!dsdiff_decode_chunk(decoder, is,
metadata.channels,
dsd2pcm, chunk_size))
break;
} else {
/* ignore other chunks */
if (!dsdiff_skip(decoder, is, chunk_size))
break;
}
/* read next chunk header; the first one was read by
dsdiff_read_metadata() */
if (!dsdiff_read_chunk_header(decoder, is, &chunk_header))
break;
}
for (unsigned i = 0; i < metadata.channels; ++i)
dsd2pcm_destroy(dsd2pcm[i]);
}
static struct tag *
dsdiff_stream_tag(struct input_stream *is)
{
struct dsdiff_metadata metadata = {
.sample_rate = 0,
.channels = 0,
};
struct dsdiff_chunk_header chunk_header;
if (!dsdiff_read_metadata(NULL, is, &metadata, &chunk_header))
return NULL;
struct audio_format audio_format;
if (!audio_format_init_checked(&audio_format, metadata.sample_rate / 8,
SAMPLE_FORMAT_S24_P32,
metadata.channels, NULL))
/* refuse to parse files which we cannot play anyway */
return NULL;
/* no total time estimate, no tags implemented yet */
return tag_new();
}
static const char *const dsdiff_suffixes[] = {
"dff",
NULL
};
static const char *const dsdiff_mime_types[] = {
"application/x-dff",
NULL
};
const struct decoder_plugin dsdiff_decoder_plugin = {
.name = "dsdiff",
.init = dsdiff_init,
.stream_decode = dsdiff_stream_decode,
.stream_tag = dsdiff_stream_tag,
.suffixes = dsdiff_suffixes,
.mime_types = dsdiff_mime_types,
};
......@@ -17,35 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_SONGVEC_H
#define MPD_SONGVEC_H
#ifndef MPD_DECODER_DSDIFF_H
#define MPD_DECODER_DSDIFF_H
#include <stddef.h>
extern const struct decoder_plugin dsdiff_decoder_plugin;
struct songvec {
struct song **base;
size_t nr;
};
void songvec_init(void);
void songvec_deinit(void);
void songvec_sort(struct songvec *sv);
struct song *
songvec_find(const struct songvec *sv, const char *uri);
int
songvec_delete(struct songvec *sv, const struct song *del);
void
songvec_add(struct songvec *sv, struct song *add);
void songvec_destroy(struct songvec *sv);
int
songvec_for_each(const struct songvec *sv,
int (*fn)(struct song *, void *), void *arg);
#endif /* SONGVEC_H */
#endif
......@@ -35,7 +35,12 @@
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
#include <libavutil/avutil.h>
#include <libavutil/log.h>
#include <libavutil/mathematics.h>
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(51,5,0)
#include <libavutil/dict.h>
#endif
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "ffmpeg"
......@@ -196,6 +201,7 @@ ffmpeg_find_audio_stream(const AVFormatContext *format_context)
return -1;
}
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(53,25,0)
/**
* On some platforms, libavcodec wants the output buffer aligned to 16
* bytes (because it uses SSE/Altivec internally). This function
......@@ -210,6 +216,7 @@ align16(void *p, size_t *length_p)
*length_p -= add;
return (char *)p + add;
}
#endif
G_GNUC_CONST
static double
......@@ -229,6 +236,40 @@ time_to_ffmpeg(double t, const AVRational time_base)
time_base);
}
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,25,0)
/**
* Copy PCM data from a AVFrame to an interleaved buffer.
*/
static int
copy_interleave_frame(const AVCodecContext *codec_context,
const AVFrame *frame,
uint8_t *buffer, size_t buffer_size)
{
int plane_size;
const int data_size =
av_samples_get_buffer_size(&plane_size,
codec_context->channels,
frame->nb_samples,
codec_context->sample_fmt, 1);
if (buffer_size < (size_t)data_size)
/* buffer is too small - shouldn't happen */
return AVERROR(EINVAL);
if (av_sample_fmt_is_planar(codec_context->sample_fmt) &&
codec_context->channels > 1) {
for (int i = 0, channels = codec_context->channels;
i < channels; i++) {
memcpy(buffer, frame->extended_data[i], plane_size);
buffer += plane_size;
}
} else {
memcpy(buffer, frame->extended_data[0], data_size);
}
return data_size;
}
#endif
static enum decoder_command
ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
const AVPacket *packet,
......@@ -246,9 +287,15 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
int packet_size = packet->size;
#endif
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,25,0)
uint8_t aligned_buffer[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16];
const size_t buffer_size = sizeof(aligned_buffer);
#else
/* libavcodec < 0.8 needs an aligned buffer */
uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16];
size_t buffer_size = sizeof(audio_buf);
int16_t *aligned_buffer = align16(audio_buf, &buffer_size);
#endif
enum decoder_command cmd = DECODE_COMMAND_NONE;
while (
......@@ -259,7 +306,22 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
#endif
cmd == DECODE_COMMAND_NONE) {
int audio_size = buffer_size;
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0)
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,25,0)
AVFrame frame;
int got_frame = 0;
int len = avcodec_decode_audio4(codec_context,
&frame, &got_frame,
&packet2);
if (len >= 0 && got_frame) {
audio_size = copy_interleave_frame(codec_context,
&frame,
aligned_buffer,
buffer_size);
if (audio_size < 0)
len = audio_size;
} else if (len >= 0)
len = -1;
#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0)
int len = avcodec_decode_audio3(codec_context,
aligned_buffer, &audio_size,
&packet2);
......@@ -297,10 +359,18 @@ static enum sample_format
ffmpeg_sample_format(G_GNUC_UNUSED const AVCodecContext *codec_context)
{
switch (codec_context->sample_fmt) {
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 94, 1)
case AV_SAMPLE_FMT_S16:
#else
case SAMPLE_FMT_S16:
#endif
return SAMPLE_FORMAT_S16;
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 94, 1)
case AV_SAMPLE_FMT_S32:
#else
case SAMPLE_FMT_S32:
#endif
return SAMPLE_FORMAT_S32;
default:
......@@ -370,9 +440,19 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
return;
}
if (av_find_stream_info(format_context)<0) {
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,6,0)
const int find_result =
avformat_find_stream_info(format_context, NULL);
#else
const int find_result = av_find_stream_info(format_context);
#endif
if (find_result < 0) {
g_warning("Couldn't find stream info\n");
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
avformat_close_input(&format_context);
#else
av_close_input_stream(format_context);
#endif
mpd_ffmpeg_stream_close(stream);
return;
}
......@@ -380,7 +460,11 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
int audio_stream = ffmpeg_find_audio_stream(format_context);
if (audio_stream == -1) {
g_warning("No audio stream inside\n");
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
avformat_close_input(&format_context);
#else
av_close_input_stream(format_context);
#endif
mpd_ffmpeg_stream_close(stream);
return;
}
......@@ -395,14 +479,11 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
if (!codec) {
g_warning("Unsupported audio codec\n");
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
avformat_close_input(&format_context);
#else
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);
#endif
mpd_ffmpeg_stream_close(stream);
return;
}
......@@ -415,8 +496,32 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
codec_context->channels, &error)) {
g_warning("%s", error->message);
g_error_free(error);
avcodec_close(codec_context);
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
avformat_close_input(&format_context);
#else
av_close_input_stream(format_context);
#endif
mpd_ffmpeg_stream_close(stream);
return;
}
/* the audio format must be read from AVCodecContext by now,
because avcodec_open() has been demonstrated to fill bogus
values into AVCodecContext.channels - a change that will be
reverted later by avcodec_decode_audio3() */
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,6,0)
const int open_result = avcodec_open2(codec_context, codec, NULL);
#else
const int open_result = avcodec_open(codec_context, codec);
#endif
if (open_result < 0) {
g_warning("Could not open codec\n");
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
avformat_close_input(&format_context);
#else
av_close_input_stream(format_context);
#endif
mpd_ffmpeg_stream_close(stream);
return;
}
......@@ -460,7 +565,11 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
} while (cmd != DECODE_COMMAND_STOP);
avcodec_close(codec_context);
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
avformat_close_input(&format_context);
#else
av_close_input_stream(format_context);
#endif
mpd_ffmpeg_stream_close(stream);
}
......@@ -470,48 +579,44 @@ typedef struct ffmpeg_tag_map {
} 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
#if LIBAVFORMAT_VERSION_INT < ((52<<16)+(50<<8))
{ 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" },
/* sentinel */
{ TAG_NUM_OF_ITEM_TYPES, NULL }
};
static bool
ffmpeg_copy_metadata(struct tag *tag,
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,1,0)
AVDictionary *m,
#else
AVMetadata *m,
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53,1,0)
#define AVDictionary AVMetadata
#define AVDictionaryEntry AVMetadataTag
#define av_dict_get av_metadata_get
#endif
const ffmpeg_tag_map tag_map)
static void
ffmpeg_copy_metadata(struct tag *tag, enum tag_type type,
AVDictionary *m, const char *name)
{
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,1,0)
AVDictionaryEntry *mt = NULL;
while ((mt = av_dict_get(m, tag_map.name, mt, 0)) != NULL)
tag_add_item(tag, tag_map.type, mt->value);
#else
AVMetadataTag *mt = NULL;
while ((mt = av_dict_get(m, name, mt, 0)) != NULL)
tag_add_item(tag, type, mt->value);
}
while ((mt = av_metadata_get(m, tag_map.name, mt, 0)) != NULL)
tag_add_item(tag, tag_map.type, mt->value);
#endif
static void
ffmpeg_copy_dictionary(struct tag *tag, AVDictionary *dict)
{
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
ffmpeg_copy_metadata(tag, i,
dict, tag_item_names[i]);
return mt != NULL;
for (const struct ffmpeg_tag_map *i = ffmpeg_tag_maps;
i->name != NULL; ++i)
ffmpeg_copy_metadata(tag, i->type, dict, i->name);
}
//no tag reading in ffmpeg, check if playable
......@@ -533,8 +638,18 @@ ffmpeg_stream_tag(struct input_stream *is)
return NULL;
}
if (av_find_stream_info(f) < 0) {
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,6,0)
const int find_result =
avformat_find_stream_info(f, NULL);
#else
const int find_result = av_find_stream_info(f);
#endif
if (find_result < 0) {
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
avformat_close_input(&f);
#else
av_close_input_stream(f);
#endif
mpd_ffmpeg_stream_close(stream);
return NULL;
}
......@@ -549,14 +664,16 @@ ffmpeg_stream_tag(struct input_stream *is)
av_metadata_conv(f, NULL, f->iformat->metadata_conv);
#endif
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_dictionary(tag, f->metadata);
int idx = ffmpeg_find_audio_stream(f);
if (idx >= 0)
ffmpeg_copy_dictionary(tag, f->streams[idx]->metadata);
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,17,0)
avformat_close_input(&f);
#else
av_close_input_stream(f);
#endif
mpd_ffmpeg_stream_close(stream);
return tag;
......
......@@ -102,6 +102,7 @@ flac_convert(void *dest,
break;
case SAMPLE_FORMAT_S24:
case SAMPLE_FORMAT_FLOAT:
case SAMPLE_FORMAT_UNDEFINED:
/* unreachable */
assert(false);
......
......@@ -94,6 +94,12 @@ mp4_read(void *user_data, void *buffer, uint32_t length)
{
struct mp4ff_input_stream *mis = user_data;
if (length == 0)
/* libmp4ff is known to attempt to read 0 bytes - make
this a special case, because the input_stream API
would not allow this */
return 0;
return decoder_read(mis->decoder, mis->input_stream, buffer, length);
}
......
......@@ -23,6 +23,7 @@
#include <glib.h>
#include <unistd.h>
#include <stdio.h> /* for SEEK_SET */
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "pcm"
......@@ -30,21 +31,35 @@
static void
pcm_stream_decode(struct decoder *decoder, struct input_stream *is)
{
static const struct audio_format audio_format = {
static const struct audio_format host_audio_format = {
.sample_rate = 44100,
.format = SAMPLE_FORMAT_S16,
.channels = 2,
};
static const struct audio_format reverse_audio_format = {
.sample_rate = 44100,
.format = SAMPLE_FORMAT_S16,
.channels = 2,
.reverse_endian = true,
};
const struct audio_format *audio_format =
(is->mime == NULL ||
strcmp(is->mime, "audio/x-mpd-cdda-pcm-reverse") != 0)
? &host_audio_format
: &reverse_audio_format;
GError *error = NULL;
enum decoder_command cmd;
double time_to_size = audio_format_time_to_size(&audio_format);
double time_to_size = audio_format_time_to_size(audio_format);
float total_time = -1;
if (is->size >= 0)
total_time = is->size / time_to_size;
decoder_initialized(decoder, &audio_format, is->seekable, total_time);
decoder_initialized(decoder, audio_format, is->seekable, total_time);
do {
char buffer[4096];
......@@ -79,6 +94,10 @@ pcm_stream_decode(struct decoder *decoder, struct input_stream *is)
static const char *const pcm_mime_types[] = {
/* for streams obtained by the cdio_paranoia input plugin */
"audio/x-mpd-cdda-pcm",
/* same as above, but with reverse byte order */
"audio/x-mpd-cdda-pcm-reverse",
NULL
};
......
......@@ -111,12 +111,11 @@ static void
format_samples_float(G_GNUC_UNUSED int bytes_per_sample, void *buffer,
uint32_t count)
{
int32_t *dst = buffer;
float *src = buffer;
assert_static(sizeof(*dst) <= sizeof(*src));
float *p = buffer;
while (count--) {
*dst++ = (int32_t)(*src++ + 0.5f);
*p /= (1 << 23);
++p;
}
}
......@@ -127,7 +126,7 @@ static enum sample_format
wavpack_bits_to_sample_format(bool is_float, int bytes_per_sample)
{
if (is_float)
return SAMPLE_FORMAT_S24_P32;
return SAMPLE_FORMAT_FLOAT;
switch (bytes_per_sample) {
case 1:
......@@ -176,7 +175,7 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek)
return;
}
if ((WavpackGetMode(wpc) & MODE_FLOAT) == MODE_FLOAT) {
if (is_float) {
format_samples = format_samples_float;
} else {
format_samples = format_samples_int;
......
......@@ -21,7 +21,7 @@
#include "decoder_api.h"
#include "decoder_internal.h"
#include "decoder_control.h"
#include "audio.h"
#include "audio_config.h"
#include "song.h"
#include "buffer.h"
#include "pipe.h"
......@@ -56,6 +56,9 @@ decoder_initialized(struct decoder *decoder,
dc->in_audio_format = *audio_format;
getOutputAudioFormat(audio_format, &dc->out_audio_format);
/* force host byte order, even if the decoder supplies reverse
endian */
dc->out_audio_format.reverse_endian = false;
dc->seekable = seekable;
dc->total_time = total_time;
......@@ -76,15 +79,70 @@ decoder_initialized(struct decoder *decoder,
&af_string));
}
enum decoder_command decoder_get_command(G_GNUC_UNUSED struct decoder * decoder)
/**
* Checks if we need an "initial seek". If so, then the initial seek
* is prepared, and the function returns true.
*/
G_GNUC_PURE
static bool
decoder_prepare_initial_seek(struct decoder *decoder)
{
const struct decoder_control *dc = decoder->dc;
assert(dc->pipe != NULL);
if (decoder->initial_seek_running)
/* initial seek has already begun - override any other
command */
return true;
if (decoder->initial_seek_pending) {
if (!dc->seekable) {
/* seeking is not possible */
decoder->initial_seek_pending = false;
return false;
}
if (dc->command == DECODE_COMMAND_NONE) {
/* begin initial seek */
decoder->initial_seek_pending = false;
decoder->initial_seek_running = true;
return true;
}
/* skip initial seek when there's another command
(e.g. STOP) */
decoder->initial_seek_pending = false;
}
return false;
}
/**
* Returns the current decoder command. May return a "virtual"
* synthesized command, e.g. to seek to the beginning of the CUE
* track.
*/
G_GNUC_PURE
static enum decoder_command
decoder_get_virtual_command(struct decoder *decoder)
{
const struct decoder_control *dc = decoder->dc;
assert(dc->pipe != NULL);
if (decoder_prepare_initial_seek(decoder))
return DECODE_COMMAND_SEEK;
return dc->command;
}
enum decoder_command
decoder_get_command(struct decoder *decoder)
{
return decoder_get_virtual_command(decoder);
}
void
decoder_command_finished(struct decoder *decoder)
{
......@@ -92,11 +150,24 @@ decoder_command_finished(struct decoder *decoder)
decoder_lock(dc);
assert(dc->command != DECODE_COMMAND_NONE);
assert(dc->command != DECODE_COMMAND_NONE ||
decoder->initial_seek_running);
assert(dc->command != DECODE_COMMAND_SEEK ||
decoder->initial_seek_running ||
dc->seek_error || decoder->seeking);
assert(dc->pipe != NULL);
if (decoder->initial_seek_running) {
assert(!decoder->seeking);
assert(decoder->chunk == NULL);
assert(music_pipe_empty(dc->pipe));
decoder->initial_seek_running = false;
decoder->timestamp = dc->start_ms / 1000.;
decoder_unlock(dc);
return;
}
if (decoder->seeking) {
decoder->seeking = false;
......@@ -121,9 +192,13 @@ double decoder_seek_where(G_GNUC_UNUSED struct decoder * decoder)
{
const struct decoder_control *dc = decoder->dc;
assert(dc->command == DECODE_COMMAND_SEEK);
assert(dc->pipe != NULL);
if (decoder->initial_seek_running)
return dc->start_ms / 1000.;
assert(dc->command == DECODE_COMMAND_SEEK);
decoder->seeking = true;
return dc->seek_where;
......@@ -133,9 +208,17 @@ void decoder_seek_error(struct decoder * decoder)
{
struct decoder_control *dc = decoder->dc;
assert(dc->command == DECODE_COMMAND_SEEK);
assert(dc->pipe != NULL);
if (decoder->initial_seek_running) {
/* d'oh, we can't seek to the sub-song start position,
what now? - no idea, ignoring the problem for now. */
decoder->initial_seek_running = false;
return;
}
assert(dc->command == DECODE_COMMAND_SEEK);
dc->seek_error = true;
decoder->seeking = false;
......@@ -289,7 +372,7 @@ decoder_data(struct decoder *decoder,
assert(length % audio_format_frame_size(&dc->in_audio_format) == 0);
decoder_lock(dc);
cmd = dc->command;
cmd = decoder_get_virtual_command(decoder);
decoder_unlock(dc);
if (cmd == DECODE_COMMAND_STOP || cmd == DECODE_COMMAND_SEEK ||
......@@ -376,8 +459,8 @@ decoder_data(struct decoder *decoder,
decoder->timestamp += (double)nbytes /
audio_format_time_to_size(&dc->out_audio_format);
if (dc->song->end_ms > 0 &&
decoder->timestamp >= dc->song->end_ms / 1000.0)
if (dc->end_ms > 0 &&
decoder->timestamp >= dc->end_ms / 1000.0)
/* the end of this range has been reached:
stop decoding */
return DECODE_COMMAND_STOP;
......@@ -407,6 +490,14 @@ decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is,
update_stream_tag(decoder, is);
/* check if we're seeking */
if (decoder_prepare_initial_seek(decoder))
/* during initial seek, no music chunk must be created
until seeking is finished; skip the rest of the
function here */
return DECODE_COMMAND_SEEK;
/* send tag to music pipe */
if (decoder->stream_tag != NULL) {
......
......@@ -96,6 +96,7 @@ dc_command_async(struct decoder_control *dc, enum decoder_command cmd)
void
dc_start(struct decoder_control *dc, struct song *song,
unsigned start_ms, unsigned end_ms,
struct music_buffer *buffer, struct music_pipe *pipe)
{
assert(song != NULL);
......@@ -104,6 +105,8 @@ dc_start(struct decoder_control *dc, struct song *song,
assert(music_pipe_empty(pipe));
dc->song = song;
dc->start_ms = start_ms;
dc->end_ms = end_ms;
dc->buffer = buffer;
dc->pipe = pipe;
dc_command(dc, DECODE_COMMAND_START);
......
......@@ -85,6 +85,23 @@ struct decoder_control {
*/
const struct song *song;
/**
* The initial seek position (in milliseconds), e.g. to the
* start of a sub-track described by a CUE file.
*
* This attribute is set by dc_start().
*/
unsigned start_ms;
/**
* The decoder will stop when it reaches this position (in
* milliseconds). 0 means don't stop before the end of the
* file.
*
* This attribute is set by dc_start().
*/
unsigned end_ms;
float total_time;
/** the #music_chunk allocator */
......@@ -229,11 +246,14 @@ decoder_current_song(const struct decoder_control *dc)
*
* @param the decoder
* @param song the song to be decoded
* @param start_ms see #decoder_control
* @param end_ms see #decoder_control
* @param pipe the pipe which receives the decoded chunks (owned by
* the caller)
*/
void
dc_start(struct decoder_control *dc, struct song *song,
unsigned start_ms, unsigned end_ms,
struct music_buffer *buffer, struct music_pipe *pipe);
void
......
......@@ -36,6 +36,25 @@ struct decoder {
*/
double timestamp;
/**
* Is the initial seek (to the start position of the sub-song)
* pending, or has it been performed already?
*/
bool initial_seek_pending;
/**
* Is the initial seek currently running? During this time,
* the decoder command is SEEK. This flag is set by
* decoder_get_virtual_command(), when the virtual SEEK
* command is generated for the first time.
*/
bool initial_seek_running;
/**
* This flag is set by decoder_seek_where(), and checked by
* decoder_command_finished(). It is used to clean up after
* seeking.
*/
bool seeking;
/**
......
......@@ -24,6 +24,7 @@
#include "conf.h"
#include "mpd_error.h"
#include "decoder/pcm_decoder_plugin.h"
#include "decoder/dsdiff_decoder_plugin.h"
#include <glib.h>
......@@ -70,6 +71,7 @@ const struct decoder_plugin *const decoder_plugins[] = {
#ifdef HAVE_AUDIOFILE
&audiofile_decoder_plugin,
#endif
&dsdiff_decoder_plugin,
#ifdef HAVE_FAAD
&faad_decoder_plugin,
#endif
......
......@@ -37,6 +37,7 @@
#include <glib.h>
#include <unistd.h>
#include <stdio.h> /* for SEEK_SET */
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "decoder_thread"
......@@ -380,6 +381,8 @@ decoder_run_song(struct decoder_control *dc,
{
struct decoder decoder = {
.dc = dc,
.initial_seek_pending = dc->start_ms > 0,
.initial_seek_running = false,
};
int ret;
......
......@@ -20,8 +20,11 @@
#include "config.h"
#include "directory.h"
#include "song.h"
#include "song_sort.h"
#include "path.h"
#include "util/list_sort.h"
#include "db_visitor.h"
#include "db_lock.h"
#include <glib.h>
......@@ -40,6 +43,8 @@ directory_new(const char *path, struct directory *parent)
directory = g_malloc0(sizeof(*directory) -
sizeof(directory->path) + pathlen + 1);
INIT_LIST_HEAD(&directory->children);
INIT_LIST_HEAD(&directory->songs);
directory->parent = parent;
memcpy(directory->path, path, pathlen + 1);
......@@ -53,84 +58,167 @@ directory_free(struct directory *directory)
{
playlist_vector_deinit(&directory->playlists);
for (unsigned i = 0; i < directory->songs.nr; ++i)
song_free(directory->songs.base[i]);
struct song *song, *ns;
directory_for_each_song_safe(song, ns, directory)
song_free(song);
for (unsigned i = 0; i < directory->children.nr; ++i)
directory_free(directory->children.base[i]);
struct directory *child, *n;
directory_for_each_child_safe(child, n, directory)
directory_free(child);
dirvec_destroy(&directory->children);
songvec_destroy(&directory->songs);
g_free(directory);
/* this resets last dir returned */
/*directory_get_path(NULL); */
}
void
directory_delete(struct directory *directory)
{
assert(holding_db_lock());
assert(directory != NULL);
assert(directory->parent != NULL);
list_del(&directory->siblings);
directory_free(directory);
}
const char *
directory_get_name(const struct directory *directory)
{
return g_basename(directory->path);
}
struct directory *
directory_new_child(struct directory *parent, const char *name_utf8)
{
assert(holding_db_lock());
assert(parent != NULL);
assert(name_utf8 != NULL);
assert(*name_utf8 != 0);
char *allocated;
const char *path_utf8;
if (directory_is_root(parent)) {
allocated = NULL;
path_utf8 = name_utf8;
} else {
allocated = g_strconcat(directory_get_path(parent),
"/", name_utf8, NULL);
path_utf8 = allocated;
}
struct directory *directory = directory_new(path_utf8, parent);
g_free(allocated);
list_add(&directory->siblings, &parent->children);
return directory;
}
struct directory *
directory_get_child(const struct directory *directory, const char *name)
{
assert(holding_db_lock());
struct directory *child;
directory_for_each_child(child, directory)
if (strcmp(directory_get_name(child), name) == 0)
return child;
return NULL;
}
void
directory_prune_empty(struct directory *directory)
{
int i;
struct dirvec *dv = &directory->children;
for (i = dv->nr; --i >= 0; ) {
struct directory *child = dv->base[i];
assert(holding_db_lock());
struct directory *child, *n;
directory_for_each_child_safe(child, n, directory) {
directory_prune_empty(child);
if (directory_is_empty(child)) {
dirvec_delete(dv, child);
directory_free(child);
}
if (directory_is_empty(child))
directory_delete(child);
}
if (!dv->nr)
dirvec_destroy(dv);
}
struct directory *
directory_lookup_directory(struct directory *directory, const char *uri)
{
struct directory *cur = directory;
struct directory *found = NULL;
char *duplicated;
char *locate;
assert(holding_db_lock());
assert(uri != NULL);
if (isRootDirectory(uri))
return directory;
duplicated = g_strdup(uri);
locate = strchr(duplicated, '/');
char *duplicated = g_strdup(uri), *name = duplicated;
while (1) {
if (locate)
*locate = '\0';
if (!(found = directory_get_child(cur, duplicated)))
char *slash = strchr(name, '/');
if (slash == name) {
directory = NULL;
break;
assert(cur == found->parent);
cur = found;
if (!locate)
}
if (slash != NULL)
*slash = '\0';
directory = directory_get_child(directory, name);
if (directory == NULL || slash == NULL)
break;
*locate = '/';
locate = strchr(locate + 1, '/');
name = slash + 1;
}
g_free(duplicated);
return found;
return directory;
}
void
directory_add_song(struct directory *directory, struct song *song)
{
assert(directory != NULL);
assert(song != NULL);
assert(song->parent == directory);
list_add_tail(&song->siblings, &directory->songs);
}
void
directory_remove_song(G_GNUC_UNUSED struct directory *directory,
struct song *song)
{
assert(directory != NULL);
assert(song != NULL);
assert(song->parent == directory);
list_del(&song->siblings);
}
struct song *
directory_get_song(const struct directory *directory, const char *name_utf8)
{
assert(holding_db_lock());
assert(directory != NULL);
assert(name_utf8 != NULL);
struct song *song;
directory_for_each_song(song, directory) {
assert(song->parent == directory);
if (strcmp(song->uri, name_utf8) == 0)
return song;
}
return NULL;
}
struct song *
directory_lookup_song(struct directory *directory, const char *uri)
{
char *duplicated, *base;
struct song *song;
assert(holding_db_lock());
assert(directory != NULL);
assert(uri != NULL);
......@@ -147,7 +235,7 @@ directory_lookup_song(struct directory *directory, const char *uri)
} else
base = duplicated;
song = songvec_find(&directory->songs, base);
struct song *song = directory_get_song(directory, base);
assert(song == NULL || song->parent == directory);
g_free(duplicated);
......@@ -155,17 +243,26 @@ directory_lookup_song(struct directory *directory, const char *uri)
}
static int
directory_cmp(G_GNUC_UNUSED void *priv,
struct list_head *_a, struct list_head *_b)
{
const struct directory *a = (const struct directory *)_a;
const struct directory *b = (const struct directory *)_b;
return g_utf8_collate(a->path, b->path);
}
void
directory_sort(struct directory *directory)
{
int i;
struct dirvec *dv = &directory->children;
assert(holding_db_lock());
dirvec_sort(dv);
songvec_sort(&directory->songs);
list_sort(NULL, &directory->children, directory_cmp);
song_list_sort(&directory->songs);
for (i = dv->nr; --i >= 0; )
directory_sort(dv->base[i]);
struct directory *child;
directory_for_each_child(child, directory)
directory_sort(child);
}
bool
......@@ -178,9 +275,9 @@ directory_walk(const struct directory *directory, bool recursive,
assert(error_r == NULL || *error_r == NULL);
if (visitor->song != NULL) {
const struct songvec *sv = &directory->songs;
for (size_t i = 0; i < sv->nr; ++i)
if (!visitor->song(sv->base[i], ctx, error_r))
struct song *song;
directory_for_each_song(song, directory)
if (!visitor->song(song, ctx, error_r))
return false;
}
......@@ -188,14 +285,12 @@ directory_walk(const struct directory *directory, bool recursive,
const struct playlist_vector *pv = &directory->playlists;
for (const struct playlist_metadata *i = pv->head;
i != NULL; i = i->next)
if (!visitor->playlist(i, ctx, error_r))
if (!visitor->playlist(i, directory, ctx, error_r))
return false;
}
const struct dirvec *dv = &directory->children;
for (size_t i = 0; i < dv->nr; ++i) {
struct directory *child = dv->base[i];
struct directory *child;
directory_for_each_child(child, directory) {
if (visitor->directory != NULL &&
!visitor->directory(child, ctx, error_r))
return false;
......
......@@ -21,8 +21,7 @@
#define MPD_DIRECTORY_H
#include "check.h"
#include "dirvec.h"
#include "songvec.h"
#include "util/list.h"
#include "playlist_vector.h"
#include <glib.h>
......@@ -34,11 +33,47 @@
#define DEVICE_INARCHIVE (dev_t)(-1)
#define DEVICE_CONTAINER (dev_t)(-2)
#define directory_for_each_child(pos, directory) \
list_for_each_entry(pos, &directory->children, siblings)
#define directory_for_each_child_safe(pos, n, directory) \
list_for_each_entry_safe(pos, n, &directory->children, siblings)
#define directory_for_each_song(pos, directory) \
list_for_each_entry(pos, &directory->songs, siblings)
#define directory_for_each_song_safe(pos, n, directory) \
list_for_each_entry_safe(pos, n, &directory->songs, siblings)
struct song;
struct db_visitor;
struct directory {
struct dirvec children;
struct songvec songs;
/**
* Pointers to the siblings of this directory within the
* parent directory. It is unused (undefined) in the root
* directory.
*
* This attribute is protected with the global #db_mutex.
* Read access in the update thread does not need protection.
*/
struct list_head siblings;
/**
* A doubly linked list of child directories.
*
* This attribute is protected with the global #db_mutex.
* Read access in the update thread does not need protection.
*/
struct list_head children;
/**
* A doubly linked list of songs within this directory.
*
* This attribute is protected with the global #db_mutex.
* Read access in the update thread does not need protection.
*/
struct list_head songs;
struct playlist_vector playlists;
......@@ -46,7 +81,7 @@ struct directory {
time_t mtime;
ino_t inode;
dev_t device;
unsigned stat; /* not needed if ino_t == dev_t == 0 is impossible */
bool have_stat; /* not needed if ino_t == dev_t == 0 is impossible */
char path[sizeof(long)];
};
......@@ -56,16 +91,44 @@ isRootDirectory(const char *name)
return name[0] == 0 || (name[0] == '/' && name[1] == 0);
}
/**
* Generic constructor for #directory object.
*/
G_GNUC_MALLOC
struct directory *
directory_new(const char *dirname, struct directory *parent);
/**
* Create a new root #directory object.
*/
G_GNUC_MALLOC
static inline struct directory *
directory_new_root(void)
{
return directory_new("", NULL);
}
/**
* Free this #directory object (and the whole object tree within it),
* assuming it was already removed from the parent.
*/
void
directory_free(struct directory *directory);
/**
* Remove this #directory object from its parent and free it. This
* must not be called with the root directory.
*
* Caller must lock the #db_mutex.
*/
void
directory_delete(struct directory *directory);
static inline bool
directory_is_empty(const struct directory *directory)
{
return directory->children.nr == 0 && directory->songs.nr == 0 &&
return list_empty(&directory->children) &&
list_empty(&directory->songs) &&
playlist_vector_is_empty(&directory->playlists);
}
......@@ -87,23 +150,47 @@ directory_is_root(const struct directory *directory)
/**
* Returns the base name of the directory.
*/
G_GNUC_PURE
const char *
directory_get_name(const struct directory *directory);
static inline struct directory *
directory_get_child(const struct directory *directory, const char *name)
{
return dirvec_find(&directory->children, name);
}
/**
* Caller must lock the #db_mutex.
*/
G_GNUC_PURE
struct directory *
directory_get_child(const struct directory *directory, const char *name);
/**
* Create a new #directory object as a child of the given one.
*
* Caller must lock the #db_mutex.
*
* @param parent the parent directory the new one will be added to
* @param name_utf8 the UTF-8 encoded name of the new sub directory
*/
G_GNUC_MALLOC
struct directory *
directory_new_child(struct directory *parent, const char *name_utf8);
/**
* Look up a sub directory, and create the object if it does not
* exist.
*
* Caller must lock the #db_mutex.
*/
static inline struct directory *
directory_new_child(struct directory *directory, const char *name)
directory_make_child(struct directory *directory, const char *name_utf8)
{
struct directory *subdir = directory_new(name, directory);
dirvec_add(&directory->children, subdir);
return subdir;
struct directory *child = directory_get_child(directory, name_utf8);
if (child == NULL)
child = directory_new_child(directory, name_utf8);
return child;
}
/**
* Caller must lock the #db_mutex.
*/
void
directory_prune_empty(struct directory *directory);
......@@ -118,8 +205,34 @@ struct directory *
directory_lookup_directory(struct directory *directory, const char *uri);
/**
* Add a song object to this directory. Its "parent" attribute must
* be set already.
*/
void
directory_add_song(struct directory *directory, struct song *song);
/**
* Remove a song object from this directory (which effectively
* invalidates the song object, because the "parent" attribute becomes
* stale), but does not free it.
*/
void
directory_remove_song(struct directory *directory, struct song *song);
/**
* Look up a song in this directory by its name.
*
* Caller must lock the #db_mutex.
*/
G_GNUC_PURE
struct song *
directory_get_song(const struct directory *directory, const char *name_utf8);
/**
* Looks up a song by its relative URI.
*
* Caller must lock the #db_mutex.
*
* @param directory the parent (or grandparent, ...) directory
* @param uri the relative URI
* @return the song, or NULL if none was found
......@@ -127,9 +240,17 @@ directory_lookup_directory(struct directory *directory, const char *uri);
struct song *
directory_lookup_song(struct directory *directory, const char *uri);
/**
* Sort all directory entries recursively.
*
* Caller must lock the #db_mutex.
*/
void
directory_sort(struct directory *directory);
/**
* Caller must lock #db_mutex.
*/
bool
directory_walk(const struct directory *directory, bool recursive,
const struct db_visitor *visitor, void *ctx,
......
......@@ -44,8 +44,6 @@ directory_quark(void)
void
directory_save(FILE *fp, const struct directory *directory)
{
size_t i;
if (!directory_is_root(directory)) {
fprintf(fp, DIRECTORY_MTIME "%lu\n",
(unsigned long)directory->mtime);
......@@ -54,9 +52,8 @@ directory_save(FILE *fp, const struct directory *directory)
directory_get_path(directory));
}
const struct dirvec *children = &directory->children;
for (i = 0; i < children->nr; ++i) {
const struct directory *cur = children->base[i];
struct directory *cur;
directory_for_each_child(cur, directory) {
char *base = g_path_get_basename(cur->path);
fprintf(fp, DIRECTORY_DIR "%s\n", base);
......@@ -68,7 +65,9 @@ directory_save(FILE *fp, const struct directory *directory)
return;
}
songvec_save(fp, &directory->songs);
struct song *song;
directory_for_each_song(song, directory)
song_save(fp, song);
playlist_vector_save(fp, &directory->playlists);
......@@ -81,7 +80,6 @@ static struct directory *
directory_load_subdir(FILE *fp, struct directory *parent, const char *name,
GString *buffer, GError **error_r)
{
struct directory *directory;
const char *line;
bool success;
......@@ -91,20 +89,13 @@ directory_load_subdir(FILE *fp, struct directory *parent, const char *name,
return NULL;
}
if (directory_is_root(parent)) {
directory = directory_new(name, parent);
} else {
char *path = g_strconcat(directory_get_path(parent), "/",
name, NULL);
directory = directory_new(path, parent);
g_free(path);
}
struct directory *directory = directory_new_child(parent, name);
line = read_text_line(fp, buffer);
if (line == NULL) {
g_set_error(error_r, directory_quark(), 0,
"Unexpected end of file");
directory_free(directory);
directory_delete(directory);
return NULL;
}
......@@ -117,7 +108,7 @@ directory_load_subdir(FILE *fp, struct directory *parent, const char *name,
if (line == NULL) {
g_set_error(error_r, directory_quark(), 0,
"Unexpected end of file");
directory_free(directory);
directory_delete(directory);
return NULL;
}
}
......@@ -125,13 +116,13 @@ directory_load_subdir(FILE *fp, struct directory *parent, const char *name,
if (!g_str_has_prefix(line, DIRECTORY_BEGIN)) {
g_set_error(error_r, directory_quark(), 0,
"Malformed line: %s", line);
directory_free(directory);
directory_delete(directory);
return NULL;
}
success = directory_load(fp, directory, buffer, error_r);
if (!success) {
directory_free(directory);
directory_delete(directory);
return NULL;
}
......@@ -153,13 +144,11 @@ directory_load(FILE *fp, struct directory *directory,
buffer, error);
if (subdir == NULL)
return false;
dirvec_add(&directory->children, subdir);
} else if (g_str_has_prefix(line, SONG_BEGIN)) {
const char *name = line + sizeof(SONG_BEGIN) - 1;
struct song *song;
if (songvec_find(&directory->songs, name) != NULL) {
if (directory_get_song(directory, name) != NULL) {
g_set_error(error, directory_quark(), 0,
"Duplicate song '%s'", name);
return NULL;
......@@ -170,7 +159,7 @@ directory_load(FILE *fp, struct directory *directory,
if (song == NULL)
return false;
songvec_add(&directory->songs, song);
directory_add_song(directory, song);
} else if (g_str_has_prefix(line, PLAYLIST_META_BEGIN)) {
/* duplicate the name, because
playlist_metadata_load() will overwrite the
......
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "dirvec.h"
#include "directory.h"
#include <glib.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
static GMutex *nr_lock = NULL;
static size_t dv_size(const struct dirvec *dv)
{
return dv->nr * sizeof(struct directory *);
}
/* Only used for sorting/searching a dirvec, not general purpose compares */
static int dirvec_cmp(const void *d1, const void *d2)
{
const struct directory *a = ((const struct directory * const *)d1)[0];
const struct directory *b = ((const struct directory * const *)d2)[0];
return g_utf8_collate(a->path, b->path);
}
void dirvec_init(void)
{
g_assert(nr_lock == NULL);
nr_lock = g_mutex_new();
}
void dirvec_deinit(void)
{
g_assert(nr_lock != NULL);
g_mutex_free(nr_lock);
nr_lock = NULL;
}
void dirvec_sort(struct dirvec *dv)
{
g_mutex_lock(nr_lock);
qsort(dv->base, dv->nr, sizeof(struct directory *), dirvec_cmp);
g_mutex_unlock(nr_lock);
}
struct directory *dirvec_find(const struct dirvec *dv, const char *path)
{
char *base;
int i;
struct directory *ret = NULL;
base = g_path_get_basename(path);
g_mutex_lock(nr_lock);
for (i = dv->nr; --i >= 0; )
if (!strcmp(directory_get_name(dv->base[i]), base)) {
ret = dv->base[i];
break;
}
g_mutex_unlock(nr_lock);
g_free(base);
return ret;
}
int dirvec_delete(struct dirvec *dv, struct directory *del)
{
size_t i;
g_mutex_lock(nr_lock);
for (i = 0; i < dv->nr; ++i) {
if (dv->base[i] != del)
continue;
/* we _don't_ call directory_free() here */
if (!--dv->nr) {
g_mutex_unlock(nr_lock);
g_free(dv->base);
dv->base = NULL;
return i;
} else {
memmove(&dv->base[i], &dv->base[i + 1],
(dv->nr - i) * sizeof(struct directory *));
dv->base = g_realloc(dv->base, dv_size(dv));
}
break;
}
g_mutex_unlock(nr_lock);
return i;
}
void dirvec_add(struct dirvec *dv, struct directory *add)
{
g_mutex_lock(nr_lock);
++dv->nr;
dv->base = g_realloc(dv->base, dv_size(dv));
dv->base[dv->nr - 1] = add;
g_mutex_unlock(nr_lock);
}
void dirvec_destroy(struct dirvec *dv)
{
g_mutex_lock(nr_lock);
dv->nr = 0;
g_mutex_unlock(nr_lock);
if (dv->base) {
g_free(dv->base);
dv->base = NULL;
}
}
int dirvec_for_each(const struct dirvec *dv,
int (*fn)(struct directory *, void *), void *arg)
{
size_t i;
size_t prev_nr;
g_mutex_lock(nr_lock);
for (i = 0; i < dv->nr; ) {
struct directory *dir = dv->base[i];
assert(dir);
prev_nr = dv->nr;
g_mutex_unlock(nr_lock);
if (fn(dir, arg) < 0)
return -1;
g_mutex_lock(nr_lock); /* dv->nr may change in fn() */
if (prev_nr == dv->nr)
++i;
}
g_mutex_unlock(nr_lock);
return 0;
}
#include <stdlib.h>
#include <string.h>
#include "dsd2pcm.h"
#define HTAPS 48 /* number of FIR constants */
#define FIFOSIZE 16 /* must be a power of two */
#define FIFOMASK (FIFOSIZE-1) /* bit mask for FIFO offsets */
#define CTABLES ((HTAPS+7)/8) /* number of "8 MACs" lookup tables */
#if FIFOSIZE*8 < HTAPS*2
#error "FIFOSIZE too small"
#endif
/*
* Properties of this 96-tap lowpass filter when applied on a signal
* with sampling rate of 44100*64 Hz:
*
* () has a delay of 17 microseconds.
*
* () flat response up to 48 kHz
*
* () if you downsample afterwards by a factor of 8, the
* spectrum below 70 kHz is practically alias-free.
*
* () stopband rejection is about 160 dB
*
* The coefficient tables ("ctables") take only 6 Kibi Bytes and
* should fit into a modern processor's fast cache.
*/
/*
* The 2nd half (48 coeffs) of a 96-tap symmetric lowpass filter
*/
static const double htaps[HTAPS] = {
0.09950731974056658,
0.09562845727714668,
0.08819647126516944,
0.07782552527068175,
0.06534876523171299,
0.05172629311427257,
0.0379429484910187,
0.02490921351762261,
0.0133774746265897,
0.003883043418804416,
-0.003284703416210726,
-0.008080250212687497,
-0.01067241812471033,
-0.01139427235000863,
-0.0106813877974587,
-0.009007905078766049,
-0.006828859761015335,
-0.004535184322001496,
-0.002425035959059578,
-0.0006922187080790708,
0.0005700762133516592,
0.001353838005269448,
0.001713709169690937,
0.001742046839472948,
0.001545601648013235,
0.001226696225277855,
0.0008704322683580222,
0.0005381636200535649,
0.000266446345425276,
7.002968738383528e-05,
-5.279407053811266e-05,
-0.0001140625650874684,
-0.0001304796361231895,
-0.0001189970287491285,
-9.396247155265073e-05,
-6.577634378272832e-05,
-4.07492895872535e-05,
-2.17407957554587e-05,
-9.163058931391722e-06,
-2.017460145032201e-06,
1.249721855219005e-06,
2.166655190537392e-06,
1.930520892991082e-06,
1.319400334374195e-06,
7.410039764949091e-07,
3.423230509967409e-07,
1.244182214744588e-07,
3.130441005359396e-08
};
static float ctables[CTABLES][256];
static unsigned char bitreverse[256];
static int precalculated = 0;
static void precalc(void)
{
int t, e, m, k;
double acc;
if (precalculated) return;
for (t=0, e=0; t<256; ++t) {
bitreverse[t] = e;
for (m=128; m && !((e^=m)&m); m>>=1)
;
}
for (t=0; t<CTABLES; ++t) {
k = HTAPS - t*8;
if (k>8) k=8;
for (e=0; e<256; ++e) {
acc = 0.0;
for (m=0; m<k; ++m) {
acc += (((e >> (7-m)) & 1)*2-1) * htaps[t*8+m];
}
ctables[CTABLES-1-t][e] = (float)acc;
}
}
precalculated = 1;
}
struct dsd2pcm_ctx_s
{
unsigned char fifo[FIFOSIZE];
unsigned fifopos;
};
extern dsd2pcm_ctx* dsd2pcm_init(void)
{
dsd2pcm_ctx* ptr;
if (!precalculated) precalc();
ptr = (dsd2pcm_ctx*) malloc(sizeof(dsd2pcm_ctx));
if (ptr) dsd2pcm_reset(ptr);
return ptr;
}
extern void dsd2pcm_destroy(dsd2pcm_ctx* ptr)
{
free(ptr);
}
extern dsd2pcm_ctx* dsd2pcm_clone(dsd2pcm_ctx* ptr)
{
dsd2pcm_ctx* p2;
p2 = (dsd2pcm_ctx*) malloc(sizeof(dsd2pcm_ctx));
if (p2) {
memcpy(p2,ptr,sizeof(dsd2pcm_ctx));
}
return p2;
}
extern void dsd2pcm_reset(dsd2pcm_ctx* ptr)
{
int i;
for (i=0; i<FIFOSIZE; ++i)
ptr->fifo[i] = 0x69; /* my favorite silence pattern */
ptr->fifopos = 0;
/* 0x69 = 01101001
* This pattern "on repeat" makes a low energy 352.8 kHz tone
* and a high energy 1.0584 MHz tone which should be filtered
* out completely by any playback system --> silence
*/
}
extern void dsd2pcm_translate(
dsd2pcm_ctx* ptr,
size_t samples,
const unsigned char *src, ptrdiff_t src_stride,
int lsbf,
float *dst, ptrdiff_t dst_stride)
{
unsigned ffp;
unsigned i;
unsigned bite1, bite2;
unsigned char* p;
double acc;
ffp = ptr->fifopos;
lsbf = lsbf ? 1 : 0;
while (samples-- > 0) {
bite1 = *src & 0xFFu;
if (lsbf) bite1 = bitreverse[bite1];
ptr->fifo[ffp] = bite1; src += src_stride;
p = ptr->fifo + ((ffp-CTABLES) & FIFOMASK);
*p = bitreverse[*p & 0xFF];
acc = 0;
for (i=0; i<CTABLES; ++i) {
bite1 = ptr->fifo[(ffp -i) & FIFOMASK] & 0xFF;
bite2 = ptr->fifo[(ffp-(CTABLES*2-1)+i) & FIFOMASK] & 0xFF;
acc += ctables[i][bite1] + ctables[i][bite2];
}
*dst = (float)acc; dst += dst_stride;
ffp = (ffp + 1) & FIFOMASK;
}
ptr->fifopos = ffp;
}
#ifndef DSD2PCM_H_INCLUDED
#define DSD2PCM_H_INCLUDED
#include <stddef.h>
#include <string.h>
#ifdef __cplusplus
extern "C" {
#endif
struct dsd2pcm_ctx_s;
typedef struct dsd2pcm_ctx_s dsd2pcm_ctx;
/**
* initializes a "dsd2pcm engine" for one channel
* (precomputes tables and allocates memory)
*
* This is the only function that is not thread-safe in terms of the
* POSIX thread-safety definition because it modifies global state
* (lookup tables are computed during the first call)
*/
extern dsd2pcm_ctx* dsd2pcm_init(void);
/**
* deinitializes a "dsd2pcm engine"
* (releases memory, don't forget!)
*/
extern void dsd2pcm_destroy(dsd2pcm_ctx *ctx);
/**
* clones the context and returns a pointer to the
* newly allocated copy
*/
extern dsd2pcm_ctx* dsd2pcm_clone(dsd2pcm_ctx *ctx);
/**
* resets the internal state for a fresh new stream
*/
extern void dsd2pcm_reset(dsd2pcm_ctx *ctx);
/**
* "translates" a stream of octets to a stream of floats
* (8:1 decimation)
* @param ctx -- pointer to abstract context (buffers)
* @param samples -- number of octets/samples to "translate"
* @param src -- pointer to first octet (input)
* @param src_stride -- src pointer increment
* @param lsbitfirst -- bitorder, 0=msb first, 1=lsbfirst
* @param dst -- pointer to first float (output)
* @param dst_stride -- dst pointer increment
*/
extern void dsd2pcm_translate(dsd2pcm_ctx *ctx,
size_t samples,
const unsigned char *src, ptrdiff_t src_stride,
int lsbitfirst,
float *dst, ptrdiff_t dst_stride);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* include guard DSD2PCM_H_INCLUDED */
#ifndef DSD2PCM_HXX_INCLUDED
#define DSD2PCM_HXX_INCLUDED
#include <algorithm>
#include <stdexcept>
#include "dsd2pcm.h"
/**
* C++ PImpl Wrapper for the dsd2pcm C library
*/
class dxd
{
dsd2pcm_ctx *handle;
public:
dxd() : handle(dsd2pcm_init())
{ if (!handle) throw std::runtime_error("wtf?!"); }
dxd(dxd const& x) : handle(dsd2pcm_clone(x.handle))
{ if (!handle) throw std::runtime_error("wtf?!"); }
~dxd() { dsd2pcm_destroy(handle); }
friend void swap(dxd & a, dxd & b)
{ std::swap(a.handle,b.handle); }
dxd& operator=(dxd x)
{ swap(*this,x); return *this; }
void translate(size_t samples,
const unsigned char *src, ptrdiff_t src_stride,
bool lsbitfirst,
float *dst, ptrdiff_t dst_stride)
{
dsd2pcm_translate(handle,samples,src,src_stride,
lsbitfirst,dst,dst_stride);
}
};
#endif // DSD2PCM_HXX_INCLUDED
You downloaded the source code for "dsd2pcm" which is a simple little
"filter" program, that takes a DSD data stream on stdin and converts
it to a PCM stream (352.8 kHz, either 16 or 24 bits) and writes it to
stdout. The code is split into three modules:
(1) dsd2pcm
This is where the 8:1 decimation magic happens. It's an
implementation of a symmetric 96-taps FIR lowpass filter
optimized for DSD inputs. If you feed this converter with
DSD64 you get a PCM stream at 352.8 kHz and floating point
samples. This module is independent and can be reused.
(2) noiseshape
A module for applying generic noise shaping filters. It's
used for the 16-bit output mode in "main" to preserve the
dynamic range. This module is independent and can be reused.
(3) main.cpp (file contains the main function and handles I/O)
The first two modules are pure C for maximum portability. In addition,
there are C++ wrapper headers for convenient use of these modules in
C++. The main application is a C++ application and makes use of the
C++ headers to access the functionality of the first two modules.
Under Linux this program is easily compiled by typing
g++ *.c *.cpp -O3 -o dsd2pcm
provided you have GCC installed. That's why I didn't bother writing
any makefiles. :-p
Cheers!
SG
#include <iostream>
#include <vector>
#include <cstring>
#include "dsd2pcm.hpp"
#include "noiseshape.hpp"
namespace {
const float my_ns_coeffs[] = {
// b1 b2 a1 a2
-1.62666423, 0.79410094, 0.61367127, 0.23311013, // section 1
-1.44870017, 0.54196219, 0.03373857, 0.70316556 // section 2
};
const int my_ns_soscount = sizeof(my_ns_coeffs)/(sizeof(my_ns_coeffs[0])*4);
inline long myround(float x)
{
return static_cast<long>(x + (x>=0 ? 0.5f : -0.5f));
}
template<typename T>
struct id { typedef T type; };
template<typename T>
inline T clip(
typename id<T>::type min,
T v,
typename id<T>::type max)
{
if (v<min) return min;
if (v>max) return max;
return v;
}
inline void write_intel16(unsigned char * ptr, unsigned word)
{
ptr[0] = word & 0xFF;
ptr[1] = (word >> 8) & 0xFF;
}
inline void write_intel24(unsigned char * ptr, unsigned long word)
{
ptr[0] = word & 0xFF;
ptr[1] = (word >> 8) & 0xFF;
ptr[2] = (word >> 16) & 0xFF;
}
} // anonymous namespace
using std::vector;
using std::cin;
using std::cout;
using std::cerr;
int main(int argc, char *argv[])
{
const int block = 16384;
int channels = -1;
int lsbitfirst = -1;
int bits = -1;
if (argc==4) {
if ('1'<=argv[1][0] && argv[1][0]<='9') channels = 1 + (argv[1][0]-'1');
if (argv[2][0]=='m' || argv[2][0]=='M') lsbitfirst=0;
if (argv[2][0]=='l' || argv[2][0]=='L') lsbitfirst=1;
if (!strcmp(argv[3],"16")) bits = 16;
if (!strcmp(argv[3],"24")) bits = 24;
}
if (channels<1 || lsbitfirst<0 || bits<0) {
cerr << "\n"
"DSD2PCM filter (raw DSD64 --> 352 kHz raw PCM)\n"
"(c) 2009 Sebastian Gesemann\n\n"
"(filter as in \"reads data from stdin and writes to stdout\")\n\n"
"Syntax: dsd2pcm <channels> <bitorder> <bitdepth>\n"
"channels = 1,2,3,...,9 (number of channels in DSD stream)\n"
"bitorder = L (lsb first), M (msb first) (DSD stream option)\n"
"bitdepth = 16 or 24 (intel byte order, output option)\n\n"
"Note: At 16 bits/sample a noise shaper kicks in that can preserve\n"
"a dynamic range of 135 dB below 30 kHz.\n\n";
return 1;
}
int bytespersample = bits/8;
vector<dxd> dxds (channels);
vector<noise_shaper> ns;
if (bits==16) {
ns.resize(channels, noise_shaper(my_ns_soscount, my_ns_coeffs) );
}
vector<unsigned char> dsd_data (block * channels);
vector<float> float_data (block);
vector<unsigned char> pcm_data (block * channels * bytespersample);
char * const dsd_in = reinterpret_cast<char*>(&dsd_data[0]);
char * const pcm_out = reinterpret_cast<char*>(&pcm_data[0]);
while (cin.read(dsd_in,block * channels)) {
for (int c=0; c<channels; ++c) {
dxds[c].translate(block,&dsd_data[0]+c,channels,
lsbitfirst,
&float_data[0],1);
unsigned char * out = &pcm_data[0] + c*bytespersample;
if (bits==16) {
for (int s=0; s<block; ++s) {
float r = float_data[s]*32768 + ns[c].get();
long smp = clip(-32768,myround(r),32767);
ns[c].update( clip(-1,smp-r,1) );
write_intel16(out,smp);
out += channels*bytespersample;
}
} else {
for (int s=0; s<block; ++s) {
float r = float_data[s]*8388608;
long smp = clip(-8388608,myround(r),8388607);
write_intel24(out,smp);
out += channels*bytespersample;
}
}
}
cout.write(pcm_out,block*channels*bytespersample);
}
}
#include <stdlib.h>
#include <string.h>
#include "noiseshape.h"
extern int noise_shape_init(
noise_shape_ctx *ctx,
int sos_count,
const float *coeffs)
{
int i;
ctx->sos_count = sos_count;
ctx->bbaa = coeffs;
ctx->t1 = (float*) malloc(sizeof(float)*sos_count);
if (!ctx->t1) goto escape1;
ctx->t2 = (float*) malloc(sizeof(float)*sos_count);
if (!ctx->t2) goto escape2;
for (i=0; i<sos_count; ++i) {
ctx->t1[i] = 0.f;
ctx->t2[i] = 0.f;
}
return 0;
escape2:
free(ctx->t1);
escape1:
return -1;
}
extern void noise_shape_destroy(
noise_shape_ctx *ctx)
{
free(ctx->t1);
free(ctx->t2);
}
extern int noise_shape_clone(
const noise_shape_ctx *from,
noise_shape_ctx *to)
{
to->sos_count = from->sos_count;
to->bbaa = from->bbaa;
to->t1 = (float*) malloc(sizeof(float)*to->sos_count);
if (!to->t1) goto error1;
to->t2 = (float*) malloc(sizeof(float)*to->sos_count);
if (!to->t2) goto error2;
memcpy(to->t1,from->t1,sizeof(float)*to->sos_count);
memcpy(to->t2,from->t2,sizeof(float)*to->sos_count);
return 0;
error2:
free(to->t1);
error1:
return -1;
}
extern float noise_shape_get(noise_shape_ctx *ctx)
{
int i;
float acc;
const float *c;
acc = 0.0;
c = ctx->bbaa;
for (i=0; i<ctx->sos_count; ++i) {
float t1i = ctx->t1[i];
float t2i = ctx->t2[i];
ctx->t2[i] = acc -= t1i * c[2] + t2i * c[3];
acc += t1i * c[0] + t2i * c[1];
c += 4;
}
return acc;
}
extern void noise_shape_update(noise_shape_ctx *ctx, float qerror)
{
float *p;
int i;
for (i=0; i<ctx->sos_count; ++i) {
ctx->t2[i] += qerror;
}
p = ctx->t1;
ctx->t1 = ctx->t2;
ctx->t2 = p;
}
#ifndef NOISE_SHAPE_H_INCLUDED
#define NOISE_SHAPE_H_INCLUDED
#ifdef __cplusplus
extern "C" {
#endif
typedef struct noise_shape_ctx_s {
int sos_count; /* number of second order sections */
const float *bbaa; /* filter coefficients, owned by user */
float *t1, *t2; /* filter state, owned by ns library */
} noise_shape_ctx;
/**
* initializes a noise_shaper context
* returns an error code or 0
*/
extern int noise_shape_init(
noise_shape_ctx *ctx,
int sos_count,
const float *coeffs);
/**
* destroys a noise_shaper context
*/
extern void noise_shape_destroy(
noise_shape_ctx *ctx);
/**
* initializes a noise_shaper context so that its state
* is a copy of a given context
* returns an error code or 0
*/
extern int noise_shape_clone(
const noise_shape_ctx *from, noise_shape_ctx *to);
/**
* computes the next "noise shaping sample". Note: This call
* alters the internal state. xxx_get and xxx_update must be
* called in an alternating manner.
*/
extern float noise_shape_get(
noise_shape_ctx *ctx);
/**
* updates the noise shaper's state with the
* last quantization error
*/
extern void noise_shape_update(
noise_shape_ctx *ctx, float qerror);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* NOISE_SHAPE_H_INCLUDED */
#ifndef NOISE_SHAPE_HXX_INCLUDED
#define NOISE_SHAPE_HXX_INCLUDED
#include <stdexcept>
#include "noiseshape.h"
/**
* C++ wrapper for the noiseshape C library
*/
class noise_shaper
{
noise_shape_ctx ctx;
public:
noise_shaper(int sos_count, const float *bbaa)
{
if (noise_shape_init(&ctx,sos_count,bbaa))
throw std::runtime_error("noise shaper initialization failed");
}
noise_shaper(noise_shaper const& x)
{
if (noise_shape_clone(&x.ctx,&ctx))
throw std::runtime_error("noise shaper initialization failed");
}
~noise_shaper()
{ noise_shape_destroy(&ctx); }
noise_shaper& operator=(noise_shaper const& x)
{
if (this != &x) {
noise_shape_destroy(&ctx);
if (noise_shape_clone(&x.ctx,&ctx))
throw std::runtime_error("noise shaper initialization failed");
}
return *this;
}
float get() { return noise_shape_get(&ctx); }
void update(float error) { noise_shape_update(&ctx,error); }
};
#endif /* NOISE_SHAPE_HXX_INCLUDED */
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*
* Just a dummy C++ file that is linked to work around an automake
* bug: automake uses CXXLD when at least one source is C++, but when
* you link a static library with a C++ source, it uses CCLD. This
* causes linker problems (undefined reference to 'operator
* delete(void*)'), because CCLD does not link with libstdc++.
*
* By linking with this empty C++ source, automake decides to use
* CXXLD.
*
*/
......@@ -22,6 +22,8 @@
#include "encoder_plugin.h"
#include "audio_format.h"
#include "pcm_buffer.h"
#include "fifo_buffer.h"
#include "growing_fifo.h"
#include <assert.h>
#include <string.h>
......@@ -38,8 +40,11 @@ struct flac_encoder {
struct pcm_buffer expand_buffer;
struct pcm_buffer buffer;
size_t buffer_length;
/**
* This buffer will hold encoded data from libFLAC until it is
* picked up with flac_encoder_read().
*/
struct fifo_buffer *output_buffer;
};
extern const struct encoder_plugin flac_encoder_plugin;
......@@ -140,11 +145,8 @@ flac_write_callback(G_GNUC_UNUSED const FLAC__StreamEncoder *fse,
{
struct flac_encoder *encoder = (struct flac_encoder *) client_data;
char *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length + bytes);
//transfer data to buffer
memcpy( buffer + encoder->buffer_length, data, bytes);
encoder->buffer_length += bytes;
growing_fifo_append(&encoder->output_buffer, data, bytes);
return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
}
......@@ -156,8 +158,8 @@ flac_encoder_close(struct encoder *_encoder)
FLAC__stream_encoder_delete(encoder->fse);
pcm_buffer_deinit(&encoder->buffer);
pcm_buffer_deinit(&encoder->expand_buffer);
fifo_buffer_free(encoder->output_buffer);
}
static bool
......@@ -201,10 +203,10 @@ flac_encoder_open(struct encoder *_encoder, struct audio_format *audio_format,
return false;
}
encoder->buffer_length = 0;
pcm_buffer_init(&encoder->buffer);
pcm_buffer_init(&encoder->expand_buffer);
encoder->output_buffer = growing_fifo_new();
/* this immediately outputs data through callback */
#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
......@@ -325,16 +327,18 @@ static size_t
flac_encoder_read(struct encoder *_encoder, void *dest, size_t length)
{
struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
char *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length);
if (length > encoder->buffer_length)
length = encoder->buffer_length;
memcpy(dest, buffer, length);
size_t max_length;
const char *src = fifo_buffer_read(encoder->output_buffer,
&max_length);
if (src == NULL)
return 0;
encoder->buffer_length -= length;
memmove(buffer, buffer + length, encoder->buffer_length);
if (length > max_length)
length = max_length;
memcpy(dest, src, length);
fifo_buffer_consume(encoder->output_buffer, length);
return length;
}
......
......@@ -20,7 +20,8 @@
#include "config.h"
#include "encoder_api.h"
#include "encoder_plugin.h"
#include "pcm_buffer.h"
#include "fifo_buffer.h"
#include "growing_fifo.h"
#include <assert.h>
#include <string.h>
......@@ -28,8 +29,7 @@
struct null_encoder {
struct encoder encoder;
struct pcm_buffer buffer;
size_t buffer_length;
struct fifo_buffer *buffer;
};
extern const struct encoder_plugin null_encoder_plugin;
......@@ -65,7 +65,7 @@ null_encoder_close(struct encoder *_encoder)
{
struct null_encoder *encoder = (struct null_encoder *)_encoder;
pcm_buffer_deinit(&encoder->buffer);
fifo_buffer_free(encoder->buffer);
}
......@@ -76,9 +76,7 @@ null_encoder_open(struct encoder *_encoder,
{
struct null_encoder *encoder = (struct null_encoder *)_encoder;
encoder->buffer_length = 0;
pcm_buffer_init(&encoder->buffer);
encoder->buffer = growing_fifo_new();
return true;
}
......@@ -88,28 +86,26 @@ null_encoder_write(struct encoder *_encoder,
G_GNUC_UNUSED GError **error)
{
struct null_encoder *encoder = (struct null_encoder *)_encoder;
char *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length + length);
memcpy(buffer+encoder->buffer_length, data, length);
encoder->buffer_length += length;
return true;
growing_fifo_append(&encoder->buffer, data, length);
return length;
}
static size_t
null_encoder_read(struct encoder *_encoder, void *dest, size_t length)
{
struct null_encoder *encoder = (struct null_encoder *)_encoder;
char *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length);
if (length > encoder->buffer_length)
length = encoder->buffer_length;
memcpy(dest, buffer, length);
size_t max_length;
const void *src = fifo_buffer_read(encoder->buffer, &max_length);
if (src == NULL)
return 0;
encoder->buffer_length -= length;
memmove(buffer, buffer + length, encoder->buffer_length);
if (length > max_length)
length = max_length;
memcpy(dest, src, length);
fifo_buffer_consume(encoder->buffer, length);
return length;
}
......
......@@ -20,7 +20,8 @@
#include "config.h"
#include "encoder_api.h"
#include "encoder_plugin.h"
#include "pcm_buffer.h"
#include "fifo_buffer.h"
#include "growing_fifo.h"
#include <assert.h>
#include <string.h>
......@@ -29,8 +30,7 @@ struct wave_encoder {
struct encoder encoder;
unsigned bits;
struct pcm_buffer buffer;
size_t buffer_length;
struct fifo_buffer *buffer;
};
struct wave_header {
......@@ -92,7 +92,6 @@ wave_encoder_init(G_GNUC_UNUSED const struct config_param *param,
encoder = g_new(struct wave_encoder, 1);
encoder_struct_init(&encoder->encoder, &wave_encoder_plugin);
pcm_buffer_init(&encoder->buffer);
return &encoder->encoder;
}
......@@ -102,7 +101,6 @@ wave_encoder_finish(struct encoder *_encoder)
{
struct wave_encoder *encoder = (struct wave_encoder *)_encoder;
pcm_buffer_deinit(&encoder->buffer);
g_free(encoder);
}
......@@ -112,7 +110,6 @@ wave_encoder_open(struct encoder *_encoder,
G_GNUC_UNUSED GError **error)
{
struct wave_encoder *encoder = (struct wave_encoder *)_encoder;
void *buffer;
assert(audio_format_valid(audio_format));
......@@ -125,6 +122,11 @@ wave_encoder_open(struct encoder *_encoder,
encoder->bits = 16;
break;
case SAMPLE_FORMAT_S24:
audio_format->format = SAMPLE_FORMAT_S24_P32;
encoder->bits = 24;
break;
case SAMPLE_FORMAT_S24_P32:
encoder->bits = 24;
break;
......@@ -139,19 +141,29 @@ wave_encoder_open(struct encoder *_encoder,
break;
}
buffer = pcm_buffer_get(&encoder->buffer, sizeof(struct wave_header) );
encoder->buffer = growing_fifo_new();
struct wave_header *header =
growing_fifo_write(&encoder->buffer, sizeof(*header));
/* create PCM wave header in initial buffer */
fill_wave_header((struct wave_header *) buffer,
fill_wave_header(header,
audio_format->channels,
encoder->bits,
audio_format->sample_rate,
(encoder->bits / 8) * audio_format->channels );
fifo_buffer_append(encoder->buffer, sizeof(*header));
encoder->buffer_length = sizeof(struct wave_header);
return true;
}
static void
wave_encoder_close(struct encoder *_encoder)
{
struct wave_encoder *encoder = (struct wave_encoder *)_encoder;
fifo_buffer_free(encoder->buffer);
}
static inline size_t
pcm16_to_wave(uint16_t *dst16, const uint16_t *src16, size_t length)
{
......@@ -198,9 +210,8 @@ wave_encoder_write(struct encoder *_encoder,
G_GNUC_UNUSED GError **error)
{
struct wave_encoder *encoder = (struct wave_encoder *)_encoder;
void *dst;
dst = pcm_buffer_get(&encoder->buffer, encoder->buffer_length + length);
void *dst = growing_fifo_write(&encoder->buffer, length);
#if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
switch (encoder->bits) {
......@@ -232,7 +243,7 @@ wave_encoder_write(struct encoder *_encoder,
#error G_BYTE_ORDER set to G_PDP_ENDIAN is not supported by wave_encoder
#endif
encoder->buffer_length += length;
fifo_buffer_append(encoder->buffer, length);
return true;
}
......@@ -240,16 +251,17 @@ static size_t
wave_encoder_read(struct encoder *_encoder, void *dest, size_t length)
{
struct wave_encoder *encoder = (struct wave_encoder *)_encoder;
uint8_t *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length );
if (length > encoder->buffer_length)
length = encoder->buffer_length;
memcpy(dest, buffer, length);
size_t max_length;
const void *src = fifo_buffer_read(encoder->buffer, &max_length);
if (src == NULL)
return 0;
encoder->buffer_length -= length;
memmove(buffer, buffer + length, encoder->buffer_length);
if (length > max_length)
length = max_length;
memcpy(dest, src, length);
fifo_buffer_consume(encoder->buffer, length);
return length;
}
......@@ -264,6 +276,7 @@ const struct encoder_plugin wave_encoder_plugin = {
.init = wave_encoder_init,
.finish = wave_encoder_finish,
.open = wave_encoder_open,
.close = wave_encoder_close,
.write = wave_encoder_write,
.read = wave_encoder_read,
.get_mime_type = wave_encoder_get_mime_type,
......
......@@ -21,7 +21,6 @@
#include "event_pipe.h"
#include "fd_util.h"
#include "mpd_error.h"
#include "glib_socket.h"
#include <stdbool.h>
#include <assert.h>
......@@ -95,7 +94,11 @@ void event_pipe_init(void)
if (ret < 0)
MPD_ERROR("Couldn't open pipe: %s", strerror(errno));
channel = g_io_channel_new_socket(event_pipe[0]);
#ifndef G_OS_WIN32
channel = g_io_channel_unix_new(event_pipe[0]);
#else
channel = g_io_channel_win32_new_fd(event_pipe[0]);
#endif
g_io_channel_set_encoding(channel, NULL, NULL);
g_io_channel_set_buffered(channel, false);
......
......@@ -58,6 +58,39 @@ fifo_buffer_new(size_t size)
return buffer;
}
static void
fifo_buffer_move(struct fifo_buffer *buffer);
struct fifo_buffer *
fifo_buffer_realloc(struct fifo_buffer *buffer, size_t new_size)
{
if (buffer == NULL)
return new_size > 0
? fifo_buffer_new(new_size)
: NULL;
/* existing data must fit in new size */
assert(new_size >= buffer->end - buffer->start);
if (new_size == 0) {
fifo_buffer_free(buffer);
return NULL;
}
/* compress the buffer when we're shrinking and the tail of
the buffer would exceed the new size */
if (buffer->end > new_size)
fifo_buffer_move(buffer);
/* existing data must fit in new size: second check */
assert(buffer->end <= new_size);
buffer = g_realloc(buffer, sizeof(*buffer) - sizeof(buffer->buffer) +
new_size);
buffer->size = new_size;
return buffer;
}
void
fifo_buffer_free(struct fifo_buffer *buffer)
{
......@@ -66,6 +99,22 @@ fifo_buffer_free(struct fifo_buffer *buffer)
g_free(buffer);
}
size_t
fifo_buffer_capacity(const struct fifo_buffer *buffer)
{
assert(buffer != NULL);
return buffer->size;
}
size_t
fifo_buffer_available(const struct fifo_buffer *buffer)
{
assert(buffer != NULL);
return buffer->end - buffer->start;
}
void
fifo_buffer_clear(struct fifo_buffer *buffer)
{
......
......@@ -57,12 +57,37 @@ struct fifo_buffer *
fifo_buffer_new(size_t size);
/**
* Change the capacity of the #fifo_buffer, while preserving existing
* data.
*
* @param buffer the old buffer, may be NULL
* @param new_size the requested new size of the #fifo_buffer; must
* not be smaller than the data which is stored in the old buffer
* @return the new buffer, may be NULL if the requested new size is 0
*/
struct fifo_buffer *
fifo_buffer_realloc(struct fifo_buffer *buffer, size_t new_size);
/**
* Frees the resources consumed by this #fifo_buffer object.
*/
void
fifo_buffer_free(struct fifo_buffer *buffer);
/**
* Return the capacity of the buffer, i.e. the size that was passed to
* fifo_buffer_new().
*/
size_t
fifo_buffer_capacity(const struct fifo_buffer *buffer);
/**
* Return the number of bytes currently stored in the buffer.
*/
size_t
fifo_buffer_available(const struct fifo_buffer *buffer);
/**
* Clears all data currently in this #fifo_buffer object. This does
* not overwrite the actuall buffer; it just resets the internal
* pointers.
......
......@@ -141,7 +141,7 @@ convert_filter_set(struct filter *_filter,
assert(audio_format_valid(&filter->out_audio_format));
assert(out_audio_format != NULL);
assert(audio_format_valid(out_audio_format));
assert(filter->in_audio_format.reverse_endian == 0);
assert(!filter->in_audio_format.reverse_endian);
filter->out_audio_format = *out_audio_format;
}
......@@ -195,7 +195,7 @@ replay_gain_filter_filter(struct filter *_filter,
memcpy(dest, src, src_size);
success = pcm_volume(dest, src_size, &filter->audio_format,
success = pcm_volume(dest, src_size, filter->audio_format.format,
filter->volume);
if (!success) {
g_set_error(error_r, replay_gain_quark(), 0,
......
......@@ -116,7 +116,7 @@ volume_filter_filter(struct filter *_filter, const void *src, size_t src_size,
memcpy(dest, src, src_size);
success = pcm_volume(dest, src_size, &filter->audio_format,
success = pcm_volume(dest, src_size, filter->audio_format.format,
filter->volume);
if (!success) {
g_set_error(error_r, volume_quark(), 0,
......
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "growing_fifo.h"
#include "fifo_buffer.h"
#include <assert.h>
#include <string.h>
/**
* Align buffer sizes at 8 kB boundaries. Must be a power of two.
*/
static const size_t GROWING_FIFO_ALIGN = 8192;
/**
* Align the specified size to the next #GROWING_FIFO_ALIGN boundary.
*/
static size_t
align(size_t size)
{
return ((size - 1) | (GROWING_FIFO_ALIGN - 1)) + 1;
}
struct fifo_buffer *
growing_fifo_new(void)
{
return fifo_buffer_new(GROWING_FIFO_ALIGN);
}
void *
growing_fifo_write(struct fifo_buffer **buffer_p, size_t length)
{
assert(buffer_p != NULL);
struct fifo_buffer *buffer = *buffer_p;
assert(buffer != NULL);
size_t max_length;
void *p = fifo_buffer_write(buffer, &max_length);
if (p != NULL && max_length >= length)
return p;
/* grow */
size_t new_size = fifo_buffer_available(buffer) + length;
assert(new_size > fifo_buffer_capacity(buffer));
*buffer_p = buffer = fifo_buffer_realloc(buffer, align(new_size));
/* try again */
p = fifo_buffer_write(buffer, &max_length);
assert(p != NULL);
assert(max_length >= length);
return p;
}
void
growing_fifo_append(struct fifo_buffer **buffer_p,
const void *data, size_t length)
{
void *p = growing_fifo_write(buffer_p, length);
memcpy(p, data, length);
fifo_buffer_append(*buffer_p, length);
}
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/** \file
*
* Helper functions for our FIFO buffer library (fifo_buffer.h) that
* allows growing the buffer on demand.
*
* This library is not thread safe.
*/
#ifndef MPD_GROWING_FIFO_H
#define MPD_GROWING_FIFO_H
#include <stddef.h>
struct fifo_buffer;
/**
* Allocate a new #fifo_buffer with the default size.
*/
struct fifo_buffer *
growing_fifo_new(void);
/**
* Prepares writing to the buffer, see fifo_buffer_write() for
* details. The difference is that this function will automatically
* grow the buffer if it is too small.
*
* The caller is responsible for limiting the capacity of the buffer.
*
* @param length the number of bytes that will be written
* @return a pointer to the end of the buffer (will not be NULL)
*/
void *
growing_fifo_write(struct fifo_buffer **buffer_p, size_t length);
/**
* A helper function that combines growing_fifo_write(), memcpy(),
* fifo_buffer_append().
*/
void
growing_fifo_append(struct fifo_buffer **buffer_p,
const void *data, size_t length);
#endif
......@@ -26,7 +26,6 @@
#include "input_internal.h"
#include "input_plugin.h"
#include "refcount.h"
#include "pcm_buffer.h"
#include <stdio.h>
#include <stdint.h>
......@@ -46,8 +45,6 @@ struct input_cdio_paranoia {
CdIo_t *cdio;
cdrom_paranoia_t *para;
int endian;
lsn_t lsn_from, lsn_to;
int lsn_relofs;
......@@ -55,8 +52,6 @@ struct input_cdio_paranoia {
char buffer[CDIO_CD_FRAMESIZE_RAW];
int buffer_lsn;
struct pcm_buffer conv_buffer;
};
static inline GQuark
......@@ -70,8 +65,6 @@ input_cdio_close(struct input_stream *is)
{
struct input_cdio_paranoia *i = (struct input_cdio_paranoia *)is;
pcm_buffer_deinit(&i->conv_buffer);
if (i->para)
cdio_paranoia_free(i->para);
if (i->drv)
......@@ -168,7 +161,6 @@ input_cdio_open(const char *uri,
i->cdio = NULL;
i->para = NULL;
i->trackno = parsed_uri.track;
pcm_buffer_init(&i->conv_buffer);
/* get list of CD's supporting CD-DA */
char *device = parsed_uri.device[0] != 0
......@@ -202,21 +194,24 @@ input_cdio_open(const char *uri,
return NULL;
}
i->endian = data_bigendianp(i->drv);
switch (i->endian) {
bool reverse_endian;
switch (data_bigendianp(i->drv)) {
case -1:
g_debug("cdda: drive returns unknown audio data, assuming Little Endian");
i->endian = 0;
g_debug("cdda: drive returns unknown audio data");
reverse_endian = false;
break;
case 0:
g_debug("cdda: drive returns audio data Little Endian.");
reverse_endian = G_BYTE_ORDER == G_BIG_ENDIAN;
break;
case 1:
g_debug("cdda: drive returns audio data Big Endian.");
reverse_endian = G_BYTE_ORDER == G_LITTLE_ENDIAN;
break;
default:
g_set_error(error_r, cdio_quark(), 0,
"Drive returns unknown data type %d", i->endian);
"Drive returns unknown data type %d",
data_bigendianp(i->drv));
input_cdio_close(&i->base);
return NULL;
}
......@@ -244,7 +239,9 @@ input_cdio_open(const char *uri,
i->base.size = (i->lsn_to - i->lsn_from + 1) * CDIO_CD_FRAMESIZE_RAW;
/* hack to make MPD select the "pcm" decoder plugin */
i->base.mime = g_strdup("audio/x-mpd-cdda-pcm");
i->base.mime = g_strdup(reverse_endian
? "audio/x-mpd-cdda-pcm-reverse"
: "audio/x-mpd-cdda-pcm");
return &i->base;
}
......@@ -287,17 +284,6 @@ input_cdio_seek(struct input_stream *is,
return true;
}
static inline size_t
pcm16_to_wave(uint16_t *dst16, const uint16_t *src16, size_t length)
{
size_t cnt = length >> 1;
while (cnt > 0) {
*dst16++ = GUINT16_TO_LE(*src16++);
cnt--;
}
return length;
}
static size_t
input_cdio_read(struct input_stream *is, void *ptr, size_t length,
GError **error_r)
......@@ -335,13 +321,6 @@ input_cdio_read(struct input_stream *is, void *ptr, size_t length,
"paranoia read error. Stopping.");
return 0;
}
//do the swapping if nessesary
if (cis->endian != 0) {
uint16_t *conv_buffer = pcm_buffer_get(&cis->conv_buffer, CDIO_CD_FRAMESIZE_RAW );
/* do endian conversion ! */
pcm16_to_wave( conv_buffer, (uint16_t*) rbuf, CDIO_CD_FRAMESIZE_RAW);
rbuf = (int16_t *)conv_buffer;
}
//store current buffer
memcpy(cis->buffer, rbuf, CDIO_CD_FRAMESIZE_RAW);
cis->buffer_lsn = cis->lsn_relofs;
......
......@@ -28,6 +28,10 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "input_ffmpeg"
#ifndef AV_VERSION_INT
#define AV_VERSION_INT(a, b, c) (a<<16 | b<<8 | c)
#endif
struct input_ffmpeg {
struct input_stream base;
......
......@@ -280,7 +280,20 @@ input_soup_open(const char *uri,
s->current_consumed = 0;
s->total_buffered = 0;
#if GCC_CHECK_VERSION(4,6)
#pragma GCC diagnostic push
/* the libsoup macro SOUP_METHOD_GET discards the "const"
attribute of the g_intern_static_string() return value;
don't make the gcc warning fatal: */
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
s->msg = soup_message_new(SOUP_METHOD_GET, uri);
#if GCC_CHECK_VERSION(4,6)
#pragma GCC diagnostic pop
#endif
soup_message_set_flags(s->msg, SOUP_MESSAGE_NO_REDIRECT);
soup_message_headers_append(s->msg->request_headers, "User-Agent",
......
......@@ -34,6 +34,7 @@ static struct {
void
io_thread_run(void)
{
assert(io_thread_inside());
assert(io.context != NULL);
assert(io.loop != NULL);
......@@ -43,6 +44,11 @@ io_thread_run(void)
static gpointer
io_thread_func(G_GNUC_UNUSED gpointer arg)
{
/* lock+unlock to synchronize with io_thread_start(), to be
sure that io.thread is set */
g_mutex_lock(io.mutex);
g_mutex_unlock(io.mutex);
io_thread_run();
return NULL;
}
......@@ -67,7 +73,9 @@ io_thread_start(GError **error_r)
assert(io.loop != NULL);
assert(io.thread == NULL);
g_mutex_lock(io.mutex);
io.thread = g_thread_create(io_thread_func, NULL, true, error_r);
g_mutex_unlock(io.mutex);
if (io.thread == NULL)
return false;
......@@ -123,15 +131,23 @@ io_thread_idle_add(GSourceFunc function, gpointer data)
return id;
}
guint
GSource *
io_thread_timeout_add(guint interval_ms, GSourceFunc function, gpointer data)
{
GSource *source = g_timeout_source_new(interval_ms);
g_source_set_callback(source, function, data, NULL);
g_source_attach(source, io.context);
return source;
}
GSource *
io_thread_timeout_add_seconds(guint interval,
GSourceFunc function, gpointer data)
{
GSource *source = g_timeout_source_new_seconds(interval);
g_source_set_callback(source, function, data, NULL);
guint id = g_source_attach(source, io.context);
g_source_unref(source);
return id;
g_source_attach(source, io.context);
return source;
}
struct call_data {
......
......@@ -62,7 +62,12 @@ io_thread_inside(void);
guint
io_thread_idle_add(GSourceFunc function, gpointer data);
guint
G_GNUC_MALLOC
GSource *
io_thread_timeout_add(guint interval_ms, GSourceFunc function, gpointer data);
G_GNUC_MALLOC
GSource *
io_thread_timeout_add_seconds(guint interval,
GSourceFunc function, gpointer data);
......
......@@ -39,11 +39,12 @@
#include "player_control.h"
#include "stats.h"
#include "sig_handlers.h"
#include "audio.h"
#include "audio_config.h"
#include "output_all.h"
#include "volume.h"
#include "log.h"
#include "permission.h"
#include "pcm_resample.h"
#include "replay_gain_config.h"
#include "decoder_list.h"
#include "input_init.h"
......@@ -53,8 +54,6 @@
#include "dbUtils.h"
#include "zeroconf.h"
#include "event_pipe.h"
#include "dirvec.h"
#include "songvec.h"
#include "tag_pool.h"
#include "mpd_error.h"
......@@ -345,8 +344,6 @@ int mpd_main(int argc, char *argv[])
io_thread_init();
winsock_init();
idle_init();
dirvec_init();
songvec_init();
tag_pool_init();
config_global_init();
......@@ -403,6 +400,13 @@ int mpd_main(int argc, char *argv[])
#ifdef ENABLE_ARCHIVE
archive_plugin_init_all();
#endif
if (!pcm_resample_global_init(&error)) {
g_warning("%s", error->message);
g_error_free(error);
return EXIT_FAILURE;
}
decoder_plugin_init_all();
update_global_init();
......@@ -526,8 +530,6 @@ int mpd_main(int argc, char *argv[])
#endif
config_global_finish();
tag_pool_deinit();
songvec_deinit();
dirvec_deinit();
idle_deinit();
stats_global_finish();
io_thread_deinit();
......
......@@ -27,7 +27,6 @@
#include <glib.h>
#define WINVER 0x0501
#include <windows.h>
static int service_argc;
......
......@@ -31,6 +31,10 @@
#include <assert.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <dirent.h>
static char *music_dir;
static size_t music_dir_length;
......@@ -52,24 +56,50 @@ strdup_chop_slash(const char *path_fs)
}
static void
check_directory(const char *path)
{
struct stat st;
if (stat(path, &st) < 0) {
g_warning("Failed to stat directory \"%s\": %s",
path, g_strerror(errno));
return;
}
if (!S_ISDIR(st.st_mode)) {
g_warning("Not a directory: %s", path);
return;
}
#ifndef WIN32
char *x = g_build_filename(path, ".", NULL);
if (stat(x, &st) < 0 && errno == EACCES)
g_warning("No permission to traverse (\"execute\") directory: %s",
path);
g_free(x);
#endif
DIR *dir = opendir(path);
if (dir == NULL && errno == EACCES)
g_warning("No permission to read directory: %s", path);
else
closedir(dir);
}
static void
mapper_set_music_dir(const char *path)
{
check_directory(path);
music_dir = strdup_chop_slash(path);
music_dir_length = strlen(music_dir);
if (!g_file_test(music_dir, G_FILE_TEST_IS_DIR))
g_warning("music directory is not a directory: \"%s\"",
music_dir);
}
static void
mapper_set_playlist_dir(const char *path)
{
playlist_dir = g_strdup(path);
check_directory(path);
if (!g_file_test(playlist_dir, G_FILE_TEST_IS_DIR))
g_warning("playlist directory is not a directory: \"%s\"",
playlist_dir);
playlist_dir = g_strdup(path);
}
void mapper_init(const char *_music_dir, const char *_playlist_dir)
......
......@@ -20,6 +20,7 @@
#include "config.h"
#include "mixer_api.h"
#include "output_api.h"
#include "event_pipe.h"
#include <glib.h>
#include <alsa/asoundlib.h>
......@@ -28,6 +29,15 @@
#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM"
#define VOLUME_MIXER_ALSA_INDEX_DEFAULT 0
struct alsa_mixer_source {
GSource source;
snd_mixer_t *mixer;
/** a linked list of all registered GPollFD objects */
GSList *fds;
};
struct alsa_mixer {
/** the base mixer class */
struct mixer base;
......@@ -41,6 +51,8 @@ struct alsa_mixer {
long volume_min;
long volume_max;
int volume_set;
struct alsa_mixer_source *source;
};
/**
......@@ -52,6 +64,161 @@ alsa_mixer_quark(void)
return g_quark_from_static_string("alsa_mixer");
}
/*
* GSource helper functions
*
*/
static GSList **
find_fd(GSList **list_r, int fd)
{
while (true) {
GSList *list = *list_r;
if (list == NULL)
return NULL;
GPollFD *p = list->data;
if (p->fd == fd)
return list_r;
list_r = &list->next;
}
}
static void
alsa_mixer_update_fd(struct alsa_mixer_source *source, const struct pollfd *p,
GSList **old_r)
{
GSList **found_r = find_fd(old_r, p->fd);
if (found_r == NULL) {
/* new fd */
GPollFD *q = g_new(GPollFD, 1);
q->fd = p->fd;
q->events = p->events;
g_source_add_poll(&source->source, q);
source->fds = g_slist_prepend(source->fds, q);
return;
}
GSList *found = *found_r;
*found_r = found->next;
GPollFD *q = found->data;
if (q->events != p->events) {
/* refresh events */
g_source_remove_poll(&source->source, q);
q->events = p->events;
g_source_add_poll(&source->source, q);
}
found->next = source->fds;
source->fds = found;
}
static void
alsa_mixer_update_fds(struct alsa_mixer_source *source)
{
int count = snd_mixer_poll_descriptors_count(source->mixer);
if (count < 0)
count = 0;
struct pollfd *pfds = g_new(struct pollfd, count);
count = snd_mixer_poll_descriptors(source->mixer, pfds, count);
if (count < 0)
count = 0;
GSList *old = source->fds;
source->fds = NULL;
for (int i = 0; i < count; ++i)
alsa_mixer_update_fd(source, &pfds[i], &old);
g_free(pfds);
for (; old != NULL; old = old->next) {
GPollFD *q = old->data;
g_source_remove_poll(&source->source, q);
g_free(q);
}
g_slist_free(old);
}
/*
* GSource methods
*
*/
static gboolean
alsa_mixer_source_prepare(GSource *_source, G_GNUC_UNUSED gint *timeout_r)
{
struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source;
alsa_mixer_update_fds(source);
return false;
}
static gboolean
alsa_mixer_source_check(GSource *_source)
{
struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source;
for (const GSList *i = source->fds; i != NULL; i = i->next) {
const GPollFD *poll_fd = i->data;
if (poll_fd->revents != 0)
return true;
}
return false;
}
static gboolean
alsa_mixer_source_dispatch(GSource *_source,
G_GNUC_UNUSED GSourceFunc callback,
G_GNUC_UNUSED gpointer user_data)
{
struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source;
snd_mixer_handle_events(source->mixer);
return true;
}
static void
alsa_mixer_source_finalize(GSource *_source)
{
struct alsa_mixer_source *source = (struct alsa_mixer_source *)_source;
for (GSList *i = source->fds; i != NULL; i = i->next)
g_free(i->data);
g_slist_free(source->fds);
}
static GSourceFuncs alsa_mixer_source_funcs = {
.prepare = alsa_mixer_source_prepare,
.check = alsa_mixer_source_check,
.dispatch = alsa_mixer_source_dispatch,
.finalize = alsa_mixer_source_finalize,
};
/*
* libasound callbacks
*
*/
static int
alsa_mixer_elem_callback(G_GNUC_UNUSED snd_mixer_elem_t *elem, unsigned mask)
{
if (mask & SND_CTL_EVENT_MASK_VALUE)
event_pipe_emit(PIPE_EVENT_MIXER);
return 0;
}
/*
* mixer_plugin methods
*
*/
static struct mixer *
alsa_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param,
G_GNUC_UNUSED GError **error_r)
......@@ -81,34 +248,28 @@ alsa_mixer_finish(struct mixer *data)
snd_config_update_free_global();
}
static void
alsa_mixer_close(struct mixer *data)
G_GNUC_PURE
static snd_mixer_elem_t *
alsa_mixer_lookup_elem(snd_mixer_t *handle, const char *name, unsigned idx)
{
struct alsa_mixer *am = (struct alsa_mixer *)data;
assert(am->handle != NULL);
for (snd_mixer_elem_t *elem = snd_mixer_first_elem(handle);
elem != NULL; elem = snd_mixer_elem_next(elem)) {
if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE &&
g_ascii_strcasecmp(snd_mixer_selem_get_name(elem),
name) == 0 &&
snd_mixer_selem_get_index(elem) == idx)
return elem;
}
snd_mixer_close(am->handle);
return NULL;
}
static bool
alsa_mixer_open(struct mixer *data, GError **error_r)
alsa_mixer_setup(struct alsa_mixer *am, GError **error_r)
{
struct alsa_mixer *am = (struct alsa_mixer *)data;
int err;
snd_mixer_elem_t *elem;
am->volume_set = -1;
err = snd_mixer_open(&am->handle, 0);
if (err < 0) {
g_set_error(error_r, alsa_mixer_quark(), err,
"snd_mixer_open() failed: %s", snd_strerror(err));
return false;
}
if ((err = snd_mixer_attach(am->handle, am->device)) < 0) {
alsa_mixer_close(data);
g_set_error(error_r, alsa_mixer_quark(), err,
"failed to attach to %s: %s",
am->device, snd_strerror(err));
......@@ -117,7 +278,6 @@ alsa_mixer_open(struct mixer *data, GError **error_r)
if ((err = snd_mixer_selem_register(am->handle, NULL,
NULL)) < 0) {
alsa_mixer_close(data);
g_set_error(error_r, alsa_mixer_quark(), err,
"snd_mixer_selem_register() failed: %s",
snd_strerror(err));
......@@ -125,38 +285,69 @@ alsa_mixer_open(struct mixer *data, GError **error_r)
}
if ((err = snd_mixer_load(am->handle)) < 0) {
alsa_mixer_close(data);
g_set_error(error_r, alsa_mixer_quark(), err,
"snd_mixer_load() failed: %s\n",
snd_strerror(err));
return false;
}
elem = snd_mixer_first_elem(am->handle);
am->elem = alsa_mixer_lookup_elem(am->handle, am->control, am->index);
if (am->elem == NULL) {
g_set_error(error_r, alsa_mixer_quark(), 0,
"no such mixer control: %s", am->control);
return false;
}
snd_mixer_selem_get_playback_volume_range(am->elem,
&am->volume_min,
&am->volume_max);
snd_mixer_elem_set_callback(am->elem, alsa_mixer_elem_callback);
while (elem) {
if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE) {
if ((g_ascii_strcasecmp(am->control,
snd_mixer_selem_get_name(elem)) == 0) &&
(am->index == snd_mixer_selem_get_index(elem))) {
break;
}
}
elem = snd_mixer_elem_next(elem);
am->source = (struct alsa_mixer_source *)
g_source_new(&alsa_mixer_source_funcs, sizeof(*am->source));
am->source->mixer = am->handle;
am->source->fds = NULL;
g_source_attach(&am->source->source, g_main_context_default());
return true;
}
static bool
alsa_mixer_open(struct mixer *data, GError **error_r)
{
struct alsa_mixer *am = (struct alsa_mixer *)data;
int err;
am->volume_set = -1;
err = snd_mixer_open(&am->handle, 0);
if (err < 0) {
g_set_error(error_r, alsa_mixer_quark(), err,
"snd_mixer_open() failed: %s", snd_strerror(err));
return false;
}
if (elem) {
am->elem = elem;
snd_mixer_selem_get_playback_volume_range(am->elem,
&am->volume_min,
&am->volume_max);
return true;
if (!alsa_mixer_setup(am, error_r)) {
snd_mixer_close(am->handle);
return false;
}
alsa_mixer_close(data);
g_set_error(error_r, alsa_mixer_quark(), 0,
"no such mixer control: %s", am->control);
return false;
return true;
}
static void
alsa_mixer_close(struct mixer *data)
{
struct alsa_mixer *am = (struct alsa_mixer *)data;
assert(am->handle != NULL);
g_source_destroy(&am->source->source);
g_source_unref(&am->source->source);
snd_mixer_elem_set_callback(am->elem, NULL);
snd_mixer_close(am->handle);
}
static int
......
......@@ -29,7 +29,6 @@
#include <sys/time.h>
#ifdef WIN32
#define WINVER 0x0501
#include <ws2tcpip.h>
#include <winsock.h>
#else
......
......@@ -193,6 +193,9 @@ static snd_pcm_format_t
get_bitformat(enum sample_format sample_format)
{
switch (sample_format) {
case SAMPLE_FORMAT_UNDEFINED:
return SND_PCM_FORMAT_UNKNOWN;
case SAMPLE_FORMAT_S8:
return SND_PCM_FORMAT_S8;
......@@ -210,9 +213,12 @@ get_bitformat(enum sample_format sample_format)
case SAMPLE_FORMAT_S32:
return SND_PCM_FORMAT_S32;
default:
return SND_PCM_FORMAT_UNKNOWN;
case SAMPLE_FORMAT_FLOAT:
return SND_PCM_FORMAT_FLOAT;
}
assert(false);
return SND_PCM_FORMAT_UNKNOWN;
}
static snd_pcm_format_t
......
......@@ -259,6 +259,16 @@ fifo_output_cancel(struct audio_output *ao)
}
}
static unsigned
fifo_output_delay(struct audio_output *ao)
{
struct fifo_data *fd = (struct fifo_data *)ao;
return fd->timer->started
? timer_delay(fd->timer)
: 0;
}
static size_t
fifo_output_play(struct audio_output *ao, const void *chunk, size_t size,
GError **error)
......@@ -268,9 +278,6 @@ fifo_output_play(struct audio_output *ao, const void *chunk, size_t size,
if (!fd->timer->started)
timer_start(fd->timer);
else
timer_sync(fd->timer);
timer_add(fd->timer, size);
while (true) {
......@@ -302,6 +309,7 @@ const struct audio_output_plugin fifo_output_plugin = {
.finish = fifo_output_finish,
.open = fifo_output_open,
.close = fifo_output_close,
.delay = fifo_output_delay,
.play = fifo_output_play,
.cancel = fifo_output_cancel,
};
......@@ -93,6 +93,11 @@ struct httpd_client {
*/
size_t current_position;
/**
* If DLNA streaming was an option.
*/
bool dlna_streaming_requested;
/* ICY */
/**
......@@ -234,6 +239,15 @@ httpd_client_handle_line(struct httpd_client *client, const char *line)
return true;
}
if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) {
/* Send as dlna */
client->dlna_streaming_requested = true;
/* metadata is not supported by dlna streaming, so disable it */
client->metadata_supported = false;
client->metadata_requested = false;
return true;
}
/* expect more request headers */
return true;
}
......@@ -285,16 +299,21 @@ httpd_client_send_response(struct httpd_client *client)
assert(client != NULL);
assert(client->state == RESPONSE);
if (!client->metadata_requested) {
if (client->dlna_streaming_requested) {
g_snprintf(buffer, sizeof(buffer),
"HTTP/1.1 200 OK\r\n"
"HTTP/1.1 206 OK\r\n"
"Content-Type: %s\r\n"
"Content-Length: 10000\r\n"
"Content-RangeX: 0-1000000/1000000\r\n"
"transferMode.dlna.org: Streaming\r\n"
"Accept-Ranges: bytes\r\n"
"Connection: close\r\n"
"Pragma: no-cache\r\n"
"Cache-Control: no-cache, no-store\r\n"
"realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
"contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n"
"\r\n",
client->httpd->content_type);
} else {
} else if (client->metadata_requested) {
gchar *metadata_header;
metadata_header = icy_server_metadata_header(
......@@ -307,6 +326,16 @@ httpd_client_send_response(struct httpd_client *client)
g_strlcpy(buffer, metadata_header, sizeof(buffer));
g_free(metadata_header);
} else { /* revert to a normal HTTP request */
g_snprintf(buffer, sizeof(buffer),
"HTTP/1.1 200 OK\r\n"
"Content-Type: %s\r\n"
"Connection: close\r\n"
"Pragma: no-cache\r\n"
"Cache-Control: no-cache, no-store\r\n"
"\r\n",
client->httpd->content_type);
}
status = g_io_channel_write_chars(client->channel,
......@@ -476,6 +505,7 @@ httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported)
client->input = fifo_buffer_new(4096);
client->state = REQUEST;
client->dlna_streaming_requested = false;
client->metadata_supported = metadata_supported;
client->metadata_requested = false;
client->metadata_sent = true;
......
......@@ -45,7 +45,6 @@ null_init(const struct config_param *param, GError **error_r)
}
nd->sync = config_get_block_bool(param, "sync", true);
nd->timer = NULL;
return &nd->base;
}
......@@ -55,8 +54,6 @@ null_finish(struct audio_output *ao)
{
struct null_data *nd = (struct null_data *)ao;
assert(nd->timer == NULL);
ao_base_finish(&nd->base);
g_free(nd);
}
......@@ -78,10 +75,18 @@ null_close(struct audio_output *ao)
{
struct null_data *nd = (struct null_data *)ao;
if (nd->timer != NULL) {
if (nd->sync)
timer_free(nd->timer);
nd->timer = NULL;
}
}
static unsigned
null_delay(struct audio_output *ao)
{
struct null_data *nd = (struct null_data *)ao;
return nd->sync && nd->timer->started
? timer_delay(nd->timer)
: 0;
}
static size_t
......@@ -96,9 +101,6 @@ null_play(struct audio_output *ao, G_GNUC_UNUSED const void *chunk, size_t size,
if (!timer->started)
timer_start(timer);
else
timer_sync(timer);
timer_add(timer, size);
return size;
......@@ -121,6 +123,7 @@ const struct audio_output_plugin null_output_plugin = {
.finish = null_finish,
.open = null_open,
.close = null_close,
.delay = null_delay,
.play = null_play,
.cancel = null_cancel,
};
......@@ -20,7 +20,6 @@
#include "config.h"
#include "openal_output_plugin.h"
#include "output_api.h"
#include "timer.h"
#include <glib.h>
......@@ -44,9 +43,8 @@ struct openal_data {
const char *device_name;
ALCdevice *device;
ALCcontext *context;
struct timer *timer;
ALuint buffers[NUM_BUFFERS];
int filled;
unsigned filled;
ALuint source;
ALenum format;
ALuint frequency;
......@@ -61,32 +59,49 @@ openal_output_quark(void)
static ALenum
openal_audio_format(struct audio_format *audio_format)
{
/* note: cannot map SAMPLE_FORMAT_S8 to AL_FORMAT_STEREO8 or
AL_FORMAT_MONO8 since OpenAL expects unsigned 8 bit
samples, while MPD uses signed samples */
switch (audio_format->format) {
case SAMPLE_FORMAT_S16:
if (audio_format->channels == 2)
return AL_FORMAT_STEREO16;
if (audio_format->channels == 1)
return AL_FORMAT_MONO16;
break;
case SAMPLE_FORMAT_S8:
if (audio_format->channels == 2)
return AL_FORMAT_STEREO8;
if (audio_format->channels == 1)
return AL_FORMAT_MONO8;
break;
/* fall back to mono */
audio_format->channels = 1;
return openal_audio_format(audio_format);
default:
/* fall back to 16 bit */
audio_format->format = SAMPLE_FORMAT_S16;
if (audio_format->channels == 2)
return AL_FORMAT_STEREO16;
if (audio_format->channels == 1)
return AL_FORMAT_MONO16;
break;
return openal_audio_format(audio_format);
}
}
G_GNUC_PURE
static inline ALint
openal_get_source_i(const struct openal_data *od, ALenum param)
{
ALint value;
alGetSourcei(od->source, param, &value);
return value;
}
return 0;
G_GNUC_PURE
static inline bool
openal_has_processed(const struct openal_data *od)
{
return openal_get_source_i(od, AL_BUFFERS_PROCESSED) > 0;
}
G_GNUC_PURE
static inline ALint
openal_is_playing(const struct openal_data *od)
{
return openal_get_source_i(od, AL_SOURCE_STATE) == AL_PLAYING;
}
static bool
......@@ -115,19 +130,6 @@ openal_setup_context(struct openal_data *od,
return true;
}
static void
openal_unqueue_buffers(struct openal_data *od)
{
ALint num;
ALuint buffer;
alGetSourcei(od->source, AL_BUFFERS_QUEUED, &num);
while (num--) {
alSourceUnqueueBuffers(od->source, 1, &buffer);
}
}
static struct audio_output *
openal_init(const struct config_param *param, GError **error_r)
{
......@@ -166,14 +168,6 @@ openal_open(struct audio_output *ao, struct audio_format *audio_format,
od->format = openal_audio_format(audio_format);
if (!od->format) {
struct audio_format_string s;
g_set_error(error, openal_output_quark(), 0,
"Unsupported audio format: %s",
audio_format_to_string(audio_format, &s));
return false;
}
if (!openal_setup_context(od, error)) {
return false;
}
......@@ -197,7 +191,6 @@ openal_open(struct audio_output *ao, struct audio_format *audio_format,
}
od->filled = 0;
od->timer = timer_new(audio_format);
od->frequency = audio_format->sample_rate;
return true;
......@@ -208,7 +201,6 @@ openal_close(struct audio_output *ao)
{
struct openal_data *od = (struct openal_data *)ao;
timer_free(od->timer);
alcMakeContextCurrent(od->context);
alDeleteSources(1, &od->source);
alDeleteBuffers(NUM_BUFFERS, od->buffers);
......@@ -216,48 +208,47 @@ openal_close(struct audio_output *ao)
alcCloseDevice(od->device);
}
static unsigned
openal_delay(struct audio_output *ao)
{
struct openal_data *od = (struct openal_data *)ao;
return od->filled < NUM_BUFFERS || openal_has_processed(od)
? 0
/* we don't know exactly how long we must wait for the
next buffer to finish, so this is a random
guess: */
: 50;
}
static size_t
openal_play(struct audio_output *ao, const void *chunk, size_t size,
G_GNUC_UNUSED GError **error)
{
struct openal_data *od = (struct openal_data *)ao;
ALuint buffer;
ALint num, state;
if (alcGetCurrentContext() != od->context) {
alcMakeContextCurrent(od->context);
}
alGetSourcei(od->source, AL_BUFFERS_PROCESSED, &num);
if (od->filled < NUM_BUFFERS) {
/* fill all buffers */
buffer = od->buffers[od->filled];
od->filled++;
} else {
/* wait for processed buffer */
while (num < 1) {
if (!od->timer->started) {
timer_start(od->timer);
} else {
timer_sync(od->timer);
}
timer_add(od->timer, size);
alGetSourcei(od->source, AL_BUFFERS_PROCESSED, &num);
}
while (!openal_has_processed(od))
g_usleep(10);
alSourceUnqueueBuffers(od->source, 1, &buffer);
}
alBufferData(buffer, od->format, chunk, size, od->frequency);
alSourceQueueBuffers(od->source, 1, &buffer);
alGetSourcei(od->source, AL_SOURCE_STATE, &state);
if (state != AL_PLAYING) {
if (!openal_is_playing(od))
alSourcePlay(od->source);
}
return size;
}
......@@ -270,7 +261,10 @@ openal_cancel(struct audio_output *ao)
od->filled = 0;
alcMakeContextCurrent(od->context);
alSourceStop(od->source);
openal_unqueue_buffers(od);
/* force-unqueue all buffers */
alSourcei(od->source, AL_BUFFER, 0);
od->filled = 0;
}
const struct audio_output_plugin openal_output_plugin = {
......@@ -279,6 +273,7 @@ const struct audio_output_plugin openal_output_plugin = {
.finish = openal_finish,
.open = openal_open,
.close = openal_close,
.delay = openal_delay,
.play = openal_play,
.cancel = openal_cancel,
};
......@@ -395,6 +395,7 @@ sample_format_to_oss(enum sample_format format)
{
switch (format) {
case SAMPLE_FORMAT_UNDEFINED:
case SAMPLE_FORMAT_FLOAT:
return AFMT_QUERY;
case SAMPLE_FORMAT_S8:
......
......@@ -114,75 +114,6 @@ osx_output_finish(struct audio_output *ao)
g_free(od);
}
static void
osx_output_cancel(struct audio_output *ao)
{
struct osx_output *od = (struct osx_output *)ao;
g_mutex_lock(od->mutex);
od->len = 0;
g_mutex_unlock(od->mutex);
}
static void
osx_output_close(struct audio_output *ao)
{
struct osx_output *od = (struct osx_output *)ao;
AudioOutputUnitStop(od->au);
AudioUnitUninitialize(od->au);
CloseComponent(od->au);
}
static OSStatus
osx_render(void *vdata,
G_GNUC_UNUSED AudioUnitRenderActionFlags *io_action_flags,
G_GNUC_UNUSED const AudioTimeStamp *in_timestamp,
G_GNUC_UNUSED UInt32 in_bus_number,
G_GNUC_UNUSED UInt32 in_number_frames,
AudioBufferList *buffer_list)
{
struct osx_output *od = (struct osx_output *) vdata;
AudioBuffer *buffer = &buffer_list->mBuffers[0];
size_t buffer_size = buffer->mDataByteSize;
size_t bytes_to_copy;
size_t trailer_length;
size_t dest_pos = 0;
g_mutex_lock(od->mutex);
bytes_to_copy = MIN(od->len, buffer_size);
buffer_size = bytes_to_copy;
od->len -= bytes_to_copy;
trailer_length = od->buffer_size - od->pos;
if (bytes_to_copy > trailer_length) {
memcpy((unsigned char*)buffer->mData + dest_pos,
od->buffer + od->pos, trailer_length);
od->pos = 0;
dest_pos += trailer_length;
bytes_to_copy -= trailer_length;
}
memcpy((unsigned char*)buffer->mData + dest_pos,
od->buffer + od->pos, bytes_to_copy);
od->pos += bytes_to_copy;
if (od->pos >= od->buffer_size)
od->pos = 0;
g_cond_signal(od->condition);
g_mutex_unlock(od->mutex);
buffer->mDataByteSize = buffer_size;
if (!buffer_size) {
g_usleep(1000);
}
return 0;
}
static bool
osx_output_set_device(struct osx_output *oo, GError **error)
{
......@@ -272,65 +203,136 @@ done:
return ret;
}
static OSStatus
osx_render(void *vdata,
G_GNUC_UNUSED AudioUnitRenderActionFlags *io_action_flags,
G_GNUC_UNUSED const AudioTimeStamp *in_timestamp,
G_GNUC_UNUSED UInt32 in_bus_number,
G_GNUC_UNUSED UInt32 in_number_frames,
AudioBufferList *buffer_list)
{
struct osx_output *od = (struct osx_output *) vdata;
AudioBuffer *buffer = &buffer_list->mBuffers[0];
size_t buffer_size = buffer->mDataByteSize;
size_t bytes_to_copy;
size_t trailer_length;
size_t dest_pos = 0;
g_mutex_lock(od->mutex);
bytes_to_copy = MIN(od->len, buffer_size);
od->len -= bytes_to_copy;
trailer_length = od->buffer_size - od->pos;
if (bytes_to_copy > trailer_length) {
memcpy((unsigned char*)buffer->mData + dest_pos,
od->buffer + od->pos, trailer_length);
od->pos = 0;
dest_pos += trailer_length;
bytes_to_copy -= trailer_length;
}
memcpy((unsigned char*)buffer->mData + dest_pos,
od->buffer + od->pos, bytes_to_copy);
od->pos += bytes_to_copy;
if (od->pos >= od->buffer_size)
od->pos = 0;
g_cond_signal(od->condition);
g_mutex_unlock(od->mutex);
if (bytes_to_copy < buffer_size)
memset((unsigned char*)buffer->mData + bytes_to_copy, 0,
buffer_size - bytes_to_copy);
return 0;
}
static bool
osx_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error)
osx_output_enable(struct audio_output *ao, GError **error_r)
{
struct osx_output *od = (struct osx_output *)ao;
ComponentDescription desc;
Component comp;
AURenderCallbackStruct callback;
AudioStreamBasicDescription stream_description;
OSStatus status;
ComponentResult result;
struct osx_output *oo = (struct osx_output *)ao;
ComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = od->component_subtype;
desc.componentSubType = oo->component_subtype;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
comp = FindNextComponent(NULL, &desc);
Component comp = FindNextComponent(NULL, &desc);
if (comp == 0) {
g_set_error(error, osx_output_quark(), 0,
g_set_error(error_r, osx_output_quark(), 0,
"Error finding OS X component");
return false;
}
status = OpenAComponent(comp, &od->au);
OSStatus status = OpenAComponent(comp, &oo->au);
if (status != noErr) {
g_set_error(error, osx_output_quark(), status,
g_set_error(error_r, osx_output_quark(), status,
"Unable to open OS X component: %s",
GetMacOSStatusCommentString(status));
return false;
}
status = AudioUnitInitialize(od->au);
if (status != noErr) {
CloseComponent(od->au);
g_set_error(error, osx_output_quark(), status,
"Unable to initialize OS X audio unit: %s",
GetMacOSStatusCommentString(status));
if (!osx_output_set_device(oo, error_r)) {
CloseComponent(oo->au);
return false;
}
if (!osx_output_set_device(od, error))
return false;
AURenderCallbackStruct callback;
callback.inputProc = osx_render;
callback.inputProcRefCon = od;
callback.inputProcRefCon = oo;
result = AudioUnitSetProperty(od->au,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, 0,
&callback, sizeof(callback));
ComponentResult result =
AudioUnitSetProperty(oo->au,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, 0,
&callback, sizeof(callback));
if (result != noErr) {
AudioUnitUninitialize(od->au);
CloseComponent(od->au);
g_set_error(error, osx_output_quark(), result,
CloseComponent(oo->au);
g_set_error(error_r, osx_output_quark(), result,
"unable to set callback for OS X audio unit");
return false;
}
return true;
}
static void
osx_output_disable(struct audio_output *ao)
{
struct osx_output *oo = (struct osx_output *)ao;
CloseComponent(oo->au);
}
static void
osx_output_cancel(struct audio_output *ao)
{
struct osx_output *od = (struct osx_output *)ao;
g_mutex_lock(od->mutex);
od->len = 0;
g_mutex_unlock(od->mutex);
}
static void
osx_output_close(struct audio_output *ao)
{
struct osx_output *od = (struct osx_output *)ao;
AudioOutputUnitStop(od->au);
AudioUnitUninitialize(od->au);
}
static bool
osx_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error)
{
struct osx_output *od = (struct osx_output *)ao;
AudioStreamBasicDescription stream_description;
stream_description.mSampleRate = audio_format->sample_rate;
stream_description.mFormatID = kAudioFormatLinearPCM;
stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
......@@ -344,9 +346,13 @@ osx_output_open(struct audio_output *ao, struct audio_format *audio_format, GErr
stream_description.mBitsPerChannel = 16;
break;
case SAMPLE_FORMAT_S32:
stream_description.mBitsPerChannel = 32;
break;
default:
audio_format->format = SAMPLE_FORMAT_S16;
stream_description.mBitsPerChannel = 16;
audio_format->format = SAMPLE_FORMAT_S32;
stream_description.mBitsPerChannel = 32;
break;
}
......@@ -360,18 +366,25 @@ osx_output_open(struct audio_output *ao, struct audio_format *audio_format, GErr
stream_description.mBytesPerFrame = stream_description.mBytesPerPacket;
stream_description.mChannelsPerFrame = audio_format->channels;
result = AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, 0,
&stream_description,
sizeof(stream_description));
ComponentResult result =
AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, 0,
&stream_description,
sizeof(stream_description));
if (result != noErr) {
AudioUnitUninitialize(od->au);
CloseComponent(od->au);
g_set_error(error, osx_output_quark(), result,
"Unable to set format on OS X device");
return false;
}
OSStatus status = AudioUnitInitialize(od->au);
if (status != noErr) {
g_set_error(error, osx_output_quark(), status,
"Unable to initialize OS X audio unit: %s",
GetMacOSStatusCommentString(status));
return false;
}
/* create a buffer of 1s */
od->buffer_size = (audio_format->sample_rate) *
audio_format_frame_size(audio_format);
......@@ -382,6 +395,7 @@ osx_output_open(struct audio_output *ao, struct audio_format *audio_format, GErr
status = AudioOutputUnitStart(od->au);
if (status != 0) {
AudioUnitUninitialize(od->au);
g_set_error(error, osx_output_quark(), status,
"unable to start audio output: %s",
GetMacOSStatusCommentString(status));
......@@ -430,6 +444,8 @@ const struct audio_output_plugin osx_output_plugin = {
.test_default_device = osx_output_test_default_device,
.init = osx_output_init,
.finish = osx_output_finish,
.enable = osx_output_enable,
.disable = osx_output_disable,
.open = osx_output_open,
.close = osx_output_close,
.play = osx_output_play,
......
......@@ -328,7 +328,7 @@ open_udp_socket(char *hostname, unsigned short *port,
GError **error_r)
{
int sd;
int size = 30000;
const int size = 30000;
/* socket creation */
sd = socket(PF_INET, SOCK_DGRAM, 0);
......@@ -338,7 +338,7 @@ open_udp_socket(char *hostname, unsigned short *port,
g_strerror(errno));
return -1;
}
if (setsockopt(sd, SOL_SOCKET, SO_SNDBUF, (void *) &size, sizeof(size)) < 0) {
if (setsockopt(sd, SOL_SOCKET, SO_SNDBUF, (const char *) &size, sizeof(size)) < 0) {
g_set_error(error_r, raop_output_quark(), errno,
"failed to set UDP buffer size: %s",
g_strerror(errno));
......
......@@ -145,6 +145,43 @@ roar_finish(struct audio_output *ao)
g_free(self);
}
static void
roar_use_audio_format(struct roar_audio_info *info,
struct audio_format *audio_format)
{
info->rate = audio_format->sample_rate;
info->channels = audio_format->channels;
info->codec = ROAR_CODEC_PCM_S;
switch (audio_format->format) {
case SAMPLE_FORMAT_UNDEFINED:
info->bits = 16;
audio_format->format = SAMPLE_FORMAT_S16;
break;
case SAMPLE_FORMAT_S8:
info->bits = 8;
break;
case SAMPLE_FORMAT_S16:
info->bits = 16;
break;
case SAMPLE_FORMAT_S24:
info->bits = 24;
break;
case SAMPLE_FORMAT_S24_P32:
info->bits = 32;
audio_format->format = SAMPLE_FORMAT_S32;
break;
case SAMPLE_FORMAT_S32:
info->bits = 32;
break;
}
}
static bool
roar_open(struct audio_output *ao, struct audio_format *audio_format, GError **error)
{
......@@ -169,33 +206,7 @@ roar_open(struct audio_output *ao, struct audio_format *audio_format, GError **e
return false;
}
self->info.rate = audio_format->sample_rate;
self->info.channels = audio_format->channels;
self->info.codec = ROAR_CODEC_PCM_S;
switch (audio_format->format)
{
case SAMPLE_FORMAT_S8:
self->info.bits = 8;
break;
case SAMPLE_FORMAT_S16:
self->info.bits = 16;
break;
case SAMPLE_FORMAT_S24:
self->info.bits = 24;
break;
case SAMPLE_FORMAT_S24_P32:
self->info.bits = 32;
audio_format->format = SAMPLE_FORMAT_S32;
break;
case SAMPLE_FORMAT_S32:
self->info.bits = 32;
break;
default:
self->info.bits = 16;
audio_format->format = SAMPLE_FORMAT_S16;
}
audio_format->reverse_endian = 0;
roar_use_audio_format(&self->info, audio_format);
if (roar_vs_stream(self->vss, &(self->info), ROAR_DIR_PLAY,
&(self->err)) < 0)
......
......@@ -74,33 +74,45 @@ winmm_output_test_default_device(void)
return waveOutGetNumDevs() > 0;
}
static UINT
get_device_id(const char *device_name)
static bool
get_device_id(const char *device_name, UINT *device_id, GError **error_r)
{
/* if device is not specified use wave mapper */
if (device_name == NULL)
return WAVE_MAPPER;
if (device_name == NULL) {
*device_id = WAVE_MAPPER;
return true;
}
UINT numdevs = waveOutGetNumDevs();
/* check for device id */
char *endptr;
UINT id = strtoul(device_name, &endptr, 0);
if (endptr > device_name && *endptr == 0)
return id;
if (endptr > device_name && *endptr == 0) {
if (id >= numdevs)
goto fail;
*device_id = id;
return true;
}
/* check for device name */
for (UINT i = 0; i < waveOutGetNumDevs(); i++) {
for (UINT i = 0; i < numdevs; i++) {
WAVEOUTCAPS caps;
MMRESULT result = waveOutGetDevCaps(i, &caps, sizeof(caps));
if (result != MMSYSERR_NOERROR)
continue;
/* szPname is only 32 chars long, so it is often truncated.
Use partial match to work around this. */
if (strstr(device_name, caps.szPname) == device_name)
return i;
if (strstr(device_name, caps.szPname) == device_name) {
*device_id = i;
return true;
}
}
/* fallback to wave mapper */
return WAVE_MAPPER;
fail:
g_set_error(error_r, winmm_output_quark(), 0,
"device \"%s\" is not found", device_name);
return false;
}
static struct audio_output *
......@@ -113,7 +125,12 @@ winmm_output_init(const struct config_param *param, GError **error_r)
}
const char *device = config_get_block_string(param, "device", NULL);
wo->device_id = get_device_id(device);
if (!get_device_id(device, &wo->device_id, error_r)) {
ao_base_finish(&wo->base);
g_free(wo);
return NULL;
}
return &wo->base;
}
......
......@@ -126,9 +126,6 @@ audio_output_disable(struct audio_output *ao)
ao_lock_command(ao, AO_COMMAND_DISABLE);
}
static void
audio_output_close_locked(struct audio_output *ao);
/**
* Object must be locked (and unlocked) by the caller.
*/
......
......@@ -213,6 +213,8 @@ ao_base_init(struct audio_output *ao,
ao->cond = g_cond_new();
ao->mixer = NULL;
ao->replay_gain_filter = NULL;
ao->other_replay_gain_filter = NULL;
/* the "convert" filter must be the last one in the chain */
......
......@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "output_plugin.h"
#include "output_internal.h"
......
......@@ -401,8 +401,12 @@ ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk,
char *dest = pcm_buffer_get(&ao->cross_fade_buffer,
other_length);
memcpy(dest, other_data, other_length);
pcm_mix(dest, data, length, &ao->in_audio_format,
1.0 - chunk->mix_ratio);
if (!pcm_mix(dest, data, length, ao->in_audio_format.format,
1.0 - chunk->mix_ratio)) {
g_warning("Cannot cross-fade format %s",
sample_format_to_string(ao->in_audio_format.format));
return NULL;
}
data = dest;
length = other_length;
......
......@@ -27,6 +27,11 @@
#include <assert.h>
#include <string.h>
#ifdef G_OS_WIN32
#include <windows.h> // for GetACP()
#include <stdio.h> // for sprintf()
#endif
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "path"
......@@ -85,11 +90,22 @@ void path_global_init(void)
charset = config_get_string(CONF_FS_CHARSET, NULL);
if (charset == NULL) {
#ifndef G_OS_WIN32
const gchar **encodings;
g_get_filename_charsets(&encodings);
if (encodings[0] != NULL && *encodings[0] != '\0')
charset = encodings[0];
#else /* G_OS_WIN32 */
/* Glib claims that file system encoding is always utf-8
* on native Win32 (i.e. not Cygwin).
* However this is true only if <gstdio.h> helpers are used.
* MPD uses regular <stdio.h> functions.
* Those functions use encoding determined by GetACP(). */
char win_charset[13];
sprintf(win_charset, "cp%u", GetACP());
charset = win_charset;
#endif
}
if (charset) {
......
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "pcm_buffer.h"
#include "poison.h"
/**
* Align the specified size to the next 8k boundary.
*/
G_GNUC_CONST
static size_t
align_8k(size_t size)
{
return ((size - 1) | 0x1fff) + 1;
}
void *
pcm_buffer_get(struct pcm_buffer *buffer, size_t size)
{
assert(buffer != NULL);
if (buffer->size < size) {
/* free the old buffer */
g_free(buffer->buffer);
buffer->size = align_8k(size);
buffer->buffer = g_malloc(buffer->size);
} else {
/* discard old buffer contents */
poison_undefined(buffer->buffer, buffer->size);
}
assert(buffer->size >= size);
return buffer->buffer;
}
......@@ -20,8 +20,12 @@
#ifndef PCM_BUFFER_H
#define PCM_BUFFER_H
#include "check.h"
#include <glib.h>
#include <assert.h>
/**
* Manager for a temporary buffer which grows as needed. We could
* allocate a new buffer every time pcm_convert() is called, but that
......@@ -39,6 +43,8 @@ struct pcm_buffer {
static inline void
pcm_buffer_init(struct pcm_buffer *buffer)
{
assert(buffer != NULL);
buffer->buffer = NULL;
buffer->size = 0;
}
......@@ -49,6 +55,8 @@ pcm_buffer_init(struct pcm_buffer *buffer)
static inline void
pcm_buffer_deinit(struct pcm_buffer *buffer)
{
assert(buffer != NULL);
g_free(buffer->buffer);
buffer->buffer = NULL;
......@@ -58,19 +66,8 @@ pcm_buffer_deinit(struct pcm_buffer *buffer)
* Get the buffer, and guarantee a minimum size. This buffer becomes
* invalid with the next pcm_buffer_get() call.
*/
static inline void *
pcm_buffer_get(struct pcm_buffer *buffer, size_t size)
{
if (buffer->size < size) {
/* free the old buffer */
g_free(buffer->buffer);
/* allocate a new buffer; align at 8 kB boundaries */
buffer->size = ((size - 1) | 0x1fff) + 1;
buffer->buffer = g_malloc(buffer->size);
}
return buffer->buffer;
}
G_GNUC_MALLOC
void *
pcm_buffer_get(struct pcm_buffer *buffer, size_t size);
#endif
......@@ -28,43 +28,63 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "pcm"
static inline uint16_t swab16(uint16_t x)
{
return (x << 8) | (x >> 8);
}
const int16_t *pcm_byteswap_16(struct pcm_buffer *buffer,
const int16_t *src, size_t len)
{
unsigned i;
int16_t *buf = pcm_buffer_get(buffer, len);
assert(buf != NULL);
for (i = 0; i < len / 2; i++)
buf[i] = swab16(src[i]);
const int16_t *src_end = src + len / sizeof(*src);
int16_t *dest = buf;
while (src < src_end) {
const int16_t x = *src++;
*dest++ = GUINT16_SWAP_LE_BE(x);
}
return buf;
}
static inline uint32_t swab32(uint32_t x)
{
return (x << 24) |
((x & 0xff00) << 8) |
((x & 0xff0000) >> 8) |
(x >> 24);
}
const int32_t *pcm_byteswap_32(struct pcm_buffer *buffer,
const int32_t *src, size_t len)
{
unsigned i;
int32_t *buf = pcm_buffer_get(buffer, len);
assert(buf != NULL);
for (i = 0; i < len / 4; i++)
buf[i] = swab32(src[i]);
const int32_t *src_end = src + len / sizeof(*src);
int32_t *dest = buf;
while (src < src_end) {
const int32_t x = *src++;
*dest++ = GUINT32_SWAP_LE_BE(x);
}
return buf;
}
const void *
pcm_byteswap(struct pcm_buffer *buffer, enum sample_format format,
const void *src, size_t size)
{
switch (format) {
case SAMPLE_FORMAT_UNDEFINED:
case SAMPLE_FORMAT_S24:
case SAMPLE_FORMAT_FLOAT:
/* not implemented */
return NULL;
case SAMPLE_FORMAT_S8:
return src;
case SAMPLE_FORMAT_S16:
return pcm_byteswap_16(buffer, src, size);
case SAMPLE_FORMAT_S24_P32:
case SAMPLE_FORMAT_S32:
return pcm_byteswap_32(buffer, src, size);
}
/* unreachable */
assert(false);
return NULL;
}
......@@ -20,6 +20,8 @@
#ifndef MPD_PCM_BYTESWAP_H
#define MPD_PCM_BYTESWAP_H
#include "audio_format.h"
#include <stdint.h>
#include <stddef.h>
......@@ -47,4 +49,19 @@ const int16_t *pcm_byteswap_16(struct pcm_buffer *buffer,
const int32_t *pcm_byteswap_32(struct pcm_buffer *buffer,
const int32_t *src, size_t len);
/**
* Changes the endianness of PCM data.
*
* @param buffer the destination pcm_buffer object
* @param format the sample format (both input and output)
* @param src the source PCM buffer
* @param src_size the number of bytes in #src
* @return the destination buffer, or NULL if the sample format is not
* supported
*/
G_GNUC_MALLOC
const void *
pcm_byteswap(struct pcm_buffer *buffer, enum sample_format format,
const void *src, size_t size);
#endif
......@@ -20,14 +20,16 @@
#include "config.h"
#include "pcm_channels.h"
#include "pcm_buffer.h"
#include "pcm_utils.h"
#include <assert.h>
static void
pcm_convert_channels_16_1_to_2(int16_t *dest, const int16_t *src,
unsigned num_frames)
pcm_convert_channels_16_1_to_2(int16_t *restrict dest,
const int16_t *restrict src,
const int16_t *restrict src_end)
{
while (num_frames-- > 0) {
while (src < src_end) {
int16_t value = *src++;
*dest++ = value;
......@@ -36,10 +38,11 @@ pcm_convert_channels_16_1_to_2(int16_t *dest, const int16_t *src,
}
static void
pcm_convert_channels_16_2_to_1(int16_t *dest, const int16_t *src,
unsigned num_frames)
pcm_convert_channels_16_2_to_1(int16_t *restrict dest,
const int16_t *restrict src,
const int16_t *restrict src_end)
{
while (num_frames-- > 0) {
while (src < src_end) {
int32_t a = *src++, b = *src++;
*dest++ = (a + b) / 2;
......@@ -47,15 +50,16 @@ pcm_convert_channels_16_2_to_1(int16_t *dest, const int16_t *src,
}
static void
pcm_convert_channels_16_n_to_2(int16_t *dest,
unsigned src_channels, const int16_t *src,
unsigned num_frames)
pcm_convert_channels_16_n_to_2(int16_t *restrict dest,
unsigned src_channels,
const int16_t *restrict src,
const int16_t *restrict src_end)
{
unsigned c;
assert(src_channels > 0);
while (num_frames-- > 0) {
while (src < src_end) {
int32_t sum = 0;
int16_t value;
......@@ -71,23 +75,25 @@ pcm_convert_channels_16_n_to_2(int16_t *dest,
const int16_t *
pcm_convert_channels_16(struct pcm_buffer *buffer,
uint8_t dest_channels,
uint8_t src_channels, const int16_t *src,
unsigned dest_channels,
unsigned src_channels, const int16_t *src,
size_t src_size, size_t *dest_size_r)
{
unsigned num_frames = src_size / src_channels / sizeof(*src);
unsigned dest_size = num_frames * dest_channels * sizeof(*src);
int16_t *dest = pcm_buffer_get(buffer, dest_size);
assert(src_size % (sizeof(*src) * src_channels) == 0);
size_t dest_size = src_size / src_channels * dest_channels;
*dest_size_r = dest_size;
int16_t *dest = pcm_buffer_get(buffer, dest_size);
const int16_t *src_end = pcm_end_pointer(src, src_size);
if (src_channels == 1 && dest_channels == 2)
pcm_convert_channels_16_1_to_2(dest, src, num_frames);
pcm_convert_channels_16_1_to_2(dest, src, src_end);
else if (src_channels == 2 && dest_channels == 1)
pcm_convert_channels_16_2_to_1(dest, src, num_frames);
pcm_convert_channels_16_2_to_1(dest, src, src_end);
else if (dest_channels == 2)
pcm_convert_channels_16_n_to_2(dest, src_channels, src,
num_frames);
src_end);
else
return NULL;
......@@ -95,10 +101,11 @@ pcm_convert_channels_16(struct pcm_buffer *buffer,
}
static void
pcm_convert_channels_24_1_to_2(int32_t *dest, const int32_t *src,
unsigned num_frames)
pcm_convert_channels_24_1_to_2(int32_t *restrict dest,
const int32_t *restrict src,
const int32_t *restrict src_end)
{
while (num_frames-- > 0) {
while (src < src_end) {
int32_t value = *src++;
*dest++ = value;
......@@ -107,10 +114,11 @@ pcm_convert_channels_24_1_to_2(int32_t *dest, const int32_t *src,
}
static void
pcm_convert_channels_24_2_to_1(int32_t *dest, const int32_t *src,
unsigned num_frames)
pcm_convert_channels_24_2_to_1(int32_t *restrict dest,
const int32_t *restrict src,
const int32_t *restrict src_end)
{
while (num_frames-- > 0) {
while (src < src_end) {
int32_t a = *src++, b = *src++;
*dest++ = (a + b) / 2;
......@@ -118,15 +126,16 @@ pcm_convert_channels_24_2_to_1(int32_t *dest, const int32_t *src,
}
static void
pcm_convert_channels_24_n_to_2(int32_t *dest,
unsigned src_channels, const int32_t *src,
unsigned num_frames)
pcm_convert_channels_24_n_to_2(int32_t *restrict dest,
unsigned src_channels,
const int32_t *restrict src,
const int32_t *restrict src_end)
{
unsigned c;
assert(src_channels > 0);
while (num_frames-- > 0) {
while (src < src_end) {
int32_t sum = 0;
int32_t value;
......@@ -142,23 +151,25 @@ pcm_convert_channels_24_n_to_2(int32_t *dest,
const int32_t *
pcm_convert_channels_24(struct pcm_buffer *buffer,
uint8_t dest_channels,
uint8_t src_channels, const int32_t *src,
unsigned dest_channels,
unsigned src_channels, const int32_t *src,
size_t src_size, size_t *dest_size_r)
{
unsigned num_frames = src_size / src_channels / sizeof(*src);
unsigned dest_size = num_frames * dest_channels * sizeof(*src);
int32_t *dest = pcm_buffer_get(buffer, dest_size);
assert(src_size % (sizeof(*src) * src_channels) == 0);
size_t dest_size = src_size / src_channels * dest_channels;
*dest_size_r = dest_size;
int32_t *dest = pcm_buffer_get(buffer, dest_size);
const int32_t *src_end = pcm_end_pointer(src, src_size);
if (src_channels == 1 && dest_channels == 2)
pcm_convert_channels_24_1_to_2(dest, src, num_frames);
pcm_convert_channels_24_1_to_2(dest, src, src_end);
else if (src_channels == 2 && dest_channels == 1)
pcm_convert_channels_24_2_to_1(dest, src, num_frames);
pcm_convert_channels_24_2_to_1(dest, src, src_end);
else if (dest_channels == 2)
pcm_convert_channels_24_n_to_2(dest, src_channels, src,
num_frames);
src_end);
else
return NULL;
......@@ -167,16 +178,17 @@ pcm_convert_channels_24(struct pcm_buffer *buffer,
static void
pcm_convert_channels_32_1_to_2(int32_t *dest, const int32_t *src,
unsigned num_frames)
const int32_t *src_end)
{
pcm_convert_channels_24_1_to_2(dest, src, num_frames);
pcm_convert_channels_24_1_to_2(dest, src, src_end);
}
static void
pcm_convert_channels_32_2_to_1(int32_t *dest, const int32_t *src,
unsigned num_frames)
pcm_convert_channels_32_2_to_1(int32_t *restrict dest,
const int32_t *restrict src,
const int32_t *restrict src_end)
{
while (num_frames-- > 0) {
while (src < src_end) {
int64_t a = *src++, b = *src++;
*dest++ = (a + b) / 2;
......@@ -186,13 +198,13 @@ pcm_convert_channels_32_2_to_1(int32_t *dest, const int32_t *src,
static void
pcm_convert_channels_32_n_to_2(int32_t *dest,
unsigned src_channels, const int32_t *src,
unsigned num_frames)
const int32_t *src_end)
{
unsigned c;
assert(src_channels > 0);
while (num_frames-- > 0) {
while (src < src_end) {
int64_t sum = 0;
int32_t value;
......@@ -208,23 +220,25 @@ pcm_convert_channels_32_n_to_2(int32_t *dest,
const int32_t *
pcm_convert_channels_32(struct pcm_buffer *buffer,
uint8_t dest_channels,
uint8_t src_channels, const int32_t *src,
unsigned dest_channels,
unsigned src_channels, const int32_t *src,
size_t src_size, size_t *dest_size_r)
{
unsigned num_frames = src_size / src_channels / sizeof(*src);
unsigned dest_size = num_frames * dest_channels * sizeof(*src);
int32_t *dest = pcm_buffer_get(buffer, dest_size);
assert(src_size % (sizeof(*src) * src_channels) == 0);
size_t dest_size = src_size / src_channels * dest_channels;
*dest_size_r = dest_size;
int32_t *dest = pcm_buffer_get(buffer, dest_size);
const int32_t *src_end = pcm_end_pointer(src, src_size);
if (src_channels == 1 && dest_channels == 2)
pcm_convert_channels_32_1_to_2(dest, src, num_frames);
pcm_convert_channels_32_1_to_2(dest, src, src_end);
else if (src_channels == 2 && dest_channels == 1)
pcm_convert_channels_32_2_to_1(dest, src, num_frames);
pcm_convert_channels_32_2_to_1(dest, src, src_end);
else if (dest_channels == 2)
pcm_convert_channels_32_n_to_2(dest, src_channels, src,
num_frames);
src_end);
else
return NULL;
......
......@@ -38,8 +38,8 @@ struct pcm_buffer;
*/
const int16_t *
pcm_convert_channels_16(struct pcm_buffer *buffer,
uint8_t dest_channels,
uint8_t src_channels, const int16_t *src,
unsigned dest_channels,
unsigned src_channels, const int16_t *src,
size_t src_size, size_t *dest_size_r);
/**
......@@ -56,8 +56,8 @@ pcm_convert_channels_16(struct pcm_buffer *buffer,
*/
const int32_t *
pcm_convert_channels_24(struct pcm_buffer *buffer,
uint8_t dest_channels,
uint8_t src_channels, const int32_t *src,
unsigned dest_channels,
unsigned src_channels, const int32_t *src,
size_t src_size, size_t *dest_size_r);
/**
......@@ -73,8 +73,8 @@ pcm_convert_channels_24(struct pcm_buffer *buffer,
*/
const int32_t *
pcm_convert_channels_32(struct pcm_buffer *buffer,
uint8_t dest_channels,
uint8_t src_channels, const int32_t *src,
unsigned dest_channels,
unsigned src_channels, const int32_t *src,
size_t src_size, size_t *dest_size_r);
#endif
......@@ -24,6 +24,7 @@
#include "pcm_byteswap.h"
#include "pcm_pack.h"
#include "audio_format.h"
#include "glib_compat.h"
#include <assert.h>
#include <string.h>
......@@ -56,6 +57,55 @@ void pcm_convert_deinit(struct pcm_convert_state *state)
pcm_buffer_deinit(&state->byteswap_buffer);
}
static const void *
pcm_convert_channels(struct pcm_buffer *buffer, enum sample_format format,
uint8_t dest_channels,
uint8_t src_channels, const void *src,
size_t src_size, size_t *dest_size_r,
GError **error_r)
{
const void *dest = NULL;
switch (format) {
case SAMPLE_FORMAT_UNDEFINED:
case SAMPLE_FORMAT_S8:
case SAMPLE_FORMAT_S24:
case SAMPLE_FORMAT_FLOAT:
g_set_error(error_r, pcm_convert_quark(), 0,
"Channel conversion not implemented for format '%s'",
sample_format_to_string(format));
return NULL;
case SAMPLE_FORMAT_S16:
dest = pcm_convert_channels_16(buffer, dest_channels,
src_channels, src,
src_size, dest_size_r);
break;
case SAMPLE_FORMAT_S24_P32:
dest = pcm_convert_channels_24(buffer, dest_channels,
src_channels, src,
src_size, dest_size_r);
break;
case SAMPLE_FORMAT_S32:
dest = pcm_convert_channels_32(buffer, dest_channels,
src_channels, src,
src_size, dest_size_r);
break;
}
if (dest == NULL) {
g_set_error(error_r, pcm_convert_quark(), 0,
"Conversion from %u to %u channels "
"is not implemented",
src_channels, dest_channels);
return NULL;
}
return dest;
}
static const int16_t *
pcm_convert_16(struct pcm_convert_state *state,
const struct audio_format *src_format,
......@@ -200,7 +250,8 @@ pcm_convert_24_packed(struct pcm_convert_state *state,
size_t dest_size = num_samples * 3;
uint8_t *dest = pcm_buffer_get(&state->pack_buffer, dest_size);
pcm_pack_24(dest, buffer, num_samples, dest_format->reverse_endian);
pcm_pack_24(dest, buffer, buffer + num_samples,
dest_format->reverse_endian);
*dest_size_r = dest_size;
return dest;
......@@ -222,7 +273,7 @@ pcm_convert_32(struct pcm_convert_state *state,
src_buffer, src_size, &len);
if (buf == NULL) {
g_set_error(error_r, pcm_convert_quark(), 0,
"Conversion from %s to 24 bit is not implemented",
"Conversion from %s to 32 bit is not implemented",
sample_format_to_string(src_format->format));
return NULL;
}
......@@ -261,6 +312,67 @@ pcm_convert_32(struct pcm_convert_state *state,
return buf;
}
static const float *
pcm_convert_float(struct pcm_convert_state *state,
const struct audio_format *src_format,
const void *src_buffer, size_t src_size,
const struct audio_format *dest_format, size_t *dest_size_r,
GError **error_r)
{
const float *buffer = src_buffer;
size_t size = src_size;
assert(dest_format->format == SAMPLE_FORMAT_FLOAT);
if (src_format->reverse_endian || dest_format->reverse_endian) {
g_set_error_literal(error_r, pcm_convert_quark(), 0,
"Reverse endian not supported");
return NULL;
}
/* convert channels first, hoping the source format is
supported (float is not) */
if (dest_format->channels != src_format->channels) {
buffer = pcm_convert_channels(&state->channels_buffer,
src_format->format,
dest_format->channels,
src_format->channels,
buffer, size, &size, error_r);
if (buffer == NULL)
return NULL;
}
/* convert to float now */
buffer = pcm_convert_to_float(&state->format_buffer,
src_format->format,
buffer, size, &size);
if (buffer == NULL) {
g_set_error(error_r, pcm_convert_quark(), 0,
"Conversion from %s to float is not implemented",
sample_format_to_string(src_format->format));
return NULL;
}
/* resample with float, because this is the best format for
libsamplerate */
if (src_format->sample_rate != dest_format->sample_rate) {
buffer = pcm_resample_float(&state->resample,
dest_format->channels,
src_format->sample_rate,
buffer, size,
dest_format->sample_rate, &size,
error_r);
if (buffer == NULL)
return NULL;
}
*dest_size_r = size;
return buffer;
}
const void *
pcm_convert(struct pcm_convert_state *state,
const struct audio_format *src_format,
......@@ -269,6 +381,20 @@ pcm_convert(struct pcm_convert_state *state,
size_t *dest_size_r,
GError **error_r)
{
if (src_format->reverse_endian) {
/* convert to host byte order, because all of our
conversion libraries assume host byte order */
src = pcm_byteswap(&state->byteswap_buffer, src_format->format,
src, src_size);
if (src == NULL) {
g_set_error(error_r, pcm_convert_quark(), 0,
"PCM byte order change of format '%s' is not implemented",
sample_format_to_string(src_format->format));
return NULL;
}
}
switch (dest_format->format) {
case SAMPLE_FORMAT_S16:
return pcm_convert_16(state,
......@@ -294,6 +420,12 @@ pcm_convert(struct pcm_convert_state *state,
dest_format, dest_size_r,
error_r);
case SAMPLE_FORMAT_FLOAT:
return pcm_convert_float(state,
src_format, src, src_size,
dest_format, dest_size_r,
error_r);
default:
g_set_error(error_r, pcm_convert_quark(), 0,
"PCM conversion to %s is not implemented",
......
......@@ -72,10 +72,9 @@ pcm_dither_sample_24_to_16(int32_t sample, struct pcm_dither *dither)
void
pcm_dither_24_to_16(struct pcm_dither *dither,
int16_t *dest, const int32_t *src,
unsigned num_samples)
int16_t *dest, const int32_t *src, const int32_t *src_end)
{
while (num_samples-- > 0)
while (src < src_end)
*dest++ = pcm_dither_sample_24_to_16(*src++, dither);
}
......@@ -87,9 +86,8 @@ pcm_dither_sample_32_to_16(int32_t sample, struct pcm_dither *dither)
void
pcm_dither_32_to_16(struct pcm_dither *dither,
int16_t *dest, const int32_t *src,
unsigned num_samples)
int16_t *dest, const int32_t *src, const int32_t *src_end)
{
while (num_samples-- > 0)
while (src < src_end)
*dest++ = pcm_dither_sample_32_to_16(*src++, dither);
}
......@@ -36,12 +36,10 @@ pcm_dither_24_init(struct pcm_dither *dither)
void
pcm_dither_24_to_16(struct pcm_dither *dither,
int16_t *dest, const int32_t *src,
unsigned num_samples);
int16_t *dest, const int32_t *src, const int32_t *src_end);
void
pcm_dither_32_to_16(struct pcm_dither *dither,
int16_t *dest, const int32_t *src,
unsigned num_samples);
int16_t *dest, const int32_t *src, const int32_t *src_end);
#endif
......@@ -22,39 +22,119 @@
#include "pcm_dither.h"
#include "pcm_buffer.h"
#include "pcm_pack.h"
#include "pcm_utils.h"
static void
pcm_convert_8_to_16(int16_t *out, const int8_t *in,
unsigned num_samples)
pcm_convert_8_to_16(int16_t *out, const int8_t *in, const int8_t *in_end)
{
while (num_samples > 0) {
while (in < in_end) {
*out++ = *in++ << 8;
--num_samples;
}
}
static void
pcm_convert_24_to_16(struct pcm_dither *dither,
int16_t *out, const int32_t *in,
unsigned num_samples)
int16_t *out, const int32_t *in, const int32_t *in_end)
{
pcm_dither_24_to_16(dither, out, in, num_samples);
pcm_dither_24_to_16(dither, out, in, in_end);
}
static void
pcm_convert_32_to_16(struct pcm_dither *dither,
int16_t *out, const int32_t *in,
unsigned num_samples)
int16_t *out, const int32_t *in, const int32_t *in_end)
{
pcm_dither_32_to_16(dither, out, in, num_samples);
pcm_dither_32_to_16(dither, out, in, in_end);
}
static void
pcm_convert_float_to_16(int16_t *out, const float *in, const float *in_end)
{
const unsigned OUT_BITS = 16;
const float factor = 1 << (OUT_BITS - 1);
while (in < in_end) {
int sample = *in++ * factor;
*out++ = pcm_clamp_16(sample);
}
}
static int16_t *
pcm_allocate_8_to_16(struct pcm_buffer *buffer,
const int8_t *src, size_t src_size, size_t *dest_size_r)
{
int16_t *dest;
*dest_size_r = src_size / sizeof(*src) * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
pcm_convert_8_to_16(dest, src, pcm_end_pointer(src, src_size));
return dest;
}
static int32_t *
pcm_convert_24_to_24p32(struct pcm_buffer *buffer, const uint8_t *src,
unsigned num_samples)
pcm_allocate_24_to_24p32(struct pcm_buffer *buffer, const uint8_t *src,
size_t src_size, size_t *dest_size_r)
{
int32_t *dest = pcm_buffer_get(buffer, num_samples * 4);
pcm_unpack_24(dest, src, num_samples, false);
int32_t *dest;
*dest_size_r = src_size / 3 * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
pcm_unpack_24(dest, src, pcm_end_pointer(src, src_size), false);
return dest;
}
static int16_t *
pcm_allocate_24_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
const uint8_t *src, size_t src_size, size_t *dest_size_r)
{
/* convert to S24_P32 first */
size_t tmp_size;
int32_t *tmp = pcm_allocate_24_to_24p32(buffer, src, src_size,
&tmp_size);
/* convert to 16 bit in-place */
int16_t *dest = (int16_t *)tmp;
*dest_size_r = tmp_size / sizeof(*tmp) * sizeof(*dest);
pcm_convert_24_to_16(dither, dest, tmp, pcm_end_pointer(tmp, tmp_size));
return dest;
}
static int16_t *
pcm_allocate_24p32_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
const int32_t *src, size_t src_size,
size_t *dest_size_r)
{
int16_t *dest;
*dest_size_r = src_size / 2;
assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
dest = pcm_buffer_get(buffer, *dest_size_r);
pcm_convert_24_to_16(dither, dest, src,
pcm_end_pointer(src, src_size));
return dest;
}
static int16_t *
pcm_allocate_32_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
const int32_t *src, size_t src_size,
size_t *dest_size_r)
{
int16_t *dest;
*dest_size_r = src_size / 2;
assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
dest = pcm_buffer_get(buffer, *dest_size_r);
pcm_convert_32_to_16(dither, dest, src,
pcm_end_pointer(src, src_size));
return dest;
}
static int16_t *
pcm_allocate_float_to_16(struct pcm_buffer *buffer,
const float *src, size_t src_size,
size_t *dest_size_r)
{
int16_t *dest;
*dest_size_r = src_size / 2;
assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
dest = pcm_buffer_get(buffer, *dest_size_r);
pcm_convert_float_to_16(dest, src,
pcm_end_pointer(src, src_size));
return dest;
}
......@@ -63,176 +143,241 @@ pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
enum sample_format src_format, const void *src,
size_t src_size, size_t *dest_size_r)
{
unsigned num_samples;
int16_t *dest;
int32_t *dest32;
assert(src_size % sample_format_size(src_format) == 0);
switch (src_format) {
case SAMPLE_FORMAT_UNDEFINED:
break;
case SAMPLE_FORMAT_S8:
num_samples = src_size;
*dest_size_r = src_size * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
pcm_convert_8_to_16(dest,
(const int8_t *)src,
num_samples);
return dest;
return pcm_allocate_8_to_16(buffer,
src, src_size, dest_size_r);
case SAMPLE_FORMAT_S16:
*dest_size_r = src_size;
return src;
case SAMPLE_FORMAT_S24:
/* convert to S24_P32 first */
num_samples = src_size / 3;
dest32 = pcm_convert_24_to_24p32(buffer, src, num_samples);
dest = (int16_t *)dest32;
/* convert to 16 bit in-place */
*dest_size_r = num_samples * sizeof(*dest);
pcm_convert_24_to_16(dither, dest, dest32,
num_samples);
return dest;
return pcm_allocate_24_to_16(buffer, dither,
src, src_size, dest_size_r);
case SAMPLE_FORMAT_S24_P32:
num_samples = src_size / 4;
*dest_size_r = num_samples * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
pcm_convert_24_to_16(dither, dest,
(const int32_t *)src,
num_samples);
return dest;
return pcm_allocate_24p32_to_16(buffer, dither, src, src_size,
dest_size_r);
case SAMPLE_FORMAT_S32:
num_samples = src_size / 4;
*dest_size_r = num_samples * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
pcm_convert_32_to_16(dither, dest,
(const int32_t *)src,
num_samples);
return dest;
return pcm_allocate_32_to_16(buffer, dither, src, src_size,
dest_size_r);
case SAMPLE_FORMAT_FLOAT:
return pcm_allocate_float_to_16(buffer, src, src_size,
dest_size_r);
}
return NULL;
}
static void
pcm_convert_8_to_24(int32_t *out, const int8_t *in,
unsigned num_samples)
pcm_convert_8_to_24(int32_t *out, const int8_t *in, const int8_t *in_end)
{
while (num_samples > 0) {
while (in < in_end)
*out++ = *in++ << 16;
--num_samples;
}
}
static void
pcm_convert_16_to_24(int32_t *out, const int16_t *in,
unsigned num_samples)
pcm_convert_16_to_24(int32_t *out, const int16_t *in, const int16_t *in_end)
{
while (num_samples > 0) {
while (in < in_end)
*out++ = *in++ << 8;
--num_samples;
}
}
static void
pcm_convert_32_to_24(int32_t *out, const int32_t *in,
unsigned num_samples)
pcm_convert_32_to_24(int32_t *restrict out,
const int32_t *restrict in,
const int32_t *restrict in_end)
{
while (num_samples > 0) {
while (in < in_end)
*out++ = *in++ >> 8;
--num_samples;
}
static void
pcm_convert_float_to_24(int32_t *out, const float *in, const float *in_end)
{
const unsigned OUT_BITS = 24;
const float factor = 1 << (OUT_BITS - 1);
while (in < in_end) {
int sample = *in++ * factor;
*out++ = pcm_clamp_24(sample);
}
}
static int32_t *
pcm_allocate_8_to_24(struct pcm_buffer *buffer,
const int8_t *src, size_t src_size, size_t *dest_size_r)
{
int32_t *dest;
*dest_size_r = src_size / sizeof(*src) * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
pcm_convert_8_to_24(dest, src, pcm_end_pointer(src, src_size));
return dest;
}
static int32_t *
pcm_allocate_16_to_24(struct pcm_buffer *buffer,
const int16_t *src, size_t src_size, size_t *dest_size_r)
{
int32_t *dest;
*dest_size_r = src_size * 2;
assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
dest = pcm_buffer_get(buffer, *dest_size_r);
pcm_convert_16_to_24(dest, src, pcm_end_pointer(src, src_size));
return dest;
}
static int32_t *
pcm_allocate_32_to_24(struct pcm_buffer *buffer,
const int32_t *src, size_t src_size, size_t *dest_size_r)
{
*dest_size_r = src_size;
int32_t *dest = pcm_buffer_get(buffer, *dest_size_r);
pcm_convert_32_to_24(dest, src, pcm_end_pointer(src, src_size));
return dest;
}
static int32_t *
pcm_allocate_float_to_24(struct pcm_buffer *buffer,
const float *src, size_t src_size,
size_t *dest_size_r)
{
*dest_size_r = src_size;
int32_t *dest = pcm_buffer_get(buffer, *dest_size_r);
pcm_convert_float_to_24(dest, src, pcm_end_pointer(src, src_size));
return dest;
}
const int32_t *
pcm_convert_to_24(struct pcm_buffer *buffer,
enum sample_format src_format, const void *src,
size_t src_size, size_t *dest_size_r)
{
unsigned num_samples;
int32_t *dest;
assert(src_size % sample_format_size(src_format) == 0);
switch (src_format) {
case SAMPLE_FORMAT_UNDEFINED:
break;
case SAMPLE_FORMAT_S8:
num_samples = src_size;
*dest_size_r = src_size * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
pcm_convert_8_to_24(dest, (const int8_t *)src,
num_samples);
return dest;
return pcm_allocate_8_to_24(buffer,
src, src_size, dest_size_r);
case SAMPLE_FORMAT_S16:
num_samples = src_size / 2;
*dest_size_r = num_samples * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
pcm_convert_16_to_24(dest, (const int16_t *)src,
num_samples);
return dest;
return pcm_allocate_16_to_24(buffer,
src, src_size, dest_size_r);
case SAMPLE_FORMAT_S24:
num_samples = src_size / 3;
*dest_size_r = num_samples * sizeof(*dest);
return pcm_convert_24_to_24p32(buffer, src, num_samples);
return pcm_allocate_24_to_24p32(buffer, src, src_size,
dest_size_r);
case SAMPLE_FORMAT_S24_P32:
*dest_size_r = src_size;
return src;
case SAMPLE_FORMAT_S32:
num_samples = src_size / 4;
*dest_size_r = num_samples * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
return pcm_allocate_32_to_24(buffer, src, src_size,
dest_size_r);
pcm_convert_32_to_24(dest, (const int32_t *)src,
num_samples);
return dest;
case SAMPLE_FORMAT_FLOAT:
return pcm_allocate_float_to_24(buffer, src, src_size,
dest_size_r);
}
return NULL;
}
static void
pcm_convert_8_to_32(int32_t *out, const int8_t *in,
unsigned num_samples)
pcm_convert_8_to_32(int32_t *out, const int8_t *in, const int8_t *in_end)
{
while (num_samples > 0) {
while (in < in_end)
*out++ = *in++ << 24;
--num_samples;
}
}
static void
pcm_convert_16_to_32(int32_t *out, const int16_t *in,
unsigned num_samples)
pcm_convert_16_to_32(int32_t *out, const int16_t *in, const int16_t *in_end)
{
while (num_samples > 0) {
while (in < in_end)
*out++ = *in++ << 16;
--num_samples;
}
}
static void
pcm_convert_24_to_32(int32_t *out, const int32_t *in,
unsigned num_samples)
pcm_convert_24_to_32(int32_t *restrict out,
const int32_t *restrict in,
const int32_t *restrict in_end)
{
while (num_samples > 0) {
while (in < in_end)
*out++ = *in++ << 8;
--num_samples;
}
}
static int32_t *
pcm_allocate_8_to_32(struct pcm_buffer *buffer,
const int8_t *src, size_t src_size, size_t *dest_size_r)
{
int32_t *dest;
*dest_size_r = src_size / sizeof(*src) * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
pcm_convert_8_to_32(dest, src, pcm_end_pointer(src, src_size));
return dest;
}
static int32_t *
pcm_allocate_16_to_32(struct pcm_buffer *buffer,
const int16_t *src, size_t src_size, size_t *dest_size_r)
{
int32_t *dest;
*dest_size_r = src_size * 2;
assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
dest = pcm_buffer_get(buffer, *dest_size_r);
pcm_convert_16_to_32(dest, src, pcm_end_pointer(src, src_size));
return dest;
}
static int32_t *
pcm_allocate_24_to_32(struct pcm_buffer *buffer,
const uint8_t *src,
size_t src_size, size_t *dest_size_r)
{
/* convert to S24_P32 first */
int32_t *dest = pcm_allocate_24_to_24p32(buffer, src, src_size,
dest_size_r);
/* convert to 32 bit in-place */
pcm_convert_24_to_32(dest, dest, pcm_end_pointer(dest, *dest_size_r));
return dest;
}
static int32_t *
pcm_allocate_24p32_to_32(struct pcm_buffer *buffer,
const int32_t *src, size_t src_size,
size_t *dest_size_r)
{
*dest_size_r = src_size;
int32_t *dest = pcm_buffer_get(buffer, *dest_size_r);
pcm_convert_24_to_32(dest, src, pcm_end_pointer(src, src_size));
return dest;
}
static int32_t *
pcm_allocate_float_to_32(struct pcm_buffer *buffer,
const float *src, size_t src_size,
size_t *dest_size_r)
{
/* convert to S24_P32 first */
int32_t *dest = pcm_allocate_float_to_24(buffer, src, src_size,
dest_size_r);
/* convert to 32 bit in-place */
pcm_convert_24_to_32(dest, dest, pcm_end_pointer(dest, *dest_size_r));
return dest;
}
const int32_t *
......@@ -240,52 +385,168 @@ pcm_convert_to_32(struct pcm_buffer *buffer,
enum sample_format src_format, const void *src,
size_t src_size, size_t *dest_size_r)
{
unsigned num_samples;
int32_t *dest;
assert(src_size % sample_format_size(src_format) == 0);
switch (src_format) {
case SAMPLE_FORMAT_UNDEFINED:
break;
case SAMPLE_FORMAT_S8:
num_samples = src_size;
*dest_size_r = src_size * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
pcm_convert_8_to_32(dest, (const int8_t *)src,
num_samples);
return dest;
return pcm_allocate_8_to_32(buffer, src, src_size,
dest_size_r);
case SAMPLE_FORMAT_S16:
num_samples = src_size / 2;
*dest_size_r = num_samples * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
pcm_convert_16_to_32(dest, (const int16_t *)src,
num_samples);
return dest;
return pcm_allocate_16_to_32(buffer, src, src_size,
dest_size_r);
case SAMPLE_FORMAT_S24:
/* convert to S24_P32 first */
num_samples = src_size / 3;
return pcm_allocate_24_to_32(buffer, src, src_size,
dest_size_r);
dest = pcm_convert_24_to_24p32(buffer, src, num_samples);
case SAMPLE_FORMAT_S24_P32:
return pcm_allocate_24p32_to_32(buffer, src, src_size,
dest_size_r);
case SAMPLE_FORMAT_S32:
*dest_size_r = src_size;
return src;
/* convert to 32 bit in-place */
*dest_size_r = num_samples * sizeof(*dest);
pcm_convert_24_to_32(dest, dest, num_samples);
return dest;
case SAMPLE_FORMAT_FLOAT:
return pcm_allocate_float_to_32(buffer, src, src_size,
dest_size_r);
}
case SAMPLE_FORMAT_S24_P32:
num_samples = src_size / 4;
*dest_size_r = num_samples * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
return NULL;
}
static void
pcm_convert_8_to_float(float *out, const int8_t *in, const int8_t *in_end)
{
enum { in_bits = sizeof(*in) * 8 };
static const float factor = 2.0f / (1 << in_bits);
while (in < in_end)
*out++ = (float)*in++ * factor;
}
static void
pcm_convert_16_to_float(float *out, const int16_t *in, const int16_t *in_end)
{
enum { in_bits = sizeof(*in) * 8 };
static const float factor = 2.0f / (1 << in_bits);
while (in < in_end)
*out++ = (float)*in++ * factor;
}
static void
pcm_convert_24_to_float(float *out, const int32_t *in, const int32_t *in_end)
{
enum { in_bits = 24 };
static const float factor = 2.0f / (1 << in_bits);
while (in < in_end)
*out++ = (float)*in++ * factor;
}
pcm_convert_24_to_32(dest, (const int32_t *)src,
num_samples);
return dest;
static void
pcm_convert_32_to_float(float *out, const int32_t *in, const int32_t *in_end)
{
enum { in_bits = sizeof(*in) * 8 };
static const float factor = 0.5f / (1 << (in_bits - 2));
while (in < in_end)
*out++ = (float)*in++ * factor;
}
static float *
pcm_allocate_8_to_float(struct pcm_buffer *buffer,
const int8_t *src, size_t src_size,
size_t *dest_size_r)
{
float *dest;
*dest_size_r = src_size / sizeof(*src) * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
pcm_convert_8_to_float(dest, src, pcm_end_pointer(src, src_size));
return dest;
}
static float *
pcm_allocate_16_to_float(struct pcm_buffer *buffer,
const int16_t *src, size_t src_size,
size_t *dest_size_r)
{
float *dest;
*dest_size_r = src_size * 2;
assert(*dest_size_r == src_size / sizeof(*src) * sizeof(*dest));
dest = pcm_buffer_get(buffer, *dest_size_r);
pcm_convert_16_to_float(dest, src, pcm_end_pointer(src, src_size));
return dest;
}
static float *
pcm_allocate_24_to_float(struct pcm_buffer *buffer,
const uint8_t *src,
size_t src_size, size_t *dest_size_r)
{
/* convert to S24_P32 first */
int32_t *tmp = pcm_allocate_24_to_24p32(buffer, src, src_size,
dest_size_r);
/* convert to float in-place */
float *dest = (float *)tmp;
pcm_convert_24_to_float(dest, tmp, pcm_end_pointer(tmp, *dest_size_r));
return dest;
}
static float *
pcm_allocate_24p32_to_float(struct pcm_buffer *buffer,
const int32_t *src, size_t src_size,
size_t *dest_size_r)
{
*dest_size_r = src_size;
float *dest = pcm_buffer_get(buffer, *dest_size_r);
pcm_convert_24_to_float(dest, src, pcm_end_pointer(src, src_size));
return dest;
}
static float *
pcm_allocate_32_to_float(struct pcm_buffer *buffer,
const int32_t *src, size_t src_size,
size_t *dest_size_r)
{
*dest_size_r = src_size;
float *dest = pcm_buffer_get(buffer, *dest_size_r);
pcm_convert_32_to_float(dest, src, pcm_end_pointer(src, src_size));
return dest;
}
const float *
pcm_convert_to_float(struct pcm_buffer *buffer,
enum sample_format src_format, const void *src,
size_t src_size, size_t *dest_size_r)
{
switch (src_format) {
case SAMPLE_FORMAT_UNDEFINED:
break;
case SAMPLE_FORMAT_S8:
return pcm_allocate_8_to_float(buffer,
src, src_size, dest_size_r);
case SAMPLE_FORMAT_S16:
return pcm_allocate_16_to_float(buffer,
src, src_size, dest_size_r);
case SAMPLE_FORMAT_S24:
return pcm_allocate_24_to_float(buffer,
src, src_size, dest_size_r);
case SAMPLE_FORMAT_S24_P32:
return pcm_allocate_24p32_to_float(buffer,
src, src_size, dest_size_r);
case SAMPLE_FORMAT_S32:
return pcm_allocate_32_to_float(buffer,
src, src_size, dest_size_r);
case SAMPLE_FORMAT_FLOAT:
*dest_size_r = src_size;
return src;
}
......
......@@ -75,4 +75,19 @@ pcm_convert_to_32(struct pcm_buffer *buffer,
enum sample_format src_format, const void *src,
size_t src_size, size_t *dest_size_r);
/**
* Converts PCM samples to 32 bit floating point.
*
* @param buffer a pcm_buffer object
* @param bits the number of in the source buffer
* @param src the source PCM buffer
* @param src_size the size of #src in bytes
* @param dest_size_r returns the number of bytes of the destination buffer
* @return the destination buffer
*/
const float *
pcm_convert_to_float(struct pcm_buffer *buffer,
enum sample_format src_format, const void *src,
size_t src_size, size_t *dest_size_r);
#endif
......@@ -22,7 +22,6 @@
#include "pcm_volume.h"
#include "pcm_utils.h"
#include "audio_format.h"
#include "mpd_error.h"
#include <glib.h>
......@@ -100,35 +99,60 @@ pcm_add_vol_32(int32_t *buffer1, const int32_t *buffer2,
}
static void
pcm_add_vol_float(float *buffer1, const float *buffer2,
unsigned num_samples, float volume1, float volume2)
{
while (num_samples > 0) {
float sample1 = *buffer1;
float sample2 = *buffer2++;
sample1 = (sample1 * volume1 + sample2 * volume2);
*buffer1++ = sample1;
--num_samples;
}
}
static bool
pcm_add_vol(void *buffer1, const void *buffer2, size_t size,
int vol1, int vol2,
const struct audio_format *format)
enum sample_format format)
{
switch (format->format) {
switch (format) {
case SAMPLE_FORMAT_UNDEFINED:
case SAMPLE_FORMAT_S24:
/* not implemented */
return false;
case SAMPLE_FORMAT_S8:
pcm_add_vol_8((int8_t *)buffer1, (const int8_t *)buffer2,
size, vol1, vol2);
break;
return true;
case SAMPLE_FORMAT_S16:
pcm_add_vol_16((int16_t *)buffer1, (const int16_t *)buffer2,
size / 2, vol1, vol2);
break;
return true;
case SAMPLE_FORMAT_S24_P32:
pcm_add_vol_24((int32_t *)buffer1, (const int32_t *)buffer2,
size / 4, vol1, vol2);
break;
return true;
case SAMPLE_FORMAT_S32:
pcm_add_vol_32((int32_t *)buffer1, (const int32_t *)buffer2,
size / 4, vol1, vol2);
break;
return true;
default:
MPD_ERROR("format %s not supported by pcm_add_vol",
sample_format_to_string(format->format));
case SAMPLE_FORMAT_FLOAT:
pcm_add_vol_float(buffer1, buffer2, size / 4,
pcm_volume_to_float(vol1),
pcm_volume_to_float(vol2));
return true;
}
/* unreachable */
assert(false);
return false;
}
static void
......@@ -188,45 +212,63 @@ pcm_add_32(int32_t *buffer1, const int32_t *buffer2, unsigned num_samples)
}
static void
pcm_add_float(float *buffer1, const float *buffer2, unsigned num_samples)
{
while (num_samples > 0) {
float sample1 = *buffer1;
float sample2 = *buffer2++;
*buffer1++ = sample1 + sample2;
--num_samples;
}
}
static bool
pcm_add(void *buffer1, const void *buffer2, size_t size,
const struct audio_format *format)
enum sample_format format)
{
switch (format->format) {
switch (format) {
case SAMPLE_FORMAT_UNDEFINED:
case SAMPLE_FORMAT_S24:
/* not implemented */
return false;
case SAMPLE_FORMAT_S8:
pcm_add_8((int8_t *)buffer1, (const int8_t *)buffer2, size);
break;
return true;
case SAMPLE_FORMAT_S16:
pcm_add_16((int16_t *)buffer1, (const int16_t *)buffer2, size / 2);
break;
return true;
case SAMPLE_FORMAT_S24_P32:
pcm_add_24((int32_t *)buffer1, (const int32_t *)buffer2, size / 4);
break;
return true;
case SAMPLE_FORMAT_S32:
pcm_add_32((int32_t *)buffer1, (const int32_t *)buffer2, size / 4);
break;
return true;
default:
MPD_ERROR("format %s not supported by pcm_add",
sample_format_to_string(format->format));
case SAMPLE_FORMAT_FLOAT:
pcm_add_float(buffer1, buffer2, size / 4);
return true;
}
/* unreachable */
assert(false);
return false;
}
void
bool
pcm_mix(void *buffer1, const void *buffer2, size_t size,
const struct audio_format *format, float portion1)
enum sample_format format, float portion1)
{
int vol1;
float s;
/* portion1 is between 0.0 and 1.0 for crossfading, MixRamp uses NaN
* to signal mixing rather than fading */
if (isnan(portion1)) {
pcm_add(buffer1, buffer2, size, format);
return;
}
if (isnan(portion1))
return pcm_add(buffer1, buffer2, size, format);
s = sin(M_PI_2 * portion1);
s *= s;
......@@ -234,5 +276,5 @@ pcm_mix(void *buffer1, const void *buffer2, size_t size,
vol1 = s * PCM_VOLUME_1 + 0.5;
vol1 = vol1 > PCM_VOLUME_1 ? PCM_VOLUME_1 : (vol1 < 0 ? 0 : vol1);
pcm_add_vol(buffer1, buffer2, size, vol1, PCM_VOLUME_1 - vol1, format);
return pcm_add_vol(buffer1, buffer2, size, vol1, PCM_VOLUME_1 - vol1, format);
}
......@@ -20,9 +20,10 @@
#ifndef PCM_MIX_H
#define PCM_MIX_H
#include <stddef.h>
#include "audio_format.h"
struct audio_format;
#include <stdbool.h>
#include <stddef.h>
/*
* Linearly mixes two PCM buffers. Both must have the same length and
......@@ -33,13 +34,16 @@ struct audio_format;
* @param buffer1 the first PCM buffer, and the destination buffer
* @param buffer2 the second PCM buffer
* @param size the size of both buffers in bytes
* @param format the audio format of both buffers
* @param format the sample format of both buffers
* @param portion1 a number between 0.0 and 1.0 specifying the portion
* of the first buffer in the mix; portion2 = (1.0 - portion1). The value
* NaN is used by the MixRamp code to specify that simple addition is required.
*
* @return true on success, false if the format is not supported
*/
void
G_GNUC_WARN_UNUSED_RESULT
bool
pcm_mix(void *buffer1, const void *buffer2, size_t size,
const struct audio_format *format, float portion1);
enum sample_format format, float portion1);
#endif
......@@ -35,19 +35,19 @@ pack_sample(uint8_t *dest, const int32_t *src0, bool reverse_endian)
}
void
pcm_pack_24(uint8_t *dest, const int32_t *src, unsigned num_samples,
pcm_pack_24(uint8_t *dest, const int32_t *src, const int32_t *src_end,
bool reverse_endian)
{
/* duplicate loop to help the compiler's optimizer (constant
parameter to the pack_sample() inline function) */
if (G_LIKELY(!reverse_endian)) {
while (num_samples-- > 0) {
while (src < src_end) {
pack_sample(dest, src++, false);
dest += 3;
}
} else {
while (num_samples-- > 0) {
while (src < src_end) {
pack_sample(dest, src++, true);
dest += 3;
}
......@@ -73,19 +73,19 @@ unpack_sample(int32_t *dest0, const uint8_t *src, bool reverse_endian)
}
void
pcm_unpack_24(int32_t *dest, const uint8_t *src, unsigned num_samples,
pcm_unpack_24(int32_t *dest, const uint8_t *src, const uint8_t *src_end,
bool reverse_endian)
{
/* duplicate loop to help the compiler's optimizer (constant
parameter to the unpack_sample() inline function) */
if (G_LIKELY(!reverse_endian)) {
while (num_samples-- > 0) {
while (src < src_end) {
unpack_sample(dest++, src, false);
src += 3;
}
} else {
while (num_samples-- > 0) {
while (src < src_end) {
unpack_sample(dest++, src, true);
src += 3;
}
......
......@@ -40,7 +40,7 @@
* @param reverse_endian is src and dest in non-host byte order?
*/
void
pcm_pack_24(uint8_t *dest, const int32_t *src, unsigned num_samples,
pcm_pack_24(uint8_t *dest, const int32_t *src, const int32_t *src_end,
bool reverse_endian);
/**
......@@ -53,7 +53,7 @@ pcm_pack_24(uint8_t *dest, const int32_t *src, unsigned num_samples,
* @param reverse_endian is src and dest in non-host byte order?
*/
void
pcm_unpack_24(int32_t *dest, const uint8_t *src, unsigned num_samples,
pcm_unpack_24(int32_t *dest, const uint8_t *src, const uint8_t *src_end,
bool reverse_endian);
#endif
......@@ -27,26 +27,43 @@
#include <string.h>
#ifdef HAVE_LIBSAMPLERATE
static bool lsr_enabled;
#endif
#ifdef HAVE_LIBSAMPLERATE
static bool
pcm_resample_lsr_enabled(void)
{
return strcmp(config_get_string(CONF_SAMPLERATE_CONVERTER, ""),
"internal") != 0;
return lsr_enabled;
}
#endif
void pcm_resample_init(struct pcm_resample_state *state)
bool
pcm_resample_global_init(GError **error_r)
{
memset(state, 0, sizeof(*state));
#ifdef HAVE_LIBSAMPLERATE
if (pcm_resample_lsr_enabled()) {
pcm_buffer_init(&state->in);
pcm_buffer_init(&state->out);
}
const char *converter =
config_get_string(CONF_SAMPLERATE_CONVERTER, "");
lsr_enabled = strcmp(converter, "internal") != 0;
if (lsr_enabled)
return pcm_resample_lsr_global_init(converter, error_r);
else
return true;
#else
(void)error_r;
return true;
#endif
}
pcm_buffer_init(&state->buffer);
void pcm_resample_init(struct pcm_resample_state *state)
{
#ifdef HAVE_LIBSAMPLERATE
if (pcm_resample_lsr_enabled())
pcm_resample_lsr_init(state);
else
#endif
pcm_resample_fallback_init(state);
}
void pcm_resample_deinit(struct pcm_resample_state *state)
......@@ -59,9 +76,37 @@ void pcm_resample_deinit(struct pcm_resample_state *state)
pcm_resample_fallback_deinit(state);
}
const float *
pcm_resample_float(struct pcm_resample_state *state,
unsigned channels,
unsigned src_rate,
const float *src_buffer, size_t src_size,
unsigned dest_rate, size_t *dest_size_r,
GError **error_r)
{
#ifdef HAVE_LIBSAMPLERATE
if (pcm_resample_lsr_enabled())
return pcm_resample_lsr_float(state, channels,
src_rate, src_buffer, src_size,
dest_rate, dest_size_r,
error_r);
#else
(void)error_r;
#endif
/* sizeof(float)==sizeof(int32_t); the fallback resampler does
not do any math on the sample values, so this hack is
possible: */
return (const float *)
pcm_resample_fallback_32(state, channels,
src_rate, (const int32_t *)src_buffer,
src_size,
dest_rate, dest_size_r);
}
const int16_t *
pcm_resample_16(struct pcm_resample_state *state,
uint8_t channels,
unsigned channels,
unsigned src_rate, const int16_t *src_buffer, size_t src_size,
unsigned dest_rate, size_t *dest_size_r,
GError **error_r)
......@@ -83,7 +128,7 @@ pcm_resample_16(struct pcm_resample_state *state,
const int32_t *
pcm_resample_32(struct pcm_resample_state *state,
uint8_t channels,
unsigned channels,
unsigned src_rate, const int32_t *src_buffer, size_t src_size,
unsigned dest_rate, size_t *dest_size_r,
GError **error_r)
......
......@@ -45,7 +45,7 @@ struct pcm_resample_state {
struct {
unsigned src_rate;
unsigned dest_rate;
uint8_t channels;
unsigned channels;
} prev;
int error;
......@@ -54,6 +54,9 @@ struct pcm_resample_state {
struct pcm_buffer buffer;
};
bool
pcm_resample_global_init(GError **error_r);
/**
* Initializes a pcm_resample_state object.
*/
......@@ -66,6 +69,26 @@ void pcm_resample_init(struct pcm_resample_state *state);
void pcm_resample_deinit(struct pcm_resample_state *state);
/**
* Resamples 32 bit float data.
*
* @param state an initialized pcm_resample_state object
* @param channels the number of channels
* @param src_rate the source sample rate
* @param src the source PCM buffer
* @param src_size the size of #src in bytes
* @param dest_rate the requested destination sample rate
* @param dest_size_r returns the number of bytes of the destination buffer
* @return the destination buffer
*/
const float *
pcm_resample_float(struct pcm_resample_state *state,
unsigned channels,
unsigned src_rate,
const float *src_buffer, size_t src_size,
unsigned dest_rate, size_t *dest_size_r,
GError **error_r);
/**
* Resamples 16 bit PCM data.
*
* @param state an initialized pcm_resample_state object
......@@ -79,7 +102,7 @@ void pcm_resample_deinit(struct pcm_resample_state *state);
*/
const int16_t *
pcm_resample_16(struct pcm_resample_state *state,
uint8_t channels,
unsigned channels,
unsigned src_rate,
const int16_t *src_buffer, size_t src_size,
unsigned dest_rate, size_t *dest_size_r,
......@@ -99,7 +122,7 @@ pcm_resample_16(struct pcm_resample_state *state,
*/
const int32_t *
pcm_resample_32(struct pcm_resample_state *state,
uint8_t channels,
unsigned channels,
unsigned src_rate,
const int32_t *src_buffer, size_t src_size,
unsigned dest_rate, size_t *dest_size_r,
......@@ -119,7 +142,7 @@ pcm_resample_32(struct pcm_resample_state *state,
*/
static inline const int32_t *
pcm_resample_24(struct pcm_resample_state *state,
uint8_t channels,
unsigned channels,
unsigned src_rate,
const int32_t *src_buffer, size_t src_size,
unsigned dest_rate, size_t *dest_size_r,
......
......@@ -23,6 +23,12 @@
#include <assert.h>
void
pcm_resample_fallback_init(struct pcm_resample_state *state)
{
pcm_buffer_init(&state->buffer);
}
void
pcm_resample_fallback_deinit(struct pcm_resample_state *state)
{
pcm_buffer_deinit(&state->buffer);
......@@ -31,7 +37,7 @@ pcm_resample_fallback_deinit(struct pcm_resample_state *state)
/* resampling code blatantly ripped from ESD */
const int16_t *
pcm_resample_fallback_16(struct pcm_resample_state *state,
uint8_t channels,
unsigned channels,
unsigned src_rate,
const int16_t *src_buffer, size_t src_size,
unsigned dest_rate,
......@@ -72,7 +78,7 @@ pcm_resample_fallback_16(struct pcm_resample_state *state,
const int32_t *
pcm_resample_fallback_32(struct pcm_resample_state *state,
uint8_t channels,
unsigned channels,
unsigned src_rate,
const int32_t *src_buffer, size_t src_size,
unsigned dest_rate,
......
......@@ -32,12 +32,26 @@
#ifdef HAVE_LIBSAMPLERATE
bool
pcm_resample_lsr_global_init(const char *converter, GError **error_r);
void
pcm_resample_lsr_init(struct pcm_resample_state *state);
void
pcm_resample_lsr_deinit(struct pcm_resample_state *state);
const float *
pcm_resample_lsr_float(struct pcm_resample_state *state,
unsigned channels,
unsigned src_rate,
const float *src_buffer, size_t src_size,
unsigned dest_rate, size_t *dest_size_r,
GError **error_r);
const int16_t *
pcm_resample_lsr_16(struct pcm_resample_state *state,
uint8_t channels,
unsigned channels,
unsigned src_rate,
const int16_t *src_buffer, size_t src_size,
unsigned dest_rate, size_t *dest_size_r,
......@@ -45,7 +59,7 @@ pcm_resample_lsr_16(struct pcm_resample_state *state,
const int32_t *
pcm_resample_lsr_32(struct pcm_resample_state *state,
uint8_t channels,
unsigned channels,
unsigned src_rate,
const int32_t *src_buffer,
G_GNUC_UNUSED size_t src_size,
......@@ -55,11 +69,14 @@ pcm_resample_lsr_32(struct pcm_resample_state *state,
#endif
void
pcm_resample_fallback_init(struct pcm_resample_state *state);
void
pcm_resample_fallback_deinit(struct pcm_resample_state *state);
const int16_t *
pcm_resample_fallback_16(struct pcm_resample_state *state,
uint8_t channels,
unsigned channels,
unsigned src_rate,
const int16_t *src_buffer, size_t src_size,
unsigned dest_rate,
......@@ -67,7 +84,7 @@ pcm_resample_fallback_16(struct pcm_resample_state *state,
const int32_t *
pcm_resample_fallback_32(struct pcm_resample_state *state,
uint8_t channels,
unsigned channels,
unsigned src_rate,
const int32_t *src_buffer,
G_GNUC_UNUSED size_t src_size,
......
......@@ -30,71 +30,88 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "pcm"
static int lsr_converter = SRC_SINC_FASTEST;
static inline GQuark
libsamplerate_quark(void)
{
return g_quark_from_static_string("libsamplerate");
}
void
pcm_resample_lsr_deinit(struct pcm_resample_state *state)
static bool
lsr_parse_converter(const char *s)
{
if (state->state != NULL)
state->state = src_delete(state->state);
assert(s != NULL);
pcm_buffer_deinit(&state->in);
pcm_buffer_deinit(&state->out);
pcm_buffer_deinit(&state->buffer);
}
if (*s == 0)
return true;
static int pcm_resample_get_converter(void)
{
const char *conf = config_get_string(CONF_SAMPLERATE_CONVERTER, NULL);
long convalgo;
char *test;
const char *test2;
size_t len;
if (!conf) {
convalgo = SRC_SINC_FASTEST;
goto out;
char *endptr;
long l = strtol(s, &endptr, 10);
if (*endptr == 0 && src_get_name(l) != NULL) {
lsr_converter = l;
return true;
}
convalgo = strtol(conf, &test, 10);
if (*test == '\0' && src_get_name(convalgo))
goto out;
len = strlen(conf);
for (convalgo = 0 ; ; convalgo++) {
test2 = src_get_name(convalgo);
if (!test2) {
convalgo = SRC_SINC_FASTEST;
size_t length = strlen(s);
for (int i = 0;; ++i) {
const char *name = src_get_name(i);
if (name == NULL)
break;
if (g_ascii_strncasecmp(s, name, length) == 0) {
lsr_converter = i;
return true;
}
if (g_ascii_strncasecmp(test2, conf, len) == 0)
goto out;
}
g_warning("unknown samplerate converter \"%s\"", conf);
out:
g_debug("selecting samplerate converter \"%s\"",
src_get_name(convalgo));
return false;
}
bool
pcm_resample_lsr_global_init(const char *converter, GError **error_r)
{
if (!lsr_parse_converter(converter)) {
g_set_error(error_r, libsamplerate_quark(), 0,
"unknown samplerate converter '%s'", converter);
return false;
}
g_debug("libsamplerate converter '%s'",
src_get_name(lsr_converter));
return true;
}
void
pcm_resample_lsr_init(struct pcm_resample_state *state)
{
memset(state, 0, sizeof(*state));
return convalgo;
pcm_buffer_init(&state->in);
pcm_buffer_init(&state->out);
pcm_buffer_init(&state->buffer);
}
void
pcm_resample_lsr_deinit(struct pcm_resample_state *state)
{
if (state->state != NULL)
state->state = src_delete(state->state);
pcm_buffer_deinit(&state->in);
pcm_buffer_deinit(&state->out);
pcm_buffer_deinit(&state->buffer);
}
static bool
pcm_resample_set(struct pcm_resample_state *state,
uint8_t channels, unsigned src_rate, unsigned dest_rate,
unsigned channels, unsigned src_rate, unsigned dest_rate,
GError **error_r)
{
static int convalgo = -1;
int error;
SRC_DATA *data = &state->data;
if (convalgo < 0)
convalgo = pcm_resample_get_converter();
/* (re)set the state/ratio if the in or out format changed */
if (channels == state->prev.channels &&
src_rate == state->prev.src_rate &&
......@@ -109,7 +126,7 @@ pcm_resample_set(struct pcm_resample_state *state,
if (state->state)
state->state = src_delete(state->state);
state->state = src_new(convalgo, channels, &error);
state->state = src_new(lsr_converter, channels, &error);
if (!state->state) {
g_set_error(error_r, libsamplerate_quark(), state->error,
"libsamplerate initialization has failed: %s",
......@@ -125,9 +142,63 @@ pcm_resample_set(struct pcm_resample_state *state,
return true;
}
static bool
lsr_process(struct pcm_resample_state *state, GError **error_r)
{
if (state->error == 0)
state->error = src_process(state->state, &state->data);
if (state->error) {
g_set_error(error_r, libsamplerate_quark(), state->error,
"libsamplerate has failed: %s",
src_strerror(state->error));
return false;
}
return true;
}
static float *
deconst_float_buffer(const float *in)
{
union {
const float *in;
float *out;
} u = { .in = in };
return u.out;
}
const float *
pcm_resample_lsr_float(struct pcm_resample_state *state,
unsigned channels,
unsigned src_rate,
const float *src_buffer, size_t src_size,
unsigned dest_rate, size_t *dest_size_r,
GError **error_r)
{
assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
if (!pcm_resample_set(state, channels, src_rate, dest_rate, error_r))
return NULL;
SRC_DATA *data = &state->data;
data->input_frames = src_size / sizeof(*src_buffer) / channels;
data->data_in = deconst_float_buffer(src_buffer);
data->output_frames = (src_size * dest_rate + src_rate - 1) / src_rate;
size_t data_out_size = data->output_frames * sizeof(float) * channels;
data->data_out = pcm_buffer_get(&state->out, data_out_size);
if (!lsr_process(state, error_r))
return NULL;
*dest_size_r = data->output_frames_gen *
sizeof(*data->data_out) * channels;
return data->data_out;
}
const int16_t *
pcm_resample_lsr_16(struct pcm_resample_state *state,
uint8_t channels,
unsigned channels,
unsigned src_rate,
const int16_t *src_buffer, size_t src_size,
unsigned dest_rate, size_t *dest_size_r,
......@@ -137,7 +208,6 @@ pcm_resample_lsr_16(struct pcm_resample_state *state,
SRC_DATA *data = &state->data;
size_t data_in_size;
size_t data_out_size;
int error;
int16_t *dest_buffer;
assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
......@@ -147,14 +217,6 @@ pcm_resample_lsr_16(struct pcm_resample_state *state,
if (!success)
return NULL;
/* there was an error previously, and nothing has changed */
if (state->error) {
g_set_error(error_r, libsamplerate_quark(), state->error,
"libsamplerate has failed: %s",
src_strerror(state->error));
return NULL;
}
data->input_frames = src_size / sizeof(*src_buffer) / channels;
data_in_size = data->input_frames * sizeof(float) * channels;
data->data_in = pcm_buffer_get(&state->in, data_in_size);
......@@ -166,14 +228,8 @@ pcm_resample_lsr_16(struct pcm_resample_state *state,
src_short_to_float_array(src_buffer, data->data_in,
data->input_frames * channels);
error = src_process(state->state, data);
if (error) {
g_set_error(error_r, libsamplerate_quark(), error,
"libsamplerate has failed: %s",
src_strerror(error));
state->error = error;
if (!lsr_process(state, error_r))
return NULL;
}
*dest_size_r = data->output_frames_gen *
sizeof(*dest_buffer) * channels;
......@@ -206,7 +262,7 @@ src_float_to_int_array (const float *in, int *out, int len)
const int32_t *
pcm_resample_lsr_32(struct pcm_resample_state *state,
uint8_t channels,
unsigned channels,
unsigned src_rate,
const int32_t *src_buffer, size_t src_size,
unsigned dest_rate, size_t *dest_size_r,
......@@ -216,7 +272,6 @@ pcm_resample_lsr_32(struct pcm_resample_state *state,
SRC_DATA *data = &state->data;
size_t data_in_size;
size_t data_out_size;
int error;
int32_t *dest_buffer;
assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
......@@ -226,14 +281,6 @@ pcm_resample_lsr_32(struct pcm_resample_state *state,
if (!success)
return NULL;
/* there was an error previously, and nothing has changed */
if (state->error) {
g_set_error(error_r, libsamplerate_quark(), state->error,
"libsamplerate has failed: %s",
src_strerror(state->error));
return NULL;
}
data->input_frames = src_size / sizeof(*src_buffer) / channels;
data_in_size = data->input_frames * sizeof(float) * channels;
data->data_in = pcm_buffer_get(&state->in, data_in_size);
......@@ -245,14 +292,8 @@ pcm_resample_lsr_32(struct pcm_resample_state *state,
src_int_to_float_array(src_buffer, data->data_in,
data->input_frames * channels);
error = src_process(state->state, data);
if (error) {
g_set_error(error_r, libsamplerate_quark(), error,
"libsamplerate has failed: %s",
src_strerror(error));
state->error = error;
if (!lsr_process(state, error_r))
return NULL;
}
*dest_size_r = data->output_frames_gen *
sizeof(*dest_buffer) * channels;
......
......@@ -25,6 +25,17 @@
#include <stdint.h>
/**
* Add a byte count to the specified pointer. This is a utility
* function to convert a source pointer and a byte count to an "end"
* pointer for use in loops.
*/
static inline const void *
pcm_end_pointer(const void *p, size_t size)
{
return (const char *)p + size;
}
/**
* Check if the value is within the range of the provided bit size,
* and caps it if necessary.
*/
......@@ -52,4 +63,32 @@ pcm_range_64(int64_t sample, unsigned bits)
return sample;
}
G_GNUC_CONST
static inline int16_t
pcm_clamp_16(int x)
{
static const int32_t MIN_VALUE = G_MININT16;
static const int32_t MAX_VALUE = G_MAXINT16;
if (G_UNLIKELY(x < MIN_VALUE))
return MIN_VALUE;
if (G_UNLIKELY(x > MAX_VALUE))
return MAX_VALUE;
return x;
}
G_GNUC_CONST
static inline int32_t
pcm_clamp_24(int x)
{
static const int32_t MIN_VALUE = -(1 << 23);
static const int32_t MAX_VALUE = (1 << 23) - 1;
if (G_UNLIKELY(x < MIN_VALUE))
return MIN_VALUE;
if (G_UNLIKELY(x > MAX_VALUE))
return MAX_VALUE;
return x;
}
#endif
......@@ -31,9 +31,9 @@
#define G_LOG_DOMAIN "pcm_volume"
static void
pcm_volume_change_8(int8_t *buffer, unsigned num_samples, int volume)
pcm_volume_change_8(int8_t *buffer, const int8_t *end, int volume)
{
while (num_samples > 0) {
while (buffer < end) {
int32_t sample = *buffer;
sample = (sample * volume + pcm_volume_dither() +
......@@ -41,14 +41,13 @@ pcm_volume_change_8(int8_t *buffer, unsigned num_samples, int volume)
/ PCM_VOLUME_1;
*buffer++ = pcm_range(sample, 8);
--num_samples;
}
}
static void
pcm_volume_change_16(int16_t *buffer, unsigned num_samples, int volume)
pcm_volume_change_16(int16_t *buffer, const int16_t *end, int volume)
{
while (num_samples > 0) {
while (buffer < end) {
int32_t sample = *buffer;
sample = (sample * volume + pcm_volume_dither() +
......@@ -56,7 +55,6 @@ pcm_volume_change_16(int16_t *buffer, unsigned num_samples, int volume)
/ PCM_VOLUME_1;
*buffer++ = pcm_range(sample, 16);
--num_samples;
}
}
......@@ -92,9 +90,9 @@ pcm_volume_sample_24(int32_t sample, int32_t volume, G_GNUC_UNUSED int32_t dithe
#endif
static void
pcm_volume_change_24(int32_t *buffer, unsigned num_samples, int volume)
pcm_volume_change_24(int32_t *buffer, const int32_t *end, int volume)
{
while (num_samples > 0) {
while (buffer < end) {
#ifdef __i386__
/* assembly version for i386 */
int32_t sample = *buffer;
......@@ -110,14 +108,13 @@ pcm_volume_change_24(int32_t *buffer, unsigned num_samples, int volume)
/ PCM_VOLUME_1;
#endif
*buffer++ = pcm_range(sample, 24);
--num_samples;
}
}
static void
pcm_volume_change_32(int32_t *buffer, unsigned num_samples, int volume)
pcm_volume_change_32(int32_t *buffer, const int32_t *end, int volume)
{
while (num_samples > 0) {
while (buffer < end) {
#ifdef __i386__
/* assembly version for i386 */
int32_t sample = *buffer;
......@@ -132,14 +129,22 @@ pcm_volume_change_32(int32_t *buffer, unsigned num_samples, int volume)
/ PCM_VOLUME_1;
*buffer++ = pcm_range_64(sample, 32);
#endif
}
}
--num_samples;
static void
pcm_volume_change_float(float *buffer, const float *end, float volume)
{
while (buffer < end) {
float sample = *buffer;
sample *= volume;
*buffer++ = sample;
}
}
bool
pcm_volume(void *buffer, int length,
const struct audio_format *format,
pcm_volume(void *buffer, size_t length,
enum sample_format format,
int volume)
{
if (volume == PCM_VOLUME_1)
......@@ -150,27 +155,36 @@ pcm_volume(void *buffer, int length,
return true;
}
switch (format->format) {
const void *end = pcm_end_pointer(buffer, length);
switch (format) {
case SAMPLE_FORMAT_UNDEFINED:
case SAMPLE_FORMAT_S24:
/* not implemented */
return false;
case SAMPLE_FORMAT_S8:
pcm_volume_change_8((int8_t *)buffer, length, volume);
pcm_volume_change_8(buffer, end, volume);
return true;
case SAMPLE_FORMAT_S16:
pcm_volume_change_16((int16_t *)buffer, length / 2,
volume);
pcm_volume_change_16(buffer, end, volume);
return true;
case SAMPLE_FORMAT_S24_P32:
pcm_volume_change_24((int32_t*)buffer, length / 4,
volume);
pcm_volume_change_24(buffer, end, volume);
return true;
case SAMPLE_FORMAT_S32:
pcm_volume_change_32((int32_t*)buffer, length / 4,
volume);
pcm_volume_change_32(buffer, end, volume);
return true;
default:
return false;
case SAMPLE_FORMAT_FLOAT:
pcm_volume_change_float(buffer, end,
pcm_volume_to_float(volume));
return true;
}
/* unreachable */
assert(false);
return false;
}
......@@ -21,6 +21,7 @@
#define PCM_VOLUME_H
#include "pcm_prng.h"
#include "audio_format.h"
#include <stdint.h>
#include <stdbool.h>
......@@ -42,6 +43,12 @@ pcm_float_to_volume(float volume)
return volume * PCM_VOLUME_1 + 0.5;
}
static inline float
pcm_volume_to_float(int volume)
{
return (float)volume / (float)PCM_VOLUME_1;
}
/**
* Returns the next volume dithering number, between -511 and +511.
* This number is taken from a global PRNG, see pcm_prng().
......@@ -62,13 +69,13 @@ pcm_volume_dither(void)
*
* @param buffer the PCM buffer
* @param length the length of the PCM buffer
* @param format the audio format of the PCM buffer
* @param format the sample format of the PCM buffer
* @param volume the volume between 0 and #PCM_VOLUME_1
* @return true on success, false if the audio format is not supported
*/
bool
pcm_volume(void *buffer, int length,
const struct audio_format *format,
pcm_volume(void *buffer, size_t length,
enum sample_format format,
int volume);
#endif
......@@ -319,9 +319,6 @@ pc_seek(struct player_control *pc, struct song *song, float seek_time)
{
assert(song != NULL);
if (pc->state == PLAYER_STATE_STOP)
return false;
player_lock(pc);
pc->next_song = song;
pc->seek_where = seek_time;
......
......@@ -76,6 +76,14 @@ struct player {
bool queued;
/**
* Was any audio output opened successfully? It might have
* failed meanwhile, but was not explicitly closed by the
* player thread. When this flag is unset, some output
* methods must not be called.
*/
bool output_open;
/**
* the song currently being played
*/
struct song *song;
......@@ -150,7 +158,13 @@ player_dc_start(struct player *player, struct music_pipe *pipe)
assert(player->queued || pc->command == PLAYER_COMMAND_SEEK);
assert(pc->next_song != NULL);
dc_start(dc, pc->next_song, player_buffer, pipe);
unsigned start_ms = pc->next_song->start_ms;
if (pc->command == PLAYER_COMMAND_SEEK)
start_ms += (unsigned)(pc->seek_where * 1000);
dc_start(dc, pc->next_song,
start_ms, pc->next_song->end_ms,
player_buffer, pipe);
}
/**
......@@ -277,6 +291,46 @@ real_song_duration(const struct song *song, double decoder_duration)
}
/**
* Wrapper for audio_output_all_open(). Upon failure, it pauses the
* player.
*
* @return true on success
*/
static bool
player_open_output(struct player *player)
{
struct player_control *pc = player->pc;
assert(audio_format_defined(&player->play_audio_format));
assert(pc->state == PLAYER_STATE_PLAY ||
pc->state == PLAYER_STATE_PAUSE);
if (audio_output_all_open(&player->play_audio_format, player_buffer)) {
player->output_open = true;
player->paused = false;
player_lock(pc);
pc->state = PLAYER_STATE_PLAY;
player_unlock(pc);
return true;
} else {
player->output_open = false;
/* pause: the user may resume playback as soon as an
audio output becomes available */
player->paused = true;
player_lock(pc);
pc->error = PLAYER_ERROR_AUDIO;
pc->state = PLAYER_STATE_PAUSE;
player_unlock(pc);
return false;
}
}
/**
* The decoder has acknowledged the "START" command (see
* player_wait_for_decoder()). This function checks if the decoder
* initialization has completed yet.
......@@ -308,7 +362,7 @@ player_check_decoder_startup(struct player *player)
decoder_unlock(dc);
if (audio_format_defined(&player->play_audio_format) &&
if (player->output_open &&
!audio_output_all_wait(pc, 1))
/* the output devices havn't finished playing
all chunks yet - wait for that */
......@@ -322,23 +376,12 @@ player_check_decoder_startup(struct player *player)
player->play_audio_format = dc->out_audio_format;
player->decoder_starting = false;
if (!player->paused &&
!audio_output_all_open(&dc->out_audio_format,
player_buffer)) {
if (!player->paused && !player_open_output(player)) {
char *uri = song_get_uri(dc->song);
g_warning("problems opening audio device "
"while playing \"%s\"", uri);
g_free(uri);
player_lock(pc);
pc->error = PLAYER_ERROR_AUDIO;
/* pause: the user may resume playback as soon
as an audio output becomes available */
pc->state = PLAYER_STATE_PAUSE;
player_unlock(pc);
player->paused = true;
return true;
}
......@@ -363,6 +406,7 @@ player_check_decoder_startup(struct player *player)
static bool
player_send_silence(struct player *player)
{
assert(player->output_open);
assert(audio_format_defined(&player->play_audio_format));
struct music_chunk *chunk = music_buffer_allocate(player_buffer);
......@@ -520,17 +564,8 @@ static void player_process_command(struct player *player)
player_lock(pc);
pc->state = PLAYER_STATE_PLAY;
} else if (audio_output_all_open(&player->play_audio_format, player_buffer)) {
/* unpaused, continue playing */
player_lock(pc);
pc->state = PLAYER_STATE_PLAY;
} else {
/* the audio device has failed - rollback to
pause mode */
pc->error = PLAYER_ERROR_AUDIO;
player->paused = true;
player_open_output(player);
player_lock(pc);
}
......@@ -567,8 +602,7 @@ static void player_process_command(struct player *player)
break;
case PLAYER_COMMAND_REFRESH:
if (audio_format_defined(&player->play_audio_format) &&
!player->paused) {
if (player->output_open && !player->paused) {
player_unlock(pc);
audio_output_all_check();
player_lock(pc);
......@@ -823,6 +857,7 @@ static void do_play(struct player_control *pc, struct decoder_control *dc)
.decoder_starting = false,
.paused = false,
.queued = true,
.output_open = false,
.song = NULL,
.xfade = XFADE_UNKNOWN,
.cross_fading = false,
......@@ -847,6 +882,10 @@ static void do_play(struct player_control *pc, struct decoder_control *dc)
player_lock(pc);
pc->state = PLAYER_STATE_PLAY;
if (pc->command == PLAYER_COMMAND_SEEK)
player.elapsed_time = pc->seek_where;
player_command_finished_locked(pc);
while (true) {
......@@ -871,7 +910,7 @@ static void do_play(struct player_control *pc, struct decoder_control *dc)
/* not enough decoded buffer space yet */
if (!player.paused &&
audio_format_defined(&player.play_audio_format) &&
player.output_open &&
audio_output_all_check() < 4 &&
!player_send_silence(&player))
break;
......@@ -894,16 +933,6 @@ static void do_play(struct player_control *pc, struct decoder_control *dc)
if (!player_check_decoder_startup(&player))
break;
/* seek to the beginning of the range */
const struct song *song = decoder_current_song(dc);
if (song != NULL && song->start_ms > 0 &&
/* we must not send a seek command until
the decoder is initialized
completely */
!player.decoder_starting &&
!dc_seek(dc, song->start_ms / 1000.0))
player_dc_stop(&player);
player_lock(pc);
continue;
}
......@@ -986,7 +1015,7 @@ static void do_play(struct player_control *pc, struct decoder_control *dc)
audio_output_all_drain();
break;
}
} else {
} else if (player.output_open) {
/* the decoder is too busy and hasn't provided
new PCM data in time: send silence (if the
output pipe is empty) */
......@@ -1035,6 +1064,7 @@ player_task(gpointer arg)
while (1) {
switch (pc->command) {
case PLAYER_COMMAND_SEEK:
case PLAYER_COMMAND_QUEUE:
assert(pc->next_song != NULL);
......@@ -1048,7 +1078,6 @@ player_task(gpointer arg)
/* fall through */
case PLAYER_COMMAND_SEEK:
case PLAYER_COMMAND_PAUSE:
pc->next_song = NULL;
player_command_finished_locked(pc);
......
......@@ -239,6 +239,18 @@ enum playlist_result
playlist_seek_song_id(struct playlist *playlist, struct player_control *pc,
unsigned id, float seek_time);
/**
* Seek within the current song. Fails if MPD is not currently
* playing.
*
* @param time the time in seconds
* @param relative if true, then the specified time is relative to the
* current position
*/
enum playlist_result
playlist_seek_current(struct playlist *playlist, struct player_control *pc,
float seek_time, bool relative);
void
playlist_increment_version_all(struct playlist *playlist);
......
......@@ -262,3 +262,27 @@ playlist_seek_song_id(struct playlist *playlist, struct player_control *pc,
return playlist_seek_song(playlist, pc, song, seek_time);
}
enum playlist_result
playlist_seek_current(struct playlist *playlist, struct player_control *pc,
float seek_time, bool relative)
{
if (!playlist->playing)
return PLAYLIST_RESULT_NOT_PLAYING;
if (relative) {
struct player_status status;
pc_get_status(pc, &status);
if (status.state != PLAYER_STATE_PLAY &&
status.state != PLAYER_STATE_PAUSE)
return PLAYLIST_RESULT_NOT_PLAYING;
seek_time += (int)status.elapsed_time;
}
if (seek_time < 0)
seek_time = 0;
return playlist_seek_song(playlist, pc, playlist->current, seek_time);
}
......@@ -418,7 +418,7 @@ playlist_move_range(struct playlist *playlist, struct player_control *pc,
playlist->current)
: -1;
if (to < 0 && playlist->current >= 0) {
if (start <= (unsigned)currentSong && (unsigned)currentSong <= end)
if (start <= (unsigned)currentSong && (unsigned)currentSong < end)
/* no-op, can't be moved to offset of itself */
return PLAYLIST_RESULT_SUCCESS;
to = (currentSong + abs(to)) % queue_length(&playlist->queue);
......
......@@ -115,9 +115,7 @@ playlist_check_translate_song(struct song *song, const char *base_uri,
if (g_path_is_absolute(uri)) {
/* XXX fs_charset vs utf8? */
char *prefix = base_uri != NULL
? map_uri_fs(base_uri)
: map_directory_fs(db_get_root());
char *prefix = map_directory_fs(db_get_root());
if (prefix != NULL && g_str_has_prefix(uri, prefix) &&
uri[strlen(prefix)] == '/')
......@@ -130,6 +128,7 @@ playlist_check_translate_song(struct song *song, const char *base_uri,
return NULL;
}
base_uri = NULL;
g_free(prefix);
}
......@@ -141,6 +140,12 @@ playlist_check_translate_song(struct song *song, const char *base_uri,
if (uri_has_scheme(uri)) {
dest = song_remote_new(uri);
g_free(uri);
} else if (g_path_is_absolute(uri) && secure) {
dest = song_file_load(uri, NULL);
if (dest == NULL) {
song_free(song);
return NULL;
}
} else {
dest = db_get_song(uri);
g_free(uri);
......
......@@ -37,6 +37,7 @@
#define MPD_REFCOUNT_H
#include <glib.h>
#include <stdbool.h>
struct refcount {
gint n;
......
......@@ -19,19 +19,17 @@
#include "config.h"
#include "resolver.h"
#include "glib_compat.h"
#ifndef G_OS_WIN32
#include <sys/socket.h>
#include <netdb.h>
#else /* G_OS_WIN32 */
#define WINVER 0x0501
#include <ws2tcpip.h>
#include <winsock.h>
#endif /* G_OS_WIN32 */
#ifdef HAVE_IPV6
#include <string.h>
#endif
char *
sockaddr_to_string(const struct sockaddr *sa, size_t length, GError **error)
......@@ -81,3 +79,63 @@ sockaddr_to_string(const struct sockaddr *sa, size_t length, GError **error)
return g_strconcat(host, ":", serv, NULL);
}
struct addrinfo *
resolve_host_port(const char *host_port, unsigned default_port,
int flags, int socktype,
GError **error_r)
{
char *p = g_strdup(host_port);
const char *host = p, *port = NULL;
if (host_port[0] == '[') {
/* IPv6 needs enclosing square braces, to
differentiate between IP colons and the port
separator */
char *q = strchr(p + 1, ']');
if (q != NULL && q[1] == ':' && q[2] != 0) {
*q = 0;
++host;
port = q + 2;
}
}
if (port == NULL) {
/* port is after the colon, but only if it's the only
colon (don't split IPv6 addresses) */
char *q = strchr(p, ':');
if (q != NULL && q[1] != 0 && strchr(q + 1, ':') == NULL) {
*q = 0;
port = q + 1;
}
}
char buffer[32];
if (port == NULL && default_port != 0) {
g_snprintf(buffer, sizeof(buffer), "%u", default_port);
port = buffer;
}
if ((flags & AI_PASSIVE) != 0 && strcmp(host, "*") == 0)
host = NULL;
const struct addrinfo hints = {
.ai_flags = flags,
.ai_family = AF_UNSPEC,
.ai_socktype = socktype,
};
struct addrinfo *ai;
int ret = getaddrinfo(host, port, &hints, &ai);
g_free(p);
if (ret != 0) {
g_set_error(error_r, resolver_quark(), ret,
"Failed to look up '%s': %s",
host_port, gai_strerror(ret));
return NULL;
}
return ai;
}
......@@ -23,6 +23,14 @@
#include <glib.h>
struct sockaddr;
struct addrinfo;
G_GNUC_CONST
static inline GQuark
resolver_quark(void)
{
return g_quark_from_static_string("resolver");
}
/**
* Converts the specified socket address into a string in the form
......@@ -34,7 +42,23 @@ struct sockaddr;
* @param error location to store the error occurring, or NULL to
* ignore errors
*/
G_GNUC_MALLOC
char *
sockaddr_to_string(const struct sockaddr *sa, size_t length, GError **error);
/**
* Resolve a specification in the form "host", "host:port",
* "[host]:port". This is a convenience wrapper for getaddrinfo().
*
* @param default_port a default port number that will be used if none
* is given in the string (if applicable); pass 0 to go without a
* default
* @return an #addrinfo linked list that must be freed with
* freeaddrinfo(), or NULL on error
*/
struct addrinfo *
resolve_host_port(const char *host_port, unsigned default_port,
int flags, int socktype,
GError **error_r);
#endif
......@@ -36,7 +36,6 @@
#include <sys/time.h>
#ifdef WIN32
#define WINVER 0x0501
#include <ws2tcpip.h>
#include <winsock.h>
#else
......
......@@ -28,7 +28,6 @@
#include <glib.h>
#ifdef WIN32
#define WINVER 0x0501
#include <ws2tcpip.h>
#include <winsock.h>
#else
......
......@@ -40,7 +40,6 @@
#include <assert.h>
#ifdef WIN32
#define WINVER 0x0501
#include <ws2tcpip.h>
#include <winsock.h>
#else
......@@ -380,24 +379,11 @@ server_socket_add_host(struct server_socket *ss, const char *hostname,
unsigned port, GError **error_r)
{
#ifdef HAVE_TCP
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
char service[20];
g_snprintf(service, sizeof(service), "%u", port);
struct addrinfo *ai;
int ret = getaddrinfo(hostname, service, &hints, &ai);
if (ret != 0) {
g_set_error(error_r, server_socket_quark(), ret,
"Failed to look up host \"%s\": %s",
hostname, gai_strerror(ret));
struct addrinfo *ai = resolve_host_port(hostname, port,
AI_PASSIVE, SOCK_STREAM,
error_r);
if (ai == NULL)
return false;
}
for (const struct addrinfo *i = ai; i != NULL; i = i->ai_next)
server_socket_add_address(ss, i->ai_addr, i->ai_addrlen);
......
......@@ -27,7 +27,6 @@
#ifndef G_OS_WIN32
#include <sys/socket.h>
#else /* G_OS_WIN32 */
#define WINVER 0x0501
#include <ws2tcpip.h>
#include <winsock.h>
#endif /* G_OS_WIN32 */
......@@ -50,9 +49,6 @@ socket_bind_listen(int domain, int type, int protocol,
{
int fd, ret;
const int reuse = 1;
#ifdef HAVE_STRUCT_UCRED
int passcred = 1;
#endif
fd = socket_cloexec_nonblock(domain, type, protocol);
if (fd < 0) {
......@@ -61,14 +57,8 @@ socket_bind_listen(int domain, int type, int protocol,
return -1;
}
#ifdef WIN32
const char *optval = (const char *)&reuse;
#else
const void *optval = &reuse;
#endif
ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
optval, sizeof(reuse));
(const char *) &reuse, sizeof(reuse));
if (ret < 0) {
g_set_error(error, listen_quark(), errno,
"setsockopt() failed: %s", g_strerror(errno));
......@@ -93,7 +83,8 @@ socket_bind_listen(int domain, int type, int protocol,
}
#ifdef HAVE_STRUCT_UCRED
setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &passcred, sizeof(passcred));
setsockopt(fd, SOL_SOCKET, SO_PASSCRED,
(const char *) &reuse, sizeof(reuse));
#endif
return fd;
......@@ -104,12 +95,6 @@ socket_keepalive(int fd)
{
const int reuse = 1;
#ifdef WIN32
const char *optval = (const char *)&reuse;
#else
const void *optval = &reuse;
#endif
return setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
optval, sizeof(reuse));
(const char *)&reuse, sizeof(reuse));
}
......@@ -20,6 +20,8 @@
#ifndef MPD_SONG_H
#define MPD_SONG_H
#include "util/list.h"
#include <stddef.h>
#include <stdbool.h>
#include <sys/time.h>
......@@ -28,6 +30,16 @@
#define SONG_TIME "Time: "
struct song {
/**
* Pointers to the siblings of this directory within the
* parent directory. It is unused (undefined) if this song is
* not in the database.
*
* This attribute is protected with the global #db_mutex.
* Read access in the update thread does not need protection.
*/
struct list_head siblings;
struct tag *tag;
struct directory *parent;
time_t mtime;
......
......@@ -20,7 +20,6 @@
#include "config.h"
#include "song_print.h"
#include "song.h"
#include "songvec.h"
#include "directory.h"
#include "tag_print.h"
#include "client.h"
......
......@@ -59,19 +59,6 @@ song_save(FILE *fp, const struct song *song)
fprintf(fp, SONG_END "\n");
}
static int
song_save_callback(struct song *song, void *data)
{
FILE *fp = data;
song_save(fp, song);
return 0;
}
void songvec_save(FILE *fp, const struct songvec *sv)
{
songvec_for_each(sv, song_save_callback, fp);
}
struct song *
song_load(FILE *fp, struct directory *parent, const char *uri,
GString *buffer, GError **error_r)
......
......@@ -27,15 +27,11 @@
#define SONG_BEGIN "song_begin: "
struct song;
struct songvec;
struct directory;
void
song_save(FILE *fp, const struct song *song);
void
songvec_save(FILE *fp, const struct songvec *sv);
/**
* Loads a song from the input file. Reading stops after the
* "song_end" line.
......
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* Copyright (C) 2003-2012 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
......@@ -18,18 +18,17 @@
*/
#include "config.h"
#include "songvec.h"
#include "song_sort.h"
#include "song.h"
#include "util/list.h"
#include "util/list_sort.h"
#include "tag.h"
#include <glib.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
static GMutex *nr_lock = NULL;
static const char *
tag_get_value_checked(const struct tag *tag, enum tag_type type)
{
......@@ -89,10 +88,11 @@ compare_tag_item(const struct tag *a, const struct tag *b, enum tag_type type)
}
/* Only used for sorting/searchin a songvec, not general purpose compares */
static int songvec_cmp(const void *s1, const void *s2)
static int
song_cmp(G_GNUC_UNUSED void *priv, struct list_head *_a, struct list_head *_b)
{
const struct song *a = ((const struct song * const *)s1)[0];
const struct song *b = ((const struct song * const *)s2)[0];
const struct song *a = (const struct song *)_a;
const struct song *b = (const struct song *)_b;
int ret;
/* first sort by album */
......@@ -114,117 +114,8 @@ static int songvec_cmp(const void *s1, const void *s2)
return g_utf8_collate(a->uri, b->uri);
}
static size_t sv_size(const struct songvec *sv)
{
return sv->nr * sizeof(struct song *);
}
void songvec_init(void)
{
g_assert(nr_lock == NULL);
nr_lock = g_mutex_new();
}
void songvec_deinit(void)
{
g_assert(nr_lock != NULL);
g_mutex_free(nr_lock);
nr_lock = NULL;
}
void songvec_sort(struct songvec *sv)
{
g_mutex_lock(nr_lock);
qsort(sv->base, sv->nr, sizeof(struct song *), songvec_cmp);
g_mutex_unlock(nr_lock);
}
struct song *
songvec_find(const struct songvec *sv, const char *uri)
{
int i;
struct song *ret = NULL;
g_mutex_lock(nr_lock);
for (i = sv->nr; --i >= 0; ) {
if (strcmp(sv->base[i]->uri, uri))
continue;
ret = sv->base[i];
break;
}
g_mutex_unlock(nr_lock);
return ret;
}
int
songvec_delete(struct songvec *sv, const struct song *del)
{
size_t i;
g_mutex_lock(nr_lock);
for (i = 0; i < sv->nr; ++i) {
if (sv->base[i] != del)
continue;
/* we _don't_ call song_free() here */
if (!--sv->nr) {
g_free(sv->base);
sv->base = NULL;
} else {
memmove(&sv->base[i], &sv->base[i + 1],
(sv->nr - i) * sizeof(struct song *));
sv->base = g_realloc(sv->base, sv_size(sv));
}
g_mutex_unlock(nr_lock);
return i;
}
g_mutex_unlock(nr_lock);
return -1; /* not found */
}
void
songvec_add(struct songvec *sv, struct song *add)
{
g_mutex_lock(nr_lock);
++sv->nr;
sv->base = g_realloc(sv->base, sv_size(sv));
sv->base[sv->nr - 1] = add;
g_mutex_unlock(nr_lock);
}
void songvec_destroy(struct songvec *sv)
{
g_mutex_lock(nr_lock);
sv->nr = 0;
g_mutex_unlock(nr_lock);
g_free(sv->base);
sv->base = NULL;
}
int
songvec_for_each(const struct songvec *sv,
int (*fn)(struct song *, void *), void *arg)
song_list_sort(struct list_head *songs)
{
size_t i;
size_t prev_nr;
g_mutex_lock(nr_lock);
for (i = 0; i < sv->nr; ) {
struct song *song = sv->base[i];
assert(song);
assert(*song->uri);
prev_nr = sv->nr;
g_mutex_unlock(nr_lock); /* fn() may block */
if (fn(song, arg) < 0)
return -1;
g_mutex_lock(nr_lock); /* sv->nr may change in fn() */
if (prev_nr == sv->nr)
++i;
}
g_mutex_unlock(nr_lock);
return 0;
list_sort(NULL, songs, song_cmp);
}
/*
* Copyright (C) 2003-2012 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_SONG_SORT_H
#define MPD_SONG_SORT_H
struct list_head;
void
song_list_sort(struct list_head *songs);
#endif
......@@ -68,6 +68,8 @@ sticker_song_get(const struct song *song);
* Finds stickers with the specified name below the specified
* directory.
*
* Caller must lock the #db_mutex.
*
* @param directory the base directory to search in
* @param name the name of the sticker
* @return true on success (even if no sticker was found), false on
......
......@@ -123,6 +123,6 @@ int stats_print(struct client *client)
(long)g_timer_elapsed(stats.timer, NULL),
(long)(pc_get_total_play_time(client->player_control) + 0.5),
stats.song_duration,
db_get_mtime());
(long)db_get_mtime());
return 0;
}
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "tcp_connect.h"
#include "fd_util.h"
#include "io_thread.h"
#include "glib_compat.h"
#include "glib_socket.h"
#include <assert.h>
#include <errno.h>
#ifdef WIN32
#include <ws2tcpip.h>
#include <winsock.h>
#else
#include <sys/socket.h>
#include <unistd.h>
#endif
struct tcp_connect {
const struct tcp_connect_handler *handler;
void *handler_ctx;
int fd;
GSource *source;
unsigned timeout_ms;
GSource *timeout_source;
};
static bool
is_in_progress_errno(int e)
{
#ifdef WIN32
return e == WSAEINPROGRESS || e == WSAEWOULDBLOCK;
#else
return e == EINPROGRESS;
#endif
}
static gboolean
tcp_connect_event(G_GNUC_UNUSED GIOChannel *source,
G_GNUC_UNUSED GIOCondition condition,
gpointer data)
{
struct tcp_connect *c = data;
assert(c->source != NULL);
assert(c->timeout_source != NULL);
/* clear the socket source */
g_source_unref(c->source);
c->source = NULL;
/* delete the timeout source */
g_source_destroy(c->timeout_source);
g_source_unref(c->timeout_source);
c->timeout_source = NULL;
/* obtain the connect result */
int s_err = 0;
socklen_t s_err_size = sizeof(s_err);
if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR,
(char*)&s_err, &s_err_size) < 0)
s_err = errno;
if (s_err == 0) {
/* connection established successfully */
c->handler->success(c->fd, c->handler_ctx);
} else {
/* there was an I/O error; close the socket and pass
the error to the handler */
close_socket(c->fd);
GError *error =
g_error_new_literal(g_file_error_quark(), s_err,
g_strerror(s_err));
c->handler->error(error, c->handler_ctx);
}
return false;
}
static gboolean
tcp_connect_timeout(gpointer data)
{
struct tcp_connect *c = data;
assert(c->source != NULL);
assert(c->timeout_source != NULL);
/* clear the timeout source */
g_source_unref(c->timeout_source);
c->timeout_source = NULL;
/* delete the socket source */
g_source_destroy(c->source);
g_source_unref(c->source);
c->source = NULL;
/* report timeout to handler */
c->handler->timeout(c->handler_ctx);
return false;
}
static gpointer
tcp_connect_init(gpointer data)
{
struct tcp_connect *c = data;
/* create a connect source */
GIOChannel *channel = g_io_channel_new_socket(c->fd);
c->source = g_io_create_watch(channel, G_IO_OUT);
g_io_channel_unref(channel);
g_source_set_callback(c->source, (GSourceFunc)tcp_connect_event, c,
NULL);
g_source_attach(c->source, io_thread_context());
/* create a timeout source */
if (c->timeout_ms > 0)
c->timeout_source =
io_thread_timeout_add(c->timeout_ms,
tcp_connect_timeout, c);
return NULL;
}
void
tcp_connect_address(const struct sockaddr *address, size_t address_length,
unsigned timeout_ms,
const struct tcp_connect_handler *handler, void *ctx,
struct tcp_connect **handle_r)
{
assert(address != NULL);
assert(address_length > 0);
assert(handler != NULL);
assert(handler->success != NULL);
assert(handler->error != NULL);
assert(handler->canceled != NULL);
assert(handler->timeout != NULL || timeout_ms == 0);
assert(handle_r != NULL);
assert(*handle_r == NULL);
int fd = socket_cloexec_nonblock(address->sa_family, SOCK_STREAM, 0);
if (fd < 0) {
GError *error =
g_error_new_literal(g_file_error_quark(), errno,
g_strerror(errno));
handler->error(error, ctx);
return;
}
int ret = connect(fd, address, address_length);
if (ret >= 0) {
/* quick connect, no I/O thread */
handler->success(fd, ctx);
return;
}
if (!is_in_progress_errno(errno)) {
GError *error =
g_error_new_literal(g_file_error_quark(), errno,
g_strerror(errno));
close_socket(fd);
handler->error(error, ctx);
return;
}
/* got EINPROGRESS, use the I/O thread to wait for the
operation to finish */
struct tcp_connect *c = g_new(struct tcp_connect, 1);
c->handler = handler;
c->handler_ctx = ctx;
c->fd = fd;
c->source = NULL;
c->timeout_ms = timeout_ms;
c->timeout_source = NULL;
*handle_r = c;
io_thread_call(tcp_connect_init, c);
}
static gpointer
tcp_connect_cancel_callback(gpointer data)
{
struct tcp_connect *c = data;
assert((c->source == NULL) == (c->timeout_source == NULL));
if (c->source == NULL)
return NULL;
/* delete the socket source */
g_source_destroy(c->source);
g_source_unref(c->source);
c->source = NULL;
/* delete the timeout source */
g_source_destroy(c->timeout_source);
g_source_unref(c->timeout_source);
c->timeout_source = NULL;
/* close the socket */
close_socket(c->fd);
/* notify the handler */
c->handler->canceled(c->handler_ctx);
return NULL;
}
void
tcp_connect_cancel(struct tcp_connect *c)
{
if (c->source == NULL)
return;
io_thread_call(tcp_connect_cancel_callback, c);
}
void
tcp_connect_free(struct tcp_connect *c)
{
assert(c->source == NULL);
g_free(c);
}
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_TCP_CONNECT_H
#define MPD_TCP_CONNECT_H
#include <glib.h>
struct sockaddr;
struct tcp_connect_handler {
/**
* The connection was established successfully.
*
* @param fd a file descriptor that must be closed with
* close_socket() when finished
*/
void (*success)(int fd, void *ctx);
/**
* An error has occurred. The method is responsible for
* freeing the GError.
*/
void (*error)(GError *error, void *ctx);
/**
* The connection could not be established in the specified
* time span.
*/
void (*timeout)(void *ctx);
/**
* The operation was canceled before a result was available.
*/
void (*canceled)(void *ctx);
};
struct tcp_connect;
/**
* Establish a TCP connection to the specified address.
*
* Note that the result may be available before this function returns.
*
* The caller must free this object with tcp_connect_free().
*
* @param timeout_ms time out after this number of milliseconds; 0
* means no timeout
* @param handle_r a handle that can be used to cancel the operation;
* the caller must initialize it to NULL
*/
void
tcp_connect_address(const struct sockaddr *address, size_t address_length,
unsigned timeout_ms,
const struct tcp_connect_handler *handler, void *ctx,
struct tcp_connect **handle_r);
/**
* Cancel the operation. It is possible that the result is delivered
* before the operation has been canceled; in that case, the
* canceled() handler method will not be invoked.
*
* Even after calling this function, tcp_connect_free() must still be
* called to free memory.
*/
void
tcp_connect_cancel(struct tcp_connect *handle);
/**
* Free memory used by this object.
*
* This function is not thread safe. It must not be called while
* other threads are still working with it. If no callback has been
* invoked so far, then you must call tcp_connect_cancel() to release
* I/O thread resources, before calling this function.
*/
void
tcp_connect_free(struct tcp_connect *handle);
#endif
......@@ -26,7 +26,6 @@
#include <string.h>
#ifdef WIN32
#define WINVER 0x0501
#include <ws2tcpip.h>
#include <winsock.h>
#else
......
......@@ -81,7 +81,7 @@ timer_delay(const struct timer *timer)
if (delay > G_MAXINT)
delay = G_MAXINT;
return delay / 1000;
return delay;
}
void timer_sync(struct timer *timer)
......
......@@ -28,7 +28,6 @@
#include <errno.h>
#ifdef WIN32
#define WINVER 0x0501
#include <ws2tcpip.h>
#include <winsock.h>
#else
......
......@@ -20,6 +20,7 @@
#include "config.h" /* must be first for large file support */
#include "update_internal.h"
#include "database.h"
#include "db_lock.h"
#include "exclude.h"
#include "directory.h"
#include "song.h"
......@@ -86,29 +87,29 @@ directory_set_stat(struct directory *dir, const struct stat *st)
{
dir->inode = st->st_ino;
dir->device = st->st_dev;
dir->stat = 1;
dir->have_stat = true;
}
/**
* Caller must lock the #db_mutex.
*/
static void
delete_song(struct directory *dir, struct song *del)
{
assert(del->parent == dir);
/* first, prevent traversers in main task from getting this */
songvec_delete(&dir->songs, del);
directory_remove_song(dir, del);
db_unlock(); /* temporary unlock, because update_remove_song() blocks */
/* now take it out of the playlist (in the main_task) */
update_remove_song(del);
/* finally, all possible references gone, free it */
song_free(del);
}
static int
delete_each_song(struct song *song, G_GNUC_UNUSED void *data)
{
struct directory *directory = data;
assert(song->parent == directory);
delete_song(directory, song);
return 0;
db_lock();
}
static void
......@@ -117,22 +118,27 @@ delete_directory(struct directory *directory);
/**
* Recursively remove all sub directories and songs from a directory,
* leaving an empty directory.
*
* Caller must lock the #db_mutex.
*/
static void
clear_directory(struct directory *directory)
{
int i;
for (i = directory->children.nr; --i >= 0;)
delete_directory(directory->children.base[i]);
struct directory *child, *n;
directory_for_each_child_safe(child, n, directory)
delete_directory(child);
assert(directory->children.nr == 0);
songvec_for_each(&directory->songs, delete_each_song, directory);
struct song *song, *ns;
directory_for_each_song_safe(song, ns, directory) {
assert(song->parent == directory);
delete_song(directory, song);
}
}
/**
* Recursively free a directory and all its contents.
*
* Caller must lock the #db_mutex.
*/
static void
delete_directory(struct directory *directory)
......@@ -141,57 +147,39 @@ delete_directory(struct directory *directory)
clear_directory(directory);
dirvec_delete(&directory->parent->children, directory);
directory_free(directory);
directory_delete(directory);
}
static void
delete_name_in(struct directory *parent, const char *name)
{
db_lock();
struct directory *directory = directory_get_child(parent, name);
struct song *song = songvec_find(&parent->songs, name);
if (directory != NULL) {
delete_directory(directory);
modified = true;
}
struct song *song = directory_get_song(parent, name);
if (song != NULL) {
delete_song(parent, song);
modified = true;
}
playlist_vector_remove(&parent->playlists, name);
}
/* passed to songvec_for_each */
static int
delete_song_if_excluded(struct song *song, void *_data)
{
GSList *exclude_list = _data;
char *name_fs;
assert(song->parent != NULL);
db_unlock();
name_fs = utf8_to_fs_charset(song->uri);
if (exclude_list_check(exclude_list, name_fs)) {
delete_song(song->parent, song);
modified = true;
}
g_free(name_fs);
return 0;
playlist_vector_remove(&parent->playlists, name);
}
static void
remove_excluded_from_directory(struct directory *directory,
GSList *exclude_list)
{
int i;
struct dirvec *dv = &directory->children;
db_lock();
for (i = dv->nr; --i >= 0; ) {
struct directory *child = dv->base[i];
struct directory *child, *n;
directory_for_each_child_safe(child, n, directory) {
char *name_fs = utf8_to_fs_charset(directory_get_name(child));
if (exclude_list_check(exclude_list, name_fs)) {
......@@ -202,26 +190,20 @@ remove_excluded_from_directory(struct directory *directory,
g_free(name_fs);
}
songvec_for_each(&directory->songs,
delete_song_if_excluded, exclude_list);
}
struct song *song, *ns;
directory_for_each_song_safe(song, ns, directory) {
assert(song->parent == directory);
/* passed to songvec_for_each */
static int
delete_song_if_removed(struct song *song, void *_data)
{
struct directory *dir = _data;
char *path;
struct stat st;
char *name_fs = utf8_to_fs_charset(song->uri);
if (exclude_list_check(exclude_list, name_fs)) {
delete_song(directory, song);
modified = true;
}
if ((path = map_song_fs(song)) == NULL ||
stat(path, &st) < 0 || !S_ISREG(st.st_mode)) {
delete_song(dir, song);
modified = true;
g_free(name_fs);
}
g_free(path);
return 0;
db_unlock();
}
static bool
......@@ -265,19 +247,33 @@ directory_child_is_regular(const struct directory *directory,
static void
removeDeletedFromDirectory(struct directory *directory)
{
int i;
struct dirvec *dv = &directory->children;
for (i = dv->nr; --i >= 0; ) {
if (directory_exists(dv->base[i]))
struct directory *child, *n;
directory_for_each_child_safe(child, n, directory) {
if (directory_exists(child))
continue;
g_debug("removing directory: %s", dv->base[i]->path);
delete_directory(dv->base[i]);
db_lock();
delete_directory(child);
db_unlock();
modified = true;
}
songvec_for_each(&directory->songs, delete_song_if_removed, directory);
struct song *song, *ns;
directory_for_each_song_safe(song, ns, directory) {
char *path;
struct stat st;
if ((path = map_song_fs(song)) == NULL ||
stat(path, &st) < 0 || !S_ISREG(st.st_mode)) {
db_lock();
delete_song(directory, song);
db_unlock();
modified = true;
}
g_free(path);
}
for (const struct playlist_metadata *pm = directory->playlists.head;
pm != NULL;) {
......@@ -346,7 +342,7 @@ inodeFoundInParent(struct directory *parent, ino_t inode, dev_t device)
{
#ifndef G_OS_WIN32
while (parent) {
if (!parent->stat && statDirectory(parent) < 0)
if (!parent->have_stat && statDirectory(parent) < 0)
return -1;
if (parent->inode == inode && parent->device == device) {
g_debug("recursive directory found");
......@@ -363,45 +359,21 @@ inodeFoundInParent(struct directory *parent, ino_t inode, dev_t device)
return 0;
}
static struct directory *
make_subdir(struct directory *parent, const char *name)
{
struct directory *directory;
directory = directory_get_child(parent, name);
if (directory == NULL) {
char *path;
if (directory_is_root(parent))
path = NULL;
else
name = path = g_strconcat(directory_get_path(parent),
"/", name, NULL);
directory = directory_new_child(parent, name);
g_free(path);
}
return directory;
}
#ifdef ENABLE_ARCHIVE
static void
update_archive_tree(struct directory *directory, char *name)
{
struct directory *subdir;
struct song *song;
char *tmp;
tmp = strchr(name, '/');
if (tmp) {
*tmp = 0;
//add dir is not there already
if ((subdir = dirvec_find(&directory->children, name)) == NULL) {
//create new directory
subdir = make_subdir(directory, name);
subdir->device = DEVICE_INARCHIVE;
}
db_lock();
subdir = directory_make_child(directory, name);
subdir->device = DEVICE_INARCHIVE;
db_unlock();
//create directories first
update_archive_tree(subdir, tmp+1);
} else {
......@@ -410,11 +382,13 @@ update_archive_tree(struct directory *directory, char *name)
return;
}
//add file
song = songvec_find(&directory->songs, name);
db_lock();
struct song *song = directory_get_song(directory, name);
db_unlock();
if (song == NULL) {
song = song_file_load(name, directory);
if (song != NULL) {
songvec_add(&directory->songs, song);
directory_add_song(directory, song);
modified = true;
g_message("added %s/%s",
directory_get_path(directory), name);
......@@ -442,7 +416,9 @@ update_archive_file(struct directory *parent, const char *name,
struct directory *directory;
char *filepath;
directory = dirvec_find(&parent->children, name);
db_lock();
directory = directory_get_child(parent, name);
db_unlock();
if (directory != NULL && directory->mtime == st->st_mtime &&
!walk_discard)
/* MPD has already scanned the archive, and it hasn't
......@@ -465,10 +441,12 @@ update_archive_file(struct directory *parent, const char *name,
if (directory == NULL) {
g_debug("creating archive directory: %s", name);
directory = make_subdir(parent, name);
db_lock();
directory = directory_new_child(parent, name);
/* mark this directory as archive (we use device for
this) */
directory->device = DEVICE_INARCHIVE;
db_unlock();
}
directory->mtime = st->st_mtime;
......@@ -494,7 +472,9 @@ update_container_file( struct directory* directory,
char* vtrack = NULL;
unsigned int tnum = 0;
char* pathname = map_directory_child_fs(directory, name);
struct directory* contdir = dirvec_find(&directory->children, name);
db_lock();
struct directory *contdir = directory_get_child(directory, name);
// directory exists already
if (contdir != NULL)
......@@ -510,14 +490,16 @@ update_container_file( struct directory* directory,
modified = true;
}
else {
db_unlock();
g_free(pathname);
return true;
}
}
contdir = make_subdir(directory, name);
contdir = directory_make_child(directory, name);
contdir->mtime = st->st_mtime;
contdir->device = DEVICE_CONTAINER;
db_unlock();
while ((vtrack = plugin->container_scan(pathname, ++tnum)) != NULL)
{
......@@ -532,7 +514,7 @@ update_container_file( struct directory* directory,
song->tag = plugin->tag_dup(child_path_fs);
g_free(child_path_fs);
songvec_add(&contdir->songs, song);
directory_add_song(contdir, song);
modified = true;
......@@ -545,7 +527,9 @@ update_container_file( struct directory* directory,
if (tnum == 1)
{
db_lock();
delete_directory(contdir);
db_unlock();
return false;
}
else
......@@ -592,13 +576,19 @@ update_regular_file(struct directory *directory,
if ((plugin = decoder_plugin_from_suffix(suffix, false)) != NULL)
{
struct song* song = songvec_find(&directory->songs, name);
db_lock();
struct song *song = directory_get_song(directory, name);
db_unlock();
if (!directory_child_access(directory, name, R_OK)) {
g_warning("no read permissions on %s/%s",
directory_get_path(directory), name);
if (song != NULL)
if (song != NULL) {
db_lock();
delete_song(directory, song);
db_unlock();
}
return;
}
......@@ -608,14 +598,19 @@ update_regular_file(struct directory *directory,
{
if (update_container_file(directory, name, st, plugin))
{
if (song != NULL)
if (song != NULL) {
db_lock();
delete_song(directory, song);
db_unlock();
}
return;
}
}
if (song == NULL) {
g_debug("reading %s/%s",
directory_get_path(directory), name);
song = song_file_load(name, directory);
if (song == NULL) {
g_debug("ignoring unrecognized file %s/%s",
......@@ -623,7 +618,7 @@ update_regular_file(struct directory *directory,
return;
}
songvec_add(&directory->songs, song);
directory_add_song(directory, song);
modified = true;
g_message("added %s/%s",
directory_get_path(directory), name);
......@@ -633,7 +628,9 @@ update_regular_file(struct directory *directory,
if (!song_file_update(song)) {
g_debug("deleting unrecognized file %s/%s",
directory_get_path(directory), name);
db_lock();
delete_song(directory, song);
db_unlock();
}
modified = true;
......@@ -668,12 +665,18 @@ updateInDirectory(struct directory *directory,
if (inodeFoundInParent(directory, st->st_ino, st->st_dev))
return;
subdir = make_subdir(directory, name);
db_lock();
subdir = directory_make_child(directory, name);
db_unlock();
assert(directory == subdir->parent);
ret = updateDirectory(subdir, st);
if (!ret)
if (!ret) {
db_lock();
delete_directory(subdir);
db_unlock();
}
} else {
g_debug("update: %s is not a directory, archive or music", name);
}
......@@ -827,34 +830,31 @@ updateDirectory(struct directory *directory, const struct stat *st)
}
static struct directory *
directory_make_child_checked(struct directory *parent, const char *path)
directory_make_child_checked(struct directory *parent, const char *name_utf8)
{
struct directory *directory;
char *base;
struct stat st;
struct song *conflicting;
directory = directory_get_child(parent, path);
db_lock();
directory = directory_get_child(parent, name_utf8);
db_unlock();
if (directory != NULL)
return directory;
base = g_path_get_basename(path);
if (stat_directory_child(parent, base, &st) < 0 ||
inodeFoundInParent(parent, st.st_ino, st.st_dev)) {
g_free(base);
if (stat_directory_child(parent, name_utf8, &st) < 0 ||
inodeFoundInParent(parent, st.st_ino, st.st_dev))
return NULL;
}
/* if we're adding directory paths, make sure to delete filenames
with potentially the same name */
conflicting = songvec_find(&parent->songs, base);
db_lock();
struct song *conflicting = directory_get_song(parent, name_utf8);
if (conflicting)
delete_song(parent, conflicting);
g_free(base);
directory = directory_new_child(parent, name_utf8);
db_unlock();
directory = directory_new_child(parent, path);
directory_set_stat(directory, &st);
return directory;
}
......@@ -864,17 +864,20 @@ addParentPathToDB(const char *utf8path)
{
struct directory *directory = db_get_root();
char *duplicated = g_strdup(utf8path);
char *slash = duplicated;
char *name_utf8 = duplicated, *slash;
while ((slash = strchr(slash, '/')) != NULL) {
while ((slash = strchr(name_utf8, '/')) != NULL) {
*slash = 0;
if (*name_utf8 == 0)
continue;
directory = directory_make_child_checked(directory,
duplicated);
if (directory == NULL || slash == NULL)
name_utf8);
if (directory == NULL)
break;
*slash++ = '/';
name_utf8 = slash + 1;
}
g_free(duplicated);
......
......@@ -25,7 +25,7 @@
#include <stdbool.h>
/**
* Checks whether the specified URI has a schema in the form
* Checks whether the specified URI has a scheme in the form
* "scheme://".
*/
G_GNUC_PURE
......
/*
* Copyright (C) 2003-2012 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*
* This code was imported from the Linux kernel.
*
*/
#ifndef _LINUX_LIST_H
#define _LINUX_LIST_H
#include <glib.h>
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) \
(&G_STRUCT_MEMBER(type, ptr, -G_STRUCT_OFFSET(type, member)))
/*
* These are non-NULL pointers that will result in page faults
* under normal circumstances, used to verify that nobody uses
* non-initialized list entries.
*/
#define LIST_POISON1 ((void *) 0x00100100)
#define LIST_POISON2 ((void *) 0x00200200)
/*
* Simple doubly linked list implementation.
*
* Some of the internal functions ("__xxx") are useful when
* manipulating whole lists rather than single entries, as
* sometimes we already know the next/prev entries and we can
* generate better code by using them directly rather than
* using the generic single-entry routines.
*/
struct list_head {
struct list_head *next, *prev;
};
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
/*
* Insert a new entry between two known consecutive entries.
*
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
#ifndef CONFIG_DEBUG_LIST
static inline void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next)
{
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}
#else
extern void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next);
#endif
/**
* list_add - add a new entry
* @new: new entry to be added
* @head: list head to add it after
*
* Insert a new entry after the specified head.
* This is good for implementing stacks.
*/
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
/**
* list_add_tail - add a new entry
* @new: new entry to be added
* @head: list head to add it before
*
* Insert a new entry before the specified head.
* This is useful for implementing queues.
*/
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
/*
* Delete a list entry by making the prev/next entries
* point to each other.
*
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next;
}
/**
* list_del - deletes entry from list.
* @entry: the element to delete from the list.
* Note: list_empty() on entry does not return true after this, the entry is
* in an undefined state.
*/
#ifndef CONFIG_DEBUG_LIST
static inline void __list_del_entry(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
}
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
#else
extern void __list_del_entry(struct list_head *entry);
extern void list_del(struct list_head *entry);
#endif
/**
* list_replace - replace old entry by new one
* @old : the element to be replaced
* @new : the new element to insert
*
* If @old was empty, it will be overwritten.
*/
static inline void list_replace(struct list_head *old,
struct list_head *new)
{
new->next = old->next;
new->next->prev = new;
new->prev = old->prev;
new->prev->next = new;
}
static inline void list_replace_init(struct list_head *old,
struct list_head *new)
{
list_replace(old, new);
INIT_LIST_HEAD(old);
}
/**
* list_del_init - deletes entry from list and reinitialize it.
* @entry: the element to delete from the list.
*/
static inline void list_del_init(struct list_head *entry)
{
__list_del_entry(entry);
INIT_LIST_HEAD(entry);
}
/**
* list_move - delete from one list and add as another's head
* @list: the entry to move
* @head: the head that will precede our entry
*/
static inline void list_move(struct list_head *list, struct list_head *head)
{
__list_del_entry(list);
list_add(list, head);
}
/**
* list_move_tail - delete from one list and add as another's tail
* @list: the entry to move
* @head: the head that will follow our entry
*/
static inline void list_move_tail(struct list_head *list,
struct list_head *head)
{
__list_del_entry(list);
list_add_tail(list, head);
}
/**
* list_is_last - tests whether @list is the last entry in list @head
* @list: the entry to test
* @head: the head of the list
*/
static inline int list_is_last(const struct list_head *list,
const struct list_head *head)
{
return list->next == head;
}
/**
* list_empty - tests whether a list is empty
* @head: the list to test.
*/
static inline int list_empty(const struct list_head *head)
{
return head->next == head;
}
/**
* list_empty_careful - tests whether a list is empty and not being modified
* @head: the list to test
*
* Description:
* tests whether a list is empty _and_ checks that no other CPU might be
* in the process of modifying either member (next or prev)
*
* NOTE: using list_empty_careful() without synchronization
* can only be safe if the only activity that can happen
* to the list entry is list_del_init(). Eg. it cannot be used
* if another CPU could re-list_add() it.
*/
static inline int list_empty_careful(const struct list_head *head)
{
struct list_head *next = head->next;
return (next == head) && (next == head->prev);
}
/**
* list_rotate_left - rotate the list to the left
* @head: the head of the list
*/
static inline void list_rotate_left(struct list_head *head)
{
struct list_head *first;
if (!list_empty(head)) {
first = head->next;
list_move_tail(first, head);
}
}
/**
* list_is_singular - tests whether a list has just one entry.
* @head: the list to test.
*/
static inline int list_is_singular(const struct list_head *head)
{
return !list_empty(head) && (head->next == head->prev);
}
static inline void __list_cut_position(struct list_head *list,
struct list_head *head, struct list_head *entry)
{
struct list_head *new_first = entry->next;
list->next = head->next;
list->next->prev = list;
list->prev = entry;
entry->next = list;
head->next = new_first;
new_first->prev = head;
}
/**
* list_cut_position - cut a list into two
* @list: a new list to add all removed entries
* @head: a list with entries
* @entry: an entry within head, could be the head itself
* and if so we won't cut the list
*
* This helper moves the initial part of @head, up to and
* including @entry, from @head to @list. You should
* pass on @entry an element you know is on @head. @list
* should be an empty list or a list you do not care about
* losing its data.
*
*/
static inline void list_cut_position(struct list_head *list,
struct list_head *head, struct list_head *entry)
{
if (list_empty(head))
return;
if (list_is_singular(head) &&
(head->next != entry && head != entry))
return;
if (entry == head)
INIT_LIST_HEAD(list);
else
__list_cut_position(list, head, entry);
}
static inline void __list_splice(const struct list_head *list,
struct list_head *prev,
struct list_head *next)
{
struct list_head *first = list->next;
struct list_head *last = list->prev;
first->prev = prev;
prev->next = first;
last->next = next;
next->prev = last;
}
/**
* list_splice - join two lists, this is designed for stacks
* @list: the new list to add.
* @head: the place to add it in the first list.
*/
static inline void list_splice(const struct list_head *list,
struct list_head *head)
{
if (!list_empty(list))
__list_splice(list, head, head->next);
}
/**
* list_splice_tail - join two lists, each list being a queue
* @list: the new list to add.
* @head: the place to add it in the first list.
*/
static inline void list_splice_tail(struct list_head *list,
struct list_head *head)
{
if (!list_empty(list))
__list_splice(list, head->prev, head);
}
/**
* list_splice_init - join two lists and reinitialise the emptied list.
* @list: the new list to add.
* @head: the place to add it in the first list.
*
* The list at @list is reinitialised
*/
static inline void list_splice_init(struct list_head *list,
struct list_head *head)
{
if (!list_empty(list)) {
__list_splice(list, head, head->next);
INIT_LIST_HEAD(list);
}
}
/**
* list_splice_tail_init - join two lists and reinitialise the emptied list
* @list: the new list to add.
* @head: the place to add it in the first list.
*
* Each of the lists is a queue.
* The list at @list is reinitialised
*/
static inline void list_splice_tail_init(struct list_head *list,
struct list_head *head)
{
if (!list_empty(list)) {
__list_splice(list, head->prev, head);
INIT_LIST_HEAD(list);
}
}
/**
* list_entry - get the struct for this entry
* @ptr: the &struct list_head pointer.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_struct within the struct.
*/
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
/**
* list_first_entry - get the first element from a list
* @ptr: the list head to take the element from.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_struct within the struct.
*
* Note, that list is expected to be not empty.
*/
#define list_first_entry(ptr, type, member) \
list_entry((ptr)->next, type, member)
/**
* list_for_each - iterate over a list
* @pos: the &struct list_head to use as a loop cursor.
* @head: the head for your list.
*/
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
/**
* __list_for_each - iterate over a list
* @pos: the &struct list_head to use as a loop cursor.
* @head: the head for your list.
*
* This variant doesn't differ from list_for_each() any more.
* We don't do prefetching in either case.
*/
#define __list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
/**
* list_for_each_prev - iterate over a list backwards
* @pos: the &struct list_head to use as a loop cursor.
* @head: the head for your list.
*/
#define list_for_each_prev(pos, head) \
for (pos = (head)->prev; pos != (head); pos = pos->prev)
/**
* list_for_each_safe - iterate over a list safe against removal of list entry
* @pos: the &struct list_head to use as a loop cursor.
* @n: another &struct list_head to use as temporary storage
* @head: the head for your list.
*/
#define list_for_each_safe(pos, n, head) \
for (pos = (head)->next, n = pos->next; pos != (head); \
pos = n, n = pos->next)
/**
* list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry
* @pos: the &struct list_head to use as a loop cursor.
* @n: another &struct list_head to use as temporary storage
* @head: the head for your list.
*/
#define list_for_each_prev_safe(pos, n, head) \
for (pos = (head)->prev, n = pos->prev; \
pos != (head); \
pos = n, n = pos->prev)
/**
* list_for_each_entry - iterate over list of given type
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*/
#define list_for_each_entry(pos, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
/**
* list_for_each_entry_reverse - iterate backwards over list of given type.
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*/
#define list_for_each_entry_reverse(pos, head, member) \
for (pos = list_entry((head)->prev, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.prev, typeof(*pos), member))
/**
* list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue()
* @pos: the type * to use as a start point
* @head: the head of the list
* @member: the name of the list_struct within the struct.
*
* Prepares a pos entry for use as a start point in list_for_each_entry_continue().
*/
#define list_prepare_entry(pos, head, member) \
((pos) ? : list_entry(head, typeof(*pos), member))
/**
* list_for_each_entry_continue - continue iteration over list of given type
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Continue to iterate over list of given type, continuing after
* the current position.
*/
#define list_for_each_entry_continue(pos, head, member) \
for (pos = list_entry(pos->member.next, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
/**
* list_for_each_entry_continue_reverse - iterate backwards from the given point
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Start to iterate over list of given type backwards, continuing after
* the current position.
*/
#define list_for_each_entry_continue_reverse(pos, head, member) \
for (pos = list_entry(pos->member.prev, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.prev, typeof(*pos), member))
/**
* list_for_each_entry_from - iterate over list of given type from the current point
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Iterate over list of given type, continuing from current position.
*/
#define list_for_each_entry_from(pos, head, member) \
for (; &pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
/**
* list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*/
#define list_for_each_entry_safe(pos, n, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member), \
n = list_entry(pos->member.next, typeof(*pos), member); \
&pos->member != (head); \
pos = n, n = list_entry(n->member.next, typeof(*n), member))
/**
* list_for_each_entry_safe_continue - continue list iteration safe against removal
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Iterate over list of given type, continuing after current point,
* safe against removal of list entry.
*/
#define list_for_each_entry_safe_continue(pos, n, head, member) \
for (pos = list_entry(pos->member.next, typeof(*pos), member), \
n = list_entry(pos->member.next, typeof(*pos), member); \
&pos->member != (head); \
pos = n, n = list_entry(n->member.next, typeof(*n), member))
/**
* list_for_each_entry_safe_from - iterate over list from current point safe against removal
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Iterate over list of given type from current point, safe against
* removal of list entry.
*/
#define list_for_each_entry_safe_from(pos, n, head, member) \
for (n = list_entry(pos->member.next, typeof(*pos), member); \
&pos->member != (head); \
pos = n, n = list_entry(n->member.next, typeof(*n), member))
/**
* list_for_each_entry_safe_reverse - iterate backwards over list safe against removal
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Iterate backwards over list of given type, safe against removal
* of list entry.
*/
#define list_for_each_entry_safe_reverse(pos, n, head, member) \
for (pos = list_entry((head)->prev, typeof(*pos), member), \
n = list_entry(pos->member.prev, typeof(*pos), member); \
&pos->member != (head); \
pos = n, n = list_entry(n->member.prev, typeof(*n), member))
/**
* list_safe_reset_next - reset a stale list_for_each_entry_safe loop
* @pos: the loop cursor used in the list_for_each_entry_safe loop
* @n: temporary storage used in list_for_each_entry_safe
* @member: the name of the list_struct within the struct.
*
* list_safe_reset_next is not safe to use in general if the list may be
* modified concurrently (eg. the lock is dropped in the loop body). An
* exception to this is if the cursor element (pos) is pinned in the list,
* and list_safe_reset_next is called after re-taking the lock and before
* completing the current iteration of the loop body.
*/
#define list_safe_reset_next(pos, n, member) \
n = list_entry(pos->member.next, typeof(*pos), member)
#endif
/*
* Copyright (C) 2003-2012 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*
* This code was imported from the Linux kernel.
*
*/
#include "list_sort.h"
#include "list.h"
#include <glib.h>
#include <string.h>
#define unlikely G_UNLIKELY
#define ARRAY_SIZE G_N_ELEMENTS
#define MAX_LIST_LENGTH_BITS 20
/*
* Returns a list organized in an intermediate format suited
* to chaining of merge() calls: null-terminated, no reserved or
* sentinel head node, "prev" links not maintained.
*/
static struct list_head *merge(void *priv,
int (*cmp)(void *priv, struct list_head *a,
struct list_head *b),
struct list_head *a, struct list_head *b)
{
struct list_head head, *tail = &head;
while (a && b) {
/* if equal, take 'a' -- important for sort stability */
if ((*cmp)(priv, a, b) <= 0) {
tail->next = a;
a = a->next;
} else {
tail->next = b;
b = b->next;
}
tail = tail->next;
}
tail->next = a?a:b;
return head.next;
}
/*
* Combine final list merge with restoration of standard doubly-linked
* list structure. This approach duplicates code from merge(), but
* runs faster than the tidier alternatives of either a separate final
* prev-link restoration pass, or maintaining the prev links
* throughout.
*/
static void merge_and_restore_back_links(void *priv,
int (*cmp)(void *priv, struct list_head *a,
struct list_head *b),
struct list_head *head,
struct list_head *a, struct list_head *b)
{
struct list_head *tail = head;
while (a && b) {
/* if equal, take 'a' -- important for sort stability */
if ((*cmp)(priv, a, b) <= 0) {
tail->next = a;
a->prev = tail;
a = a->next;
} else {
tail->next = b;
b->prev = tail;
b = b->next;
}
tail = tail->next;
}
tail->next = a ? a : b;
do {
/*
* In worst cases this loop may run many iterations.
* Continue callbacks to the client even though no
* element comparison is needed, so the client's cmp()
* routine can invoke cond_resched() periodically.
*/
(*cmp)(priv, tail->next, tail->next);
tail->next->prev = tail;
tail = tail->next;
} while (tail->next);
tail->next = head;
head->prev = tail;
}
/**
* list_sort - sort a list
* @priv: private data, opaque to list_sort(), passed to @cmp
* @head: the list to sort
* @cmp: the elements comparison function
*
* This function implements "merge sort", which has O(nlog(n))
* complexity.
*
* The comparison function @cmp must return a negative value if @a
* should sort before @b, and a positive value if @a should sort after
* @b. If @a and @b are equivalent, and their original relative
* ordering is to be preserved, @cmp must return 0.
*/
void list_sort(void *priv, struct list_head *head,
int (*cmp)(void *priv, struct list_head *a,
struct list_head *b))
{
struct list_head *part[MAX_LIST_LENGTH_BITS+1]; /* sorted partial lists
-- last slot is a sentinel */
int lev; /* index into part[] */
int max_lev = 0;
struct list_head *list;
if (list_empty(head))
return;
memset(part, 0, sizeof(part));
head->prev->next = NULL;
list = head->next;
while (list) {
struct list_head *cur = list;
list = list->next;
cur->next = NULL;
for (lev = 0; part[lev]; lev++) {
cur = merge(priv, cmp, part[lev], cur);
part[lev] = NULL;
}
if (lev > max_lev) {
max_lev = lev;
}
part[lev] = cur;
}
for (lev = 0; lev < max_lev; lev++)
if (part[lev])
list = merge(priv, cmp, part[lev], list);
merge_and_restore_back_links(priv, cmp, head, part[max_lev], list);
}
/*
* Copyright (C) 2003-2012 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*
* This code was imported from the Linux kernel.
*
*/
#ifndef _LINUX_LIST_SORT_H
#define _LINUX_LIST_SORT_H
struct list_head;
void list_sort(void *priv, struct list_head *head,
int (*cmp)(void *priv, struct list_head *a,
struct list_head *b));
#endif
......@@ -34,7 +34,11 @@
#include <pwd.h>
#endif
#ifdef HAVE_IPV6
#if HAVE_IPV6 && WIN32
#include <winsock2.h>
#endif
#if HAVE_IPV6 && ! WIN32
#include <sys/socket.h>
#endif
......
......@@ -115,8 +115,8 @@ filter_plugin_by_name(G_GNUC_UNUSED const char *name)
}
bool
pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED int length,
G_GNUC_UNUSED const struct audio_format *format,
pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED size_t length,
G_GNUC_UNUSED enum sample_format format,
G_GNUC_UNUSED int volume)
{
assert(false);
......
......@@ -52,8 +52,8 @@ idle_add(G_GNUC_UNUSED unsigned flags)
* No-op dummy.
*/
bool
pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED int length,
G_GNUC_UNUSED const struct audio_format *format,
pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED size_t length,
G_GNUC_UNUSED enum sample_format format,
G_GNUC_UNUSED int volume)
{
return true;
......
......@@ -21,6 +21,7 @@
#include "io_thread.h"
#include "decoder_list.h"
#include "decoder_api.h"
#include "tag_pool.h"
#include "input_init.h"
#include "input_stream.h"
#include "audio_format.h"
......@@ -56,8 +57,8 @@ idle_add(G_GNUC_UNUSED unsigned flags)
* No-op dummy.
*/
bool
pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED int length,
G_GNUC_UNUSED const struct audio_format *format,
pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED size_t length,
G_GNUC_UNUSED enum sample_format format,
G_GNUC_UNUSED int volume)
{
return true;
......@@ -180,6 +181,7 @@ int main(int argc, char **argv)
decoder_name = argv[1];
decoder.uri = argv[2];
g_thread_init(NULL);
g_log_set_default_handler(my_log_func, NULL);
io_thread_init();
......@@ -189,6 +191,8 @@ int main(int argc, char **argv)
return EXIT_FAILURE;
}
tag_pool_init();
if (!input_stream_global_init(&error)) {
g_warning("%s", error->message);
g_error_free(error);
......@@ -244,5 +248,7 @@ int main(int argc, char **argv)
return 1;
}
tag_pool_deinit();
return 0;
}
......@@ -30,7 +30,6 @@
#include <unistd.h>
#ifdef WIN32
#define WINVER 0x0501
#include <ws2tcpip.h>
#include <winsock.h>
#else
......
......@@ -124,7 +124,15 @@ run_output(struct audio_output *ao, struct audio_format *audio_format)
/* open the audio output */
GError *error = NULL;
if (!ao_plugin_enable(ao, &error)) {
g_printerr("Failed to enable audio output: %s\n",
error->message);
g_error_free(error);
return false;
}
if (!ao_plugin_open(ao, audio_format, &error)) {
ao_plugin_disable(ao);
g_printerr("Failed to open audio output: %s\n",
error->message);
g_error_free(error);
......@@ -158,6 +166,7 @@ run_output(struct audio_output *ao, struct audio_format *audio_format)
&error);
if (consumed == 0) {
ao_plugin_close(ao);
ao_plugin_disable(ao);
g_printerr("Failed to play: %s\n",
error->message);
g_error_free(error);
......@@ -173,6 +182,7 @@ run_output(struct audio_output *ao, struct audio_format *audio_format)
}
ao_plugin_close(ao);
ao_plugin_disable(ao);
return true;
}
......
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "resolver.h"
#ifdef WIN32
#include <ws2tcpip.h>
#include <winsock.h>
#else
#include <sys/socket.h>
#include <netdb.h>
#endif
#include <stdlib.h>
int main(int argc, char **argv)
{
if (argc != 2) {
g_printerr("Usage: run_resolver HOST\n");
return EXIT_FAILURE;
}
GError *error = NULL;
struct addrinfo *ai =
resolve_host_port(argv[1], 80, AI_PASSIVE, SOCK_STREAM,
&error);
if (ai == NULL) {
g_printerr("%s\n", error->message);
g_error_free(error);
return EXIT_FAILURE;
}
for (const struct addrinfo *i = ai; i != NULL; i = i->ai_next) {
char *p = sockaddr_to_string(i->ai_addr, i->ai_addrlen,
&error);
if (p == NULL) {
freeaddrinfo(ai);
g_printerr("%s\n", error->message);
g_error_free(error);
return EXIT_FAILURE;
}
g_print("%s\n", p);
g_free(p);
}
freeaddrinfo(ai);
return EXIT_SUCCESS;
}
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "resolver.h"
#include "io_thread.h"
#include "tcp_connect.h"
#include "fd_util.h"
#include <assert.h>
#include <stdlib.h>
#ifdef WIN32
#include <ws2tcpip.h>
#include <winsock.h>
#else
#include <sys/socket.h>
#include <netdb.h>
#endif
static struct tcp_connect *handle;
static GMutex *mutex;
static GCond *cond;
static bool done, success;
static void
my_tcp_connect_success(int fd, G_GNUC_UNUSED void *ctx)
{
assert(!done);
assert(!success);
close_socket(fd);
g_print("success\n");
g_mutex_lock(mutex);
done = success = true;
g_cond_signal(cond);
g_mutex_unlock(mutex);
}
static void
my_tcp_connect_error(GError *error, G_GNUC_UNUSED void *ctx)
{
assert(!done);
assert(!success);
g_printerr("error: %s\n", error->message);
g_error_free(error);
g_mutex_lock(mutex);
done = true;
g_cond_signal(cond);
g_mutex_unlock(mutex);
}
static void
my_tcp_connect_timeout(G_GNUC_UNUSED void *ctx)
{
assert(!done);
assert(!success);
g_printerr("timeout\n");
g_mutex_lock(mutex);
done = true;
g_cond_signal(cond);
g_mutex_unlock(mutex);
}
static void
my_tcp_connect_canceled(G_GNUC_UNUSED void *ctx)
{
assert(!done);
assert(!success);
g_printerr("canceled\n");
g_mutex_lock(mutex);
done = true;
g_cond_signal(cond);
g_mutex_unlock(mutex);
}
static const struct tcp_connect_handler my_tcp_connect_handler = {
.success = my_tcp_connect_success,
.error = my_tcp_connect_error,
.timeout = my_tcp_connect_timeout,
.canceled = my_tcp_connect_canceled,
};
int main(int argc, char **argv)
{
if (argc != 2) {
g_printerr("Usage: run_tcp_connect IP:PORT\n");
return 1;
}
GError *error = NULL;
struct addrinfo *ai = resolve_host_port(argv[1], 80, 0, SOCK_STREAM,
&error);
if (ai == NULL) {
g_printerr("%s\n", error->message);
g_error_free(error);
return EXIT_FAILURE;
}
/* initialize GLib */
g_thread_init(NULL);
/* initialize MPD */
io_thread_init();
if (!io_thread_start(&error)) {
freeaddrinfo(ai);
g_printerr("%s", error->message);
g_error_free(error);
return EXIT_FAILURE;
}
/* open the connection */
mutex = g_mutex_new();
cond = g_cond_new();
tcp_connect_address(ai->ai_addr, ai->ai_addrlen, 5000,
&my_tcp_connect_handler, NULL,
&handle);
freeaddrinfo(ai);
if (handle != NULL) {
g_mutex_lock(mutex);
while (!done)
g_cond_wait(cond, mutex);
g_mutex_unlock(mutex);
tcp_connect_free(handle);
}
g_cond_free(cond);
g_mutex_free(mutex);
/* deinitialize everything */
io_thread_deinit();
return EXIT_SUCCESS;
}
......@@ -59,7 +59,7 @@ int main(int argc, char **argv)
audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, 2);
while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) {
if (!pcm_volume(buffer, nbytes, &audio_format,
if (!pcm_volume(buffer, nbytes, audio_format.format,
PCM_VOLUME_1 / 2)) {
g_printerr("pcm_volume() has failed\n");
return 2;
......
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*
* Compatibility header for GLib before 2.16.
*/
#ifndef MPD_TEST_GLIB_COMPAT_H
#define MPD_TEST_GLIB_COMPAT_H
#include <glib.h>
#if !GLIB_CHECK_VERSION(2,16,0)
#define g_assert_cmpint(n1, cmp, n2) g_assert((n1) cmp (n2))
static void (*test_functions[256])(void);
static unsigned num_test_functions;
static inline void
g_test_init(G_GNUC_UNUSED int *argc, G_GNUC_UNUSED char ***argv, ...)
{
}
static inline void
g_test_add_func(G_GNUC_UNUSED const char *testpath, void (test_funcvoid)(void))
{
test_functions[num_test_functions++] = test_funcvoid;
}
static inline int
g_test_run(void)
{
for (unsigned i = 0; i < num_test_functions; ++i)
test_functions[i]();
return 0;
}
#endif /* !2.16 */
#endif
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_TEST_PCM_ALL_H
#define MPD_TEST_PCM_ALL_H
void
test_pcm_dither_24(void);
void
test_pcm_dither_32(void);
void
test_pcm_pack_24(void);
void
test_pcm_unpack_24(void);
void
test_pcm_channels_16(void);
void
test_pcm_channels_32(void);
void
test_pcm_byteswap_16(void);
void
test_pcm_byteswap_32(void);
#endif
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "test_pcm_all.h"
#include "pcm_byteswap.h"
#include "pcm_buffer.h"
#include "test_glib_compat.h"
#include <glib.h>
void
test_pcm_byteswap_16(void)
{
enum { N = 256 };
int16_t src[N];
for (unsigned i = 0; i < G_N_ELEMENTS(src); ++i)
src[i] = g_random_int();
struct pcm_buffer buffer;
pcm_buffer_init(&buffer);
const int16_t *dest = pcm_byteswap_16(&buffer, src, sizeof(src));
g_assert(dest != NULL);
for (unsigned i = 0; i < N; ++i)
g_assert_cmpint(dest[i], ==,
(int16_t)GUINT16_SWAP_LE_BE(src[i]));
}
void
test_pcm_byteswap_32(void)
{
enum { N = 256 };
int32_t src[N];
for (unsigned i = 0; i < G_N_ELEMENTS(src); ++i)
src[i] = g_random_int();
struct pcm_buffer buffer;
pcm_buffer_init(&buffer);
const int32_t *dest = pcm_byteswap_32(&buffer, src, sizeof(src));
g_assert(dest != NULL);
for (unsigned i = 0; i < N; ++i)
g_assert_cmpint(dest[i], ==,
(int32_t)GUINT32_SWAP_LE_BE(src[i]));
}
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "test_pcm_all.h"
#include "test_glib_compat.h"
#include "pcm_channels.h"
#include "pcm_buffer.h"
#include <glib.h>
void
test_pcm_channels_16(void)
{
enum { N = 256 };
int16_t src[N * 2];
for (unsigned i = 0; i < G_N_ELEMENTS(src); ++i)
src[i] = g_random_int();
struct pcm_buffer buffer;
pcm_buffer_init(&buffer);
/* stereo to mono */
size_t dest_size;
const int16_t *dest =
pcm_convert_channels_16(&buffer, 1, 2, src, sizeof(src),
&dest_size);
g_assert(dest != NULL);
g_assert_cmpint(dest_size, ==, sizeof(src) / 2);
for (unsigned i = 0; i < N; ++i)
g_assert_cmpint(dest[i], ==,
(src[i * 2] + src[i * 2 + 1]) / 2);
/* mono to stereo */
dest = pcm_convert_channels_16(&buffer, 2, 1, src, sizeof(src),
&dest_size);
g_assert(dest != NULL);
g_assert_cmpint(dest_size, ==, sizeof(src) * 2);
for (unsigned i = 0; i < N; ++i) {
g_assert_cmpint(dest[i * 2], ==, src[i]);
g_assert_cmpint(dest[i * 2 + 1], ==, src[i]);
}
pcm_buffer_deinit(&buffer);
}
void
test_pcm_channels_32(void)
{
enum { N = 256 };
int32_t src[N * 2];
for (unsigned i = 0; i < G_N_ELEMENTS(src); ++i)
src[i] = g_random_int();
struct pcm_buffer buffer;
pcm_buffer_init(&buffer);
/* stereo to mono */
size_t dest_size;
const int32_t *dest =
pcm_convert_channels_32(&buffer, 1, 2, src, sizeof(src),
&dest_size);
g_assert(dest != NULL);
g_assert_cmpint(dest_size, ==, sizeof(src) / 2);
for (unsigned i = 0; i < N; ++i)
g_assert_cmpint(dest[i], ==,
((int64_t)src[i * 2] + (int64_t)src[i * 2 + 1]) / 2);
/* mono to stereo */
dest = pcm_convert_channels_32(&buffer, 2, 1, src, sizeof(src),
&dest_size);
g_assert(dest != NULL);
g_assert_cmpint(dest_size, ==, sizeof(src) * 2);
for (unsigned i = 0; i < N; ++i) {
g_assert_cmpint(dest[i * 2], ==, src[i]);
g_assert_cmpint(dest[i * 2 + 1], ==, src[i]);
}
pcm_buffer_deinit(&buffer);
}
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test_pcm_all.h"
#include "test_glib_compat.h"
#include "pcm_dither.h"
#include <glib.h>
/**
* Generate a random 24 bit PCM sample.
*/
static int32_t
random24(void)
{
int32_t x = g_random_int() & 0xffffff;
if (x & 0x800000)
x |= 0xff000000;
return x;
}
void
test_pcm_dither_24(void)
{
struct pcm_dither dither;
pcm_dither_24_init(&dither);
enum { N = 256 };
int32_t src[N];
for (unsigned i = 0; i < N; ++i)
src[i] = random24();
int16_t dest[N];
pcm_dither_24_to_16(&dither, dest, src, src + N);
for (unsigned i = 0; i < N; ++i) {
g_assert_cmpint(dest[i], >=, (src[i] >> 8) - 8);
g_assert_cmpint(dest[i], <, (src[i] >> 8) + 8);
}
}
void
test_pcm_dither_32(void)
{
struct pcm_dither dither;
pcm_dither_24_init(&dither);
enum { N = 256 };
int32_t src[N];
for (unsigned i = 0; i < N; ++i)
src[i] = g_random_int();
int16_t dest[N];
pcm_dither_32_to_16(&dither, dest, src, src + N);
for (unsigned i = 0; i < N; ++i) {
g_assert_cmpint(dest[i], >=, (src[i] >> 16) - 8);
g_assert_cmpint(dest[i], <, (src[i] >> 16) + 8);
}
}
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test_pcm_all.h"
#include "test_glib_compat.h"
#include <glib.h>
int
main(int argc, char **argv)
{
g_test_init (&argc, &argv, NULL);
g_test_add_func("/pcm/dither/24", test_pcm_dither_24);
g_test_add_func("/pcm/dither/32", test_pcm_dither_32);
g_test_add_func("/pcm/pack/pack24", test_pcm_pack_24);
g_test_add_func("/pcm/pack/unpack24", test_pcm_unpack_24);
g_test_add_func("/pcm/channels/16", test_pcm_channels_16);
g_test_add_func("/pcm/channels/32", test_pcm_channels_32);
g_test_add_func("/pcm/byteswap/16", test_pcm_byteswap_16);
g_test_add_func("/pcm/byteswap/32", test_pcm_byteswap_32);
g_test_run();
}
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test_pcm_all.h"
#include "pcm_pack.h"
#include "test_glib_compat.h"
#include <glib.h>
/**
* Generate a random 24 bit PCM sample.
*/
static int32_t
random24(void)
{
int32_t x = g_random_int() & 0xffffff;
if (x & 0x800000)
x |= 0xff000000;
return x;
}
void
test_pcm_pack_24(void)
{
enum { N = 256 };
int32_t src[N * 3];
for (unsigned i = 0; i < N; ++i)
src[i] = random24();
uint8_t dest[N * 3];
pcm_pack_24(dest, src, src + N, false);
for (unsigned i = 0; i < N; ++i) {
int32_t d;
if (G_BYTE_ORDER == G_BIG_ENDIAN)
d = (dest[i * 3] << 16) | (dest[i * 3 + 1] << 8)
| dest[i * 3 + 2];
else
d = (dest[i * 3 + 2] << 16) | (dest[i * 3 + 1] << 8)
| dest[i * 3];
if (d & 0x800000)
d |= 0xff000000;
g_assert_cmpint(d, ==, src[i]);
}
}
void
test_pcm_unpack_24(void)
{
enum { N = 256 };
uint8_t src[N * 3];
for (unsigned i = 0; i < G_N_ELEMENTS(src); ++i)
src[i] = g_random_int_range(0, 256);
int32_t dest[N];
pcm_unpack_24(dest, src, src + G_N_ELEMENTS(src), false);
for (unsigned i = 0; i < N; ++i) {
int32_t s;
if (G_BYTE_ORDER == G_BIG_ENDIAN)
s = (src[i * 3] << 16) | (src[i * 3 + 1] << 8)
| src[i * 3 + 2];
else
s = (src[i * 3 + 2] << 16) | (src[i * 3 + 1] << 8)
| src[i * 3];
if (s & 0x800000)
s |= 0xff000000;
g_assert_cmpint(s, ==, dest[i]);
}
}
......@@ -25,53 +25,8 @@
{
g_main_context_default
Memcheck:Leak
fun:malloc
fun:g_malloc
fun:g_slice_alloc
fun:g_slist_append
fun:g_main_context_new
fun:g_main_context_default
}
{
g_main_context_default
Memcheck:Leak
fun:malloc
fun:g_malloc
fun:g_slice_alloc
fun:g_ptr_array_sized_new
fun:g_main_context_new
fun:g_main_context_default
}
{
g_main_context_default
Memcheck:Leak
fun:calloc
fun:g_malloc0
fun:g_main_context_new
fun:g_main_context_default
}
{
g_main_context_default
Memcheck:Leak
fun:malloc
fun:g_malloc
fun:g_slice_alloc
fun:g_main_context_add_poll_unlocked
fun:g_main_context_new
fun:g_main_context_default
}
{
g_main_context_default
Memcheck:Leak
fun:malloc
fun:g_malloc
fun:g_slice_alloc
fun:g_slist_prepend
fun:g_main_context_new
fun:?alloc
...
fun:g_main_context_default
}
......@@ -238,60 +193,37 @@
}
{
<insert a suppression name here>
Memcheck:Leak
fun:*alloc
fun:_dl_open
}
{
dlopen
<insert_a_suppression_name_here>
Memcheck:Leak
fun:calloc
fun:_dlerror_run
fun:?alloc
...
fun:do_dlopen
}
# is that a leak in libdbus?
{
<insert a suppression name here>
<insert_a_suppression_name_here>
Memcheck:Leak
fun:malloc
obj:/usr/lib/libdbus-1.so.3.4.0
obj:/usr/lib/libdbus-1.so.3.4.0
fun:avahi_client_new
fun:?alloc
...
fun:dlopen*
}
{
<insert a suppression name here>
<insert_a_suppression_name_here>
Memcheck:Leak
fun:malloc
obj:/usr/lib/libdbus-1.so.3.4.0
fun:dbus_message_unref
obj:/usr/lib/libdbus-1.so.3.4.0
fun:dbus_connection_send_with_reply_and_block
fun:dbus_bus_register
obj:/usr/lib/libdbus-1.so.3.4.0
fun:avahi_client_new
...
fun:dlclose
}
{
<insert a suppression name here>
Memcheck:Leak
fun:malloc
obj:/usr/lib/libdbus-1.so.3.4.0
fun:dbus_message_new_method_call
fun:dbus_bus_register
obj:/usr/lib/libdbus-1.so.3.4.0
fun:avahi_client_new
}
# is that a leak in libdbus?
{
<insert a suppression name here>
Memcheck:Leak
fun:malloc
obj:/usr/lib/libdbus-1.so.3.4.0
obj:/usr/lib/libdbus-1.so.3.4.0
fun:?alloc
...
obj:*/libdbus-*.so.*
fun:avahi_client_new
}
......@@ -504,3 +436,18 @@
fun:nss_parse_service_list
fun:__nss_database_lookup
}
{
<insert_a_suppression_name_here>
Memcheck:Leak
fun:?alloc
...
fun:xmlInitParser
}
{
<insert_a_suppression_name_here>
Memcheck:Leak
fun:?alloc
fun:snd1_dlobj_cache_get
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment