From 993d85125e699774e31a32af3e7a5f61291e3fb4 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 17 Aug 2021 10:54:42 +0200 Subject: [PATCH 01/15] increment version number to 0.22.11 --- NEWS | 2 ++ android/AndroidManifest.xml | 4 ++-- doc/conf.py | 2 +- meson.build | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index ab1194dee..e918c8150 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,5 @@ +ver 0.22.11 (not yet released) + ver 0.22.10 (2021/08/06) * protocol - support "albumart" for virtual tracks in CUE sheets diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 0139560e3..d449cc2d2 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="59" + android:versionName="0.22.11"> diff --git a/doc/conf.py b/doc/conf.py index 652e8ffb9..161fd05e3 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.10' +version = '0.22.11' # The full version, including alpha/beta/rc tags. release = version diff --git a/meson.build b/meson.build index 09b6908d9..524483f57 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'mpd', ['c', 'cpp'], - version: '0.22.10', + version: '0.22.11', meson_version: '>= 0.49.0', default_options: [ 'c_std=c11', From cfe024ea138d3c9f72ac2875c22e75cc9517b52a Mon Sep 17 00:00:00 2001 From: Samir Benmendil Date: Sun, 15 Aug 2021 12:46:22 +0100 Subject: [PATCH 02/15] command/file: return directory_uri if real_uri is unset Prevent a segfault when accessing album art. Fix #1224 #1225 --- NEWS | 2 ++ src/command/FileCommands.cxx | 3 +++ 2 files changed, 5 insertions(+) diff --git a/NEWS b/NEWS index e918c8150..a22eb7d63 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,6 @@ ver 0.22.11 (not yet released) +* protocol + - fix "albumart" crash ver 0.22.10 (2021/08/06) * protocol diff --git a/src/command/FileCommands.cxx b/src/command/FileCommands.cxx index 1a2b3b8c4..e6ff5672d 100644 --- a/src/command/FileCommands.cxx +++ b/src/command/FileCommands.cxx @@ -267,6 +267,9 @@ try { AtScopeExit(db, song) { db->ReturnSong(song); }; + if (song->real_uri == nullptr) + return directory_uri; + const char *real_uri = song->real_uri; /* this is a simplification which is just enough for CUE From f9a0db716af072117dde8e7cd8a59a52874375ef Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 23 Aug 2021 20:39:23 +0200 Subject: [PATCH 03/15] android: build with NDK r23 --- NEWS | 2 ++ android/build.py | 25 ++++++------------------- doc/user.rst | 2 +- python/build/openssl.py | 2 -- 4 files changed, 9 insertions(+), 22 deletions(-) diff --git a/NEWS b/NEWS index a22eb7d63..4f7ecf500 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,8 @@ ver 0.22.11 (not yet released) * protocol - fix "albumart" crash +* Android + - build with NDK r23 ver 0.22.10 (2021/08/06) * protocol diff --git a/android/build.py b/android/build.py index 664bcccdd..c3577b0b7 100755 --- a/android/build.py +++ b/android/build.py @@ -24,15 +24,13 @@ android_abis = { 'armeabi-v7a': { 'arch': 'arm-linux-androideabi', 'ndk_arch': 'arm', - 'toolchain_arch': 'arm-linux-androideabi', 'llvm_triple': 'armv7-linux-androideabi', - 'cflags': '-fpic -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=softfp', + 'cflags': '-fpic -mfpu=neon -mfloat-abi=softfp', }, 'arm64-v8a': { 'arch': 'aarch64-linux-android', 'ndk_arch': 'arm64', - 'toolchain_arch': 'aarch64-linux-android', 'llvm_triple': 'aarch64-linux-android', 'cflags': '-fpic', }, @@ -40,7 +38,6 @@ android_abis = { 'x86': { 'arch': 'i686-linux-android', 'ndk_arch': 'x86', - 'toolchain_arch': 'x86', 'llvm_triple': 'i686-linux-android', 'cflags': '-fPIC -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32', }, @@ -48,7 +45,6 @@ android_abis = { 'x86_64': { 'arch': 'x86_64-linux-android', 'ndk_arch': 'x86_64', - 'toolchain_arch': 'x86_64', 'llvm_triple': 'x86_64-linux-android', 'cflags': '-fPIC -m64', }, @@ -84,37 +80,28 @@ class AndroidNdkToolchain: ndk_arch = abi_info['ndk_arch'] android_api_level = '21' - # select the NDK compiler - gcc_version = '4.9' - install_prefix = os.path.join(arch_path, 'root') self.arch = arch self.install_prefix = install_prefix - self.toolchain_arch = abi_info['toolchain_arch'] - toolchain_path = os.path.join(ndk_path, 'toolchains', self.toolchain_arch + '-' + gcc_version, 'prebuilt', build_arch) llvm_path = os.path.join(ndk_path, 'toolchains', 'llvm', 'prebuilt', build_arch) llvm_triple = abi_info['llvm_triple'] + android_api_level common_flags = '-Os -g' common_flags += ' ' + abi_info['cflags'] - toolchain_bin = os.path.join(toolchain_path, 'bin') llvm_bin = os.path.join(llvm_path, 'bin') self.cc = os.path.join(llvm_bin, 'clang') self.cxx = os.path.join(llvm_bin, 'clang++') - common_flags += ' -target ' + llvm_triple + ' -gcc-toolchain ' + toolchain_path + common_flags += ' -target ' + llvm_triple common_flags += ' -fvisibility=hidden -fdata-sections -ffunction-sections' - # required flags from https://android.googlesource.com/platform/ndk/+/ndk-release-r20/docs/BuildSystemMaintainers.md#additional-required-arguments - common_flags += ' -fno-addrsig' - - self.ar = os.path.join(toolchain_bin, arch + '-ar') - self.ranlib = os.path.join(toolchain_bin, arch + '-ranlib') - self.nm = os.path.join(toolchain_bin, arch + '-nm') - self.strip = os.path.join(toolchain_bin, arch + '-strip') + self.ar = os.path.join(llvm_bin, 'llvm-ar') + self.ranlib = os.path.join(llvm_bin, 'llvm-ranlib') + self.nm = os.path.join(llvm_bin, 'llvm-nm') + self.strip = os.path.join(llvm_bin, 'llvm-strip') self.cflags = common_flags self.cxxflags = common_flags diff --git a/doc/user.rst b/doc/user.rst index 2e9d8e710..d98dc093b 100644 --- a/doc/user.rst +++ b/doc/user.rst @@ -176,7 +176,7 @@ Compiling for Android You need: * Android SDK -* `Android NDK r22 `_ +* `Android NDK r23 `_ * `Meson 0.49.0 `__ and `Ninja `__ * cmake diff --git a/python/build/openssl.py b/python/build/openssl.py index 605a04c74..ecf88d4e2 100644 --- a/python/build/openssl.py +++ b/python/build/openssl.py @@ -48,7 +48,6 @@ class OpenSSLProject(MakeProject): } openssl_arch = openssl_archs[toolchain.arch] - cross_compile_prefix = toolchain.toolchain_arch + '-' subprocess.check_call(['./Configure', 'no-shared', @@ -57,7 +56,6 @@ class OpenSSLProject(MakeProject): 'no-tests', 'no-asm', # "asm" causes build failures on Windows openssl_arch, - '--cross-compile-prefix=' + cross_compile_prefix, '--prefix=' + toolchain.install_prefix], cwd=src, env=toolchain.env) MakeProject.build(self, toolchain, src) From ca2439f595b8fbf8a370744fd9f2ade25525d013 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 23 Aug 2021 21:36:57 +0200 Subject: [PATCH 04/15] filter/ffmpeg: pass "channel_layout" instead of "channels" to buffersrc Fixes part 1 of https://github.com/MusicPlayerDaemon/MPD/issues/1235 --- NEWS | 2 + src/lib/ffmpeg/ChannelLayout.hxx | 66 ++++++++++++++++++++++++++++++++ src/lib/ffmpeg/Filter.cxx | 7 +++- 3 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 src/lib/ffmpeg/ChannelLayout.hxx diff --git a/NEWS b/NEWS index 4f7ecf500..dece9dc95 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,8 @@ ver 0.22.11 (not yet released) * protocol - fix "albumart" crash +* filter + - ffmpeg: pass "channel_layout" instead of "channels" to buffersrc * Android - build with NDK r23 diff --git a/src/lib/ffmpeg/ChannelLayout.hxx b/src/lib/ffmpeg/ChannelLayout.hxx new file mode 100644 index 000000000..02f74b268 --- /dev/null +++ b/src/lib/ffmpeg/ChannelLayout.hxx @@ -0,0 +1,66 @@ +/* + * Copyright 2003-2021 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_CHANNEL_LAYOUT_HXX +#define MPD_FFMPEG_CHANNEL_LAYOUT_HXX + +extern "C" { +#include +} + +/** + * Convert a MPD channel count to a libavutil channel_layout bit mask. + */ +static constexpr uint64_t +ToFfmpegChannelLayout(unsigned channels) noexcept +{ + switch (channels) { + case 1: + return AV_CH_LAYOUT_MONO; + + case 2: + return AV_CH_LAYOUT_STEREO; + + case 3: + return AV_CH_LAYOUT_SURROUND; + + case 4: + // TODO is this AV_CH_LAYOUT_2_2? + return AV_CH_LAYOUT_QUAD; + + case 5: + // TODO is this AV_CH_LAYOUT_5POINT0_BACK? + return AV_CH_LAYOUT_5POINT0; + + case 6: + return AV_CH_LAYOUT_5POINT1; + + case 7: + return AV_CH_LAYOUT_6POINT1; + + case 8: + return AV_CH_LAYOUT_7POINT1; + + default: + /* unreachable */ + return 0; + } +} + +#endif diff --git a/src/lib/ffmpeg/Filter.cxx b/src/lib/ffmpeg/Filter.cxx index 1749082c6..a91a30a53 100644 --- a/src/lib/ffmpeg/Filter.cxx +++ b/src/lib/ffmpeg/Filter.cxx @@ -18,10 +18,13 @@ */ #include "Filter.hxx" +#include "ChannelLayout.hxx" #include "SampleFormat.hxx" #include "pcm/AudioFormat.hxx" #include "util/RuntimeError.hxx" +#include + #include namespace Ffmpeg { @@ -57,10 +60,10 @@ FilterContext::MakeAudioBufferSource(AudioFormat &audio_format, char abuffer_args[256]; sprintf(abuffer_args, - "sample_rate=%u:sample_fmt=%s:channels=%u:time_base=1/%u", + "sample_rate=%u:sample_fmt=%s:channel_layout=0x%" PRIx64 ":time_base=1/%u", audio_format.sample_rate, av_get_sample_fmt_name(src_format), - audio_format.channels, + ToFfmpegChannelLayout(audio_format.channels), audio_format.sample_rate); return {RequireFilterByName("abuffer"), "abuffer", abuffer_args, nullptr, graph_ctx}; From a62a35e1db47b7dee160ed24dca4b19f982eb312 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 24 Aug 2021 12:32:44 +0200 Subject: [PATCH 05/15] lib/ffmpeg/Filter: remove FilterContext destructor Fixes potential double-free bugs which currently did not occur because the destructors happened to be called in the right order. --- src/lib/ffmpeg/Filter.hxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/ffmpeg/Filter.hxx b/src/lib/ffmpeg/Filter.hxx index 8990bb6f1..3cf11546d 100644 --- a/src/lib/ffmpeg/Filter.hxx +++ b/src/lib/ffmpeg/Filter.hxx @@ -101,10 +101,10 @@ public: FilterContext(FilterContext &&src) noexcept :context(std::exchange(src.context, nullptr)) {} - ~FilterContext() noexcept { - if (context != nullptr) - avfilter_free(context); - } + /* note: we don't need a destructor calling avfilter_free() + here because the AVFilterGraph owns and frees all the + AVFilterContext instances */ + // TODO: do we really need this wrapper class anymore? FilterContext &operator=(FilterContext &&src) noexcept { using std::swap; From 71a5311b0608152f16741ad6ff3d5af7d0926776 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 24 Aug 2021 12:57:55 +0200 Subject: [PATCH 06/15] lib/ffmpeg/Filter: eliminate class FilterContext Since AVFilterContext are freed automatically, this wrapper class serves no purpose. Let's remove it. --- src/filter/plugins/FfmpegFilter.cxx | 12 ++-- src/filter/plugins/FfmpegFilter.hxx | 6 +- src/filter/plugins/FfmpegFilterPlugin.cxx | 19 +++--- src/filter/plugins/HdcdFilterPlugin.cxx | 16 +++--- src/lib/ffmpeg/Filter.cxx | 39 ++++++++++--- src/lib/ffmpeg/Filter.hxx | 70 +++++------------------ 6 files changed, 72 insertions(+), 90 deletions(-) diff --git a/src/filter/plugins/FfmpegFilter.cxx b/src/filter/plugins/FfmpegFilter.cxx index c42ed1538..7786407fe 100644 --- a/src/filter/plugins/FfmpegFilter.cxx +++ b/src/filter/plugins/FfmpegFilter.cxx @@ -32,12 +32,12 @@ extern "C" { FfmpegFilter::FfmpegFilter(const AudioFormat &in_audio_format, const AudioFormat &_out_audio_format, Ffmpeg::FilterGraph &&_graph, - Ffmpeg::FilterContext &&_buffer_src, - Ffmpeg::FilterContext &&_buffer_sink) noexcept + AVFilterContext &_buffer_src, + AVFilterContext &_buffer_sink) noexcept :Filter(_out_audio_format), graph(std::move(_graph)), - buffer_src(std::move(_buffer_src)), - buffer_sink(std::move(_buffer_sink)), + buffer_src(_buffer_src), + buffer_sink(_buffer_sink), in_format(Ffmpeg::ToFfmpegSampleFormat(in_audio_format.format)), in_sample_rate(in_audio_format.sample_rate), in_channels(in_audio_format.channels), @@ -61,7 +61,7 @@ FfmpegFilter::FilterPCM(ConstBuffer src) memcpy(frame.GetData(0), src.data, src.size); - int err = av_buffersrc_add_frame(buffer_src.get(), frame.get()); + int err = av_buffersrc_add_frame(&buffer_src, frame.get()); if (err < 0) throw MakeFfmpegError(err, "av_buffersrc_write_frame() failed"); @@ -69,7 +69,7 @@ FfmpegFilter::FilterPCM(ConstBuffer src) frame.Unref(); - err = av_buffersink_get_frame(buffer_sink.get(), frame.get()); + err = av_buffersink_get_frame(&buffer_sink, frame.get()); if (err < 0) { if (err == AVERROR(EAGAIN) || err == AVERROR_EOF) return nullptr; diff --git a/src/filter/plugins/FfmpegFilter.hxx b/src/filter/plugins/FfmpegFilter.hxx index 30e86d2bb..e889eb672 100644 --- a/src/filter/plugins/FfmpegFilter.hxx +++ b/src/filter/plugins/FfmpegFilter.hxx @@ -30,7 +30,7 @@ */ class FfmpegFilter final : public Filter { Ffmpeg::FilterGraph graph; - Ffmpeg::FilterContext buffer_src, buffer_sink; + AVFilterContext &buffer_src, &buffer_sink; Ffmpeg::Frame frame; FfmpegBuffer interleave_buffer; @@ -51,8 +51,8 @@ public: FfmpegFilter(const AudioFormat &in_audio_format, const AudioFormat &_out_audio_format, Ffmpeg::FilterGraph &&_graph, - Ffmpeg::FilterContext &&_buffer_src, - Ffmpeg::FilterContext &&_buffer_sink) noexcept; + AVFilterContext &_buffer_src, + AVFilterContext &_buffer_sink) noexcept; /* virtual methods from class Filter */ ConstBuffer FilterPCM(ConstBuffer src) override; diff --git a/src/filter/plugins/FfmpegFilterPlugin.cxx b/src/filter/plugins/FfmpegFilterPlugin.cxx index e07c50d32..0cbd85821 100644 --- a/src/filter/plugins/FfmpegFilterPlugin.cxx +++ b/src/filter/plugins/FfmpegFilterPlugin.cxx @@ -42,14 +42,13 @@ PreparedFfmpegFilter::Open(AudioFormat &in_audio_format) { Ffmpeg::FilterGraph graph; - auto buffer_src = - Ffmpeg::FilterContext::MakeAudioBufferSource(in_audio_format, - *graph); + auto &buffer_src = + Ffmpeg::MakeAudioBufferSource(in_audio_format, *graph); - auto buffer_sink = Ffmpeg::FilterContext::MakeAudioBufferSink(*graph); + auto &buffer_sink = Ffmpeg::MakeAudioBufferSink(*graph); - Ffmpeg::FilterInOut io_sink("out", *buffer_sink); - Ffmpeg::FilterInOut io_src("in", *buffer_src); + Ffmpeg::FilterInOut io_sink("out", buffer_sink); + Ffmpeg::FilterInOut io_src("in", buffer_src); auto io = graph.Parse(graph_string, std::move(io_sink), std::move(io_src)); @@ -62,14 +61,14 @@ PreparedFfmpegFilter::Open(AudioFormat &in_audio_format) graph.CheckAndConfigure(); const auto out_audio_format = - Ffmpeg::DetectFilterOutputFormat(in_audio_format, *buffer_src, - *buffer_sink); + Ffmpeg::DetectFilterOutputFormat(in_audio_format, buffer_src, + buffer_sink); return std::make_unique(in_audio_format, out_audio_format, std::move(graph), - std::move(buffer_src), - std::move(buffer_sink)); + buffer_src, + buffer_sink); } static std::unique_ptr diff --git a/src/filter/plugins/HdcdFilterPlugin.cxx b/src/filter/plugins/HdcdFilterPlugin.cxx index c632e8d67..a1781f34d 100644 --- a/src/filter/plugins/HdcdFilterPlugin.cxx +++ b/src/filter/plugins/HdcdFilterPlugin.cxx @@ -42,14 +42,14 @@ OpenHdcdFilter(AudioFormat &in_audio_format) { Ffmpeg::FilterGraph graph; - auto buffer_src = - Ffmpeg::FilterContext::MakeAudioBufferSource(in_audio_format, - *graph); + auto &buffer_src = + Ffmpeg::MakeAudioBufferSource(in_audio_format, + *graph); - auto buffer_sink = Ffmpeg::FilterContext::MakeAudioBufferSink(*graph); + auto &buffer_sink = Ffmpeg::MakeAudioBufferSink(*graph); - Ffmpeg::FilterInOut io_sink("out", *buffer_sink); - Ffmpeg::FilterInOut io_src("in", *buffer_src); + Ffmpeg::FilterInOut io_sink("out", buffer_sink); + Ffmpeg::FilterInOut io_src("in", buffer_src); auto io = graph.Parse(hdcd_graph, std::move(io_sink), std::move(io_src)); @@ -69,8 +69,8 @@ OpenHdcdFilter(AudioFormat &in_audio_format) return std::make_unique(in_audio_format, out_audio_format, std::move(graph), - std::move(buffer_src), - std::move(buffer_sink)); + buffer_src, + buffer_sink); } class PreparedHdcdFilter final : public PreparedFilter { diff --git a/src/lib/ffmpeg/Filter.cxx b/src/lib/ffmpeg/Filter.cxx index a91a30a53..c5e7a3a5c 100644 --- a/src/lib/ffmpeg/Filter.cxx +++ b/src/lib/ffmpeg/Filter.cxx @@ -39,9 +39,32 @@ RequireFilterByName(const char *name) return *filter; } -FilterContext -FilterContext::MakeAudioBufferSource(AudioFormat &audio_format, - AVFilterGraph &graph_ctx) +static AVFilterContext & +CreateFilter(const AVFilter &filt, + const char *name, const char *args, void *opaque, + AVFilterGraph &graph_ctx) +{ + AVFilterContext *context = nullptr; + int err = avfilter_graph_create_filter(&context, &filt, + name, args, opaque, + &graph_ctx); + if (err < 0) + throw MakeFfmpegError(err, "avfilter_graph_create_filter() failed"); + + return *context; +} + +static AVFilterContext & +CreateFilter(const AVFilter &filt, + const char *name, + AVFilterGraph &graph_ctx) +{ + return CreateFilter(filt, name, nullptr, nullptr, graph_ctx); +} + +AVFilterContext & +MakeAudioBufferSource(AudioFormat &audio_format, + AVFilterGraph &graph_ctx) { AVSampleFormat src_format = ToFfmpegSampleFormat(audio_format.format); if (src_format == AV_SAMPLE_FMT_NONE) { @@ -66,13 +89,15 @@ FilterContext::MakeAudioBufferSource(AudioFormat &audio_format, ToFfmpegChannelLayout(audio_format.channels), audio_format.sample_rate); - return {RequireFilterByName("abuffer"), "abuffer", abuffer_args, nullptr, graph_ctx}; + return CreateFilter(RequireFilterByName("abuffer"), "abuffer", + abuffer_args, nullptr, graph_ctx); } -FilterContext -FilterContext::MakeAudioBufferSink(AVFilterGraph &graph_ctx) +AVFilterContext & +MakeAudioBufferSink(AVFilterGraph &graph_ctx) { - return {RequireFilterByName("abuffersink"), "abuffersink", graph_ctx}; + return CreateFilter(RequireFilterByName("abuffersink"), "abuffersink", + graph_ctx); } } // namespace Ffmpeg diff --git a/src/lib/ffmpeg/Filter.hxx b/src/lib/ffmpeg/Filter.hxx index 3cf11546d..0cac102f7 100644 --- a/src/lib/ffmpeg/Filter.hxx +++ b/src/lib/ffmpeg/Filter.hxx @@ -77,63 +77,21 @@ public: } }; -class FilterContext { - AVFilterContext *context = nullptr; +/** + * Create an "abuffer" filter. + * + * @param the input audio format; may be modified by the + * function to ask the caller to do format conversion + */ +AVFilterContext & +MakeAudioBufferSource(AudioFormat &audio_format, + AVFilterGraph &graph_ctx); -public: - FilterContext() = default; - - FilterContext(const AVFilter &filt, - const char *name, const char *args, void *opaque, - AVFilterGraph &graph_ctx) { - int err = avfilter_graph_create_filter(&context, &filt, - name, args, opaque, - &graph_ctx); - if (err < 0) - throw MakeFfmpegError(err, "avfilter_graph_create_filter() failed"); - } - - FilterContext(const AVFilter &filt, - const char *name, - AVFilterGraph &graph_ctx) - :FilterContext(filt, name, nullptr, nullptr, graph_ctx) {} - - FilterContext(FilterContext &&src) noexcept - :context(std::exchange(src.context, nullptr)) {} - - /* note: we don't need a destructor calling avfilter_free() - here because the AVFilterGraph owns and frees all the - AVFilterContext instances */ - // TODO: do we really need this wrapper class anymore? - - FilterContext &operator=(FilterContext &&src) noexcept { - using std::swap; - swap(context, src.context); - return *this; - } - - /** - * Create an "abuffer" filter. - * - * @param the input audio format; may be modified by the - * function to ask the caller to do format conversion - */ - static FilterContext MakeAudioBufferSource(AudioFormat &audio_format, - AVFilterGraph &graph_ctx); - - /** - * Create an "abuffersink" filter. - */ - static FilterContext MakeAudioBufferSink(AVFilterGraph &graph_ctx); - - auto &operator*() noexcept { - return *context; - } - - auto *get() noexcept { - return context; - } -}; +/** + * Create an "abuffersink" filter. + */ +AVFilterContext & +MakeAudioBufferSink(AVFilterGraph &graph_ctx); class FilterGraph { AVFilterGraph *graph = nullptr; From 7b4225aa1f946d8ba64d4c3fcc07cb449c903cad Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 24 Aug 2021 13:26:28 +0200 Subject: [PATCH 07/15] lib/ffmpeg/Filter: add ParseSingleInOut() Merge some duplicate code. --- src/filter/plugins/FfmpegFilterPlugin.cxx | 12 +----------- src/filter/plugins/HdcdFilterPlugin.cxx | 13 +------------ src/lib/ffmpeg/Filter.cxx | 13 +++++++++++++ src/lib/ffmpeg/Filter.hxx | 3 +++ 4 files changed, 18 insertions(+), 23 deletions(-) diff --git a/src/filter/plugins/FfmpegFilterPlugin.cxx b/src/filter/plugins/FfmpegFilterPlugin.cxx index 0cbd85821..db377ca5e 100644 --- a/src/filter/plugins/FfmpegFilterPlugin.cxx +++ b/src/filter/plugins/FfmpegFilterPlugin.cxx @@ -47,17 +47,7 @@ PreparedFfmpegFilter::Open(AudioFormat &in_audio_format) auto &buffer_sink = Ffmpeg::MakeAudioBufferSink(*graph); - Ffmpeg::FilterInOut io_sink("out", buffer_sink); - Ffmpeg::FilterInOut io_src("in", buffer_src); - auto io = graph.Parse(graph_string, std::move(io_sink), - std::move(io_src)); - - if (io.first.get() != nullptr) - throw std::runtime_error("FFmpeg filter has an open input"); - - if (io.second.get() != nullptr) - throw std::runtime_error("FFmpeg filter has an open output"); - + graph.ParseSingleInOut(graph_string, buffer_sink, buffer_src); graph.CheckAndConfigure(); const auto out_audio_format = diff --git a/src/filter/plugins/HdcdFilterPlugin.cxx b/src/filter/plugins/HdcdFilterPlugin.cxx index a1781f34d..29aedc40a 100644 --- a/src/filter/plugins/HdcdFilterPlugin.cxx +++ b/src/filter/plugins/HdcdFilterPlugin.cxx @@ -48,18 +48,7 @@ OpenHdcdFilter(AudioFormat &in_audio_format) auto &buffer_sink = Ffmpeg::MakeAudioBufferSink(*graph); - Ffmpeg::FilterInOut io_sink("out", buffer_sink); - Ffmpeg::FilterInOut io_src("in", buffer_src); - - auto io = graph.Parse(hdcd_graph, std::move(io_sink), - std::move(io_src)); - - if (io.first.get() != nullptr) - throw std::runtime_error("FFmpeg filter has an open input"); - - if (io.second.get() != nullptr) - throw std::runtime_error("FFmpeg filter has an open output"); - + graph.ParseSingleInOut(hdcd_graph, buffer_sink, buffer_src); graph.CheckAndConfigure(); auto out_audio_format = in_audio_format; diff --git a/src/lib/ffmpeg/Filter.cxx b/src/lib/ffmpeg/Filter.cxx index c5e7a3a5c..51e43dc3d 100644 --- a/src/lib/ffmpeg/Filter.cxx +++ b/src/lib/ffmpeg/Filter.cxx @@ -100,4 +100,17 @@ MakeAudioBufferSink(AVFilterGraph &graph_ctx) graph_ctx); } +void +FilterGraph::ParseSingleInOut(const char *filters, AVFilterContext &in, + AVFilterContext &out) +{ + auto [inputs, outputs] = Parse(filters, {"out", in}, {"in", out}); + + if (inputs.get() != nullptr) + throw std::runtime_error("FFmpeg filter has an open input"); + + if (outputs.get() != nullptr) + throw std::runtime_error("FFmpeg filter has an open output"); +} + } // namespace Ffmpeg diff --git a/src/lib/ffmpeg/Filter.hxx b/src/lib/ffmpeg/Filter.hxx index 0cac102f7..f57af3c1b 100644 --- a/src/lib/ffmpeg/Filter.hxx +++ b/src/lib/ffmpeg/Filter.hxx @@ -138,6 +138,9 @@ public: return std::make_pair(std::move(inputs), std::move(outputs)); } + void ParseSingleInOut(const char *filters, AVFilterContext &in, + AVFilterContext &out); + std::pair Parse(const char *filters) { AVFilterInOut *inputs, *outputs; int err = avfilter_graph_parse2(graph, filters, From ebfbb74f9e7af0d791230dfca96d60a32fc3b458 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 24 Aug 2021 11:05:06 +0200 Subject: [PATCH 08/15] lib/ffmpeg/DetectFilterFormat: return AudioFormat::Undefined() on EAGAIN --- src/filter/plugins/FfmpegFilterPlugin.cxx | 3 +++ src/lib/ffmpeg/DetectFilterFormat.cxx | 8 +++++++- src/lib/ffmpeg/DetectFilterFormat.hxx | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/filter/plugins/FfmpegFilterPlugin.cxx b/src/filter/plugins/FfmpegFilterPlugin.cxx index db377ca5e..94e83f50a 100644 --- a/src/filter/plugins/FfmpegFilterPlugin.cxx +++ b/src/filter/plugins/FfmpegFilterPlugin.cxx @@ -54,6 +54,9 @@ PreparedFfmpegFilter::Open(AudioFormat &in_audio_format) Ffmpeg::DetectFilterOutputFormat(in_audio_format, buffer_src, buffer_sink); + if (!out_audio_format.IsDefined()) + throw std::runtime_error("Unable to determine FFmpeg filter output format"); + return std::make_unique(in_audio_format, out_audio_format, std::move(graph), diff --git a/src/lib/ffmpeg/DetectFilterFormat.cxx b/src/lib/ffmpeg/DetectFilterFormat.cxx index 4367307d5..7b44eabb4 100644 --- a/src/lib/ffmpeg/DetectFilterFormat.cxx +++ b/src/lib/ffmpeg/DetectFilterFormat.cxx @@ -62,8 +62,14 @@ DetectFilterOutputFormat(const AudioFormat &in_audio_format, frame.Unref(); err = av_buffersink_get_frame(&buffer_sink, frame.get()); - if (err < 0) + if (err < 0) { + if (err == AVERROR(EAGAIN)) + /* one sample was not enough input data for + the given filter graph */ + return AudioFormat::Undefined(); + throw MakeFfmpegError(err, "av_buffersink_get_frame() failed"); + } const SampleFormat sample_format = FromFfmpegSampleFormat(AVSampleFormat(frame->format)); if (sample_format == SampleFormat::UNDEFINED) diff --git a/src/lib/ffmpeg/DetectFilterFormat.hxx b/src/lib/ffmpeg/DetectFilterFormat.hxx index 662f0e733..18bf61994 100644 --- a/src/lib/ffmpeg/DetectFilterFormat.hxx +++ b/src/lib/ffmpeg/DetectFilterFormat.hxx @@ -35,6 +35,9 @@ namespace Ffmpeg { * between. * * This function can throw if the FFmpeg filter fails. + * + * @return the output format or AudioFormat::Undefined() if it was not + * possible to determine the format */ AudioFormat DetectFilterOutputFormat(const AudioFormat &in_audio_format, From b904f8af033d069903d77138bde5ab9fd97b30d8 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 24 Aug 2021 11:45:28 +0200 Subject: [PATCH 09/15] lib/ffmpeg/Filter: add FilterContext::MakeAformat() --- src/lib/ffmpeg/Filter.cxx | 30 ++++++++++++++++++++++++++++++ src/lib/ffmpeg/Filter.hxx | 10 ++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/lib/ffmpeg/Filter.cxx b/src/lib/ffmpeg/Filter.cxx index 51e43dc3d..d435ca5c8 100644 --- a/src/lib/ffmpeg/Filter.cxx +++ b/src/lib/ffmpeg/Filter.cxx @@ -100,6 +100,36 @@ MakeAudioBufferSink(AVFilterGraph &graph_ctx) graph_ctx); } +AVFilterContext & +MakeAformat(AudioFormat &audio_format, + AVFilterGraph &graph_ctx) +{ + AVSampleFormat dest_format = ToFfmpegSampleFormat(audio_format.format); + if (dest_format == AV_SAMPLE_FMT_NONE) { + switch (audio_format.format) { + case SampleFormat::S24_P32: + audio_format.format = SampleFormat::S32; + dest_format = AV_SAMPLE_FMT_S32; + break; + + default: + audio_format.format = SampleFormat::S16; + dest_format = AV_SAMPLE_FMT_S16; + break; + } + } + + char args[256]; + sprintf(args, + "sample_rates=%u:sample_fmts=%s:channel_layouts=0x%" PRIx64, + audio_format.sample_rate, + av_get_sample_fmt_name(dest_format), + ToFfmpegChannelLayout(audio_format.channels)); + + return CreateFilter(RequireFilterByName("aformat"), "aformat", + args, nullptr, graph_ctx); +} + void FilterGraph::ParseSingleInOut(const char *filters, AVFilterContext &in, AVFilterContext &out) diff --git a/src/lib/ffmpeg/Filter.hxx b/src/lib/ffmpeg/Filter.hxx index f57af3c1b..c55ac2b1f 100644 --- a/src/lib/ffmpeg/Filter.hxx +++ b/src/lib/ffmpeg/Filter.hxx @@ -93,6 +93,16 @@ MakeAudioBufferSource(AudioFormat &audio_format, AVFilterContext & MakeAudioBufferSink(AVFilterGraph &graph_ctx); +/** + * Create an "aformat" filter. + * + * @param the output audio format; may be modified by the function if + * the given format is not supported by libavfilter + */ +AVFilterContext & +MakeAformat(AudioFormat &audio_format, + AVFilterGraph &graph_ctx); + class FilterGraph { AVFilterGraph *graph = nullptr; From b5b40d8235f02440948bd4d0584309f6a6e94fc9 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 24 Aug 2021 12:09:08 +0200 Subject: [PATCH 10/15] filter/ffmpeg: automatically retry with "aformat" If DetectFilterOutputFormat() fails to determine the output format, insert an "aformat" filter which attempts to force a specific output format. Fixes part 2 of of https://github.com/MusicPlayerDaemon/MPD/issues/1235 --- NEWS | 1 + src/filter/plugins/FfmpegFilterPlugin.cxx | 41 ++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index dece9dc95..966ed91e7 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,7 @@ ver 0.22.11 (not yet released) - fix "albumart" crash * filter - ffmpeg: pass "channel_layout" instead of "channels" to buffersrc + - ffmpeg: fix "av_buffersink_get_frame() failed: Resource temporarily unavailable" * Android - build with NDK r23 diff --git a/src/filter/plugins/FfmpegFilterPlugin.cxx b/src/filter/plugins/FfmpegFilterPlugin.cxx index 94e83f50a..d61a23b95 100644 --- a/src/filter/plugins/FfmpegFilterPlugin.cxx +++ b/src/filter/plugins/FfmpegFilterPlugin.cxx @@ -37,6 +37,40 @@ public: std::unique_ptr Open(AudioFormat &af) override; }; +/** + * Fallback for PreparedFfmpegFilter::Open() just in case the filter's + * native output format could not be determined. + * + * TODO: improve the MPD filter API to allow returning the output + * format later, and eliminate this kludge + */ +static auto +OpenWithAformat(const char *graph_string, AudioFormat &in_audio_format) +{ + Ffmpeg::FilterGraph graph; + + auto &buffer_src = + Ffmpeg::MakeAudioBufferSource(in_audio_format, *graph); + + auto &buffer_sink = Ffmpeg::MakeAudioBufferSink(*graph); + + AudioFormat out_audio_format = in_audio_format; + auto &aformat = Ffmpeg::MakeAformat(out_audio_format, *graph); + + int error = avfilter_link(&aformat, 0, &buffer_sink, 0); + if (error < 0) + throw MakeFfmpegError(error, "avfilter_link() failed"); + + graph.ParseSingleInOut(graph_string, aformat, buffer_src); + graph.CheckAndConfigure(); + + return std::make_unique(in_audio_format, + out_audio_format, + std::move(graph), + buffer_src, + buffer_sink); +} + std::unique_ptr PreparedFfmpegFilter::Open(AudioFormat &in_audio_format) { @@ -55,7 +89,12 @@ PreparedFfmpegFilter::Open(AudioFormat &in_audio_format) buffer_sink); if (!out_audio_format.IsDefined()) - throw std::runtime_error("Unable to determine FFmpeg filter output format"); + /* the filter's native output format could not be + determined yet, but we need to know it now; as a + workaround for this MPD API deficiency, try again + with an "aformat" filter which forces a specific + output format */ + return OpenWithAformat(graph_string, in_audio_format); return std::make_unique(in_audio_format, out_audio_format, From 8439119e244b2456239d9b274a4bf819dd667cd9 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 24 Aug 2021 13:42:00 +0200 Subject: [PATCH 11/15] filter/ffmpeg: support double-precision samples Insert an "aformat" filter which converts double-precision to single-precision. Closes https://github.com/MusicPlayerDaemon/MPD/issues/1235 --- NEWS | 1 + src/filter/plugins/FfmpegFilterPlugin.cxx | 11 ++++++++++- src/lib/ffmpeg/Filter.cxx | 8 ++++++++ src/lib/ffmpeg/Filter.hxx | 7 +++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 966ed91e7..3107a602f 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,7 @@ ver 0.22.11 (not yet released) * filter - ffmpeg: pass "channel_layout" instead of "channels" to buffersrc - ffmpeg: fix "av_buffersink_get_frame() failed: Resource temporarily unavailable" + - ffmpeg: support double-precision samples (by converting to single precision) * Android - build with NDK r23 diff --git a/src/filter/plugins/FfmpegFilterPlugin.cxx b/src/filter/plugins/FfmpegFilterPlugin.cxx index d61a23b95..20d70507d 100644 --- a/src/filter/plugins/FfmpegFilterPlugin.cxx +++ b/src/filter/plugins/FfmpegFilterPlugin.cxx @@ -81,7 +81,16 @@ PreparedFfmpegFilter::Open(AudioFormat &in_audio_format) auto &buffer_sink = Ffmpeg::MakeAudioBufferSink(*graph); - graph.ParseSingleInOut(graph_string, buffer_sink, buffer_src); + /* if the filter's output format is not supported by MPD, this + "aformat" filter is inserted at the end and takes care for + the required conversion */ + auto &aformat = Ffmpeg::MakeAutoAformat(*graph); + + int error = avfilter_link(&aformat, 0, &buffer_sink, 0); + if (error < 0) + throw MakeFfmpegError(error, "avfilter_link() failed"); + + graph.ParseSingleInOut(graph_string, aformat, buffer_src); graph.CheckAndConfigure(); const auto out_audio_format = diff --git a/src/lib/ffmpeg/Filter.cxx b/src/lib/ffmpeg/Filter.cxx index d435ca5c8..ca011a341 100644 --- a/src/lib/ffmpeg/Filter.cxx +++ b/src/lib/ffmpeg/Filter.cxx @@ -130,6 +130,14 @@ MakeAformat(AudioFormat &audio_format, args, nullptr, graph_ctx); } +AVFilterContext & +MakeAutoAformat(AVFilterGraph &graph_ctx) +{ + return CreateFilter(RequireFilterByName("aformat"), "aformat", + "sample_fmts=flt|s32|s16", + nullptr, graph_ctx); +} + void FilterGraph::ParseSingleInOut(const char *filters, AVFilterContext &in, AVFilterContext &out) diff --git a/src/lib/ffmpeg/Filter.hxx b/src/lib/ffmpeg/Filter.hxx index c55ac2b1f..d2e3a38c5 100644 --- a/src/lib/ffmpeg/Filter.hxx +++ b/src/lib/ffmpeg/Filter.hxx @@ -103,6 +103,13 @@ AVFilterContext & MakeAformat(AudioFormat &audio_format, AVFilterGraph &graph_ctx); +/** + * Create an "aformat" filter which automatically converts the output + * to a format supported by MPD. + */ +AVFilterContext & +MakeAutoAformat(AVFilterGraph &graph_ctx); + class FilterGraph { AVFilterGraph *graph = nullptr; From 811860c3b41f5c24e388342fcb534a57c1645f80 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 24 Aug 2021 21:54:06 +0200 Subject: [PATCH 12/15] android/Context: use [[gnu::pure]] --- src/android/Context.hxx | 4 ++-- src/android/Environment.hxx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/android/Context.hxx b/src/android/Context.hxx index bdbdc641f..6be0735a2 100644 --- a/src/android/Context.hxx +++ b/src/android/Context.hxx @@ -30,10 +30,10 @@ public: Context(JNIEnv *env, jobject obj) noexcept :Java::GlobalObject(env, obj) {} - gcc_pure + [[gnu::pure]] AllocatedPath GetCacheDir(JNIEnv *env) const noexcept; - gcc_pure + [[gnu::pure]] AudioManager *GetAudioManager(JNIEnv *env) noexcept; }; diff --git a/src/android/Environment.hxx b/src/android/Environment.hxx index f8b964deb..15cc604cd 100644 --- a/src/android/Environment.hxx +++ b/src/android/Environment.hxx @@ -33,10 +33,10 @@ namespace Environment { /** * Determine the mount point of the external SD card. */ - gcc_pure + [[gnu::pure]] AllocatedPath getExternalStorageDirectory() noexcept; - gcc_pure + [[gnu::pure]] AllocatedPath getExternalStoragePublicDirectory(const char *type) noexcept; } From 2dba06dc34eb4b01463e1666bde883d8f2ddc8f2 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 24 Aug 2021 21:58:09 +0200 Subject: [PATCH 13/15] android/Context: add GetExternalFilesDir() --- src/android/Context.cxx | 19 +++++++++++++++++++ src/android/Context.hxx | 4 ++++ 2 files changed, 23 insertions(+) diff --git a/src/android/Context.cxx b/src/android/Context.cxx index 03c0cd52a..2c51779bd 100644 --- a/src/android/Context.cxx +++ b/src/android/Context.cxx @@ -26,6 +26,25 @@ #include "AudioManager.hxx" +AllocatedPath +Context::GetExternalFilesDir(JNIEnv *env, const char *_type) noexcept +{ + assert(_type != nullptr); + + Java::Class cls{env, env->GetObjectClass(Get())}; + jmethodID method = env->GetMethodID(cls, "getExternalFilesDir", + "(Ljava/lang/String;)Ljava/io/File;"); + assert(method); + + Java::String type{env, _type}; + + jobject file = env->CallObjectMethod(Get(), method, type.Get()); + if (Java::DiscardException(env) || file == nullptr) + return nullptr; + + return Java::File::ToAbsolutePath(env, file); +} + AllocatedPath Context::GetCacheDir(JNIEnv *env) const noexcept { diff --git a/src/android/Context.hxx b/src/android/Context.hxx index 6be0735a2..da3d27c05 100644 --- a/src/android/Context.hxx +++ b/src/android/Context.hxx @@ -30,6 +30,10 @@ public: Context(JNIEnv *env, jobject obj) noexcept :Java::GlobalObject(env, obj) {} + [[gnu::pure]] + AllocatedPath GetExternalFilesDir(JNIEnv *env, + const char *type) noexcept; + [[gnu::pure]] AllocatedPath GetCacheDir(JNIEnv *env) const noexcept; From 263d1ba002731eaab3686ce7429b47356e5cd53f Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 24 Aug 2021 22:11:54 +0200 Subject: [PATCH 14/15] Main: playlist_directory defaults to "/sdcard/Android/data/org.musicpd/files/playlists" Closes https://github.com/MusicPlayerDaemon/MPD/issues/1233 --- NEWS | 1 + src/Main.cxx | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 3107a602f..770a72aca 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,7 @@ ver 0.22.11 (not yet released) - ffmpeg: support double-precision samples (by converting to single precision) * Android - build with NDK r23 + - playlist_directory defaults to "/sdcard/Android/data/org.musicpd/files/playlists" ver 0.22.10 (2021/08/06) * protocol diff --git a/src/Main.cxx b/src/Main.cxx index 1a2a94ef5..c522fca61 100644 --- a/src/Main.cxx +++ b/src/Main.cxx @@ -157,7 +157,17 @@ glue_daemonize_init(const struct options *options, static void glue_mapper_init(const ConfigData &config) { - mapper_init(config.GetPath(ConfigOption::PLAYLIST_DIR)); + auto playlist_directory = config.GetPath(ConfigOption::PLAYLIST_DIR); + +#ifdef ANDROID + /* if there is no explicit configuration, store playlists in + "/sdcard/Android/data/org.musicpd/files/playlists" */ + if (playlist_directory.IsNull()) + playlist_directory = context->GetExternalFilesDir(Java::GetEnv(), + "playlists"); +#endif + + mapper_init(std::move(playlist_directory)); } #ifdef ENABLE_DATABASE From 94c196108d823618601277855f0f6c0b4f5abc03 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 24 Aug 2021 22:15:22 +0200 Subject: [PATCH 15/15] release v0.22.11 --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 770a72aca..920d8e522 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,4 @@ -ver 0.22.11 (not yet released) +ver 0.22.11 (2021/08/24) * protocol - fix "albumart" crash * filter