Compare commits

...

39 Commits

Author SHA1 Message Date
Max Kellermann
f6c65cba58 release v0.22.3 2020-11-06 16:12:54 +01:00
Max Kellermann
f849b07766 storage/curl: fix nullptr dereference
Pass a std::string to PathTraitsUTF8::Relative(), implicitly casting
it to std::string_view.  This selects the right overload which returns
std::string_view instead of `const char *`; the latter could return
`nullptr` which would cause the implicit conversion of the return
value to std::string_view to crash.

Regression caused by commits ead208987d and a98d627c0b.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/995
2020-11-06 15:35:47 +01:00
Max Kellermann
2da3cff1e8 filter/LoadChain: use the AutoConvertFilter
This adds support for input samples other than 16 bit to the FFmpeg
filter plugin.
2020-11-04 20:15:19 +01:00
Max Kellermann
0c965d0573 filter/AutoConvert: move the Filter class to TwoFilters.cxx 2020-11-04 20:15:19 +01:00
Max Kellermann
77c14692c9 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.
2020-11-04 20:15:19 +01:00
Max Kellermann
226eb26300 filter/ffmpeg: interleave the output AVFrame
If the FFmpeg filter outputs planar data, interleave it, just like the
FFmpeg decoder plugin does.
2020-11-04 20:15:19 +01:00
Max Kellermann
2d606fa989 decoder/ffmpeg: move code to lib/ffmpeg/Interleave.cxx
To be reused by the FFmpeg filter plugin.
2020-11-04 20:15:19 +01:00
Max Kellermann
7a0342c8bb decoder/ffmpeg: use AVFrame fields instead of AVCodecContext fields 2020-11-04 20:06:45 +01:00
Max Kellermann
42c9d765cf lib/ffmpeg/Buffer: add missing include 2020-11-04 20:06:41 +01:00
Max Kellermann
a8a80ee689 lib/ffmpeg/Buffer: disallow copying 2020-11-04 19:51:21 +01:00
Max Kellermann
f9bdb4b0b8 lib/ffmpeg/Buffer: add noexcept 2020-11-04 19:50:38 +01:00
Max Kellermann
9332527872 lib/ffmpeg/{Buffer,Time}: remove obsolete "#undef SampleFormat"
This compatibility macro has been removed from FFmpeg long ago.
2020-11-04 19:50:33 +01:00
Max Kellermann
84f772357e filter/convert: convert_filter_new() returns std::unique_ptr 2020-11-04 16:47:11 +01:00
Max Kellermann
f2b9785a67 filter/chain: pass std::string_view to filter_chain_append() 2020-11-04 16:37:53 +01:00
Max Kellermann
eeaec99c59 filter/LoadChain: use IterableSplitString() 2020-11-04 16:36:11 +01:00
Max Kellermann
b0002e3b73 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.
2020-11-04 16:34:38 +01:00
Max Kellermann
27c589da97 filter/chain: remove unused field ChainFilter::Child::name 2020-11-04 16:26:50 +01:00
Max Kellermann
6484af472b increment version number to 0.22.3 2020-11-04 16:14:40 +01:00
Max Kellermann
92a218b7a9 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.
2020-11-04 16:13:12 +01:00
Max Kellermann
d69a1f98af doc/plugins.rst: more markup 2020-11-04 16:11:26 +01:00
Max Kellermann
23a6f62ea3 doc/user.rst: fix typo 2020-11-04 15:59:40 +01:00
Max Kellermann
e0d3ca71b3 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.
2020-11-04 14:38:58 +01:00
Max Kellermann
4f40b9f7cf 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.
2020-11-04 13:43:21 +01:00
Max Kellermann
bb009daf66 playlist/registry: simplify ExtractMimeTypeMainPart() 2020-11-04 13:34:04 +01:00
Max Kellermann
dc432f3ffa release v0.22.2 2020-10-28 17:25:33 +01:00
Max Kellermann
37710195ca meson_options.txt: disable the "smbclient" plugin by default
The bug https://bugzilla.samba.org/show_bug.cgi?id=11413 makes MPD
crash after at most a minute of using the plugin.  Since this bug is
five years old already and it doesn't look like it will ever be fixed,
all libsmbclient code in MPD is scheduled for removal.  For now, the
plugin is disabled by default so people are less likely to hit the
crash bug.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/991
2020-10-28 17:21:27 +01:00
Max Kellermann
7b9295ff99 lib/yajl/Handle: strip newlines from error messages
Closes https://github.com/MusicPlayerDaemon/MPD/issues/981
2020-10-28 16:06:52 +01:00
Max Kellermann
5f61d440eb lib/yajl/Handle: un-inline the throwing code
Reduces header dependencies.
2020-10-28 16:02:14 +01:00
Max Kellermann
6bc73a9ebe util/FormatString: update API documentation 2020-10-28 15:48:42 +01:00
Max Kellermann
1195eb266e protocol/Ack: remove unused variable ack_domain 2020-10-28 15:47:05 +01:00
Max Kellermann
3562a3e51e Main: save the state_file on shutdown
This got lost in commit 5d597a3646 (v0.21.19), but it was never
noticed because the state_file_interval was way too short due to
commit 3413d1bf23, fixed recently by commit 27cc7b352d
2020-10-28 15:29:47 +01:00
Max Kellermann
bbfa6fe632 db/simple: purge songs for unavailable decoder plugins on update 2020-10-28 14:36:20 +01:00
Max Kellermann
bf97d13d0b fs/Traits: add GetPathSuffix() 2020-10-28 14:29:46 +01:00
Max Kellermann
b5673b6333 db/simple/Directory: add pure attribute 2020-10-28 14:24:58 +01:00
Max Kellermann
ee802867df db/update/Walk: add code comments 2020-10-28 14:23:39 +01:00
Max Kellermann
ecaa51e322 db/simple: purge special directories for unavailable plugins on update 2020-10-27 19:14:31 +01:00
Max Kellermann
0779333064 db/update/Walk: adjust lamba indent 2020-10-27 19:14:31 +01:00
Max Kellermann
6f1a4a73b7 fs/Traits: add GetFilenameSuffix() 2020-10-27 19:14:31 +01:00
Max Kellermann
945ed2610a increment version number to 0.22.2 2020-10-27 18:34:39 +01:00
42 changed files with 583 additions and 223 deletions

