[Pkg-mpd-commits] [pkg-mpd] 01/05: New upstream version 0.20.17

Florian Schlichting fsfs at moszumanska.debian.org
Mon Feb 12 19:55:58 UTC 2018


This is an automated email from the git hooks/post-receive script.

fsfs pushed a commit to branch master
in repository pkg-mpd.

commit f9b88a924a397a76f4561acbec9b456139a7fa51
Author: Florian Schlichting <fsfs at debian.org>
Date:   Mon Feb 12 20:15:39 2018 +0100

    New upstream version 0.20.17
---
 Makefile.am                                        |  85 ++++---
 Makefile.in                                        | 121 +++++----
 NEWS                                               |  30 +++
 aclocal.m4                                         |  44 ++--
 android/AndroidManifest.xml                        |   6 +-
 android/build.py                                   |  26 +-
 configure                                          |  22 +-
 configure.ac                                       |   4 +-
 doc/doxygen.conf                                   |   2 +-
 doc/user.xml                                       |  21 +-
 python/build/autotools.py                          |  38 ++-
 python/build/libs.py                               | 274 +++++++++++++++++++--
 python/build/makeproject.py                        |  28 +++
 python/build/project.py                            |  26 +-
 python/build/quilt.py                              |   9 +
 src/IOThread.cxx                                   |  21 +-
 src/IOThread.hxx                                   |   5 +
 src/MusicPipe.hxx                                  |   2 +
 src/SongFilter.cxx                                 |  34 ++-
 src/SongFilter.hxx                                 |  16 +-
 src/SongSave.cxx                                   |   3 +-
 src/archive/plugins/Iso9660ArchivePlugin.cxx       |   5 +
 src/db/plugins/simple/Mount.cxx                    |  19 +-
 src/db/update/Queue.hxx                            |   4 +
 src/db/update/Service.cxx                          |  15 +-
 src/db/update/Service.hxx                          |   1 -
 src/decoder/DecoderControl.cxx                     |   3 +-
 src/decoder/DecoderControl.hxx                     |   3 +
 src/decoder/DecoderThread.cxx                      |  38 ++-
 src/event/Loop.cxx                                 |   6 +-
 src/event/Loop.hxx                                 |   3 +-
 src/filter/Observer.cxx                            |   4 +
 src/filter/plugins/ConvertFilterPlugin.cxx         |  19 +-
 src/input/ThreadInputStream.cxx                    |  11 +-
 src/input/ThreadInputStream.hxx                    |   2 +-
 src/input/plugins/FileInputPlugin.cxx              |   3 +
 src/lib/icu/Compare.hxx                            |  12 +
 src/lib/upnp/Init.cxx                              |   6 +-
 src/mixer/plugins/volume_mapping.c                 |   7 +
 src/neighbor/plugins/SmbclientNeighborPlugin.cxx   |  17 +-
 src/output/Init.cxx                                |   3 +-
 src/output/Internal.hxx                            |   1 -
 src/output/OutputThread.cxx                        |  11 +-
 src/output/plugins/HaikuOutputPlugin.cxx           |   1 -
 src/output/plugins/PulseOutputPlugin.cxx           |   4 +
 src/pcm/Order.cxx                                  |   2 +-
 src/pcm/SoxrResampler.cxx                          |   8 +
 src/pcm/SoxrResampler.hxx                          |   1 +
 src/player/Control.cxx                             |   1 +
 src/player/Control.hxx                             |   3 +
 src/player/Thread.cxx                              |  74 +++---
 src/playlist/plugins/SoundCloudPlaylistPlugin.cxx  |   2 +-
 src/protocol/ArgParser.cxx                         |   3 +-
 src/queue/PlaylistControl.cxx                      |   6 +-
 src/queue/PlaylistState.cxx                        |   7 +-
 src/storage/StorageState.cxx                       |  20 +-
 src/tag/ReplayGain.cxx                             |   9 +-
 src/thread/Id.hxx                                  |  16 +-
 src/thread/Thread.cxx                              |  50 ++--
 src/thread/Thread.hxx                              |  43 ++--
 src/thread/Util.cxx                                |  27 +-
 src/util/NumberParser.hxx                          |   5 +
 src/util/RefCount.hxx                              |   5 -
 .../NullMixerListener.hxx                          |  32 +--
 test/read_mixer.cxx                                |  10 +-
 test/run_output.cxx                                |  13 +-
 win32/build.py                                     |   6 +-
 67 files changed, 944 insertions(+), 414 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 396bc0c..409f784 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -61,8 +61,8 @@ src_mpd_LDADD = \
 	libnet.a \
 	$(FS_LIBS) \
 	libsystem.a \
-	libutil.a \
 	$(ICU_LDADD) \
+	libutil.a \
 	$(SYSTEMD_DAEMON_LIBS)
 
 src_mpd_SOURCES = \
@@ -285,30 +285,62 @@ libmain_a_CPPFLAGS = $(AM_CPPFLAGS) -Iandroid/build/include
 
 src_mpd_LDADD += libandroid.a libjava.a
 
-all-local: android/build/bin/$(APK_NAME)-debug.apk
+all-local: android/build/$(APK_NAME)-debug.apk
 clean-local:
 	rm -rf android/build
 
 libmpd.so: $(filter %.a,$(src_mpd_LDADD)) libmain.a
 	$(AM_V_CXXLD)$(CXXLD) -shared -Wl,--no-undefined,-shared,-Bsymbolic -llog -lz -o $@ $(AM_CXXFLAGS) $(CXXFLAGS) $(LDFLAGS) src/libmain_a-Main.o $(src_mpd_LDADD) $(LIBS)
 
-android/build/build.xml: android/AndroidManifest.xml
-	rm -rf android/build
-	mkdir -p android/build/include android/build/res android/build/src/org
-	ln -s $(abs_srcdir)/android/AndroidManifest.xml $(abs_srcdir)/android/custom_rules.xml android/build
-	ln -s $(abs_srcdir)/android/src android/build/src/org/musicpd
-	ln -s $(abs_srcdir)/android/res/values $(abs_srcdir)/android/res/layout android/build/res
-	$(ANDROID_SDK)/tools/android update project --path android/build --target android-17 --name $(APK_NAME)
+ANDROID_SDK_BUILD_TOOLS_VERSION = 27.0.0
+ANDROID_SDK_PLATFORM = android-17
+
+ANDROID_BUILD_TOOLS_DIR = $(ANDROID_SDK)/build-tools/$(ANDROID_SDK_BUILD_TOOLS_VERSION)
+ANDROID_SDK_PLATFORM_DIR = $(ANDROID_SDK)/platforms/$(ANDROID_SDK_PLATFORM)
+
+JAVAC = javac
+AAPT = $(ANDROID_BUILD_TOOLS_DIR)/aapt
+DX = $(ANDROID_BUILD_TOOLS_DIR)/dx
+ZIPALIGN = $(ANDROID_BUILD_TOOLS_DIR)/zipalign
+
+ANDROID_XML_RES := $(wildcard $(srcdir)/android/res/*/*.xml)
+ANDROID_XML_RES_COPIES := $(patsubst $(srcdir)/android/%,android/build/%,$(ANDROID_XML_RES))
+
+JAVA_SOURCE_NAMES = Bridge.java Loader.java Main.java
+JAVA_SOURCE_PATHS = $(addprefix $(srcdir)/android/src/,$(JAVA_SOURCE_NAMES))
 
-android/build/bin/classes/org/musicpd/Bridge.class: android/src/Bridge.java android/build/build.xml android/build/res/drawable/icon.png
-	cd android/build && ant compile-jni-classes
+JAVA_CLASSFILES_DIR = android/build/classes
 
-android/build/include/org_musicpd_Bridge.h: android/build/bin/classes/org/musicpd/Bridge.class
-	javah -classpath $(ANDROID_SDK)/platforms/android-17/android.jar:android/build/bin/classes -d $(@D) org.musicpd.Bridge
+$(ANDROID_XML_RES_COPIES): $(ANDROID_XML_RES)
+	@$(MKDIR_P) $(dir $@)
+	cp $(patsubst android/build/%,$(srcdir)/android/%,$@) $@
+
+android/build/resources.apk: $(ANDROID_XML_RES_COPIES) android/build/res/drawable/icon.png
+	@$(MKDIR_P) android/build/gen
+	$(AAPT) package -f -m --auto-add-overlay \
+		--custom-package org.musicpd \
+		-M $(srcdir)/android/AndroidManifest.xml \
+		-S android/build/res \
+		-J android/build/gen \
+		-I $(ANDROID_SDK_PLATFORM_DIR)/android.jar \
+		-F android/build/resources.apk
+
+# R.java is generated by aapt, when resources.apk is generated
+android/build/gen/org/musicpd/R.java: android/build/resources.apk
+
+android/build/classes.dex: $(JAVA_SOURCE_PATHS) android/build/gen/org/musicpd/R.java
+	@$(MKDIR_P) $(JAVA_CLASSFILES_DIR)
+	$(JAVAC) -source 1.5 -target 1.5 -Xlint:-options \
+		-cp $(ANDROID_SDK_PLATFORM_DIR)/android.jar:$(JAVA_CLASSFILES_DIR) \
+		-d $(JAVA_CLASSFILES_DIR) $^
+	$(DX) --dex --output $@ $(JAVA_CLASSFILES_DIR)
+
+android/build/include/org_musicpd_Bridge.h: android/build/classes.dex
+	javah -classpath $(ANDROID_SDK_PLATFORM_DIR)/android.jar:$(JAVA_CLASSFILES_DIR) -d $(@D) org.musicpd.Bridge
 
 BUILT_SOURCES = android/build/include/org_musicpd_Bridge.h
 
-android/build/libs/armeabi-v7a/libmpd.so: libmpd.so android/build/build.xml
+android/build/lib/armeabi-v7a/libmpd.so: libmpd.so
 	mkdir -p $(@D)
 	rm -f $@
 	$(STRIP) -o $@ $<
@@ -317,24 +349,19 @@ android/build/res/drawable/icon.png: mpd.svg
 	mkdir -p $(@D)
 	rsvg-convert --width=48 --height=48 $< -o $@
 
-APK_DEPS = android/build/res/drawable/icon.png \
-	android/build/libs/armeabi-v7a/libmpd.so \
-	$(wildcard $(srcdir)/android/src/*.java) \
-	android/build/build.xml
-
-android/build/bin/$(APK_NAME)-debug.apk: $(APK_DEPS)
-	cd android/build && ant nodeps debug
+.DELETE_ON_ERROR: android/build/unsigned.apk
+android/build/unsigned.apk: android/build/classes.dex android/build/resources.apk android/build/lib/armeabi-v7a/libmpd.so
+	cp android/build/resources.apk $@
+	cd $(dir $@) && zip -q -r $(notdir $@) classes.dex lib
 
-android/build/bin/$(APK_NAME)-release-unsigned.apk: $(APK_DEPS)
-	cd android/build && ant nodeps release
+android/build/$(APK_NAME)-debug.apk: android/build/unsigned.apk
+	jarsigner -keystore $(HOME)/.android/debug.keystore -storepass android -signedjar $@ $< androiddebugkey
 
-android/build/bin/$(APK_NAME)-release-unaligned.apk: android/build/bin/$(APK_NAME)-release-unsigned.apk
+android/build/$(APK_NAME)-release-unaligned.apk: android/build/unsigned.apk
 	jarsigner -digestalg SHA1 -sigalg MD5withRSA -storepass:env ANDROID_KEYSTORE_PASS -keystore $(ANDROID_KEYSTORE) -signedjar $@ $< $(ANDROID_KEY_ALIAS)
 
-ANDROID_SDK_BUILD_TOOLS_VERSION = 20.0.0
-
-android/build/bin/$(APK_NAME).apk: android/build/bin/$(APK_NAME)-release-unaligned.apk
-	$(ANDROID_SDK)/build-tools/$(ANDROID_SDK_BUILD_TOOLS_VERSION)/zipalign -f 4 $< $@
+android/build/$(APK_NAME).apk: android/build/$(APK_NAME)-release-unaligned.apk
+	$(ZIPALIGN) -f 4 $< $@
 
 endif
 
@@ -2132,6 +2159,7 @@ test_run_output_LDADD = $(MPD_LIBS) \
 	libutil.a
 test_run_output_SOURCES = test/run_output.cxx \
 	test/ScopeIOThread.hxx \
+	test/NullMixerListener.hxx \
 	src/Log.cxx src/LogBackend.cxx \
 	src/IOThread.cxx \
 	src/output/Domain.cxx \
@@ -2155,6 +2183,7 @@ test_read_mixer_LDADD = \
 	libsystem.a \
 	libutil.a
 test_read_mixer_SOURCES = test/read_mixer.cxx \
+	test/NullMixerListener.hxx \
 	src/Log.cxx src/LogBackend.cxx \
 	src/mixer/MixerControl.cxx \
 	src/filter/FilterPlugin.cxx \
diff --git a/Makefile.in b/Makefile.in
index de57cc9..d9bea55 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -1,7 +1,7 @@
-# Makefile.in generated by automake 1.15 from Makefile.am.
+# Makefile.in generated by automake 1.15.1 from Makefile.am.
 # @configure_input@
 
-# Copyright (C) 1994-2014 Free Software Foundation, Inc.
+# Copyright (C) 1994-2017 Free Software Foundation, Inc.
 
 # This Makefile.in is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -2037,8 +2037,8 @@ src_mpd_DEPENDENCIES = libmpd.a $(am__DEPENDENCIES_3) \
 	$(am__DEPENDENCIES_19) $(am__DEPENDENCIES_21) \
 	$(am__DEPENDENCIES_22) $(am__DEPENDENCIES_23) libconf.a \
 	libbasic.a libevent.a libthread.a libnet.a \
-	$(am__DEPENDENCIES_25) libsystem.a libutil.a \
-	$(am__DEPENDENCIES_26) $(am__DEPENDENCIES_1) $(am__append_5)
+	$(am__DEPENDENCIES_25) libsystem.a $(am__DEPENDENCIES_26) \
+	libutil.a $(am__DEPENDENCIES_1) $(am__append_5)
 src_mpd_LINK = $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(src_mpd_LDFLAGS) \
 	$(LDFLAGS) -o $@
 am__src_pcm_dsd2pcm_dsd2pcm_SOURCES_DIST = src/pcm/dsd2pcm/dsd2pcm.c \
@@ -2201,9 +2201,9 @@ test_read_conf_OBJECTS = $(am_test_read_conf_OBJECTS)
 @ENABLE_TEST_TRUE at test_read_conf_DEPENDENCIES = libconf.a \
 @ENABLE_TEST_TRUE@	$(am__DEPENDENCIES_25) \
 @ENABLE_TEST_TRUE@	$(am__DEPENDENCIES_26) libsystem.a libutil.a
-am__test_read_mixer_SOURCES_DIST = test/read_mixer.cxx src/Log.cxx \
-	src/LogBackend.cxx src/mixer/MixerControl.cxx \
-	src/filter/FilterPlugin.cxx \
+am__test_read_mixer_SOURCES_DIST = test/read_mixer.cxx \
+	test/NullMixerListener.hxx src/Log.cxx src/LogBackend.cxx \
+	src/mixer/MixerControl.cxx src/filter/FilterPlugin.cxx \
 	src/filter/plugins/VolumeFilterPlugin.cxx
 @ENABLE_TEST_TRUE at am_test_read_mixer_OBJECTS =  \
 @ENABLE_TEST_TRUE@	test/read_mixer.$(OBJEXT) src/Log.$(OBJEXT) \
@@ -2391,12 +2391,13 @@ am__test_run_normalize_SOURCES_DIST = test/run_normalize.cxx \
 test_run_normalize_OBJECTS = $(am_test_run_normalize_OBJECTS)
 @ENABLE_TEST_TRUE at test_run_normalize_DEPENDENCIES = libutil.a
 am__test_run_output_SOURCES_DIST = test/run_output.cxx \
-	test/ScopeIOThread.hxx src/Log.cxx src/LogBackend.cxx \
-	src/IOThread.cxx src/output/Domain.cxx src/output/Init.cxx \
-	src/output/Finish.cxx src/output/Registry.cxx \
-	src/output/OutputPlugin.cxx src/mixer/MixerControl.cxx \
-	src/mixer/MixerType.cxx src/filter/FilterPlugin.cxx \
-	src/filter/FilterConfig.cxx src/filter/Observer.cxx
+	test/ScopeIOThread.hxx test/NullMixerListener.hxx src/Log.cxx \
+	src/LogBackend.cxx src/IOThread.cxx src/output/Domain.cxx \
+	src/output/Init.cxx src/output/Finish.cxx \
+	src/output/Registry.cxx src/output/OutputPlugin.cxx \
+	src/mixer/MixerControl.cxx src/mixer/MixerType.cxx \
+	src/filter/FilterPlugin.cxx src/filter/FilterConfig.cxx \
+	src/filter/Observer.cxx
 @ENABLE_TEST_TRUE at am_test_run_output_OBJECTS =  \
 @ENABLE_TEST_TRUE@	test/run_output.$(OBJEXT) src/Log.$(OBJEXT) \
 @ENABLE_TEST_TRUE@	src/LogBackend.$(OBJEXT) \
@@ -3293,7 +3294,7 @@ src_mpd_LDADD = libmpd.a $(NEIGHBOR_LIBS) $(DB_LIBS) $(STORAGE_LIBS) \
 	$(SQLITE_LIBS) $(DECODER_LIBS) $(INPUT_LIBS) $(ARCHIVE_LIBS) \
 	$(OUTPUT_LIBS) $(TAG_LIBS) $(FILTER_LIBS) $(ENCODER_LIBS) \
 	$(MIXER_LIBS) libconf.a libbasic.a libevent.a libthread.a \
-	libnet.a $(FS_LIBS) libsystem.a libutil.a $(ICU_LDADD) \
+	libnet.a $(FS_LIBS) libsystem.a $(ICU_LDADD) libutil.a \
 	$(SYSTEMD_DAEMON_LIBS) $(am__append_5)
 src_mpd_SOURCES = \
 	src/Main.cxx src/Main.hxx
@@ -3428,13 +3429,20 @@ UPNP_SOURCES = \
 @ANDROID_TRUE@	src/Main.cxx src/Main.hxx
 
 @ANDROID_TRUE at libmain_a_CPPFLAGS = $(AM_CPPFLAGS) -Iandroid/build/include
+ at ANDROID_TRUE@ANDROID_SDK_BUILD_TOOLS_VERSION = 27.0.0
+ at ANDROID_TRUE@ANDROID_SDK_PLATFORM = android-17
+ at ANDROID_TRUE@ANDROID_BUILD_TOOLS_DIR = $(ANDROID_SDK)/build-tools/$(ANDROID_SDK_BUILD_TOOLS_VERSION)
+ at ANDROID_TRUE@ANDROID_SDK_PLATFORM_DIR = $(ANDROID_SDK)/platforms/$(ANDROID_SDK_PLATFORM)
+ at ANDROID_TRUE@JAVAC = javac
+ at ANDROID_TRUE@AAPT = $(ANDROID_BUILD_TOOLS_DIR)/aapt
+ at ANDROID_TRUE@DX = $(ANDROID_BUILD_TOOLS_DIR)/dx
+ at ANDROID_TRUE@ZIPALIGN = $(ANDROID_BUILD_TOOLS_DIR)/zipalign
+ at ANDROID_TRUE@ANDROID_XML_RES := $(wildcard $(srcdir)/android/res/*/*.xml)
+ at ANDROID_TRUE@ANDROID_XML_RES_COPIES := $(patsubst $(srcdir)/android/%,android/build/%,$(ANDROID_XML_RES))
+ at ANDROID_TRUE@JAVA_SOURCE_NAMES = Bridge.java Loader.java Main.java
+ at ANDROID_TRUE@JAVA_SOURCE_PATHS = $(addprefix $(srcdir)/android/src/,$(JAVA_SOURCE_NAMES))
+ at ANDROID_TRUE@JAVA_CLASSFILES_DIR = android/build/classes
 @ANDROID_TRUE at BUILT_SOURCES = android/build/include/org_musicpd_Bridge.h
