diff --git a/NEWS b/NEWS index 2ee609f6f..3c7871c0d 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,17 @@ ver 0.23 (not yet released) - new tags "ComposerSort", "Ensemble", "Movement", "MovementNumber", and "Location" * new build-time dependency: libfmt +ver 0.22.11 (2021/08/24) +* protocol + - fix "albumart" crash +* 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 + - playlist_directory defaults to "/sdcard/Android/data/org.musicpd/files/playlists" + 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 4cb565e82..8bd8ed71d 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="59" + android:versionName="0.22.11"> 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 2f1fb55f1..40ee79fd5 100644 --- a/doc/user.rst +++ b/doc/user.rst @@ -177,7 +177,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) diff --git a/src/Main.cxx b/src/Main.cxx index 2b2540e45..0a36bf48a 100644 --- a/src/Main.cxx +++ b/src/Main.cxx @@ -158,7 +158,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 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 bdbdc641f..da3d27c05 100644 --- a/src/android/Context.hxx +++ b/src/android/Context.hxx @@ -30,10 +30,14 @@ public: Context(JNIEnv *env, jobject obj) noexcept :Java::GlobalObject(env, obj) {} - gcc_pure + [[gnu::pure]] + AllocatedPath GetExternalFilesDir(JNIEnv *env, + const char *type) noexcept; + + [[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; } diff --git a/src/command/FileCommands.cxx b/src/command/FileCommands.cxx index 008e784e5..c66a62413 100644 --- a/src/command/FileCommands.cxx +++ b/src/command/FileCommands.cxx @@ -251,6 +251,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 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..20d70507d 100644 --- a/src/filter/plugins/FfmpegFilterPlugin.cxx +++ b/src/filter/plugins/FfmpegFilterPlugin.cxx @@ -37,39 +37,79 @@ 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) { 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); - auto io = graph.Parse(graph_string, std::move(io_sink), - std::move(io_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); - 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"); + 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 = - Ffmpeg::DetectFilterOutputFormat(in_audio_format, *buffer_src, - *buffer_sink); + Ffmpeg::DetectFilterOutputFormat(in_audio_format, buffer_src, + buffer_sink); + + if (!out_audio_format.IsDefined()) + /* 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, 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..29aedc40a 100644 --- a/src/filter/plugins/HdcdFilterPlugin.cxx +++ b/src/filter/plugins/HdcdFilterPlugin.cxx @@ -42,24 +42,13 @@ 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); - - 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"); + auto &buffer_sink = Ffmpeg::MakeAudioBufferSink(*graph); + graph.ParseSingleInOut(hdcd_graph, buffer_sink, buffer_src); graph.CheckAndConfigure(); auto out_audio_format = in_audio_format; @@ -69,8 +58,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/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/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, diff --git a/src/lib/ffmpeg/Filter.cxx b/src/lib/ffmpeg/Filter.cxx index 1749082c6..ca011a341 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 { @@ -36,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) { @@ -57,19 +83,72 @@ 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}; + 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); +} + +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); +} + +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) +{ + 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 8990bb6f1..d2e3a38c5 100644 --- a/src/lib/ffmpeg/Filter.hxx +++ b/src/lib/ffmpeg/Filter.hxx @@ -77,63 +77,38 @@ 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; +/** + * Create an "abuffersink" filter. + */ +AVFilterContext & +MakeAudioBufferSink(AVFilterGraph &graph_ctx); - 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"); - } +/** + * 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); - 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)) {} - - ~FilterContext() noexcept { - if (context != nullptr) - avfilter_free(context); - } - - 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 "aformat" filter which automatically converts the output + * to a format supported by MPD. + */ +AVFilterContext & +MakeAutoAformat(AVFilterGraph &graph_ctx); class FilterGraph { AVFilterGraph *graph = nullptr; @@ -180,6 +155,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,