21
NEWS

@@ -1,3 +1,24 @@
ver 0.22.3 (2020/11/06)
* playlist
- add option "as_directory", making CUE file expansion optional
* storage
- curl: fix crash bug
* 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
on update
* input
- qobuz/tidal: fix protocol errors due to newlines in error messages
- smbclient: disable by default due to libsmbclient crash bug
* playlist
- soundcloud: fix protocol errors due to newlines in error messages
* state_file: save on shutdown
ver 0.22.1 (2020/10/17)
* decoder
- opus: apply the OpusHead output gain even if there is no EBU R128 tag

@@ -38,7 +38,7 @@ author = 'Max Kellermann'
# built documents.
#
# The short X.Y version.
version = '0.22.1'
version = '0.22.3'
# The full version, including alpha/beta/rc tags.
release = version

@@ -71,6 +71,11 @@ Load music files from a SMB/CIFS server. It is used when
:code:`music_directory` contains a ``smb://`` URI, for example
:samp:`smb://myfileserver/Music`.
Note that :file:`libsmbclient` has a serious bug which causes MPD to
crash, and therefore this plugin is disabled by default and should not
be used until the bug is fixed:
https://bugzilla.samba.org/show_bug.cgi?id=11413
nfs
---
@@ -1224,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
----
@@ -1248,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
----------

