From bb009daf66372ffa102d432df2643502891cbe6c Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 4 Nov 2020 13:34:02 +0100 Subject: [PATCH 01/22] playlist/registry: simplify ExtractMimeTypeMainPart() --- src/playlist/PlaylistRegistry.cxx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/playlist/PlaylistRegistry.cxx b/src/playlist/PlaylistRegistry.cxx index 9aa87b73b..5b7ef71d3 100644 --- a/src/playlist/PlaylistRegistry.cxx +++ b/src/playlist/PlaylistRegistry.cxx @@ -207,11 +207,7 @@ gcc_pure static StringView ExtractMimeTypeMainPart(StringView s) noexcept { - const auto separator = s.Find(';'); - if (separator != nullptr) - s.SetEnd(separator); - - return s; + return s.Split(';').first; } static std::unique_ptr From 4f40b9f7cfd1e83f043077da51541546d6c9708a Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 4 Nov 2020 13:38:20 +0100 Subject: [PATCH 02/22] meson.build: disable ld.so lazy binding and enable relro Since MPD is a long-running daemon, it doesn't make sense to use dynamic binding. That allows the relocations to be read-only ("relro"), which a hardening feature. --- meson.build | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/meson.build b/meson.build index d70c9bf10..a98271484 100644 --- a/meson.build +++ b/meson.build @@ -96,6 +96,11 @@ test_cflags = test_common_flags + [ ] test_ldflags = [ + # make relocations read-only (hardening) + '-Wl,-z,relro', + + # no lazy binding, please - not worth it for a daemon + '-Wl,-z,now', ] if get_option('buildtype') != 'debug' From e0d3ca71b3b798f7371d11088ccd6015bd3de55a Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 4 Nov 2020 14:38:05 +0100 Subject: [PATCH 03/22] meson.build: switch to C11 It's been 9 years already, and there's no point in insisting on the 21 year old C standard. MPD doesn't have a lot of C code left, but why not compile it with the latest language revision. --- meson.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index a98271484..409f7b3c7 100644 --- a/meson.build +++ b/meson.build @@ -4,8 +4,8 @@ project( version: '0.22.2', meson_version: '>= 0.49.0', default_options: [ - 'c_std=c99', - 'build.c_std=c99', + 'c_std=c11', + 'build.c_std=c11', 'cpp_std=c++17', 'build.cpp_std=c++17', 'warning_level=3', From 23a6f62ea343cee5ec106b175ed12c2c2a8c706c Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 4 Nov 2020 15:59:38 +0100 Subject: [PATCH 04/22] doc/user.rst: fix typo --- doc/user.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user.rst b/doc/user.rst index db87ee7d0..06235c432 100644 --- a/doc/user.rst +++ b/doc/user.rst @@ -413,7 +413,7 @@ The following table lists the audio_output options valid for all plugins: * - **format samplerate:bits:channels** - Always open the audio output with the specified audio format, regardless of the format of the input file. This is optional for most plugins. See :ref:`audio_output_format` for a detailed description of the value. - * - **enabed yes|no** + * - **enabled yes|no** - Specifies whether this audio output is enabled when :program:`MPD` is started. By default, all audio outputs are enabled. This is just the default setting when there is no state file; with a state file, the previous state is restored. * - **tags yes|no** - If set to no, then :program:`MPD` will not send tags to this output. This is only useful for output plugins that can receive tags, for example the httpd output plugin. From d69a1f98af6a967bc740e383389c092cd22c1634 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 4 Nov 2020 16:09:27 +0100 Subject: [PATCH 05/22] doc/plugins.rst: more markup --- doc/plugins.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/plugins.rst b/doc/plugins.rst index 107140942..45a7b347f 100644 --- a/doc/plugins.rst +++ b/doc/plugins.rst @@ -1229,23 +1229,23 @@ Playlist plugins asx --- -Reads .asx playlist files. +Reads :file:`.asx` playlist files. cue --- -Reads .cue files. +Reads :file:`.cue` files. embcue ------ -Reads CUE sheets from the "CUESHEET" tag of song files. +Reads CUE sheets from the ``CUESHEET`` tag of song files. m3u --- -Reads .m3u playlist files. +Reads :file:`.m3u` playlist files. extm3u ------ -Reads extended .m3u playlist files. +Reads extended :file:`.m3u` playlist files. flac ---- @@ -1253,11 +1253,11 @@ Reads the cuesheet metablock from a FLAC file. pls --- -Reads .pls playlist files. +Reads :file:`.pls` playlist files. rss --- -Reads music links from .rss files. +Reads music links from :file:`.rss` files. soundcloud ---------- From 92a218b7a9f5b3d89682de28075f158e53c57464 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 4 Nov 2020 15:25:03 +0100 Subject: [PATCH 06/22] playlist/registry: add option "as_directory" This allows users to disable the "CUE files as directories" feature without having to disable the CUE playlist plugin completely. This feature has been annoying some users. --- NEWS | 1 + doc/plugins.rst | 2 ++ doc/user.rst | 5 +++++ src/db/update/Playlist.cxx | 2 +- src/db/update/SpecialDirectory.cxx | 11 ++++++++++- src/playlist/PlaylistRegistry.cxx | 17 +++++++++++++++++ src/playlist/PlaylistRegistry.hxx | 8 ++++++++ 7 files changed, 44 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index a1b778d7e..7781f6e9a 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,7 @@ ver 0.22.2 (2020/10/28) - qobuz/tidal: fix protocol errors due to newlines in error messages - smbclient: disable by default due to libsmbclient crash bug * playlist + - add option "as_directory", making CUE file expansion optional - soundcloud: fix protocol errors due to newlines in error messages * state_file: save on shutdown diff --git a/doc/plugins.rst b/doc/plugins.rst index 45a7b347f..06fe714c1 100644 --- a/doc/plugins.rst +++ b/doc/plugins.rst @@ -1231,6 +1231,8 @@ asx Reads :file:`.asx` playlist files. +.. _cue_playlist: + cue --- Reads :file:`.cue` files. diff --git a/doc/user.rst b/doc/user.rst index 06235c432..abd7c7b1f 100644 --- a/doc/user.rst +++ b/doc/user.rst @@ -500,6 +500,11 @@ The following table lists the playlist_plugin options valid for all plugins: - The name of the plugin * - **enabled yes|no** - Allows you to disable a playlist plugin without recompiling. By default, all plugins are enabled. + * - **as_directory yes|no** + - With this option, a playlist file of this type is parsed during + database update and converted to a virtual directory, allowing + MPD clients to access individual entries. By default, this is + only enabled for the :ref:`cue plugin `. More information can be found in the :ref:`playlist_plugins` reference. diff --git a/src/db/update/Playlist.cxx b/src/db/update/Playlist.cxx index 03509522a..46286bb90 100644 --- a/src/db/update/Playlist.cxx +++ b/src/db/update/Playlist.cxx @@ -95,7 +95,7 @@ UpdateWalk::UpdatePlaylistFile(Directory &directory, if (plugin == nullptr) return false; - if (plugin->as_folder) + if (GetPlaylistPluginAsFolder(*plugin)) UpdatePlaylistFile(directory, name, info, *plugin); PlaylistInfo pi(name, info.mtime); diff --git a/src/db/update/SpecialDirectory.cxx b/src/db/update/SpecialDirectory.cxx index 49c025d18..368bfb2db 100644 --- a/src/db/update/SpecialDirectory.cxx +++ b/src/db/update/SpecialDirectory.cxx @@ -52,7 +52,16 @@ static bool HavePlaylistPluginForFilename(const char *filename) noexcept { const char *suffix = PathTraitsUTF8::GetFilenameSuffix(filename); - return suffix != nullptr && playlist_suffix_supported(suffix); + if (suffix == nullptr) + return false; + + const auto plugin = FindPlaylistPluginBySuffix(suffix); + if (plugin == nullptr) + return false; + + /* discard the special directory if the user disables the + plugin's "as_directory" setting */ + return GetPlaylistPluginAsFolder(*plugin); } bool diff --git a/src/playlist/PlaylistRegistry.cxx b/src/playlist/PlaylistRegistry.cxx index 5b7ef71d3..b9f6bb7ec 100644 --- a/src/playlist/PlaylistRegistry.cxx +++ b/src/playlist/PlaylistRegistry.cxx @@ -71,6 +71,9 @@ static constexpr unsigned n_playlist_plugins = /** which plugins have been initialized successfully? */ static bool playlist_plugins_enabled[n_playlist_plugins]; +/** which plugins have the "as_folder" option enabled? */ +static bool playlist_plugins_as_folder[n_playlist_plugins]; + #define playlist_plugins_for_each_enabled(plugin) \ playlist_plugins_for_each(plugin) \ if (playlist_plugins_enabled[playlist_plugin_iterator - playlist_plugins]) @@ -96,6 +99,10 @@ playlist_list_global_init(const ConfigData &config) playlist_plugins_enabled[i] = playlist_plugin_init(playlist_plugins[i], *param); + + playlist_plugins_as_folder[i] = + param->GetBlockValue("as_directory", + playlist_plugins[i]->as_folder); } } @@ -106,6 +113,16 @@ playlist_list_global_finish() noexcept playlist_plugin_finish(plugin); } +bool +GetPlaylistPluginAsFolder(const PlaylistPlugin &plugin) noexcept +{ + /* this loop has no end condition because it must finish when + the plugin was found */ + for (std::size_t i = 0;; ++i) + if (playlist_plugins[i] == &plugin) + return playlist_plugins_as_folder[i]; +} + static std::unique_ptr playlist_list_open_uri_scheme(const char *uri, Mutex &mutex, bool *tried) diff --git a/src/playlist/PlaylistRegistry.hxx b/src/playlist/PlaylistRegistry.hxx index 5e38ca23a..205fbb4b2 100644 --- a/src/playlist/PlaylistRegistry.hxx +++ b/src/playlist/PlaylistRegistry.hxx @@ -59,6 +59,14 @@ public: } }; +/** + * Shall this playlists supported by this plugin be represented as + * directories in the database? + */ +gcc_const +bool +GetPlaylistPluginAsFolder(const PlaylistPlugin &plugin) noexcept; + /** * Opens a playlist by its URI. */ From 6484af472b83a512e559616d44261526e62cc955 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 4 Nov 2020 16:14:25 +0100 Subject: [PATCH 07/22] increment version number to 0.22.3 --- NEWS | 5 ++++- doc/conf.py | 2 +- meson.build | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 7781f6e9a..bb0190284 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,7 @@ +ver 0.22.3 (not yet released) +* playlist + - add option "as_directory", making CUE file expansion optional + ver 0.22.2 (2020/10/28) * database - simple: purge songs and virtual directories for unavailable plugins @@ -6,7 +10,6 @@ ver 0.22.2 (2020/10/28) - qobuz/tidal: fix protocol errors due to newlines in error messages - smbclient: disable by default due to libsmbclient crash bug * playlist - - add option "as_directory", making CUE file expansion optional - soundcloud: fix protocol errors due to newlines in error messages * state_file: save on shutdown diff --git a/doc/conf.py b/doc/conf.py index fb9443dd3..2df29d071 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -38,7 +38,7 @@ author = 'Max Kellermann' # built documents. # # The short X.Y version. -version = '0.22.2' +version = '0.22.3' # The full version, including alpha/beta/rc tags. release = version diff --git a/meson.build b/meson.build index 409f7b3c7..e0df9d4d2 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'mpd', ['c', 'cpp'], - version: '0.22.2', + version: '0.22.3', meson_version: '>= 0.49.0', default_options: [ 'c_std=c11', From 27c589da975a4448960feb99a3b9774c116dc066 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 4 Nov 2020 16:26:03 +0100 Subject: [PATCH 08/22] filter/chain: remove unused field ChainFilter::Child::name --- src/filter/plugins/ChainFilterPlugin.cxx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/filter/plugins/ChainFilterPlugin.cxx b/src/filter/plugins/ChainFilterPlugin.cxx index 07bca48af..878a4f2cb 100644 --- a/src/filter/plugins/ChainFilterPlugin.cxx +++ b/src/filter/plugins/ChainFilterPlugin.cxx @@ -31,12 +31,10 @@ class ChainFilter final : public Filter { struct Child { - const char *name; std::unique_ptr filter; - Child(const char *_name, - std::unique_ptr _filter) noexcept - :name(_name), filter(std::move(_filter)) {} + explicit Child(std::unique_ptr &&_filter) noexcept + :filter(std::move(_filter)) {} }; std::list children; @@ -50,13 +48,12 @@ public: explicit ChainFilter(AudioFormat _audio_format) :Filter(_audio_format) {} - void Append(const char *name, - std::unique_ptr filter) noexcept { + void Append(std::unique_ptr filter) noexcept { assert(out_audio_format.IsValid()); out_audio_format = filter->GetOutAudioFormat(); assert(out_audio_format.IsValid()); - children.emplace_back(name, std::move(filter)); + children.emplace_back(std::move(filter)); RewindFlush(); } @@ -121,7 +118,7 @@ PreparedChainFilter::Open(AudioFormat &in_audio_format) for (auto &child : children) { AudioFormat audio_format = chain->GetOutAudioFormat(); - chain->Append(child.name, child.Open(audio_format)); + chain->Append(child.Open(audio_format)); } return chain; From b0002e3b73e2d09a9eb0d815f86342dc96918ef5 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 4 Nov 2020 16:22:40 +0100 Subject: [PATCH 09/22] filter/chain: copy the child name filter_chain_parse() passes a temporary string pointer which results in a use-after-free in the PreparedChainFilter::Child::Open() error message. --- NEWS | 2 ++ src/filter/plugins/ChainFilterPlugin.cxx | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index bb0190284..756259edc 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,8 @@ ver 0.22.3 (not yet released) * playlist - add option "as_directory", making CUE file expansion optional +* filter + - fix garbage after "Audio format not supported by filter" message ver 0.22.2 (2020/10/28) * database diff --git a/src/filter/plugins/ChainFilterPlugin.cxx b/src/filter/plugins/ChainFilterPlugin.cxx index 878a4f2cb..604a8344d 100644 --- a/src/filter/plugins/ChainFilterPlugin.cxx +++ b/src/filter/plugins/ChainFilterPlugin.cxx @@ -28,6 +28,7 @@ #include #include #include +#include class ChainFilter final : public Filter { struct Child { @@ -72,7 +73,7 @@ private: class PreparedChainFilter final : public PreparedFilter { struct Child { - const char *name; + const std::string name; std::unique_ptr filter; Child(const char *_name, @@ -105,7 +106,7 @@ PreparedChainFilter::Child::Open(const AudioFormat &prev_audio_format) if (conv_audio_format != prev_audio_format) throw FormatRuntimeError("Audio format not supported by filter '%s': %s", - name, + name.c_str(), ToString(prev_audio_format).c_str()); return new_filter; From eeaec99c596b1bc8b5dbadbb27b82bf50fd981c7 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 4 Nov 2020 16:32:54 +0100 Subject: [PATCH 10/22] filter/LoadChain: use IterableSplitString() --- src/filter/LoadChain.cxx | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/src/filter/LoadChain.cxx b/src/filter/LoadChain.cxx index 26e6f883a..b24abc8f8 100644 --- a/src/filter/LoadChain.cxx +++ b/src/filter/LoadChain.cxx @@ -21,12 +21,10 @@ #include "Factory.hxx" #include "Prepared.hxx" #include "plugins/ChainFilterPlugin.hxx" +#include "util/IterableSplitString.hxx" -#include #include -#include - static void filter_chain_append_new(PreparedFilter &chain, FilterFactory &factory, const char *template_name) @@ -40,18 +38,11 @@ filter_chain_parse(PreparedFilter &chain, FilterFactory &factory, const char *spec) { - const char *const end = spec + strlen(spec); + for (const std::string_view i : IterableSplitString(spec, ',')) { + if (i.empty()) + continue; - while (true) { - const char *comma = std::find(spec, end, ','); - if (comma > spec) { - const std::string name(spec, comma); - filter_chain_append_new(chain, factory, name.c_str()); - } - - if (comma == end) - break; - - spec = comma + 1; + const std::string name(i); + filter_chain_append_new(chain, factory, name.c_str()); } } From f2b9785a6706234d9c804a277f614a3f138eb171 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 4 Nov 2020 16:29:13 +0100 Subject: [PATCH 11/22] filter/chain: pass std::string_view to filter_chain_append() --- src/filter/LoadChain.cxx | 10 ++++++---- src/filter/plugins/ChainFilterPlugin.cxx | 6 +++--- src/filter/plugins/ChainFilterPlugin.hxx | 3 ++- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/filter/LoadChain.cxx b/src/filter/LoadChain.cxx index b24abc8f8..01c886fce 100644 --- a/src/filter/LoadChain.cxx +++ b/src/filter/LoadChain.cxx @@ -27,10 +27,13 @@ static void filter_chain_append_new(PreparedFilter &chain, FilterFactory &factory, - const char *template_name) + std::string_view template_name) { filter_chain_append(chain, template_name, - factory.MakeFilter(template_name)); + /* unfortunately, MakeFilter() wants a + null-terminated string, so we need to + copy it here */ + factory.MakeFilter(std::string(template_name).c_str())); } void @@ -42,7 +45,6 @@ filter_chain_parse(PreparedFilter &chain, if (i.empty()) continue; - const std::string name(i); - filter_chain_append_new(chain, factory, name.c_str()); + filter_chain_append_new(chain, factory, i); } } diff --git a/src/filter/plugins/ChainFilterPlugin.cxx b/src/filter/plugins/ChainFilterPlugin.cxx index 604a8344d..249130081 100644 --- a/src/filter/plugins/ChainFilterPlugin.cxx +++ b/src/filter/plugins/ChainFilterPlugin.cxx @@ -76,7 +76,7 @@ class PreparedChainFilter final : public PreparedFilter { const std::string name; std::unique_ptr filter; - Child(const char *_name, + Child(std::string_view _name, std::unique_ptr _filter) :name(_name), filter(std::move(_filter)) {} @@ -89,7 +89,7 @@ class PreparedChainFilter final : public PreparedFilter { std::list children; public: - void Append(const char *name, + void Append(std::string_view name, std::unique_ptr filter) noexcept { children.emplace_back(name, std::move(filter)); } @@ -175,7 +175,7 @@ filter_chain_new() noexcept } void -filter_chain_append(PreparedFilter &_chain, const char *name, +filter_chain_append(PreparedFilter &_chain, std::string_view name, std::unique_ptr filter) noexcept { auto &chain = (PreparedChainFilter &)_chain; diff --git a/src/filter/plugins/ChainFilterPlugin.hxx b/src/filter/plugins/ChainFilterPlugin.hxx index 06eb7c60a..8dd7e526c 100644 --- a/src/filter/plugins/ChainFilterPlugin.hxx +++ b/src/filter/plugins/ChainFilterPlugin.hxx @@ -28,6 +28,7 @@ #define MPD_FILTER_CHAIN_HXX #include +#include class PreparedFilter; @@ -45,7 +46,7 @@ filter_chain_new() noexcept; * @param filter the filter to be appended to #chain */ void -filter_chain_append(PreparedFilter &chain, const char *name, +filter_chain_append(PreparedFilter &chain, std::string_view name, std::unique_ptr filter) noexcept; #endif From 84f772357eb2582e28da6af04665a093ee468f0b Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 4 Nov 2020 16:46:24 +0100 Subject: [PATCH 12/22] filter/convert: convert_filter_new() returns std::unique_ptr --- src/filter/plugins/AutoConvertFilterPlugin.cxx | 4 ++-- src/filter/plugins/ConvertFilterPlugin.cxx | 4 ++-- src/filter/plugins/ConvertFilterPlugin.hxx | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/filter/plugins/AutoConvertFilterPlugin.cxx b/src/filter/plugins/AutoConvertFilterPlugin.cxx index 54c692406..0fe11cff3 100644 --- a/src/filter/plugins/AutoConvertFilterPlugin.cxx +++ b/src/filter/plugins/AutoConvertFilterPlugin.cxx @@ -85,8 +85,8 @@ PreparedAutoConvertFilter::Open(AudioFormat &in_audio_format) if (in_audio_format != child_audio_format) { /* yes - create a convert_filter */ - convert.reset(convert_filter_new(in_audio_format, - child_audio_format)); + convert = convert_filter_new(in_audio_format, + child_audio_format); } return std::make_unique(std::move(new_filter), diff --git a/src/filter/plugins/ConvertFilterPlugin.cxx b/src/filter/plugins/ConvertFilterPlugin.cxx index 91ddddeef..d04d09da6 100644 --- a/src/filter/plugins/ConvertFilterPlugin.cxx +++ b/src/filter/plugins/ConvertFilterPlugin.cxx @@ -117,13 +117,13 @@ convert_filter_prepare() noexcept return std::make_unique(); } -Filter * +std::unique_ptr convert_filter_new(const AudioFormat in_audio_format, const AudioFormat out_audio_format) { std::unique_ptr filter(new ConvertFilter(in_audio_format)); filter->Set(out_audio_format); - return filter.release(); + return filter; } void diff --git a/src/filter/plugins/ConvertFilterPlugin.hxx b/src/filter/plugins/ConvertFilterPlugin.hxx index a5299147e..0f78da519 100644 --- a/src/filter/plugins/ConvertFilterPlugin.hxx +++ b/src/filter/plugins/ConvertFilterPlugin.hxx @@ -29,7 +29,7 @@ struct AudioFormat; std::unique_ptr convert_filter_prepare() noexcept; -Filter * +std::unique_ptr convert_filter_new(AudioFormat in_audio_format, AudioFormat out_audio_format); From 9332527872e0fa5d33adaf292e5d7e1a1ec920f5 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 4 Nov 2020 19:50:21 +0100 Subject: [PATCH 13/22] lib/ffmpeg/{Buffer,Time}: remove obsolete "#undef SampleFormat" This compatibility macro has been removed from FFmpeg long ago. --- src/lib/ffmpeg/Buffer.hxx | 5 ----- src/lib/ffmpeg/Time.hxx | 5 ----- 2 files changed, 10 deletions(-) diff --git a/src/lib/ffmpeg/Buffer.hxx b/src/lib/ffmpeg/Buffer.hxx index cab4375d9..c719a2257 100644 --- a/src/lib/ffmpeg/Buffer.hxx +++ b/src/lib/ffmpeg/Buffer.hxx @@ -26,11 +26,6 @@ extern "C" { #include -/* suppress the ffmpeg compatibility macro */ -#ifdef SampleFormat -#undef SampleFormat -#endif - class FfmpegBuffer { void *data; unsigned size; diff --git a/src/lib/ffmpeg/Time.hxx b/src/lib/ffmpeg/Time.hxx index 03b8e8bce..39ebd3855 100644 --- a/src/lib/ffmpeg/Time.hxx +++ b/src/lib/ffmpeg/Time.hxx @@ -31,11 +31,6 @@ extern "C" { #include #include -/* suppress the ffmpeg compatibility macro */ -#ifdef SampleFormat -#undef SampleFormat -#endif - /* redefine AV_TIME_BASE_Q because libavutil's macro definition is a compound literal, which is illegal in C++ */ #ifdef AV_TIME_BASE_Q From f9bdb4b0b8f95fd64c9e4544a35b3ca280d2cf26 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 4 Nov 2020 19:47:07 +0100 Subject: [PATCH 14/22] lib/ffmpeg/Buffer: add `noexcept` --- src/lib/ffmpeg/Buffer.hxx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/ffmpeg/Buffer.hxx b/src/lib/ffmpeg/Buffer.hxx index c719a2257..330f5ed11 100644 --- a/src/lib/ffmpeg/Buffer.hxx +++ b/src/lib/ffmpeg/Buffer.hxx @@ -27,24 +27,24 @@ extern "C" { #include class FfmpegBuffer { - void *data; - unsigned size; + void *data = nullptr; + unsigned size = 0; public: - FfmpegBuffer():data(nullptr), size(0) {} + FfmpegBuffer() noexcept = default; - ~FfmpegBuffer() { + ~FfmpegBuffer() noexcept { av_free(data); } gcc_malloc - void *Get(size_t min_size) { + void *Get(size_t min_size) noexcept { av_fast_malloc(&data, &size, min_size); return data; } template - T *GetT(size_t n) { + T *GetT(size_t n) noexcept { return (T *)Get(n * sizeof(T)); } }; From a8a80ee689d8f2ac0d64102c910751d43b41969d Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 4 Nov 2020 19:51:07 +0100 Subject: [PATCH 15/22] lib/ffmpeg/Buffer: disallow copying --- src/lib/ffmpeg/Buffer.hxx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/ffmpeg/Buffer.hxx b/src/lib/ffmpeg/Buffer.hxx index 330f5ed11..521f40d2e 100644 --- a/src/lib/ffmpeg/Buffer.hxx +++ b/src/lib/ffmpeg/Buffer.hxx @@ -37,6 +37,9 @@ public: av_free(data); } + FfmpegBuffer(const FfmpegBuffer &) = delete; + FfmpegBuffer &operator=(const FfmpegBuffer &) = delete; + gcc_malloc void *Get(size_t min_size) noexcept { av_fast_malloc(&data, &size, min_size); From 42c9d765cf6bfde93b4ebad3b2c031afda21c864 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 4 Nov 2020 20:06:32 +0100 Subject: [PATCH 16/22] lib/ffmpeg/Buffer: add missing include --- src/lib/ffmpeg/Buffer.hxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/ffmpeg/Buffer.hxx b/src/lib/ffmpeg/Buffer.hxx index 521f40d2e..300c5bf19 100644 --- a/src/lib/ffmpeg/Buffer.hxx +++ b/src/lib/ffmpeg/Buffer.hxx @@ -20,6 +20,8 @@ #ifndef MPD_FFMPEG_BUFFER_HXX #define MPD_FFMPEG_BUFFER_HXX +#include "util/Compiler.h" + extern "C" { #include } From 7a0342c8bbf0f1f0138c1d394dd11f4e7a8b7ac5 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 4 Nov 2020 19:57:25 +0100 Subject: [PATCH 17/22] decoder/ffmpeg: use AVFrame fields instead of AVCodecContext fields --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 26 ++++++++++----------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 1a601a55d..2b8774bd3 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -182,25 +182,24 @@ start_time_fallback(const AVStream &stream) * Throws #std::exception on error. */ static ConstBuffer -copy_interleave_frame(const AVCodecContext &codec_context, - const AVFrame &frame, - FfmpegBuffer &global_buffer) +copy_interleave_frame(const AVFrame &frame, FfmpegBuffer &global_buffer) { assert(frame.nb_samples > 0); + const AVSampleFormat format = AVSampleFormat(frame.format); + const unsigned channels = frame.channels; + const std::size_t n_frames = frame.nb_samples; + int plane_size; const int data_size = - av_samples_get_buffer_size(&plane_size, - codec_context.channels, - frame.nb_samples, - codec_context.sample_fmt, 1); + av_samples_get_buffer_size(&plane_size, channels, + n_frames, format, 1); assert(data_size != 0); if (data_size < 0) throw MakeFfmpegError(data_size); void *output_buffer; - if (av_sample_fmt_is_planar(codec_context.sample_fmt) && - codec_context.channels > 1) { + if (av_sample_fmt_is_planar(format) && channels > 1) { output_buffer = global_buffer.GetT(data_size); if (output_buffer == nullptr) /* Not enough memory - shouldn't happen */ @@ -208,9 +207,9 @@ copy_interleave_frame(const AVCodecContext &codec_context, PcmInterleave(output_buffer, ConstBuffer((const void *const*)frame.extended_data, - codec_context.channels), - frame.nb_samples, - av_get_bytes_per_sample(codec_context.sample_fmt)); + channels), + n_frames, + av_get_bytes_per_sample(format)); } else { output_buffer = frame.extended_data[0]; } @@ -257,8 +256,7 @@ FfmpegSendFrame(DecoderClient &client, InputStream *is, size_t &skip_bytes, FfmpegBuffer &buffer) { - ConstBuffer output_buffer = - copy_interleave_frame(codec_context, frame, buffer); + ConstBuffer output_buffer = copy_interleave_frame(frame, buffer); if (skip_bytes > 0) { if (skip_bytes >= output_buffer.size) { From 2d606fa9895f195bb17db513fe1b467bfd5d498b Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 4 Nov 2020 19:56:24 +0100 Subject: [PATCH 18/22] decoder/ffmpeg: move code to lib/ffmpeg/Interleave.cxx To be reused by the FFmpeg filter plugin. --- src/decoder/plugins/FfmpegDecoderPlugin.cxx | 45 +------------ src/lib/ffmpeg/Interleave.cxx | 71 +++++++++++++++++++++ src/lib/ffmpeg/Interleave.hxx | 40 ++++++++++++ src/lib/ffmpeg/meson.build | 1 + 4 files changed, 115 insertions(+), 42 deletions(-) create mode 100644 src/lib/ffmpeg/Interleave.cxx create mode 100644 src/lib/ffmpeg/Interleave.hxx diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 2b8774bd3..05940fc41 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -25,6 +25,7 @@ #include "lib/ffmpeg/Domain.hxx" #include "lib/ffmpeg/Error.hxx" #include "lib/ffmpeg/Init.hxx" +#include "lib/ffmpeg/Interleave.hxx" #include "lib/ffmpeg/Buffer.hxx" #include "lib/ffmpeg/Frame.hxx" #include "lib/ffmpeg/Format.hxx" @@ -176,47 +177,6 @@ start_time_fallback(const AVStream &stream) return FfmpegTimestampFallback(stream.start_time, 0); } -/** - * Copy PCM data from a non-empty AVFrame to an interleaved buffer. - * - * Throws #std::exception on error. - */ -static ConstBuffer -copy_interleave_frame(const AVFrame &frame, FfmpegBuffer &global_buffer) -{ - assert(frame.nb_samples > 0); - - const AVSampleFormat format = AVSampleFormat(frame.format); - const unsigned channels = frame.channels; - const std::size_t n_frames = frame.nb_samples; - - int plane_size; - const int data_size = - av_samples_get_buffer_size(&plane_size, channels, - n_frames, format, 1); - assert(data_size != 0); - if (data_size < 0) - throw MakeFfmpegError(data_size); - - void *output_buffer; - if (av_sample_fmt_is_planar(format) && channels > 1) { - output_buffer = global_buffer.GetT(data_size); - if (output_buffer == nullptr) - /* Not enough memory - shouldn't happen */ - throw std::bad_alloc(); - - PcmInterleave(output_buffer, - ConstBuffer((const void *const*)frame.extended_data, - channels), - n_frames, - av_get_bytes_per_sample(format)); - } else { - output_buffer = frame.extended_data[0]; - } - - return { output_buffer, (size_t)data_size }; -} - /** * Convert AVPacket::pts to a stream-relative time stamp (still in * AVStream::time_base units). Returns a negative value on error. @@ -256,7 +216,8 @@ FfmpegSendFrame(DecoderClient &client, InputStream *is, size_t &skip_bytes, FfmpegBuffer &buffer) { - ConstBuffer output_buffer = copy_interleave_frame(frame, buffer); + ConstBuffer output_buffer = + Ffmpeg::InterleaveFrame(frame, buffer); if (skip_bytes > 0) { if (skip_bytes >= output_buffer.size) { diff --git a/src/lib/ffmpeg/Interleave.cxx b/src/lib/ffmpeg/Interleave.cxx new file mode 100644 index 000000000..93a9a8bef --- /dev/null +++ b/src/lib/ffmpeg/Interleave.cxx @@ -0,0 +1,71 @@ +/* + * Copyright 2003-2020 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "Interleave.hxx" +#include "Buffer.hxx" +#include "Error.hxx" +#include "pcm/Interleave.hxx" +#include "util/ConstBuffer.hxx" + +extern "C" { +#include +} + +#include +#include // for std::bad_alloc + +namespace Ffmpeg { + +ConstBuffer +InterleaveFrame(const AVFrame &frame, FfmpegBuffer &buffer) +{ + assert(frame.nb_samples > 0); + + const AVSampleFormat format = AVSampleFormat(frame.format); + const unsigned channels = frame.channels; + const std::size_t n_frames = frame.nb_samples; + + int plane_size; + const int data_size = + av_samples_get_buffer_size(&plane_size, channels, + n_frames, format, 1); + assert(data_size != 0); + if (data_size < 0) + throw MakeFfmpegError(data_size); + + void *output_buffer; + if (av_sample_fmt_is_planar(format) && channels > 1) { + output_buffer = buffer.GetT(data_size); + if (output_buffer == nullptr) + /* Not enough memory - shouldn't happen */ + throw std::bad_alloc(); + + PcmInterleave(output_buffer, + ConstBuffer((const void *const*)frame.extended_data, + channels), + n_frames, + av_get_bytes_per_sample(format)); + } else { + output_buffer = frame.extended_data[0]; + } + + return { output_buffer, (size_t)data_size }; +} + +} // namespace Ffmpeg diff --git a/src/lib/ffmpeg/Interleave.hxx b/src/lib/ffmpeg/Interleave.hxx new file mode 100644 index 000000000..f2d433620 --- /dev/null +++ b/src/lib/ffmpeg/Interleave.hxx @@ -0,0 +1,40 @@ +/* + * Copyright 2003-2020 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FFMPEG_INTERLEAVE_HXX +#define MPD_FFMPEG_INTERLEAVE_HXX + +struct AVFrame; +template struct ConstBuffer; +class FfmpegBuffer; + +namespace Ffmpeg { + +/** + * Return interleaved data from the given non-empty #AVFrame. If the + * data is planar, then the data is copied to a buffer. + * + * Throws on error. + */ +ConstBuffer +InterleaveFrame(const AVFrame &frame, FfmpegBuffer &buffer); + +} // namespace Ffmpeg + +#endif diff --git a/src/lib/ffmpeg/meson.build b/src/lib/ffmpeg/meson.build index 0a0217ccf..fde110edc 100644 --- a/src/lib/ffmpeg/meson.build +++ b/src/lib/ffmpeg/meson.build @@ -26,6 +26,7 @@ endif ffmpeg = static_library( 'ffmpeg', 'Init.cxx', + 'Interleave.cxx', 'LogError.cxx', 'LogCallback.cxx', 'Error.cxx', From 226eb263001ebaa6bdaa3ca1c8087abf0d06e6e3 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 4 Nov 2020 20:08:48 +0100 Subject: [PATCH 19/22] filter/ffmpeg: interleave the output AVFrame If the FFmpeg filter outputs planar data, interleave it, just like the FFmpeg decoder plugin does. --- NEWS | 1 + src/filter/plugins/FfmpegFilter.cxx | 3 ++- src/filter/plugins/FfmpegFilter.hxx | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 756259edc..46c7d8324 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,7 @@ ver 0.22.3 (not yet released) - add option "as_directory", making CUE file expansion optional * filter - fix garbage after "Audio format not supported by filter" message + - ffmpeg: support planar output ver 0.22.2 (2020/10/28) * database diff --git a/src/filter/plugins/FfmpegFilter.cxx b/src/filter/plugins/FfmpegFilter.cxx index 1b841833a..cd076200b 100644 --- a/src/filter/plugins/FfmpegFilter.cxx +++ b/src/filter/plugins/FfmpegFilter.cxx @@ -18,6 +18,7 @@ */ #include "FfmpegFilter.hxx" +#include "lib/ffmpeg/Interleave.hxx" #include "lib/ffmpeg/SampleFormat.hxx" #include "util/ConstBuffer.hxx" @@ -79,5 +80,5 @@ FfmpegFilter::FilterPCM(ConstBuffer src) /* TODO: call av_buffersink_get_frame() repeatedly? Not possible with MPD's current Filter API */ - return {frame.GetData(0), frame->nb_samples * GetOutAudioFormat().GetFrameSize()}; + return Ffmpeg::InterleaveFrame(*frame, interleave_buffer); } diff --git a/src/filter/plugins/FfmpegFilter.hxx b/src/filter/plugins/FfmpegFilter.hxx index 8a048cffc..286780d9d 100644 --- a/src/filter/plugins/FfmpegFilter.hxx +++ b/src/filter/plugins/FfmpegFilter.hxx @@ -21,6 +21,7 @@ #define MPD_FFMPEG_FILTER__HXX #include "filter/Filter.hxx" +#include "lib/ffmpeg/Buffer.hxx" #include "lib/ffmpeg/Filter.hxx" #include "lib/ffmpeg/Frame.hxx" @@ -32,6 +33,8 @@ class FfmpegFilter final : public Filter { Ffmpeg::FilterContext buffer_src, buffer_sink; Ffmpeg::Frame frame; + FfmpegBuffer interleave_buffer; + const int in_format, in_sample_rate, in_channels; const size_t in_audio_frame_size; From 77c14692c9b7cb85efb8e1fb1a6024284a75d53c Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 4 Nov 2020 16:50:10 +0100 Subject: [PATCH 20/22] filter/AutoConvert: eliminate AutoConvertFilter if possible If no conversion is necessary, return the child Filter as-is. This allows removing all nullptr checks from AutoConvertFilter. --- .../plugins/AutoConvertFilterPlugin.cxx | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/filter/plugins/AutoConvertFilterPlugin.cxx b/src/filter/plugins/AutoConvertFilterPlugin.cxx index 0fe11cff3..d3eb38447 100644 --- a/src/filter/plugins/AutoConvertFilterPlugin.cxx +++ b/src/filter/plugins/AutoConvertFilterPlugin.cxx @@ -47,9 +47,7 @@ public: void Reset() noexcept override { filter->Reset(); - - if (convert) - convert->Reset(); + convert->Reset(); } ConstBuffer FilterPCM(ConstBuffer src) override; @@ -81,13 +79,14 @@ PreparedAutoConvertFilter::Open(AudioFormat &in_audio_format) /* need to convert? */ - std::unique_ptr convert; - if (in_audio_format != child_audio_format) { - /* yes - create a convert_filter */ + if (in_audio_format == child_audio_format) + /* no */ + return new_filter; - convert = convert_filter_new(in_audio_format, - child_audio_format); - } + /* yes - create a convert_filter */ + + auto convert = convert_filter_new(in_audio_format, + child_audio_format); return std::make_unique(std::move(new_filter), std::move(convert)); @@ -96,20 +95,16 @@ PreparedAutoConvertFilter::Open(AudioFormat &in_audio_format) ConstBuffer AutoConvertFilter::FilterPCM(ConstBuffer src) { - if (convert != nullptr) - src = convert->FilterPCM(src); - + src = convert->FilterPCM(src); return filter->FilterPCM(src); } ConstBuffer AutoConvertFilter::Flush() { - if (convert != nullptr) { - auto result = convert->Flush(); - if (!result.IsNull()) - return filter->FilterPCM(result); - } + auto result = convert->Flush(); + if (!result.IsNull()) + return filter->FilterPCM(result); return filter->Flush(); } From 0c965d05734d18b4cfd2e4b5ac1b2a6ca0c6515d Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 4 Nov 2020 16:54:41 +0100 Subject: [PATCH 21/22] filter/AutoConvert: move the Filter class to TwoFilters.cxx --- .../plugins/AutoConvertFilterPlugin.cxx | 49 ++----------------- src/filter/plugins/TwoFilters.cxx | 39 +++++++++++++++ src/filter/plugins/TwoFilters.hxx | 49 +++++++++++++++++++ src/filter/plugins/meson.build | 1 + 4 files changed, 92 insertions(+), 46 deletions(-) create mode 100644 src/filter/plugins/TwoFilters.cxx create mode 100644 src/filter/plugins/TwoFilters.hxx diff --git a/src/filter/plugins/AutoConvertFilterPlugin.cxx b/src/filter/plugins/AutoConvertFilterPlugin.cxx index d3eb38447..b9bbd256f 100644 --- a/src/filter/plugins/AutoConvertFilterPlugin.cxx +++ b/src/filter/plugins/AutoConvertFilterPlugin.cxx @@ -19,6 +19,7 @@ #include "AutoConvertFilterPlugin.hxx" #include "ConvertFilterPlugin.hxx" +#include "TwoFilters.hxx" #include "filter/Filter.hxx" #include "filter/Prepared.hxx" #include "pcm/AudioFormat.hxx" @@ -27,33 +28,6 @@ #include #include -class AutoConvertFilter final : public Filter { - /** - * The underlying filter. - */ - std::unique_ptr filter; - - /** - * A convert_filter, just in case conversion is needed. nullptr - * if unused. - */ - std::unique_ptr convert; - -public: - AutoConvertFilter(std::unique_ptr &&_filter, - std::unique_ptr &&_convert) - :Filter(_filter->GetOutAudioFormat()), - filter(std::move(_filter)), convert(std::move(_convert)) {} - - void Reset() noexcept override { - filter->Reset(); - convert->Reset(); - } - - ConstBuffer FilterPCM(ConstBuffer src) override; - ConstBuffer Flush() override; -}; - class PreparedAutoConvertFilter final : public PreparedFilter { /** * The underlying filter. @@ -88,25 +62,8 @@ PreparedAutoConvertFilter::Open(AudioFormat &in_audio_format) auto convert = convert_filter_new(in_audio_format, child_audio_format); - return std::make_unique(std::move(new_filter), - std::move(convert)); -} - -ConstBuffer -AutoConvertFilter::FilterPCM(ConstBuffer src) -{ - src = convert->FilterPCM(src); - return filter->FilterPCM(src); -} - -ConstBuffer -AutoConvertFilter::Flush() -{ - auto result = convert->Flush(); - if (!result.IsNull()) - return filter->FilterPCM(result); - - return filter->Flush(); + return std::make_unique(std::move(convert), + std::move(new_filter)); } std::unique_ptr diff --git a/src/filter/plugins/TwoFilters.cxx b/src/filter/plugins/TwoFilters.cxx new file mode 100644 index 000000000..0defa5799 --- /dev/null +++ b/src/filter/plugins/TwoFilters.cxx @@ -0,0 +1,39 @@ +/* + * Copyright 2003-2020 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "TwoFilters.hxx" +#include "util/ConstBuffer.hxx" + +ConstBuffer +TwoFilters::FilterPCM(ConstBuffer src) +{ + return second->FilterPCM(first->FilterPCM(src)); +} + +ConstBuffer +TwoFilters::Flush() +{ + auto result = first->Flush(); + if (!result.IsNull()) + /* Flush() output from the first Filter must be + filtered by the second Filter */ + return second->FilterPCM(result); + + return second->Flush(); +} diff --git a/src/filter/plugins/TwoFilters.hxx b/src/filter/plugins/TwoFilters.hxx new file mode 100644 index 000000000..cbd86a6df --- /dev/null +++ b/src/filter/plugins/TwoFilters.hxx @@ -0,0 +1,49 @@ +/* + * Copyright 2003-2020 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_WITH_CONVERT_FILTER_HXX +#define MPD_WITH_CONVERT_FILTER_HXX + +#include "filter/Filter.hxx" + +#include + +/** + * A #Filter implementation which chains two other filters. + */ +class TwoFilters final : public Filter { + std::unique_ptr first, second; + +public: + template + TwoFilters(F &&_first, S &&_second) noexcept + :Filter(_second->GetOutAudioFormat()), + first(std::forward(_first)), + second(std::forward(_second)) {} + + void Reset() noexcept override { + first->Reset(); + second->Reset(); + } + + ConstBuffer FilterPCM(ConstBuffer src) override; + ConstBuffer Flush() override; +}; + +#endif diff --git a/src/filter/plugins/meson.build b/src/filter/plugins/meson.build index 8ad526c3d..cf313f20b 100644 --- a/src/filter/plugins/meson.build +++ b/src/filter/plugins/meson.build @@ -14,6 +14,7 @@ filter_plugins = static_library( 'filter_plugins', '../../AudioCompress/compress.c', 'NullFilterPlugin.cxx', + 'TwoFilters.cxx', 'ChainFilterPlugin.cxx', 'AutoConvertFilterPlugin.cxx', 'ConvertFilterPlugin.cxx', From 2da3cff1e8a5e1558d47a8493eaa9503b2d56b9e Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 4 Nov 2020 17:07:28 +0100 Subject: [PATCH 22/22] filter/LoadChain: use the AutoConvertFilter This adds support for input samples other than 16 bit to the FFmpeg filter plugin. --- NEWS | 1 + src/filter/LoadChain.cxx | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 46c7d8324..e0e7d2a83 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,7 @@ ver 0.22.3 (not yet released) * filter - fix garbage after "Audio format not supported by filter" message - ffmpeg: support planar output + - ffmpeg: support sample formats other than 16 bit ver 0.22.2 (2020/10/28) * database diff --git a/src/filter/LoadChain.cxx b/src/filter/LoadChain.cxx index 01c886fce..cca351a72 100644 --- a/src/filter/LoadChain.cxx +++ b/src/filter/LoadChain.cxx @@ -20,6 +20,7 @@ #include "LoadChain.hxx" #include "Factory.hxx" #include "Prepared.hxx" +#include "plugins/AutoConvertFilterPlugin.hxx" #include "plugins/ChainFilterPlugin.hxx" #include "util/IterableSplitString.hxx" @@ -29,11 +30,14 @@ static void filter_chain_append_new(PreparedFilter &chain, FilterFactory &factory, std::string_view template_name) { + /* using the AutoConvert filter just in case the specified + filter plugin does not support the exact input format */ + filter_chain_append(chain, template_name, /* unfortunately, MakeFilter() wants a null-terminated string, so we need to copy it here */ - factory.MakeFilter(std::string(template_name).c_str())); + autoconvert_filter_new(factory.MakeFilter(std::string(template_name).c_str()))); } void