diff --git a/include/libcamera/internal/software_isp/software_isp.h b/include/libcamera/internal/software_isp/software_isp.h
index 3e879c630..451596163 100644
--- a/include/libcamera/internal/software_isp/software_isp.h
+++ b/include/libcamera/internal/software_isp/software_isp.h
@@ -33,6 +33,7 @@
 #include "libcamera/internal/pipeline_handler.h"
 #include "libcamera/internal/shared_mem_object.h"
 #include "libcamera/internal/software_isp/debayer_params.h"
+#include "libcamera/internal/software_isp/swstats_cpu.h"
 
 namespace libcamera {
 
@@ -91,6 +92,10 @@ public:
 private:
 	void paramsBufferReady(const uint32_t paramsBufferId);
 	bool allocateParamsBuffers(const unsigned int bufferCount);
+	std::unique_ptr<SwStatsCpu> allocateStatsBuffers(
+		const CameraManager &cm,
+		std::map<uint32_t, SharedFD> &fdStats,
+		const unsigned int bufferCount);
 	void setSensorCtrls(const ControlList &sensorControls);
 	void statsReady(uint32_t frame, const uint32_t statsBufferId);
 	void statsProcessed(const uint32_t statsBufferId);
diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h
index fd3f97cbb..cf2f88fcb 100644
--- a/include/libcamera/internal/software_isp/swstats_cpu.h
+++ b/include/libcamera/internal/software_isp/swstats_cpu.h
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2023, Linaro Ltd
- * Copyright (C) 2023, Red Hat Inc.
+ * Copyright (C) 2023-2026 Red Hat Inc.
  *
  * Authors:
  * Hans de Goede <hdegoede@redhat.com>
@@ -11,6 +11,7 @@
 
 #pragma once
 
+#include <map>
 #include <stdint.h>
 #include <vector>
 
@@ -35,7 +36,7 @@ struct StreamConfiguration;
 class SwStatsCpu
 {
 public:
-	SwStatsCpu(const CameraManager &cm);
+	SwStatsCpu(const CameraManager &cm, std::unique_ptr<std::map<uint32_t, SharedMemObject<SwIspStats>>> sharedStats);
 	~SwStatsCpu() = default;
 
 	/*
@@ -46,9 +47,9 @@ public:
 	 */
 	static constexpr uint32_t kStatPerNumFrames = 4;
 
-	bool isValid() const { return sharedStats_.fd().isValid(); }
+	bool isValid() const { return sharedStats_->begin()->second.fd().isValid(); }
 
-	const SharedFD &getStatsFD() { return sharedStats_.fd(); }
+	const SharedFD &getStatsFD() { return sharedStats_->begin()->second.fd(); }
 
 	const Size &patternSize() { return patternSize_; }
 
@@ -119,7 +120,7 @@ private:
 	unsigned int sumShift_;
 
 	std::vector<SwIspStats> stats_;
-	SharedMemObject<SwIspStats> sharedStats_;
+	std::unique_ptr<std::map<uint32_t, SharedMemObject<SwIspStats>>> sharedStats_;
 	Benchmark bench_;
 };
 
diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
index c44e035b8..c4da647e8 100644
--- a/src/libcamera/software_isp/software_isp.cpp
+++ b/src/libcamera/software_isp/software_isp.cpp
@@ -14,6 +14,8 @@
 #include <sys/mman.h>
 #include <sys/types.h>
 #include <unistd.h>
+#include <utility>
+#include <vector>
 
 #include <libcamera/base/log.h>
 #include <libcamera/base/shared_fd.h>
@@ -26,7 +28,10 @@
 
 #include "libcamera/internal/bayer_format.h"
 #include "libcamera/internal/framebuffer.h"
+#include "libcamera/internal/ipa_manager.h"
+#include "libcamera/internal/shared_mem_object.h"
 #include "libcamera/internal/software_isp/debayer_params.h"
+#include "libcamera/internal/software_isp/swisp_stats.h"
 
 #include "debayer_cpu.h"
 #if HAVE_DEBAYER_EGL
@@ -99,17 +104,15 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe,
 
 	const CameraManager &cm = *pipe->cameraManager();
 
-	auto stats = std::make_unique<SwStatsCpu>(cm);
-	if (!stats->isValid()) {
-		LOG(SoftwareIsp, Error) << "Failed to create SwStatsCpu object";
-		return;
-	}
-	stats->statsReady.connect(this, &SoftwareIsp::statsReady);
-
 	std::map<uint32_t, SharedFD> fdParams;
 	for (auto &[bufferId, item] : sharedParams_)
 		fdParams[bufferId] = item.fd();
 
+	std::map<uint32_t, SharedFD> fdStats;
+	auto stats = allocateStatsBuffers(cm, fdStats, bufferCount);
+	if (!stats)
+		return;
+
 #if HAVE_DEBAYER_EGL
 	const GlobalConfiguration &configuration = cm._d()->configuration();
 	std::optional<std::string> softISPMode = configuration.option<std::string>({ "software_isp", "mode" });
@@ -203,6 +206,33 @@ bool SoftwareIsp::allocateParamsBuffers(const unsigned int bufferCount)
 	return true;
 }
 
