From 5a8271ad70fb30460aa0aefb85fbaca484fceb01 Mon Sep 17 00:00:00 2001
From: Xavier Roumegue <xavier.roumegue@oss.nxp.com>
Date: Wed, 14 Dec 2022 16:52:18 +0100
Subject: libcamera: pipeline: simple: converter: Use generic converter
 interface

Move the simple converter implementation to a generic V4L2 M2M class
derived from the converter interface. This latter could be used by
other pipeline implementations and as base class for customized V4L2 M2M
converters.

Signed-off-by: Xavier Roumegue <xavier.roumegue@oss.nxp.com>
Reviewed-by: Jacopo Mondi <jacopo@jmondi.org>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
---
 .../internal/converter/converter_v4l2_m2m.h        |  98 +++++
 include/libcamera/internal/converter/meson.build   |   5 +
 include/libcamera/internal/meson.build             |   2 +
 src/libcamera/converter/converter_v4l2_m2m.cpp     | 453 +++++++++++++++++++++
 src/libcamera/converter/meson.build                |   5 +
 src/libcamera/meson.build                          |   1 +
 src/libcamera/pipeline/simple/converter.cpp        | 405 ------------------
 src/libcamera/pipeline/simple/converter.h          |  98 -----
 src/libcamera/pipeline/simple/meson.build          |   1 -
 src/libcamera/pipeline/simple/simple.cpp           |   6 +-
 10 files changed, 567 insertions(+), 507 deletions(-)
 create mode 100644 include/libcamera/internal/converter/converter_v4l2_m2m.h
 create mode 100644 include/libcamera/internal/converter/meson.build
 create mode 100644 src/libcamera/converter/converter_v4l2_m2m.cpp
 create mode 100644 src/libcamera/converter/meson.build
 delete mode 100644 src/libcamera/pipeline/simple/converter.cpp
 delete mode 100644 src/libcamera/pipeline/simple/converter.h

diff --git a/include/libcamera/internal/converter/converter_v4l2_m2m.h b/include/libcamera/internal/converter/converter_v4l2_m2m.h
new file mode 100644
index 00000000..815916d0
--- /dev/null
+++ b/include/libcamera/internal/converter/converter_v4l2_m2m.h
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Laurent Pinchart
+ * Copyright 2022 NXP
+ *
+ * converter_v4l2_m2m.h - V4l2 M2M Format converter interface
+ */
+
+#pragma once
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/signal.h>
+
+#include <libcamera/pixel_format.h>
+
+#include "libcamera/internal/converter.h"
+
+namespace libcamera {
+
+class FrameBuffer;
+class MediaDevice;
+class Size;
+class SizeRange;
+struct StreamConfiguration;
+class V4L2M2MDevice;
+
+class V4L2M2MConverter : public Converter
+{
+public:
+	V4L2M2MConverter(MediaDevice *media);
+
+	int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; }
+	bool isValid() const { return m2m_ != nullptr; }
+
+	std::vector<PixelFormat> formats(PixelFormat input);
+	SizeRange sizes(const Size &input);
+
+	std::tuple<unsigned int, unsigned int>
+	strideAndFrameSize(const PixelFormat &pixelFormat, const Size &size);
+
+	int configure(const StreamConfiguration &inputCfg,
+		      const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfg);
+	int exportBuffers(unsigned int ouput, unsigned int count,
+			  std::vector<std::unique_ptr<FrameBuffer>> *buffers);
+
+	int start();
+	void stop();
+
+	int queueBuffers(FrameBuffer *input,
+			 const std::map<unsigned int, FrameBuffer *> &outputs);
+
+private:
+	class Stream : protected Loggable
+	{
+	public:
+		Stream(V4L2M2MConverter *converter, unsigned int index);
+
+		bool isValid() const { return m2m_ != nullptr; }
+
+		int configure(const StreamConfiguration &inputCfg,
+			      const StreamConfiguration &outputCfg);
+		int exportBuffers(unsigned int count,
+				  std::vector<std::unique_ptr<FrameBuffer>> *buffers);
+
+		int start();
+		void stop();
+
+		int queueBuffers(FrameBuffer *input, FrameBuffer *output);
+
+	protected:
+		std::string logPrefix() const override;
+
+	private:
+		void captureBufferReady(FrameBuffer *buffer);
+		void outputBufferReady(FrameBuffer *buffer);
+
+		V4L2M2MConverter *converter_;
+		unsigned int index_;
+		std::unique_ptr<V4L2M2MDevice> m2m_;
+
+		unsigned int inputBufferCount_;
+		unsigned int outputBufferCount_;
+	};
+
+	std::unique_ptr<V4L2M2MDevice> m2m_;
+
+	std::vector<Stream> streams_;
+	std::map<FrameBuffer *, unsigned int> queue_;
+};
+
+} /* namespace libcamera */
diff --git a/include/libcamera/internal/converter/meson.build b/include/libcamera/internal/converter/meson.build
new file mode 100644
index 00000000..891e79e7
--- /dev/null
+++ b/include/libcamera/internal/converter/meson.build
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: CC0-1.0
+
+libcamera_internal_headers += files([
+    'converter_v4l2_m2m.h',
+])
diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
index 341af8a2..d7508805 100644
--- a/include/libcamera/internal/meson.build
+++ b/include/libcamera/internal/meson.build
@@ -45,3 +45,5 @@ libcamera_internal_headers = files([
     'v4l2_videodevice.h',
     'yaml_parser.h',
 ])