- at ANDROID_TRUE@APK_DEPS = android/build/res/drawable/icon.png \
- at ANDROID_TRUE@	android/build/libs/armeabi-v7a/libmpd.so \
- at ANDROID_TRUE@	$(wildcard $(srcdir)/android/src/*.java) \
- at ANDROID_TRUE@	android/build/build.xml
-
- at ANDROID_TRUE@ANDROID_SDK_BUILD_TOOLS_VERSION = 20.0.0
 @ENABLE_HAIKU_TRUE at noinst_DATA = src/haiku/mpd.rdef
 @HAVE_WINDOWS_TRUE at noinst_DATA = win32/res/mpd.rc
 @ENABLE_HAIKU_TRUE at EXTRA_src_mpd_DEPENDENCIES = src/haiku/mpd.rsrc
@@ -4464,6 +4472,7 @@ FILTER_LIBS = \
 
 @ENABLE_TEST_TRUE at test_run_output_SOURCES = test/run_output.cxx \
 @ENABLE_TEST_TRUE@	test/ScopeIOThread.hxx \
+ at ENABLE_TEST_TRUE@	test/NullMixerListener.hxx \
 @ENABLE_TEST_TRUE@	src/Log.cxx src/LogBackend.cxx \
 @ENABLE_TEST_TRUE@	src/IOThread.cxx \
 @ENABLE_TEST_TRUE@	src/output/Domain.cxx \
@@ -4488,6 +4497,7 @@ FILTER_LIBS = \
 @ENABLE_TEST_TRUE@	libutil.a
 
 @ENABLE_TEST_TRUE at test_read_mixer_SOURCES = test/read_mixer.cxx \
+ at ENABLE_TEST_TRUE@	test/NullMixerListener.hxx \
 @ENABLE_TEST_TRUE@	src/Log.cxx src/LogBackend.cxx \
 @ENABLE_TEST_TRUE@	src/mixer/MixerControl.cxx \
 @ENABLE_TEST_TRUE@	src/filter/FilterPlugin.cxx \
@@ -15307,7 +15317,7 @@ distdir: $(DISTFILES)
 	  ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \
 	|| chmod -R a+r "$(distdir)"
 dist-gzip: distdir
-	tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz
+	tardir=$(distdir) && $(am__tar) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).tar.gz
 	$(am__post_remove_distdir)
 
 dist-bzip2: distdir
@@ -15332,7 +15342,7 @@ dist-shar: distdir
 	@echo WARNING: "Support for shar distribution archives is" \
 	               "deprecated." >&2
 	@echo WARNING: "It will be removed altogether in Automake 2.0" >&2
-	shar $(distdir) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).shar.gz
+	shar $(distdir) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).shar.gz
 	$(am__post_remove_distdir)
 
 dist-zip: distdir
@@ -15350,7 +15360,7 @@ dist dist-all:
 distcheck: dist
 	case '$(DIST_ARCHIVES)' in \
 	*.tar.gz*) \
-	  GZIP=$(GZIP_ENV) gzip -dc $(distdir).tar.gz | $(am__untar) ;;\
+	  eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).tar.gz | $(am__untar) ;;\
 	*.tar.bz2*) \
 	  bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\
 	*.tar.lz*) \
@@ -15360,7 +15370,7 @@ distcheck: dist
 	*.tar.Z*) \
 	  uncompress -c $(distdir).tar.Z | $(am__untar) ;;\
 	*.shar.gz*) \
-	  GZIP=$(GZIP_ENV) gzip -dc $(distdir).shar.gz | unshar ;;\
+	  eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).shar.gz | unshar ;;\
 	*.zip*) \
 	  unzip $(distdir).zip ;;\
 	esac
@@ -15603,9 +15613,9 @@ maintainer-clean-generic:
 	@echo "This command is intended for maintainers to use"
 	@echo "it deletes files that may require special tools to rebuild."
 	-test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES)
+ at ANDROID_FALSE@@ENABLE_DOCUMENTATION_FALSE@@ENABLE_HAIKU_FALSE at clean-local:
 @ENABLE_DOCUMENTATION_FALSE at uninstall-local:
 @ENABLE_DOCUMENTATION_FALSE at install-data-local:
- at ANDROID_FALSE@@ENABLE_DOCUMENTATION_FALSE@@ENABLE_HAIKU_FALSE at clean-local:
 clean: clean-am
 
 clean-am: clean-binPROGRAMS clean-generic clean-local \
@@ -15717,28 +15727,41 @@ uninstall-man: uninstall-man1 uninstall-man5
 .PRECIOUS: Makefile
 
 
- at ANDROID_TRUE@all-local: android/build/bin/$(APK_NAME)-debug.apk
+ at ANDROID_TRUE@all-local: android/build/$(APK_NAME)-debug.apk
 @ANDROID_TRUE at clean-local:
 @ANDROID_TRUE@	rm -rf android/build
 
 @ANDROID_TRUE at libmpd.so: $(filter %.a,$(src_mpd_LDADD)) libmain.a
 @ANDROID_TRUE@	$(AM_V_CXXLD)$(CXXLD) -shared -Wl,--no-undefined,-shared,-Bsymbolic -llog -lz -o $@ $(AM_CXXFLAGS) $(CXXFLAGS) $(LDFLAGS) src/libmain_a-Main.o $(src_mpd_LDADD) $(LIBS)
 
- at ANDROID_TRUE@android/build/build.xml: android/AndroidManifest.xml
- at ANDROID_TRUE@	rm -rf android/build
- at ANDROID_TRUE@	mkdir -p android/build/include android/build/res android/build/src/org
- at ANDROID_TRUE@	ln -s $(abs_srcdir)/android/AndroidManifest.xml $(abs_srcdir)/android/custom_rules.xml android/build
- at ANDROID_TRUE@	ln -s $(abs_srcdir)/android/src android/build/src/org/musicpd
- at ANDROID_TRUE@	ln -s $(abs_srcdir)/android/res/values $(abs_srcdir)/android/res/layout android/build/res
- at ANDROID_TRUE@	$(ANDROID_SDK)/tools/android update project --path android/build --target android-17 --name $(APK_NAME)
-
- at ANDROID_TRUE@android/build/bin/classes/org/musicpd/Bridge.class: android/src/Bridge.java android/build/build.xml android/build/res/drawable/icon.png
- at ANDROID_TRUE@	cd android/build && ant compile-jni-classes
-
- at ANDROID_TRUE@android/build/include/org_musicpd_Bridge.h: android/build/bin/classes/org/musicpd/Bridge.class
- at ANDROID_TRUE@	javah -classpath $(ANDROID_SDK)/platforms/android-17/android.jar:android/build/bin/classes -d $(@D) org.musicpd.Bridge
-
- at ANDROID_TRUE@android/build/libs/armeabi-v7a/libmpd.so: libmpd.so android/build/build.xml
+ at ANDROID_TRUE@$(ANDROID_XML_RES_COPIES): $(ANDROID_XML_RES)
+ at ANDROID_TRUE@	@$(MKDIR_P) $(dir $@)
+ at ANDROID_TRUE@	cp $(patsubst android/build/%,$(srcdir)/android/%,$@) $@
+
+ at ANDROID_TRUE@android/build/resources.apk: $(ANDROID_XML_RES_COPIES) android/build/res/drawable/icon.png
+ at ANDROID_TRUE@	@$(MKDIR_P) android/build/gen
+ at ANDROID_TRUE@	$(AAPT) package -f -m --auto-add-overlay \
+ at ANDROID_TRUE@		--custom-package org.musicpd \
+ at ANDROID_TRUE@		-M $(srcdir)/android/AndroidManifest.xml \
+ at ANDROID_TRUE@		-S android/build/res \
+ at ANDROID_TRUE@		-J android/build/gen \
+ at ANDROID_TRUE@		-I $(ANDROID_SDK_PLATFORM_DIR)/android.jar \
+ at ANDROID_TRUE@		-F android/build/resources.apk
+
+# R.java is generated by aapt, when resources.apk is generated
+ at ANDROID_TRUE@android/build/gen/org/musicpd/R.java: android/build/resources.apk
+
+ at ANDROID_TRUE@android/build/classes.dex: $(JAVA_SOURCE_PATHS) android/build/gen/org/musicpd/R.java
+ at ANDROID_TRUE@	@$(MKDIR_P) $(JAVA_CLASSFILES_DIR)
+ at ANDROID_TRUE@	$(JAVAC) -source 1.5 -target 1.5 -Xlint:-options \
+ at ANDROID_TRUE@		-cp $(ANDROID_SDK_PLATFORM_DIR)/android.jar:$(JAVA_CLASSFILES_DIR) \
+ at ANDROID_TRUE@		-d $(JAVA_CLASSFILES_DIR) $^
+ at ANDROID_TRUE@	$(DX) --dex --output $@ $(JAVA_CLASSFILES_DIR)
+
+ at ANDROID_TRUE@android/build/include/org_musicpd_Bridge.h: android/build/classes.dex
+ at ANDROID_TRUE@	javah -classpath $(ANDROID_SDK_PLATFORM_DIR)/android.jar:$(JAVA_CLASSFILES_DIR) -d $(@D) org.musicpd.Bridge
+
+ at ANDROID_TRUE@android/build/lib/armeabi-v7a/libmpd.so: libmpd.so
 @ANDROID_TRUE@	mkdir -p $(@D)
 @ANDROID_TRUE@	rm -f $@
 @ANDROID_TRUE@	$(STRIP) -o $@ $<
@@ -15747,17 +15770,19 @@ uninstall-man: uninstall-man1 uninstall-man5
 @ANDROID_TRUE@	mkdir -p $(@D)
 @ANDROID_TRUE@	rsvg-convert --width=48 --height=48 $< -o $@
 
- at ANDROID_TRUE@android/build/bin/$(APK_NAME)-debug.apk: $(APK_DEPS)
- at ANDROID_TRUE@	cd android/build && ant nodeps debug
+ at ANDROID_TRUE@.DELETE_ON_ERROR: android/build/unsigned.apk
+ at ANDROID_TRUE@android/build/unsigned.apk: android/build/classes.dex android/build/resources.apk android/build/lib/armeabi-v7a/libmpd.so
+ at ANDROID_TRUE@	cp android/build/resources.apk $@
+ at ANDROID_TRUE@	cd $(dir $@) && zip -q -r $(notdir $@) classes.dex lib
 
- at ANDROID_TRUE@android/build/bin/$(APK_NAME)-release-unsigned.apk: $(APK_DEPS)
- at ANDROID_TRUE@	cd android/build && ant nodeps release
+ at ANDROID_TRUE@android/build/$(APK_NAME)-debug.apk: android/build/unsigned.apk
+ at ANDROID_TRUE@	jarsigner -keystore $(HOME)/.android/debug.keystore -storepass android -signedjar $@ $< androiddebugkey
 
- at ANDROID_TRUE@android/build/bin/$(APK_NAME)-release-unaligned.apk: android/build/bin/$(APK_NAME)-release-unsigned.apk
+ at ANDROID_TRUE@android/build/$(APK_NAME)-release-unaligned.apk: android/build/unsigned.apk
 @ANDROID_TRUE@	jarsigner -digestalg SHA1 -sigalg MD5withRSA -storepass:env ANDROID_KEYSTORE_PASS -keystore $(ANDROID_KEYSTORE) -signedjar $@ $< $(ANDROID_KEY_ALIAS)
 
- at ANDROID_TRUE@android/build/bin/$(APK_NAME).apk: android/build/bin/$(APK_NAME)-release-unaligned.apk
- at ANDROID_TRUE@	$(ANDROID_SDK)/build-tools/$(ANDROID_SDK_BUILD_TOOLS_VERSION)/zipalign -f 4 $< $@
+ at ANDROID_TRUE@android/build/$(APK_NAME).apk: android/build/$(APK_NAME)-release-unaligned.apk
+ at ANDROID_TRUE@	$(ZIPALIGN) -f 4 $< $@
 
 #
 # Windows resource file
diff --git a/NEWS b/NEWS
index 00a841b..fd91dea 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,33 @@
+ver 0.20.17 (2018/02/11)
+* output
+  - alsa: fix crash bug with 8 channels
+* mixer
+  - alsa: fix rounding error at volume 0
+* fix real-time and idle scheduling with Musl
+* Android
+  - fix compatibility with Android 4.0
+
+ver 0.20.16 (2018/02/03)
+* output
+  - pulse: fix crash during auto-detection
+* database
+  - simple: fix search within mount points
+  - upnp: enable IPv6
+* archive
+  - iso9660: libcdio 2.0 compatibility
+* fix crash in debug build on Haiku and other operating systems
+
+ver 0.20.15 (2018/01/05)
+* queue: fix crash after seek failure
+* resampler
+  - soxr: clear internal state after manual song change
+* state file
+  - make mount point restore errors non-fatal
+  - fix crash when restoring mounts with incompatible database plugin
+* Android
+  - build without Ant
+  - fix for SIGSYS crash
+
 ver 0.20.14 (2018/01/01)
 * database
   - simple: fix file corruption in the presence of mount points
diff --git a/aclocal.m4 b/aclocal.m4
index d9035f6..82a3458 100644
--- a/aclocal.m4
+++ b/aclocal.m4
@@ -1,6 +1,6 @@
-# generated automatically by aclocal 1.15 -*- Autoconf -*-
+# generated automatically by aclocal 1.15.1 -*- Autoconf -*-
 
-# Copyright (C) 1996-2014 Free Software Foundation, Inc.
+# Copyright (C) 1996-2017 Free Software Foundation, Inc.
 
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -20,7 +20,7 @@ You have another version of autoconf.  It may work, but is not guaranteed to.
 If you have problems, you may need to regenerate the build system entirely.
 To do so, use the procedure documented by the package, typically 'autoreconf'.])])
 
-# Copyright (C) 2002-2014 Free Software Foundation, Inc.
+# Copyright (C) 2002-2017 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -35,7 +35,7 @@ AC_DEFUN([AM_AUTOMAKE_VERSION],
 [am__api_version='1.15'
 dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to
 dnl require some minimum version.  Point them to the right macro.
-m4_if([$1], [1.15], [],
+m4_if([$1], [1.15.1], [],
       [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl
 ])
 
@@ -51,14 +51,14 @@ m4_define([_AM_AUTOCONF_VERSION], [])
 # Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced.
 # This function is AC_REQUIREd by AM_INIT_AUTOMAKE.
 AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION],
-[AM_AUTOMAKE_VERSION([1.15])dnl
+[AM_AUTOMAKE_VERSION([1.15.1])dnl
 m4_ifndef([AC_AUTOCONF_VERSION],
   [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
 _AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))])
 
 # AM_AUX_DIR_EXPAND                                         -*- Autoconf -*-
 
-# Copyright (C) 2001-2014 Free Software Foundation, Inc.
+# Copyright (C) 2001-2017 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -110,7 +110,7 @@ am_aux_dir=`cd "$ac_aux_dir" && pwd`
 
 # AM_CONDITIONAL                                            -*- Autoconf -*-
 
-# Copyright (C) 1997-2014 Free Software Foundation, Inc.
+# Copyright (C) 1997-2017 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -141,7 +141,7 @@ AC_CONFIG_COMMANDS_PRE(
 Usually this means the macro was only invoked conditionally.]])
 fi])])
 
-# Copyright (C) 1999-2014 Free Software Foundation, Inc.
+# Copyright (C) 1999-2017 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -332,7 +332,7 @@ _AM_SUBST_NOTMAKE([am__nodep])dnl
 
 # Generate code to set up dependency tracking.              -*- Autoconf -*-
 
-# Copyright (C) 1999-2014 Free Software Foundation, Inc.
+# Copyright (C) 1999-2017 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -408,7 +408,7 @@ AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS],
 
 # Do all the work for Automake.                             -*- Autoconf -*-
 
-# Copyright (C) 1996-2014 Free Software Foundation, Inc.
+# Copyright (C) 1996-2017 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -605,7 +605,7 @@ for _am_header in $config_headers :; do
 done
 echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count])
 
-# Copyright (C) 2001-2014 Free Software Foundation, Inc.
+# Copyright (C) 2001-2017 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -626,7 +626,7 @@ if test x"${install_sh+set}" != xset; then
 fi
 AC_SUBST([install_sh])])
 
-# Copyright (C) 2003-2014 Free Software Foundation, Inc.
+# Copyright (C) 2003-2017 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -647,7 +647,7 @@ AC_SUBST([am__leading_dot])])
 
 # Check to see how 'make' treats includes.	            -*- Autoconf -*-
 
-# Copyright (C) 2001-2014 Free Software Foundation, Inc.
+# Copyright (C) 2001-2017 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -697,7 +697,7 @@ rm -f confinc confmf
 
 # Fake the existence of programs that GNU maintainers use.  -*- Autoconf -*-
 
-# Copyright (C) 1997-2014 Free Software Foundation, Inc.
+# Copyright (C) 1997-2017 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -736,7 +736,7 @@ fi
 
 # Helper functions for option handling.                     -*- Autoconf -*-
 
-# Copyright (C) 2001-2014 Free Software Foundation, Inc.
+# Copyright (C) 2001-2017 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -765,7 +765,7 @@ AC_DEFUN([_AM_SET_OPTIONS],
 AC_DEFUN([_AM_IF_OPTION],
 [m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])])
 
-# Copyright (C) 1999-2014 Free Software Foundation, Inc.
+# Copyright (C) 1999-2017 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -812,7 +812,7 @@ AC_LANG_POP([C])])
 # For backward compatibility.
 AC_DEFUN_ONCE([AM_PROG_CC_C_O], [AC_REQUIRE([AC_PROG_CC])])
 
-# Copyright (C) 2001-2014 Free Software Foundation, Inc.
+# Copyright (C) 2001-2017 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -831,7 +831,7 @@ AC_DEFUN([AM_RUN_LOG],
 
 # Check to make sure that the build environment is sane.    -*- Autoconf -*-
 
-# Copyright (C) 1996-2014 Free Software Foundation, Inc.
+# Copyright (C) 1996-2017 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -912,7 +912,7 @@ AC_CONFIG_COMMANDS_PRE(
 rm -f conftest.file
 ])
 
-# Copyright (C) 2009-2014 Free Software Foundation, Inc.
+# Copyright (C) 2009-2017 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -972,7 +972,7 @@ AC_SUBST([AM_BACKSLASH])dnl
 _AM_SUBST_NOTMAKE([AM_BACKSLASH])dnl
 ])
 
-# Copyright (C) 2001-2014 Free Software Foundation, Inc.
+# Copyright (C) 2001-2017 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -1000,7 +1000,7 @@ fi
 INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s"
 AC_SUBST([INSTALL_STRIP_PROGRAM])])
 
-# Copyright (C) 2006-2014 Free Software Foundation, Inc.
+# Copyright (C) 2006-2017 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -1019,7 +1019,7 @@ AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)])
 
 # Check how to create a tarball.                            -*- Autoconf -*-
 
-# Copyright (C) 2004-2014 Free Software Foundation, Inc.
+# Copyright (C) 2004-2017 Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml
index 208c6e1..713c136 100644
--- a/android/AndroidManifest.xml
+++ b/android/AndroidManifest.xml
@@ -2,10 +2,10 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="org.musicpd"
           android:installLocation="auto"
-          android:versionCode="13"
-          android:versionName="0.19.9">
+          android:versionCode="16"
+          android:versionName="0.20.17">
 
-  <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="17"/>
+  <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="17"/>
 
   <application android:icon="@drawable/icon" android:label="@string/app_name">
     <activity android:name=".Main"
diff --git a/android/build.py b/android/build.py
index fab6ecb..3e2e71d 100755
--- a/android/build.py
+++ b/android/build.py
@@ -46,7 +46,7 @@ class AndroidNdkToolchain:
 
         self.ndk_arch = 'arm'
         android_abi = 'armeabi-v7a'
-        ndk_platform = 'android-21'
+        ndk_platform = 'android-14'
 
         # select the NDK compiler
         gcc_version = '4.9'
@@ -65,7 +65,9 @@ class AndroidNdkToolchain:
         llvm_path = os.path.join(ndk_path, 'toolchains', 'llvm', 'prebuilt', build_arch)
         llvm_triple = 'armv7-none-linux-androideabi'
 
-        common_flags = '-march=armv7-a -mfloat-abi=softfp'
+        common_flags = '-Os -g'
+        common_flags += ' -fPIC'
+        common_flags += ' -march=armv7-a -mfpu=vfp -mfloat-abi=softfp'
 
         toolchain_bin = os.path.join(toolchain_path, 'bin')
         llvm_bin = os.path.join(llvm_path, 'bin')
@@ -73,17 +75,19 @@ class AndroidNdkToolchain:
         self.cxx = os.path.join(llvm_bin, 'clang++')
         common_flags += ' -target ' + llvm_triple + ' -integrated-as -gcc-toolchain ' + toolchain_path
 
+        common_flags += ' -fvisibility=hidden -fdata-sections -ffunction-sections'
+
         self.ar = os.path.join(toolchain_bin, arch + '-ar')
         self.ranlib = os.path.join(toolchain_bin, arch + '-ranlib')
         self.nm = os.path.join(toolchain_bin, arch + '-nm')
         self.strip = os.path.join(toolchain_bin, arch + '-strip')
 
-        self.cflags = '-Os -g ' + common_flags
-        self.cxxflags = '-Os -g ' + common_flags
+        self.cflags = common_flags
+        self.cxxflags = common_flags
         self.cppflags = '--sysroot=' + sysroot + \
             ' -isystem ' + os.path.join(install_prefix, 'include') + \
             ' -isystem ' + os.path.join(sysroot, 'usr', 'include', arch) + \
-            ' -D__ANDROID_API__=21'
+            ' -D__ANDROID_API__=14'
         self.ldflags = '--sysroot=' + sysroot + \
             ' -L' + os.path.join(install_prefix, 'lib') + \
             ' -L' + os.path.join(target_root, 'usr', 'lib') + \
@@ -98,15 +102,13 @@ class AndroidNdkToolchain:
         libcxx_path = os.path.join(ndk_path, 'sources/cxx-stl/llvm-libc++')
         libcxx_libs_path = os.path.join(libcxx_path, 'libs', android_abi)
 
-        libstdcxx_cppflags = '-nostdinc++ -isystem ' + os.path.join(libcxx_path, 'include') + ' -isystem ' + os.path.join(ndk_path, 'sources/android/support/include')
-        libstdcxx_ldadd = os.path.join(libcxx_libs_path, 'libc++_static.a') + ' ' + os.path.join(libcxx_libs_path, 'libc++abi.a')
-
-        if self.is_armv7:
-            libstdcxx_ldadd += ' ' + os.path.join(libcxx_libs_path, 'libunwind.a')
+        libstdcxx_flags = '-stdlib=libc++'
+        libstdcxx_cxxflags = libstdcxx_flags + ' -isystem ' + os.path.join(libcxx_path, 'include') + ' -isystem ' + os.path.join(ndk_path, 'sources/android/support/include')
+        libstdcxx_ldflags = libstdcxx_flags + ' -static-libstdc++ -L' + libcxx_libs_path
 
         if use_cxx:
-            self.libs += ' ' + libstdcxx_ldadd
-            self.cppflags += ' ' + libstdcxx_cppflags
+            self.cxxflags += ' ' + libstdcxx_cxxflags
+            self.ldflags += ' ' + libstdcxx_ldflags
 
         self.env = dict(os.environ)
 
diff --git a/configure b/configure
index a66d846..9e66e0d 100755
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for mpd 0.20.14.
+# Generated by GNU Autoconf 2.69 for mpd 0.20.17.
 #
 # Report bugs to <musicpd-dev-team at lists.sourceforge.net>.
 #
@@ -580,8 +580,8 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='mpd'
 PACKAGE_TARNAME='mpd'
-PACKAGE_VERSION='0.20.14'
-PACKAGE_STRING='mpd 0.20.14'
+PACKAGE_VERSION='0.20.17'
+PACKAGE_STRING='mpd 0.20.17'
 PACKAGE_BUGREPORT='musicpd-dev-team at lists.sourceforge.net'
 PACKAGE_URL=''
 
@@ -1785,7 +1785,7 @@ if test "$ac_init_help" = "long"; then
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures mpd 0.20.14 to adapt to many kinds of systems.
+\`configure' configures mpd 0.20.17 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1856,7 +1856,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of mpd 0.20.14:";;
+     short | recursive ) echo "Configuration of mpd 0.20.17:";;
    esac
   cat <<\_ACEOF
 
@@ -2209,7 +2209,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-mpd configure 0.20.14
+mpd configure 0.20.17
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -2616,7 +2616,7 @@ cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by mpd $as_me 0.20.14, which was
+It was created by mpd $as_me 0.20.17, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -2967,7 +2967,7 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
 VERSION_MAJOR=0
 VERSION_MINOR=20
-VERSION_REVISION=14
+VERSION_REVISION=17
 VERSION_EXTRA=0
 
 
@@ -3486,7 +3486,7 @@ fi
 
 # Define the identity of the package.
  PACKAGE='mpd'
- VERSION='0.20.14'
+ VERSION='0.20.17'
 
 
 cat >>confdefs.h <<_ACEOF
@@ -21779,7 +21779,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by mpd $as_me 0.20.14, which was
+This file was extended by mpd $as_me 0.20.17, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -21845,7 +21845,7 @@ _ACEOF
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-mpd config.status 0.20.14
+mpd config.status 0.20.17
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
diff --git a/configure.ac b/configure.ac
index d9190dc..800baa0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,10 +1,10 @@
 AC_PREREQ(2.60)
 
-AC_INIT(mpd, 0.20.14, musicpd-dev-team at lists.sourceforge.net)
+AC_INIT(mpd, 0.20.17, musicpd-dev-team at lists.sourceforge.net)
 
 VERSION_MAJOR=0
 VERSION_MINOR=20
-VERSION_REVISION=14
+VERSION_REVISION=17
 VERSION_EXTRA=0
 
 AC_CONFIG_SRCDIR([src/Main.cxx])
diff --git a/doc/doxygen.conf b/doc/doxygen.conf
index 5d3fd9a..dbde08c 100644
--- a/doc/doxygen.conf
+++ b/doc/doxygen.conf
@@ -38,7 +38,7 @@ PROJECT_NAME           = MPD
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 0.20.14
+PROJECT_NUMBER         = 0.20.17
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
diff --git a/doc/user.xml b/doc/user.xml
index 5cc7412..feab01c 100644
--- a/doc/user.xml
+++ b/doc/user.xml
@@ -66,6 +66,23 @@
       </para>
     </section>
 
+    <section id="install_android">
+      <title>Installing on Android</title>
+
+      <para>
+        An experimental Android build is available on <ulink
+        url="https://play.google.com/store/apps/details?id=org.musicpd">Google
+        Play</ulink>.  After installing and launching it, MPD will
+        scan the music in your <filename>Music</filename> directory
+        and you can control it as usual with a MPD client.
+      </para>
+
+      <para>
+        If you need to tweak the configuration, you can create a file
+        called <filename>mpd.conf</filename> on the data partition.
+      </para>
+    </section>
+
     <section id="install_source">
       <title>Compiling from source</title>
 
@@ -323,7 +340,9 @@ systemctl start mpd.socket</programlisting>
         <application>MPD</application> as a user daemon (and not as a
         system daemon), the configuration is read from
         <filename>$XDG_CONFIG_HOME/mpd/mpd.conf</filename> (usually
-        <filename>~/.config/mpd/mpd.conf</filename>).
+        <filename>~/.config/mpd/mpd.conf</filename>).  On Android,
+        <filename>mpd.conf</filename> will be loaded from the
+        top-level directory of the data partition.
       </para>
 
       <para>
diff --git a/python/build/autotools.py b/python/build/autotools.py
index d9a1f15..58d5d8c 100644
--- a/python/build/autotools.py
+++ b/python/build/autotools.py
@@ -1,21 +1,30 @@
-import os.path, subprocess
+import os.path, subprocess, sys
 
-from build.project import Project
+from build.makeproject import MakeProject
 
-class AutotoolsProject(Project):
+class AutotoolsProject(MakeProject):
     def __init__(self, url, md5, installed, configure_args=[],
                  autogen=False,
                  cppflags='',
+                 ldflags='',
+                 libs='',
+                 subdirs=None,
                  **kwargs):
-        Project.__init__(self, url, md5, installed, **kwargs)
+        MakeProject.__init__(self, url, md5, installed, **kwargs)
         self.configure_args = configure_args
         self.autogen = autogen
         self.cppflags = cppflags
+        self.ldflags = ldflags
+        self.libs = libs
+        self.subdirs = subdirs
 
-    def build(self, toolchain):
+    def configure(self, toolchain):
         src = self.unpack(toolchain)
         if self.autogen:
-            subprocess.check_call(['libtoolize', '--force'], cwd=src)
+            if sys.platform == 'darwin':
+                subprocess.check_call(['glibtoolize', '--force'], cwd=src)
+            else:
+                subprocess.check_call(['libtoolize', '--force'], cwd=src)
             subprocess.check_call(['aclocal'], cwd=src)
             subprocess.check_call(['automake', '--add-missing', '--force-missing', '--foreign'], cwd=src)
             subprocess.check_call(['autoconf'], cwd=src)
@@ -29,8 +38,8 @@ class AutotoolsProject(Project):
             'CFLAGS=' + toolchain.cflags,
             'CXXFLAGS=' + toolchain.cxxflags,
             'CPPFLAGS=' + toolchain.cppflags + ' ' + self.cppflags,
-            'LDFLAGS=' + toolchain.ldflags,
-            'LIBS=' + toolchain.libs,
+            'LDFLAGS=' + toolchain.ldflags + ' ' + self.ldflags,
+            'LIBS=' + toolchain.libs + ' ' + self.libs,
             'AR=' + toolchain.ar,
             'RANLIB=' + toolchain.ranlib,
             'STRIP=' + toolchain.strip,
@@ -40,7 +49,12 @@ class AutotoolsProject(Project):
         ] + self.configure_args
 
         subprocess.check_call(configure, cwd=build, env=toolchain.env)
-        subprocess.check_call(['/usr/bin/make', '--quiet', '-j12'],
-                              cwd=build, env=toolchain.env)
-        subprocess.check_call(['/usr/bin/make', '--quiet', 'install'],
-                              cwd=build, env=toolchain.env)
+        return build
+
+    def build(self, toolchain):
+        build = self.configure(toolchain)
+        if self.subdirs is not None:
+            for subdir in self.subdirs:
+                MakeProject.build(self, toolchain, os.path.join(build, subdir))
+        else:
+            MakeProject.build(self, toolchain, build)
diff --git a/python/build/libs.py b/python/build/libs.py
index 5acabf6..7d93799 100644
--- a/python/build/libs.py
+++ b/python/build/libs.py
@@ -1,3 +1,6 @@
+import re
+from os.path import abspath
+
 from build.project import Project
 from build.zlib import ZlibProject
 from build.autotools import AutotoolsProject
@@ -5,24 +8,35 @@ from build.ffmpeg import FfmpegProject
 from build.boost import BoostProject
 
 libogg = AutotoolsProject(
-    'http://downloads.xiph.org/releases/ogg/libogg-1.3.2.tar.xz',
-    '5c3a34309d8b98640827e5d0991a4015',
+    'http://downloads.xiph.org/releases/ogg/libogg-1.3.3.tar.xz',
+    '4f3fc6178a533d392064f14776b23c397ed4b9f48f5de297aba73b643f955c08',
     'lib/libogg.a',
-    ['--disable-shared', '--enable-static'],
+    [
+        '--disable-shared', '--enable-static',
+    ],
 )
 
 libvorbis = AutotoolsProject(
     'http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.5.tar.xz',
     '28cb28097c07a735d6af56e598e1c90f',
     'lib/libvorbis.a',
-    ['--disable-shared', '--enable-static'],
+    [
+        '--disable-shared', '--enable-static',
+    ],
 )
 
 opus = AutotoolsProject(
     'https://archive.mozilla.org/pub/opus/opus-1.2.1.tar.gz',
     'cfafd339ccd9c5ef8d6ab15d7e1a412c054bf4cb4ecbbbcc78c12ef2def70732',
     'lib/libopus.a',
-    ['--disable-shared', '--enable-static'],
+    [
+        '--disable-shared', '--enable-static',
+        '--disable-doc',
+        '--disable-extra-programs',
+    ],
+
+    # suppress "visibility default" from opus_defines.h
+    cppflags='-DOPUS_EXPORT=',
 )
 
 flac = AutotoolsProject(
@@ -32,7 +46,9 @@ flac = AutotoolsProject(
     [
         '--disable-shared', '--enable-static',
         '--disable-xmms-plugin', '--disable-cpplibs',
+        '--disable-doxygen-docs',
     ],
+    subdirs=['include', 'src/libFLAC'],
 )
 
 zlib = ZlibProject(
@@ -45,21 +61,36 @@ libid3tag = AutotoolsProject(
     'ftp://ftp.mars.org/pub/mpeg/libid3tag-0.15.1b.tar.gz',
     'e5808ad997ba32c498803822078748c3',
     'lib/libid3tag.a',
-    ['--disable-shared', '--enable-static'],
+    [
+        '--disable-shared', '--enable-static',
+
+        # without this, libid3tag's configure.ac ignores -O* and -f*
+        '--disable-debugging',
+    ],
     autogen=True,
+
+    edits={
+        # fix bug in libid3tag's configure.ac which discards all but the last optimization flag
+        'configure.ac': lambda data: re.sub(r'optimize="\$1"', r'optimize="$optimize $1"', data, count=1),
+    }
 )
 
 libmad = AutotoolsProject(
     'ftp://ftp.mars.org/pub/mpeg/libmad-0.15.1b.tar.gz',
     '1be543bc30c56fb6bea1d7bf6a64e66c',
     'lib/libmad.a',
-    ['--disable-shared', '--enable-static'],
+    [
+        '--disable-shared', '--enable-static',
+
+        # without this, libmad's configure.ac ignores -O* and -f*
+        '--disable-debugging',
+    ],
     autogen=True,
 )
 
 liblame = AutotoolsProject(
-    'http://downloads.sourceforge.net/project/lame/lame/3.99/lame-3.99.5.tar.gz',
-    '24346b4158e4af3bd9f2e194bb23eb473c75fb7377011523353196b19b9a23ff',
+    'http://downloads.sourceforge.net/project/lame/lame/3.100/lame-3.100.tar.gz',
+    'ddfe36cab873794038ae2c1210557ad34857a4b6bdc515785d1da9e175b1da1e',
     'lib/libmp3lame.a',
     [
         '--disable-shared', '--enable-static',
@@ -89,21 +120,229 @@ ffmpeg = FfmpegProject(
         '--disable-pixelutils',
         '--disable-network',
         '--disable-encoders',
+        '--disable-muxers',
         '--disable-protocols',
         '--disable-devices',
         '--disable-filters',
+        '--disable-filters',
         '--disable-v4l2_m2m',
 
-        # clang misinterprets the "B0" in hevc_mvs.c as binary
-        # literal, which breaks the build; but we don't need that
-        # video codec anyway
+        '--disable-parser=bmp',
+        '--disable-parser=cavsvideo',
+        '--disable-parser=dvbsub',
+        '--disable-parser=dvdsub',
+        '--disable-parser=dvd_nav',
+        '--disable-parser=flac',
+        '--disable-parser=g729',
+        '--disable-parser=gsm',
+        '--disable-parser=h261',
+        '--disable-parser=h263',
+        '--disable-parser=h264',
+        '--disable-parser=hevc',
+        '--disable-parser=mjpeg',
+        '--disable-parser=mlp',
+        '--disable-parser=mpeg4video',
+        '--disable-parser=mpegaudio',
+        '--disable-parser=mpegvideo',
+        '--disable-parser=opus',
+        '--disable-parser=vc1',
+        '--disable-parser=vp3',
+        '--disable-parser=vp8',
+        '--disable-parser=vp9',
+        '--disable-parser=png',
+        '--disable-parser=pnm',
+        '--disable-parser=xma',
+
+        '--disable-demuxer=aqtitle',
+        '--disable-demuxer=ass',
+        '--disable-demuxer=bethsoftvid',
+        '--disable-demuxer=bink',
+        '--disable-demuxer=cavsvideo',
+        '--disable-demuxer=cdxl',
+        '--disable-demuxer=dvbsub',
+        '--disable-demuxer=dvbtxt',
+        '--disable-demuxer=h261',
+        '--disable-demuxer=h263',
+        '--disable-demuxer=h264',
+        '--disable-demuxer=ico',
+        '--disable-demuxer=image2',
+        '--disable-demuxer=jacosub',
+        '--disable-demuxer=lrc',
+        '--disable-demuxer=microdvd',
+        '--disable-demuxer=mjpeg',
+        '--disable-demuxer=mjpeg_2000',
+        '--disable-demuxer=mpegps',
+        '--disable-demuxer=mpegvideo',
+        '--disable-demuxer=mpl2',
+        '--disable-demuxer=mpsub',
+        '--disable-demuxer=pjs',
+        '--disable-demuxer=rawvideo',
+        '--disable-demuxer=realtext',
+        '--disable-demuxer=sami',
+        '--disable-demuxer=scc',
+        '--disable-demuxer=srt',
+        '--disable-demuxer=stl',
+        '--disable-demuxer=subviewer',
+        '--disable-demuxer=subviewer1',
+        '--disable-demuxer=swf',
+        '--disable-demuxer=tedcaptions',
+        '--disable-demuxer=vobsub',
+        '--disable-demuxer=vplayer',
+        '--disable-demuxer=webvtt',
+        '--disable-demuxer=yuv4mpegpipe',
+
+        # we don't need these decoders, because we have the dedicated
+        # libraries
+        '--disable-decoder=flac',
+        '--disable-decoder=mp1',
+        '--disable-decoder=mp1float',
+        '--disable-decoder=mp2',
+        '--disable-decoder=mp2float',
+        '--disable-decoder=mp3',
+        '--disable-decoder=mp3adu',
+        '--disable-decoder=mp3adufloat',
+        '--disable-decoder=mp3float',
+        '--disable-decoder=mp3on4',
+        '--disable-decoder=mp3on4float',
+        '--disable-decoder=opus',
+        '--disable-decoder=vorbis',
+
+        # audio codecs nobody uses
+        '--disable-decoder=atrac1',
+        '--disable-decoder=atrac3',
+        '--disable-decoder=atrac3al',
+        '--disable-decoder=atrac3p',
+        '--disable-decoder=atrac3pal',
+        '--disable-decoder=binkaudio_dct',
+        '--disable-decoder=binkaudio_rdft',
+        '--disable-decoder=bmv_audio',
+        '--disable-decoder=dsicinaudio',
+        '--disable-decoder=dvaudio',
+        '--disable-decoder=metasound',
+        '--disable-decoder=paf_audio',
+        '--disable-decoder=ra_144',
+        '--disable-decoder=ra_288',
+        '--disable-decoder=ralf',
+        '--disable-decoder=qdm2',
+        '--disable-decoder=qdmc',
+
+        # disable lots of image and video codecs
+        '--disable-decoder=ass',
+        '--disable-decoder=asv1',
+        '--disable-decoder=asv2',
+        '--disable-decoder=apng',
+        '--disable-decoder=avrn',
+        '--disable-decoder=avrp',
+        '--disable-decoder=bethsoftvid',
+        '--disable-decoder=bink',
+        '--disable-decoder=bmp',
+        '--disable-decoder=bmv_video',
+        '--disable-decoder=cavs',
+        '--disable-decoder=ccaption',
+        '--disable-decoder=cdgraphics',
+        '--disable-decoder=clearvideo',
+        '--disable-decoder=dirac',
+        '--disable-decoder=dsicinvideo',
+        '--disable-decoder=dvbsub',
+        '--disable-decoder=dvdsub',
+        '--disable-decoder=dvvideo',
+        '--disable-decoder=exr',
+        '--disable-decoder=ffv1',
+        '--disable-decoder=ffvhuff',
+        '--disable-decoder=ffwavesynth',
+        '--disable-decoder=flic',
+        '--disable-decoder=flv',
+        '--disable-decoder=fraps',
+        '--disable-decoder=gif',
+        '--disable-decoder=h261',
+        '--disable-decoder=h263',
+        '--disable-decoder=h263i',
+        '--disable-decoder=h263p',
+        '--disable-decoder=h264',
         '--disable-decoder=hevc',
+        '--disable-decoder=hnm4_video',
+        '--disable-decoder=hq_hqa',
+        '--disable-decoder=hqx',
+        '--disable-decoder=idcin',
+        '--disable-decoder=iff_ilbm',
+        '--disable-decoder=indeo2',
+        '--disable-decoder=indeo3',
+        '--disable-decoder=indeo4',
+        '--disable-decoder=indeo5',
+        '--disable-decoder=interplay_video',
+        '--disable-decoder=jacosub',
+        '--disable-decoder=jpeg2000',
+        '--disable-decoder=jpegls',
+        '--disable-decoder=microdvd',
+        '--disable-decoder=mimic',
+        '--disable-decoder=mjpeg',
+        '--disable-decoder=mmvideo',
+        '--disable-decoder=mpl2',
+        '--disable-decoder=motionpixels',
+        '--disable-decoder=mpeg1video',
+        '--disable-decoder=mpeg2video',
+        '--disable-decoder=mpeg4',
+        '--disable-decoder=mpegvideo',
+        '--disable-decoder=mscc',
+        '--disable-decoder=msmpeg4_crystalhd',
+        '--disable-decoder=msmpeg4v1',
+        '--disable-decoder=msmpeg4v2',
+        '--disable-decoder=msmpeg4v3',
+        '--disable-decoder=msvideo1',
+        '--disable-decoder=mszh',
+        '--disable-decoder=mvc1',
+        '--disable-decoder=mvc2',
+        '--disable-decoder=on2avc',
+        '--disable-decoder=paf_video',
+        '--disable-decoder=png',
+        '--disable-decoder=qdraw',
+        '--disable-decoder=qpeg',
+        '--disable-decoder=rawvideo',
+        '--disable-decoder=realtext',
+        '--disable-decoder=roq',
+        '--disable-decoder=roq_dpcm',
+        '--disable-decoder=rscc',
+        '--disable-decoder=rv10',
+        '--disable-decoder=rv20',
+        '--disable-decoder=rv30',
+        '--disable-decoder=rv40',
+        '--disable-decoder=sami',
+        '--disable-decoder=sheervideo',
+        '--disable-decoder=snow',
+        '--disable-decoder=srt',
+        '--disable-decoder=stl',
+        '--disable-decoder=subrip',
+        '--disable-decoder=subviewer',
+        '--disable-decoder=subviewer1',
+        '--disable-decoder=svq1',
+        '--disable-decoder=svq3',
+        '--disable-decoder=tiff',
+        '--disable-decoder=mottiertexseqvideo',
+        '--disable-decoder=truemotion1',
+        '--disable-decoder=truemotion2',
+        '--disable-decoder=truemotion2rt',
+        '--disable-decoder=twinvq',
+        '--disable-decoder=utvideo',
+        '--disable-decoder=vc1',
+        '--disable-decoder=vmdvideo',
+        '--disable-decoder=vp3',
+        '--disable-decoder=vp5',
+        '--disable-decoder=vp6',
+        '--disable-decoder=vp7',
+        '--disable-decoder=vp8',
+        '--disable-decoder=vp9',
+        '--disable-decoder=vqa',
+        '--disable-decoder=webvtt',
+        '--disable-decoder=wmv1',
+        '--disable-decoder=wmv2',
+        '--disable-decoder=wmv3',
+        '--disable-decoder=yuv4',
     ],
 )
 
 curl = AutotoolsProject(
-    'http://curl.haxx.se/download/curl-7.57.0.tar.xz',
-    'f5f6fd3c72b7b8389969f4fb671ed8532fa9b5bb7a5cae7ca89bc1cea45c7878',
+    'http://curl.haxx.se/download/curl-7.58.0.tar.xz',
+    '6a813875243609eb75f37fa72044e4ad618b55ec15a4eafdac2df6a7e800e3e3',
     'lib/libcurl.a',
     [
         '--disable-shared', '--enable-static',
@@ -114,16 +353,19 @@ curl = AutotoolsProject(
         '--disable-ldap', '--disable-ldaps',
         '--disable-rtsp', '--disable-proxy', '--disable-dict', '--disable-telnet',
         '--disable-tftp', '--disable-pop3', '--disable-imap', '--disable-smtp',
+        '--disable-smb',
         '--disable-gopher',
         '--disable-manual',
         '--disable-threaded-resolver', '--disable-verbose', '--disable-sspi',
         '--disable-crypto-auth', '--disable-ntlm-wb', '--disable-tls-srp', '--disable-cookies',
         '--without-ssl', '--without-gnutls', '--without-nss', '--without-libssh2',
     ],
+
+    patches='src/lib/curl/patches',
 )
 
 boost = BoostProject(
-    'http://downloads.sourceforge.net/project/boost/boost/1.65.1/boost_1_65_1.tar.bz2',
-    '9807a5d16566c57fd74fb522764e0b134a8bbe6b6e8967b83afefd30dcd3be81',
+    'http://downloads.sourceforge.net/project/boost/boost/1.66.0/boost_1_66_0.tar.bz2',
+    '5721818253e6a0989583192f96782c4a98eb6204965316df9f5ad75819225ca9',
     'include/boost/version.hpp',
 )
diff --git a/python/build/makeproject.py b/python/build/makeproject.py
new file mode 100644
index 0000000..fe676bf
--- /dev/null
+++ b/python/build/makeproject.py
@@ -0,0 +1,28 @@
+import subprocess
+
+from build.project import Project
+
+class MakeProject(Project):
+    def __init__(self, url, md5, installed,
+                 install_target='install',
+                 **kwargs):
+        Project.__init__(self, url, md5, installed, **kwargs)
+        self.install_target = install_target
+
+    def get_simultaneous_jobs(self):
+        return 12
+
+    def get_make_args(self, toolchain):
+        return ['--quiet', '-j' + str(self.get_simultaneous_jobs())]
+
+    def get_make_install_args(self, toolchain):
+        return ['--quiet', self.install_target]
+
+    def make(self, toolchain, wd, args):
+        subprocess.check_call(['/usr/bin/make'] + args,
+                              cwd=wd, env=toolchain.env)
+
+    def build(self, toolchain, wd, install=True):
+        self.make(toolchain, wd, self.get_make_args(toolchain))
+        if install:
+            self.make(toolchain, wd, self.get_make_install_args(toolchain))
diff --git a/python/build/project.py b/python/build/project.py
index c79c4f5..b78c892 100644
--- a/python/build/project.py
+++ b/python/build/project.py
@@ -3,10 +3,13 @@ import re
 
 from build.download import download_and_verify
 from build.tar import untar
+from build.quilt import push_all
 
 class Project:
     def __init__(self, url, md5, installed, name=None, version=None,
                  base=None,
+                 patches=None,
+                 edits=None,
                  use_cxx=False):
         if base is None:
             basename = os.path.basename(url)
@@ -17,7 +20,7 @@ class Project:
             self.base = base
 
         if name is None or version is None:
-            m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?)$', self.base)
+            m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?[\d.]*)$', self.base)
             if name is None: name = m.group(1)
             if version is None: version = m.group(2)
 
@@ -28,6 +31,11 @@ class Project:
         self.md5 = md5
         self.installed = installed
 
+        if patches is not None:
+            srcdir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
+            patches = os.path.join(srcdir, patches)
+        self.patches = patches
+        self.edits = edits
         self.use_cxx = use_cxx
 
     def download(self, toolchain):
@@ -47,7 +55,21 @@ class Project:
             parent_path = toolchain.src_path
         else:
             parent_path = toolchain.build_path
-        return untar(self.download(toolchain), parent_path, self.base)
+        path = untar(self.download(toolchain), parent_path, self.base)
+
+        if self.patches is not None:
+            push_all(toolchain, path, self.patches)
+
+        if self.edits is not None:
+            for filename, function in self.edits.items():
+                with open(os.path.join(path, filename), 'r+t') as f:
+                    old_data = f.read()
+                    new_data = function(old_data)
+                    f.seek(0)
+                    f.truncate(0)
+                    f.write(new_data)
+
+        return path
 
     def make_build_path(self, toolchain):
         path = os.path.join(toolchain.build_path, self.base)
diff --git a/python/build/quilt.py b/python/build/quilt.py
new file mode 100644
index 0000000..876453d
--- /dev/null
+++ b/python/build/quilt.py
@@ -0,0 +1,9 @@
+import subprocess
+
+def run_quilt(toolchain, cwd, patches_path, *args):
+    env = dict(toolchain.env)
+    env['QUILT_PATCHES'] = patches_path
+    subprocess.check_call(['quilt'] + list(args), cwd=cwd, env=env)
+
+def push_all(toolchain, src_path, patches_path):
+    run_quilt(toolchain, src_path, patches_path, 'push', '-a')
diff --git a/src/IOThread.cxx b/src/IOThread.cxx
index a83c125..0a8e0e2 100644
--- a/src/IOThread.cxx
+++ b/src/IOThread.cxx
@@ -27,12 +27,17 @@
 
 #include <assert.h>
 
-static struct {
+static struct IOThread {
 	Mutex mutex;
 	Cond cond;
 
 	EventLoop *loop;
 	Thread thread;
+
+	IOThread():thread(BIND_THIS_METHOD(Run)) {}
+
+private:
+	void Run() noexcept;
 } io;
 
 void
@@ -44,15 +49,15 @@ io_thread_run(void)
 	io.loop->Run();
 }
 
-static void
-io_thread_func(gcc_unused void *arg)
+inline void
+IOThread::Run() noexcept
 {
 	SetThreadName("io");
 
 	/* lock+unlock to synchronize with io_thread_start(), to be
 	   sure that io.thread is set */
-	io.mutex.lock();
-	io.mutex.unlock();
+	mutex.lock();
+	mutex.unlock();
 
 	io_thread_run();
 }