+std::unique_ptr<SwStatsCpu> SoftwareIsp::allocateStatsBuffers(
+	const CameraManager &cm,
+	std::map<uint32_t, SharedFD> &fdStats,
+	const unsigned int bufferCount)
+{
+	auto sharedStats = std::make_unique<std::map<uint32_t, SharedMemObject<SwIspStats>>>();
+	for (unsigned int bufferId = 0; bufferId < bufferCount; bufferId++) {
+		auto shared = SharedMemObject<SwIspStats>("softIsp_stats");
+		if (!shared) {
+			LOG(SoftwareIsp, Error) << "Failed to create shared memory for statistics";
+			return nullptr;
+		}
+		if (!shared.fd().isValid()) {
+			LOG(SoftwareIsp, Error) << "Invalid fd of shared statistics";
+			return nullptr;
+		}
+
+		fdStats[bufferId] = shared.fd();
+		sharedStats->emplace(bufferId, std::move(shared));
+	}
+
+	auto stats = std::make_unique<SwStatsCpu>(cm, std::move(sharedStats));
+	stats->statsReady.connect(this, &SoftwareIsp::statsReady);
+
+	return stats;
+}
+
 /**
  * \fn int SoftwareIsp::loadConfiguration([[maybe_unused]] const std::string &filename)
  * \brief Load a configuration from a file
diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp
index 0e645f7e1..e6beb9692 100644
--- a/src/libcamera/software_isp/swstats_cpu.cpp
+++ b/src/libcamera/software_isp/swstats_cpu.cpp
@@ -11,6 +11,8 @@
 
 #include "libcamera/internal/software_isp/swstats_cpu.h"
 
+#include <memory>
+
 #include <libcamera/base/log.h>
 
 #include <libcamera/stream.h>
@@ -36,9 +38,11 @@ namespace libcamera {
  */
 
 /**
- * \fn SwStatsCpu::SwStatsCpu(const CameraManager &cm)
+ * \fn SwStatsCpu::SwStatsCpu(const CameraManager &cm, std::unique_ptr<std::map<uint32_t, SharedMemObject<SwIspStats>>> sharedStats)
  * \brief Construct a SwStatsCpu object
  * \param[in] cm The camera manager
+ * \param[in] sharedStats Mapping of statistics buffer ids to statistics
+ *   instances that are shared with the IPA
  *
  * Creates a SwStatsCpu object and initialises shared memory for statistics
  * exchange.
@@ -159,12 +163,9 @@ namespace libcamera {
 
 LOG_DEFINE_CATEGORY(SwStatsCpu)
 
-SwStatsCpu::SwStatsCpu(const CameraManager &cm)
-	: sharedStats_("softIsp_stats"), bench_(cm, "CPU stats")
+SwStatsCpu::SwStatsCpu(const CameraManager &cm, std::unique_ptr<std::map<uint32_t, SharedMemObject<SwIspStats>>> sharedStats)
+	: sharedStats_(std::move(sharedStats)), bench_(cm, "CPU stats")
 {
-	if (!sharedStats_)
-		LOG(SwStatsCpu, Error)
-			<< "Failed to create shared memory for statistics";
 }
 
 static constexpr unsigned int kRedYMul = 77; /* 0.299 * 256 */
@@ -354,20 +355,21 @@ void SwStatsCpu::finishFrame(uint32_t frame,
 			     const uint32_t statsBufferId)
 {
 	bool valid = frame % kStatPerNumFrames == 0;
+	SharedMemObject<SwIspStats> &stats = sharedStats_->at(statsBufferId);
 
 	if (valid) {
-		sharedStats_->sum_ = RGB<uint64_t>({ 0, 0, 0 });
-		sharedStats_->yHistogram.fill(0);
+		stats->sum_ = RGB<uint64_t>({ 0, 0, 0 });
+		stats->yHistogram.fill(0);
 		for (const auto &s : stats_) {
-			sharedStats_->sum_ += s.sum_;
+			stats->sum_ += s.sum_;
 			for (unsigned int j = 0; j < SwIspStats::kYHistogramSize; j++)
-				sharedStats_->yHistogram[j] += s.yHistogram[j];
+				stats->yHistogram[j] += s.yHistogram[j];
 		}
 
-		sharedStats_->sum_ >>= sumShift_;
+		stats->sum_ >>= sumShift_;
 	}
 
-	sharedStats_->valid = valid;
+	stats->valid = valid;
 	statsReady.emit(frame, statsBufferId);
 }
 
