diff --git a/Makefile.am b/Makefile.am
index 11e9d4476..64e4b1d4e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1932,6 +1932,9 @@ test_DumpDatabase_SOURCES = test/DumpDatabase.cxx \
 	src/db/Selection.cxx \
 	src/db/PlaylistVector.cxx \
 	src/db/DatabaseLock.cxx \
+	src/AudioFormat.cxx \
+	src/AudioParser.cxx \
+	src/pcm/SampleFormat.cxx \
 	src/SongSave.cxx \
 	src/DetachedSong.cxx \
 	src/TagSave.cxx \
diff --git a/NEWS b/NEWS
index 105f3375c..817824d2e 100644
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,8 @@ ver 0.21 (not yet released)
   - "outputs" prints the plugin name
   - "outputset" sets runtime attributes
   - close connection when client sends HTTP request
+* database
+  - simple: scan audio formats
 * player
   - "one-shot" single mode
 * input
diff --git a/doc/protocol.xml b/doc/protocol.xml
index e0532e0e4..c2a74af1e 100644
--- a/doc/protocol.xml
+++ b/doc/protocol.xml
@@ -256,6 +256,17 @@
           </para>
         </listitem>
 
+        <listitem>
+          <para>
+            <varname>Format</varname>: the audio format of the song
+            (or an approximation to a format supported by MPD and the
+            decoder plugin being used).  When playing this file, the
+            <varname>audio</varname> value in the <link
+            linkend="command_status"><command>status</command></link>
+            response should be the same.
+          </para>
+        </listitem>
+
         <listitem>
           <para>
             <varname>Last-Modified</varname>: the time stamp of the
diff --git a/src/SongPrint.cxx b/src/SongPrint.cxx
index 9db113f80..b42287c02 100644
--- a/src/SongPrint.cxx
+++ b/src/SongPrint.cxx
@@ -92,6 +92,9 @@ song_print_info(Response &r, const LightSong &song, bool base) noexcept
 	if (!IsNegative(song.mtime))
 		time_print(r, "Last-Modified", song.mtime);
 
+	if (song.audio_format.IsDefined())
+		r.Format("Format: %s\n", ToString(song.audio_format).c_str());
+
 	tag_print(r, song.tag);
 }
 
diff --git a/src/SongSave.cxx b/src/SongSave.cxx
index c3593d823..e42c58f49 100644
--- a/src/SongSave.cxx
+++ b/src/SongSave.cxx
@@ -19,6 +19,7 @@
 
 #include "config.h"
 #include "SongSave.hxx"
+#include "AudioParser.hxx"
 #include "db/plugins/simple/Song.hxx"
 #include "DetachedSong.hxx"
 #include "TagSave.hxx"
@@ -28,6 +29,8 @@
 #include "tag/Tag.hxx"
 #include "tag/Builder.hxx"
 #include "util/ChronoUtil.hxx"
+#include "util/StringAPI.hxx"
+#include "util/StringBuffer.hxx"
 #include "util/StringStrip.hxx"
 #include "util/RuntimeError.hxx"
 #include "util/NumberParser.hxx"
@@ -56,6 +59,9 @@ song_save(BufferedOutputStream &os, const Song &song)
 
 	tag_save(os, song.tag);
 
+	if (song.audio_format.IsDefined())
+		os.Format("Format: %s\n", ToString(song.audio_format).c_str());
+
 	if (!IsNegative(song.mtime))
 		os.Format(SONG_MTIME ": %li\n",
 			  (long)std::chrono::system_clock::to_time_t(song.mtime));
@@ -78,7 +84,8 @@ song_save(BufferedOutputStream &os, const DetachedSong &song)
 }
 
 std::unique_ptr<DetachedSong>
