diff --git a/include/libcamera/internal/camera_manager.h b/include/libcamera/internal/camera_manager.h
index cfedee433857..89c8e4eda766 100644
--- a/include/libcamera/internal/camera_manager.h
+++ b/include/libcamera/internal/camera_manager.h
@@ -26,6 +26,8 @@ namespace libcamera {
 class Camera;
 class DeviceEnumerator;
 class IPAManager;
+class MediaDevice;
+class PipelineHandler;
 class PipelineHandlerFactoryBase;
 
 class CameraManager::Private : public Extensible::Private, public Thread
@@ -53,6 +55,7 @@ private:
 	int init();
 	void createPipelineHandlers();
 	void pipelineFactoryMatch(const PipelineHandlerFactoryBase *factory);
+	std::shared_ptr<PipelineHandler> findMatchingHandler(const MediaDevice *media);
 	void cleanup() LIBCAMERA_TSA_EXCLUDES(mutex_);
 
 	/*
@@ -70,6 +73,12 @@ private:
 
 	std::unique_ptr<DeviceEnumerator> enumerator_;
 
+	/*
+	 * Active pipeline handler instances, accessed from the CameraManager
+	 * thread only.
+	 */
+	std::vector<std::weak_ptr<PipelineHandler>> pipes_;
+
 	std::unique_ptr<IPAManager> ipaManager_;
 
 	const GlobalConfiguration configuration_;
diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp
index 784ae012e280..bd9cc155b961 100644
--- a/src/libcamera/camera_manager.cpp
+++ b/src/libcamera/camera_manager.cpp
@@ -178,7 +178,37 @@ void CameraManager::Private::pipelineFactoryMatch(const PipelineHandlerFactoryBa
 		LOG(Camera, Debug)
 			<< "Pipeline handler \"" << factory->name()
 			<< "\" matched";
+
+		pipes_.push_back(pipe);
+	}
+}
+
+/*
+ * Find the active pipeline handler instance that has acquired the media device,
+ * if any. Expired entries are pruned from the registry as a side effect. Called
+ * from the CameraManager thread only.
+ */
+std::shared_ptr<PipelineHandler>
+CameraManager::Private::findMatchingHandler(const MediaDevice *media)
+{
+	ASSERT(Thread::current() == this);
+
+	std::shared_ptr<PipelineHandler> match;
+
+	for (auto it = pipes_.begin(); it != pipes_.end();) {
+		std::shared_ptr<PipelineHandler> pipe = it->lock();
+		if (!pipe) {
+			it = pipes_.erase(it);
+			continue;
+		}
+
+		if (!match && pipe->usesMediaDevice(media))
+			match = std::move(pipe);
+
+		++it;
 	}
+
+	return match;
 }
 
 void CameraManager::Private::cleanup()
