diff --git a/NEWS b/NEWS index 2d9448207..3f3023a00 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,14 @@ ver 0.23 (not yet released) * protocol - new command "getvol" +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 + - ffmpeg: support planar output + - ffmpeg: support sample formats other than 16 bit + ver 0.22.2 (2020/10/28) * database - simple: purge songs and virtual directories for unavailable plugins 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/doc/plugins.rst b/doc/plugins.rst index 107140942..06fe714c1 100644 --- a/doc/plugins.rst +++ b/doc/plugins.rst @@ -1229,23 +1229,25 @@ Playlist plugins asx --- -Reads .asx playlist files. +Reads :file:`.asx` playlist files. + +.. _cue_playlist: 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 +1255,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 ---------- diff --git a/doc/user.rst b/doc/user.rst index db87ee7d0..abd7c7b1f 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. @@ -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/meson.build b/meson.build index c1cf5b569..dce9ca2ea 100644 --- a/meson.build +++ b/meson.build @@ -4,8 +4,8 @@ project( version: '0.23~git', 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', @@ -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' 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/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 1a601a55d..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,48 +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 AVCodecContext &codec_context, - const AVFrame &frame, - FfmpegBuffer &global_buffer) -{ - assert(frame.nb_samples > 0); - - 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); - 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) { - 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, - codec_context.channels), - frame.nb_samples, - av_get_bytes_per_sample(codec_context.sample_fmt)); - } 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. @@ -258,7 +217,7 @@ FfmpegSendFrame(DecoderClient &client, InputStream *is, FfmpegBuffer &buffer) { ConstBuffer output_buffer = - copy_interleave_frame(codec_context, frame, buffer); + Ffmpeg::InterleaveFrame(frame, buffer); if (skip_bytes > 0) { if (skip_bytes >= output_buffer.size) { diff --git a/src/filter/LoadChain.cxx b/src/filter/LoadChain.cxx index 26e6f883a..cca351a72 100644 --- a/src/filter/LoadChain.cxx +++ b/src/filter/LoadChain.cxx @@ -20,19 +20,24 @@ #include "LoadChain.hxx" #include "Factory.hxx" #include "Prepared.hxx" +#include "plugins/AutoConvertFilterPlugin.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) + 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, - factory.MakeFilter(template_name)); + /* unfortunately, MakeFilter() wants a + null-terminated string, so we need to + copy it here */ + autoconvert_filter_new(factory.MakeFilter(std::string(template_name).c_str()))); } void @@ -40,18 +45,10 @@ 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; + filter_chain_append_new(chain, factory, i); } } diff --git a/src/filter/plugins/AutoConvertFilterPlugin.cxx b/src/filter/plugins/AutoConvertFilterPlugin.cxx index 54c692406..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,35 +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(); - - if (convert) - convert->Reset(); - } - - ConstBuffer FilterPCM(ConstBuffer src) override; - ConstBuffer Flush() override; -}; - class PreparedAutoConvertFilter final : public PreparedFilter { /** * The underlying filter. @@ -81,37 +53,17 @@ 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.reset(convert_filter_new(in_audio_format, - child_audio_format)); - } + /* yes - create a convert_filter */ - return std::make_unique(std::move(new_filter), - std::move(convert)); -} + auto convert = convert_filter_new(in_audio_format, + child_audio_format); -ConstBuffer -AutoConvertFilter::FilterPCM(ConstBuffer src) -{ - if (convert != nullptr) - 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); - } - - return filter->Flush(); + return std::make_unique(std::move(convert), + std::move(new_filter)); } std::unique_ptr diff --git a/src/filter/plugins/ChainFilterPlugin.cxx b/src/filter/plugins/ChainFilterPlugin.cxx index 07bca48af..249130081 100644 --- a/src/filter/plugins/ChainFilterPlugin.cxx +++ b/src/filter/plugins/ChainFilterPlugin.cxx @@ -28,15 +28,14 @@ #include #include #include +#include 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 +49,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(); } @@ -75,10 +73,10 @@ private: class PreparedChainFilter final : public PreparedFilter { struct Child { - const char *name; + 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)) {} @@ -91,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)); } @@ -108,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; @@ -121,7 +119,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; @@ -177,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 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); 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; 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', diff --git a/src/lib/ffmpeg/Buffer.hxx b/src/lib/ffmpeg/Buffer.hxx index cab4375d9..300c5bf19 100644 --- a/src/lib/ffmpeg/Buffer.hxx +++ b/src/lib/ffmpeg/Buffer.hxx @@ -20,36 +20,36 @@ #ifndef MPD_FFMPEG_BUFFER_HXX #define MPD_FFMPEG_BUFFER_HXX +#include "util/Compiler.h" + extern "C" { #include } #include -/* suppress the ffmpeg compatibility macro */ -#ifdef SampleFormat -#undef SampleFormat -#endif - 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); } + FfmpegBuffer(const FfmpegBuffer &) = delete; + FfmpegBuffer &operator=(const FfmpegBuffer &) = delete; + 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)); } }; 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/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 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', diff --git a/src/playlist/PlaylistRegistry.cxx b/src/playlist/PlaylistRegistry.cxx index 9aa87b73b..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) @@ -207,11 +224,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 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. */