-song_load(TextFile &file, const char *uri)
+song_load(TextFile &file, const char *uri,
+	  AudioFormat *audio_format_r)
 {
 	auto song = std::make_unique<DetachedSong>(uri);
 
@@ -100,6 +107,15 @@ song_load(TextFile &file, const char *uri)
 			tag.AddItem(type, value);
 		} else if (strcmp(line, "Time") == 0) {
 			tag.SetDuration(SignedSongTime::FromS(ParseDouble(value)));
+		} else if (StringIsEqual(line, "Format")) {
+			if (audio_format_r != nullptr) {
+				try {
+					*audio_format_r =
+						ParseAudioFormat(value, false);
+				} catch (...) {
+					/* ignore parser errors */
+				}
+			}
 		} else if (strcmp(line, "Playlist") == 0) {
 			tag.SetHasPlaylist(strcmp(value, "yes") == 0);
 		} else if (strcmp(line, SONG_MTIME) == 0) {
diff --git a/src/SongSave.hxx b/src/SongSave.hxx
index 9e6ae151d..81505b91c 100644
--- a/src/SongSave.hxx
+++ b/src/SongSave.hxx
@@ -25,6 +25,7 @@
 #define SONG_BEGIN "song_begin: "
 
 struct Song;
+struct AudioFormat;
 class DetachedSong;
 class BufferedOutputStream;
 class TextFile;
@@ -42,6 +43,7 @@ song_save(BufferedOutputStream &os, const DetachedSong &song);
  * Throws #std::runtime_error on error.
  */
 std::unique_ptr<DetachedSong>
-song_load(TextFile &file, const char *uri);
+song_load(TextFile &file, const char *uri,
+	  AudioFormat *audio_format_r=nullptr);
 
 #endif
diff --git a/src/SongUpdate.cxx b/src/SongUpdate.cxx
index e43c45961..5738955b3 100644
--- a/src/SongUpdate.cxx
+++ b/src/SongUpdate.cxx
@@ -77,19 +77,23 @@ Song::UpdateFile(Storage &storage) noexcept
 		return false;
 
 	TagBuilder tag_builder;
+	auto new_audio_format = AudioFormat::Undefined();
 
 	const auto path_fs = storage.MapFS(relative_uri.c_str());
 	if (path_fs.IsNull()) {
 		const auto absolute_uri =
 			storage.MapUTF8(relative_uri.c_str());
-		if (!tag_stream_scan(absolute_uri.c_str(), tag_builder))
+		if (!tag_stream_scan(absolute_uri.c_str(), tag_builder,
+				     &new_audio_format))
 			return false;
 	} else {
-		if (!ScanFileTagsWithGeneric(path_fs, tag_builder))
+		if (!ScanFileTagsWithGeneric(path_fs, tag_builder,
+					     &new_audio_format))
 			return false;
 	}
 
 	mtime = info.mtime;
+	audio_format = new_audio_format;
 	tag_builder.Commit(tag);
 	return true;
 }
diff --git a/src/db/LightSong.hxx b/src/db/LightSong.hxx
index 7b5b51043..7bc21e030 100644
--- a/src/db/LightSong.hxx
+++ b/src/db/LightSong.hxx
@@ -21,6 +21,7 @@
 #define MPD_LIGHT_SONG_HXX
 
 #include "Chrono.hxx"
+#include "AudioFormat.hxx"
 #include "Compiler.h"
 
 #include <string>
@@ -78,6 +79,12 @@ struct LightSong {
 	 */
 	SongTime end_time = SongTime::zero();
 
+	/**
+	 * The audio format of the song, if given by the decoder
+	 * plugin.  May be undefined if unknown.
+	 */
+	AudioFormat audio_format = AudioFormat::Undefined();
+
 	LightSong(const char *_uri, const Tag &_tag) noexcept
 		:uri(_uri), tag(_tag) {}
 
diff --git a/src/db/plugins/simple/DirectorySave.cxx b/src/db/plugins/simple/DirectorySave.cxx
index 3cba77295..35406cadd 100644
--- a/src/db/plugins/simple/DirectorySave.cxx
+++ b/src/db/plugins/simple/DirectorySave.cxx
@@ -161,10 +161,15 @@ directory_load(TextFile &file, Directory &directory)
 			if (directory.FindSong(name) != nullptr)
 				throw FormatRuntimeError("Duplicate song '%s'", name);
 
-			auto song = song_load(file, name);
+			auto audio_format = AudioFormat::Undefined();
+			auto detached_song = song_load(file, name,
+						       &audio_format);
 
-			directory.AddSong(Song::NewFrom(std::move(*song),
-							directory));
+			auto song = Song::NewFrom(std::move(*detached_song),
+						  directory);
+			song->audio_format = audio_format;
+
+			directory.AddSong(song);
 		} else if ((p = StringAfterPrefix(line, PLAYLIST_META_BEGIN))) {
 			const char *name = p;
 			playlist_metadata_load(file, directory.playlists, name);
diff --git a/src/db/plugins/simple/Song.cxx b/src/db/plugins/simple/Song.cxx
index 61ed929df..4933e0e38 100644
--- a/src/db/plugins/simple/Song.cxx
+++ b/src/db/plugins/simple/Song.cxx
@@ -104,5 +104,6 @@ Song::Export() const noexcept
 	dest.mtime = mtime;
 	dest.start_time = start_time;
 	dest.end_time = end_time;
+	dest.audio_format = audio_format;
 	return dest;
 }
diff --git a/src/db/plugins/simple/Song.hxx b/src/db/plugins/simple/Song.hxx
index 6802c7059..1cd344f9f 100644
--- a/src/db/plugins/simple/Song.hxx
+++ b/src/db/plugins/simple/Song.hxx
@@ -23,6 +23,7 @@
 #include "check.h"
 #include "Chrono.hxx"
 #include "tag/Tag.hxx"
+#include "AudioFormat.hxx"
 #include "Compiler.h"
 
 #include <boost/intrusive/list.hpp>
@@ -88,6 +89,12 @@ struct Song {
 	 */
 	SongTime end_time = SongTime::zero();
 
+	/**
+	 * The audio format of the song, if given by the decoder
+	 * plugin.  May be undefined if unknown.
+	 */
+	AudioFormat audio_format = AudioFormat::Undefined();
+
 	/**
 	 * The file name.
 	 */