From c0d6008781c3032145e1740cb069830a3ef76eb4 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@musicpd.org>
Date: Mon, 18 Mar 2019 18:35:23 +0100
Subject: [PATCH] filter/hdcd: move generic code to class FfmpegFilter

---
 src/filter/plugins/FfmpegFilter.cxx     | 76 ++++++++++++++++++++++
 src/filter/plugins/FfmpegFilter.hxx     | 56 +++++++++++++++++
 src/filter/plugins/HdcdFilterPlugin.cxx | 83 +++++--------------------
 src/filter/plugins/meson.build          |  1 +
 4 files changed, 150 insertions(+), 66 deletions(-)
 create mode 100644 src/filter/plugins/FfmpegFilter.cxx
 create mode 100644 src/filter/plugins/FfmpegFilter.hxx

diff --git a/src/filter/plugins/FfmpegFilter.cxx b/src/filter/plugins/FfmpegFilter.cxx
new file mode 100644
index 000000000..bea6eec4b
--- /dev/null
+++ b/src/filter/plugins/FfmpegFilter.cxx
@@ -0,0 +1,76 @@
+/*
+ * 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 "FfmpegFilter.hxx"
+#include "lib/ffmpeg/SampleFormat.hxx"
+#include "util/ConstBuffer.hxx"
+
+extern "C" {
+#include <libavfilter/buffersrc.h>
+#include <libavfilter/buffersink.h>
+}
+
+FfmpegFilter::FfmpegFilter(const AudioFormat &in_audio_format,
+			   const AudioFormat &_out_audio_format,
+			   Ffmpeg::FilterGraph &&_graph,
+			   Ffmpeg::FilterContext &&_buffer_src,
+			   Ffmpeg::FilterContext &&_buffer_sink) noexcept
+	:Filter(_out_audio_format),
+	 graph(std::move(_graph)),
+	 buffer_src(std::move(_buffer_src)),
+	 buffer_sink(std::move(_buffer_sink)),
+	 in_audio_frame_size(in_audio_format.GetFrameSize()),
+	 out_audio_frame_size(_out_audio_format.GetFrameSize())
+{
+	in_frame->format = Ffmpeg::ToFfmpegSampleFormat(in_audio_format.format);
+	in_frame->sample_rate = in_audio_format.sample_rate;
+	in_frame->channels = in_audio_format.channels;
+}
+
+ConstBuffer<void>
+FfmpegFilter::FilterPCM(ConstBuffer<void> src)
+{
+	/* submit source data into the FFmpeg audio buffer source */
+
+	in_frame->nb_samples = src.size / in_audio_frame_size;
+
+	in_frame.GetBuffer();
+	in_frame.MakeWritable();
+
+	memcpy(in_frame.GetData(0), src.data, src.size);
+
+	int err = av_buffersrc_write_frame(buffer_src.get(), in_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()};
+}
diff --git a/src/filter/plugins/FfmpegFilter.hxx b/src/filter/plugins/FfmpegFilter.hxx
new file mode 100644
index 000000000..640a81eb3
--- /dev/null
+++ b/src/filter/plugins/FfmpegFilter.hxx
@@ -0,0 +1,56 @@
+/*
+ * 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_FFMPEG_FILTER__HXX
+#define MPD_FFMPEG_FILTER__HXX
+
+#include "filter/Filter.hxx"
+#include "lib/ffmpeg/Filter.hxx"
+#include "lib/ffmpeg/Frame.hxx"
+
+/**
+ * A #Filter implementation using FFmpeg's libavfilter.
+ */
+class FfmpegFilter final : public Filter {
+	Ffmpeg::FilterGraph graph;
+	Ffmpeg::FilterContext buffer_src, buffer_sink;
+	Ffmpeg::Frame in_frame, out_frame;
+
+	const size_t in_audio_frame_size;
+	const size_t out_audio_frame_size;
+
+public:
+	/**
+	 * @param _graph a checked and configured AVFilterGraph
+	 * @param _buffer_src an "abuffer" filter which serves as
+	 * input
+	 * @param _buffer_sink an "abuffersink" filter which serves as
+	 * output
+	 */
+	FfmpegFilter(const AudioFormat &in_audio_format,
+		     const AudioFormat &_out_audio_format,
+		     Ffmpeg::FilterGraph &&_graph,
+		     Ffmpeg::FilterContext &&_buffer_src,
+		     Ffmpeg::FilterContext &&_buffer_sink) noexcept;
+
+	/* virtual methods from class Filter */
+	ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
+};
+
+#endif
diff --git a/src/filter/plugins/HdcdFilterPlugin.cxx b/src/filter/plugins/HdcdFilterPlugin.cxx
index 32fa08237..33fdcf868 100644
--- a/src/filter/plugins/HdcdFilterPlugin.cxx
+++ b/src/filter/plugins/HdcdFilterPlugin.cxx
@@ -18,6 +18,7 @@
  */
 
 #include "HdcdFilterPlugin.hxx"
