[v4,06/18] libcamera: software_isp: Add SwStatsCpu class
diff mbox series

Message ID 20240229183654.7206-7-hdegoede@redhat.com
State Superseded
Headers show
Series
  • libcamera: introduce Software ISP and Software IPA
Related show

Commit Message

Hans de Goede Feb. 29, 2024, 6:36 p.m. UTC
Add a CPU based SwStats implementation for SoftwareISP / SoftIPA use.

This implementation offers a configure function + functions to gather
statistics on a line by line basis. This allows CPU based software
debayering to call into interlace debayering and statistics gathering
on a line by line bases while the input data is still hot in the cache.

This implementation also allows specifying a window over which to gather
statistics instead of processing the whole frame.

Doxygen documentation by Dennis Bonke.

Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
Tested-by: Pavel Machek <pavel@ucw.cz>
Reviewed-by: Pavel Machek <pavel@ucw.cz>
Co-developed-by: Andrey Konovalov <andrey.konovalov@linaro.org>
Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
Co-developed-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: Pavel Machek <pavel@ucw.cz>
Co-developed-by: Dennis Bonke <admin@dennisbonke.com>
Signed-off-by: Dennis Bonke <admin@dennisbonke.com>
Co-developed-by: Marttico <g.martti@gmail.com>
Signed-off-by: Marttico <g.martti@gmail.com>
Co-developed-by: Toon Langendam <t.langendam@gmail.com>
Signed-off-by: Toon Langendam <t.langendam@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v3:
- Merge SwStats base-class into SwStatsCpu
- Move swstats_cpu.h to src/libcamera/software_isp/
- Move documentation to .cpp file and extend it
- Rename a bunch of foo_bar symbols to fooBar
- Change a couple of defines and hardcoded values to constexpr
- Remove statsBayer10P in swstats_cpu.cpp, instead move the loop to
  statsBGGR10PLine0 / statsGBRG10PLine0
- Make startFrame() and finishFrame() normal methods instead of
  using function pointers for these
---
 include/libcamera/internal/meson.build        |   1 +
 .../internal/software_isp/meson.build         |   5 +
 .../internal/software_isp/swisp_stats.h       |  38 ++++
 src/libcamera/meson.build                     |   1 +
 src/libcamera/software_isp/meson.build        |   5 +
 src/libcamera/software_isp/swstats_cpu.cpp    | 208 ++++++++++++++++++
 src/libcamera/software_isp/swstats_cpu.h      | 159 +++++++++++++
 7 files changed, 417 insertions(+)
 create mode 100644 include/libcamera/internal/software_isp/meson.build
 create mode 100644 include/libcamera/internal/software_isp/swisp_stats.h
 create mode 100644 src/libcamera/software_isp/meson.build
 create mode 100644 src/libcamera/software_isp/swstats_cpu.cpp
 create mode 100644 src/libcamera/software_isp/swstats_cpu.h

Comments

Milan Zamazal March 4, 2024, 3:05 p.m. UTC | #1
Hans de Goede <hdegoede@redhat.com> writes:

> Add a CPU based SwStats implementation for SoftwareISP / SoftIPA use.
>
> This implementation offers a configure function + functions to gather
> statistics on a line by line basis. This allows CPU based software
> debayering to call into interlace debayering and statistics gathering
> on a line by line bases while the input data is still hot in the cache.
>
> This implementation also allows specifying a window over which to gather
> statistics instead of processing the whole frame.
>
> Doxygen documentation by Dennis Bonke.
>
> Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
> Tested-by: Pavel Machek <pavel@ucw.cz>
> Reviewed-by: Pavel Machek <pavel@ucw.cz>
> Co-developed-by: Andrey Konovalov <andrey.konovalov@linaro.org>
> Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
> Co-developed-by: Pavel Machek <pavel@ucw.cz>
> Signed-off-by: Pavel Machek <pavel@ucw.cz>
> Co-developed-by: Dennis Bonke <admin@dennisbonke.com>
> Signed-off-by: Dennis Bonke <admin@dennisbonke.com>
> Co-developed-by: Marttico <g.martti@gmail.com>
> Signed-off-by: Marttico <g.martti@gmail.com>
> Co-developed-by: Toon Langendam <t.langendam@gmail.com>
> Signed-off-by: Toon Langendam <t.langendam@gmail.com>
> Signed-off-by: Hans de Goede <hdegoede@redhat.com>

