diff options
43 files changed, 732 insertions, 238 deletions
diff --git a/include/libcamera/base/mutex.h b/include/libcamera/base/mutex.h index fa9a8d0d..20ebe6fe 100644 --- a/include/libcamera/base/mutex.h +++ b/include/libcamera/base/mutex.h @@ -23,10 +23,6 @@ namespace libcamera { class LIBCAMERA_TSA_CAPABILITY("mutex") Mutex final { public: - constexpr Mutex() - { - } - void lock() LIBCAMERA_TSA_ACQUIRE() { mutex_.lock(); @@ -84,10 +80,6 @@ private: class ConditionVariable final { public: - ConditionVariable() - { - } - void notify_one() noexcept { cv_.notify_one(); diff --git a/include/libcamera/base/span.h b/include/libcamera/base/span.h index 92cce4f0..806db106 100644 --- a/include/libcamera/base/span.h +++ b/include/libcamera/base/span.h @@ -346,13 +346,7 @@ public: } constexpr Span(const Span &other) noexcept = default; - - constexpr Span &operator=(const Span &other) noexcept - { - data_ = other.data_; - size_ = other.size_; - return *this; - } + constexpr Span &operator=(const Span &other) noexcept = default; constexpr iterator begin() const { return data(); } constexpr const_iterator cbegin() const { return begin(); } diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h index 7c7828ae..4bfe9615 100644 --- a/include/libcamera/controls.h +++ b/include/libcamera/controls.h @@ -125,7 +125,7 @@ struct control_type<Span<T, N>> : public control_type<std::remove_cv_t<T>> { }; template<typename T> -struct control_type<T, std::enable_if_t<std::is_enum_v<T>>> : public control_type<int32_t> { +struct control_type<T, std::enable_if_t<std::is_enum_v<T> && sizeof(T) == sizeof(int32_t)>> : public control_type<int32_t> { }; } /* namespace details */ diff --git a/include/libcamera/internal/ipa_manager.h b/include/libcamera/internal/ipa_manager.h index 16dede0c..a0d448cf 100644 --- a/include/libcamera/internal/ipa_manager.h +++ b/include/libcamera/internal/ipa_manager.h @@ -7,6 +7,7 @@ #pragma once +#include <memory> #include <stdint.h> #include <vector> @@ -67,7 +68,7 @@ private: bool isSignatureValid(IPAModule *ipa) const; - std::vector<IPAModule *> modules_; + std::vector<std::unique_ptr<IPAModule>> modules_; #if HAVE_IPA_PUBKEY static const uint8_t publicKeyData_[]; diff --git a/include/libcamera/internal/media_object.h b/include/libcamera/internal/media_object.h index 9356d204..54e2e5ce 100644 --- a/include/libcamera/internal/media_object.h +++ b/include/libcamera/internal/media_object.h @@ -112,7 +112,7 @@ public: unsigned int deviceMinor() const { return minor_; } const std::vector<MediaPad *> &pads() const { return pads_; } - const std::vector<MediaEntity *> ancillaryEntities() const { return ancillaryEntities_; } + const std::vector<MediaEntity *> &ancillaryEntities() const { return ancillaryEntities_; } const MediaPad *getPadByIndex(unsigned int index) const; const MediaPad *getPadById(unsigned int id) const; diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h index 7d8fdd48..217cd5d9 100644 --- a/include/libcamera/internal/software_isp/debayer_params.h +++ b/include/libcamera/internal/software_isp/debayer_params.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ /* - * Copyright (C) 2023, 2024 Red Hat Inc. + * Copyright (C) 2023-2025 Red Hat Inc. * * Authors: * Hans de Goede <hdegoede@redhat.com> @@ -18,11 +18,37 @@ namespace libcamera { struct DebayerParams { static constexpr unsigned int kRGBLookupSize = 256; - using ColorLookupTable = std::array<uint8_t, kRGBLookupSize>; + struct CcmColumn { + int16_t r; + int16_t g; + int16_t b; + }; - ColorLookupTable red; - ColorLookupTable green; - ColorLookupTable blue; + using LookupTable = std::array<uint8_t, kRGBLookupSize>; + using CcmLookupTable = std::array<CcmColumn, kRGBLookupSize>; + + /* + * Color lookup tables when CCM is not used. + * + * Each color of a debayered pixel is amended by the corresponding + * value in the given table. + */ + LookupTable red; + LookupTable green; + LookupTable blue; + + /* + * Color and gamma lookup tables when CCM is used. + * + * Each of the CcmLookupTable's corresponds to a CCM column; together they + * make a complete 3x3 CCM lookup table. The CCM is applied on debayered + * pixels and then the gamma lookup table is used to set the resulting + * values of all the three colors. + */ + CcmLookupTable redCcm; + CcmLookupTable greenCcm; + CcmLookupTable blueCcm; + LookupTable gammaLut; }; } /* namespace libcamera */ diff --git a/include/libcamera/internal/software_isp/software_isp.h b/include/libcamera/internal/software_isp/software_isp.h index 133b545c..78624659 100644 --- a/include/libcamera/internal/software_isp/software_isp.h +++ b/include/libcamera/internal/software_isp/software_isp.h @@ -85,6 +85,7 @@ public: Signal<FrameBuffer *> inputBufferReady; Signal<FrameBuffer *> outputBufferReady; Signal<uint32_t, uint32_t> ispStatsReady; + Signal<uint32_t, const ControlList &> metadataReady; Signal<const ControlList &> setSensorControls; private: @@ -99,6 +100,7 @@ private: SharedMemObject<DebayerParams> sharedParams_; DebayerParams debayerParams_; DmaBufAllocator dmaHeap_; + bool ccmEnabled_; std::unique_ptr<ipa::soft::IPAProxySoft> ipa_; std::deque<FrameBuffer *> queuedInputBuffers_; diff --git a/include/libcamera/internal/v4l2_videodevice.h b/include/libcamera/internal/v4l2_videodevice.h index f021c2a0..ae6a76cb 100644 --- a/include/libcamera/internal/v4l2_videodevice.h +++ b/include/libcamera/internal/v4l2_videodevice.h @@ -8,7 +8,6 @@ #pragma once #include <array> -#include <atomic> #include <memory> #include <optional> #include <ostream> @@ -158,7 +157,7 @@ private: std::vector<Plane> planes_; }; - std::atomic<uint64_t> lastUsedCounter_; + uint64_t lastUsedCounter_; std::vector<Entry> cache_; /* \todo Expose the miss counter through an instrumentation API. */ unsigned int missCounter_; diff --git a/include/libcamera/ipa/soft.mojom b/include/libcamera/ipa/soft.mojom index d52e6f1a..77328c5f 100644 --- a/include/libcamera/ipa/soft.mojom +++ b/include/libcamera/ipa/soft.mojom @@ -16,8 +16,9 @@ interface IPASoftInterface { init(libcamera.IPASettings settings, libcamera.SharedFD fdStats, libcamera.SharedFD fdParams, - libcamera.ControlInfoMap sensorCtrlInfoMap) - => (int32 ret, libcamera.ControlInfoMap ipaControls); + libcamera.IPACameraSensorInfo sensorInfo, + libcamera.ControlInfoMap sensorControls) + => (int32 ret, libcamera.ControlInfoMap ipaControls, bool ccmEnabled); start() => (int32 ret); stop(); configure(IPAConfigInfo configInfo) @@ -33,4 +34,5 @@ interface IPASoftInterface { interface IPASoftEventInterface { setSensorControls(libcamera.ControlList sensorControls); setIspParams(); + metadataReady(uint32 frame, libcamera.ControlList metadata); }; diff --git a/src/apps/cam/camera_session.cpp b/src/apps/cam/camera_session.cpp index 9e934827..97c1ae44 100644 --- a/src/apps/cam/camera_session.cpp +++ b/src/apps/cam/camera_session.cpp @@ -5,9 +5,12 @@ * Camera capture session */ +#include "camera_session.h" + #include <iomanip> #include <iostream> #include <limits.h> +#include <optional> #include <sstream> #include <libcamera/control_ids.h> @@ -16,7 +19,6 @@ #include "../common/event_loop.h" #include "../common/stream_options.h" -#include "camera_session.h" #include "capture_script.h" #include "file_sink.h" #ifdef HAVE_KMS @@ -173,6 +175,11 @@ void CameraSession::listControls() const std::cout << "Control: " << io.str() << id->vendor() << "::" << id->name() << ":" << std::endl; + + std::optional<int32_t> def; + if (!info.def().isNone()) + def = info.def().get<int32_t>(); + for (const auto &value : info.values()) { int32_t val = value.get<int32_t>(); const auto &it = id->enumerators().find(val); @@ -182,7 +189,10 @@ void CameraSession::listControls() const std::cout << "UNKNOWN"; else std::cout << it->second; - std::cout << " (" << val << ")" << std::endl; + + std::cout << " (" << val << ")" + << (val == def ? " [default]" : "") + << std::endl; } } diff --git a/src/apps/cam/file_sink.cpp b/src/apps/cam/file_sink.cpp index 76e21db9..65794a2f 100644 --- a/src/apps/cam/file_sink.cpp +++ b/src/apps/cam/file_sink.cpp @@ -5,6 +5,8 @@ * File Sink */ +#include "file_sink.h" + #include <array> #include <assert.h> #include <fcntl.h> @@ -21,8 +23,6 @@ #include "../common/image.h" #include "../common/ppm_writer.h" -#include "file_sink.h" - using namespace libcamera; FileSink::FileSink([[maybe_unused]] const libcamera::Camera *camera, diff --git a/src/apps/cam/file_sink.h b/src/apps/cam/file_sink.h index 71b7fe0f..26cd61b3 100644 --- a/src/apps/cam/file_sink.h +++ b/src/apps/cam/file_sink.h @@ -11,6 +11,7 @@ #include <memory> #include <string> +#include <libcamera/controls.h> #include <libcamera/stream.h> #include "frame_sink.h" diff --git a/src/apps/cam/main.cpp b/src/apps/cam/main.cpp index 460dbc81..fa266eca 100644 --- a/src/apps/cam/main.cpp +++ b/src/apps/cam/main.cpp @@ -5,6 +5,8 @@ * cam - The libcamera swiss army knife */ +#include "main.h" + #include <atomic> #include <iomanip> #include <iostream> @@ -19,7 +21,6 @@ #include "../common/stream_options.h" #include "camera_session.h" -#include "main.h" using namespace libcamera; diff --git a/src/apps/qcam/main_window.cpp b/src/apps/qcam/main_window.cpp index 3880a846..d2ccbd23 100644 --- a/src/apps/qcam/main_window.cpp +++ b/src/apps/qcam/main_window.cpp @@ -386,10 +386,7 @@ int MainWindow::startCapture() /* Use a format supported by the viewfinder if available. */ std::vector<PixelFormat> formats = vfConfig.formats().pixelformats(); for (const PixelFormat &format : viewfinder_->nativeFormats()) { - auto match = std::find_if(formats.begin(), formats.end(), - [&](const PixelFormat &f) { - return f == format; - }); + auto match = std::find(formats.begin(), formats.end(), format); if (match != formats.end()) { vfConfig.pixelFormat = format; break; 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 0ba2391f..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 */ @@ -14,9 +14,9 @@ #include <libcamera/base/log.h> -#include "simple/ipa_context.h" +#include <libcamera/control_ids.h> -#include "control_ids.h" +#include "simple/ipa_context.h" namespace libcamera { @@ -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.cpp b/src/libcamera/camera.cpp index 56c58519..c180a5fd 100644 --- a/src/libcamera/camera.cpp +++ b/src/libcamera/camera.cpp @@ -21,6 +21,7 @@ #include <libcamera/color_space.h> #include <libcamera/control_ids.h> #include <libcamera/framebuffer_allocator.h> +#include <libcamera/property_ids.h> #include <libcamera/request.h> #include <libcamera/stream.h> @@ -587,7 +588,8 @@ CameraConfiguration::Status CameraConfiguration::validateColorSpaces(ColorSpaceF * \param[in] pipe The pipeline handler responsible for the camera device */ Camera::Private::Private(PipelineHandler *pipe) - : requestSequence_(0), pipe_(pipe->shared_from_this()), + : controlInfo_({}, controls::controls), properties_(properties::properties), + requestSequence_(0), pipe_(pipe->shared_from_this()), disconnected_(false), state_(CameraAvailable) { } 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/ipa_manager.cpp b/src/libcamera/ipa_manager.cpp index cfc24d38..830750dc 100644 --- a/src/libcamera/ipa_manager.cpp +++ b/src/libcamera/ipa_manager.cpp @@ -149,11 +149,7 @@ IPAManager::IPAManager() << "No IPA found in '" IPA_MODULE_DIR "'"; } -IPAManager::~IPAManager() -{ - for (IPAModule *module : modules_) - delete module; -} +IPAManager::~IPAManager() = default; /** * \brief Identify shared library objects within a directory @@ -226,15 +222,13 @@ unsigned int IPAManager::addDir(const char *libDir, unsigned int maxDepth) unsigned int count = 0; for (const std::string &file : files) { - IPAModule *ipaModule = new IPAModule(file); - if (!ipaModule->isValid()) { - delete ipaModule; + auto ipaModule = std::make_unique<IPAModule>(file); + if (!ipaModule->isValid()) continue; - } LOG(IPAManager, Debug) << "Loaded IPA module '" << file << "'"; - modules_.push_back(ipaModule); + modules_.push_back(std::move(ipaModule)); count++; } @@ -250,9 +244,9 @@ unsigned int IPAManager::addDir(const char *libDir, unsigned int maxDepth) IPAModule *IPAManager::module(PipelineHandler *pipe, uint32_t minVersion, uint32_t maxVersion) { - for (IPAModule *module : modules_) { + for (const auto &module : modules_) { if (module->match(pipe, minVersion, maxVersion)) - return module; + return module.get(); } return nullptr; diff --git a/src/libcamera/media_device.cpp b/src/libcamera/media_device.cpp index d71dad74..75abd91d 100644 --- a/src/libcamera/media_device.cpp +++ b/src/libcamera/media_device.cpp @@ -13,6 +13,7 @@ #include <string> #include <string.h> #include <sys/ioctl.h> +#include <tuple> #include <unistd.h> #include <vector> @@ -164,7 +165,7 @@ void MediaDevice::unlock() if (!fd_.isValid()) return; - lockf(fd_.get(), F_ULOCK, 0); + std::ignore = lockf(fd_.get(), F_ULOCK, 0); } /** 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_); } /** diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp index e241eb47..f5b3fa09 100644 --- a/src/libcamera/v4l2_videodevice.cpp +++ b/src/libcamera/v4l2_videodevice.cpp @@ -190,7 +190,7 @@ V4L2BufferCache::V4L2BufferCache(const std::vector<std::unique_ptr<FrameBuffer>> { for (const std::unique_ptr<FrameBuffer> &buffer : buffers) cache_.emplace_back(true, - lastUsedCounter_.fetch_add(1, std::memory_order_acq_rel), + lastUsedCounter_++, *buffer.get()); } @@ -258,7 +258,7 @@ int V4L2BufferCache::get(const FrameBuffer &buffer) return -ENOENT; cache_[use] = Entry(false, - lastUsedCounter_.fetch_add(1, std::memory_order_acq_rel), + lastUsedCounter_++, buffer); return use; diff --git a/test/threads.cpp b/test/threads.cpp index 8d6ee151..c00d95a4 100644 --- a/test/threads.cpp +++ b/test/threads.cpp @@ -52,14 +52,8 @@ protected: { cancelled_ = true; - /* - * Cancel the thread and call a guaranteed cancellation point - * (nanosleep). - */ pthread_cancel(pthread_self()); - - struct timespec req{ 0, 100*000*000 }; - nanosleep(&req, nullptr); + pthread_testcancel(); cancelled_ = false; } |