diff --git a/include/libcamera/internal/pipeline_handler.h b/include/libcamera/internal/pipeline_handler.h
index ec4f662d7399..483553b0d027 100644
--- a/include/libcamera/internal/pipeline_handler.h
+++ b/include/libcamera/internal/pipeline_handler.h
@@ -45,7 +45,7 @@ public:
 	MediaDevice *acquireMediaDevice(DeviceEnumerator *enumerator,
 					const DeviceMatch &dm);
 
-	bool acquire();
+	bool acquire(Camera *camera);
 	void release(Camera *camera);
 
 	virtual std::unique_ptr<CameraConfiguration> generateConfiguration(Camera *camera,
@@ -74,6 +74,7 @@ protected:
 	virtual int queueRequestDevice(Camera *camera, Request *request) = 0;
 	virtual void stopDevice(Camera *camera) = 0;
 
+	virtual bool acquireDevice(Camera *camera);
 	virtual void releaseDevice(Camera *camera);
 
 	CameraManager *manager_;
diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp
index 2d947a442bff..4e0a6584a673 100644
--- a/src/libcamera/camera.cpp
+++ b/src/libcamera/camera.cpp
@@ -835,7 +835,7 @@ int Camera::acquire()
 	if (ret < 0)
 		return ret == -EACCES ? -EBUSY : ret;
 
-	if (!d->pipe_->acquire()) {
+	if (!d->pipe_->acquire(this)) {
 		LOG(Camera, Info)
 			<< "Pipeline handler in use by another process";
 		return -EBUSY;
diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp
index cfade4908118..4303a5893a4d 100644
--- a/src/libcamera/pipeline_handler.cpp
+++ b/src/libcamera/pipeline_handler.cpp
@@ -143,6 +143,7 @@ MediaDevice *PipelineHandler::acquireMediaDevice(DeviceEnumerator *enumerator,
 
 /**
  * \brief Acquire exclusive access to the pipeline handler for the process
+ * \param[in] camera The camera which is being acquired
  *
  * This function locks all the media devices used by the pipeline to ensure
  * that no other process can access them concurrently.
@@ -155,13 +156,16 @@ MediaDevice *PipelineHandler::acquireMediaDevice(DeviceEnumerator *enumerator,
  * Pipeline handlers shall not call this function directly as the Camera class
  * handles access internally.
  *
+ * Any resources acquired during an overridden acquireDevice() must be released
+ * by a corresponding override of releaseDevice().
+ *
  * \context This function is \threadsafe.
  *
  * \return True if the pipeline handler was acquired, false if another process
  * has already acquired it
  * \sa release()
  */
-bool PipelineHandler::acquire()
+bool PipelineHandler::acquire(Camera *camera)
 {
 	MutexLocker locker(lock_);
 
@@ -177,10 +181,27 @@ bool PipelineHandler::acquire()
 		}
 	}
 
+	if (!acquireDevice(camera)) {
+		unlockMediaDevices();
+		return false;
+	}
+
 	++useCount_;
 	return true;
 }
 
+/**
+ * \brief Acquire resources associated with this camera
+ * \param[in] camera The camera for which to acquire resources
+ *
+ * Pipeline handlers may override this in order to perform resource acquisition
+ * operations when a camera is acquired, such as opening device nodes.
+ */
+bool PipelineHandler::acquireDevice([[maybe_unused]] Camera *camera)
+{
+	return true;
+}
+
 /**
  * \brief Release exclusive access to the pipeline handler
  * \param[in] camera The camera for which to release data