One more minor typo in a docstring below, after it's fixed:

Reviewed-by: Milan Zamazal <mzamazal@redhat.com>

> ---
> Changes in v3:
> - Merge SwStats base-class into SwStatsCpu
> - Move swstats_cpu.h to src/libcamera/software_isp/
> - Move documentation to .cpp file and extend it
> - Rename a bunch of foo_bar symbols to fooBar
> - Change a couple of defines and hardcoded values to constexpr
> - Remove statsBayer10P in swstats_cpu.cpp, instead move the loop to
>   statsBGGR10PLine0 / statsGBRG10PLine0
> - Make startFrame() and finishFrame() normal methods instead of
>   using function pointers for these
> ---
>  include/libcamera/internal/meson.build        |   1 +
>  .../internal/software_isp/meson.build         |   5 +
>  .../internal/software_isp/swisp_stats.h       |  38 ++++
>  src/libcamera/meson.build                     |   1 +
>  src/libcamera/software_isp/meson.build        |   5 +
>  src/libcamera/software_isp/swstats_cpu.cpp    | 208 ++++++++++++++++++
>  src/libcamera/software_isp/swstats_cpu.h      | 159 +++++++++++++
>  7 files changed, 417 insertions(+)
>  create mode 100644 include/libcamera/internal/software_isp/meson.build
>  create mode 100644 include/libcamera/internal/software_isp/swisp_stats.h
>  create mode 100644 src/libcamera/software_isp/meson.build
>  create mode 100644 src/libcamera/software_isp/swstats_cpu.cpp
>  create mode 100644 src/libcamera/software_isp/swstats_cpu.h
>

[...]

