From 726e9274ea95fa46352556d340c5793a8da51fcd Mon Sep 17 00:00:00 2001 From: Naushir Patuck Date: Wed, 3 May 2023 13:20:27 +0100 Subject: pipeline: ipa: raspberrypi: Refactor and move the Raspberry Pi code Split the Raspberry Pi pipeline handler and IPA source code into common and VC4/BCM2835 specific file structures. For the pipeline handler, the common code files now live in src/libcamera/pipeline/rpi/common/ and the VC4-specific files in src/libcamera/pipeline/rpi/vc4/. For the IPA, the common code files now live in src/ipa/rpi/{cam_helper,controller}/ and the vc4 specific files in src/ipa/rpi/vc4/. With this change, the camera tuning files are now installed under share/libcamera/ipa/rpi/vc4/. To build the pipeline and IPA, the meson configuration options have now changed from "raspberrypi" to "rpi/vc4": meson setup build -Dipas=rpi/vc4 -Dpipelines=rpi/vc4 Signed-off-by: Naushir Patuck Reviewed-by: Jacopo Mondi Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- src/ipa/rpi/vc4/raspberrypi.cpp | 1853 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 1853 insertions(+) create mode 100644 src/ipa/rpi/vc4/raspberrypi.cpp (limited to 'src/ipa/rpi/vc4/raspberrypi.cpp') diff --git a/src/ipa/rpi/vc4/raspberrypi.cpp b/src/ipa/rpi/vc4/raspberrypi.cpp new file mode 100644 index 00000000..5d3bf4ca --- /dev/null +++ b/src/ipa/rpi/vc4/raspberrypi.cpp @@ -0,0 +1,1853 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (C) 2019-2021, Raspberry Pi Ltd + * + * rpi.cpp - Raspberry Pi Image Processing Algorithms + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "libcamera/internal/mapped_framebuffer.h" + +#include "cam_helper/cam_helper.h" +#include "controller/af_algorithm.h" +#include "controller/af_status.h" +#include "controller/agc_algorithm.h" +#include "controller/agc_status.h" +#include "controller/alsc_status.h" +#include "controller/awb_algorithm.h" +#include "controller/awb_status.h" +#include "controller/black_level_status.h" +#include "controller/ccm_algorithm.h" +#include "controller/ccm_status.h" +#include "controller/contrast_algorithm.h" +#include "controller/contrast_status.h" +#include "controller/controller.h" +#include "controller/denoise_algorithm.h" +#include "controller/denoise_status.h" +#include "controller/dpc_status.h" +#include "controller/geq_status.h" +#include "controller/lux_status.h" +#include "controller/metadata.h" +#include "controller/sharpen_algorithm.h" +#include "controller/sharpen_status.h" +#include "controller/statistics.h" + +namespace libcamera { + +using namespace std::literals::chrono_literals; +using utils::Duration; + +/* Number of metadata objects available in the context list. */ +constexpr unsigned int numMetadataContexts = 16; + +/* Number of frame length times to hold in the queue. */ +constexpr unsigned int FrameLengthsQueueSize = 10; + +/* Configure the sensor with these values initially. */ +constexpr double defaultAnalogueGain = 1.0; +constexpr Duration defaultExposureTime = 20.0ms; +constexpr Duration defaultMinFrameDuration = 1.0s / 30.0; +constexpr Duration defaultMaxFrameDuration = 250.0s; + +/* + * Determine the minimum allowable inter-frame duration to run the controller + * algorithms. If the pipeline handler provider frames at a rate higher than this, + * we rate-limit the controller Prepare() and Process() calls to lower than or + * equal to this rate. + */ +constexpr Duration controllerMinFrameDuration = 1.0s / 30.0; + +/* List of controls handled by the Raspberry Pi IPA */ +static const ControlInfoMap::Map ipaControls{ + { &controls::AeEnable, ControlInfo(false, true) }, + { &controls::ExposureTime, ControlInfo(0, 66666) }, + { &controls::AnalogueGain, ControlInfo(1.0f, 16.0f) }, + { &controls::AeMeteringMode, ControlInfo(controls::AeMeteringModeValues) }, + { &controls::AeConstraintMode, ControlInfo(controls::AeConstraintModeValues) }, + { &controls::AeExposureMode, ControlInfo(controls::AeExposureModeValues) }, + { &controls::ExposureValue, ControlInfo(-8.0f, 8.0f, 0.0f) }, + { &controls::AwbEnable, ControlInfo(false, true) }, + { &controls::ColourGains, ControlInfo(0.0f, 32.0f) }, + { &controls::AwbMode, ControlInfo(controls::AwbModeValues) }, + { &controls::Brightness, ControlInfo(-1.0f, 1.0f, 0.0f) }, + { &controls::Contrast, ControlInfo(0.0f, 32.0f, 1.0f) }, + { &controls::Saturation, ControlInfo(0.0f, 32.0f, 1.0f) }, + { &controls::Sharpness, ControlInfo(0.0f, 16.0f, 1.0f) }, + { &controls::ColourCorrectionMatrix, ControlInfo(-16.0f, 16.0f) }, + { &controls::ScalerCrop, ControlInfo(Rectangle{}, Rectangle(65535, 65535, 65535, 65535), Rectangle{}) }, + { &controls::FrameDurationLimits, ControlInfo(INT64_C(33333), INT64_C(120000)) }, + { &controls::draft::NoiseReductionMode, ControlInfo(controls::draft::NoiseReductionModeValues) } +}; + +/* IPA controls handled conditionally, if the lens has a focus control */ +static const ControlInfoMap::Map ipaAfControls{ + { &controls::AfMode, ControlInfo(controls::AfModeValues) }, + { &controls::AfRange, ControlInfo(controls::AfRangeValues) }, + { &controls::AfSpeed, ControlInfo(controls::AfSpeedValues) }, + { &controls::AfMetering, ControlInfo(controls::AfMeteringValues) }, + { &controls::AfWindows, ControlInfo(Rectangle{}, Rectangle(65535, 65535, 65535, 65535), Rectangle{}) }, + { &controls::AfTrigger, ControlInfo(controls::AfTriggerValues) }, + { &controls::AfPause, ControlInfo(controls::AfPauseValues) }, + { &controls::LensPosition, ControlInfo(0.0f, 32.0f, 1.0f) } +}; + +LOG_DEFINE_CATEGORY(IPARPI) + +namespace ipa::RPi { + +class IPARPi : public IPARPiInterface +{ +public: + IPARPi() + : controller_(), frameCount_(0), checkCount_(0), mistrustCount_(0), + lastRunTimestamp_(0), lsTable_(nullptr), firstStart_(true), + lastTimeout_(0s) + { + } + + ~IPARPi() + { + if (lsTable_) + munmap(lsTable_, MaxLsGridSize); + } + + int init(const IPASettings &settings, bool lensPresent, IPAInitResult *result) override; + void start(const ControlList &controls, StartConfig *startConfig) override; + void stop() override {} + + int configure(const IPACameraSensorInfo &sensorInfo, const IPAConfig &data, + ControlList *controls, IPAConfigResult *result) override; + void mapBuffers(const std::vector &buffers) override; + void unmapBuffers(const std::vector &ids) override; + void signalStatReady(const uint32_t bufferId, uint32_t ipaContext) override; + void signalQueueRequest(const ControlList &controls) override; + void signalIspPrepare(const ISPConfig &data) override; + +private: + void setMode(const IPACameraSensorInfo &sensorInfo); + bool validateSensorControls(); + bool validateIspControls(); + bool validateLensControls(); + void queueRequest(const ControlList &controls); + void returnEmbeddedBuffer(unsigned int bufferId); + void prepareISP(const ISPConfig &data); + void reportMetadata(unsigned int ipaContext); + void fillDeviceStatus(const ControlList &sensorControls, unsigned int ipaContext); + RPiController::StatisticsPtr fillStatistics(bcm2835_isp_stats *stats) const; + void processStats(unsigned int bufferId, unsigned int ipaContext); + void setCameraTimeoutValue(); + void applyFrameDurations(Duration minFrameDuration, Duration maxFrameDuration); + void applyAGC(const struct AgcStatus *agcStatus, ControlList &ctrls); + void applyAWB(const struct AwbStatus *awbStatus, ControlList &ctrls); + void applyDG(const struct AgcStatus *dgStatus, ControlList &ctrls); + void applyCCM(const struct CcmStatus *ccmStatus, ControlList &ctrls); + void applyBlackLevel(const struct BlackLevelStatus *blackLevelStatus, ControlList &ctrls); + void applyGamma(const struct ContrastStatus *contrastStatus, ControlList &ctrls); + void applyGEQ(const struct GeqStatus *geqStatus, ControlList &ctrls); + void applyDenoise(const struct DenoiseStatus *denoiseStatus, ControlList &ctrls); + void applySharpen(const struct SharpenStatus *sharpenStatus, ControlList &ctrls); + void applyDPC(const struct DpcStatus *dpcStatus, ControlList &ctrls); + void applyLS(const struct AlscStatus *lsStatus, ControlList &ctrls); + void applyAF(const struct AfStatus *afStatus, ControlList &lensCtrls); + void resampleTable(uint16_t dest[], const std::vector &src, int destW, int destH); + + std::map buffers_; + + ControlInfoMap sensorCtrls_; + ControlInfoMap ispCtrls_; + ControlInfoMap lensCtrls_; + bool lensPresent_; + ControlList libcameraMetadata_; + + /* Camera sensor params. */ + CameraMode mode_; + + /* Raspberry Pi controller specific defines. */ + std::unique_ptr helper_; + RPiController::Controller controller_; + std::array rpiMetadata_; + + /* + * We count frames to decide if the frame must be hidden (e.g. from + * display) or mistrusted (i.e. not given to the control algos). + */ + uint64_t frameCount_; + + /* For checking the sequencing of Prepare/Process calls. */ + uint64_t checkCount_; + + /* How many frames we should avoid running control algos on. */ + unsigned int mistrustCount_; + + /* Number of frames that need to be dropped on startup. */ + unsigned int dropFrameCount_; + + /* Frame timestamp for the last run of the controller. */ + uint64_t lastRunTimestamp_; + + /* Do we run a Controller::process() for this frame? */ + bool processPending_; + + /* LS table allocation passed in from the pipeline handler. */ + SharedFD lsTableHandle_; + void *lsTable_; + + /* Distinguish the first camera start from others. */ + bool firstStart_; + + /* Frame duration (1/fps) limits. */ + Duration minFrameDuration_; + Duration maxFrameDuration_; + + /* Track the frame length times over FrameLengthsQueueSize frames. */ + std::deque frameLengths_; + Duration lastTimeout_; +}; + +int IPARPi::init(const IPASettings &settings, bool lensPresent, IPAInitResult *result) +{ + /* + * Load the "helper" for this sensor. This tells us all the device specific stuff + * that the kernel driver doesn't. We only do this the first time; we don't need + * to re-parse the metadata after a simple mode-switch for no reason. + */ + helper_ = std::unique_ptr(RPiController::CamHelper::create(settings.sensorModel)); + if (!helper_) { + LOG(IPARPI, Error) << "Could not create camera helper for " + << settings.sensorModel; + return -EINVAL; + } + + /* + * Pass out the sensor config to the pipeline handler in order + * to setup the staggered writer class. + */ + int gainDelay, exposureDelay, vblankDelay, hblankDelay, sensorMetadata; + helper_->getDelays(exposureDelay, gainDelay, vblankDelay, hblankDelay); + sensorMetadata = helper_->sensorEmbeddedDataPresent(); + + result->sensorConfig.gainDelay = gainDelay; + result->sensorConfig.exposureDelay = exposureDelay; + result->sensorConfig.vblankDelay = vblankDelay; + result->sensorConfig.hblankDelay = hblankDelay; + result->sensorConfig.sensorMetadata = sensorMetadata; + + /* Load the tuning file for this sensor. */ + int ret = controller_.read(settings.configurationFile.c_str()); + if (ret) { + LOG(IPARPI, Error) + << "Failed to load tuning data file " + << settings.configurationFile; + return ret; + } + + const std::string &target = controller_.getTarget(); + if (target != "bcm2835") { + LOG(IPARPI, Error) + << "Tuning data file target returned \"" << target << "\"" + << ", expected \"bcm2835\""; + return -EINVAL; + } + + lensPresent_ = lensPresent; + + controller_.initialise(); + + /* Return the controls handled by the IPA */ + ControlInfoMap::Map ctrlMap = ipaControls; + if (lensPresent_) + ctrlMap.merge(ControlInfoMap::Map(ipaAfControls)); + result->controlInfo = ControlInfoMap(std::move(ctrlMap), controls::controls); + + return 0; +} + +void IPARPi::start(const ControlList &controls, StartConfig *startConfig) +{ + RPiController::Metadata metadata; + + ASSERT(startConfig); + if (!controls.empty()) { + /* We have been given some controls to action before start. */ + queueRequest(controls); + } + + controller_.switchMode(mode_, &metadata); + + /* Reset the frame lengths queue state. */ + lastTimeout_ = 0s; + frameLengths_.clear(); + frameLengths_.resize(FrameLengthsQueueSize, 0s); + + /* SwitchMode may supply updated exposure/gain values to use. */ + AgcStatus agcStatus; + agcStatus.shutterTime = 0.0s; + agcStatus.analogueGain = 0.0; + + metadata.get("agc.status", agcStatus); + if (agcStatus.shutterTime && agcStatus.analogueGain) { + ControlList ctrls(sensorCtrls_); + applyAGC(&agcStatus, ctrls); + startConfig->controls = std::move(ctrls); + setCameraTimeoutValue(); + } + + /* + * Initialise frame counts, and decide how many frames must be hidden or + * "mistrusted", which depends on whether this is a startup from cold, + * or merely a mode switch in a running system. + */ + frameCount_ = 0; + checkCount_ = 0; + if (firstStart_) { + dropFrameCount_ = helper_->hideFramesStartup(); + mistrustCount_ = helper_->mistrustFramesStartup(); + + /* + * Query the AGC/AWB for how many frames they may take to + * converge sufficiently. Where these numbers are non-zero + * we must allow for the frames with bad statistics + * (mistrustCount_) that they won't see. But if zero (i.e. + * no convergence necessary), no frames need to be dropped. + */ + unsigned int agcConvergenceFrames = 0; + RPiController::AgcAlgorithm *agc = dynamic_cast( + controller_.getAlgorithm("agc")); + if (agc) { + agcConvergenceFrames = agc->getConvergenceFrames(); + if (agcConvergenceFrames) + agcConvergenceFrames += mistrustCount_; + } + + unsigned int awbConvergenceFrames = 0; + RPiController::AwbAlgorithm *awb = dynamic_cast( + controller_.getAlgorithm("awb")); + if (awb) { + awbConvergenceFrames = awb->getConvergenceFrames(); + if (awbConvergenceFrames) + awbConvergenceFrames += mistrustCount_; + } + + dropFrameCount_ = std::max({ dropFrameCount_, agcConvergenceFrames, awbConvergenceFrames }); + LOG(IPARPI, Debug) << "Drop " << dropFrameCount_ << " frames on startup"; + } else { + dropFrameCount_ = helper_->hideFramesModeSwitch(); + mistrustCount_ = helper_->mistrustFramesModeSwitch(); + } + + startConfig->dropFrameCount = dropFrameCount_; + + firstStart_ = false; + lastRunTimestamp_ = 0; +} + +void IPARPi::setMode(const IPACameraSensorInfo &sensorInfo) +{ + mode_.bitdepth = sensorInfo.bitsPerPixel; + mode_.width = sensorInfo.outputSize.width; + mode_.height = sensorInfo.outputSize.height; + mode_.sensorWidth = sensorInfo.activeAreaSize.width; + mode_.sensorHeight = sensorInfo.activeAreaSize.height; + mode_.cropX = sensorInfo.analogCrop.x; + mode_.cropY = sensorInfo.analogCrop.y; + mode_.pixelRate = sensorInfo.pixelRate; + + /* + * Calculate scaling parameters. The scale_[xy] factors are determined + * by the ratio between the crop rectangle size and the output size. + */ + mode_.scaleX = sensorInfo.analogCrop.width / sensorInfo.outputSize.width; + mode_.scaleY = sensorInfo.analogCrop.height / sensorInfo.outputSize.height; + + /* + * We're not told by the pipeline handler how scaling is split between + * binning and digital scaling. For now, as a heuristic, assume that + * downscaling up to 2 is achieved through binning, and that any + * additional scaling is achieved through digital scaling. + * + * \todo Get the pipeline handle to provide the full data + */ + mode_.binX = std::min(2, static_cast(mode_.scaleX)); + mode_.binY = std::min(2, static_cast(mode_.scaleY)); + + /* The noise factor is the square root of the total binning factor. */ + mode_.noiseFactor = sqrt(mode_.binX * mode_.binY); + + /* + * Calculate the line length as the ratio between the line length in + * pixels and the pixel rate. + */ + mode_.minLineLength = sensorInfo.minLineLength * (1.0s / sensorInfo.pixelRate); + mode_.maxLineLength = sensorInfo.maxLineLength * (1.0s / sensorInfo.pixelRate); + + /* + * Set the frame length limits for the mode to ensure exposure and + * framerate calculations are clipped appropriately. + */ + mode_.minFrameLength = sensorInfo.minFrameLength; + mode_.maxFrameLength = sensorInfo.maxFrameLength; + + /* Store these for convenience. */ + mode_.minFrameDuration = mode_.minFrameLength * mode_.minLineLength; + mode_.maxFrameDuration = mode_.maxFrameLength * mode_.maxLineLength; + + /* + * Some sensors may have different sensitivities in different modes; + * the CamHelper will know the correct value. + */ + mode_.sensitivity = helper_->getModeSensitivity(mode_); + + const ControlInfo &gainCtrl = sensorCtrls_.at(V4L2_CID_ANALOGUE_GAIN); + const ControlInfo &shutterCtrl = sensorCtrls_.at(V4L2_CID_EXPOSURE); + + mode_.minAnalogueGain = helper_->gain(gainCtrl.min().get()); + mode_.maxAnalogueGain = helper_->gain(gainCtrl.max().get()); + + /* Shutter speed is calculated based on the limits of the frame durations. */ + mode_.minShutter = helper_->exposure(shutterCtrl.min().get(), mode_.minLineLength); + mode_.maxShutter = Duration::max(); + helper_->getBlanking(mode_.maxShutter, + mode_.minFrameDuration, mode_.maxFrameDuration); +} + +int IPARPi::configure(const IPACameraSensorInfo &sensorInfo, const IPAConfig &ipaConfig, + ControlList *controls, IPAConfigResult *result) +{ + sensorCtrls_ = ipaConfig.sensorControls; + ispCtrls_ = ipaConfig.ispControls; + + if (!validateSensorControls()) { + LOG(IPARPI, Error) << "Sensor control validation failed."; + return -1; + } + + if (!validateIspControls()) { + LOG(IPARPI, Error) << "ISP control validation failed."; + return -1; + } + + if (lensPresent_) { + lensCtrls_ = ipaConfig.lensControls; + if (!validateLensControls()) { + LOG(IPARPI, Warning) << "Lens validation failed, " + << "no lens control will be available."; + lensPresent_ = false; + } + } + + /* Setup a metadata ControlList to output metadata. */ + libcameraMetadata_ = ControlList(controls::controls); + + /* Re-assemble camera mode using the sensor info. */ + setMode(sensorInfo); + + mode_.transform = static_cast(ipaConfig.transform); + + /* Store the lens shading table pointer and handle if available. */ + if (ipaConfig.lsTableHandle.isValid()) { + /* Remove any previous table, if there was one. */ + if (lsTable_) { + munmap(lsTable_, MaxLsGridSize); + lsTable_ = nullptr; + } + + /* Map the LS table buffer into user space. */ + lsTableHandle_ = std::move(ipaConfig.lsTableHandle); + if (lsTableHandle_.isValid()) { + lsTable_ = mmap(nullptr, MaxLsGridSize, PROT_READ | PROT_WRITE, + MAP_SHARED, lsTableHandle_.get(), 0); + + if (lsTable_ == MAP_FAILED) { + LOG(IPARPI, Error) << "dmaHeap mmap failure for LS table."; + lsTable_ = nullptr; + } + } + } + + /* Pass the camera mode to the CamHelper to setup algorithms. */ + helper_->setCameraMode(mode_); + + /* + * Initialise this ControlList correctly, even if empty, in case the IPA is + * running is isolation mode (passing the ControlList through the IPC layer). + */ + ControlList ctrls(sensorCtrls_); + + /* The pipeline handler passes out the mode's sensitivity. */ + result->modeSensitivity = mode_.sensitivity; + + if (firstStart_) { + /* Supply initial values for frame durations. */ + applyFrameDurations(defaultMinFrameDuration, defaultMaxFrameDuration); + + /* Supply initial values for gain and exposure. */ + AgcStatus agcStatus; + agcStatus.shutterTime = defaultExposureTime; + agcStatus.analogueGain = defaultAnalogueGain; + applyAGC(&agcStatus, ctrls); + } + + ASSERT(controls); + *controls = std::move(ctrls); + + /* + * Apply the correct limits to the exposure, gain and frame duration controls + * based on the current sensor mode. + */ + ControlInfoMap::Map ctrlMap = ipaControls; + ctrlMap[&controls::FrameDurationLimits] = + ControlInfo(static_cast(mode_.minFrameDuration.get()), + static_cast(mode_.maxFrameDuration.get())); + + ctrlMap[&controls::AnalogueGain] = + ControlInfo(static_cast(mode_.minAnalogueGain), + static_cast(mode_.maxAnalogueGain)); + + ctrlMap[&controls::ExposureTime] = + ControlInfo(static_cast(mode_.minShutter.get()), + static_cast(mode_.maxShutter.get())); + + /* Declare Autofocus controls, only if we have a controllable lens */ + if (lensPresent_) + ctrlMap.merge(ControlInfoMap::Map(ipaAfControls)); + + result->controlInfo = ControlInfoMap(std::move(ctrlMap), controls::controls); + return 0; +} + +void IPARPi::mapBuffers(const std::vector &buffers) +{ + for (const IPABuffer &buffer : buffers) { + const FrameBuffer fb(buffer.planes); + buffers_.emplace(buffer.id, + MappedFrameBuffer(&fb, MappedFrameBuffer::MapFlag::ReadWrite)); + } +} + +void IPARPi::unmapBuffers(const std::vector &ids) +{ + for (unsigned int id : ids) { + auto it = buffers_.find(id); + if (it == buffers_.end()) + continue; + + buffers_.erase(id); + } +} + +void IPARPi::signalStatReady(uint32_t bufferId, uint32_t ipaContext) +{ + unsigned int context = ipaContext % rpiMetadata_.size(); + + if (++checkCount_ != frameCount_) /* assert here? */ + LOG(IPARPI, Error) << "WARNING: Prepare/Process mismatch!!!"; + if (processPending_ && frameCount_ > mistrustCount_) + processStats(bufferId, context); + + reportMetadata(context); + + statsMetadataComplete.emit(bufferId, libcameraMetadata_); +} + +void IPARPi::signalQueueRequest(const ControlList &controls) +{ + queueRequest(controls); +} + +void IPARPi::signalIspPrepare(const ISPConfig &data) +{ + /* + * At start-up, or after a mode-switch, we may want to + * avoid running the control algos for a few frames in case + * they are "unreliable". + */ + prepareISP(data); + frameCount_++; + + /* Ready to push the input buffer into the ISP. */ + runIsp.emit(data.bayerBufferId); +} + +void IPARPi::reportMetadata(unsigned int ipaContext) +{ + RPiController::Metadata &rpiMetadata = rpiMetadata_[ipaContext]; + std::unique_lock lock(rpiMetadata); + + /* + * Certain information about the current frame and how it will be + * processed can be extracted and placed into the libcamera metadata + * buffer, where an application could query it. + */ + DeviceStatus *deviceStatus = rpiMetadata.getLocked("device.status"); + if (deviceStatus) { + libcameraMetadata_.set(controls::ExposureTime, + deviceStatus->shutterSpeed.get()); + libcameraMetadata_.set(controls::AnalogueGain, deviceStatus->analogueGain); + libcameraMetadata_.set(controls::FrameDuration, + helper_->exposure(deviceStatus->frameLength, deviceStatus->lineLength).get()); + if (deviceStatus->sensorTemperature) + libcameraMetadata_.set(controls::SensorTemperature, *deviceStatus->sensorTemperature); + if (deviceStatus->lensPosition) + libcameraMetadata_.set(controls::LensPosition, *deviceStatus->lensPosition); + } + + AgcStatus *agcStatus = rpiMetadata.getLocked("agc.status"); + if (agcStatus) { + libcameraMetadata_.set(controls::AeLocked, agcStatus->locked); + libcameraMetadata_.set(controls::DigitalGain, agcStatus->digitalGain); + } + + LuxStatus *luxStatus = rpiMetadata.getLocked("lux.status"); + if (luxStatus) + libcameraMetadata_.set(controls::Lux, luxStatus->lux); + + AwbStatus *awbStatus = rpiMetadata.getLocked("awb.status"); + if (awbStatus) { + libcameraMetadata_.set(controls::ColourGains, { static_cast(awbStatus->gainR), + static_cast(awbStatus->gainB) }); + libcameraMetadata_.set(controls::ColourTemperature, awbStatus->temperatureK); + } + + BlackLevelStatus *blackLevelStatus = rpiMetadata.getLocked("black_level.status"); + if (blackLevelStatus) + libcameraMetadata_.set(controls::SensorBlackLevels, + { static_cast(blackLevelStatus->blackLevelR), + static_cast(blackLevelStatus->blackLevelG), + static_cast(blackLevelStatus->blackLevelG), + static_cast(blackLevelStatus->blackLevelB) }); + + RPiController::FocusRegions *focusStatus = + rpiMetadata.getLocked("focus.status"); + if (focusStatus) { + /* + * Calculate the average FoM over the central (symmetric) positions + * to give an overall scene FoM. This can change later if it is + * not deemed suitable. + */ + libcamera::Size size = focusStatus->size(); + unsigned rows = size.height; + unsigned cols = size.width; + + uint64_t sum = 0; + unsigned int numRegions = 0; + for (unsigned r = rows / 3; r < rows - rows / 3; ++r) { + for (unsigned c = cols / 4; c < cols - cols / 4; ++c) { + sum += focusStatus->get({ (int)c, (int)r }).val; + numRegions++; + } + } + + uint32_t focusFoM = (sum / numRegions) >> 16; + libcameraMetadata_.set(controls::FocusFoM, focusFoM); + } + + CcmStatus *ccmStatus = rpiMetadata.getLocked("ccm.status"); + if (ccmStatus) { + float m[9]; + for (unsigned int i = 0; i < 9; i++) + m[i] = ccmStatus->matrix[i]; + libcameraMetadata_.set(controls::ColourCorrectionMatrix, m); + } + + const AfStatus *afStatus = rpiMetadata.getLocked("af.status"); + if (afStatus) { + int32_t s, p; + switch (afStatus->state) { + case AfState::Scanning: + s = controls::AfStateScanning; + break; + case AfState::Focused: + s = controls::AfStateFocused; + break; + case AfState::Failed: + s = controls::AfStateFailed; + break; + default: + s = controls::AfStateIdle; + } + switch (afStatus->pauseState) { + case AfPauseState::Pausing: + p = controls::AfPauseStatePausing; + break; + case AfPauseState::Paused: + p = controls::AfPauseStatePaused; + break; + default: + p = controls::AfPauseStateRunning; + } + libcameraMetadata_.set(controls::AfState, s); + libcameraMetadata_.set(controls::AfPauseState, p); + } +} + +bool IPARPi::validateSensorControls() +{ + static const uint32_t ctrls[] = { + V4L2_CID_ANALOGUE_GAIN, + V4L2_CID_EXPOSURE, + V4L2_CID_VBLANK, + V4L2_CID_HBLANK, + }; + + for (auto c : ctrls) { + if (sensorCtrls_.find(c) == sensorCtrls_.end()) { + LOG(IPARPI, Error) << "Unable to find sensor control " + << utils::hex(c); + return false; + } + } + + return true; +} + +bool IPARPi::validateIspControls() +{ + static const uint32_t ctrls[] = { + V4L2_CID_RED_BALANCE, + V4L2_CID_BLUE_BALANCE, + V4L2_CID_DIGITAL_GAIN, + V4L2_CID_USER_BCM2835_ISP_CC_MATRIX, + V4L2_CID_USER_BCM2835_ISP_GAMMA, + V4L2_CID_USER_BCM2835_ISP_BLACK_LEVEL, + V4L2_CID_USER_BCM2835_ISP_GEQ, + V4L2_CID_USER_BCM2835_ISP_DENOISE, + V4L2_CID_USER_BCM2835_ISP_SHARPEN, + V4L2_CID_USER_BCM2835_ISP_DPC, + V4L2_CID_USER_BCM2835_ISP_LENS_SHADING, + V4L2_CID_USER_BCM2835_ISP_CDN, + }; + + for (auto c : ctrls) { + if (ispCtrls_.find(c) == ispCtrls_.end()) { + LOG(IPARPI, Error) << "Unable to find ISP control " + << utils::hex(c); + return false; + } + } + + return true; +} + +bool IPARPi::validateLensControls() +{ + if (lensCtrls_.find(V4L2_CID_FOCUS_ABSOLUTE) == lensCtrls_.end()) { + LOG(IPARPI, Error) << "Unable to find Lens control V4L2_CID_FOCUS_ABSOLUTE"; + return false; + } + + return true; +} + +/* + * Converting between enums (used in the libcamera API) and the names that + * we use to identify different modes. Unfortunately, the conversion tables + * must be kept up-to-date by hand. + */ +static const std::map MeteringModeTable = { + { controls::MeteringCentreWeighted, "centre-weighted" }, + { controls::MeteringSpot, "spot" }, + { controls::MeteringMatrix, "matrix" }, + { controls::MeteringCustom, "custom" }, +}; + +static const std::map ConstraintModeTable = { + { controls::ConstraintNormal, "normal" }, + { controls::ConstraintHighlight, "highlight" }, + { controls::ConstraintShadows, "shadows" }, + { controls::ConstraintCustom, "custom" }, +}; + +static const std::map ExposureModeTable = { + { controls::ExposureNormal, "normal" }, + { controls::ExposureShort, "short" }, + { controls::ExposureLong, "long" }, + { controls::ExposureCustom, "custom" }, +}; + +static const std::map AwbModeTable = { + { controls::AwbAuto, "auto" }, + { controls::AwbIncandescent, "incandescent" }, + { controls::AwbTungsten, "tungsten" }, + { controls::AwbFluorescent, "fluorescent" }, + { controls::AwbIndoor, "indoor" }, + { controls::AwbDaylight, "daylight" }, + { controls::AwbCloudy, "cloudy" }, + { controls::AwbCustom, "custom" }, +}; + +static const std::map DenoiseModeTable = { + { controls::draft::NoiseReductionModeOff, RPiController::DenoiseMode::Off }, + { controls::draft::NoiseReductionModeFast, RPiController::DenoiseMode::ColourFast }, + { controls::draft::NoiseReductionModeHighQuality, RPiController::DenoiseMode::ColourHighQuality }, + { controls::draft::NoiseReductionModeMinimal, RPiController::DenoiseMode::ColourOff }, + { controls::draft::NoiseReductionModeZSL, RPiController::DenoiseMode::ColourHighQuality }, +}; + +static const std::map AfModeTable = { + { controls::AfModeManual, RPiController::AfAlgorithm::AfModeManual }, + { controls::AfModeAuto, RPiController::AfAlgorithm::AfModeAuto }, + { controls::AfModeContinuous, RPiController::AfAlgorithm::AfModeContinuous }, +}; + +static const std::map AfRangeTable = { + { controls::AfRangeNormal, RPiController::AfAlgorithm::AfRangeNormal }, + { controls::AfRangeMacro, RPiController::AfAlgorithm::AfRangeMacro }, + { controls::AfRangeFull, RPiController::AfAlgorithm::AfRangeFull }, +}; + +static const std::map AfPauseTable = { + { controls::AfPauseImmediate, RPiController::AfAlgorithm::AfPauseImmediate }, + { controls::AfPauseDeferred, RPiController::AfAlgorithm::AfPauseDeferred }, + { controls::AfPauseResume, RPiController::AfAlgorithm::AfPauseResume }, +}; + +void IPARPi::queueRequest(const ControlList &controls) +{ + using RPiController::AfAlgorithm; + + /* Clear the return metadata buffer. */ + libcameraMetadata_.clear(); + + /* Because some AF controls are mode-specific, handle AF mode change first. */ + if (controls.contains(controls::AF_MODE)) { + AfAlgorithm *af = dynamic_cast(controller_.getAlgorithm("af")); + if (!af) { + LOG(IPARPI, Warning) + << "Could not set AF_MODE - no AF algorithm"; + } + + int32_t idx = controls.get(controls::AF_MODE).get(); + auto mode = AfModeTable.find(idx); + if (mode == AfModeTable.end()) { + LOG(IPARPI, Error) << "AF mode " << idx + << " not recognised"; + } else + af->setMode(mode->second); + } + + /* Iterate over controls */ + for (auto const &ctrl : controls) { + LOG(IPARPI, Debug) << "Request ctrl: " + << controls::controls.at(ctrl.first)->name() + << " = " << ctrl.second.toString(); + + switch (ctrl.first) { + case controls::AE_ENABLE: { + RPiController::AgcAlgorithm *agc = dynamic_cast( + controller_.getAlgorithm("agc")); + if (!agc) { + LOG(IPARPI, Warning) + << "Could not set AE_ENABLE - no AGC algorithm"; + break; + } + + if (ctrl.second.get() == false) + agc->disableAuto(); + else + agc->enableAuto(); + + libcameraMetadata_.set(controls::AeEnable, ctrl.second.get()); + break; + } + + case controls::EXPOSURE_TIME: { + RPiController::AgcAlgorithm *agc = dynamic_cast( + controller_.getAlgorithm("agc")); + if (!agc) { + LOG(IPARPI, Warning) + << "Could not set EXPOSURE_TIME - no AGC algorithm"; + break; + } + + /* The control provides units of microseconds. */ + agc->setFixedShutter(ctrl.second.get() * 1.0us); + + libcameraMetadata_.set(controls::ExposureTime, ctrl.second.get()); + break; + } + + case controls::ANALOGUE_GAIN: { + RPiController::AgcAlgorithm *agc = dynamic_cast( + controller_.getAlgorithm("agc")); + if (!agc) { + LOG(IPARPI, Warning) + << "Could not set ANALOGUE_GAIN - no AGC algorithm"; + break; + } + + agc->setFixedAnalogueGain(ctrl.second.get()); + + libcameraMetadata_.set(controls::AnalogueGain, + ctrl.second.get()); + break; + } + + case controls::AE_METERING_MODE: { + RPiController::AgcAlgorithm *agc = dynamic_cast( + controller_.getAlgorithm("agc")); + if (!agc) { + LOG(IPARPI, Warning) + << "Could not set AE_METERING_MODE - no AGC algorithm"; + break; + } + + int32_t idx = ctrl.second.get(); + if (MeteringModeTable.count(idx)) { + agc->setMeteringMode(MeteringModeTable.at(idx)); + libcameraMetadata_.set(controls::AeMeteringMode, idx); + } else { + LOG(IPARPI, Error) << "Metering mode " << idx + << " not recognised"; + } + break; + } + + case controls::AE_CONSTRAINT_MODE: { + RPiController::AgcAlgorithm *agc = dynamic_cast( + controller_.getAlgorithm("agc")); + if (!agc) { + LOG(IPARPI, Warning) + << "Could not set AE_CONSTRAINT_MODE - no AGC algorithm"; + break; + } + + int32_t idx = ctrl.second.get(); + if (ConstraintModeTable.count(idx)) { + agc->setConstraintMode(ConstraintModeTable.at(idx)); + libcameraMetadata_.set(controls::AeConstraintMode, idx); + } else { + LOG(IPARPI, Error) << "Constraint mode " << idx + << " not recognised"; + } + break; + } + + case controls::AE_EXPOSURE_MODE: { + RPiController::AgcAlgorithm *agc = dynamic_cast( + controller_.getAlgorithm("agc")); + if (!agc) { + LOG(IPARPI, Warning) + << "Could not set AE_EXPOSURE_MODE - no AGC algorithm"; + break; + } + + int32_t idx = ctrl.second.get(); + if (ExposureModeTable.count(idx)) { + agc->setExposureMode(ExposureModeTable.at(idx)); + libcameraMetadata_.set(controls::AeExposureMode, idx); + } else { + LOG(IPARPI, Error) << "Exposure mode " << idx + << " not recognised"; + } + break; + } + + case controls::EXPOSURE_VALUE: { + RPiController::AgcAlgorithm *agc = dynamic_cast( + controller_.getAlgorithm("agc")); + if (!agc) { + LOG(IPARPI, Warning) + << "Could not set EXPOSURE_VALUE - no AGC algorithm"; + break; + } + + /* + * The SetEv() function takes in a direct exposure multiplier. + * So convert to 2^EV + */ + double ev = pow(2.0, ctrl.second.get()); + agc->setEv(ev); + libcameraMetadata_.set(controls::ExposureValue, + ctrl.second.get()); + break; + } + + case controls::AWB_ENABLE: { + RPiController::AwbAlgorithm *awb = dynamic_cast( + controller_.getAlgorithm("awb")); + if (!awb) { + LOG(IPARPI, Warning) + << "Could not set AWB_ENABLE - no AWB algorithm"; + break; + } + + if (ctrl.second.get() == false) + awb->disableAuto(); + else + awb->enableAuto(); + + libcameraMetadata_.set(controls::AwbEnable, + ctrl.second.get()); + break; + } + + case controls::AWB_MODE: { + RPiController::AwbAlgorithm *awb = dynamic_cast( + controller_.getAlgorithm("awb")); + if (!awb) { + LOG(IPARPI, Warning) + << "Could not set AWB_MODE - no AWB algorithm"; + break; + } + + int32_t idx = ctrl.second.get(); + if (AwbModeTable.count(idx)) { + awb->setMode(AwbModeTable.at(idx)); + libcameraMetadata_.set(controls::AwbMode, idx); + } else { + LOG(IPARPI, Error) << "AWB mode " << idx + << " not recognised"; + } + break; + } + + case controls::COLOUR_GAINS: { + auto gains = ctrl.second.get>(); + RPiController::AwbAlgorithm *awb = dynamic_cast( + controller_.getAlgorithm("awb")); + if (!awb) { + LOG(IPARPI, Warning) + << "Could not set COLOUR_GAINS - no AWB algorithm"; + break; + } + + awb->setManualGains(gains[0], gains[1]); + if (gains[0] != 0.0f && gains[1] != 0.0f) + /* A gain of 0.0f will switch back to auto mode. */ + libcameraMetadata_.set(controls::ColourGains, + { gains[0], gains[1] }); + break; + } + + case controls::BRIGHTNESS: { + RPiController::ContrastAlgorithm *contrast = dynamic_cast( + controller_.getAlgorithm("contrast")); + if (!contrast) { + LOG(IPARPI, Warning) + << "Could not set BRIGHTNESS - no contrast algorithm"; + break; + } + + contrast->setBrightness(ctrl.second.get() * 65536); + libcameraMetadata_.set(controls::Brightness, + ctrl.second.get()); + break; + } + + case controls::CONTRAST: { + RPiController::ContrastAlgorithm *contrast = dynamic_cast( + controller_.getAlgorithm("contrast")); + if (!contrast) { + LOG(IPARPI, Warning) + << "Could not set CONTRAST - no contrast algorithm"; + break; + } + + contrast->setContrast(ctrl.second.get()); + libcameraMetadata_.set(controls::Contrast, + ctrl.second.get()); + break; + } + + case controls::SATURATION: { + RPiController::CcmAlgorithm *ccm = dynamic_cast( + controller_.getAlgorithm("ccm")); + if (!ccm) { + LOG(IPARPI, Warning) + << "Could not set SATURATION - no ccm algorithm"; + break; + } + + ccm->setSaturation(ctrl.second.get()); + libcameraMetadata_.set(controls::Saturation, + ctrl.second.get()); + break; + } + + case controls::SHARPNESS: { + RPiController::SharpenAlgorithm *sharpen = dynamic_cast( + controller_.getAlgorithm("sharpen")); + if (!sharpen) { + LOG(IPARPI, Warning) + << "Could not set SHARPNESS - no sharpen algorithm"; + break; + } + + sharpen->setStrength(ctrl.second.get()); + libcameraMetadata_.set(controls::Sharpness, + ctrl.second.get()); + break; + } + + case controls::SCALER_CROP: { + /* We do nothing with this, but should avoid the warning below. */ + break; + } + + case controls::FRAME_DURATION_LIMITS: { + auto frameDurations = ctrl.second.get>(); + applyFrameDurations(frameDurations[0] * 1.0us, frameDurations[1] * 1.0us); + break; + } + + case controls::NOISE_REDUCTION_MODE: { + RPiController::DenoiseAlgorithm *sdn = dynamic_cast( + controller_.getAlgorithm("SDN")); + if (!sdn) { + LOG(IPARPI, Warning) + << "Could not set NOISE_REDUCTION_MODE - no SDN algorithm"; + break; + } + + int32_t idx = ctrl.second.get(); + auto mode = DenoiseModeTable.find(idx); + if (mode != DenoiseModeTable.end()) { + sdn->setMode(mode->second); + + /* + * \todo If the colour denoise is not going to run due to an + * analysis image resolution or format mismatch, we should + * report the status correctly in the metadata. + */ + libcameraMetadata_.set(controls::draft::NoiseReductionMode, idx); + } else { + LOG(IPARPI, Error) << "Noise reduction mode " << idx + << " not recognised"; + } + break; + } + + case controls::AF_MODE: + break; /* We already handled this one above */ + + case controls::AF_RANGE: { + AfAlgorithm *af = dynamic_cast(controller_.getAlgorithm("af")); + if (!af) { + LOG(IPARPI, Warning) + << "Could not set AF_RANGE - no focus algorithm"; + break; + } + + auto range = AfRangeTable.find(ctrl.second.get()); + if (range == AfRangeTable.end()) { + LOG(IPARPI, Error) << "AF range " << ctrl.second.get() + << " not recognised"; + break; + } + af->setRange(range->second); + break; + } + + case controls::AF_SPEED: { + AfAlgorithm *af = dynamic_cast(controller_.getAlgorithm("af")); + if (!af) { + LOG(IPARPI, Warning) + << "Could not set AF_SPEED - no focus algorithm"; + break; + } + + AfAlgorithm::AfSpeed speed = ctrl.second.get() == controls::AfSpeedFast ? + AfAlgorithm::AfSpeedFast : AfAlgorithm::AfSpeedNormal; + af->setSpeed(speed); + break; + } + + case controls::AF_METERING: { + AfAlgorithm *af = dynamic_cast(controller_.getAlgorithm("af")); + if (!af) { + LOG(IPARPI, Warning) + << "Could not set AF_METERING - no AF algorithm"; + break; + } + af->setMetering(ctrl.second.get() == controls::AfMeteringWindows); + break; + } + + case controls::AF_WINDOWS: { + AfAlgorithm *af = dynamic_cast(controller_.getAlgorithm("af")); + if (!af) { + LOG(IPARPI, Warning) + << "Could not set AF_WINDOWS - no AF algorithm"; + break; + } + af->setWindows(ctrl.second.get>()); + break; + } + + case controls::AF_PAUSE: { + AfAlgorithm *af = dynamic_cast(controller_.getAlgorithm("af")); + if (!af || af->getMode() != AfAlgorithm::AfModeContinuous) { + LOG(IPARPI, Warning) + << "Could not set AF_PAUSE - no AF algorithm or not Continuous"; + break; + } + auto pause = AfPauseTable.find(ctrl.second.get()); + if (pause == AfPauseTable.end()) { + LOG(IPARPI, Error) << "AF pause " << ctrl.second.get() + << " not recognised"; + break; + } + af->pause(pause->second); + break; + } + + case controls::AF_TRIGGER: { + AfAlgorithm *af = dynamic_cast(controller_.getAlgorithm("af")); + if (!af || af->getMode() != AfAlgorithm::AfModeAuto) { + LOG(IPARPI, Warning) + << "Could not set AF_TRIGGER - no AF algorithm or not Auto"; + break; + } else { + if (ctrl.second.get() == controls::AfTriggerStart) + af->triggerScan(); + else + af->cancelScan(); + } + break; + } + + case controls::LENS_POSITION: { + AfAlgorithm *af = dynamic_cast(controller_.getAlgorithm("af")); + if (af) { + int32_t hwpos; + if (af->setLensPosition(ctrl.second.get(), &hwpos)) { + ControlList lensCtrls(lensCtrls_); + lensCtrls.set(V4L2_CID_FOCUS_ABSOLUTE, hwpos); + setLensControls.emit(lensCtrls); + } + } else { + LOG(IPARPI, Warning) + << "Could not set LENS_POSITION - no AF algorithm"; + } + break; + } + + default: + LOG(IPARPI, Warning) + << "Ctrl " << controls::controls.at(ctrl.first)->name() + << " is not handled."; + break; + } + } +} + +void IPARPi::returnEmbeddedBuffer(unsigned int bufferId) +{ + embeddedComplete.emit(bufferId); +} + +void IPARPi::prepareISP(const ISPConfig &data) +{ + int64_t frameTimestamp = data.controls.get(controls::SensorTimestamp).value_or(0); + unsigned int ipaContext = data.ipaContext % rpiMetadata_.size(); + RPiController::Metadata &rpiMetadata = rpiMetadata_[ipaContext]; + Span embeddedBuffer; + + rpiMetadata.clear(); + fillDeviceStatus(data.controls, ipaContext); + + if (data.embeddedBufferPresent) { + /* + * Pipeline handler has supplied us with an embedded data buffer, + * we must pass it to the CamHelper for parsing. + */ + auto it = buffers_.find(data.embeddedBufferId); + ASSERT(it != buffers_.end()); + embeddedBuffer = it->second.planes()[0]; + } + + /* + * AGC wants to know the algorithm status from the time it actioned the + * sensor exposure/gain changes. So fetch it from the metadata list + * indexed by the IPA cookie returned, and put it in the current frame + * metadata. + */ + AgcStatus agcStatus; + RPiController::Metadata &delayedMetadata = rpiMetadata_[data.delayContext]; + if (!delayedMetadata.get("agc.status", agcStatus)) + rpiMetadata.set("agc.delayed_status", agcStatus); + + /* + * This may overwrite the DeviceStatus using values from the sensor + * metadata, and may also do additional custom processing. + */ + helper_->prepare(embeddedBuffer, rpiMetadata); + + /* Done with embedded data now, return to pipeline handler asap. */ + if (data.embeddedBufferPresent) + returnEmbeddedBuffer(data.embeddedBufferId); + + /* Allow a 10% margin on the comparison below. */ + Duration delta = (frameTimestamp - lastRunTimestamp_) * 1.0ns; + if (lastRunTimestamp_ && frameCount_ > dropFrameCount_ && + delta < controllerMinFrameDuration * 0.9) { + /* + * Ensure we merge the previous frame's metadata with the current + * frame. This will not overwrite exposure/gain values for the + * current frame, or any other bits of metadata that were added + * in helper_->Prepare(). + */ + RPiController::Metadata &lastMetadata = + rpiMetadata_[(ipaContext ? ipaContext : rpiMetadata_.size()) - 1]; + rpiMetadata.mergeCopy(lastMetadata); + processPending_ = false; + return; + } + + lastRunTimestamp_ = frameTimestamp; + processPending_ = true; + + ControlList ctrls(ispCtrls_); + + controller_.prepare(&rpiMetadata); + + /* Lock the metadata buffer to avoid constant locks/unlocks. */ + std::unique_lock lock(rpiMetadata); + + AwbStatus *awbStatus = rpiMetadata.getLocked("awb.status"); + if (awbStatus) + applyAWB(awbStatus, ctrls); + + CcmStatus *ccmStatus = rpiMetadata.getLocked("ccm.status"); + if (ccmStatus) + applyCCM(ccmStatus, ctrls); + + AgcStatus *dgStatus = rpiMetadata.getLocked("agc.status"); + if (dgStatus) + applyDG(dgStatus, ctrls); + + AlscStatus *lsStatus = rpiMetadata.getLocked("alsc.status"); + if (lsStatus) + applyLS(lsStatus, ctrls); + + ContrastStatus *contrastStatus = rpiMetadata.getLocked("contrast.status"); + if (contrastStatus) + applyGamma(contrastStatus, ctrls); + + BlackLevelStatus *blackLevelStatus = rpiMetadata.getLocked("black_level.status"); + if (blackLevelStatus) + applyBlackLevel(blackLevelStatus, ctrls); + + GeqStatus *geqStatus = rpiMetadata.getLocked("geq.status"); + if (geqStatus) + applyGEQ(geqStatus, ctrls); + + DenoiseStatus *denoiseStatus = rpiMetadata.getLocked("denoise.status"); + if (denoiseStatus) + applyDenoise(denoiseStatus, ctrls); + + SharpenStatus *sharpenStatus = rpiMetadata.getLocked("sharpen.status"); + if (sharpenStatus) + applySharpen(sharpenStatus, ctrls); + + DpcStatus *dpcStatus = rpiMetadata.getLocked("dpc.status"); + if (dpcStatus) + applyDPC(dpcStatus, ctrls); + + const AfStatus *afStatus = rpiMetadata.getLocked("af.status"); + if (afStatus) { + ControlList lensctrls(lensCtrls_); + applyAF(afStatus, lensctrls); + if (!lensctrls.empty()) + setLensControls.emit(lensctrls); + } + + if (!ctrls.empty()) + setIspControls.emit(ctrls); +} + +void IPARPi::fillDeviceStatus(const ControlList &sensorControls, unsigned int ipaContext) +{ + DeviceStatus deviceStatus = {}; + + int32_t exposureLines = sensorControls.get(V4L2_CID_EXPOSURE).get(); + int32_t gainCode = sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get(); + int32_t vblank = sensorControls.get(V4L2_CID_VBLANK).get(); + int32_t hblank = sensorControls.get(V4L2_CID_HBLANK).get(); + + deviceStatus.lineLength = helper_->hblankToLineLength(hblank); + deviceStatus.shutterSpeed = helper_->exposure(exposureLines, deviceStatus.lineLength); + deviceStatus.analogueGain = helper_->gain(gainCode); + deviceStatus.frameLength = mode_.height + vblank; + + RPiController::AfAlgorithm *af = dynamic_cast( + controller_.getAlgorithm("af")); + if (af) + deviceStatus.lensPosition = af->getLensPosition(); + + LOG(IPARPI, Debug) << "Metadata - " << deviceStatus; + + rpiMetadata_[ipaContext].set("device.status", deviceStatus); +} + +RPiController::StatisticsPtr IPARPi::fillStatistics(bcm2835_isp_stats *stats) const +{ + using namespace RPiController; + + const Controller::HardwareConfig &hw = controller_.getHardwareConfig(); + unsigned int i; + StatisticsPtr statistics = + std::make_unique(Statistics::AgcStatsPos::PreWb, Statistics::ColourStatsPos::PostLsc); + + /* RGB histograms are not used, so do not populate them. */ + statistics->yHist = RPiController::Histogram(stats->hist[0].g_hist, + hw.numHistogramBins); + + /* All region sums are based on a 16-bit normalised pipeline bit-depth. */ + unsigned int scale = Statistics::NormalisationFactorPow2 - hw.pipelineWidth; + + statistics->awbRegions.init(hw.awbRegions); + for (i = 0; i < statistics->awbRegions.numRegions(); i++) + statistics->awbRegions.set(i, { { stats->awb_stats[i].r_sum << scale, + stats->awb_stats[i].g_sum << scale, + stats->awb_stats[i].b_sum << scale }, + stats->awb_stats[i].counted, + stats->awb_stats[i].notcounted }); + + statistics->agcRegions.init(hw.agcRegions); + for (i = 0; i < statistics->agcRegions.numRegions(); i++) + statistics->agcRegions.set(i, { { stats->agc_stats[i].r_sum << scale, + stats->agc_stats[i].g_sum << scale, + stats->agc_stats[i].b_sum << scale }, + stats->agc_stats[i].counted, + stats->awb_stats[i].notcounted }); + + statistics->focusRegions.init(hw.focusRegions); + for (i = 0; i < statistics->focusRegions.numRegions(); i++) + statistics->focusRegions.set(i, { stats->focus_stats[i].contrast_val[1][1] / 1000, + stats->focus_stats[i].contrast_val_num[1][1], + stats->focus_stats[i].contrast_val_num[1][0] }); + return statistics; +} + +void IPARPi::processStats(unsigned int bufferId, unsigned int ipaContext) +{ + RPiController::Metadata &rpiMetadata = rpiMetadata_[ipaContext]; + + auto it = buffers_.find(bufferId); + if (it == buffers_.end()) { + LOG(IPARPI, Error) << "Could not find stats buffer!"; + return; + } + + Span mem = it->second.planes()[0]; + bcm2835_isp_stats *stats = reinterpret_cast(mem.data()); + RPiController::StatisticsPtr statistics = fillStatistics(stats); + + /* Save the focus stats in the metadata structure to report out later. */ + rpiMetadata_[ipaContext].set("focus.status", statistics->focusRegions); + + helper_->process(statistics, rpiMetadata); + controller_.process(statistics, &rpiMetadata); + + struct AgcStatus agcStatus; + if (rpiMetadata.get("agc.status", agcStatus) == 0) { + ControlList ctrls(sensorCtrls_); + applyAGC(&agcStatus, ctrls); + + setDelayedControls.emit(ctrls, ipaContext); + setCameraTimeoutValue(); + } +} + +void IPARPi::setCameraTimeoutValue() +{ + /* + * Take the maximum value of the exposure queue as the camera timeout + * value to pass back to the pipeline handler. Only signal if it has changed + * from the last set value. + */ + auto max = std::max_element(frameLengths_.begin(), frameLengths_.end()); + + if (*max != lastTimeout_) { + setCameraTimeout.emit(max->get()); + lastTimeout_ = *max; + } +} + +void IPARPi::applyAWB(const struct AwbStatus *awbStatus, ControlList &ctrls) +{ + LOG(IPARPI, Debug) << "Applying WB R: " << awbStatus->gainR << " B: " + << awbStatus->gainB; + + ctrls.set(V4L2_CID_RED_BALANCE, + static_cast(awbStatus->gainR * 1000)); + ctrls.set(V4L2_CID_BLUE_BALANCE, + static_cast(awbStatus->gainB * 1000)); +} + +void IPARPi::applyFrameDurations(Duration minFrameDuration, Duration maxFrameDuration) +{ + /* + * This will only be applied once AGC recalculations occur. + * The values may be clamped based on the sensor mode capabilities as well. + */ + minFrameDuration_ = minFrameDuration ? minFrameDuration : defaultMinFrameDuration; + maxFrameDuration_ = maxFrameDuration ? maxFrameDuration : defaultMaxFrameDuration; + minFrameDuration_ = std::clamp(minFrameDuration_, + mode_.minFrameDuration, mode_.maxFrameDuration); + maxFrameDuration_ = std::clamp(maxFrameDuration_, + mode_.minFrameDuration, mode_.maxFrameDuration); + maxFrameDuration_ = std::max(maxFrameDuration_, minFrameDuration_); + + /* Return the validated limits via metadata. */ + libcameraMetadata_.set(controls::FrameDurationLimits, + { static_cast(minFrameDuration_.get()), + static_cast(maxFrameDuration_.get()) }); + + /* + * Calculate the maximum exposure time possible for the AGC to use. + * getBlanking() will update maxShutter with the largest exposure + * value possible. + */ + Duration maxShutter = Duration::max(); + helper_->getBlanking(maxShutter, minFrameDuration_, maxFrameDuration_); + + RPiController::AgcAlgorithm *agc = dynamic_cast( + controller_.getAlgorithm("agc")); + agc->setMaxShutter(maxShutter); +} + +void IPARPi::applyAGC(const struct AgcStatus *agcStatus, ControlList &ctrls) +{ + const int32_t minGainCode = helper_->gainCode(mode_.minAnalogueGain); + const int32_t maxGainCode = helper_->gainCode(mode_.maxAnalogueGain); + int32_t gainCode = helper_->gainCode(agcStatus->analogueGain); + + /* + * Ensure anything larger than the max gain code will not be passed to + * DelayedControls. The AGC will correctly handle a lower gain returned + * by the sensor, provided it knows the actual gain used. + */ + gainCode = std::clamp(gainCode, minGainCode, maxGainCode); + + /* getBlanking might clip exposure time to the fps limits. */ + Duration exposure = agcStatus->shutterTime; + auto [vblank, hblank] = helper_->getBlanking(exposure, minFrameDuration_, maxFrameDuration_); + int32_t exposureLines = helper_->exposureLines(exposure, + helper_->hblankToLineLength(hblank)); + + LOG(IPARPI, Debug) << "Applying AGC Exposure: " << exposure + << " (Shutter lines: " << exposureLines << ", AGC requested " + << agcStatus->shutterTime << ") Gain: " + << agcStatus->analogueGain << " (Gain Code: " + << gainCode << ")"; + + ctrls.set(V4L2_CID_VBLANK, static_cast(vblank)); + ctrls.set(V4L2_CID_EXPOSURE, exposureLines); + ctrls.set(V4L2_CID_ANALOGUE_GAIN, gainCode); + + /* + * At present, there is no way of knowing if a control is read-only. + * As a workaround, assume that if the minimum and maximum values of + * the V4L2_CID_HBLANK control are the same, it implies the control + * is read-only. This seems to be the case for all the cameras our IPA + * works with. + * + * \todo The control API ought to have a flag to specify if a control + * is read-only which could be used below. + */ + if (mode_.minLineLength != mode_.maxLineLength) + ctrls.set(V4L2_CID_HBLANK, static_cast(hblank)); + + /* + * Store the frame length times in a circular queue, up-to FrameLengthsQueueSize + * elements. This will be used to advertise a camera timeout value to the + * pipeline handler. + */ + frameLengths_.pop_front(); + frameLengths_.push_back(helper_->exposure(vblank + mode_.height, + helper_->hblankToLineLength(hblank))); +} + +void IPARPi::applyDG(const struct AgcStatus *dgStatus, ControlList &ctrls) +{ + ctrls.set(V4L2_CID_DIGITAL_GAIN, + static_cast(dgStatus->digitalGain * 1000)); +} + +void IPARPi::applyCCM(const struct CcmStatus *ccmStatus, ControlList &ctrls) +{ + bcm2835_isp_custom_ccm ccm; + + for (int i = 0; i < 9; i++) { + ccm.ccm.ccm[i / 3][i % 3].den = 1000; + ccm.ccm.ccm[i / 3][i % 3].num = 1000 * ccmStatus->matrix[i]; + } + + ccm.enabled = 1; + ccm.ccm.offsets[0] = ccm.ccm.offsets[1] = ccm.ccm.offsets[2] = 0; + + ControlValue c(Span{ reinterpret_cast(&ccm), + sizeof(ccm) }); + ctrls.set(V4L2_CID_USER_BCM2835_ISP_CC_MATRIX, c); +} + +void IPARPi::applyGamma(const struct ContrastStatus *contrastStatus, ControlList &ctrls) +{ + const unsigned int numGammaPoints = controller_.getHardwareConfig().numGammaPoints; + struct bcm2835_isp_gamma gamma; + + for (unsigned int i = 0; i < numGammaPoints - 1; i++) { + int x = i < 16 ? i * 1024 + : (i < 24 ? (i - 16) * 2048 + 16384 + : (i - 24) * 4096 + 32768); + gamma.x[i] = x; + gamma.y[i] = std::min(65535, contrastStatus->gammaCurve.eval(x)); + } + + gamma.x[numGammaPoints - 1] = 65535; + gamma.y[numGammaPoints - 1] = 65535; + gamma.enabled = 1; + + ControlValue c(Span{ reinterpret_cast(&gamma), + sizeof(gamma) }); + ctrls.set(V4L2_CID_USER_BCM2835_ISP_GAMMA, c); +} + +void IPARPi::applyBlackLevel(const struct BlackLevelStatus *blackLevelStatus, ControlList &ctrls) +{ + bcm2835_isp_black_level blackLevel; + + blackLevel.enabled = 1; + blackLevel.black_level_r = blackLevelStatus->blackLevelR; + blackLevel.black_level_g = blackLevelStatus->blackLevelG; + blackLevel.black_level_b = blackLevelStatus->blackLevelB; + + ControlValue c(Span{ reinterpret_cast(&blackLevel), + sizeof(blackLevel) }); + ctrls.set(V4L2_CID_USER_BCM2835_ISP_BLACK_LEVEL, c); +} + +void IPARPi::applyGEQ(const struct GeqStatus *geqStatus, ControlList &ctrls) +{ + bcm2835_isp_geq geq; + + geq.enabled = 1; + geq.offset = geqStatus->offset; + geq.slope.den = 1000; + geq.slope.num = 1000 * geqStatus->slope; + + ControlValue c(Span{ reinterpret_cast(&geq), + sizeof(geq) }); + ctrls.set(V4L2_CID_USER_BCM2835_ISP_GEQ, c); +} + +void IPARPi::applyDenoise(const struct DenoiseStatus *denoiseStatus, ControlList &ctrls) +{ + using RPiController::DenoiseMode; + + bcm2835_isp_denoise denoise; + DenoiseMode mode = static_cast(denoiseStatus->mode); + + denoise.enabled = mode != DenoiseMode::Off; + denoise.constant = denoiseStatus->noiseConstant; + denoise.slope.num = 1000 * denoiseStatus->noiseSlope; + denoise.slope.den = 1000; + denoise.strength.num = 1000 * denoiseStatus->strength; + denoise.strength.den = 1000; + + /* Set the CDN mode to match the SDN operating mode. */ + bcm2835_isp_cdn cdn; + switch (mode) { + case DenoiseMode::ColourFast: + cdn.enabled = 1; + cdn.mode = CDN_MODE_FAST; + break; + case DenoiseMode::ColourHighQuality: + cdn.enabled = 1; + cdn.mode = CDN_MODE_HIGH_QUALITY; + break; + default: + cdn.enabled = 0; + } + + ControlValue c(Span{ reinterpret_cast(&denoise), + sizeof(denoise) }); + ctrls.set(V4L2_CID_USER_BCM2835_ISP_DENOISE, c); + + c = ControlValue(Span{ reinterpret_cast(&cdn), + sizeof(cdn) }); + ctrls.set(V4L2_CID_USER_BCM2835_ISP_CDN, c); +} + +void IPARPi::applySharpen(const struct SharpenStatus *sharpenStatus, ControlList &ctrls) +{ + bcm2835_isp_sharpen sharpen; + + sharpen.enabled = 1; + sharpen.threshold.num = 1000 * sharpenStatus->threshold; + sharpen.threshold.den = 1000; + sharpen.strength.num = 1000 * sharpenStatus->strength; + sharpen.strength.den = 1000; + sharpen.limit.num = 1000 * sharpenStatus->limit; + sharpen.limit.den = 1000; + + ControlValue c(Span{ reinterpret_cast(&sharpen), + sizeof(sharpen) }); + ctrls.set(V4L2_CID_USER_BCM2835_ISP_SHARPEN, c); +} + +void IPARPi::applyDPC(const struct DpcStatus *dpcStatus, ControlList &ctrls) +{ + bcm2835_isp_dpc dpc; + + dpc.enabled = 1; + dpc.strength = dpcStatus->strength; + + ControlValue c(Span{ reinterpret_cast(&dpc), + sizeof(dpc) }); + ctrls.set(V4L2_CID_USER_BCM2835_ISP_DPC, c); +} + +void IPARPi::applyLS(const struct AlscStatus *lsStatus, ControlList &ctrls) +{ + /* + * Program lens shading tables into pipeline. + * Choose smallest cell size that won't exceed 63x48 cells. + */ + const int cellSizes[] = { 16, 32, 64, 128, 256 }; + unsigned int numCells = std::size(cellSizes); + unsigned int i, w, h, cellSize; + for (i = 0; i < numCells; i++) { + cellSize = cellSizes[i]; + w = (mode_.width + cellSize - 1) / cellSize; + h = (mode_.height + cellSize - 1) / cellSize; + if (w < 64 && h <= 48) + break; + } + + if (i == numCells) { + LOG(IPARPI, Error) << "Cannot find cell size"; + return; + } + + /* We're going to supply corner sampled tables, 16 bit samples. */ + w++, h++; + bcm2835_isp_lens_shading ls = { + .enabled = 1, + .grid_cell_size = cellSize, + .grid_width = w, + .grid_stride = w, + .grid_height = h, + /* .dmabuf will be filled in by pipeline handler. */ + .dmabuf = 0, + .ref_transform = 0, + .corner_sampled = 1, + .gain_format = GAIN_FORMAT_U4P10 + }; + + if (!lsTable_ || w * h * 4 * sizeof(uint16_t) > MaxLsGridSize) { + LOG(IPARPI, Error) << "Do not have a correctly allocate lens shading table!"; + return; + } + + if (lsStatus) { + /* Format will be u4.10 */ + uint16_t *grid = static_cast(lsTable_); + + resampleTable(grid, lsStatus->r, w, h); + resampleTable(grid + w * h, lsStatus->g, w, h); + std::memcpy(grid + 2 * w * h, grid + w * h, w * h * sizeof(uint16_t)); + resampleTable(grid + 3 * w * h, lsStatus->b, w, h); + } + + ControlValue c(Span{ reinterpret_cast(&ls), + sizeof(ls) }); + ctrls.set(V4L2_CID_USER_BCM2835_ISP_LENS_SHADING, c); +} + +void IPARPi::applyAF(const struct AfStatus *afStatus, ControlList &lensCtrls) +{ + if (afStatus->lensSetting) { + ControlValue v(afStatus->lensSetting.value()); + lensCtrls.set(V4L2_CID_FOCUS_ABSOLUTE, v); + } +} + +/* + * Resamples a 16x12 table with central sampling to destW x destH with corner + * sampling. + */ +void IPARPi::resampleTable(uint16_t dest[], const std::vector &src, + int destW, int destH) +{ + /* + * Precalculate and cache the x sampling locations and phases to + * save recomputing them on every row. + */ + assert(destW > 1 && destH > 1 && destW <= 64); + int xLo[64], xHi[64]; + double xf[64]; + double x = -0.5, xInc = 16.0 / (destW - 1); + for (int i = 0; i < destW; i++, x += xInc) { + xLo[i] = floor(x); + xf[i] = x - xLo[i]; + xHi[i] = xLo[i] < 15 ? xLo[i] + 1 : 15; + xLo[i] = xLo[i] > 0 ? xLo[i] : 0; + } + + /* Now march over the output table generating the new values. */ + double y = -0.5, yInc = 12.0 / (destH - 1); + for (int j = 0; j < destH; j++, y += yInc) { + int yLo = floor(y); + double yf = y - yLo; + int yHi = yLo < 11 ? yLo + 1 : 11; + yLo = yLo > 0 ? yLo : 0; + double const *rowAbove = src.data() + yLo * 16; + double const *rowBelow = src.data() + yHi * 16; + for (int i = 0; i < destW; i++) { + double above = rowAbove[xLo[i]] * (1 - xf[i]) + rowAbove[xHi[i]] * xf[i]; + double below = rowBelow[xLo[i]] * (1 - xf[i]) + rowBelow[xHi[i]] * xf[i]; + int result = floor(1024 * (above * (1 - yf) + below * yf) + .5); + *(dest++) = result > 16383 ? 16383 : result; /* want u4.10 */ + } + } +} + +} /* namespace ipa::RPi */ + +/* + * External IPA module interface + */ +extern "C" { +const struct IPAModuleInfo ipaModuleInfo = { + IPA_MODULE_API_VERSION, + 1, + "PipelineHandlerRPi", + "rpi/vc4", +}; + +IPAInterface *ipaCreate() +{ + return new ipa::RPi::IPARPi(); +} + +} /* extern "C" */ + +} /* namespace libcamera */ -- cgit v1.2.1