@@ -528,6 +528,9 @@  namespace libcamera {
  * \a data.operation field, as defined by the IPA protocol, and the rest of the
  * \a data is interpreted accordingly. The pipeline handler shall queue the
  * action and execute it as appropriate.
+ *
+ * The signal is only emitted when the IPA is running, that is after start() and
+ * before stop() have been called.
  */
 
 } /* namespace libcamera */
@@ -12,7 +12,6 @@ 
 #include <string.h>
 #include <sys/types.h>
 
-#include "ipa_context_wrapper.h"
 #include "ipa_module.h"
 #include "ipa_proxy.h"
 #include "log.h"
@@ -271,40 +270,35 @@  std::unique_ptr<IPAInterface> IPAManager::createIPA(PipelineHandler *pipe,
 	if (!m)
 		return nullptr;
 
-	if (!m->isOpenSource()) {
-		IPAProxyFactory *pf = nullptr;
-		std::vector<IPAProxyFactory *> &factories = IPAProxyFactory::factories();
+	/*
+	 * Load and run the IPA module in a thread if it is open-source, or
+	 * isolate it in a separate process otherwise.
+	 *
+	 * \todo Implement a better proxy selection
+	 */
+	const char *proxyName = m->isOpenSource()
+			      ? "IPAProxyThread" : "IPAProxyLinux";
+	IPAProxyFactory *pf = nullptr;
 
-		for (IPAProxyFactory *factory : factories) {
-			/* TODO: Better matching */
-			if (!strcmp(factory->name().c_str(), "IPAProxyLinux")) {
-				pf = factory;
-				break;
-			}
+	for (IPAProxyFactory *factory : IPAProxyFactory::factories()) {
+		if (!strcmp(factory->name().c_str(), proxyName)) {
+			pf = factory;
+			break;
 		}
-
-		if (!pf) {
-			LOG(IPAManager, Error) << "Failed to get proxy factory";
-			return nullptr;
-		}
-
-		std::unique_ptr<IPAProxy> proxy = pf->create(m);
-		if (!proxy->isValid()) {
-			LOG(IPAManager, Error) << "Failed to load proxy";
-			return nullptr;
-		}
-
-		return proxy;
 	}
 
-	if (!m->load())
+	if (!pf) {
+		LOG(IPAManager, Error) << "Failed to get proxy factory";
 		return nullptr;
+	}
 
-	struct ipa_context *ctx = m->createContext();
-	if (!ctx)
+	std::unique_ptr<IPAProxy> proxy = pf->create(m);
+	if (!proxy->isValid()) {
+		LOG(IPAManager, Error) << "Failed to load proxy";
 		return nullptr;
+	}
 
-	return std::make_unique<IPAContextWrapper>(ctx);
+	return proxy;
 }
 
 } /* namespace libcamera */
new file mode 100644
@@ -0,0 +1,162 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * ipa_proxy_thread.cpp - Proxy running an Image Processing Algorithm in a thread
+ */
+
+#include <memory>
+
+#include <ipa/ipa_interface.h>
+#include <ipa/ipa_module_info.h>
+
+#include "ipa_context_wrapper.h"
+#include "ipa_module.h"
+#include "ipa_proxy.h"
+#include "log.h"
+#include "thread.h"
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(IPAProxy)
+
+class IPAProxyThread : public IPAProxy, public Object
+{
+public:
+	IPAProxyThread(IPAModule *ipam);
+
+	int init() override;
+	int start() override;
+	void stop() override;
+
+	void configure(const std::map<unsigned int, IPAStream> &streamConfig,
+		       const std::map<unsigned int, const ControlInfoMap &> &entityControls) override;
+	void mapBuffers(const std::vector<IPABuffer> &buffers) override;
+	void unmapBuffers(const std::vector<unsigned int> &ids) override;
+	void processEvent(const IPAOperationData &event) override;
+
+private:
+	void queueFrameAction(unsigned int frame, const IPAOperationData &data);
+
+	/* Helper class to invoke processEvent() in another thread. */
+	class ThreadProxy : public Object
+	{
+	public:
+		void setIPA(IPAInterface *ipa)
+		{
+			ipa_ = ipa;
+		}
+
+		int start()
+		{
+			return ipa_->start();
+		}
+
+		void stop()
+		{
+			ipa_->stop();
+		}
+
+		void processEvent(const IPAOperationData &event)
+		{
+			ipa_->processEvent(event);
+		}
+
+	private:
+		IPAInterface *ipa_;
+	};
+
+	bool running_;
+	Thread thread_;
+	ThreadProxy proxy_;
+	std::unique_ptr<IPAInterface> ipa_;
+};
+
+IPAProxyThread::IPAProxyThread(IPAModule *ipam)
+	: running_(false)
+{
+	if (!ipam->load())
+		return;
+
+	struct ipa_context *ctx = ipam->createContext();
+	if (!ctx) {
+		LOG(IPAProxy, Error)
+			<< "Failed to create IPA context for " << ipam->path();
+		return;
+	}
+
+	ipa_ = std::make_unique<IPAContextWrapper>(ctx);
+	proxy_.setIPA(ipa_.get());
+
+	/*
+	 * Proxy the queueFrameAction signal to dispatch it in the caller's
+	 * thread.
+	 */
+	ipa_->queueFrameAction.connect(this, &IPAProxyThread::queueFrameAction);
+
+	valid_ = true;
+}
+
+int IPAProxyThread::init()
+{
+	int ret = ipa_->init();
+	if (ret)
+		return ret;
+
+	proxy_.moveToThread(&thread_);
+
+	return 0;
+}
+
+int IPAProxyThread::start()
+{
+	running_ = true;
+	thread_.start();
+
+	return proxy_.invokeMethod(&ThreadProxy::start, ConnectionTypeBlocking);
+}
+
+void IPAProxyThread::stop()
+{
+	running_ = false;
+
+	proxy_.invokeMethod(&ThreadProxy::stop, ConnectionTypeBlocking);
+
+	thread_.exit();
+	thread_.wait();
+}
+
+void IPAProxyThread::configure(const std::map<unsigned int, IPAStream> &streamConfig,
+			       const std::map<unsigned int, const ControlInfoMap &> &entityControls)
+{
+	ipa_->configure(streamConfig, entityControls);
+}
+
+void IPAProxyThread::mapBuffers(const std::vector<IPABuffer> &buffers)
+{
+	ipa_->mapBuffers(buffers);
+}
+
+void IPAProxyThread::unmapBuffers(const std::vector<unsigned int> &ids)
+{
+	ipa_->unmapBuffers(ids);
+}
+
+void IPAProxyThread::processEvent(const IPAOperationData &event)
+{
+	if (!running_)
+		return;
+
+	/* Dispatch the processEvent() call to the thread. */
+	proxy_.invokeMethod(&ThreadProxy::processEvent, ConnectionTypeQueued,
+			    event);
+}
+
+void IPAProxyThread::queueFrameAction(unsigned int frame, const IPAOperationData &data)
+{
+	IPAInterface::queueFrameAction.emit(frame, data);
+}
+
+REGISTER_IPA_PROXY(IPAProxyThread)
+
+} /* namespace libcamera */
@@ -1,3 +1,4 @@ 
 libcamera_sources += files([
     'ipa_proxy_linux.cpp',
+    'ipa_proxy_thread.cpp',
 ])