diff --git a/include/libcamera/internal/converter.h b/include/libcamera/internal/converter.h
new file mode 100644
index 00000000..e2237c57
--- /dev/null
+++ b/include/libcamera/internal/converter.h
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Laurent Pinchart
+ * Copyright 2022 NXP
+ *
+ * converter.h - Generic stream converter infrastructure
+ */
+
+#pragma once
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include <libcamera/base/class.h>
+#include <libcamera/base/signal.h>
+
+#include <libcamera/geometry.h>
+#include <libcamera/pixel_format.h>
+
+namespace libcamera {
+
+class FrameBuffer;
+class MediaDevice;
+class Size;
+class SizeRange;
+struct StreamConfiguration;
+
+class Converter
+{
+public:
+	Converter(MediaDevice *media);
+	virtual ~Converter();
+	virtual int loadConfiguration(const std::string &filename) = 0;
+
+	virtual bool isValid() const = 0;
+
+	virtual std::vector<PixelFormat> formats(PixelFormat input) = 0;
+	virtual SizeRange sizes(const Size &input) = 0;
+
+	virtual std::tuple<unsigned int, unsigned int>
+	strideAndFrameSize(const PixelFormat &pixelFormat, const Size &size) = 0;
+
+	virtual int configure(const StreamConfiguration &inputCfg,
+			      const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfg) = 0;
+	virtual int exportBuffers(unsigned int ouput, unsigned int count,
+				  std::vector<std::unique_ptr<FrameBuffer>> *buffers) = 0;
+
+	virtual int start() = 0;
+	virtual void stop() = 0;
+
+	virtual int queueBuffers(FrameBuffer *input,
+				 const std::map<unsigned int, FrameBuffer *> &outputs) = 0;
+
+	std::string deviceNode_;
+	Signal<FrameBuffer *> inputBufferReady;
+	Signal<FrameBuffer *> outputBufferReady;
+};
+
+class ConverterFactory
+{
+public:
+	ConverterFactory(const std::string name);
+	virtual ~ConverterFactory() = default;
+
+	static std::unique_ptr<Converter> create(MediaDevice *media);
+
+	static void registerType(ConverterFactory *factory);
+	static std::vector<ConverterFactory *> &factories();
+	static std::vector<std::string> names();
+
+protected:
+	virtual Converter *createInstance(MediaDevice *media) = 0;
+	virtual const std::vector<std::string> aliases() const = 0;
+
+private:
+	LIBCAMERA_DISABLE_COPY_AND_MOVE(ConverterFactory)
+
+	std::string name_;
+};
+
+#define REGISTER_CONVERTER(name, converter, ...)                            \
+	class converter##Factory final : public ConverterFactory                \
+	{                                                                       \
+	public:                                                                 \
+		converter##Factory() : ConverterFactory(name) {}                    \
+                                                                            \
+	private:                                                                \
+		Converter *createInstance(MediaDevice *media)                       \
+		{                                                                   \
+			return new converter(media);                                    \
+		}                                                                   \
+		std::vector<std::string> aliases_ = { __VA_ARGS__ };                \
+		const std::vector<std::string> aliases() const { return aliases_; } \
+	};                                                                      \
+	static converter##Factory global_##converter##Factory;
+
+} /* namespace libcamera */
diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
index 7a780d48..8f50d755 100644
--- a/include/libcamera/internal/meson.build
+++ b/include/libcamera/internal/meson.build
@@ -19,6 +19,7 @@ libcamera_internal_headers = files([
     'camera_sensor_properties.h',
     'control_serializer.h',
     'control_validator.h',
+    'converter.h',
     'delayed_controls.h',
     'device_enumerator.h',
     'device_enumerator_sysfs.h',
diff --git a/src/libcamera/converter.cpp b/src/libcamera/converter.cpp
new file mode 100644
index 00000000..89a594d1
--- /dev/null
+++ b/src/libcamera/converter.cpp
@@ -0,0 +1,102 @@
+
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright 2022 NXP
+ *
+ * converter.cpp - Generic Format converter interface
+ */
+
+#include <algorithm>
+
+#include <libcamera/base/log.h>
+
+#include "libcamera/internal/converter.h"
+#include "libcamera/internal/media_device.h"
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(Converter)
+
+Converter::Converter(MediaDevice *media)
+{
+	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();
+}
+
+Converter::~Converter()
+{
+}
+
+ConverterFactory::ConverterFactory(const std::string name)
+	: name_(name)
+{
+	registerType(this);
+}
+
+std::unique_ptr<Converter> ConverterFactory::create(MediaDevice *media)
+{
+	std::vector<ConverterFactory *> &factories =
+		ConverterFactory::factories();
+
+	for (ConverterFactory *factory : factories) {
+		std::vector<std::string> aliases = factory->aliases();
+		auto it = std::find(aliases.begin(), aliases.end(), media->driver());
+
+		if (it == aliases.end() && media->driver() != factory->name_)
+			continue;
+
+		LOG(Converter, Debug)
+			<< "Creating converter from "
+			<< factory->name_ << " factory with "
+			<< (it == aliases.end() ? "no" : media->driver()) << " alias.";
+
+		Converter *converter = factory->createInstance(media);
+		return std::unique_ptr<Converter>(converter);
+	}
+
+	return nullptr;
+}
+
+void ConverterFactory::registerType(ConverterFactory *factory)
+{
+	std::vector<ConverterFactory *> &factories =
+		ConverterFactory::factories();
+
+	factories.push_back(factory);
+}
+
+std::vector<std::string> ConverterFactory::names()
+{
+	std::vector<std::string> list;
+
+	std::vector<ConverterFactory *> &factories =
+		ConverterFactory::factories();
+
+	for (ConverterFactory *factory : factories) {
+		list.push_back(factory->name_);
+		for (auto alias : factory->aliases())
+			list.push_back(alias);
+	}
+
+	return list;
+}
+
+std::vector<ConverterFactory *> &ConverterFactory::factories()
+{
+	/*
+	 * The static factories map is defined inside the function to ensure
+	 * it gets initialized on first use, without any dependency on link
+	 * order.
+	 */
+	static std::vector<ConverterFactory *> factories;
+	return factories;
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index 63b47b17..a261d4b4 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -13,6 +13,7 @@ libcamera_sources = files([
     'controls.cpp',
     'control_serializer.cpp',
     'control_validator.cpp',
+    'converter.cpp',
     'delayed_controls.cpp',
     'device_enumerator.cpp',
     'device_enumerator_sysfs.cpp',
