diff --git a/Makefile.am b/Makefile.am
index 547dbeca1..3c56e2539 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -721,6 +721,7 @@ libstorage_a_SOURCES = \
 	src/storage/MemoryDirectoryReader.cxx src/storage/MemoryDirectoryReader.hxx \
 	src/storage/Configured.cxx src/storage/Configured.hxx \
 	src/storage/plugins/LocalStorage.cxx src/storage/plugins/LocalStorage.hxx \
+	src/storage/StorageState.cxx src/storage/StorageState.hxx \
 	src/storage/FileInfo.hxx
 
 libstorage_a_CPPFLAGS = $(AM_CPPFLAGS) \
diff --git a/NEWS b/NEWS
index 8863ae8ab..4812e159e 100644
--- a/NEWS
+++ b/NEWS
@@ -15,14 +15,18 @@ ver 0.21 (not yet released)
 * mixer
   - sndio: new mixer plugin
 
-ver 0.20.13 (not yet released)
+ver 0.20.13 (2017/12/18)
 * output
   - osx: set up ring buffer to hold at least 100ms
+* mixer
+  - alsa: fix rounding errors
 * database
   - simple: don't purge mount points on update/rescan
   - simple: fix "mount" bug caused by bad compiler optimization
+  - simple: fix "lsinfo" into mount points
   - upnp: work around libupnp 1.6.24 API breakage
 * queue: fix spuriously misplaced prioritized songs
+* save and restore mountpoints within the state file
 * include Windows cross-build script in source tarball
 * fix Windows build failures
 
diff --git a/src/StateFile.cxx b/src/StateFile.cxx
index ba2be8d14..6443f8a66 100644
--- a/src/StateFile.cxx
+++ b/src/StateFile.cxx
@@ -24,6 +24,7 @@
 #include "fs/io/TextFile.hxx"
 #include "fs/io/FileOutputStream.hxx"
 #include "fs/io/BufferedOutputStream.hxx"
+#include "storage/StorageState.hxx"
 #include "Partition.hxx"
 #include "Instance.hxx"
 #include "mixer/Volume.hxx"
@@ -56,6 +57,9 @@ StateFile::RememberVersions() noexcept
 	prev_output_version = audio_output_state_get_version();
 	prev_playlist_version = playlist_state_get_hash(partition.playlist,
 							partition.pc);
+#ifdef ENABLE_DATABASE
+	prev_storage_version = storage_state_get_hash(partition.instance);
+#endif
 }
 
 bool
@@ -64,7 +68,11 @@ StateFile::IsModified() const noexcept
 	return prev_volume_version != sw_volume_state_get_hash() ||
 		prev_output_version != audio_output_state_get_version() ||
 		prev_playlist_version != playlist_state_get_hash(partition.playlist,
-								 partition.pc);
+								 partition.pc)
+#ifdef ENABLE_DATABASE
+		|| prev_storage_version != storage_state_get_hash(partition.instance)
+#endif
+		;
 }
 
 inline void
@@ -72,6 +80,11 @@ StateFile::Write(BufferedOutputStream &os)
 {
 	save_sw_volume_state(os);
 	audio_output_state_save(os, partition.outputs);
+
+#ifdef ENABLE_DATABASE
+	storage_state_save(os, partition.instance);
+#endif
+
 	playlist_state_save(os, partition.playlist, partition.pc);
 }
 
