summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ipa/libipa/agc_mean_luminance.cpp12
-rw-r--r--src/ipa/rkisp1/algorithms/agc.cpp25
-rw-r--r--src/ipa/rkisp1/algorithms/agc.h1
-rw-r--r--src/ipa/rkisp1/rkisp1.cpp7
-rw-r--r--src/ipa/simple/algorithms/agc.cpp11
-rw-r--r--src/ipa/simple/algorithms/awb.cpp50
-rw-r--r--src/ipa/simple/algorithms/awb.h6
-rw-r--r--src/ipa/simple/algorithms/blc.cpp11
-rw-r--r--src/ipa/simple/algorithms/ccm.cpp76
-rw-r--r--src/ipa/simple/algorithms/ccm.h43
-rw-r--r--src/ipa/simple/algorithms/lut.cpp78
-rw-r--r--src/ipa/simple/algorithms/lut.h6
-rw-r--r--src/ipa/simple/algorithms/meson.build1
-rw-r--r--src/ipa/simple/data/uncalibrated.yaml9
-rw-r--r--src/ipa/simple/ipa_context.h32
-rw-r--r--src/ipa/simple/soft_simple.cpp32
-rw-r--r--src/libcamera/camera_manager.cpp44
-rw-r--r--src/libcamera/pipeline/rpi/pisp/pisp.cpp2
-rw-r--r--src/libcamera/pipeline/simple/simple.cpp96
-rw-r--r--src/libcamera/software_isp/debayer.cpp60
-rw-r--r--src/libcamera/software_isp/debayer.h3
-rw-r--r--src/libcamera/software_isp/debayer_cpu.cpp158
-rw-r--r--src/libcamera/software_isp/debayer_cpu.h39
-rw-r--r--src/libcamera/software_isp/software_isp.cpp30
24 files changed, 655 insertions, 177 deletions
diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp
index 02555a44..f617fde8 100644
--- a/src/ipa/libipa/agc_mean_luminance.cpp
+++ b/src/ipa/libipa/agc_mean_luminance.cpp
@@ -541,6 +541,18 @@ AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex,
std::shared_ptr<ExposureModeHelper> exposureModeHelper =
exposureModeHelpers_.at(exposureModeIndex);
+ if (effectiveExposureValue == 0s) {
+ LOG(AgcMeanLuminance, Error)
+ << "Effective exposure value is 0. This is a bug in AGC "
+ "and must be fixed for proper operation.";
+ /*
+ * Return an arbitrary exposure time > 0 to ensure regulation
+ * doesn't get stuck with 0 in case the sensor driver allows a
+ * min exposure of 0.
+ */
+ return exposureModeHelper->splitExposure(10ms);
+ }
+
double gain = estimateInitialGain();
gain = constraintClampGain(constraintModeIndex, yHist, gain);
diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
index 5a3ba013..b3ac9400 100644
--- a/src/ipa/rkisp1/algorithms/agc.cpp
+++ b/src/ipa/rkisp1/algorithms/agc.cpp
@@ -193,14 +193,10 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
context.activeState.agc.minFrameDuration = std::chrono::microseconds(frameDurationLimits.min().get<int64_t>());
context.activeState.agc.maxFrameDuration = std::chrono::microseconds(frameDurationLimits.max().get<int64_t>());
- /*
- * Define the measurement window for AGC as a centered rectangle
- * covering 3/4 of the image width and height.
- */
- context.configuration.agc.measureWindow.h_offs = configInfo.outputSize.width / 8;
- context.configuration.agc.measureWindow.v_offs = configInfo.outputSize.height / 8;
- context.configuration.agc.measureWindow.h_size = 3 * configInfo.outputSize.width / 4;
- context.configuration.agc.measureWindow.v_size = 3 * configInfo.outputSize.height / 4;
+ context.configuration.agc.measureWindow.h_offs = 0;
+ context.configuration.agc.measureWindow.v_offs = 0;
+ context.configuration.agc.measureWindow.h_size = configInfo.outputSize.width;
+ context.configuration.agc.measureWindow.v_size = configInfo.outputSize.height;
setLimits(context.configuration.sensor.minExposureTime,
context.configuration.sensor.maxExposureTime,
@@ -439,15 +435,20 @@ void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext,
*/
double Agc::estimateLuminance(double gain) const
{
+ ASSERT(expMeans_.size() == weights_.size());
double ySum = 0.0;
+ double wSum = 0.0;
/* Sum the averages, saturated to 255. */
- for (uint8_t expMean : expMeans_)
- ySum += std::min(expMean * gain, 255.0);
+ for (unsigned i = 0; i < expMeans_.size(); i++) {
+ double w = weights_[i];
+ ySum += std::min(expMeans_[i] * gain, 255.0) * w;
+ wSum += w;
+ }
/* \todo Weight with the AWB gains */
- return ySum / expMeans_.size() / 255;
+ return ySum / wSum / 255;
}
/**
@@ -515,6 +516,8 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
Histogram hist({ params->hist.hist_bins, context.hw->numHistogramBins },
[](uint32_t x) { return x >> 4; });
expMeans_ = { params->ae.exp_mean, context.hw->numAeCells };
+ std::vector<uint8_t> &modeWeights = meteringModes_.at(frameContext.agc.meteringMode);
+ weights_ = { modeWeights.data(), modeWeights.size() };
/*
* Set the AGC limits using the fixed exposure time and/or gain in
diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h
index 62bcde99..7867eed9 100644
--- a/src/ipa/rkisp1/algorithms/agc.h
+++ b/src/ipa/rkisp1/algorithms/agc.h
@@ -55,6 +55,7 @@ private:
utils::Duration frameDuration);
Span<const uint8_t> expMeans_;
+ Span<const uint8_t> weights_;
std::map<int32_t, std::vector<uint8_t>> meteringModes_;
};
diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp
index 7547d2f2..70ce0cba 100644
--- a/src/ipa/rkisp1/rkisp1.cpp
+++ b/src/ipa/rkisp1/rkisp1.cpp
@@ -211,8 +211,7 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision,
int IPARkISP1::start()
{
- setControls(0);
-
+ /* \todo Properly handle startup controls. */
return 0;
}
@@ -455,6 +454,10 @@ void IPARkISP1::setControls(unsigned int frame)
uint32_t gain = context_.camHelper->gainCode(frameContext.agc.gain);
uint32_t vblank = frameContext.agc.vblank;
+ LOG(IPARkISP1, Debug)
+ << "Set controls for frame " << frame << ": exposure " << exposure
+ << ", gain " << frameContext.agc.gain << ", vblank " << vblank;
+
ControlList ctrls(sensorControls_);
ctrls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(exposure));
ctrls.set(V4L2_CID_ANALOGUE_GAIN, static_cast<int32_t>(gain));
diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp
index 72aade14..c46bb0eb 100644
--- a/src/ipa/simple/algorithms/agc.cpp
+++ b/src/ipa/simple/algorithms/agc.cpp
@@ -11,6 +11,8 @@
#include <libcamera/base/log.h>
+#include "control_ids.h"
+
namespace libcamera {
LOG_DEFINE_CATEGORY(IPASoftExposure)
@@ -97,10 +99,15 @@ void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou
void Agc::process(IPAContext &context,
[[maybe_unused]] const uint32_t frame,
- [[maybe_unused]] IPAFrameContext &frameContext,
+ IPAFrameContext &frameContext,
const SwIspStats *stats,
- [[maybe_unused]] ControlList &metadata)
+ ControlList &metadata)
{
+ utils::Duration exposureTime =
+ context.configuration.agc.lineDuration * frameContext.sensor.exposure;
+ metadata.set(controls::ExposureTime, exposureTime.get<std::micro>());
+ metadata.set(controls::AnalogueGain, frameContext.sensor.gain);
+
/*
* Calculate Mean Sample Value (MSV) according to formula from:
* https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf
diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp
index 195de41d..55719059 100644
--- a/src/ipa/simple/algorithms/awb.cpp
+++ b/src/ipa/simple/algorithms/awb.cpp
@@ -12,8 +12,13 @@
#include <libcamera/base/log.h>
+#include <libcamera/control_ids.h>
+
+#include "libipa/colours.h"
#include "simple/ipa_context.h"
+#include "control_ids.h"
+
namespace libcamera {
LOG_DEFINE_CATEGORY(IPASoftAwb)
@@ -23,21 +28,38 @@ namespace ipa::soft::algorithms {
int Awb::configure(IPAContext &context,
[[maybe_unused]] const IPAConfigInfo &configInfo)
{
- auto &gains = context.activeState.gains;
- gains.red = gains.green = gains.blue = 1.0;
+ auto &gains = context.activeState.awb.gains;
+ gains = { { 1.0, 1.0, 1.0 } };
return 0;
}
+void Awb::prepare(IPAContext &context,
+ [[maybe_unused]] const uint32_t frame,
+ IPAFrameContext &frameContext,
+ [[maybe_unused]] DebayerParams *params)
+{
+ auto &gains = context.activeState.awb.gains;
+ frameContext.gains.red = gains.r();
+ frameContext.gains.blue = gains.b();
+}
+
void Awb::process(IPAContext &context,
[[maybe_unused]] const uint32_t frame,
- [[maybe_unused]] IPAFrameContext &frameContext,
+ IPAFrameContext &frameContext,
const SwIspStats *stats,
- [[maybe_unused]] ControlList &metadata)
+ ControlList &metadata)
{
const SwIspStats::Histogram &histogram = stats->yHistogram;
const uint8_t blackLevel = context.activeState.blc.level;
+ const float maxGain = 1024.0;
+ const float mdGains[] = {
+ static_cast<float>(frameContext.gains.red / maxGain),
+ static_cast<float>(frameContext.gains.blue / maxGain)
+ };
+ metadata.set(controls::ColourGains, mdGains);
+
/*
* Black level must be subtracted to get the correct AWB ratios, they
* would be off if they were computed from the whole brightness range
@@ -54,12 +76,20 @@ void Awb::process(IPAContext &context,
* Calculate red and blue gains for AWB.
* Clamp max gain at 4.0, this also avoids 0 division.
*/
- auto &gains = context.activeState.gains;
- gains.red = sumR <= sumG / 4 ? 4.0 : static_cast<double>(sumG) / sumR;
- gains.blue = sumB <= sumG / 4 ? 4.0 : static_cast<double>(sumG) / sumB;
- /* Green gain is fixed to 1.0 */
-
- LOG(IPASoftAwb, Debug) << "gain R/B " << gains.red << "/" << gains.blue;
+ auto &gains = context.activeState.awb.gains;
+ gains = { {
+ sumR <= sumG / 4 ? 4.0f : static_cast<float>(sumG) / sumR,
+ 1.0,
+ sumB <= sumG / 4 ? 4.0f : static_cast<float>(sumG) / sumB,
+ } };
+
+ RGB<double> rgbGains{ { 1 / gains.r(), 1 / gains.g(), 1 / gains.b() } };
+ context.activeState.awb.temperatureK = estimateCCT(rgbGains);
+ metadata.set(controls::ColourTemperature, context.activeState.awb.temperatureK);
+
+ LOG(IPASoftAwb, Debug)
+ << "gain R/B: " << gains << "; temperature: "
+ << context.activeState.awb.temperatureK;
}
REGISTER_IPA_ALGORITHM(Awb, "Awb")
diff --git a/src/ipa/simple/algorithms/awb.h b/src/ipa/simple/algorithms/awb.h
index db1496cd..ad993f39 100644
--- a/src/ipa/simple/algorithms/awb.h
+++ b/src/ipa/simple/algorithms/awb.h
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
- * Copyright (C) 2024, Red Hat Inc.
+ * Copyright (C) 2024-2025 Red Hat Inc.
*
* Auto white balance
*/
@@ -20,6 +20,10 @@ public:
~Awb() = default;
int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
+ void prepare(IPAContext &context,
+ const uint32_t frame,
+ IPAFrameContext &frameContext,
+ DebayerParams *params) override;
void process(IPAContext &context,
const uint32_t frame,
IPAFrameContext &frameContext,
diff --git a/src/ipa/simple/algorithms/blc.cpp b/src/ipa/simple/algorithms/blc.cpp
index 1d7d370b..53c356c8 100644
--- a/src/ipa/simple/algorithms/blc.cpp
+++ b/src/ipa/simple/algorithms/blc.cpp
@@ -11,6 +11,8 @@
#include <libcamera/base/log.h>
+#include "control_ids.h"
+
namespace libcamera {
namespace ipa::soft::algorithms {
@@ -49,8 +51,15 @@ void BlackLevel::process(IPAContext &context,
[[maybe_unused]] const uint32_t frame,
IPAFrameContext &frameContext,
const SwIspStats *stats,
- [[maybe_unused]] ControlList &metadata)
+ ControlList &metadata)
{
+ /* Assign each of the R G G B channels as the same black level. */
+ const int32_t blackLevel = context.activeState.blc.level * 256;
+ const int32_t blackLevels[] = {
+ blackLevel, blackLevel, blackLevel, blackLevel
+ };
+ metadata.set(controls::SensorBlackLevels, blackLevels);
+
if (context.configuration.black.level.has_value())
return;
diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp
new file mode 100644
index 00000000..d5ba928d
--- /dev/null
+++ b/src/ipa/simple/algorithms/ccm.cpp
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Ideas On Board
+ * Copyright (C) 2024-2025, Red Hat Inc.
+ *
+ * Color correction matrix
+ */
+
+#include "ccm.h"
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/utils.h>
+
+#include <libcamera/control_ids.h>
+
+namespace {
+
+constexpr unsigned int kTemperatureThreshold = 100;
+
+}
+
+namespace libcamera {
+
+namespace ipa::soft::algorithms {
+
+LOG_DEFINE_CATEGORY(IPASoftCcm)
+
+int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData)
+{
+ int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm");
+ if (ret < 0) {
+ LOG(IPASoftCcm, Error)
+ << "Failed to parse 'ccm' parameter from tuning file.";
+ return ret;
+ }
+
+ context.ccmEnabled = true;
+
+ return 0;
+}
+
+void Ccm::prepare(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params)
+{
+ const unsigned int ct = context.activeState.awb.temperatureK;
+
+ /* Change CCM only on bigger temperature changes. */
+ if (frame > 0 &&
+ utils::abs_diff(ct, lastCt_) < kTemperatureThreshold) {
+ frameContext.ccm.ccm = context.activeState.ccm.ccm;
+ context.activeState.ccm.changed = false;
+ return;
+ }
+
+ lastCt_ = ct;
+ Matrix<float, 3, 3> ccm = ccm_.getInterpolated(ct);
+
+ context.activeState.ccm.ccm = ccm;
+ frameContext.ccm.ccm = ccm;
+ context.activeState.ccm.changed = true;
+}
+
+void Ccm::process([[maybe_unused]] IPAContext &context,
+ [[maybe_unused]] const uint32_t frame,
+ IPAFrameContext &frameContext,
+ [[maybe_unused]] const SwIspStats *stats,
+ ControlList &metadata)
+{
+ metadata.set(controls::ColourCorrectionMatrix, frameContext.ccm.ccm.data());
+}
+
+REGISTER_IPA_ALGORITHM(Ccm, "Ccm")
+
+} /* namespace ipa::soft::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/algorithms/ccm.h b/src/ipa/simple/algorithms/ccm.h
new file mode 100644
index 00000000..f4e2b85b
--- /dev/null
+++ b/src/ipa/simple/algorithms/ccm.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024-2025, Red Hat Inc.
+ *
+ * Color correction matrix
+ */
+
+#pragma once
+
+#include "libcamera/internal/matrix.h"
+
+#include <libipa/interpolator.h>
+
+#include "algorithm.h"
+
+namespace libcamera {
+
+namespace ipa::soft::algorithms {
+
+class Ccm : public Algorithm
+{
+public:
+ Ccm() = default;
+ ~Ccm() = default;
+
+ int init(IPAContext &context, const YamlObject &tuningData) override;
+ void prepare(IPAContext &context,
+ const uint32_t frame,
+ IPAFrameContext &frameContext,
+ DebayerParams *params) override;
+ void process(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const SwIspStats *stats,
+ ControlList &metadata) override;
+
+private:
+ unsigned int lastCt_;
+ Interpolator<Matrix<float, 3, 3>> ccm_;
+};
+
+} /* namespace ipa::soft::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/algorithms/lut.cpp b/src/ipa/simple/algorithms/lut.cpp
index 18f3a550..e8638f27 100644
--- a/src/ipa/simple/algorithms/lut.cpp
+++ b/src/ipa/simple/algorithms/lut.cpp
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
- * Copyright (C) 2024, Red Hat Inc.
+ * Copyright (C) 2024-2025, Red Hat Inc.
*
* Color lookup tables construction
*/
@@ -80,42 +80,78 @@ void Lut::updateGammaTable(IPAContext &context)
context.activeState.gamma.contrast = contrast;
}
+int16_t Lut::ccmValue(unsigned int i, float ccm) const
+{
+ return std::round(i * ccm);
+}
+
void Lut::prepare(IPAContext &context,
[[maybe_unused]] const uint32_t frame,
- [[maybe_unused]] IPAFrameContext &frameContext,
- [[maybe_unused]] DebayerParams *params)
+ IPAFrameContext &frameContext,
+ DebayerParams *params)
{
+ frameContext.contrast = context.activeState.knobs.contrast;
+
/*
* Update the gamma table if needed. This means if black level changes
* and since the black level gets updated only if a lower value is
* observed, it's not permanently prone to minor fluctuations or
* rounding errors.
*/
- if (context.activeState.gamma.blackLevel != context.activeState.blc.level ||
- context.activeState.gamma.contrast != context.activeState.knobs.contrast)
+ const bool gammaUpdateNeeded =
+ context.activeState.gamma.blackLevel != context.activeState.blc.level ||
+ context.activeState.gamma.contrast != context.activeState.knobs.contrast;
+ if (gammaUpdateNeeded)
updateGammaTable(context);
- auto &gains = context.activeState.gains;
+ auto &gains = context.activeState.awb.gains;
auto &gammaTable = context.activeState.gamma.gammaTable;
const unsigned int gammaTableSize = gammaTable.size();
-
- for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {
- const double div = static_cast<double>(DebayerParams::kRGBLookupSize) /
- gammaTableSize;
- /* Apply gamma after gain! */
- unsigned int idx;
- idx = std::min({ static_cast<unsigned int>(i * gains.red / div),
- gammaTableSize - 1 });
- params->red[i] = gammaTable[idx];
- idx = std::min({ static_cast<unsigned int>(i * gains.green / div),
- gammaTableSize - 1 });
- params->green[i] = gammaTable[idx];
- idx = std::min({ static_cast<unsigned int>(i * gains.blue / div),
- gammaTableSize - 1 });
- params->blue[i] = gammaTable[idx];
+ const double div = static_cast<double>(DebayerParams::kRGBLookupSize) /
+ gammaTableSize;
+
+ if (!context.ccmEnabled) {
+ for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {
+ /* Apply gamma after gain! */
+ const RGB<float> lutGains = (gains * i / div).min(gammaTableSize - 1);
+ params->red[i] = gammaTable[static_cast<unsigned int>(lutGains.r())];
+ params->green[i] = gammaTable[static_cast<unsigned int>(lutGains.g())];
+ params->blue[i] = gammaTable[static_cast<unsigned int>(lutGains.b())];
+ }
+ } else if (context.activeState.ccm.changed || gammaUpdateNeeded) {
+ Matrix<float, 3, 3> gainCcm = { { gains.r(), 0, 0,
+ 0, gains.g(), 0,
+ 0, 0, gains.b() } };
+ auto ccm = gainCcm * context.activeState.ccm.ccm;
+ auto &red = params->redCcm;
+ auto &green = params->greenCcm;
+ auto &blue = params->blueCcm;
+ for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {
+ red[i].r = ccmValue(i, ccm[0][0]);
+ red[i].g = ccmValue(i, ccm[1][0]);
+ red[i].b = ccmValue(i, ccm[2][0]);
+ green[i].r = ccmValue(i, ccm[0][1]);
+ green[i].g = ccmValue(i, ccm[1][1]);
+ green[i].b = ccmValue(i, ccm[2][1]);
+ blue[i].r = ccmValue(i, ccm[0][2]);
+ blue[i].g = ccmValue(i, ccm[1][2]);
+ blue[i].b = ccmValue(i, ccm[2][2]);
+ params->gammaLut[i] = gammaTable[i / div];
+ }
}
}
+void Lut::process([[maybe_unused]] IPAContext &context,
+ [[maybe_unused]] const uint32_t frame,
+ [[maybe_unused]] IPAFrameContext &frameContext,
+ [[maybe_unused]] const SwIspStats *stats,
+ ControlList &metadata)
+{
+ const auto &contrast = frameContext.contrast;
+ if (contrast)
+ metadata.set(controls::Contrast, contrast.value());
+}
+
REGISTER_IPA_ALGORITHM(Lut, "Lut")
} /* namespace ipa::soft::algorithms */
diff --git a/src/ipa/simple/algorithms/lut.h b/src/ipa/simple/algorithms/lut.h
index 889f864b..ba8b9021 100644
--- a/src/ipa/simple/algorithms/lut.h
+++ b/src/ipa/simple/algorithms/lut.h
@@ -30,9 +30,15 @@ public:
const uint32_t frame,
IPAFrameContext &frameContext,
DebayerParams *params) override;
+ void process(IPAContext &context,
+ const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const SwIspStats *stats,
+ ControlList &metadata) override;
private:
void updateGammaTable(IPAContext &context);
+ int16_t ccmValue(unsigned int i, float ccm) const;
};
} /* namespace ipa::soft::algorithms */
diff --git a/src/ipa/simple/algorithms/meson.build b/src/ipa/simple/algorithms/meson.build
index 37a2eb53..2d0adb05 100644
--- a/src/ipa/simple/algorithms/meson.build
+++ b/src/ipa/simple/algorithms/meson.build
@@ -4,5 +4,6 @@ soft_simple_ipa_algorithms = files([
'awb.cpp',
'agc.cpp',
'blc.cpp',
+ 'ccm.cpp',
'lut.cpp',
])
diff --git a/src/ipa/simple/data/uncalibrated.yaml b/src/ipa/simple/data/uncalibrated.yaml
index 3f147112..5508e668 100644
--- a/src/ipa/simple/data/uncalibrated.yaml
+++ b/src/ipa/simple/data/uncalibrated.yaml
@@ -5,6 +5,15 @@ version: 1
algorithms:
- BlackLevel:
- Awb:
+ # Color correction matrices can be defined here. The CCM algorithm
+ # has a significant performance impact, and should only be enabled
+ # if tuned.
+ # - Ccm:
+ # ccms:
+ # - ct: 6500
+ # ccm: [ 1, 0, 0,
+ # 0, 1, 0,
+ # 0, 0, 1]
- Lut:
- Agc:
...
diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h
index 4af51306..7dc2cd7a 100644
--- a/src/ipa/simple/ipa_context.h
+++ b/src/ipa/simple/ipa_context.h
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
- * Copyright (C) 2024 Red Hat, Inc.
+ * Copyright (C) 2024-2025 Red Hat, Inc.
*
* Simple pipeline IPA Context
*/
@@ -13,8 +13,13 @@
#include <libcamera/controls.h>
+#include "libcamera/internal/matrix.h"
+#include "libcamera/internal/vector.h"
+
#include <libipa/fc_queue.h>
+#include "core_ipa_interface.h"
+
namespace libcamera {
namespace ipa::soft {
@@ -24,6 +29,7 @@ struct IPASessionConfiguration {
struct {
int32_t exposureMin, exposureMax;
double againMin, againMax, againMinStep;
+ utils::Duration lineDuration;
} agc;
struct {
std::optional<uint8_t> level;
@@ -36,10 +42,9 @@ struct IPAActiveState {
} blc;
struct {
- double red;
- double green;
- double blue;
- } gains;
+ RGB<float> gains;
+ unsigned int temperatureK;
+ } awb;
static constexpr unsigned int kGammaLookupSize = 1024;
struct {
@@ -47,6 +52,12 @@ struct IPAActiveState {
uint8_t blackLevel;
double contrast;
} gamma;
+
+ struct {
+ Matrix<float, 3, 3> ccm;
+ bool changed;
+ } ccm;
+
struct {
/* 0..2 range, 1.0 = normal */
std::optional<double> contrast;
@@ -55,9 +66,18 @@ struct IPAActiveState {
struct IPAFrameContext : public FrameContext {
struct {
+ Matrix<float, 3, 3> ccm;
+ } ccm;
+
+ struct {
int32_t exposure;
double gain;
} sensor;
+ struct {
+ double red;
+ double blue;
+ } gains;
+ std::optional<double> contrast;
};
struct IPAContext {
@@ -66,10 +86,12 @@ struct IPAContext {
{
}
+ IPACameraSensorInfo sensorInfo;
IPASessionConfiguration configuration;
IPAActiveState activeState;
FCQueue<IPAFrameContext> frameContexts;
ControlInfoMap::Map ctrlMap;
+ bool ccmEnabled;
};
} /* namespace ipa::soft */
diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp
index b26e4e15..c94c4cd5 100644
--- a/src/ipa/simple/soft_simple.cpp
+++ b/src/ipa/simple/soft_simple.cpp
@@ -5,6 +5,7 @@
* Simple Software Image Processing Algorithm module
*/
+#include <chrono>
#include <stdint.h>
#include <sys/mman.h>
@@ -32,6 +33,8 @@
namespace libcamera {
LOG_DEFINE_CATEGORY(IPASoft)
+using namespace std::literals::chrono_literals;
+
namespace ipa::soft {
/* Maximum number of frame contexts to be held */
@@ -50,8 +53,10 @@ public:
int init(const IPASettings &settings,
const SharedFD &fdStats,
const SharedFD &fdParams,
- const ControlInfoMap &sensorInfoMap,
- ControlInfoMap *ipaControls) override;
+ const IPACameraSensorInfo &sensorInfo,
+ const ControlInfoMap &sensorControls,
+ ControlInfoMap *ipaControls,
+ bool *ccmEnabled) override;
int configure(const IPAConfigInfo &configInfo) override;
int start() override;
@@ -88,8 +93,10 @@ IPASoftSimple::~IPASoftSimple()
int IPASoftSimple::init(const IPASettings &settings,
const SharedFD &fdStats,
const SharedFD &fdParams,
- const ControlInfoMap &sensorInfoMap,
- ControlInfoMap *ipaControls)
+ const IPACameraSensorInfo &sensorInfo,
+ const ControlInfoMap &sensorControls,
+ ControlInfoMap *ipaControls,
+ bool *ccmEnabled)
{
camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel);
if (!camHelper_) {
@@ -98,6 +105,8 @@ int IPASoftSimple::init(const IPASettings &settings,
<< settings.sensorModel;
}
+ context_.sensorInfo = sensorInfo;
+
/* Load the tuning data file */
File file(settings.configurationFile);
if (!file.open(File::OpenModeFlag::ReadOnly)) {
@@ -125,6 +134,8 @@ int IPASoftSimple::init(const IPASettings &settings,
if (ret)
return ret;
+ *ccmEnabled = context_.ccmEnabled;
+
params_ = nullptr;
stats_ = nullptr;
@@ -169,12 +180,12 @@ int IPASoftSimple::init(const IPASettings &settings,
* Don't save the min and max control values yet, as e.g. the limits
* for V4L2_CID_EXPOSURE depend on the configured sensor resolution.
*/
- if (sensorInfoMap.find(V4L2_CID_EXPOSURE) == sensorInfoMap.end()) {
+ if (sensorControls.find(V4L2_CID_EXPOSURE) == sensorControls.end()) {
LOG(IPASoft, Error) << "Don't have exposure control";
return -EINVAL;
}
- if (sensorInfoMap.find(V4L2_CID_ANALOGUE_GAIN) == sensorInfoMap.end()) {
+ if (sensorControls.find(V4L2_CID_ANALOGUE_GAIN) == sensorControls.end()) {
LOG(IPASoft, Error) << "Don't have gain control";
return -EINVAL;
}
@@ -194,6 +205,8 @@ int IPASoftSimple::configure(const IPAConfigInfo &configInfo)
context_.activeState = {};
context_.frameContexts.clear();
+ context_.configuration.agc.lineDuration =
+ context_.sensorInfo.minLineLength * 1.0s / context_.sensorInfo.pixelRate;
context_.configuration.agc.exposureMin = exposureInfo.min().get<int32_t>();
context_.configuration.agc.exposureMax = exposureInfo.max().get<int32_t>();
if (!context_.configuration.agc.exposureMin) {
@@ -295,15 +308,10 @@ void IPASoftSimple::processStats(const uint32_t frame,
int32_t again = sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();
frameContext.sensor.gain = camHelper_ ? camHelper_->gain(again) : again;
- /*
- * Software ISP currently does not produce any metadata. Use an empty
- * ControlList for now.
- *
- * \todo Implement proper metadata handling
- */
ControlList metadata(controls::controls);
for (auto const &algo : algorithms())
algo->process(context_, frame, frameContext, stats_, metadata);
+ metadataReady.emit(frame, metadata);
/* Sanity check */
if (!sensorControls.contains(V4L2_CID_EXPOSURE) ||
diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp
index 87e6717e..400109f1 100644
--- a/src/libcamera/camera_manager.cpp
+++ b/src/libcamera/camera_manager.cpp
@@ -202,24 +202,24 @@ void CameraManager::Private::addCamera(std::shared_ptr<Camera> camera)
{
ASSERT(Thread::current() == this);
- MutexLocker locker(mutex_);
+ {
+ MutexLocker locker(mutex_);
- for (const std::shared_ptr<Camera> &c : cameras_) {
- if (c->id() == camera->id()) {
- LOG(Camera, Fatal)
- << "Trying to register a camera with a duplicated ID '"
- << camera->id() << "'";
- return;
+ for (const std::shared_ptr<Camera> &c : cameras_) {
+ if (c->id() == camera->id()) {
+ LOG(Camera, Fatal)
+ << "Trying to register a camera with a duplicated ID '"
+ << camera->id() << "'";
+ return;
+ }
}
- }
- cameras_.push_back(std::move(camera));
-
- unsigned int index = cameras_.size() - 1;
+ cameras_.push_back(camera);
+ }
/* Report the addition to the public signal */
CameraManager *const o = LIBCAMERA_O_PTR();
- o->cameraAdded.emit(cameras_[index]);
+ o->cameraAdded.emit(camera);
}
/**
@@ -236,20 +236,22 @@ void CameraManager::Private::removeCamera(std::shared_ptr<Camera> camera)
{
ASSERT(Thread::current() == this);
- MutexLocker locker(mutex_);
+ {
+ MutexLocker locker(mutex_);
- auto iter = std::find_if(cameras_.begin(), cameras_.end(),
- [camera](std::shared_ptr<Camera> &c) {
- return c.get() == camera.get();
- });
- if (iter == cameras_.end())
- return;
+ auto iter = std::find_if(cameras_.begin(), cameras_.end(),
+ [camera](std::shared_ptr<Camera> &c) {
+ return c.get() == camera.get();
+ });
+ if (iter == cameras_.end())
+ return;
+
+ cameras_.erase(iter);
+ }
LOG(Camera, Debug)
<< "Unregistering camera '" << camera->id() << "'";
- cameras_.erase(iter);
-
/* Report the removal to the public signal */
CameraManager *const o = LIBCAMERA_O_PTR();
o->cameraRemoved.emit(camera);
diff --git a/src/libcamera/pipeline/rpi/pisp/pisp.cpp b/src/libcamera/pipeline/rpi/pisp/pisp.cpp
index 0f9e3326..42ca7c80 100644
--- a/src/libcamera/pipeline/rpi/pisp/pisp.cpp
+++ b/src/libcamera/pipeline/rpi/pisp/pisp.cpp
@@ -1700,7 +1700,7 @@ void PiSPCameraData::platformFreeBuffers()
void PiSPCameraData::cfeBufferDequeue(FrameBuffer *buffer)
{
RPi::Stream *stream = nullptr;
- int index;
+ int index = 0;
if (!isRunning())
return;
diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
index 6e039bf3..fd0ccdca 100644
--- a/src/libcamera/pipeline/simple/simple.cpp
+++ b/src/libcamera/pipeline/simple/simple.cpp
@@ -181,6 +181,56 @@ LOG_DEFINE_CATEGORY(SimplePipeline)
class SimplePipelineHandler;
+struct SimpleFrameInfo {
+ SimpleFrameInfo(uint32_t f, Request *r, bool m)
+ : frame(f), request(r), metadataRequired(m), metadataProcessed(false)
+ {
+ }
+
+ uint32_t frame;
+ Request *request;
+ bool metadataRequired;
+ bool metadataProcessed;
+};
+
+class SimpleFrames
+{
+public:
+ void create(Request *request, bool metadataRequested);
+ void destroy(uint32_t frame);
+ void clear();
+
+ SimpleFrameInfo *find(uint32_t frame);
+
+private:
+ std::map<uint32_t, SimpleFrameInfo> frameInfo_;
+};
+
+void SimpleFrames::create(Request *request, bool metadataRequired)
+{
+ const uint32_t frame = request->sequence();
+ auto [it, inserted] = frameInfo_.try_emplace(frame, frame, request, metadataRequired);
+ ASSERT(inserted);
+}
+
+void SimpleFrames::destroy(uint32_t frame)
+{
+ frameInfo_.erase(frame);
+}
+
+void SimpleFrames::clear()
+{
+ frameInfo_.clear();
+}
+
+SimpleFrameInfo *SimpleFrames::find(uint32_t frame)
+{
+ auto info = frameInfo_.find(frame);
+ if (info == frameInfo_.end())
+ return nullptr;
+ return &info->second;
+}
+
struct SimplePipelineInfo {
const char *driver;
/*
@@ -293,15 +343,18 @@ public:
std::unique_ptr<Converter> converter_;
std::unique_ptr<SoftwareIsp> swIsp_;
+ SimpleFrames frameInfo_;
private:
void tryPipeline(unsigned int code, const Size &size);
static std::vector<const MediaPad *> routedSourcePads(MediaPad *sink);
+ void tryCompleteRequest(Request *request);
void conversionInputDone(FrameBuffer *buffer);
void conversionOutputDone(FrameBuffer *buffer);
void ispStatsReady(uint32_t frame, uint32_t bufferId);
+ void metadataReady(uint32_t frame, const ControlList &metadata);
void setSensorControls(const ControlList &sensorControls);
};
@@ -540,6 +593,7 @@ int SimpleCameraData::init()
swIsp_->inputBufferReady.connect(this, &SimpleCameraData::conversionInputDone);
swIsp_->outputBufferReady.connect(this, &SimpleCameraData::conversionOutputDone);
swIsp_->ispStatsReady.connect(this, &SimpleCameraData::ispStatsReady);
+ swIsp_->metadataReady.connect(this, &SimpleCameraData::metadataReady);
swIsp_->setSensorControls.connect(this, &SimpleCameraData::setSensorControls);
}
}
@@ -785,7 +839,7 @@ void SimpleCameraData::imageBufferReady(FrameBuffer *buffer)
/* No conversion, just complete the request. */
Request *request = buffer->request();
pipe->completeBuffer(request, buffer);
- pipe->completeRequest(request);
+ tryCompleteRequest(request);
return;
}
@@ -803,7 +857,10 @@ void SimpleCameraData::imageBufferReady(FrameBuffer *buffer)
const RequestOutputs &outputs = conversionQueue_.front();
for (auto &[stream, buf] : outputs.outputs)
pipe->completeBuffer(outputs.request, buf);
- pipe->completeRequest(outputs.request);
+ SimpleFrameInfo *info = frameInfo_.find(outputs.request->sequence());
+ if (info)
+ info->metadataRequired = false;
+ tryCompleteRequest(outputs.request);
conversionQueue_.pop();
return;
@@ -861,7 +918,7 @@ void SimpleCameraData::imageBufferReady(FrameBuffer *buffer)
/* Otherwise simply complete the request. */
pipe->completeBuffer(request, buffer);
- pipe->completeRequest(request);
+ tryCompleteRequest(request);
}
void SimpleCameraData::clearIncompleteRequests()
@@ -872,6 +929,24 @@ void SimpleCameraData::clearIncompleteRequests()
}
}
+void SimpleCameraData::tryCompleteRequest(Request *request)
+{
+ if (request->hasPendingBuffers())
+ return;
+
+ SimpleFrameInfo *info = frameInfo_.find(request->sequence());
+ if (!info) {
+ /* Something is really wrong, let's return. */
+ return;
+ }
+
+ if (info->metadataRequired && !info->metadataProcessed)
+ return;
+
+ frameInfo_.destroy(info->frame);
+ pipe()->completeRequest(request);
+}
+
void SimpleCameraData::conversionInputDone(FrameBuffer *buffer)
{
/* Queue the input buffer back for capture. */
@@ -885,7 +960,7 @@ void SimpleCameraData::conversionOutputDone(FrameBuffer *buffer)
/* Complete the buffer and the request. */
Request *request = buffer->request();
if (pipe->completeBuffer(request, buffer))
- pipe->completeRequest(request);
+ tryCompleteRequest(request);
}
void SimpleCameraData::ispStatsReady(uint32_t frame, uint32_t bufferId)
@@ -894,6 +969,17 @@ void SimpleCameraData::ispStatsReady(uint32_t frame, uint32_t bufferId)
delayedCtrls_->get(frame));
}
+void SimpleCameraData::metadataReady(uint32_t frame, const ControlList &metadata)
+{
+ SimpleFrameInfo *info = frameInfo_.find(frame);
+ if (!info)
+ return;
+
+ info->request->metadata().merge(metadata);
+ info->metadataProcessed = true;
+ tryCompleteRequest(info->request);
+}
+
void SimpleCameraData::setSensorControls(const ControlList &sensorControls)
{
delayedCtrls_->push(sensorControls);
@@ -1398,6 +1484,7 @@ void SimplePipelineHandler::stopDevice(Camera *camera)
video->bufferReady.disconnect(data, &SimpleCameraData::imageBufferReady);
+ data->frameInfo_.clear();
data->clearIncompleteRequests();
data->conversionBuffers_.clear();
@@ -1426,6 +1513,7 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request)
}
}
+ data->frameInfo_.create(request, !!data->swIsp_);
if (data->useConversion_) {
data->conversionQueue_.push({ request, std::move(buffers) });
if (data->swIsp_)
diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp
index f0b83261..e9e18c48 100644
--- a/src/libcamera/software_isp/debayer.cpp
+++ b/src/libcamera/software_isp/debayer.cpp
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2023, Linaro Ltd
- * Copyright (C) 2023, 2024 Red Hat Inc.
+ * Copyright (C) 2023-2025 Red Hat Inc.
*
* Authors:
* Hans de Goede <hdegoede@redhat.com>
@@ -24,8 +24,39 @@ namespace libcamera {
*/
/**
- * \typedef DebayerParams::ColorLookupTable
- * \brief Type of the lookup tables for red, green, blue values
+ * \struct DebayerParams::CcmColumn
+ * \brief Type of a single column of a color correction matrix (CCM)
+ *
+ * When multiplying an input pixel, columns in the CCM correspond to the red,
+ * green or blue component of input pixel values, while rows correspond to the
+ * red, green or blue components of the output pixel values. The members of the
+ * CcmColumn structure are named after the colour components of the output pixel
+ * values they correspond to.
+ */
+
+/**
+ * \var DebayerParams::CcmColumn::r
+ * \brief Red (first) component of a CCM column
+ */
+
+/**
+ * \var DebayerParams::CcmColumn::g
+ * \brief Green (second) component of a CCM column
+ */
+
+/**
+ * \var DebayerParams::CcmColumn::b
+ * \brief Blue (third) component of a CCM column
+ */
+
+/**
+ * \typedef DebayerParams::LookupTable
+ * \brief Type of the lookup tables for single lookup values
+ */
+
+/**
+ * \typedef DebayerParams::CcmLookupTable
+ * \brief Type of the CCM lookup tables for red, green, blue values
*/
/**
@@ -44,6 +75,26 @@ namespace libcamera {
*/
/**
+ * \var DebayerParams::redCcm
+ * \brief Lookup table for the CCM red column, mapping input values to output values
+ */
+
+/**
+ * \var DebayerParams::greenCcm
+ * \brief Lookup table for the CCM green column, mapping input values to output values
+ */
+
+/**
+ * \var DebayerParams::blueCcm
+ * \brief Lookup table for the CCM blue column, mapping input values to output values
+ */
+
+/**
+ * \var DebayerParams::gammaLut
+ * \brief Gamma lookup table used with color correction matrix
+ */
+
+/**
* \class Debayer
* \brief Base debayering class
*
@@ -57,10 +108,11 @@ Debayer::~Debayer()
}
/**
- * \fn int Debayer::configure(const StreamConfiguration &inputCfg, const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs)
+ * \fn int Debayer::configure()
* \brief Configure the debayer object according to the passed in parameters
* \param[in] inputCfg The input configuration
* \param[in] outputCfgs The output configurations
+ * \param[in] ccmEnabled Whether a color correction matrix is applied
*
* \return 0 on success, a negative errno on failure
*/
diff --git a/src/libcamera/software_isp/debayer.h b/src/libcamera/software_isp/debayer.h
index d7ca060d..ba033d44 100644
--- a/src/libcamera/software_isp/debayer.h
+++ b/src/libcamera/software_isp/debayer.h
@@ -33,7 +33,8 @@ public:
virtual ~Debayer() = 0;
virtual int configure(const StreamConfiguration &inputCfg,
- const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs) = 0;
+ const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs,
+ bool ccmEnabled) = 0;
virtual std::vector<PixelFormat> formats(PixelFormat inputFormat) = 0;
diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp
index 31ab96ab..66f6038c 100644
--- a/src/libcamera/software_isp/debayer_cpu.cpp
+++ b/src/libcamera/software_isp/debayer_cpu.cpp
@@ -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-2025 Red Hat Inc.
*
* Authors:
* Hans de Goede <hdegoede@redhat.com>
@@ -11,9 +11,11 @@
#include "debayer_cpu.h"
+#include <algorithm>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <time.h>
+#include <utility>
#include <linux/dma-buf.h>
@@ -51,8 +53,12 @@ DebayerCpu::DebayerCpu(std::unique_ptr<SwStatsCpu> stats)
enableInputMemcpy_ = true;
/* Initialize color lookup tables */
- for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++)
+ for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {
red_[i] = green_[i] = blue_[i] = i;
+ redCcm_[i] = { static_cast<int16_t>(i), 0, 0 };
+ greenCcm_[i] = { 0, static_cast<int16_t>(i), 0 };
+ blueCcm_[i] = { 0, 0, static_cast<int16_t>(i) };
+ }
}
DebayerCpu::~DebayerCpu() = default;
@@ -62,59 +68,71 @@ DebayerCpu::~DebayerCpu() = default;
const pixel_t *curr = (const pixel_t *)src[1] + xShift_; \
const pixel_t *next = (const pixel_t *)src[2] + xShift_;
+#define GAMMA(value) \
+ *dst++ = gammaLut_[std::clamp(value, 0, static_cast<int>(gammaLut_.size()) - 1)]
+
+#define STORE_PIXEL(b_, g_, r_) \
+ if constexpr (ccmEnabled) { \
+ const DebayerParams::CcmColumn &blue = blueCcm_[b_]; \
+ const DebayerParams::CcmColumn &green = greenCcm_[g_]; \
+ const DebayerParams::CcmColumn &red = redCcm_[r_]; \
+ GAMMA(blue.b + green.b + red.b); \
+ GAMMA(blue.g + green.g + red.g); \
+ GAMMA(blue.r + green.r + red.r); \
+ } else { \
+ *dst++ = blue_[b_]; \
+ *dst++ = green_[g_]; \
+ *dst++ = red_[r_]; \
+ } \
+ if constexpr (addAlphaByte) \
+ *dst++ = 255; \
+ x++;
+
/*
* RGR
* GBG
* RGR
*/
-#define BGGR_BGR888(p, n, div) \
- *dst++ = blue_[curr[x] / (div)]; \
- *dst++ = green_[(prev[x] + curr[x - p] + curr[x + n] + next[x]) / (4 * (div))]; \
- *dst++ = red_[(prev[x - p] + prev[x + n] + next[x - p] + next[x + n]) / (4 * (div))]; \
- if constexpr (addAlphaByte) \
- *dst++ = 255; \
- x++;
+#define BGGR_BGR888(p, n, div) \
+ STORE_PIXEL( \
+ curr[x] / (div), \
+ (prev[x] + curr[x - p] + curr[x + n] + next[x]) / (4 * (div)), \
+ (prev[x - p] + prev[x + n] + next[x - p] + next[x + n]) / (4 * (div)))
/*
* GBG
* RGR
* GBG
*/
-#define GRBG_BGR888(p, n, div) \
- *dst++ = blue_[(prev[x] + next[x]) / (2 * (div))]; \
- *dst++ = green_[curr[x] / (div)]; \
- *dst++ = red_[(curr[x - p] + curr[x + n]) / (2 * (div))]; \
- if constexpr (addAlphaByte) \
- *dst++ = 255; \
- x++;
+#define GRBG_BGR888(p, n, div) \
+ STORE_PIXEL( \
+ (prev[x] + next[x]) / (2 * (div)), \
+ curr[x] / (div), \
+ (curr[x - p] + curr[x + n]) / (2 * (div)))
/*
* GRG
* BGB
* GRG
*/
-#define GBRG_BGR888(p, n, div) \
- *dst++ = blue_[(curr[x - p] + curr[x + n]) / (2 * (div))]; \
- *dst++ = green_[curr[x] / (div)]; \
- *dst++ = red_[(prev[x] + next[x]) / (2 * (div))]; \
- if constexpr (addAlphaByte) \
- *dst++ = 255; \
- x++;
+#define GBRG_BGR888(p, n, div) \
+ STORE_PIXEL( \
+ (curr[x - p] + curr[x + n]) / (2 * (div)), \
+ curr[x] / (div), \
+ (prev[x] + next[x]) / (2 * (div)))
/*
* BGB
* GRG
* BGB
*/
-#define RGGB_BGR888(p, n, div) \
- *dst++ = blue_[(prev[x - p] + prev[x + n] + next[x - p] + next[x + n]) / (4 * (div))]; \
- *dst++ = green_[(prev[x] + curr[x - p] + curr[x + n] + next[x]) / (4 * (div))]; \
- *dst++ = red_[curr[x] / (div)]; \
- if constexpr (addAlphaByte) \
- *dst++ = 255; \
- x++;
+#define RGGB_BGR888(p, n, div) \
+ STORE_PIXEL( \
+ (prev[x - p] + prev[x + n] + next[x - p] + next[x + n]) / (4 * (div)), \
+ (prev[x] + curr[x - p] + curr[x + n] + next[x]) / (4 * (div)), \
+ curr[x] / (div))
-template<bool addAlphaByte>
+template<bool addAlphaByte, bool ccmEnabled>
void DebayerCpu::debayer8_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
{
DECLARE_SRC_POINTERS(uint8_t)
@@ -125,7 +143,7 @@ void DebayerCpu::debayer8_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
}
}
-template<bool addAlphaByte>
+template<bool addAlphaByte, bool ccmEnabled>
void DebayerCpu::debayer8_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
{
DECLARE_SRC_POINTERS(uint8_t)
@@ -136,7 +154,7 @@ void DebayerCpu::debayer8_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
}
}
-template<bool addAlphaByte>
+template<bool addAlphaByte, bool ccmEnabled>
void DebayerCpu::debayer10_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
{
DECLARE_SRC_POINTERS(uint16_t)
@@ -148,7 +166,7 @@ void DebayerCpu::debayer10_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
}
}
-template<bool addAlphaByte>
+template<bool addAlphaByte, bool ccmEnabled>
void DebayerCpu::debayer10_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
{
DECLARE_SRC_POINTERS(uint16_t)
@@ -160,7 +178,7 @@ void DebayerCpu::debayer10_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
}
}
-template<bool addAlphaByte>
+template<bool addAlphaByte, bool ccmEnabled>
void DebayerCpu::debayer12_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
{
DECLARE_SRC_POINTERS(uint16_t)
@@ -172,7 +190,7 @@ void DebayerCpu::debayer12_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
}
}
-template<bool addAlphaByte>
+template<bool addAlphaByte, bool ccmEnabled>
void DebayerCpu::debayer12_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
{
DECLARE_SRC_POINTERS(uint16_t)
@@ -184,7 +202,7 @@ void DebayerCpu::debayer12_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
}
}
-template<bool addAlphaByte>
+template<bool addAlphaByte, bool ccmEnabled>
void DebayerCpu::debayer10P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
{
const int widthInBytes = window_.width * 5 / 4;
@@ -210,7 +228,7 @@ void DebayerCpu::debayer10P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
}
}
-template<bool addAlphaByte>
+template<bool addAlphaByte, bool ccmEnabled>
void DebayerCpu::debayer10P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
{
const int widthInBytes = window_.width * 5 / 4;
@@ -231,7 +249,7 @@ void DebayerCpu::debayer10P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
}
}
-template<bool addAlphaByte>
+template<bool addAlphaByte, bool ccmEnabled>
void DebayerCpu::debayer10P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[])
{
const int widthInBytes = window_.width * 5 / 4;
@@ -252,7 +270,7 @@ void DebayerCpu::debayer10P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[])
}
}
-template<bool addAlphaByte>
+template<bool addAlphaByte, bool ccmEnabled>
void DebayerCpu::debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[])
{
const int widthInBytes = window_.width * 5 / 4;
@@ -368,7 +386,17 @@ int DebayerCpu::setupStandardBayerOrder(BayerFormat::Order order)
return 0;
}
-int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputFormat)
+#define SET_DEBAYER_METHODS(method0, method1) \
+ debayer0_ = addAlphaByte \
+ ? (ccmEnabled ? &DebayerCpu::method0<true, true> : &DebayerCpu::method0<true, false>) \
+ : (ccmEnabled ? &DebayerCpu::method0<false, true> : &DebayerCpu::method0<false, false>); \
+ debayer1_ = addAlphaByte \
+ ? (ccmEnabled ? &DebayerCpu::method1<true, true> : &DebayerCpu::method1<true, false>) \
+ : (ccmEnabled ? &DebayerCpu::method1<false, true> : &DebayerCpu::method1<false, false>);
+
+int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat,
+ PixelFormat outputFormat,
+ bool ccmEnabled)
{
BayerFormat bayerFormat =
BayerFormat::fromPixelFormat(inputFormat);
@@ -423,16 +451,13 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputF
isStandardBayerOrder(bayerFormat.order)) {
switch (bayerFormat.bitDepth) {
case 8:
- debayer0_ = addAlphaByte ? &DebayerCpu::debayer8_BGBG_BGR888<true> : &DebayerCpu::debayer8_BGBG_BGR888<false>;
- debayer1_ = addAlphaByte ? &DebayerCpu::debayer8_GRGR_BGR888<true> : &DebayerCpu::debayer8_GRGR_BGR888<false>;
+ SET_DEBAYER_METHODS(debayer8_BGBG_BGR888, debayer8_GRGR_BGR888)
break;
case 10:
- debayer0_ = addAlphaByte ? &DebayerCpu::debayer10_BGBG_BGR888<true> : &DebayerCpu::debayer10_BGBG_BGR888<false>;
- debayer1_ = addAlphaByte ? &DebayerCpu::debayer10_GRGR_BGR888<true> : &DebayerCpu::debayer10_GRGR_BGR888<false>;
+ SET_DEBAYER_METHODS(debayer10_BGBG_BGR888, debayer10_GRGR_BGR888)
break;
case 12:
- debayer0_ = addAlphaByte ? &DebayerCpu::debayer12_BGBG_BGR888<true> : &DebayerCpu::debayer12_BGBG_BGR888<false>;
- debayer1_ = addAlphaByte ? &DebayerCpu::debayer12_GRGR_BGR888<true> : &DebayerCpu::debayer12_GRGR_BGR888<false>;
+ SET_DEBAYER_METHODS(debayer12_BGBG_BGR888, debayer12_GRGR_BGR888)
break;
}
setupStandardBayerOrder(bayerFormat.order);
@@ -443,20 +468,16 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputF
bayerFormat.packing == BayerFormat::Packing::CSI2) {
switch (bayerFormat.order) {
case BayerFormat::BGGR:
- debayer0_ = addAlphaByte ? &DebayerCpu::debayer10P_BGBG_BGR888<true> : &DebayerCpu::debayer10P_BGBG_BGR888<false>;
- debayer1_ = addAlphaByte ? &DebayerCpu::debayer10P_GRGR_BGR888<true> : &DebayerCpu::debayer10P_GRGR_BGR888<false>;
+ SET_DEBAYER_METHODS(debayer10P_BGBG_BGR888, debayer10P_GRGR_BGR888)
return 0;
case BayerFormat::GBRG:
- debayer0_ = addAlphaByte ? &DebayerCpu::debayer10P_GBGB_BGR888<true> : &DebayerCpu::debayer10P_GBGB_BGR888<false>;
- debayer1_ = addAlphaByte ? &DebayerCpu::debayer10P_RGRG_BGR888<true> : &DebayerCpu::debayer10P_RGRG_BGR888<false>;
+ SET_DEBAYER_METHODS(debayer10P_GBGB_BGR888, debayer10P_RGRG_BGR888)
return 0;
case BayerFormat::GRBG:
- debayer0_ = addAlphaByte ? &DebayerCpu::debayer10P_GRGR_BGR888<true> : &DebayerCpu::debayer10P_GRGR_BGR888<false>;
- debayer1_ = addAlphaByte ? &DebayerCpu::debayer10P_BGBG_BGR888<true> : &DebayerCpu::debayer10P_BGBG_BGR888<false>;
+ SET_DEBAYER_METHODS(debayer10P_GRGR_BGR888, debayer10P_BGBG_BGR888)
return 0;
case BayerFormat::RGGB:
- debayer0_ = addAlphaByte ? &DebayerCpu::debayer10P_RGRG_BGR888<true> : &DebayerCpu::debayer10P_RGRG_BGR888<false>;
- debayer1_ = addAlphaByte ? &DebayerCpu::debayer10P_GBGB_BGR888<true> : &DebayerCpu::debayer10P_GBGB_BGR888<false>;
+ SET_DEBAYER_METHODS(debayer10P_RGRG_BGR888, debayer10P_GBGB_BGR888)
return 0;
default:
break;
@@ -467,7 +488,8 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputF
}
int DebayerCpu::configure(const StreamConfiguration &inputCfg,
- const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs)
+ const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs,
+ bool ccmEnabled)
{
if (getInputConfig(inputCfg.pixelFormat, inputConfig_) != 0)
return -EINVAL;
@@ -506,7 +528,10 @@ int DebayerCpu::configure(const StreamConfiguration &inputCfg,
return -EINVAL;
}
- if (setDebayerFunctions(inputCfg.pixelFormat, outputCfg.pixelFormat) != 0)
+ int ret = setDebayerFunctions(inputCfg.pixelFormat,
+ outputCfg.pixelFormat,
+ ccmEnabled);
+ if (ret != 0)
return -EINVAL;
window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) &
@@ -748,8 +773,23 @@ void DebayerCpu::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output
dmaSyncers.emplace_back(plane.fd, DmaSyncer::SyncType::Write);
green_ = params.green;
- red_ = swapRedBlueGains_ ? params.blue : params.red;
- blue_ = swapRedBlueGains_ ? params.red : params.blue;
+ greenCcm_ = params.greenCcm;
+ if (swapRedBlueGains_) {
+ red_ = params.blue;
+ blue_ = params.red;
+ redCcm_ = params.blueCcm;
+ blueCcm_ = params.redCcm;
+ for (unsigned int i = 0; i < 256; i++) {
+ std::swap(redCcm_[i].r, redCcm_[i].b);
+ std::swap(blueCcm_[i].r, blueCcm_[i].b);
+ }
+ } else {
+ red_ = params.red;
+ blue_ = params.blue;
+ redCcm_ = params.redCcm;
+ blueCcm_ = params.blueCcm;
+ }
+ gammaLut_ = params.gammaLut;
/* Copy metadata from the input buffer */
FrameMetadata &metadata = output->_d()->metadata();
diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h
index 2c47e7c6..926195e9 100644
--- a/src/libcamera/software_isp/debayer_cpu.h
+++ b/src/libcamera/software_isp/debayer_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-2025 Red Hat Inc.
*
* Authors:
* Hans de Goede <hdegoede@redhat.com>
@@ -31,7 +31,8 @@ public:
~DebayerCpu();
int configure(const StreamConfiguration &inputCfg,
- const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs);
+ const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs,
+ bool ccmEnabled);
Size patternSize(PixelFormat inputFormat);
std::vector<PixelFormat> formats(PixelFormat input);
std::tuple<unsigned int, unsigned int>
@@ -85,28 +86,28 @@ private:
using debayerFn = void (DebayerCpu::*)(uint8_t *dst, const uint8_t *src[]);
/* 8-bit raw bayer format */
- template<bool addAlphaByte>
+ template<bool addAlphaByte, bool ccmEnabled>
void debayer8_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]);
- template<bool addAlphaByte>
+ template<bool addAlphaByte, bool ccmEnabled>
void debayer8_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]);
/* unpacked 10-bit raw bayer format */
- template<bool addAlphaByte>
+ template<bool addAlphaByte, bool ccmEnabled>
void debayer10_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]);
- template<bool addAlphaByte>
+ template<bool addAlphaByte, bool ccmEnabled>
void debayer10_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]);
/* unpacked 12-bit raw bayer format */
- template<bool addAlphaByte>
+ template<bool addAlphaByte, bool ccmEnabled>
void debayer12_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]);
- template<bool addAlphaByte>
+ template<bool addAlphaByte, bool ccmEnabled>
void debayer12_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]);
/* CSI-2 packed 10-bit raw bayer format (all the 4 orders) */
- template<bool addAlphaByte>
+ template<bool addAlphaByte, bool ccmEnabled>
void debayer10P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]);
- template<bool addAlphaByte>
+ template<bool addAlphaByte, bool ccmEnabled>
void debayer10P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]);
- template<bool addAlphaByte>
+ template<bool addAlphaByte, bool ccmEnabled>
void debayer10P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[]);
- template<bool addAlphaByte>
+ template<bool addAlphaByte, bool ccmEnabled>
void debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]);
struct DebayerInputConfig {
@@ -125,7 +126,9 @@ private:
int getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config);
int getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config);
int setupStandardBayerOrder(BayerFormat::Order order);
- int setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputFormat);
+ int setDebayerFunctions(PixelFormat inputFormat,
+ PixelFormat outputFormat,
+ bool ccmEnabled);
void setupInputMemcpy(const uint8_t *linePointers[]);
void shiftLinePointers(const uint8_t *linePointers[], const uint8_t *src);
void memcpyNextLine(const uint8_t *linePointers[]);
@@ -135,9 +138,13 @@ private:
/* Max. supported Bayer pattern height is 4, debayering this requires 5 lines */
static constexpr unsigned int kMaxLineBuffers = 5;
- DebayerParams::ColorLookupTable red_;
- DebayerParams::ColorLookupTable green_;
- DebayerParams::ColorLookupTable blue_;
+ DebayerParams::LookupTable red_;
+ DebayerParams::LookupTable green_;
+ DebayerParams::LookupTable blue_;
+ DebayerParams::CcmLookupTable redCcm_;
+ DebayerParams::CcmLookupTable greenCcm_;
+ DebayerParams::CcmLookupTable blueCcm_;
+ DebayerParams::LookupTable gammaLut_;
debayerFn debayer0_;
debayerFn debayer1_;
debayerFn debayer2_;
diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
index a64e6b2c..28e2a360 100644
--- a/src/libcamera/software_isp/software_isp.cpp
+++ b/src/libcamera/software_isp/software_isp.cpp
@@ -56,6 +56,11 @@ LOG_DEFINE_CATEGORY(SoftwareIsp)
*/
/**
+ * \var SoftwareIsp::metadataReady
+ * \brief A signal emitted when the metadata for IPA is ready
+ */
+
+/**
* \var SoftwareIsp::setSensorControls
* \brief A signal emitted when the values to write to the sensor controls are
* ready
@@ -128,11 +133,20 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,
std::string ipaTuningFile =
ipa_->configurationFile(sensor->model() + ".yaml", "uncalibrated.yaml");
- int ret = ipa_->init(IPASettings{ ipaTuningFile, sensor->model() },
- debayer_->getStatsFD(),
- sharedParams_.fd(),
- sensor->controls(),
- ipaControls);
+ IPACameraSensorInfo sensorInfo{};
+ int ret = sensor->sensorInfo(&sensorInfo);
+ if (ret) {
+ LOG(SoftwareIsp, Error) << "Camera sensor information not available";
+ return;
+ }
+
+ ret = ipa_->init(IPASettings{ ipaTuningFile, sensor->model() },
+ debayer_->getStatsFD(),
+ sharedParams_.fd(),
+ sensorInfo,
+ sensor->controls(),
+ ipaControls,
+ &ccmEnabled_);
if (ret) {
LOG(SoftwareIsp, Error) << "IPA init failed";
debayer_.reset();
@@ -140,6 +154,10 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,
}
ipa_->setIspParams.connect(this, &SoftwareIsp::saveIspParams);
+ ipa_->metadataReady.connect(this,
+ [this](uint32_t frame, const ControlList &metadata) {
+ metadataReady.emit(frame, metadata);
+ });
ipa_->setSensorControls.connect(this, &SoftwareIsp::setSensorCtrls);
debayer_->moveToThread(&ispWorkerThread_);
@@ -244,7 +262,7 @@ int SoftwareIsp::configure(const StreamConfiguration &inputCfg,
if (ret < 0)
return ret;
- return debayer_->configure(inputCfg, outputCfgs);
+ return debayer_->configure(inputCfg, outputCfgs, ccmEnabled_);
}
/**