+
+subdir('converter')
diff --git a/src/libcamera/converter/converter_v4l2_m2m.cpp b/src/libcamera/converter/converter_v4l2_m2m.cpp
new file mode 100644
index 00000000..2a4d1d99
--- /dev/null
+++ b/src/libcamera/converter/converter_v4l2_m2m.cpp
@@ -0,0 +1,453 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Laurent Pinchart
+ * Copyright 2022 NXP
+ *
+ * converter_v4l2_m2m.cpp - V4L2 M2M Format converter
+ */
+
+#include "libcamera/internal/converter/converter_v4l2_m2m.h"
+
+#include <algorithm>
+#include <limits.h>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/signal.h>
+#include <libcamera/base/utils.h>
+
+#include <libcamera/framebuffer.h>
+#include <libcamera/geometry.h>
+#include <libcamera/stream.h>
+
+#include "libcamera/internal/media_device.h"
+#include "libcamera/internal/v4l2_videodevice.h"
+
+/**
+ * \file internal/converter/converter_v4l2_m2m.h
+ * \brief V4L2 M2M based converter
+ */
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(Converter)
+
+/* -----------------------------------------------------------------------------
+ * V4L2M2MConverter::Stream
+ */
+
+V4L2M2MConverter::Stream::Stream(V4L2M2MConverter *converter, unsigned int index)
+	: converter_(converter), index_(index)
+{
+	m2m_ = std::make_unique<V4L2M2MDevice>(converter->deviceNode());
+
+	m2m_->output()->bufferReady.connect(this, &Stream::outputBufferReady);
+	m2m_->capture()->bufferReady.connect(this, &Stream::captureBufferReady);
+
+	int ret = m2m_->open();
+	if (ret < 0)
+		m2m_.reset();
+}
+
+int V4L2M2MConverter::Stream::configure(const StreamConfiguration &inputCfg,
+					const StreamConfiguration &outputCfg)
+{
+	V4L2PixelFormat videoFormat =
+		m2m_->output()->toV4L2PixelFormat(inputCfg.pixelFormat);
+
+	V4L2DeviceFormat format;
+	format.fourcc = videoFormat;
+	format.size = inputCfg.size;
+	format.planesCount = 1;
+	format.planes[0].bpl = inputCfg.stride;
+
+	int ret = m2m_->output()->setFormat(&format);
+	if (ret < 0) {
+		LOG(Converter, Error)
+			<< "Failed to set input format: " << strerror(-ret);
+		return ret;
+	}
+
+	if (format.fourcc != videoFormat || format.size != inputCfg.size ||
+	    format.planes[0].bpl != inputCfg.stride) {
+		LOG(Converter, Error)
+			<< "Input format not supported (requested "
+			<< inputCfg.size << "-" << videoFormat
+			<< ", got " << format << ")";
+		return -EINVAL;
+	}
+
+	/* Set the pixel format and size on the output. */
+	videoFormat = m2m_->capture()->toV4L2PixelFormat(outputCfg.pixelFormat);
+	format = {};
+	format.fourcc = videoFormat;
+	format.size = outputCfg.size;
+
+	ret = m2m_->capture()->setFormat(&format);
+	if (ret < 0) {
+		LOG(Converter, Error)
+			<< "Failed to set output format: " << strerror(-ret);
+		return ret;
+	}
+
+	if (format.fourcc != videoFormat || format.size != outputCfg.size) {
+		LOG(Converter, Error)
+			<< "Output format not supported";
+		return -EINVAL;
+	}
+
+	inputBufferCount_ = inputCfg.bufferCount;
+	outputBufferCount_ = outputCfg.bufferCount;
+
+	return 0;
+}
+
+int V4L2M2MConverter::Stream::exportBuffers(unsigned int count,
+					    std::vector<std::unique_ptr<FrameBuffer>> *buffers)
+{
+	return m2m_->capture()->exportBuffers(count, buffers);
+}
+
+int V4L2M2MConverter::Stream::start()
+{
+	int ret = m2m_->output()->importBuffers(inputBufferCount_);
+	if (ret < 0)
+		return ret;
+
+	ret = m2m_->capture()->importBuffers(outputBufferCount_);
+	if (ret < 0) {
+		stop();
+		return ret;
+	}
+
+	ret = m2m_->output()->streamOn();
+	if (ret < 0) {
+		stop();
+		return ret;
+	}
+
+	ret = m2m_->capture()->streamOn();
+	if (ret < 0) {
+		stop();
+		return ret;
+	}
+
+	return 0;
+}
+
+void V4L2M2MConverter::Stream::stop()
+{
+	m2m_->capture()->streamOff();
+	m2m_->output()->streamOff();
+	m2m_->capture()->releaseBuffers();
+	m2m_->output()->releaseBuffers();
+}
+
+int V4L2M2MConverter::Stream::queueBuffers(FrameBuffer *input, FrameBuffer *output)
+{
+	int ret = m2m_->output()->queueBuffer(input);
+	if (ret < 0)
+		return ret;
+
+	ret = m2m_->capture()->queueBuffer(output);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+std::string V4L2M2MConverter::Stream::logPrefix() const
+{
+	return "stream" + std::to_string(index_);
+}
+
+void V4L2M2MConverter::Stream::outputBufferReady(FrameBuffer *buffer)
+{
+	auto it = converter_->queue_.find(buffer);
+	if (it == converter_->queue_.end())
+		return;
+
+	if (!--it->second) {
+		converter_->inputBufferReady.emit(buffer);
+		converter_->queue_.erase(it);
+	}
+}
+
+void V4L2M2MConverter::Stream::captureBufferReady(FrameBuffer *buffer)
+{
+	converter_->outputBufferReady.emit(buffer);
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2M2MConverter
+ */
+
+/**
+ * \class libcamera::V4L2M2MConverter
+ * \brief The V4L2 M2M converter implements the converter interface based on
+ * V4L2 M2M device.
+*/
+
+/**
+ * \fn V4L2M2MConverter::V4L2M2MConverter
+ * \brief Construct a V4L2M2MConverter instance
+ * \param[in] media The media device implementing the converter
+ */
+
+V4L2M2MConverter::V4L2M2MConverter(MediaDevice *media)
+	: Converter(media)
+{
+	if (deviceNode().empty())
+		return;
+
+	m2m_ = std::make_unique<V4L2M2MDevice>(deviceNode());
+	int ret = m2m_->open();
+	if (ret < 0) {
+		m2m_.reset();
+		return;
+	}
+}
+
+/**
+ * \fn libcamera::V4L2M2MConverter::loadConfiguration
+ * \details \copydetails libcamera::Converter::loadConfiguration
+ */
+
+/**
+ * \fn libcamera::V4L2M2MConverter::isValid
+ * \details \copydetails libcamera::Converter::isValid
+ */
+
+/**
+ * \fn libcamera::V4L2M2MConverter::formats
+ * \details \copydetails libcamera::Converter::formats
+ */
+std::vector<PixelFormat> V4L2M2MConverter::formats(PixelFormat input)
+{
+	if (!m2m_)
+		return {};
+
+	/*
+	 * Set the format on the input side (V4L2 output) of the converter to
+	 * enumerate the conversion capabilities on its output (V4L2 capture).
+	 */
+	V4L2DeviceFormat v4l2Format;
+	v4l2Format.fourcc = m2m_->output()->toV4L2PixelFormat(input);
+	v4l2Format.size = { 1, 1 };
+
+	int ret = m2m_->output()->setFormat(&v4l2Format);
+	if (ret < 0) {
+		LOG(Converter, Error)
+			<< "Failed to set format: " << strerror(-ret);
+		return {};
+	}
+
+	if (v4l2Format.fourcc != m2m_->output()->toV4L2PixelFormat(input)) {
+		LOG(Converter, Debug)
+			<< "Input format " << input << " not supported.";
+		return {};
+	}
+
+	std::vector<PixelFormat> pixelFormats;
+
+	for (const auto &format : m2m_->capture()->formats()) {
+		PixelFormat pixelFormat = format.first.toPixelFormat();
+		if (pixelFormat)
+			pixelFormats.push_back(pixelFormat);
+	}
+
+	return pixelFormats;
+}
+
+/**
+ * \copydoc libcamera::Converter::sizes
+ */
+SizeRange V4L2M2MConverter::sizes(const Size &input)
+{
+	if (!m2m_)
+		return {};
+
+	/*
+	 * Set the size on the input side (V4L2 output) of the converter to
+	 * enumerate the scaling capabilities on its output (V4L2 capture).
+	 */
+	V4L2DeviceFormat format;
+	format.fourcc = V4L2PixelFormat();
+	format.size = input;
+
+	int ret = m2m_->output()->setFormat(&format);
+	if (ret < 0) {
+		LOG(Converter, Error)
+			<< "Failed to set format: " << strerror(-ret);
+		return {};
+	}
+
+	SizeRange sizes;
+
+	format.size = { 1, 1 };
+	ret = m2m_->capture()->setFormat(&format);
+	if (ret < 0) {
+		LOG(Converter, Error)
+			<< "Failed to set format: " << strerror(-ret);
+		return {};
+	}
+
+	sizes.min = format.size;
+
+	format.size = { UINT_MAX, UINT_MAX };
+	ret = m2m_->capture()->setFormat(&format);
+	if (ret < 0) {
+		LOG(Converter, Error)
+			<< "Failed to set format: " << strerror(-ret);
+		return {};
+	}
+
+	sizes.max = format.size;
+
+	return sizes;
+}
+
+/**
+ * \copydoc libcamera::Converter::strideAndFrameSize
+ */
+std::tuple<unsigned int, unsigned int>
+V4L2M2MConverter::strideAndFrameSize(const PixelFormat &pixelFormat,
+				     const Size &size)
+{
+	V4L2DeviceFormat format;
+	format.fourcc = m2m_->capture()->toV4L2PixelFormat(pixelFormat);
+	format.size = size;
+
+	int ret = m2m_->capture()->tryFormat(&format);
+	if (ret < 0)
+		return std::make_tuple(0, 0);
+
+	return std::make_tuple(format.planes[0].bpl, format.planes[0].size);
+}
+
+/**
+ * \copydoc libcamera::Converter::configure
+ */
+int V4L2M2MConverter::configure(const StreamConfiguration &inputCfg,
+				const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs)
+{
+	int ret = 0;
+
+	streams_.clear();
+	streams_.reserve(outputCfgs.size());
+
+	for (unsigned int i = 0; i < outputCfgs.size(); ++i) {
+		Stream &stream = streams_.emplace_back(this, i);
+
+		if (!stream.isValid()) {
+			LOG(Converter, Error)
+				<< "Failed to create stream " << i;
+			ret = -EINVAL;
+			break;
+		}
+
+		ret = stream.configure(inputCfg, outputCfgs[i]);
+		if (ret < 0)
+			break;
+	}
+
+	if (ret < 0) {
+		streams_.clear();
+		return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * \copydoc libcamera::Converter::exportBuffers
+ */
+int V4L2M2MConverter::exportBuffers(unsigned int output, unsigned int count,
+				    std::vector<std::unique_ptr<FrameBuffer>> *buffers)
+{
+	if (output >= streams_.size())
+		return -EINVAL;
+
+	return streams_[output].exportBuffers(count, buffers);
+}
+
+/**
+ * \copydoc libcamera::Converter::start
+ */
+int V4L2M2MConverter::start()
+{
+	int ret;
+
+	for (Stream &stream : streams_) {
+		ret = stream.start();
+		if (ret < 0) {
+			stop();
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * \copydoc libcamera::Converter::stop
+ */
+void V4L2M2MConverter::stop()
+{
+	for (Stream &stream : utils::reverse(streams_))
+		stream.stop();
+}
+
+/**
+ * \copydoc libcamera::Converter::queueBuffers
+ */
+int V4L2M2MConverter::queueBuffers(FrameBuffer *input,
+				   const std::map<unsigned int, FrameBuffer *> &outputs)
+{
+	unsigned int mask = 0;
+	int ret;
+
+	/*
+	 * Validate the outputs as a sanity check: at least one output is
+	 * required, all outputs must reference a valid stream and no two
+	 * outputs can reference the same stream.
+	 */
+	if (outputs.empty())
+		return -EINVAL;
+
+	for (auto [index, buffer] : outputs) {
+		if (!buffer)
+			return -EINVAL;
+		if (index >= streams_.size())
+			return -EINVAL;
+		if (mask & (1 << index))
+			return -EINVAL;
+
+		mask |= 1 << index;
+	}
+
+	/* Queue the input and output buffers to all the streams. */
+	for (auto [index, buffer] : outputs) {
+		ret = streams_[index].queueBuffers(input, buffer);
+		if (ret < 0)
+			return ret;
+	}
+
+	/*
+	 * Add the input buffer to the queue, with the number of streams as a
+	 * reference count. Completion of the input buffer will be signalled by
+	 * the stream that releases the last reference.
+	 */
+	queue_.emplace(std::piecewise_construct,
+		       std::forward_as_tuple(input),
+		       std::forward_as_tuple(outputs.size()));
+
+	return 0;
+}
+
+static std::initializer_list<std::string> compatibles = {
+	"pxp",
+};
+
+REGISTER_CONVERTER("v4l2_m2m", V4L2M2MConverter, compatibles)
+
+} /* namespace libcamera */
diff --git a/src/libcamera/converter/meson.build b/src/libcamera/converter/meson.build
new file mode 100644
index 00000000..2aa72fe4
--- /dev/null
+++ b/src/libcamera/converter/meson.build
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: CC0-1.0
+
+libcamera_sources += files([
+        'converter_v4l2_m2m.cpp'
+])
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index e9d0324e..ffc294f3 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -62,6 +62,7 @@ libatomic = cc.find_library('atomic', required : false)
 libthreads = dependency('threads')
 
 subdir('base')
+subdir('converter')
 subdir('ipa')
 subdir('pipeline')
 subdir('proxy')
diff --git a/src/libcamera/pipeline/simple/converter.cpp b/src/libcamera/pipeline/simple/converter.cpp
deleted file mode 100644
index acaaa64c..00000000
--- a/src/libcamera/pipeline/simple/converter.cpp
+++ /dev/null
@@ -1,405 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2020, Laurent Pinchart
- *
- * converter.cpp - Format converter for simple pipeline handler
- */
-
-#include "converter.h"
-
-#include <algorithm>
-#include <limits.h>
-
-#include <libcamera/base/log.h>
-#include <libcamera/base/signal.h>
-#include <libcamera/base/utils.h>
-
-#include <libcamera/framebuffer.h>
-#include <libcamera/geometry.h>
-#include <libcamera/stream.h>
-
-#include "libcamera/internal/media_device.h"
-#include "libcamera/internal/v4l2_videodevice.h"
-
-namespace libcamera {
-
-LOG_DECLARE_CATEGORY(SimplePipeline)
-
-/* -----------------------------------------------------------------------------
- * SimpleConverter::Stream
- */
-
-SimpleConverter::Stream::Stream(SimpleConverter *converter, unsigned int index)
-	: converter_(converter), index_(index)
-{
-	m2m_ = std::make_unique<V4L2M2MDevice>(converter->deviceNode_);
-
-	m2m_->output()->bufferReady.connect(this, &Stream::outputBufferReady);
-	m2m_->capture()->bufferReady.connect(this, &Stream::captureBufferReady);
-
-	int ret = m2m_->open();
-	if (ret < 0)
-		m2m_.reset();
-}
-
-int SimpleConverter::Stream::configure(const StreamConfiguration &inputCfg,
-				       const StreamConfiguration &outputCfg)
-{
-	V4L2PixelFormat videoFormat =
-		m2m_->output()->toV4L2PixelFormat(inputCfg.pixelFormat);
-
-	V4L2DeviceFormat format;
-	format.fourcc = videoFormat;
-	format.size = inputCfg.size;
-	format.planesCount = 1;
-	format.planes[0].bpl = inputCfg.stride;
-
-	int ret = m2m_->output()->setFormat(&format);
-	if (ret < 0) {
-		LOG(SimplePipeline, Error)
-			<< "Failed to set input format: " << strerror(-ret);
-		return ret;
-	}
-
-	if (format.fourcc != videoFormat || format.size != inputCfg.size ||
-	    format.planes[0].bpl != inputCfg.stride) {
-		LOG(SimplePipeline, Error)
-			<< "Input format not supported (requested "
-			<< inputCfg.size << "-" << videoFormat
-			<< ", got " << format << ")";
-		return -EINVAL;
-	}
-
-	/* Set the pixel format and size on the output. */
-	videoFormat = m2m_->capture()->toV4L2PixelFormat(outputCfg.pixelFormat);
-	format = {};
-	format.fourcc = videoFormat;
-	format.size = outputCfg.size;
-
-	ret = m2m_->capture()->setFormat(&format);
-	if (ret < 0) {
-		LOG(SimplePipeline, Error)
-			<< "Failed to set output format: " << strerror(-ret);
-		return ret;
-	}
-
-	if (format.fourcc != videoFormat || format.size != outputCfg.size) {
-		LOG(SimplePipeline, Error)
-			<< "Output format not supported";
-		return -EINVAL;
-	}
-
-	inputBufferCount_ = inputCfg.bufferCount;
-	outputBufferCount_ = outputCfg.bufferCount;
-
-	return 0;
-}
-
-int SimpleConverter::Stream::exportBuffers(unsigned int count,
-					   std::vector<std::unique_ptr<FrameBuffer>> *buffers)
-{
-	return m2m_->capture()->exportBuffers(count, buffers);
-}
-
-int SimpleConverter::Stream::start()
-{
-	int ret = m2m_->output()->importBuffers(inputBufferCount_);
-	if (ret < 0)
-		return ret;
-
-	ret = m2m_->capture()->importBuffers(outputBufferCount_);
-	if (ret < 0) {
-		stop();
-		return ret;
-	}
-
-	ret = m2m_->output()->streamOn();
-	if (ret < 0) {
-		stop();
-		return ret;
-	}
-
-	ret = m2m_->capture()->streamOn();
-	if (ret < 0) {
-		stop();
-		return ret;
-	}
-
-	return 0;
-}
-
-void SimpleConverter::Stream::stop()
-{
-	m2m_->capture()->streamOff();
-	m2m_->output()->streamOff();
-	m2m_->capture()->releaseBuffers();
-	m2m_->output()->releaseBuffers();
-}
-
-int SimpleConverter::Stream::queueBuffers(FrameBuffer *input,
-					  FrameBuffer *output)
-{
-	int ret = m2m_->output()->queueBuffer(input);
-	if (ret < 0)
-		return ret;
-
-	ret = m2m_->capture()->queueBuffer(output);
-	if (ret < 0)
-		return ret;
-
-	return 0;
-}
-
-std::string SimpleConverter::Stream::logPrefix() const
-{
-	return "stream" + std::to_string(index_);
-}
-
-void SimpleConverter::Stream::outputBufferReady(FrameBuffer *buffer)
-{
-	auto it = converter_->queue_.find(buffer);
-	if (it == converter_->queue_.end())
-		return;
-
-	if (!--it->second) {
-		converter_->inputBufferReady.emit(buffer);
-		converter_->queue_.erase(it);
-	}
-}
-
-void SimpleConverter::Stream::captureBufferReady(FrameBuffer *buffer)
-{
-	converter_->outputBufferReady.emit(buffer);
-}
-
-/* -----------------------------------------------------------------------------
- * SimpleConverter
- */
-
-SimpleConverter::SimpleConverter(MediaDevice *media)
-{
-	/*
-	 * Locate the video node. There's no need to validate the pipeline
-	 * further, the caller guarantees that this is a V4L2 mem2mem device.
-	 */
-	const std::vector<MediaEntity *> &entities = media->entities();
-	auto it = std::find_if(entities.begin(), entities.end(),
-			       [](MediaEntity *entity) {
-				       return entity->function() == MEDIA_ENT_F_IO_V4L;
-			       });
-	if (it == entities.end())
-		return;
-
-	deviceNode_ = (*it)->deviceNode();
-
-	m2m_ = std::make_unique<V4L2M2MDevice>(deviceNode_);
-	int ret = m2m_->open();
-	if (ret < 0) {
-		m2m_.reset();
-		return;
-	}
-}
-
-std::vector<PixelFormat> SimpleConverter::formats(PixelFormat input)
-{
-	if (!m2m_)
-		return {};
-
-	/*
-	 * Set the format on the input side (V4L2 output) of the converter to
-	 * enumerate the conversion capabilities on its output (V4L2 capture).
-	 */
-	V4L2DeviceFormat v4l2Format;
-	v4l2Format.fourcc = m2m_->output()->toV4L2PixelFormat(input);
-	v4l2Format.size = { 1, 1 };
-
-	int ret = m2m_->output()->setFormat(&v4l2Format);
-	if (ret < 0) {
-		LOG(SimplePipeline, Error)
-			<< "Failed to set format: " << strerror(-ret);
-		return {};
-	}
-
-	if (v4l2Format.fourcc != m2m_->output()->toV4L2PixelFormat(input)) {
-		LOG(SimplePipeline, Debug)
-			<< "Input format " << input << " not supported.";
-		return {};
-	}
-
-	std::vector<PixelFormat> pixelFormats;
-
-	for (const auto &format : m2m_->capture()->formats()) {
-		PixelFormat pixelFormat = format.first.toPixelFormat();
-		if (pixelFormat)
-			pixelFormats.push_back(pixelFormat);
-	}
-
-	return pixelFormats;
-}
-
-SizeRange SimpleConverter::sizes(const Size &input)
-{
-	if (!m2m_)
-		return {};
-
-	/*
-	 * Set the size on the input side (V4L2 output) of the converter to
-	 * enumerate the scaling capabilities on its output (V4L2 capture).
-	 */
-	V4L2DeviceFormat format;
-	format.fourcc = V4L2PixelFormat();
-	format.size = input;
-
-	int ret = m2m_->output()->setFormat(&format);
-	if (ret < 0) {
-		LOG(SimplePipeline, Error)
-			<< "Failed to set format: " << strerror(-ret);
-		return {};
-	}
-
-	SizeRange sizes;
-
-	format.size = { 1, 1 };
-	ret = m2m_->capture()->setFormat(&format);
-	if (ret < 0) {
-		LOG(SimplePipeline, Error)
-			<< "Failed to set format: " << strerror(-ret);
-		return {};
-	}
-
-	sizes.min = format.size;
-
-	format.size = { UINT_MAX, UINT_MAX };
-	ret = m2m_->capture()->setFormat(&format);
-	if (ret < 0) {
-		LOG(SimplePipeline, Error)
-			<< "Failed to set format: " << strerror(-ret);
-		return {};
-	}
-
-	sizes.max = format.size;
-
-	return sizes;
-}
-
-std::tuple<unsigned int, unsigned int>
-SimpleConverter::strideAndFrameSize(const PixelFormat &pixelFormat,
-				    const Size &size)
-{
-	V4L2DeviceFormat format;
-	format.fourcc = m2m_->capture()->toV4L2PixelFormat(pixelFormat);
-	format.size = size;
-
-	int ret = m2m_->capture()->tryFormat(&format);
-	if (ret < 0)
-		return std::make_tuple(0, 0);
-
-	return std::make_tuple(format.planes[0].bpl, format.planes[0].size);
-}
-
-int SimpleConverter::configure(const StreamConfiguration &inputCfg,
-			       const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs)
-{
-	int ret = 0;
-
-	streams_.clear();
-	streams_.reserve(outputCfgs.size());
-
-	for (unsigned int i = 0; i < outputCfgs.size(); ++i) {
-		Stream &stream = streams_.emplace_back(this, i);
-
-		if (!stream.isValid()) {
-			LOG(SimplePipeline, Error)
-				<< "Failed to create stream " << i;
-			ret = -EINVAL;
-			break;
-		}
-
-		ret = stream.configure(inputCfg, outputCfgs[i]);
-		if (ret < 0)
-			break;
-	}
-
-	if (ret < 0) {
-		streams_.clear();
-		return ret;
-	}
-
-	return 0;
-}
-
-int SimpleConverter::exportBuffers(unsigned int output, unsigned int count,
-				   std::vector<std::unique_ptr<FrameBuffer>> *buffers)
-{
-	if (output >= streams_.size())
-		return -EINVAL;
-
-	return streams_[output].exportBuffers(count, buffers);
-}
-
-int SimpleConverter::start()
-{
-	int ret;
-
-	for (Stream &stream : streams_) {
-		ret = stream.start();
-		if (ret < 0) {
-			stop();
-			return ret;
-		}
-	}
-
-	return 0;
-}
-
-void SimpleConverter::stop()
-{
-	for (Stream &stream : utils::reverse(streams_))
-		stream.stop();
-}
-
-int SimpleConverter::queueBuffers(FrameBuffer *input,
-				  const std::map<unsigned int, FrameBuffer *> &outputs)
-{
-	unsigned int mask = 0;
-	int ret;
-
-	/*
-	 * Validate the outputs as a sanity check: at least one output is
-	 * required, all outputs must reference a valid stream and no two
-	 * outputs can reference the same stream.
-	 */
-	if (outputs.empty())
-		return -EINVAL;
-
-	for (auto [index, buffer] : outputs) {
-		if (!buffer)
-			return -EINVAL;
-		if (index >= streams_.size())
-			return -EINVAL;
-		if (mask & (1 << index))
-			return -EINVAL;
-
-		mask |= 1 << index;
-	}
-
-	/* Queue the input and output buffers to all the streams. */
-	for (auto [index, buffer] : outputs) {
-		ret = streams_[index].queueBuffers(input, buffer);
-		if (ret < 0)
-			return ret;
-	}
-
-	/*
-	 * Add the input buffer to the queue, with the number of streams as a
-	 * reference count. Completion of the input buffer will be signalled by
-	 * the stream that releases the last reference.
-	 */
-	queue_.emplace(std::piecewise_construct,
-		       std::forward_as_tuple(input),
-		       std::forward_as_tuple(outputs.size()));
-
-	return 0;
-}
-
-} /* namespace libcamera */
diff --git a/src/libcamera/pipeline/simple/converter.h b/src/libcamera/pipeline/simple/converter.h
deleted file mode 100644
index f0ebe2e0..00000000
--- a/src/libcamera/pipeline/simple/converter.h
+++ /dev/null
@@ -1,98 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2020, Laurent Pinchart
- *
- * converter.h - Format converter for simple pipeline handler
- */
-
-#pragma once
-
-#include <functional>
-#include <map>
-#include <memory>
-#include <string>
-#include <tuple>
-#include <vector>
-
-#include <libcamera/pixel_format.h>
-
-#include <libcamera/base/log.h>
-#include <libcamera/base/signal.h>
-
-namespace libcamera {
-
-class FrameBuffer;
-class MediaDevice;
-class Size;
-class SizeRange;
-struct StreamConfiguration;
-class V4L2M2MDevice;
-
-class SimpleConverter
-{
-public:
-	SimpleConverter(MediaDevice *media);
-
-	bool isValid() const { return m2m_ != nullptr; }
-
-	std::vector<PixelFormat> formats(PixelFormat input);
-	SizeRange sizes(const Size &input);
-
-	std::tuple<unsigned int, unsigned int>
-	strideAndFrameSize(const PixelFormat &pixelFormat, const Size &size);
-
-	int configure(const StreamConfiguration &inputCfg,
-		      const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfg);
-	int exportBuffers(unsigned int ouput, unsigned int count,
-			  std::vector<std::unique_ptr<FrameBuffer>> *buffers);
-
-	int start();
-	void stop();
-
-	int queueBuffers(FrameBuffer *input,
-			 const std::map<unsigned int, FrameBuffer *> &outputs);
-
-	Signal<FrameBuffer *> inputBufferReady;
-	Signal<FrameBuffer *> outputBufferReady;
-
-private:
-	class Stream : protected Loggable
-	{
-	public:
-		Stream(SimpleConverter *converter, unsigned int index);
-
-		bool isValid() const { return m2m_ != nullptr; }
-
-		int configure(const StreamConfiguration &inputCfg,
-			      const StreamConfiguration &outputCfg);
-		int exportBuffers(unsigned int count,
-				  std::vector<std::unique_ptr<FrameBuffer>> *buffers);
-
-		int start();
-		void stop();
-
-		int queueBuffers(FrameBuffer *input, FrameBuffer *output);
-
-	protected:
-		std::string logPrefix() const override;
-
-	private:
-		void captureBufferReady(FrameBuffer *buffer);
-		void outputBufferReady(FrameBuffer *buffer);
-
-		SimpleConverter *converter_;
-		unsigned int index_;
-		std::unique_ptr<V4L2M2MDevice> m2m_;
-
-		unsigned int inputBufferCount_;
-		unsigned int outputBufferCount_;
-	};
-
-	std::string deviceNode_;
-	std::unique_ptr<V4L2M2MDevice> m2m_;
-
-	std::vector<Stream> streams_;
-	std::map<FrameBuffer *, unsigned int> queue_;
-};
-
-} /* namespace libcamera */
diff --git a/src/libcamera/pipeline/simple/meson.build b/src/libcamera/pipeline/simple/meson.build
index 9c99b32f..42b0896d 100644
--- a/src/libcamera/pipeline/simple/meson.build
+++ b/src/libcamera/pipeline/simple/meson.build
@@ -1,6 +1,5 @@
 # SPDX-License-Identifier: CC0-1.0
 
 libcamera_sources += files([
-    'converter.cpp',
     'simple.cpp',
 ])
diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
index a32de7f3..24ded4db 100644
--- a/src/libcamera/pipeline/simple/simple.cpp
+++ b/src/libcamera/pipeline/simple/simple.cpp
@@ -30,13 +30,13 @@
 
 #include "libcamera/internal/camera.h"
 #include "libcamera/internal/camera_sensor.h"
+#include "libcamera/internal/converter.h"
 #include "libcamera/internal/device_enumerator.h"
 #include "libcamera/internal/media_device.h"
 #include "libcamera/internal/pipeline_handler.h"
 #include "libcamera/internal/v4l2_subdevice.h"
 #include "libcamera/internal/v4l2_videodevice.h"
 
-#include "converter.h"
 
 namespace libcamera {
 
@@ -266,7 +266,7 @@ public:
 	std::vector<Configuration> configs_;
 	std::map<PixelFormat, std::vector<const Configuration *>> formats_;
 
-	std::unique_ptr<SimpleConverter> converter_;
+	std::unique_ptr<Converter> converter_;
 	std::vector<std::unique_ptr<FrameBuffer>> converterBuffers_;
 	bool useConverter_;
 	std::queue<std::map<unsigned int, FrameBuffer *>> converterQueue_;
@@ -492,7 +492,7 @@ int SimpleCameraData::init()
 	/* Open the converter, if any. */
 	MediaDevice *converter = pipe->converter();
 	if (converter) {
-		converter_ = std::make_unique<SimpleConverter>(converter);
+		converter_ = ConverterFactoryBase::create(converter);
 		if (!converter_->isValid()) {
 			LOG(SimplePipeline, Warning)
 				<< "Failed to create converter, disabling format conversion";
-- 
cgit v1.2.1