> diff --git a/src/libcamera/software_isp/swstats_cpu.h b/src/libcamera/software_isp/swstats_cpu.h
> new file mode 100644
> index 00000000..df13263b
> --- /dev/null
> +++ b/src/libcamera/software_isp/swstats_cpu.h
> @@ -0,0 +1,159 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2023, Linaro Ltd
> + * Copyright (C) 2023, Red Hat Inc.
> + *
> + * Authors:
> + * Hans de Goede <hdegoede@redhat.com>
> + *
> + * swstats_cpu.h - CPU based software statistics implementation
> + */
> +
> +#pragma once
> +
> +#include <stdint.h>
> +
> +#include <libcamera/base/signal.h>
> +
> +#include <libcamera/geometry.h>
> +
> +#include "libcamera/internal/shared_mem_object.h"
> +#include "libcamera/internal/software_isp/swisp_stats.h"
> +
> +namespace libcamera {
> +
> +class PixelFormat;
> +struct StreamConfiguration;
> +
> +class SwStatsCpu
> +{
> +public:
> +	SwStatsCpu();
> +	~SwStatsCpu() = default;
> +
> +	/**
> +	 * \brief Gets wether the statistics object is valid.

wether -> weather

[...]
Hans de Goede March 4, 2024, 3:30 p.m. UTC | #2
Hi,

On 3/4/24 4:05 PM, Milan Zamazal wrote:
> Hans de Goede <hdegoede@redhat.com> writes:
> 
>> Add a CPU based SwStats implementation for SoftwareISP / SoftIPA use.
>>
>> This implementation offers a configure function + functions to gather
>> statistics on a line by line basis. This allows CPU based software
>> debayering to call into interlace debayering and statistics gathering
>> on a line by line bases while the input data is still hot in the cache.
>>
>> This implementation also allows specifying a window over which to gather
>> statistics instead of processing the whole frame.
>>
>> Doxygen documentation by Dennis Bonke.
>>
>> Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
>> Tested-by: Pavel Machek <pavel@ucw.cz>
>> Reviewed-by: Pavel Machek <pavel@ucw.cz>
>> Co-developed-by: Andrey Konovalov <andrey.konovalov@linaro.org>
>> Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
>> Co-developed-by: Pavel Machek <pavel@ucw.cz>
>> Signed-off-by: Pavel Machek <pavel@ucw.cz>
>> Co-developed-by: Dennis Bonke <admin@dennisbonke.com>
>> Signed-off-by: Dennis Bonke <admin@dennisbonke.com>
>> Co-developed-by: Marttico <g.martti@gmail.com>
>> Signed-off-by: Marttico <g.martti@gmail.com>
>> Co-developed-by: Toon Langendam <t.langendam@gmail.com>
>> Signed-off-by: Toon Langendam <t.langendam@gmail.com>
>> Signed-off-by: Hans de Goede <hdegoede@redhat.com>
> 
> One more minor typo in a docstring below, after it's fixed:
> 
> Reviewed-by: Milan Zamazal <mzamazal@redhat.com>
> 
>> ---
>> Changes in v3:
>> - Merge SwStats base-class into SwStatsCpu
>> - Move swstats_cpu.h to src/libcamera/software_isp/
>> - Move documentation to .cpp file and extend it
>> - Rename a bunch of foo_bar symbols to fooBar
>> - Change a couple of defines and hardcoded values to constexpr
>> - Remove statsBayer10P in swstats_cpu.cpp, instead move the loop to
>>   statsBGGR10PLine0 / statsGBRG10PLine0
>> - Make startFrame() and finishFrame() normal methods instead of
>>   using function pointers for these
>> ---
>>  include/libcamera/internal/meson.build        |   1 +
>>  .../internal/software_isp/meson.build         |   5 +
>>  .../internal/software_isp/swisp_stats.h       |  38 ++++
>>  src/libcamera/meson.build                     |   1 +
>>  src/libcamera/software_isp/meson.build        |   5 +
>>  src/libcamera/software_isp/swstats_cpu.cpp    | 208 ++++++++++++++++++
>>  src/libcamera/software_isp/swstats_cpu.h      | 159 +++++++++++++
>>  7 files changed, 417 insertions(+)
>>  create mode 100644 include/libcamera/internal/software_isp/meson.build
>>  create mode 100644 include/libcamera/internal/software_isp/swisp_stats.h
>>  create mode 100644 src/libcamera/software_isp/meson.build
>>  create mode 100644 src/libcamera/software_isp/swstats_cpu.cpp
>>  create mode 100644 src/libcamera/software_isp/swstats_cpu.h
>>
> 
> [...]
> 
>> diff --git a/src/libcamera/software_isp/swstats_cpu.h b/src/libcamera/software_isp/swstats_cpu.h
>> new file mode 100644
>> index 00000000..df13263b
>> --- /dev/null
>> +++ b/src/libcamera/software_isp/swstats_cpu.h
>> @@ -0,0 +1,159 @@
>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
>> +/*
>> + * Copyright (C) 2023, Linaro Ltd
>> + * Copyright (C) 2023, Red Hat Inc.
>> + *
>> + * Authors:
>> + * Hans de Goede <hdegoede@redhat.com>
>> + *
>> + * swstats_cpu.h - CPU based software statistics implementation
>> + */
>> +
>> +#pragma once
>> +
>> +#include <stdint.h>
>> +
>> +#include <libcamera/base/signal.h>
>> +
>> +#include <libcamera/geometry.h>
>> +
>> +#include "libcamera/internal/shared_mem_object.h"
>> +#include "libcamera/internal/software_isp/swisp_stats.h"
>> +
>> +namespace libcamera {
>> +
>> +class PixelFormat;
>> +struct StreamConfiguration;
>> +
>> +class SwStatsCpu
>> +{
>> +public:
>> +	SwStatsCpu();
>> +	~SwStatsCpu() = default;
>> +
>> +	/**
>> +	 * \brief Gets wether the statistics object is valid.
> 
> wether -> weather

Actually that should be whether, weather is talking about
how sunny / cloudy it is. I'll do:

s/wether/whether/

for v5.

Thanks & Regards,

Hans






> 
> [...]
>
Milan Zamazal March 4, 2024, 5:25 p.m. UTC | #3
Hans de Goede <hdegoede@redhat.com> writes:

> Hi,
>
> On 3/4/24 4:05 PM, Milan Zamazal wrote:
>> Hans de Goede <hdegoede@redhat.com> writes:
>> 
>>> Add a CPU based SwStats implementation for SoftwareISP / SoftIPA use.
>>>
>>> This implementation offers a configure function + functions to gather
>>> statistics on a line by line basis. This allows CPU based software
>>> debayering to call into interlace debayering and statistics gathering
>>> on a line by line bases while the input data is still hot in the cache.
>>>
>>> This implementation also allows specifying a window over which to gather
>>> statistics instead of processing the whole frame.
>>>
>>> Doxygen documentation by Dennis Bonke.
>>>
>>> Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
>>> Tested-by: Pavel Machek <pavel@ucw.cz>
>>> Reviewed-by: Pavel Machek <pavel@ucw.cz>
>>> Co-developed-by: Andrey Konovalov <andrey.konovalov@linaro.org>
>>> Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
>>> Co-developed-by: Pavel Machek <pavel@ucw.cz>
>>> Signed-off-by: Pavel Machek <pavel@ucw.cz>
>>> Co-developed-by: Dennis Bonke <admin@dennisbonke.com>
>>> Signed-off-by: Dennis Bonke <admin@dennisbonke.com>
>>> Co-developed-by: Marttico <g.martti@gmail.com>
>>> Signed-off-by: Marttico <g.martti@gmail.com>
>>> Co-developed-by: Toon Langendam <t.langendam@gmail.com>
>>> Signed-off-by: Toon Langendam <t.langendam@gmail.com>
>>> Signed-off-by: Hans de Goede <hdegoede@redhat.com>
>> 
>> One more minor typo in a docstring below, after it's fixed:
>> 
>> Reviewed-by: Milan Zamazal <mzamazal@redhat.com>
>> 
>>> ---
>>> Changes in v3:
>>> - Merge SwStats base-class into SwStatsCpu
>>> - Move swstats_cpu.h to src/libcamera/software_isp/
>>> - Move documentation to .cpp file and extend it
>>> - Rename a bunch of foo_bar symbols to fooBar
>>> - Change a couple of defines and hardcoded values to constexpr
>>> - Remove statsBayer10P in swstats_cpu.cpp, instead move the loop to
>>>   statsBGGR10PLine0 / statsGBRG10PLine0
>>> - Make startFrame() and finishFrame() normal methods instead of
>>>   using function pointers for these
>>> ---
>>>  include/libcamera/internal/meson.build        |   1 +
>>>  .../internal/software_isp/meson.build         |   5 +
>>>  .../internal/software_isp/swisp_stats.h       |  38 ++++
>>>  src/libcamera/meson.build                     |   1 +
>>>  src/libcamera/software_isp/meson.build        |   5 +
>>>  src/libcamera/software_isp/swstats_cpu.cpp    | 208 ++++++++++++++++++
>>>  src/libcamera/software_isp/swstats_cpu.h      | 159 +++++++++++++
>>>  7 files changed, 417 insertions(+)
>>>  create mode 100644 include/libcamera/internal/software_isp/meson.build
>>>  create mode 100644 include/libcamera/internal/software_isp/swisp_stats.h
>>>  create mode 100644 src/libcamera/software_isp/meson.build
>>>  create mode 100644 src/libcamera/software_isp/swstats_cpu.cpp
>>>  create mode 100644 src/libcamera/software_isp/swstats_cpu.h
>>>
>> 
>> [...]
>> 
>>> diff --git a/src/libcamera/software_isp/swstats_cpu.h b/src/libcamera/software_isp/swstats_cpu.h
>>> new file mode 100644
>>> index 00000000..df13263b
>>> --- /dev/null
>>> +++ b/src/libcamera/software_isp/swstats_cpu.h
>>> @@ -0,0 +1,159 @@
>>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
>>> +/*
>>> + * Copyright (C) 2023, Linaro Ltd
>>> + * Copyright (C) 2023, Red Hat Inc.
>>> + *
>>> + * Authors:
>>> + * Hans de Goede <hdegoede@redhat.com>
>>> + *
>>> + * swstats_cpu.h - CPU based software statistics implementation
>>> + */
>>> +
>>> +#pragma once
>>> +
>>> +#include <stdint.h>
>>> +
>>> +#include <libcamera/base/signal.h>
>>> +
>>> +#include <libcamera/geometry.h>
>>> +
>>> +#include "libcamera/internal/shared_mem_object.h"
>>> +#include "libcamera/internal/software_isp/swisp_stats.h"
>>> +
>>> +namespace libcamera {
>>> +
>>> +class PixelFormat;
>>> +struct StreamConfiguration;
>>> +
>>> +class SwStatsCpu
>>> +{
>>> +public:
>>> +	SwStatsCpu();
>>> +	~SwStatsCpu() = default;
>>> +
>>> +	/**
>>> +	 * \brief Gets wether the statistics object is valid.
>> 
>> wether -> weather
>
> Actually that should be whether, weather is talking about
> how sunny / cloudy it is. I'll do:

Absolutely!  Cool, typo in a typo fix :-).

I wonder how brain works when one wants to type an English word, the right one,
knows how to spell it and still the fingers type a similarly sounding different
word unnoticed.  I've observed this phenomenon also in e-mails of native
speakers so it cannot be explained simply as that writing in a foreign language
is more demanding.

> s/wether/whether/

This one looks correct. :-)

> for v5.
>
> Thanks & Regards,
>
> Hans
>
>
>
>
>
>
>> 
>> [...]
>>

Patch
diff mbox series

diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
index 5807dfd9..160fdc37 100644
--- a/include/libcamera/internal/meson.build
+++ b/include/libcamera/internal/meson.build
@@ -50,3 +50,4 @@  libcamera_internal_headers = files([
 ])
 
 subdir('converter')
+subdir('software_isp')
diff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build
new file mode 100644
index 00000000..66c9c3fb
--- /dev/null
+++ b/include/libcamera/internal/software_isp/meson.build
@@ -0,0 +1,5 @@ 
+# SPDX-License-Identifier: CC0-1.0
+
+libcamera_internal_headers += files([
+    'swisp_stats.h',
+])
diff --git a/include/libcamera/internal/software_isp/swisp_stats.h b/include/libcamera/internal/software_isp/swisp_stats.h
new file mode 100644
index 00000000..afe42c9a
--- /dev/null
+++ b/include/libcamera/internal/software_isp/swisp_stats.h
@@ -0,0 +1,38 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Linaro Ltd
+ *
+ * swisp_stats.h - Statistics data format used by the software ISP and software IPA
+ */
+
+#pragma once
+
+namespace libcamera {
+
+/**
+ * \brief Struct that holds the statistics for the Software ISP.
+ */
+struct SwIspStats {
+	/**
+	 * \brief Holds the sum of all sampled red pixels.
+	 */
+	unsigned long sumR_;
+	/**
+	 * \brief Holds the sum of all sampled green pixels.
+	 */
+	unsigned long sumG_;
+	/**
+	 * \brief Holds the sum of all sampled blue pixels.
+	 */
+	unsigned long sumB_;
+	/**
+	 * \brief Number of bins in the yHistogram.
+	 */
+	static constexpr unsigned int kYHistogramSize = 16;
+	/**
+	 * \brief A histogram of luminance values.
+	 */
+	std::array<unsigned int, kYHistogramSize> yHistogram;
+};
+
+} /* namespace libcamera */
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index 94a95ae3..91e4cc60 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -71,6 +71,7 @@  subdir('converter')
 subdir('ipa')
 subdir('pipeline')
 subdir('proxy')
+subdir('software_isp')
 
 null_dep = dependency('', required : false)
 
diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build
new file mode 100644
index 00000000..e1fb8ccc
--- /dev/null
+++ b/src/libcamera/software_isp/meson.build
@@ -0,0 +1,5 @@ 
+# SPDX-License-Identifier: CC0-1.0
+
+libcamera_sources += files([
+    'swstats_cpu.cpp',
+])
diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp
new file mode 100644
index 00000000..448d0e4c
--- /dev/null
+++ b/src/libcamera/software_isp/swstats_cpu.cpp
@@ -0,0 +1,208 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Linaro Ltd
+ * Copyright (C) 2023, Red Hat Inc.
+ *
+ * Authors:
+ * Hans de Goede <hdegoede@redhat.com>
+ *
+ * swstats_cpu.cpp - CPU based software statistics implementation
+ */
+
+#include "swstats_cpu.h"
+
+#include <libcamera/base/log.h>
+
+#include <libcamera/stream.h>
+
+#include "libcamera/internal/bayer_format.h"
+
+namespace libcamera {
+
+/**
+ * \class SwStatsCpu
+ * \brief Class for gathering statistics on the CPU
+ *
+ * CPU based software ISP statistics implementation.
+ *
+ * This class offers a configure function + functions to gather statistics
+ * on a line by line basis. This allows CPU based software debayering to
+ * interlace debayering and statistics gathering on a line by line basis
+ * while the input data is still hot in the cache.
+ *
+ * It is also possible to specify a window over which to gather
+ * statistics instead of processing the whole frame.
+ */
+
+LOG_DEFINE_CATEGORY(SwStatsCpu)
+
+SwStatsCpu::SwStatsCpu()
+{
+	sharedStats_ = SharedMemObject<SwIspStats>("softIsp_stats");
+	if (!sharedStats_.fd().isValid())
+		LOG(SwStatsCpu, Error)
+			<< "Failed to create shared memory for statistics";
+}
+
+static const unsigned int kRedYMul = 77; /* 0.299 * 256 */
+static const unsigned int kGreenYMul = 150; /* 0.587 * 256 */
+static const unsigned int kBlueYMul = 29; /* 0.114 * 256 */
+
+#define SWSTATS_START_LINE_STATS(pixel_t) \
+	pixel_t r, g, g2, b;              \
+	unsigned int yVal;                \
+                                          \
+	unsigned int sumR = 0;            \
+	unsigned int sumG = 0;            \
+	unsigned int sumB = 0;
+
+#define SWSTATS_ACCUMULATE_LINE_STATS(div) \
+	sumR += r;                         \
+	sumG += g;                         \
+	sumB += b;                         \
+                                           \
+	yVal = r * kRedYMul;               \
+	yVal += g * kGreenYMul;            \
+	yVal += b * kBlueYMul;             \
+	stats_.yHistogram[yVal * SwIspStats::kYHistogramSize / (256 * 256 * (div))]++;
+
+#define SWSTATS_FINISH_LINE_STATS() \
+	stats_.sumR_ += sumR;       \
+	stats_.sumG_ += sumG;       \
+	stats_.sumB_ += sumB;
+
+void SwStatsCpu::statsBGGR10PLine0(const uint8_t *src[])
+{
+	const uint8_t *src0 = src[1] + window_.x * 5 / 4;
+	const uint8_t *src1 = src[2] + window_.x * 5 / 4;
+	const int widthInBytes = window_.width * 5 / 4;
+
+	if (swapLines_)
+		std::swap(src0, src1);
+
+	SWSTATS_START_LINE_STATS(uint8_t)
+
+	/* x += 5 sample every other 2x2 block */
+	for (int x = 0; x < widthInBytes; x += 5) {
+		/* BGGR */
+		b = src0[x];
+		g = src0[x + 1];
+		g2 = src1[x];
+		r = src1[x + 1];
+		g = (g + g2) / 2;
+		/* Data is already 8 bits, divide by 1 */
+		SWSTATS_ACCUMULATE_LINE_STATS(1)
+	}
+
+	SWSTATS_FINISH_LINE_STATS()
+}
+
+void SwStatsCpu::statsGBRG10PLine0(const uint8_t *src[])
+{
+	const uint8_t *src0 = src[1] + window_.x * 5 / 4;
+	const uint8_t *src1 = src[2] + window_.x * 5 / 4;
+	const int widthInBytes = window_.width * 5 / 4;
+
+	if (swapLines_)
+		std::swap(src0, src1);
+
+	SWSTATS_START_LINE_STATS(uint8_t)
+
+	/* x += 5 sample every other 2x2 block */
+	for (int x = 0; x < widthInBytes; x += 5) {
+		/* GBRG */
+		g = src0[x];
+		b = src0[x + 1];
+		r = src1[x];
+		g2 = src1[x + 1];
+		g = (g + g2) / 2;
+		/* Data is already 8 bits, divide by 1 */
+		SWSTATS_ACCUMULATE_LINE_STATS(1)
+	}
+
+	SWSTATS_FINISH_LINE_STATS()
+}
+
+/**
+ * \brief Reset state to start statistics gathering for a new frame.
+ *
+ * This may only be called after a successful setWindow() call.
+ */
+void SwStatsCpu::startFrame(void)
+{
+	stats_.sumR_ = 0;
+	stats_.sumB_ = 0;
+	stats_.sumG_ = 0;
+	stats_.yHistogram.fill(0);
+}
+
+/**
+ * \brief Finish statistics calculation for the current frame.
+ *
+ * This may only be called after a successful setWindow() call.
+ */
+void SwStatsCpu::finishFrame(void)
+{
+	*sharedStats_ = stats_;
+	statsReady.emit(0);
+}
+
+/**
+ * \brief Configure the statistics object for the passed in input format.
+ * \param[in] inputCfg The input format
+ *
+ * \return 0 on success, a negative errno value on failure
+ */
+int SwStatsCpu::configure(const StreamConfiguration &inputCfg)
+{
+	BayerFormat bayerFormat =
+		BayerFormat::fromPixelFormat(inputCfg.pixelFormat);
+
+	if (bayerFormat.bitDepth == 10 &&
+	    bayerFormat.packing == BayerFormat::Packing::CSI2) {
+		patternSize_.height = 2;
+		patternSize_.width = 4; /* 5 bytes per *4* pixels */
+		/* Skip every 3th and 4th line, sample every other 2x2 block */
+		ySkipMask_ = 0x02;
+		xShift_ = 0;
+
+		switch (bayerFormat.order) {
+		case BayerFormat::BGGR:
+		case BayerFormat::GRBG:
+			stats0_ = &SwStatsCpu::statsBGGR10PLine0;
+			swapLines_ = bayerFormat.order == BayerFormat::GRBG;
+			return 0;
+		case BayerFormat::GBRG:
+		case BayerFormat::RGGB:
+			stats0_ = &SwStatsCpu::statsGBRG10PLine0;
+			swapLines_ = bayerFormat.order == BayerFormat::RGGB;
+			return 0;
+		default:
+			break;
+		}
+	}
+
+	LOG(SwStatsCpu, Info)
+		<< "Unsupported input format " << inputCfg.pixelFormat.toString();
+	return -EINVAL;
+}
+
+/**
+ * \brief Specify window coordinates over which to gather statistics.
+ * \param[in] window The window object.
+ */
+void SwStatsCpu::setWindow(Rectangle window)
+{
+	window_ = window;
+
+	window_.x &= ~(patternSize_.width - 1);
+	window_.x += xShift_;
+	window_.y &= ~(patternSize_.height - 1);
+
+	/* width_ - xShift_ to make sure the window fits */
+	window_.width -= xShift_;
+	window_.width &= ~(patternSize_.width - 1);
+	window_.height &= ~(patternSize_.height - 1);
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/software_isp/swstats_cpu.h b/src/libcamera/software_isp/swstats_cpu.h
new file mode 100644
index 00000000..df13263b
--- /dev/null
+++ b/src/libcamera/software_isp/swstats_cpu.h
@@ -0,0 +1,159 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Linaro Ltd
+ * Copyright (C) 2023, Red Hat Inc.
+ *
+ * Authors:
+ * Hans de Goede <hdegoede@redhat.com>
+ *
+ * swstats_cpu.h - CPU based software statistics implementation
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <libcamera/base/signal.h>
+
+#include <libcamera/geometry.h>
+
+#include "libcamera/internal/shared_mem_object.h"
+#include "libcamera/internal/software_isp/swisp_stats.h"
+
+namespace libcamera {
+
+class PixelFormat;
+struct StreamConfiguration;
+
+class SwStatsCpu
+{
+public:
+	SwStatsCpu();
+	~SwStatsCpu() = default;
+
+	/**
+	 * \brief Gets wether the statistics object is valid.
+	 *
+	 * \return true if it's valid, false otherwise
+	 */
+	bool isValid() const { return sharedStats_.fd().isValid(); }
+
+	/**
+	 * \brief Get the file descriptor for the statistics.
+	 *
+	 * \return the file descriptor
+	 */
+	const SharedFD &getStatsFD() { return sharedStats_.fd(); }
+
+	/**
+	 * \brief Get the pattern size.
+	 *
+	 * For some input-formats, e.g. Bayer data, processing is done multiple lines
+	 * and/or columns at a time. Get width and height at which the (bayer) pattern
+	 * repeats. Window values are rounded down to a multiple of this and the height
+	 * also indicates if processLine2() should be called or not.
+	 * This may only be called after a successful configure() call.
+	 *
+	 * \return the pattern size
+	 */
+	const Size &patternSize() { return patternSize_; }
+
+	int configure(const StreamConfiguration &inputCfg);
+	void setWindow(Rectangle window);
+	void startFrame();
+	void finishFrame();
+
+	/**
+	 * \brief Process line 0.
+	 * \param[in] y The y coordinate.
+	 * \param[in] src The input data.
+	 *
+	 * This function processes line 0 for input formats with patternSize height == 1.
+	 * It'll process line 0 and 1 for input formats with patternSize height >= 2.
+	 * This function may only be called after a successful setWindow() call.
+	 */
+	void processLine0(unsigned int y, const uint8_t *src[])
+	{
+		if ((y & ySkipMask_) || y < (unsigned int)window_.y ||
+		    y >= (window_.y + window_.height))
+			return;
+
+		(this->*stats0_)(src);
+	}
+
+	/**
+	 * \brief Process line 2 and 3.
+	 * \param[in] y The y coordinate.
+	 * \param[in] src The input data.
+	 *
+	 * This function processes line 2 and 3 for input formats with patternSize height == 4.
+	 * This function may only be called after a successful setWindow() call.
+	 */
+	void processLine2(unsigned int y, const uint8_t *src[])
+	{
+		if ((y & ySkipMask_) || y < (unsigned int)window_.y ||
+		    y >= (window_.y + window_.height))
+			return;
+
+		(this->*stats2_)(src);
+	}
+
+	/**
+	 * \brief Signals that the statistics are ready.
+	 *
+	 * The int parameter isn't actually used.
+	 */
+	Signal<int> statsReady;
+
+private:
+	/**
+	 * \brief Called when there is data to get statistics from.
+	 * \param[in] src The input data
+	 *
+	 * These functions take an array of (patternSize_.height + 1) src
+	 * pointers each pointing to a line in the source image. The middle
+	 * element of the array will point to the actual line being processed.
+	 * Earlier element(s) will point to the previous line(s) and later
+	 * element(s) to the next line(s).
+	 *
+	 * See the documentation of DebayerCpu::debayerFn for more details.
+	 */
+	using statsProcessFn = void (SwStatsCpu::*)(const uint8_t *src[]);
+
+	void statsBGGR10PLine0(const uint8_t *src[]);
+	void statsGBRG10PLine0(const uint8_t *src[]);
+
+	/* Variables set by configure(), used every line */
+	statsProcessFn stats0_;
+	statsProcessFn stats2_;
+	bool swapLines_;
+
+	/**
+	 * \brief Skip lines where this bitmask is set in y.
+	 */
+	unsigned int ySkipMask_;
+
+	/**
+	 * \brief Statistics window, set by setWindow(), used ever line.
+	 */
+	Rectangle window_;
+
+	/**
+	 * \brief The size of the bayer pattern.
+	 *
+	 * Valid sizes are: 2x2, 4x2 or 4x4.
+	 */
+	Size patternSize_;
+
+	/**
+	 * \brief The offset of x, applied to window_.x for bayer variants.
+	 *
+	 * This can either be 0 or 1.
+	 */
+	unsigned int xShift_;
+
+	SharedMemObject<SwIspStats> sharedStats_;
+	SwIspStats stats_;
+};
+
+} /* namespace libcamera */