@@ -73,7 +78,7 @@ io_thread_start()
 	assert(!io.thread.IsDefined());
 
 	const std::lock_guard<Mutex> protect(io.mutex);
-	io.thread.Start(io_thread_func, nullptr);
+	io.thread.Start();
 }
 
 void
@@ -103,8 +108,12 @@ io_thread_get() noexcept
 	return *io.loop;
 }
 
+#ifndef NDEBUG
+
 bool
 io_thread_inside() noexcept
 {
 	return io.thread.IsInside();
 }
+
+#endif
diff --git a/src/IOThread.hxx b/src/IOThread.hxx
index 63cbbee..341c82a 100644
--- a/src/IOThread.hxx
+++ b/src/IOThread.hxx
@@ -20,6 +20,7 @@
 #ifndef MPD_IO_THREAD_HXX
 #define MPD_IO_THREAD_HXX
 
+#include "check.h"
 #include "Compiler.h"
 
 class EventLoop;
@@ -53,6 +54,8 @@ gcc_const
 EventLoop &
 io_thread_get() noexcept;
 
+#ifndef NDEBUG
+
 /**
  * Is the current thread the I/O thread?
  */
@@ -61,3 +64,5 @@ bool
 io_thread_inside() noexcept;
 
 #endif
+
+#endif
diff --git a/src/MusicPipe.hxx b/src/MusicPipe.hxx
index f9e4232..ecb9234 100644
--- a/src/MusicPipe.hxx
+++ b/src/MusicPipe.hxx
@@ -95,6 +95,7 @@ public:
 	 */
 	gcc_pure
 	const MusicChunk *Peek() const noexcept {
+		const std::lock_guard<Mutex> protect(mutex);
 		return head;
 	}
 