+#include "FfmpegFilter.hxx"
 #include "filter/FilterPlugin.hxx"
 #include "filter/Filter.hxx"
 #include "filter/NullFilter.hxx"
@@ -27,12 +28,6 @@
 #include "lib/ffmpeg/SampleFormat.hxx"
 #include "config/Block.hxx"
 #include "AudioFormat.hxx"
-#include "util/ConstBuffer.hxx"
-
-extern "C" {
-#include <libavfilter/buffersrc.h>
-#include <libavfilter/buffersink.h>
-}
 
 #include <string.h>
 
@@ -47,32 +42,20 @@ MaybeHdcd(const AudioFormat &audio_format) noexcept
 		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<void> FilterPCM(ConstBuffer<void> src) override;
-};
-
-inline
-HdcdFilter::HdcdFilter(AudioFormat &audio_format)
-	:Filter(audio_format)
+static auto
+OpenHdcdFilter(AudioFormat &in_audio_format)
 {
-	buffer_src = Ffmpeg::FilterContext::MakeAudioBufferSource(audio_format,
-								  *graph);
+	Ffmpeg::FilterGraph graph;
 
-	buffer_sink = Ffmpeg::FilterContext::MakeAudioBufferSink(*graph);
+	auto buffer_src =
+		Ffmpeg::FilterContext::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));
 
@@ -84,15 +67,15 @@ HdcdFilter::HdcdFilter(AudioFormat &audio_format)
 
 	graph.CheckAndConfigure();
 
-	frame->format = Ffmpeg::ToFfmpegSampleFormat(audio_format.format);
-	frame->sample_rate = audio_format.sample_rate;
-	frame->channels = audio_format.channels;
-
+	auto out_audio_format = in_audio_format;
 	// 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 = out_audio_format.GetFrameSize();
+	return std::make_unique<FfmpegFilter>(in_audio_format,
+					      out_audio_format,
+					      std::move(graph),
+					      std::move(buffer_src),
+					      std::move(buffer_sink));
 }
 
 class PreparedHdcdFilter final : public PreparedFilter {
@@ -105,45 +88,13 @@ std::unique_ptr<Filter>
 PreparedHdcdFilter::Open(AudioFormat &audio_format)
 {
 	if (MaybeHdcd(audio_format))
-		return std::make_unique<HdcdFilter>(audio_format);
+		return OpenHdcdFilter(audio_format);
 	else
 		/* this cannot be HDCD, so let's copy as-is using
 		   NullFilter */
 		return std::make_unique<NullFilter>(audio_format);
 }
 
-ConstBuffer<void>
-HdcdFilter::FilterPCM(ConstBuffer<void> 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<PreparedFilter>
 hdcd_filter_init(const ConfigBlock &)
 {
diff --git a/src/filter/plugins/meson.build b/src/filter/plugins/meson.build
index 371f08bfc..b9130a090 100644
--- a/src/filter/plugins/meson.build
+++ b/src/filter/plugins/meson.build
@@ -3,6 +3,7 @@ filter_plugins_deps = []
 
 if libavfilter_dep.found()
   filter_plugins_sources += [
+    'FfmpegFilter.cxx',
     'HdcdFilterPlugin.cxx',
   ]
   filter_plugins_deps += ffmpeg_dep