@@ -86,7 +86,7 @@ For example, the following installs a fairly complete list of build dependencies
libpulse-dev libshout3-dev \
libsndio-dev \
libmpdclient-dev \
libnfs-dev libsmbclient-dev \
libnfs-dev \
libupnp-dev \
libavahi-client-dev \
libsqlite3-dev \
@@ -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 <cue_playlist>`.
More information can be found in the :ref:`playlist_plugins`
reference.

@@ -1,11 +1,11 @@
project(
'mpd',
['c', 'cpp'],
version: '0.22.1',
version: '0.22.3',
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'
@@ -224,7 +229,6 @@ log_dep = declare_dependency(
sources = [
version_cxx,
'src/Main.cxx',
'src/protocol/Ack.cxx',
'src/protocol/ArgParser.cxx',
'src/protocol/Result.cxx',
'src/command/CommandError.cxx',

@@ -92,7 +92,11 @@ option('cdio_paranoia', type: 'feature', description: 'libcdio_paranoia input pl
option('curl', type: 'feature', description: 'HTTP client using CURL')
option('mms', type: 'feature', description: 'MMS protocol support using libmms')
option('nfs', type: 'feature', description: 'NFS protocol support using libnfs')
option('smbclient', type: 'feature', description: 'SMB support using libsmbclient')
# The "smbclient" plugin is disabled by default because libsmbclient
# has a serious bug which crashes MPD very quickly:
# https://bugzilla.samba.org/show_bug.cgi?id=11413
option('smbclient', type: 'feature', value: 'disabled', description: 'SMB support using libsmbclient')
#
# Commercial services

@@ -533,6 +533,9 @@ MainConfigured(const struct options &options, const ConfigData &raw_config)
/* cleanup */
if (instance.state_file)
instance.state_file->Write();
instance.BeginShutdownUpdate();
ZeroconfDeinit();

@@ -23,6 +23,7 @@
#include "db/plugins/simple/Directory.hxx"
#include "storage/StorageInterface.hxx"
#include "storage/FileInfo.hxx"
#include "decoder/DecoderList.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/FileInfo.hxx"
#include "tag/Builder.hxx"
@@ -40,6 +41,14 @@
#ifdef ENABLE_DATABASE
bool
Song::IsPluginAvailable() const noexcept
{
const char *suffix = GetFilenameSuffix();
return suffix != nullptr &&
decoder_plugins_supports_suffix(suffix);
}
SongPtr
Song::LoadFile(Storage &storage, const char *path_utf8, Directory &parent)
{

@@ -31,6 +31,7 @@ db_glue_sources = [
'update/Remove.cxx',
'update/ExcludeList.cxx',
'update/VirtualDirectory.cxx',
'update/SpecialDirectory.cxx',
'DatabaseGlue.cxx',
'Configured.cxx',
'DatabaseSong.cxx',

@@ -132,6 +132,14 @@ public:
return mounted_database != nullptr;
}
/**
* Checks whether this is a "special" directory
* (e.g. #DEVICE_PLAYLIST) and whether the underlying plugin
* is available.
*/
gcc_pure
bool IsPluginAvailable() const noexcept;
/**
* Remove this #Directory object from its parent and free it. This
* must not be called with the root Directory.

@@ -34,6 +34,14 @@ Song::Song(DetachedSong &&other, Directory &_parent) noexcept
{
}
const char *
Song::GetFilenameSuffix() const noexcept
{
return target.empty()
? PathTraitsUTF8::GetFilenameSuffix(filename.c_str())
: PathTraitsUTF8::GetPathSuffix(target.c_str());
}
std::string
Song::GetURI() const noexcept
{

@@ -108,6 +108,16 @@ struct Song {
Song(DetachedSong &&other, Directory &_parent) noexcept;
gcc_pure
const char *GetFilenameSuffix() const noexcept;
/**
* Checks whether the decoder plugin for this song is
* available.
*/
gcc_pure
bool IsPluginAvailable() const noexcept;
/**
* allocate a new song structure with a local file name and attempt to
* load its metadata. If all decoder plugin fail to read its meta

@@ -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);

@@ -0,0 +1,83 @@
/*
* 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 "db/plugins/simple/Directory.hxx"
#include "archive/ArchiveList.hxx"
#include "decoder/DecoderList.hxx"
#include "playlist/PlaylistRegistry.hxx"
#include "fs/Traits.hxx"
gcc_pure
static bool
HaveArchivePluginForFilename(const char *filename) noexcept
{
#ifdef ENABLE_ARCHIVE
const char *suffix = PathTraitsUTF8::GetFilenameSuffix(filename);
return suffix != nullptr &&
archive_plugin_from_suffix(suffix) != nullptr;
#else
(void)filename;
return false;
#endif
}
gcc_pure
static bool
HaveContainerPluginForFilename(const char *filename) noexcept
{
const char *suffix = PathTraitsUTF8::GetFilenameSuffix(filename);
return suffix != nullptr &&
// TODO: check if this plugin really supports containers
decoder_plugins_supports_suffix(suffix);
}
gcc_pure
static bool
HavePlaylistPluginForFilename(const char *filename) noexcept
{
const char *suffix = PathTraitsUTF8::GetFilenameSuffix(filename);
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
Directory::IsPluginAvailable() const noexcept
{
switch (device) {
case DEVICE_INARCHIVE:
return HaveArchivePluginForFilename(GetName());
case DEVICE_CONTAINER:
return HaveContainerPluginForFilename(GetName());
case DEVICE_PLAYLIST:
return HavePlaylistPluginForFilename(GetName());
default:
return true;
}
}

@@ -69,46 +69,58 @@ UpdateWalk::RemoveExcludedFromDirectory(Directory &directory,
const ScopeDatabaseLock protect;
directory.ForEachChildSafe([&](Directory &child){
const auto name_fs =
AllocatedPath::FromUTF8(child.GetName());
const auto name_fs =
AllocatedPath::FromUTF8(child.GetName());
if (name_fs.IsNull() || exclude_list.Check(name_fs)) {
editor.DeleteDirectory(&child);
modified = true;
}
});
if (name_fs.IsNull() || exclude_list.Check(name_fs)) {
editor.DeleteDirectory(&child);
modified = true;
}
});
directory.ForEachSongSafe([&](Song &song){
assert(&song.parent == &directory);
assert(&song.parent == &directory);
const auto name_fs = AllocatedPath::FromUTF8(song.filename);
if (name_fs.IsNull() || exclude_list.Check(name_fs)) {
editor.DeleteSong(directory, &song);
modified = true;
}
});
const auto name_fs = AllocatedPath::FromUTF8(song.filename);
if (name_fs.IsNull() || exclude_list.Check(name_fs)) {
editor.DeleteSong(directory, &song);
modified = true;
}
});
}
inline void
UpdateWalk::PurgeDeletedFromDirectory(Directory &directory) noexcept
{
directory.ForEachChildSafe([&](Directory &child){
if (child.IsMount() || DirectoryExists(storage, child))
return;
if (child.IsMount())
/* mount points are always preserved */
return;
editor.LockDeleteDirectory(&child);
if (DirectoryExists(storage, child) &&
child.IsPluginAvailable())
return;
modified = true;
});
/* the directory was deleted (or the plugin which
handles this "virtual" directory is unavailable) */
editor.LockDeleteDirectory(&child);
modified = true;
});
directory.ForEachSongSafe([&](Song &song){
if (!directory_child_is_regular(storage, directory,
song.filename)) {
editor.LockDeleteSong(directory, &song);
if (!directory_child_is_regular(storage, directory,
song.filename) ||
!song.IsPluginAvailable()) {
/* the song file was deleted (or the decoder
plugin is unavailable) */
modified = true;
}
});
editor.LockDeleteSong(directory, &song);
modified = true;
}
});
for (auto i = directory.playlists.begin(),
end = directory.playlists.end();

@@ -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<void>
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<uint8_t>(data_size);
if (output_buffer == nullptr)
/* Not enough memory - shouldn't happen */
throw std::bad_alloc();
PcmInterleave(output_buffer,
ConstBuffer<const void *>((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<void> output_buffer =
copy_interleave_frame(codec_context, frame, buffer);
Ffmpeg::InterleaveFrame(frame, buffer);
if (skip_bytes > 0) {
if (skip_bytes >= output_buffer.size) {

@@ -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 <algorithm>
#include <string>
#include <string.h>
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);
}
}

@@ -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 <cassert>
#include <memory>
class AutoConvertFilter final : public Filter {
/**
* The underlying filter.
*/
std::unique_ptr<Filter> filter;
/**
* A convert_filter, just in case conversion is needed. nullptr
* if unused.
*/
std::unique_ptr<Filter> convert;
public:
AutoConvertFilter(std::unique_ptr<Filter> &&_filter,
std::unique_ptr<Filter> &&_convert)
:Filter(_filter->GetOutAudioFormat()),
filter(std::move(_filter)), convert(std::move(_convert)) {}
void Reset() noexcept override {
filter->Reset();
if (convert)
convert->Reset();
}
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
ConstBuffer<void> 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<Filter> 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<AutoConvertFilter>(std::move(new_filter),
std::move(convert));
}
auto convert = convert_filter_new(in_audio_format,
child_audio_format);
ConstBuffer<void>
AutoConvertFilter::FilterPCM(ConstBuffer<void> src)
{
if (convert != nullptr)
src = convert->FilterPCM(src);
return filter->FilterPCM(src);
}
ConstBuffer<void>
AutoConvertFilter::Flush()
{
if (convert != nullptr) {
auto result = convert->Flush();
if (!result.IsNull())
return filter->FilterPCM(result);
}
return filter->Flush();
return std::make_unique<TwoFilters>(std::move(convert),
std::move(new_filter));
}
std::unique_ptr<PreparedFilter>

@@ -28,15 +28,14 @@
#include <cassert>
#include <list>
#include <memory>
#include <string>
class ChainFilter final : public Filter {
struct Child {
const char *name;
std::unique_ptr<Filter> filter;
Child(const char *_name,
std::unique_ptr<Filter> _filter) noexcept
:name(_name), filter(std::move(_filter)) {}
explicit Child(std::unique_ptr<Filter> &&_filter) noexcept
:filter(std::move(_filter)) {}
};
std::list<Child> children;
@@ -50,13 +49,12 @@ public:
explicit ChainFilter(AudioFormat _audio_format)
:Filter(_audio_format) {}
void Append(const char *name,
std::unique_ptr<Filter> filter) noexcept {
void Append(std::unique_ptr<Filter> 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<PreparedFilter> filter;
Child(const char *_name,
Child(std::string_view _name,
std::unique_ptr<PreparedFilter> _filter)
:name(_name), filter(std::move(_filter)) {}
@@ -91,7 +89,7 @@ class PreparedChainFilter final : public PreparedFilter {
std::list<Child> children;
public:
void Append(const char *name,
void Append(std::string_view name,
std::unique_ptr<PreparedFilter> 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<PreparedFilter> filter) noexcept
{
auto &chain = (PreparedChainFilter &)_chain;

@@ -28,6 +28,7 @@
#define MPD_FILTER_CHAIN_HXX
#include <memory>
#include <string_view>
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<PreparedFilter> filter) noexcept;
#endif

@@ -117,13 +117,13 @@ convert_filter_prepare() noexcept
return std::make_unique<PreparedConvertFilter>();
}
Filter *
std::unique_ptr<Filter>
convert_filter_new(const AudioFormat in_audio_format,
const AudioFormat out_audio_format)
{
std::unique_ptr<ConvertFilter> filter(new ConvertFilter(in_audio_format));
filter->Set(out_audio_format);
return filter.release();
return filter;
}
void

@@ -29,7 +29,7 @@ struct AudioFormat;
std::unique_ptr<PreparedFilter>
convert_filter_prepare() noexcept;
Filter *
std::unique_ptr<Filter>
convert_filter_new(AudioFormat in_audio_format,
AudioFormat out_audio_format);

@@ -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<void> 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);
}

@@ -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;

@@ -17,7 +17,23 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "Ack.hxx"
#include "util/Domain.hxx"
#include "TwoFilters.hxx"
#include "util/ConstBuffer.hxx"
const Domain ack_domain("ack");
ConstBuffer<void>
TwoFilters::FilterPCM(ConstBuffer<void> src)
{
return second->FilterPCM(first->FilterPCM(src));
}
ConstBuffer<void>
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();
}

@@ -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 <memory>
/**
* A #Filter implementation which chains two other filters.
*/
class TwoFilters final : public Filter {
std::unique_ptr<Filter> first, second;
public:
template<typename F, typename S>
TwoFilters(F &&_first, S &&_second) noexcept
:Filter(_second->GetOutAudioFormat()),
first(std::forward<F>(_first)),
second(std::forward<S>(_second)) {}
void Reset() noexcept override {
first->Reset();
second->Reset();
}
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
ConstBuffer<void> Flush() override;
};
#endif

@@ -14,6 +14,7 @@ filter_plugins = static_library(
'filter_plugins',
'../../AudioCompress/compress.c',
'NullFilterPlugin.cxx',
'TwoFilters.cxx',
'ChainFilterPlugin.cxx',
'AutoConvertFilterPlugin.cxx',
'ConvertFilterPlugin.cxx',

@@ -88,6 +88,19 @@ struct PathTraitsFS {
#endif
}
gcc_pure
static const_pointer GetFilenameSuffix(const_pointer filename) noexcept {
const_pointer dot = StringFindLast(filename, '.');
return dot != nullptr && dot > filename && dot[1] != 0
? dot + 1
: nullptr;
}
gcc_pure
static const_pointer GetPathSuffix(const_pointer path) noexcept {
return GetFilenameSuffix(GetBase(path));
}
#ifdef _WIN32
gcc_pure gcc_nonnull_all
static constexpr bool IsDrive(const_pointer p) noexcept {
@@ -199,6 +212,19 @@ struct PathTraitsUTF8 {
return std::strrchr(p, SEPARATOR);
}
gcc_pure
static const_pointer GetFilenameSuffix(const_pointer filename) noexcept {
const_pointer dot = StringFindLast(filename, '.');
return dot != nullptr && dot > filename && dot[1] != 0
? dot + 1
: nullptr;
}
gcc_pure
static const_pointer GetPathSuffix(const_pointer path) noexcept {
return GetFilenameSuffix(GetBase(path));
}
#ifdef _WIN32
gcc_pure gcc_nonnull_all
static constexpr bool IsDrive(const_pointer p) noexcept {

@@ -20,36 +20,36 @@
#ifndef MPD_FFMPEG_BUFFER_HXX
#define MPD_FFMPEG_BUFFER_HXX
#include "util/Compiler.h"
extern "C" {
#include <libavutil/mem.h>
}
#include <cstddef>
/* 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<typename T>
T *GetT(size_t n) {
T *GetT(size_t n) noexcept {
return (T *)Get(n * sizeof(T));
}
};

@@ -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 <libavutil/frame.h>
}
#include <cassert>
#include <new> // for std::bad_alloc
namespace Ffmpeg {
ConstBuffer<void>
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<uint8_t>(data_size);
if (output_buffer == nullptr)
/* Not enough memory - shouldn't happen */
throw std::bad_alloc();
PcmInterleave(output_buffer,
ConstBuffer<const void *>((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

@@ -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<typename T> 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<void>
InterleaveFrame(const AVFrame &frame, FfmpegBuffer &buffer);
} // namespace Ffmpeg
#endif

@@ -31,11 +31,6 @@ extern "C" {
#include <cassert>
#include <cstdint>
/* 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

@@ -26,6 +26,7 @@ endif
ffmpeg = static_library(
'ffmpeg',
'Init.cxx',
'Interleave.cxx',
'LogError.cxx',
'LogCallback.cxx',
'Error.cxx',

67
src/lib/yajl/Handle.cxx Normal file

@@ -0,0 +1,67 @@
/*
* Copyright 2018-2020 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "Handle.hxx"
#include "util/RuntimeError.hxx"
#include "util/ScopeExit.hxx"
#include "util/StringStrip.hxx"
#include <cstring>
/**
* Strip whitespace at the beginning and end and replace newline
* characters which are illegal in the MPD protocol.
*/
static const char *
StripErrorMessage(char *s) noexcept
{
s = Strip(s);
while (auto newline = std::strchr(s, '\n'))
*newline = ';';
return s;
}
namespace Yajl {
void
Handle::ThrowError()
{
unsigned char *str = yajl_get_error(handle, false,
nullptr, 0);
AtScopeExit(this, str) {
yajl_free_error(handle, str);
};
throw FormatRuntimeError("Failed to parse JSON: %s",
StripErrorMessage((char *)str));
}
} // namespace Yajl

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2018 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2018-2020 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -30,12 +30,8 @@
#ifndef YAJL_HANDLE_HXX
#define YAJL_HANDLE_HXX
#include "util/RuntimeError.hxx"
#include "util/ScopeExit.hxx"
#include <yajl/yajl_parse.h>
#include <stdexcept>
#include <algorithm>
namespace Yajl {
@@ -77,15 +73,12 @@ public:
private:
void HandleStatus(yajl_status status) {
if (status == yajl_status_error) {
unsigned char *str = yajl_get_error(handle, false,
nullptr, 0);
AtScopeExit(this, str) {
yajl_free_error(handle, str);
};
throw FormatRuntimeError("Failed to parse JSON: %s", str);
}
if (status == yajl_status_error)
ThrowError();
}
[[noreturn]]
void ThrowError();
};
} // namespace Yajl

@@ -5,6 +5,7 @@ endif
yajl = static_library(
'yajl',
'Handle.cxx',
'ResponseParser.cxx',
'ParseInputStream.cxx',
include_directories: inc,

@@ -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<SongEnumerator>
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<SongEnumerator>

@@ -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.
*/

@@ -25,8 +25,6 @@
#include <stdexcept>
#include <utility>
class Domain;
enum ack {
ACK_ERROR_NOT_LIST = 1,
ACK_ERROR_ARG = 2,
@@ -43,8 +41,6 @@ enum ack {
ACK_ERROR_EXIST = 56,
};
extern const Domain ack_domain;
class ProtocolError : public std::runtime_error {
enum ack code;

@@ -80,7 +80,7 @@ std::string_view
CurlStorage::MapToRelativeUTF8(std::string_view uri_utf8) const noexcept
{
return PathTraitsUTF8::Relative(base,
CurlUnescape(uri_utf8).c_str());
CurlUnescape(uri_utf8));
}
class BlockingHttpRequest : protected CurlResponseHandler {

@@ -27,16 +27,14 @@
template<typename T> class AllocatedString;
/**
* Format into a newly allocated string. The caller frees the return
* value with delete[].
* Format into an #AllocatedString.
*/
gcc_nonnull_all
AllocatedString<char>
FormatStringV(const char *fmt, std::va_list args) noexcept;
/**
* Format into a newly allocated string. The caller frees the return
* value with delete[].
* Format into an #AllocatedString.
*/
gcc_nonnull(1) gcc_printf(1,2)
AllocatedString<char>

@@ -264,7 +264,6 @@ if enable_database
executable(
'DumpDatabase',
'DumpDatabase.cxx',
'../src/protocol/Ack.cxx',
'../src/db/Registry.cxx',
'../src/db/Selection.cxx',
'../src/db/PlaylistVector.cxx',