@@ -120,6 +121,7 @@ public:
 	 */
 	gcc_pure
 	unsigned GetSize() const noexcept {
+		const std::lock_guard<Mutex> protect(mutex);
 		return size;
 	}
 
diff --git a/src/SongFilter.cxx b/src/SongFilter.cxx
index 4c3e746..c0e0a2e 100644
--- a/src/SongFilter.cxx
+++ b/src/SongFilter.cxx
@@ -24,6 +24,8 @@
 #include "tag/Tag.hxx"
 #include "util/ConstBuffer.hxx"
 #include "util/StringAPI.hxx"
+#include "util/StringCompare.hxx"
+#include "util/StringView.hxx"
 #include "util/ASCII.hxx"
 #include "util/TimeParser.hxx"
 #include "util/UriUtil.hxx"
@@ -59,7 +61,7 @@ locate_parse_type(const char *str) noexcept
 
 SongFilter::Item::Item(unsigned _tag, const char *_value, bool _fold_case)
 	:tag(_tag),
-	 value(AllocatedString<>::Duplicate(_value)),
+	 value(_value),
 	 fold_case(_fold_case ? IcuCompare(value.c_str()) : IcuCompare())
 {
 }
@@ -274,3 +276,33 @@ SongFilter::GetBase() const noexcept
 
 	return nullptr;
 }
