From c673528cffc4b71cd27d59903162da85d67b453b Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 12 Mar 2019 12:24:14 +0100 Subject: [PATCH] filter/hdcd: new filter plugin based on FFmpeg's "af_hdcd" --- NEWS | 2 + src/filter/FilterRegistry.cxx | 5 + src/filter/plugins/HdcdFilterPlugin.cxx | 160 ++++++++++++++++++++++++ src/filter/plugins/HdcdFilterPlugin.hxx | 27 ++++ src/filter/plugins/meson.build | 13 +- 5 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 src/filter/plugins/HdcdFilterPlugin.cxx create mode 100644 src/filter/plugins/HdcdFilterPlugin.hxx diff --git a/NEWS b/NEWS index 2eaf2c0c9..6a93361b1 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,8 @@ ver 0.22 (not yet released) * input - ffmpeg: allow partial reads +* filter + - hdcd: new plugin based on FFmpeg's "af_hdcd" for HDCD playback ver 0.21.7 (not yet released) * require Meson 0.49.0 for native libgcrypt-config support diff --git a/src/filter/FilterRegistry.cxx b/src/filter/FilterRegistry.cxx index 9a5cad78f..a9995c4ee 100644 --- a/src/filter/FilterRegistry.cxx +++ b/src/filter/FilterRegistry.cxx @@ -22,6 +22,8 @@ #include "plugins/NullFilterPlugin.hxx" #include "plugins/RouteFilterPlugin.hxx" #include "plugins/NormalizeFilterPlugin.hxx" +#include "plugins/HdcdFilterPlugin.hxx" +#include "config.h" #include @@ -29,6 +31,9 @@ static const FilterPlugin *const filter_plugins[] = { &null_filter_plugin, &route_filter_plugin, &normalize_filter_plugin, +#ifdef HAVE_LIBAVFILTER + &hdcd_filter_plugin, +#endif nullptr, }; diff --git a/src/filter/plugins/HdcdFilterPlugin.cxx b/src/filter/plugins/HdcdFilterPlugin.cxx new file mode 100644 index 000000000..03392614a --- /dev/null +++ b/src/filter/plugins/HdcdFilterPlugin.cxx @@ -0,0 +1,160 @@ +/* + * Copyright 2003-2019 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 "HdcdFilterPlugin.hxx" +#include "filter/FilterPlugin.hxx" +#include "filter/Filter.hxx" +#include "filter/NullFilter.hxx" +#include "filter/Prepared.hxx" +#include "lib/ffmpeg/Filter.hxx" +#include "lib/ffmpeg/Frame.hxx" +#include "lib/ffmpeg/SampleFormat.hxx" +#include "config/Block.hxx" +#include "AudioFormat.hxx" +#include "util/ConstBuffer.hxx" + +extern "C" { +#include +#include +} + +#include + +static constexpr const char *hdcd_graph = "hdcd"; + +gcc_pure +static bool +MaybeHdcd(const AudioFormat &audio_format) noexcept +{ + return audio_format.sample_rate == 44100 && + audio_format.format == SampleFormat::S16 && + audio_format.channels == 2; +} + +class HdcdFilter final : public Filter { + Ffmpeg::FilterGraph graph; + Ffmpeg::FilterContext buffer_src, buffer_sink; + Ffmpeg::Frame frame, out_frame; + + size_t in_audio_frame_size; + size_t out_audio_frame_size; + +public: + explicit HdcdFilter(AudioFormat &audio_format); + + /* virtual methods from class Filter */ + ConstBuffer FilterPCM(ConstBuffer src) override; +}; + +inline +HdcdFilter::HdcdFilter(AudioFormat &audio_format) + :Filter(audio_format) +{ + buffer_src = Ffmpeg::FilterContext::MakeAudioBufferSource(audio_format, + *graph); + + 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"); + + graph.CheckAndConfigure(); + + frame->format = Ffmpeg::ToFfmpegSampleFormat(audio_format.format); + frame->sample_rate = audio_format.sample_rate; + frame->channels = audio_format.channels; + + // TODO: convert to 32 bit only if HDCD actually detected + out_audio_format.format = SampleFormat::S32; + + in_audio_frame_size = audio_format.GetFrameSize(); + out_audio_frame_size = audio_format.GetFrameSize(); +} + +class PreparedHdcdFilter final : public PreparedFilter { +public: + /* virtual methods from class PreparedFilter */ + std::unique_ptr Open(AudioFormat &af) override; +}; + +std::unique_ptr +PreparedHdcdFilter::Open(AudioFormat &audio_format) +{ + if (MaybeHdcd(audio_format)) + return std::make_unique(audio_format); + else + /* this cannot be HDCD, so let's copy as-is using + NullFilter */ + return std::make_unique(audio_format); +} + +ConstBuffer +HdcdFilter::FilterPCM(ConstBuffer src) +{ + /* submit source data into the FFmpeg audio buffer source */ + + frame->nb_samples = src.size / in_audio_frame_size; + + frame.GetBuffer(); + frame.MakeWritable(); + + memcpy(frame.GetData(0), src.data, src.size); + + int err = av_buffersrc_write_frame(buffer_src.get(), frame.get()); + if (err < 0) + throw MakeFfmpegError(err, "av_buffersrc_write_frame() failed"); + + /* collect filtered data from the FFmpeg audio buffer sink */ + + err = av_buffersink_get_frame(buffer_sink.get(), out_frame.get()); + if (err < 0) { + if (err == AVERROR(EAGAIN) || err == AVERROR_EOF) + return nullptr; + + throw MakeFfmpegError(err, "av_buffersink_get_frame() failed"); + } + + /* TODO: call av_buffersink_get_frame() repeatedly? Not + possible with MPD's current Filter API */ + + return {out_frame.GetData(0), out_frame->nb_samples * GetOutAudioFormat().GetFrameSize()}; +} + +static std::unique_ptr +hdcd_filter_init(const ConfigBlock &) +{ + /* check if the graph can be parsed (and discard the + object) */ + Ffmpeg::FilterGraph().Parse(hdcd_graph); + + return std::make_unique(); +} + +const FilterPlugin hdcd_filter_plugin = { + "hdcd", + hdcd_filter_init, +}; diff --git a/src/filter/plugins/HdcdFilterPlugin.hxx b/src/filter/plugins/HdcdFilterPlugin.hxx new file mode 100644 index 000000000..b0d6c0b29 --- /dev/null +++ b/src/filter/plugins/HdcdFilterPlugin.hxx @@ -0,0 +1,27 @@ +/* + * Copyright 2003-2019 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_HDCD_FILTER_PLUGIN_HXX +#define MPD_HDCD_FILTER_PLUGIN_HXX + +struct FilterPlugin; + +extern const FilterPlugin hdcd_filter_plugin; + +#endif diff --git a/src/filter/plugins/meson.build b/src/filter/plugins/meson.build index 10370225a..371f08bfc 100644 --- a/src/filter/plugins/meson.build +++ b/src/filter/plugins/meson.build @@ -1,3 +1,13 @@ +filter_plugins_sources = [] +filter_plugins_deps = [] + +if libavfilter_dep.found() + filter_plugins_sources += [ + 'HdcdFilterPlugin.cxx', + ] + filter_plugins_deps += ffmpeg_dep +endif + filter_plugins = static_library( 'filter_plugins', '../../AudioCompress/compress.c', @@ -9,6 +19,7 @@ filter_plugins = static_library( 'NormalizeFilterPlugin.cxx', 'ReplayGainFilterPlugin.cxx', 'VolumeFilterPlugin.cxx', + filter_plugins_sources, include_directories: inc, ) @@ -18,5 +29,5 @@ filter_plugins_dep = declare_dependency( filter_api_dep, pcm_dep, config_dep, - ], + ] + filter_plugins_deps, )