diff --git a/include/libcamera/internal/software_isp/software_isp.h b/include/libcamera/internal/software_isp/software_isp.h
index 86cb8f8de..85c9059e2 100644
--- a/include/libcamera/internal/software_isp/software_isp.h
+++ b/include/libcamera/internal/software_isp/software_isp.h
@@ -88,7 +88,8 @@ public:
 	Signal<const ControlList &> setSensorControls;
 
 private:
-	void saveIspParams();
+	void saveIspParams(const uint32_t paramsBufferId);
+	void paramsBufferReady(const uint32_t paramsBufferId);
 	void setSensorCtrls(const ControlList &sensorControls);
 	void statsReady(uint32_t frame, uint32_t bufferId);
 	void inputReady(FrameBuffer *input);
diff --git a/include/libcamera/ipa/soft.mojom b/include/libcamera/ipa/soft.mojom
index e75b03a3d..18789d5de 100644
--- a/include/libcamera/ipa/soft.mojom
+++ b/include/libcamera/ipa/soft.mojom
@@ -25,7 +25,7 @@ interface IPASoftInterface {
 		=> (int32 ret);
 
 	[async] queueRequest(uint32 frame, libcamera.ControlList sensorControls);
-	[async] computeParams(uint32 frame);
+	[async] computeParams(uint32 frame, uint32 paramsBufferId);
 	[async] processStats(uint32 frame,
 			     uint32 bufferId,
 			     libcamera.ControlList sensorControls);
@@ -33,6 +33,6 @@ interface IPASoftInterface {
 
 interface IPASoftEventInterface {
 	setSensorControls(libcamera.ControlList sensorControls);
-	paramsComputed();
+	paramsComputed(uint32 paramsBufferId);
 	metadataReady(uint32 frame, libcamera.ControlList metadata);
 };
diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp
index cfc1389e4..69bfef302 100644
--- a/src/ipa/simple/soft_simple.cpp
+++ b/src/ipa/simple/soft_simple.cpp
@@ -64,7 +64,8 @@ public:
 	void stop() override;
 
 	void queueRequest(const uint32_t frame, const ControlList &controls) override;
-	void computeParams(const uint32_t frame) override;
+	void computeParams(const uint32_t frame,
+			   const uint32_t paramsBufferId) override;
 	void processStats(const uint32_t frame, const uint32_t bufferId,
 			  const ControlList &sensorControls) override;
 
@@ -283,7 +284,8 @@ void IPASoftSimple::queueRequest(const uint32_t frame, const ControlList &contro
 		algo->queueRequest(context_, frame, frameContext, controls);
 }
 
-void IPASoftSimple::computeParams(const uint32_t frame)
+void IPASoftSimple::computeParams(const uint32_t frame,
+				  const uint32_t paramsBufferId)
 {
 	context_.activeState.combinedMatrix = Matrix<float, 3, 3>::identity();
 
@@ -292,7 +294,7 @@ void IPASoftSimple::computeParams(const uint32_t frame)
 		algo->prepare(context_, frame, frameContext, params_);
 	params_->combinedMatrix = context_.activeState.combinedMatrix;
 
-	paramsComputed.emit();
+	paramsComputed.emit(paramsBufferId);
 }
 
 void IPASoftSimple::processStats(const uint32_t frame,
diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp
index 2d7abfb83..7b1be52b2 100644
--- a/src/libcamera/software_isp/debayer.cpp
+++ b/src/libcamera/software_isp/debayer.cpp
@@ -104,9 +104,10 @@ Debayer::~Debayer()
  */
 
 /**
- * \fn void Debayer::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, DebayerParams params)
+ * \fn void Debayer::process(uint32_t frame, const uint32_t paramsBufferId, FrameBuffer *input, FrameBuffer *output, DebayerParams params)
  * \brief Process the bayer data into the requested format
  * \param[in] frame The frame number
+ * \param[in] paramsBufferId The id of the params buffer in use
  * \param[in] input The input buffer
  * \param[in] output The output buffer
  * \param[in] params The parameters to be used in debayering
@@ -142,6 +143,11 @@ Debayer::~Debayer()
  * current stream.
  */
 
+/**
+ * \var Debayer::paramsBufferReady
+ * \brief Signals when the processing params are no longer needed
+ */
+
 /**
  * \var Signal<FrameBuffer *> Debayer::inputBufferReady
  * \brief Signals when the input buffer is ready
diff --git a/src/libcamera/software_isp/debayer.h b/src/libcamera/software_isp/debayer.h
index a2a17ec18..9a97851b7 100644
--- a/src/libcamera/software_isp/debayer.h
+++ b/src/libcamera/software_isp/debayer.h
@@ -47,7 +47,10 @@ public:
 	virtual std::tuple<unsigned int, unsigned int>
 	strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) = 0;
 
-	virtual void process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, const DebayerParams &params) = 0;
+	virtual void process(uint32_t frame,
+			     const uint32_t paramsBufferId,
+			     FrameBuffer *input, FrameBuffer *output,
+			     const DebayerParams &params) = 0;
 	virtual int start() { return 0; }
 	virtual void stop() {}
 
@@ -59,6 +62,7 @@ public:
 
 	Signal<FrameBuffer *> inputBufferReady;
 	Signal<FrameBuffer *> outputBufferReady;
+	Signal<uint32_t> paramsBufferReady;
 
 	struct DebayerInputConfig {
 		Size patternSize;
diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp
index 1f9b24da0..51b58fce5 100644
--- a/src/libcamera/software_isp/debayer_cpu.cpp
+++ b/src/libcamera/software_isp/debayer_cpu.cpp
@@ -971,7 +971,9 @@ void DebayerCpu::updateLookupTables(const DebayerParams &params)
 	params_ = params;
 }
 
-void DebayerCpu::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, const DebayerParams &params)
+void DebayerCpu::process(uint32_t frame, const uint32_t paramsBufferId,
+			 FrameBuffer *input, FrameBuffer *output,
+			 const DebayerParams &params)
 {
 	bench_.startFrame();
 
@@ -981,6 +983,8 @@ void DebayerCpu::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output
 
 	updateLookupTables(params);
 
+	paramsBufferReady.emit(paramsBufferId);
+
 	/* Copy metadata from the input buffer */
 	FrameMetadata &metadata = output->_d()->metadata();
 	metadata.status = input->metadata().status;
diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h
index 68da95083..1b4e8548a 100644
--- a/src/libcamera/software_isp/debayer_cpu.h
+++ b/src/libcamera/software_isp/debayer_cpu.h
@@ -42,7 +42,9 @@ public:
 	std::vector<PixelFormat> formats(PixelFormat input) override;
 	std::tuple<unsigned int, unsigned int>
 	strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) override;
-	void process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, const DebayerParams &params) override;
+	void process(uint32_t frame, const uint32_t paramsBufferId,
+		     FrameBuffer *input, FrameBuffer *output,
+		     const DebayerParams &params) override;
 	int start() override;
 	void stop() override;
 	SizeRange sizes(PixelFormat inputFormat, const Size &inputSize) override;
diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp
index 99825d49e..60fd5463f 100644
--- a/src/libcamera/software_isp/debayer_egl.cpp
+++ b/src/libcamera/software_isp/debayer_egl.cpp
@@ -546,7 +546,9 @@ int DebayerEGL::debayerGPU(FrameBuffer *input, FrameBuffer *output, const Debaye
 	return 0;
 }
 
-void DebayerEGL::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, const DebayerParams &params)
+void DebayerEGL::process(uint32_t frame, const uint32_t paramsBufferId,
+			 FrameBuffer *input, FrameBuffer *output,
+			 const DebayerParams &params)
 {
 	bench_.startFrame();
 
@@ -563,6 +565,7 @@ void DebayerEGL::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output
 		LOG(Debayer, Error) << "debayerGPU failed";
 		goto error;
 	}