@@ -123,6 +136,10 @@ try {
 			playlist_state_restore(line, file, song_loader,
 					       partition.playlist,
 					       partition.pc);
+#ifdef ENABLE_DATABASE
+		success = success || storage_state_restore(line, file, partition.instance);
+#endif
+
 		if (!success)
 			FormatError(state_file_domain,
 				    "Unrecognized line in state file: %s",
diff --git a/src/StateFile.hxx b/src/StateFile.hxx
index 3eba7f620..ed0f9fcef 100644
--- a/src/StateFile.hxx
+++ b/src/StateFile.hxx
@@ -47,6 +47,10 @@ class StateFile final {
 	unsigned prev_volume_version = 0, prev_output_version = 0,
 		prev_playlist_version = 0;
 
+#ifdef ENABLE_DATABASE
+	unsigned prev_storage_version = 0;
+#endif
+
 public:
 	static constexpr std::chrono::steady_clock::duration DEFAULT_INTERVAL = std::chrono::minutes(2);
 
diff --git a/src/db/plugins/simple/Directory.cxx b/src/db/plugins/simple/Directory.cxx
index 4c784fd8b..3bc9c35aa 100644
--- a/src/db/plugins/simple/Directory.cxx
+++ b/src/db/plugins/simple/Directory.cxx
@@ -227,7 +227,7 @@ Directory::Walk(bool recursive, const SongFilter *filter,
 		   call will lock it again */
 		const ScopeDatabaseUnlock unlock;
 		WalkMount(GetPath(), *mounted_database,
-			  recursive, filter,
+			  "", recursive, filter,
 			  visit_directory, visit_song,
 			  visit_playlist);
 		return;
diff --git a/src/db/plugins/simple/Mount.cxx b/src/db/plugins/simple/Mount.cxx
index bf3a4b815..c07b67abc 100644
--- a/src/db/plugins/simple/Mount.cxx
+++ b/src/db/plugins/simple/Mount.cxx
@@ -72,7 +72,7 @@ PrefixVisitPlaylist(const char *base, const VisitPlaylist &visit_playlist,
 
 void
 WalkMount(const char *base, const Database &db,
-	  bool recursive, const SongFilter *filter,
+	  const char* uri, bool recursive, const SongFilter *filter,
 	  const VisitDirectory &visit_directory, const VisitSong &visit_song,
 	  const VisitPlaylist &visit_playlist)
 {
@@ -93,5 +93,5 @@ WalkMount(const char *base, const Database &db,
 		vp = std::bind(PrefixVisitPlaylist,
 			       base, std::ref(visit_playlist), _1, _2);
 
-	db.Visit(DatabaseSelection("", recursive, filter), vd, vs, vp);
+	db.Visit(DatabaseSelection(uri, recursive, filter), vd, vs, vp);
 }
diff --git a/src/db/plugins/simple/Mount.hxx b/src/db/plugins/simple/Mount.hxx
index 0c6abd1d1..c79e52354 100644
--- a/src/db/plugins/simple/Mount.hxx
+++ b/src/db/plugins/simple/Mount.hxx
@@ -27,7 +27,7 @@ class SongFilter;
 
 void
 WalkMount(const char *base, const Database &db,
-	  bool recursive, const SongFilter *filter,
+	  const char* uri, bool recursive, const SongFilter *filter,
 	  const VisitDirectory &visit_directory, const VisitSong &visit_song,
 	  const VisitPlaylist &visit_playlist);
 
diff --git a/src/db/plugins/simple/SimpleDatabasePlugin.cxx b/src/db/plugins/simple/SimpleDatabasePlugin.cxx
index 9330735b6..55e2f694c 100644
--- a/src/db/plugins/simple/SimpleDatabasePlugin.cxx
+++ b/src/db/plugins/simple/SimpleDatabasePlugin.cxx
@@ -20,6 +20,7 @@
 #include "config.h"
 #include "SimpleDatabasePlugin.hxx"
 #include "PrefixedLightSong.hxx"
+#include "Mount.hxx"
 #include "db/DatabasePlugin.hxx"
 #include "db/Selection.hxx"
 #include "db/Helpers.hxx"
@@ -271,6 +272,18 @@ SimpleDatabase::Visit(const DatabaseSelection &selection,
 	ScopeDatabaseLock protect;
 
 	auto r = root->LookupDirectory(selection.uri.c_str());
+
+	if (r.directory->IsMount()) {
+		/* pass the request and the remaining uri to the mounted database */
+		protect.unlock();
+
+		WalkMount(r.directory->GetPath(), *(r.directory->mounted_database),
+			(r.uri == nullptr)?"":r.uri, selection.recursive, selection.filter,
+			visit_directory, visit_song, visit_playlist);
+
+		return;
+	}
+
 	if (r.uri == nullptr) {
 		/* it's a directory */
 
diff --git a/src/mixer/plugins/AlsaMixerPlugin.cxx b/src/mixer/plugins/AlsaMixerPlugin.cxx
index 9958b2d24..04c3c53ca 100644
--- a/src/mixer/plugins/AlsaMixerPlugin.cxx
+++ b/src/mixer/plugins/AlsaMixerPlugin.cxx
@@ -282,7 +282,9 @@ AlsaMixer::SetVolume(unsigned volume)
 {
 	assert(handle != nullptr);
 
-	int err = set_normalized_playback_volume(elem, 0.01*volume, 1);
+	double cur = get_normalized_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT);
+	int delta = volume - lrint(100.*cur);
+	int err = set_normalized_playback_volume(elem, cur + 0.01*delta, delta);
 	if (err < 0)
 		throw FormatRuntimeError("failed to set ALSA volume: %s",
 					 snd_strerror(err));
diff --git a/src/storage/StorageState.cxx b/src/storage/StorageState.cxx
new file mode 100644
index 000000000..a774e31aa
--- /dev/null
+++ b/src/storage/StorageState.cxx
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2003-2017 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Save and load mounts of the compound storage to/from the state file.
+ *
+ */
+
+#include "config.h"
+#include "StorageState.hxx"
+#include "fs/io/TextFile.hxx"
+#include "fs/io/BufferedOutputStream.hxx"
+#include "storage/Registry.hxx"
+#include "storage/CompositeStorage.hxx"
+#include "db/plugins/simple/SimpleDatabasePlugin.hxx"
+#include "util/StringCompare.hxx"
+#include "util/Domain.hxx"
+#include "Instance.hxx"
+#include "Log.hxx"
+
+#include <list>
+#include <boost/crc.hpp>
+
+#define MOUNT_STATE_BEGIN        "mount_begin"
+#define MOUNT_STATE_END          "mount_end"
+#define MOUNT_STATE_STORAGE_URI  "uri: "
+#define MOUNT_STATE_MOUNTED_URL  "mounted_url: "
+
+static constexpr Domain storage_domain("storage");
+
+void
+storage_state_save(BufferedOutputStream &os, const Instance &instance)
+{
+	const auto visitor = [&os](const char *mount_uri, const Storage &storage) {
+		std::string uri = storage.MapUTF8("");
+		if (uri.empty() || StringIsEmpty(mount_uri))
+			return;
+
+		os.Format(
+			MOUNT_STATE_BEGIN "\n"
+			MOUNT_STATE_STORAGE_URI "%s\n"
+			MOUNT_STATE_MOUNTED_URL "%s\n"
+			MOUNT_STATE_END "\n", mount_uri, uri.c_str());
+	};
+
+	((CompositeStorage*)instance.storage)->VisitMounts(visitor);
+}
+
+bool
+storage_state_restore(const char *line, TextFile &file, Instance &instance)
+{
+	if (!StringStartsWith(line, MOUNT_STATE_BEGIN))
+		return false;
+
+	std::string url;
+	std::string uri;
+	const char* value;
+
+	while ((line = file.ReadLine()) != nullptr) {
+		if (StringStartsWith(line, MOUNT_STATE_END))
+			break;
+
+		if ((value = StringAfterPrefix(line, MOUNT_STATE_MOUNTED_URL)))
+			url = value;
+		else if ((value = StringAfterPrefix(line, MOUNT_STATE_STORAGE_URI)))
+			uri = value;
+		else
+			FormatError(storage_domain, "Unrecognized line in mountpoint state: %s", line);
+	}
+
+	if (url.empty() || uri.empty()) {
+		LogError(storage_domain, "Missing value in mountpoint state.");	
+		return true;
+	}
+
+	FormatDebug(storage_domain, "Restoring mount %s => %s", uri.c_str(), url.c_str());
+
+	auto &event_loop = instance.io_thread.GetEventLoop();
+	Storage *storage = CreateStorageURI(event_loop, url.c_str());
+	if (storage == nullptr) {
+		FormatError(storage_domain, "Unrecognized storage URI: %s", url.c_str());
+		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;
+		}
+	}
+#endif
+
+	((CompositeStorage*)instance.storage)->Mount(uri.c_str(), storage);
+
+	return true;
+}
+
+unsigned
+storage_state_get_hash(const Instance &instance)
+{
+	std::list<std::string> mounts;
+
+	const auto visitor = [&mounts](const char *mount_uri, const Storage &storage) {
+		mounts.push_back(std::string(mount_uri) + ":" + storage.MapUTF8(""));
+	};
+
+	((CompositeStorage*)instance.storage)->VisitMounts(visitor);
+
+	mounts.sort();
+
+	boost::crc_32_type result;
+
+	for (auto mount: mounts) {
+		result.process_bytes(mount.c_str(), mount.length());
+	}
+
+	return result.checksum();
+}
diff --git a/src/storage/StorageState.hxx b/src/storage/StorageState.hxx
new file mode 100644
index 000000000..659cc9868
--- /dev/null
+++ b/src/storage/StorageState.hxx
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2003-2017 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Saving and loading the playlist to/from the state file.
+ *
+ */
+
+#ifndef MPD_STORAGE_STATE_HXX
+#define MPD_STORAGE_STATE_HXX
+
+struct Instance;
+class BufferedOutputStream;
+class TextFile;
+
+void
+storage_state_save(BufferedOutputStream &os, const Instance &instance);
+
+bool
+storage_state_restore(const char *line, TextFile &file, Instance &instance);
+
+/**
+ * Generates a hash number for the current state of the composite storage.
+ * This is used by timer_save_state_file() to determine whether the state
+ * has changed and the state file should be saved.
+ */
+unsigned
+storage_state_get_hash(const Instance &instance);
+
+#endif