+
+SongFilter
+SongFilter::WithoutBasePrefix(const char *_prefix) const noexcept
+{
+	const StringView prefix(_prefix);
+	SongFilter result;
+
+	for (const auto &i : items) {
+		if (i.GetTag() == LOCATE_TAG_BASE_TYPE) {
+			const char *s = StringAfterPrefix(i.GetValue(), prefix);
+			if (s != nullptr) {
+				if (*s == 0)
+					continue;
+
+				if (*s == '/') {
+					++s;
+
+					if (*s != 0)
+						result.items.emplace_back(LOCATE_TAG_BASE_TYPE, s);
+
+					continue;
+				}
+			}
+		}
+
+		result.items.emplace_back(i);
+	}
+
+	return result;
+}
diff --git a/src/SongFilter.hxx b/src/SongFilter.hxx
index ec83178..f0fa736 100644
--- a/src/SongFilter.hxx
+++ b/src/SongFilter.hxx
@@ -21,9 +21,9 @@
 #define MPD_SONG_FILTER_HXX
 
 #include "lib/icu/Compare.hxx"
-#include "util/AllocatedString.hxx"
 #include "Compiler.h"
 
+#include <string>
 #include <list>
 
 #include <stdint.h>
@@ -49,7 +49,7 @@ public:
 	class Item {
 		uint8_t tag;
 
-		AllocatedString<> value;
+		std::string value;
 
 		/**
 		 * This value is only set if case folding is enabled.
@@ -66,11 +66,6 @@ public:
 		Item(unsigned tag, const char *value, bool fold_case=false);
 		Item(unsigned tag, time_t time);
 
-		Item(const Item &other) = delete;
-		Item(Item &&) = default;
-
-		Item &operator=(const Item &other) = delete;
-
 		unsigned GetTag() const {
 			return tag;
 		}
@@ -157,6 +152,13 @@ public:
 	 */
 	gcc_pure
 	const char *GetBase() const noexcept;
+
+	/**
+	 * Create a copy of the filter with the given prefix stripped
+	 * from all #LOCATE_TAG_BASE_TYPE items.  This is used to
+	 * filter songs in mounted databases.
+	 */
+	SongFilter WithoutBasePrefix(const char *prefix) const noexcept;
 };
 
 /**
diff --git a/src/SongSave.cxx b/src/SongSave.cxx
index ace38d4..ed5ab6d 100644
--- a/src/SongSave.cxx
+++ b/src/SongSave.cxx
@@ -28,6 +28,7 @@
 #include "tag/TagBuilder.hxx"
 #include "util/StringUtil.hxx"
 #include "util/RuntimeError.hxx"
+#include "util/NumberParser.hxx"
 
 #include <string.h>
 #include <stdlib.h>
@@ -94,7 +95,7 @@ song_load(TextFile &file, const char *uri)
 		if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) {
 			tag.AddItem(type, value);
 		} else if (strcmp(line, "Time") == 0) {
-			tag.SetDuration(SignedSongTime::FromS(atof(value)));
+			tag.SetDuration(SignedSongTime::FromS(ParseDouble(value)));
 		} else if (strcmp(line, "Playlist") == 0) {
 			tag.SetHasPlaylist(strcmp(value, "yes") == 0);
 		} else if (strcmp(line, SONG_MTIME) == 0) {
diff --git a/src/archive/plugins/Iso9660ArchivePlugin.cxx b/src/archive/plugins/Iso9660ArchivePlugin.cxx
index 536745d..3639215 100644
--- a/src/archive/plugins/Iso9660ArchivePlugin.cxx
+++ b/src/archive/plugins/Iso9660ArchivePlugin.cxx
@@ -115,7 +115,12 @@ Iso9660ArchiveFile::Visit(char *path, size_t length, size_t capacity,
 			visitor.VisitArchiveEntry(path + 1);
 		}
 	}
+
+#if LIBCDIO_VERSION_NUM >= 20000
+	iso9660_filelist_free(entlist);
+#else
 	_cdio_list_free (entlist, true);
+#endif
 }
 
 static ArchiveFile *
diff --git a/src/db/plugins/simple/Mount.cxx b/src/db/plugins/simple/Mount.cxx
index c07b67a..22db95f 100644
--- a/src/db/plugins/simple/Mount.cxx
+++ b/src/db/plugins/simple/Mount.cxx
@@ -20,18 +20,12 @@
 #include "config.h"
 #include "Mount.hxx"
 #include "PrefixedLightSong.hxx"
+#include "SongFilter.hxx"
 #include "db/Selection.hxx"
 #include "db/LightDirectory.hxx"
 #include "db/Interface.hxx"
 #include "fs/Traits.hxx"
 
-#ifdef _LIBCPP_VERSION
-/* workaround for "error: incomplete type 'PlaylistInfo' used in type
-   trait expression" with libc++ version 3900 (from Android NDK
-   r13b) */
-#include "db/PlaylistInfo.hxx"
-#endif
-
 #include <string>
 
 struct PrefixedLightDirectory : LightDirectory {
@@ -93,5 +87,16 @@ WalkMount(const char *base, const Database &db,
 		vp = std::bind(PrefixVisitPlaylist,
 			       base, std::ref(visit_playlist), _1, _2);
 
+	SongFilter prefix_filter;
+
+	if (base != nullptr && filter != nullptr) {
+		/* if the SongFilter contains a LOCATE_TAG_BASE_TYPE
+		   item, copy the SongFilter and drop the mount point
+		   from the filter, because the mounted database
+		   doesn't know its own location within MPD's VFS */
+		prefix_filter = filter->WithoutBasePrefix(base);
+		filter = &prefix_filter;
+	}
+
 	db.Visit(DatabaseSelection(uri, recursive, filter), vd, vs, vp);
 }
diff --git a/src/db/update/Queue.hxx b/src/db/update/Queue.hxx
index 90373aa..63a7acb 100644
--- a/src/db/update/Queue.hxx
+++ b/src/db/update/Queue.hxx
@@ -49,6 +49,10 @@ struct UpdateQueueItem {
 	bool IsDefined() const {
 		return id != 0;
 	}
+
+	void Clear() {
+		id = 0;
+	}
 };
 
 class UpdateQueue {
diff --git a/src/db/update/Service.cxx b/src/db/update/Service.cxx
index 2e7fe72..eee89c4 100644
--- a/src/db/update/Service.cxx
+++ b/src/db/update/Service.cxx
@@ -29,6 +29,7 @@
 #include "Idle.hxx"
 #include "Log.hxx"
 #include "thread/Thread.hxx"
+#include "thread/Name.hxx"
 #include "thread/Util.hxx"
 
 #ifndef NDEBUG
@@ -43,6 +44,7 @@ UpdateService::UpdateService(EventLoop &_loop, SimpleDatabase &_db,
 	:DeferredMonitor(_loop),
 	 db(_db), storage(_storage),
 	 listener(_listener),
+	 update_thread(BIND_THIS_METHOD(Task)),
 	 update_task_id(0),
 	 walk(nullptr)
 {
@@ -112,6 +114,8 @@ UpdateService::Task()
 {
 	assert(walk != nullptr);
 
+	SetThreadName("update");
+
 	if (!next.path_utf8.empty())
 		FormatDebug(update_domain, "starting: %s",
 			    next.path_utf8.c_str());
@@ -141,13 +145,6 @@ UpdateService::Task()
 }
 
 void
-UpdateService::Task(void *ctx)
-{
-	UpdateService &service = *(UpdateService *)ctx;
-	return service.Task();
-}
-
-void
 UpdateService::StartThread(UpdateQueueItem &&i)
 {
 	assert(GetEventLoop().IsInsideOrNull());
@@ -158,7 +155,7 @@ UpdateService::StartThread(UpdateQueueItem &&i)
 	next = std::move(i);
 	walk = new UpdateWalk(GetEventLoop(), listener, *next.storage);
 
-	update_thread.Start(Task, this);
+	update_thread.Start();
 
 	FormatDebug(update_domain,
 		    "spawned thread for update job id %i", next.id);
@@ -258,7 +255,7 @@ UpdateService::RunDeferred()
 	delete walk;
 	walk = nullptr;
 
-	next = UpdateQueueItem();
+	next.Clear();
 
 	idle_add(IDLE_UPDATE);
 
diff --git a/src/db/update/Service.hxx b/src/db/update/Service.hxx
index 9bd2c34..4ca38c5 100644
--- a/src/db/update/Service.hxx
+++ b/src/db/update/Service.hxx
@@ -98,7 +98,6 @@ private:
 
 	/* the update thread */
 	void Task();
-	static void Task(void *ctx);
 
 	void StartThread(UpdateQueueItem &&i);
 
diff --git a/src/decoder/DecoderControl.cxx b/src/decoder/DecoderControl.cxx
index 7a271b2..ef9b0bd 100644
--- a/src/decoder/DecoderControl.cxx
+++ b/src/decoder/DecoderControl.cxx
@@ -30,7 +30,8 @@
 DecoderControl::DecoderControl(Mutex &_mutex, Cond &_client_cond,
 			       const AudioFormat _configured_audio_format,
 			       const ReplayGainConfig &_replay_gain_config)
-	:mutex(_mutex), client_cond(_client_cond),
+	:thread(BIND_THIS_METHOD(RunThread)),
+	 mutex(_mutex), client_cond(_client_cond),
 	 configured_audio_format(_configured_audio_format),
 	 replay_gain_config(_replay_gain_config) {}
 
diff --git a/src/decoder/DecoderControl.hxx b/src/decoder/DecoderControl.hxx
index cc858c9..dfd8232 100644
--- a/src/decoder/DecoderControl.hxx
+++ b/src/decoder/DecoderControl.hxx
@@ -415,6 +415,9 @@ public:
 	 * mixramp_start/mixramp_end.
 	 */
 	void CycleMixRamp();
+
+private:
+	void RunThread();
 };
 
 #endif
diff --git a/src/decoder/DecoderThread.cxx b/src/decoder/DecoderThread.cxx
index 7d4a915..50dace3 100644
--- a/src/decoder/DecoderThread.cxx
+++ b/src/decoder/DecoderThread.cxx
@@ -513,30 +513,28 @@ try {
 	dc.client_cond.signal();
 }
 
-static void
-decoder_task(void *arg)
+void
+DecoderControl::RunThread()
 {
-	DecoderControl &dc = *(DecoderControl *)arg;
-
 	SetThreadName("decoder");
 
-	const std::lock_guard<Mutex> protect(dc.mutex);
+	const std::lock_guard<Mutex> protect(mutex);
 
 	do {
-		assert(dc.state == DecoderState::STOP ||
-		       dc.state == DecoderState::ERROR);
+		assert(state == DecoderState::STOP ||
+		       state == DecoderState::ERROR);
 
-		switch (dc.command) {
+		switch (command) {
 		case DecoderCommand::START:
-			dc.CycleMixRamp();
-			dc.replay_gain_prev_db = dc.replay_gain_db;
-			dc.replay_gain_db = 0;
+			CycleMixRamp();
+			replay_gain_prev_db = replay_gain_db;
+			replay_gain_db = 0;
 
-			decoder_run(dc);
+			decoder_run(*this);
 
-			if (dc.state == DecoderState::ERROR) {
+			if (state == DecoderState::ERROR) {
 				try {
-					std::rethrow_exception(dc.error);
+					std::rethrow_exception(error);
 				} catch (const std::exception &e) {
 					LogError(e);
 				} catch (...) {
@@ -552,20 +550,20 @@ decoder_task(void *arg)
 			/* we need to clear the pipe here; usually the
 			   PlayerThread is responsible, but it is not
 			   aware that the decoder has finished */
-			dc.pipe->Clear(*dc.buffer);
+			pipe->Clear(*buffer);
 
-			decoder_run(dc);
+			decoder_run(*this);
 			break;
 
 		case DecoderCommand::STOP:
-			dc.CommandFinishedLocked();
+			CommandFinishedLocked();
 			break;
 
 		case DecoderCommand::NONE:
-			dc.Wait();
+			Wait();
 			break;
 		}
-	} while (dc.command != DecoderCommand::NONE || !dc.quit);
+	} while (command != DecoderCommand::NONE || !quit);
 }
 
 void
@@ -574,5 +572,5 @@ decoder_thread_start(DecoderControl &dc)
 	assert(!dc.thread.IsDefined());
 
 	dc.quit = false;
-	dc.thread.Start(decoder_task, &dc);
+	dc.thread.Start();
 }
diff --git a/src/event/Loop.cxx b/src/event/Loop.cxx
index ada820c..9e09534 100644
--- a/src/event/Loop.cxx
+++ b/src/event/Loop.cxx
@@ -27,7 +27,7 @@
 #include <algorithm>
 
 EventLoop::EventLoop()
-	:SocketMonitor(*this)
+	:SocketMonitor(*this), quit(false)
 {
 	SocketMonitor::Open(wake_fd.Get());
 	SocketMonitor::Schedule(SocketMonitor::READ);
@@ -46,7 +46,9 @@ EventLoop::~EventLoop()
 void
 EventLoop::Break()
 {
-	quit = true;
+	if (quit.exchange(true))
+		return;
+
 	wake_fd.Write();
 }
 
diff --git a/src/event/Loop.hxx b/src/event/Loop.hxx
index 1060a87..cf2ec33 100644
--- a/src/event/Loop.hxx
+++ b/src/event/Loop.hxx
@@ -30,6 +30,7 @@
 #include "SocketMonitor.hxx"
 
 #include <chrono>
+#include <atomic>
 #include <list>
 #include <set>
 
@@ -82,7 +83,7 @@ class EventLoop final : SocketMonitor
 
 	std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
 
-	bool quit = false;
+	std::atomic_bool quit;
 
 	/**
 	 * True when the object has been modified and another check is
diff --git a/src/filter/Observer.cxx b/src/filter/Observer.cxx
index 5d6602d..d7835a7 100644
--- a/src/filter/Observer.cxx
+++ b/src/filter/Observer.cxx
@@ -73,6 +73,10 @@ public:
 		return filter;
 	}
 
+	void Reset() override {
+		filter->Reset();
+	}
+
 	ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override {
 		return filter->FilterPCM(src);
 	}
diff --git a/src/filter/plugins/ConvertFilterPlugin.cxx b/src/filter/plugins/ConvertFilterPlugin.cxx
index 1ede2c0..cec9522 100644
--- a/src/filter/plugins/ConvertFilterPlugin.cxx
+++ b/src/filter/plugins/ConvertFilterPlugin.cxx
@@ -53,10 +53,16 @@ public:
 	void Set(const AudioFormat &_out_audio_format);
 
 	void Reset() override {
-		state.Reset();
+		if (IsActive())
+			state.Reset();
 	}
 
 	ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
+
+private:
+	bool IsActive() const noexcept {
+		return out_audio_format != in_audio_format;
+	}
 };
 
 class PreparedConvertFilter final : public PreparedFilter {
@@ -80,7 +86,7 @@ ConvertFilter::Set(const AudioFormat &_out_audio_format)
 		/* no change */
 		return;
 
-	if (out_audio_format != in_audio_format) {
+	if (IsActive()) {
 		out_audio_format = in_audio_format;
 		state.Close();
 	}
@@ -111,7 +117,7 @@ ConvertFilter::~ConvertFilter()
 {
 	assert(in_audio_format.IsValid());
 
-	if (out_audio_format != in_audio_format)
+	if (IsActive())
 		state.Close();
 }
 
@@ -120,11 +126,10 @@ ConvertFilter::FilterPCM(ConstBuffer<void> src)
 {
 	assert(in_audio_format.IsValid());
 
-	if (out_audio_format == in_audio_format)
+	return IsActive()
+		? state.Convert(src)
 		/* optimized special case: no-op */
-		return src;
-
-	return state.Convert(src);
+		: src;
 }
 
 const FilterPlugin convert_filter_plugin = {
diff --git a/src/input/ThreadInputStream.cxx b/src/input/ThreadInputStream.cxx
index 2463c7d..88251a7 100644
--- a/src/input/ThreadInputStream.cxx
+++ b/src/input/ThreadInputStream.cxx
@@ -54,10 +54,10 @@ ThreadInputStream::Start()
 	assert(p != nullptr);
 
 	buffer = new CircularBuffer<uint8_t>((uint8_t *)p, buffer_size);
-	thread.Start(ThreadFunc, this);
+	thread.Start();
 }
 
-inline void
+void
 ThreadInputStream::ThreadFunc()
 {
 	FormatThreadName("input:%s", plugin);
@@ -108,13 +108,6 @@ ThreadInputStream::ThreadFunc()
 }
 
 void
-ThreadInputStream::ThreadFunc(void *ctx)
-{
-	ThreadInputStream &tis = *(ThreadInputStream *)ctx;
-	tis.ThreadFunc();
-}
-
-void
 ThreadInputStream::Check()
 {
 	assert(!thread.IsInside());
diff --git a/src/input/ThreadInputStream.hxx b/src/input/ThreadInputStream.hxx
index 021bf6e..046e082 100644
--- a/src/input/ThreadInputStream.hxx
+++ b/src/input/ThreadInputStream.hxx
@@ -73,6 +73,7 @@ public:
 			  size_t _buffer_size)
 		:InputStream(_uri, _mutex, _cond),
 		 plugin(_plugin),
+		 thread(BIND_THIS_METHOD(ThreadFunc)),
 		 buffer_size(_buffer_size) {}
 
 	virtual ~ThreadInputStream();
@@ -138,7 +139,6 @@ protected:
 
 private:
 	void ThreadFunc();
-	static void ThreadFunc(void *ctx);
 };
 
 #endif
diff --git a/src/input/plugins/FileInputPlugin.cxx b/src/input/plugins/FileInputPlugin.cxx
index 2d2850c..518de23 100644
--- a/src/input/plugins/FileInputPlugin.cxx
+++ b/src/input/plugins/FileInputPlugin.cxx
@@ -65,10 +65,13 @@ OpenFileInputStream(Path path,
 		throw FormatRuntimeError("Not a regular file: %s",
 					 path.c_str());
 
+#if !defined(__BIONIC__) || __ANDROID_API__ >= 21
+	/* posix_fadvise() requires Android API 21 */
 #ifdef POSIX_FADV_SEQUENTIAL
 	posix_fadvise(reader.GetFD().Get(), (off_t)0, info.GetSize(),
 		      POSIX_FADV_SEQUENTIAL);
 #endif
+#endif
 
 	return InputStreamPtr(new FileInputStream(path.ToUTF8().c_str(),
 						  std::move(reader), info.GetSize(),
diff --git a/src/lib/icu/Compare.hxx b/src/lib/icu/Compare.hxx
index 9ee2e68..d380265 100644
--- a/src/lib/icu/Compare.hxx
+++ b/src/lib/icu/Compare.hxx
@@ -37,6 +37,18 @@ public:
 
 	explicit IcuCompare(const char *needle) noexcept;
 
+	IcuCompare(const IcuCompare &src) noexcept
+		:needle(src
+			? AllocatedString<>::Duplicate(src.needle.c_str())
+			: nullptr) {}
+
+	IcuCompare &operator=(const IcuCompare &src) noexcept {
+		needle = src
+			? AllocatedString<>::Duplicate(src.needle.c_str())
+			: nullptr;
+		return *this;
+	}
+
 	IcuCompare(IcuCompare &&) = default;
 	IcuCompare &operator=(IcuCompare &&) = default;
 
diff --git a/src/lib/upnp/Init.cxx b/src/lib/upnp/Init.cxx
index ac2167f..5084254 100644
--- a/src/lib/upnp/Init.cxx
+++ b/src/lib/upnp/Init.cxx
@@ -34,7 +34,11 @@ static unsigned upnp_ref;
 static void
 DoInit()
 {
-	auto code = UpnpInit(0, 0);
+#ifdef UPNP_ENABLE_IPV6
+	auto code = UpnpInit2(nullptr, 0);
+#else
+	auto code = UpnpInit(nullptr, 0);
+#endif
 	if (code != UPNP_E_SUCCESS)
 		throw FormatRuntimeError("UpnpInit() failed: %s",
 					 UpnpGetErrorMessage(code));
diff --git a/src/mixer/plugins/volume_mapping.c b/src/mixer/plugins/volume_mapping.c
index 4e559cf..2078d34 100644
--- a/src/mixer/plugins/volume_mapping.c
+++ b/src/mixer/plugins/volume_mapping.c
@@ -139,6 +139,13 @@ static int set_normalized_volume(snd_mixer_elem_t *elem,
 		return set_raw[ctl_dir](elem, value);
 	}
 
+	/* two special cases to avoid rounding errors at 0% and
+	   100% */
+	if (volume <= 0)
+		return set_dB[ctl_dir](elem, min, dir);
+	else if (volume >= 100)
+		return set_dB[ctl_dir](elem, max, dir);
+
 	if (use_linear_dB_scale(min, max)) {
 		value = lrint_dir(volume * (max - min), dir) + min;
 		return set_dB[ctl_dir](elem, value, dir);
diff --git a/src/neighbor/plugins/SmbclientNeighborPlugin.cxx b/src/neighbor/plugins/SmbclientNeighborPlugin.cxx
index 0b831a3..319e880 100644
--- a/src/neighbor/plugins/SmbclientNeighborPlugin.cxx
+++ b/src/neighbor/plugins/SmbclientNeighborPlugin.cxx
@@ -69,7 +69,8 @@ class SmbclientNeighborExplorer final : public NeighborExplorer {
 
 public:
 	SmbclientNeighborExplorer(NeighborListener &_listener)
-		:NeighborExplorer(_listener) {}
+		:NeighborExplorer(_listener),
+		 thread(BIND_THIS_METHOD(ThreadFunc)) {}
 
 	/* virtual methods from class NeighborExplorer */
 	void Open() override;
@@ -79,14 +80,13 @@ public:
 private:
 	void Run();
 	void ThreadFunc();
-	static void ThreadFunc(void *ctx);
 };
 
 void
 SmbclientNeighborExplorer::Open()
 {
 	quit = false;
-	thread.Start(ThreadFunc, this);
+	thread.Start();
 }
 
 void
@@ -239,6 +239,8 @@ SmbclientNeighborExplorer::Run()
 inline void
 SmbclientNeighborExplorer::ThreadFunc()
 {
+	SetThreadName("smbclient");
+
 	mutex.lock();
 
 	while (!quit) {
@@ -257,15 +259,6 @@ SmbclientNeighborExplorer::ThreadFunc()
 	mutex.unlock();
 }
 
-void
-SmbclientNeighborExplorer::ThreadFunc(void *ctx)
-{
-	SetThreadName("smbclient");
-
-	SmbclientNeighborExplorer &e = *(SmbclientNeighborExplorer *)ctx;
-	e.ThreadFunc();
-}
-
 static NeighborExplorer *
 smbclient_neighbor_create(gcc_unused EventLoop &loop,
 			  NeighborListener &listener,
diff --git a/src/output/Init.cxx b/src/output/Init.cxx
index 453200d..34b53d9 100644
--- a/src/output/Init.cxx
+++ b/src/output/Init.cxx
@@ -51,7 +51,8 @@
 
 AudioOutput::AudioOutput(const AudioOutputPlugin &_plugin,
 			 const ConfigBlock &block)
-	:plugin(_plugin)
+	:plugin(_plugin),
+	 thread(BIND_THIS_METHOD(Task))
 {
 	assert(plugin.finish != nullptr);
 	assert(plugin.open != nullptr);
diff --git a/src/output/Internal.hxx b/src/output/Internal.hxx
index 75355e8..4d23b82 100644
--- a/src/output/Internal.hxx
+++ b/src/output/Internal.hxx
@@ -515,7 +515,6 @@ private:
 	 * The OutputThread.
 	 */
 	void Task();
-	static void Task(void *arg);
 };
 
 /**
diff --git a/src/output/OutputThread.cxx b/src/output/OutputThread.cxx
index d3f9c2b..91d61f5 100644
--- a/src/output/OutputThread.cxx
+++ b/src/output/OutputThread.cxx
@@ -396,7 +396,7 @@ AudioOutput::Pause()
 	pause = false;
 }
 
-inline void
+void
 AudioOutput::Task()
 {
 	FormatThreadName("output:%s", name);
@@ -513,16 +513,9 @@ AudioOutput::Task()
 }
 
 void
-AudioOutput::Task(void *arg)
-{
-	AudioOutput *ao = (AudioOutput *)arg;
-	ao->Task();
-}
-
-void
 AudioOutput::StartThread()
 {
 	assert(command == Command::NONE);
 
-	thread.Start(Task, this);
+	thread.Start();
 }
diff --git a/src/output/plugins/HaikuOutputPlugin.cxx b/src/output/plugins/HaikuOutputPlugin.cxx
index 732a909..2487ace 100644
--- a/src/output/plugins/HaikuOutputPlugin.cxx
+++ b/src/output/plugins/HaikuOutputPlugin.cxx
@@ -77,7 +77,6 @@ public:
 	void Close();
 
 	size_t Play(const void *chunk, size_t size);
-	void Cancel();
 
 	std::chrono::steady_clock::duration Delay() noexcept;
 
diff --git a/src/output/plugins/PulseOutputPlugin.cxx b/src/output/plugins/PulseOutputPlugin.cxx
index 01302e5..77c411d 100644
--- a/src/output/plugins/PulseOutputPlugin.cxx
+++ b/src/output/plugins/PulseOutputPlugin.cxx
@@ -27,6 +27,7 @@
 #include "../Wrapper.hxx"
 #include "mixer/MixerList.hxx"
 #include "mixer/plugins/PulseMixerPlugin.hxx"
+#include "util/ScopeExit.hxx"
 #include "Log.hxx"
 
 #include <pulse/thread-mainloop.h>
@@ -854,7 +855,10 @@ PulseOutput::TestDefaultDevice()
 try {
 	const ConfigBlock empty;
 	PulseOutput po(empty);
+	po.Enable();
+	AtScopeExit(&po) { po.Disable(); };
 	po.WaitConnection();
+
 	return true;
 } catch (const std::runtime_error &e) {
 	return false;
diff --git a/src/pcm/Order.cxx b/src/pcm/Order.cxx
index 1812b14..736eb47 100644
--- a/src/pcm/Order.cxx
+++ b/src/pcm/Order.cxx
@@ -88,7 +88,7 @@ static inline ConstBuffer<V>
 ToAlsaChannelOrder71(PcmBuffer &buffer, ConstBuffer<V> src)
 {
 	auto dest = buffer.GetT<V>(src.size);
-	ToAlsaChannelOrder71(dest, src.data, src.size / 6);
+	ToAlsaChannelOrder71(dest, src.data, src.size / 8);
 	return { dest, src.size };
 }
 
diff --git a/src/pcm/SoxrResampler.cxx b/src/pcm/SoxrResampler.cxx
index 621d3f9..868ad59 100644
--- a/src/pcm/SoxrResampler.cxx
+++ b/src/pcm/SoxrResampler.cxx
@@ -139,6 +139,14 @@ SoxrPcmResampler::Close()
 	soxr_delete(soxr);
 }
 
+void
+SoxrPcmResampler::Reset()
+{
+#if SOXR_THIS_VERSION >= SOXR_VERSION(0,1,2)
+	soxr_clear(soxr);
+#endif
+}
+
 ConstBuffer<void>
 SoxrPcmResampler::Resample(ConstBuffer<void> src)
 {
diff --git a/src/pcm/SoxrResampler.hxx b/src/pcm/SoxrResampler.hxx
index 0be8611..9f3114a 100644
--- a/src/pcm/SoxrResampler.hxx
+++ b/src/pcm/SoxrResampler.hxx
@@ -41,6 +41,7 @@ class SoxrPcmResampler final : public PcmResampler {
 public:
 	AudioFormat Open(AudioFormat &af, unsigned new_sample_rate) override;
 	void Close() override;
+	void Reset() override;
 	ConstBuffer<void> Resample(ConstBuffer<void> src) override;
 };
 
diff --git a/src/player/Control.cxx b/src/player/Control.cxx
index b6c3688..013b57b 100644
--- a/src/player/Control.cxx
+++ b/src/player/Control.cxx
@@ -37,6 +37,7 @@ PlayerControl::PlayerControl(PlayerListener &_listener,
 	 buffer_chunks(_buffer_chunks),
 	 buffered_before_play(_buffered_before_play),
 	 configured_audio_format(_configured_audio_format),
+	 thread(BIND_THIS_METHOD(RunThread)),
 	 replay_gain_config(_replay_gain_config)
 {
 }
diff --git a/src/player/Control.hxx b/src/player/Control.hxx
index e5023f0..39e24e4 100644
--- a/src/player/Control.hxx
+++ b/src/player/Control.hxx
@@ -537,6 +537,9 @@ public:
 	void ApplyEnabled() override {
 		LockUpdateAudio();
 	}
+
+private:
+	void RunThread();
 };
 
 #endif
diff --git a/src/player/Thread.cxx b/src/player/Thread.cxx
index 913e541..5888330 100644
--- a/src/player/Thread.cxx
+++ b/src/player/Thread.cxx
@@ -1147,91 +1147,89 @@ do_play(PlayerControl &pc, DecoderControl &dc,
 	player.Run();
 }
 
-static void
-player_task(void *arg)
+void
+PlayerControl::RunThread()
 {
-	PlayerControl &pc = *(PlayerControl *)arg;
-
 	SetThreadName("player");
 
-	DecoderControl dc(pc.mutex, pc.cond,
-			  pc.configured_audio_format,
-			  pc.replay_gain_config);
+	DecoderControl dc(mutex, cond,
+			  configured_audio_format,
+			  replay_gain_config);
 	decoder_thread_start(dc);
 
-	MusicBuffer buffer(pc.buffer_chunks);
+	MusicBuffer buffer(buffer_chunks);
 
-	pc.Lock();
+	Lock();
 
 	while (1) {
-		switch (pc.command) {
+		switch (command) {
 		case PlayerCommand::SEEK:
 		case PlayerCommand::QUEUE:
-			assert(pc.next_song != nullptr);
+			assert(next_song != nullptr);
 
-			pc.Unlock();
-			do_play(pc, dc, buffer);
-			pc.listener.OnPlayerSync();
-			pc.Lock();
+			Unlock();
+			do_play(*this, dc, buffer);
+			listener.OnPlayerSync();
+			Lock();
 			break;
 
 		case PlayerCommand::STOP:
-			pc.Unlock();
-			pc.outputs.Cancel();
-			pc.Lock();
+			Unlock();
+			outputs.Cancel();
+			Lock();
 
 			/* fall through */
 
 		case PlayerCommand::PAUSE:
-			delete pc.next_song;
-			pc.next_song = nullptr;
+			delete next_song;
+			next_song = nullptr;
 
-			pc.CommandFinished();
+			CommandFinished();
 			break;
 
 		case PlayerCommand::CLOSE_AUDIO:
-			pc.Unlock();
+			Unlock();
 
-			pc.outputs.Release();
+			outputs.Release();
 
-			pc.Lock();
-			pc.CommandFinished();
+			Lock();
+			CommandFinished();
 
 			assert(buffer.IsEmptyUnsafe());
 
 			break;
 
 		case PlayerCommand::UPDATE_AUDIO:
-			pc.Unlock();
-			pc.outputs.EnableDisable();
-			pc.Lock();
-			pc.CommandFinished();
+			Unlock();
+			outputs.EnableDisable();
+			Lock();
+			CommandFinished();
 			break;
 
 		case PlayerCommand::EXIT:
-			pc.Unlock();
+			Unlock();
 
 			dc.Quit();
 
-			pc.outputs.Close();
+			outputs.Close();
 
-			pc.LockCommandFinished();
+			LockCommandFinished();
 			return;
 
 		case PlayerCommand::CANCEL:
-			delete pc.next_song;
-			pc.next_song = nullptr;
+			delete next_song;
+			next_song = nullptr;
 
-			pc.CommandFinished();
+			CommandFinished();
 			break;
 
 		case PlayerCommand::REFRESH:
 			/* no-op when not playing */
-			pc.CommandFinished();
+			CommandFinished();
 			break;
 
 		case PlayerCommand::NONE:
-			pc.Wait();
+			Wait();
 			break;
 		}
 	}
@@ -1242,5 +1240,5 @@ StartPlayerThread(PlayerControl &pc)
 {
 	assert(!pc.thread.IsDefined());
 
-	pc.thread.Start(player_task, &pc);
+	pc.thread.Start();
 }
diff --git a/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx b/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx
index a4dcb19..88a9606 100644
--- a/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx
+++ b/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx
@@ -313,7 +313,7 @@ soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond)
 
 	SoundCloudJsonData data;
 	yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, &data);
-	AtScopeExit(hand, &data) { yajl_free(hand); };
+	AtScopeExit(hand) { yajl_free(hand); };
 
 	int ret = soundcloud_parse_json(u, hand, mutex, cond);
 
diff --git a/src/protocol/ArgParser.cxx b/src/protocol/ArgParser.cxx
index 185c578..47fdfa4 100644
--- a/src/protocol/ArgParser.cxx
+++ b/src/protocol/ArgParser.cxx
@@ -21,6 +21,7 @@
 #include "ArgParser.hxx"
 #include "Ack.hxx"
 #include "Chrono.hxx"
+#include "util/NumberParser.hxx"
 
 #include <stdlib.h>
 
@@ -151,7 +152,7 @@ float
 ParseCommandArgFloat(const char *s)
 {
 	char *endptr;
-	auto value = strtof(s, &endptr);
+	auto value = ParseFloat(s, &endptr);
 	if (endptr == s || *endptr != 0)
 		throw FormatProtocolError(ACK_ERROR_ARG,
 					  "Float expected: %s", s);
diff --git a/src/queue/PlaylistControl.cxx b/src/queue/PlaylistControl.cxx
index 018ddcb..c71f29b 100644
--- a/src/queue/PlaylistControl.cxx
+++ b/src/queue/PlaylistControl.cxx
@@ -212,8 +212,6 @@ playlist::SeekSongOrder(PlayerControl &pc, unsigned i, SongTime seek_time)
 {
 	assert(queue.IsValidOrder(i));
 
-	const DetachedSong *queued_song = GetQueuedSong();
-
 	pc.LockClearError();
 	stop_on_error = true;
 	error_count = 0;
@@ -226,8 +224,6 @@ playlist::SeekSongOrder(PlayerControl &pc, unsigned i, SongTime seek_time)
 
 		playing = true;
 		current = i;
-
-		queued_song = nullptr;
 	}
 
 	queued = -1;
@@ -235,7 +231,7 @@ playlist::SeekSongOrder(PlayerControl &pc, unsigned i, SongTime seek_time)
 	try {
 		pc.LockSeek(new DetachedSong(queue.GetOrder(i)), seek_time);
 	} catch (...) {
-		UpdateQueuedSong(pc, queued_song);
+		UpdateQueuedSong(pc, nullptr);
 		throw;
 	}
 
diff --git a/src/queue/PlaylistState.cxx b/src/queue/PlaylistState.cxx
index d08610e..614f01c 100644
--- a/src/queue/PlaylistState.cxx
+++ b/src/queue/PlaylistState.cxx
@@ -35,6 +35,7 @@
 #include "util/CharUtil.hxx"
 #include "util/StringAPI.hxx"
 #include "util/StringCompare.hxx"
+#include "util/NumberParser.hxx"
 #include "Log.hxx"
 
 #include <string.h>
@@ -148,7 +149,7 @@ playlist_state_restore(const char *line, TextFile &file,
 	while ((line = file.ReadLine()) != nullptr) {
 		const char *p;
 		if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_TIME))) {
-			seek_time = SongTime::FromS(atof(p));
+			seek_time = SongTime::FromS(ParseDouble(p));
 		} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_REPEAT))) {
 			playlist.SetRepeat(pc, StringIsEqual(p, "1"));
 		} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_SINGLE))) {
@@ -158,12 +159,12 @@ playlist_state_restore(const char *line, TextFile &file,
 		} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_CROSSFADE))) {
 			pc.SetCrossFade(atoi(p));
 		} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_MIXRAMPDB))) {
-			pc.SetMixRampDb(atof(p));
+			pc.SetMixRampDb(ParseFloat(p));
 		} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY))) {
 			/* this check discards "nan" which was used
 			   prior to MPD 0.18 */
 			if (IsDigitASCII(*p))
-				pc.SetMixRampDelay(atof(p));
+				pc.SetMixRampDelay(ParseFloat(p));
 		} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_RANDOM))) {
 			random_mode = StringIsEqual(p, "1");
 		} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_CURRENT))) {
diff --git a/src/storage/StorageState.cxx b/src/storage/StorageState.cxx
index 721a0ce..1eedda8 100644
--- a/src/storage/StorageState.cxx
+++ b/src/storage/StorageState.cxx
@@ -48,6 +48,9 @@ static constexpr Domain storage_domain("storage");
 void
 storage_state_save(BufferedOutputStream &os, const Instance &instance)
 {
+	if (instance.storage == nullptr)
+		return;
+
 	const auto visitor = [&os](const char *mount_uri, const Storage &storage) {
 		std::string uri = storage.MapUTF8("");
 		if (uri.empty() || StringIsEmpty(mount_uri))
@@ -85,6 +88,12 @@ storage_state_restore(const char *line, TextFile &file, Instance &instance)
 			FormatError(storage_domain, "Unrecognized line in mountpoint state: %s", line);
 	}
 
+	if (instance.storage == nullptr)
+		/* without storage (a CompositeStorage instance), we
+		   cannot mount, and therefore we silently ignore the
+		   state file */
+		return true;
+
 	if (url.empty() || uri.empty()) {
 		LogError(storage_domain, "Missing value in mountpoint state.");	
 		return true;
@@ -99,16 +108,18 @@ storage_state_restore(const char *line, TextFile &file, Instance &instance)
 		return true;
 	}
 
-#ifdef ENABLE_DATABASE
 	Database *db = instance.database;
 	if (db != nullptr && db->IsPlugin(simple_db_plugin)) {
 		try {
 			((SimpleDatabase *)db)->Mount(uri.c_str(), url.c_str());
 		} catch (...) {
-			throw;
+			delete storage;
+			FormatError(std::current_exception(),
+				    "Failed to restore mount to %s",
+				    url.c_str());
+			return true;
 		}
 	}
-#endif
 
 	((CompositeStorage*)instance.storage)->Mount(uri.c_str(), storage);
 
@@ -118,6 +129,9 @@ storage_state_restore(const char *line, TextFile &file, Instance &instance)
 unsigned
 storage_state_get_hash(const Instance &instance)
 {
+	if (instance.storage == nullptr)
+		return 0;
+
 	std::set<std::string> mounts;
 
 	const auto visitor = [&mounts](const char *mount_uri, const Storage &storage) {
diff --git a/src/tag/ReplayGain.cxx b/src/tag/ReplayGain.cxx
index 10f2f8a..ccdcf3f 100644
--- a/src/tag/ReplayGain.cxx
+++ b/src/tag/ReplayGain.cxx
@@ -22,6 +22,7 @@
 #include "VorbisComment.hxx"
 #include "ReplayGainInfo.hxx"
 #include "util/ASCII.hxx"
+#include "util/NumberParser.hxx"
 
 #include <assert.h>
 #include <stdlib.h>
@@ -33,16 +34,16 @@ ParseReplayGainTagTemplate(ReplayGainInfo &info, const T t)
 	const char *value;
 
 	if ((value = t["replaygain_track_gain"]) != nullptr) {
-		info.track.gain = atof(value);
+		info.track.gain = ParseFloat(value);
 		return true;
 	} else if ((value = t["replaygain_album_gain"]) != nullptr) {
-		info.album.gain = atof(value);
+		info.album.gain = ParseFloat(value);
 		return true;
 	} else if ((value = t["replaygain_track_peak"]) != nullptr) {
-		info.track.peak = atof(value);
+		info.track.peak = ParseFloat(value);
 		return true;
 	} else if ((value = t["replaygain_album_peak"]) != nullptr) {
-		info.album.peak = atof(value);
+		info.album.peak = ParseFloat(value);
 		return true;
 	} else
 		return false;
diff --git a/src/thread/Id.hxx b/src/thread/Id.hxx
index b0bdcb4..b4ae741 100644
--- a/src/thread/Id.hxx
+++ b/src/thread/Id.hxx
@@ -52,13 +52,11 @@ public:
 	constexpr ThreadId(pthread_t _id):id(_id) {}
 #endif
 
-	gcc_const
-	static ThreadId Null() noexcept {
+	static constexpr ThreadId Null() noexcept {
 #ifdef _WIN32
 		return 0;
 #else
-		static ThreadId null;
-		return null;
+		return pthread_t();
 #endif
 	}
 
@@ -81,11 +79,13 @@ public:
 
 	gcc_pure
 	bool operator==(const ThreadId &other) const noexcept {
-#ifdef _WIN32
+		/* note: not using pthread_equal() because that
+		   function "is undefined if either thread ID is not
+		   valid so we can't safely use it on
+		   default-constructed values" (comment from
+		   libstdc++) - and if both libstdc++ and libc++ get
+		   away with this, we can do it as well */
 		return id == other.id;
-#else
-		return pthread_equal(id, other.id);
-#endif
 	}
 
 	/**
diff --git a/src/thread/Thread.cxx b/src/thread/Thread.cxx
index 06c2f45..21aec11 100644
--- a/src/thread/Thread.cxx
+++ b/src/thread/Thread.cxx
@@ -25,39 +25,21 @@
 #include "java/Global.hxx"
 #endif
 
-bool
-Thread::Start(void (*_f)(void *ctx), void *_ctx)
+void
+Thread::Start()
 {
 	assert(!IsDefined());
 
-	f = _f;
-	ctx = _ctx;
-
 #ifdef _WIN32
 	handle = ::CreateThread(nullptr, 0, ThreadProc, this, 0, &id);
 	if (handle == nullptr)
 		throw MakeLastError("Failed to create thread");
 #else
-#ifndef NDEBUG
-	creating = true;
-#endif
-
 	int e = pthread_create(&handle, nullptr, ThreadProc, this);
 
-	if (e != 0) {
-#ifndef NDEBUG
-		creating = false;
-#endif
+	if (e != 0)
 		throw MakeErrno(e, "Failed to create thread");
-	}
-
-	defined = true;
-#ifndef NDEBUG
-	creating = false;
 #endif
-#endif
-
-	return true;
 }
 
 void
@@ -72,7 +54,17 @@ Thread::Join()
 	handle = nullptr;
 #else
 	pthread_join(handle, nullptr);
-	defined = false;
+	handle = pthread_t();
+#endif
+}
+
+inline void
+Thread::Run()
+{
+	f();
+
+#ifdef ANDROID
+	Java::DetachCurrentThread();
 #endif
 }
 
@@ -83,7 +75,7 @@ Thread::ThreadProc(LPVOID ctx)
 {
 	Thread &thread = *(Thread *)ctx;
 
-	thread.f(thread.ctx);
+	thread.Run();
 	return 0;
 }
 
@@ -95,18 +87,10 @@ Thread::ThreadProc(void *ctx)
 	Thread &thread = *(Thread *)ctx;
 
 #ifndef NDEBUG
-	/* this works around a race condition that causes an assertion
-	   failure due to IsInside() spuriously returning false right
-	   after the thread has been created, and the calling thread
-	   hasn't initialised "defined" yet */
-	thread.defined = true;
+	thread.inside_handle = pthread_self();
 #endif
 
-	thread.f(thread.ctx);
-
-#ifdef ANDROID
-	Java::DetachCurrentThread();
-#endif
+	thread.Run();
 
 	return nullptr;
 }
diff --git a/src/thread/Thread.hxx b/src/thread/Thread.hxx
index 5d1bb6a..45250c5 100644
--- a/src/thread/Thread.hxx
+++ b/src/thread/Thread.hxx
@@ -21,6 +21,7 @@
 #define MPD_THREAD_HXX
 
 #include "check.h"
+#include "util/BindMethod.hxx"
 #include "Compiler.h"
 
 #ifdef _WIN32
@@ -32,28 +33,28 @@
 #include <assert.h>
 
 class Thread {
+	typedef BoundMethod<void()> Function;
+	const Function f;
+
 #ifdef _WIN32
 	HANDLE handle = nullptr;
 	DWORD id;
 #else
-	pthread_t handle;
-	bool defined = false;
+	pthread_t handle = pthread_t();
 
 #ifndef NDEBUG
 	/**
-	 * The thread is currently being created.  This is a workaround for
-	 * IsInside(), which may return false until pthread_create() has
-	 * initialised the #handle.
+	 * This handle is only used by IsInside(), and is set by the
+	 * thread function.  Since #handle is set by pthread_create()
+	 * which is racy, we need this attribute for early checks
+	 * inside the thread function.
 	 */
-	bool creating = false;
+	pthread_t inside_handle = pthread_t();
 #endif
 #endif
 
-	void (*f)(void *ctx);
-	void *ctx;
-
 public:
-	Thread() = default;
+	explicit Thread(Function _f):f(_f) {}
 
 	Thread(const Thread &) = delete;
 
@@ -69,10 +70,11 @@ public:
 #ifdef _WIN32
 		return handle != nullptr;
 #else
-		return defined;
+		return handle != pthread_t();
 #endif
-  }
+	}
 
+#ifndef NDEBUG
 	/**
 	 * Check if this thread is the current thread.
 	 */
@@ -81,18 +83,23 @@ public:
 #ifdef _WIN32
 		return GetCurrentThreadId() == id;
 #else
-#ifdef NDEBUG
-		constexpr bool creating = false;
-#endif
-		return IsDefined() && (creating ||
-				       pthread_equal(pthread_self(), handle));
+		/* note: not using pthread_equal() because that
+		   function "is undefined if either thread ID is not
+		   valid so we can't safely use it on
+		   default-constructed values" (comment from
+		   libstdc++) - and if both libstdc++ and libc++ get
+		   away with this, we can do it as well */
+		return pthread_self() == inside_handle;
 #endif
 	}
+#endif
 
-	bool Start(void (*f)(void *ctx), void *ctx);
+	void Start();
 	void Join();
 
 private:
+	void Run();
+
 #ifdef _WIN32
 	static DWORD WINAPI ThreadProc(LPVOID ctx);
 #else
diff --git a/src/thread/Util.cxx b/src/thread/Util.cxx
index 6213d97..0dbf818 100644
--- a/src/thread/Util.cxx
+++ b/src/thread/Util.cxx
@@ -40,8 +40,10 @@
 
 #ifdef __linux__
 
+#ifndef ANDROID
+
 static int
-ioprio_set(int which, int who, int ioprio)
+linux_ioprio_set(int which, int who, int ioprio)
 {
 	return syscall(__NR_ioprio_set, which, who, ioprio);
 }
@@ -55,7 +57,20 @@ ioprio_set_idle()
 	static constexpr int _IOPRIO_IDLE =
 		(_IOPRIO_CLASS_IDLE << _IOPRIO_CLASS_SHIFT) | 7;
 
-	ioprio_set(_IOPRIO_WHO_PROCESS, 0, _IOPRIO_IDLE);
+	linux_ioprio_set(_IOPRIO_WHO_PROCESS, 0, _IOPRIO_IDLE);
+}
+
+#endif /* !ANDROID */
+
+/**
+ * Wrapper for the "sched_setscheduler" system call.  We don't use the
+ * one from the C library because Musl has an intentionally broken
+ * implementation.
+ */
+static int
+linux_sched_setscheduler(pid_t pid, int sched, const struct sched_param *param)
+{
+	return syscall(__NR_sched_setscheduler, pid, sched, param);
 }
 
 #endif
@@ -66,10 +81,14 @@ SetThreadIdlePriority()
 #ifdef __linux__
 #ifdef SCHED_IDLE
 	static struct sched_param sched_param;
-	sched_setscheduler(0, SCHED_IDLE, &sched_param);
+	linux_sched_setscheduler(0, SCHED_IDLE, &sched_param);
 #endif
 
+#ifndef ANDROID
+	/* this system call is forbidden via seccomp on Android 8 and
+	   leads to crash (SIGSYS) */
 	ioprio_set_idle();
+#endif
 
 #elif defined(_WIN32)
 	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE);