+	paramsBufferReady.emit(paramsBufferId);
 
 	metadata.planes()[0].bytesused = output->planes()[0].length;
 
@@ -593,6 +596,7 @@ void DebayerEGL::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output
 	return;
 
 error:
+	paramsBufferReady.emit(paramsBufferId);
 	bench_.finishFrame();
 	metadata.status = FrameMetadata::FrameError;
 	return;
diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h
index 875e7cfc5..a3a4448e7 100644
--- a/src/libcamera/software_isp/debayer_egl.h
+++ b/src/libcamera/software_isp/debayer_egl.h
@@ -51,7 +51,9 @@ public:
 	std::vector<PixelFormat> formats(PixelFormat input) override;
 	std::tuple<unsigned int, unsigned int> strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) override;
 
-	void process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, const DebayerParams &params) override;
+	void process(uint32_t frame, const uint32_t paramsBufferId,
+		     FrameBuffer *input, FrameBuffer *output,
+		     const DebayerParams &params) override;
 	int start() override;
 	void stop() override;
 
diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
index f17f1ca1f..a1200e924 100644
--- a/src/libcamera/software_isp/software_isp.cpp
+++ b/src/libcamera/software_isp/software_isp.cpp
@@ -128,6 +128,7 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,
 
 	debayer_->inputBufferReady.connect(this, &SoftwareIsp::inputReady);
 	debayer_->outputBufferReady.connect(this, &SoftwareIsp::outputReady);
+	debayer_->paramsBufferReady.connect(this, &SoftwareIsp::paramsBufferReady);
 
 	ipa_ = pipe->createIPA<ipa::soft::IPAProxySoft>(0, 0);
 	if (!ipa_) {
@@ -408,16 +409,23 @@ void SoftwareIsp::stop()
  */
 void SoftwareIsp::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output)
 {
-	ipa_->computeParams(frame);
+	/* \todo Provide a real value */
+	constexpr uint32_t paramsBufferId = 0;
+	ipa_->computeParams(frame, paramsBufferId);
 	debayer_->invokeMethod(&Debayer::process,
-			       ConnectionTypeQueued, frame, input, output, debayerParams_);
+			       ConnectionTypeQueued, frame, paramsBufferId,
+			       input, output, debayerParams_);
 }
 
-void SoftwareIsp::saveIspParams()
+void SoftwareIsp::saveIspParams([[maybe_unused]] const uint32_t paramsBufferId)
 {
 	debayerParams_ = *sharedParams_;
 }
 
+void SoftwareIsp::paramsBufferReady([[maybe_unused]] const uint32_t paramsBufferId)
+{
+}
+
 void SoftwareIsp::setSensorCtrls(const ControlList &sensorControls)
 {
 	setSensorControls.emit(sensorControls);