@@ -88,7 +107,7 @@ SetThreadRealtime()
 	policy |= SCHED_RESET_ON_FORK;
 #endif
 
-	if (sched_setscheduler(0, policy, &sched_param) < 0)
+	if (linux_sched_setscheduler(0, policy, &sched_param) < 0)
 		throw MakeErrno("sched_setscheduler failed");
 #endif	// __linux__
 };
diff --git a/src/util/NumberParser.hxx b/src/util/NumberParser.hxx
index 47e9aac..67d42af 100644
--- a/src/util/NumberParser.hxx
+++ b/src/util/NumberParser.hxx
@@ -78,7 +78,12 @@ ParseDouble(const char *p, char **endptr=nullptr)
 static inline float
 ParseFloat(const char *p, char **endptr=nullptr)
 {
+#if defined(__BIONIC__) && __ANDROID_API__ < 21
+	/* strtof() requires API level 21 */
 	return (float)ParseDouble(p, endptr);
+#else
+	return strtof(p, endptr);
+#endif
 }
 
 #endif
diff --git a/src/util/RefCount.hxx b/src/util/RefCount.hxx
index 8faf839..a3466eb 100644
--- a/src/util/RefCount.hxx
+++ b/src/util/RefCount.hxx
@@ -42,11 +42,6 @@ class RefCount {
 	std::atomic_uint n;
 
 public:
-#ifndef _LIBCPP_VERSION
-	/* the "constexpr" is missing in libc++'s "atomic"
-	   implementation */
-	constexpr
-#endif
 	RefCount():n(1) {}
 
 	void Increment() {
diff --git a/src/pcm/SoxrResampler.hxx b/test/NullMixerListener.hxx
similarity index 55%
copy from src/pcm/SoxrResampler.hxx
copy to test/NullMixerListener.hxx
index 0be8611..ed3986f 100644
--- a/src/pcm/SoxrResampler.hxx
+++ b/test/NullMixerListener.hxx
@@ -1,5 +1,5 @@
 /*
- * Copyright 2003-2017 The Music Player Daemon Project
+ * Copyright 2003-2018 The Music Player Daemon Project
  * http://www.musicpd.org
  *
  * This program is free software; you can redistribute it and/or modify
@@ -17,34 +17,14 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
-#ifndef MPD_PCM_SOXR_RESAMPLER_HXX
-#define MPD_PCM_SOXR_RESAMPLER_HXX
+#ifndef NULL_MIXER_LISTENER_HXX
+#define NULL_MIXER_LISTENER_HXX
 
-#include "Resampler.hxx"
-#include "PcmBuffer.hxx"
-#include "Compiler.h"
-
-struct AudioFormat;
-struct ConfigBlock;
-
-/**
- * A resampler using soxr.
- */
-class SoxrPcmResampler final : public PcmResampler {
-	struct soxr *soxr;
-
-	unsigned channels;
-	float ratio;
-
-	PcmBuffer buffer;
+#include "mixer/Listener.hxx"
 
+class NullMixerListener : public MixerListener {
 public:
-	AudioFormat Open(AudioFormat &af, unsigned new_sample_rate) override;
-	void Close() override;
-	ConstBuffer<void> Resample(ConstBuffer<void> src) override;
+	void OnMixerVolumeChanged(Mixer &, int) override {}
 };
 
-void
-pcm_resample_soxr_global_init(const ConfigBlock &block);
-
 #endif
diff --git a/test/read_mixer.cxx b/test/read_mixer.cxx
index 4c7efab..a0d7487 100644
--- a/test/read_mixer.cxx
+++ b/test/read_mixer.cxx
@@ -18,6 +18,7 @@
  */
 
 #include "config.h"
+#include "NullMixerListener.hxx"
 #include "mixer/MixerControl.hxx"
 #include "mixer/MixerList.hxx"
 #include "filter/FilterRegistry.hxx"
@@ -50,9 +51,14 @@ try {
 
 	EventLoop event_loop;
 
+	NullMixerListener mixer_listener;
 	Mixer *mixer = mixer_new(event_loop, alsa_mixer_plugin,
-				 *(AudioOutput *)nullptr,
-				 *(MixerListener *)nullptr,
+				 /* ugly dangerous dummy pointer to
+				    make the compiler happy; this
+				    works with most mixers, because
+				    they don't need the AudioOutput */
+				 *(AudioOutput *)0x1,
+				 mixer_listener,
 				 ConfigBlock());
 
 	mixer_open(mixer);
diff --git a/test/run_output.cxx b/test/run_output.cxx
index 8af4621..27e65cb 100644
--- a/test/run_output.cxx
+++ b/test/run_output.cxx
@@ -18,6 +18,7 @@
  */
 
 #include "config.h"
+#include "NullMixerListener.hxx"
 #include "output/Internal.hxx"
 #include "output/OutputPlugin.hxx"
 #include "output/Client.hxx"
@@ -44,6 +45,8 @@
 #include <stdlib.h>
 #include <stdio.h>
 
+void AudioOutput::Task() {}
+
 class DummyAudioOutputClient final : public AudioOutputClient {
 public:
 	/* virtual methods from AudioOutputClient */
@@ -62,7 +65,9 @@ filter_plugin_by_name(gcc_unused const char *name) noexcept
 }
 
 static AudioOutput *
-load_audio_output(EventLoop &event_loop, AudioOutputClient &client,
+load_audio_output(EventLoop &event_loop,
+		  NullMixerListener &mixer_listener,
+		  AudioOutputClient &client,
 		  const char *name)
 {
 	const auto *param = config_find_block(ConfigBlockOption::AUDIO_OUTPUT,
@@ -72,7 +77,7 @@ load_audio_output(EventLoop &event_loop, AudioOutputClient &client,
 					 name);
 
 	return audio_output_new(event_loop, ReplayGainConfig(), *param,
-				*(MixerListener *)nullptr,
+				mixer_listener,
 				client);
 }
 
@@ -142,8 +147,10 @@ try {
 
 	/* initialize the audio output */
 
+	NullMixerListener mixer_listener;
 	DummyAudioOutputClient client;
-	AudioOutput *ao = load_audio_output(event_loop, client, argv[2]);
+	AudioOutput *ao = load_audio_output(event_loop, mixer_listener,
+					    client, argv[2]);
 
 	/* parse the audio format */
 
diff --git a/win32/build.py b/win32/build.py
index d2b1aef..45c5dfa 100755
--- a/win32/build.py
+++ b/win32/build.py
@@ -50,14 +50,14 @@ class CrossGccToolchain:
         self.nm = os.path.join(toolchain_bin, arch + '-nm')
         self.strip = os.path.join(toolchain_bin, arch + '-strip')
 
-        common_flags = ''
+        common_flags = '-O2 -g'
 
         if not x64:
             # enable SSE support which is required for LAME
             common_flags += ' -march=pentium3'
 
-        self.cflags = '-O2 -g ' + common_flags
-        self.cxxflags = '-O2 -g ' + common_flags
+        self.cflags = common_flags
+        self.cxxflags = common_flags
         self.cppflags = '-isystem ' + os.path.join(install_prefix, 'include')
         self.ldflags = '-L' + os.path.join(install_prefix, 'lib')
         self.libs = ''

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-mpd/pkg-mpd.git



More information about the Pkg-mpd-commits mailing list