summaryrefslogtreecommitdiff
path: root/src/ipa/raspberrypi/raspberrypi.cpp
diff options
context:
space:
mode:
authorNaushir Patuck <naush@raspberrypi.com>2020-05-03 16:48:42 +0100
committerLaurent Pinchart <laurent.pinchart@ideasonboard.com>2020-05-11 23:54:40 +0300
commit0db2c8dc75e466e7648dc1b95380495c6a126349 (patch)
treefc723a251981ded749c900947a2f510ed56e60da /src/ipa/raspberrypi/raspberrypi.cpp
parent740fd1b62f670bd1ad4965ef0866ef5d51bdf947 (diff)
libcamera: ipa: Raspberry Pi IPA
Initial implementation of the Raspberry Pi (BCM2835) libcamera IPA and associated libraries. All code is licensed under the BSD-2-Clause terms. Copyright (c) 2019-2020 Raspberry Pi Trading Ltd. Signed-off-by: Naushir Patuck <naush@raspberrypi.com> Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Diffstat (limited to 'src/ipa/raspberrypi/raspberrypi.cpp')
-rw-r--r--src/ipa/raspberrypi/raspberrypi.cpp1088
1 files changed, 1088 insertions, 0 deletions
diff --git a/src/ipa/raspberrypi/raspberrypi.cpp b/src/ipa/raspberrypi/raspberrypi.cpp
new file mode 100644
index 00000000..3bcc0815
--- /dev/null
+++ b/src/ipa/raspberrypi/raspberrypi.cpp
@@ -0,0 +1,1088 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019-2020, Raspberry Pi (Trading) Ltd.
+ *
+ * rpi.cpp - Raspberry Pi Image Processing Algorithms
+ */
+
+#include <algorithm>
+#include <fcntl.h>
+#include <math.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/mman.h>
+
+#include <ipa/ipa_interface.h>
+#include <ipa/ipa_module_info.h>
+#include <ipa/raspberrypi.h>
+#include <libcamera/buffer.h>
+#include <libcamera/control_ids.h>
+#include <libcamera/controls.h>
+#include <libcamera/request.h>
+#include <libcamera/span.h>
+#include <libipa/ipa_interface_wrapper.h>
+
+#include <linux/bcm2835-isp.h>
+
+#include "agc_algorithm.hpp"
+#include "agc_status.h"
+#include "alsc_status.h"
+#include "awb_algorithm.hpp"
+#include "awb_status.h"
+#include "black_level_status.h"
+#include "cam_helper.hpp"
+#include "ccm_algorithm.hpp"
+#include "ccm_status.h"
+#include "contrast_algorithm.hpp"
+#include "contrast_status.h"
+#include "controller.hpp"
+#include "dpc_status.h"
+#include "geq_status.h"
+#include "lux_status.h"
+#include "metadata.hpp"
+#include "noise_status.h"
+#include "sdn_status.h"
+#include "sharpen_status.h"
+
+#include "camera_sensor.h"
+#include "log.h"
+#include "utils.h"
+
+namespace libcamera {
+
+/* Configure the sensor with these values initially. */
+#define DEFAULT_ANALOGUE_GAIN 1.0
+#define DEFAULT_EXPOSURE_TIME 20000
+
+LOG_DEFINE_CATEGORY(IPARPI)
+
+class IPARPi : public IPAInterface
+{
+public:
+ IPARPi()
+ : lastMode_({}), controller_(), controllerInit_(false),
+ frame_count_(0), check_count_(0), hide_count_(0),
+ mistrust_count_(0), lsTableHandle_(0), lsTable_(nullptr)
+ {
+ }
+
+ ~IPARPi()
+ {
+ }
+
+ int init(const IPASettings &settings) override;
+ int start() override { return 0; }
+ void stop() override {}
+
+ void configure(const CameraSensorInfo &sensorInfo,
+ const std::map<unsigned int, IPAStream> &streamConfig,
+ const std::map<unsigned int, const ControlInfoMap &> &entityControls) override;
+ void mapBuffers(const std::vector<IPABuffer> &buffers) override;
+ void unmapBuffers(const std::vector<unsigned int> &ids) override;
+ void processEvent(const IPAOperationData &event) override;
+
+private:
+ void setMode(const CameraSensorInfo &sensorInfo);
+ void queueRequest(const ControlList &controls);
+ void returnEmbeddedBuffer(unsigned int bufferId);
+ void prepareISP(unsigned int bufferId);
+ void reportMetadata();
+ bool parseEmbeddedData(unsigned int bufferId, struct DeviceStatus &deviceStatus);
+ void processStats(unsigned int bufferId);
+ void applyAGC(const struct AgcStatus *agcStatus);
+ 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 SdnStatus *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 resampleTable(uint16_t dest[], double const src[12][16], int dest_w, int dest_h);
+
+ std::map<unsigned int, FrameBuffer> buffers_;
+ std::map<unsigned int, void *> buffersMemory_;
+
+ ControlInfoMap unicam_ctrls_;
+ ControlInfoMap isp_ctrls_;
+ ControlList libcameraMetadata_;
+
+ /* IPA configuration. */
+ std::string tuningFile_;
+
+ /* Camera sensor params. */
+ CameraMode mode_;
+ CameraMode lastMode_;
+
+ /* Raspberry Pi controller specific defines. */
+ std::unique_ptr<RPi::CamHelper> helper_;
+ RPi::Controller controller_;
+ bool controllerInit_;
+ RPi::Metadata 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 frame_count_;
+ /* For checking the sequencing of Prepare/Process calls. */
+ uint64_t check_count_;
+ /* How many frames the pipeline handler should hide, or "drop". */
+ unsigned int hide_count_;
+ /* How many frames we should avoid running control algos on. */
+ unsigned int mistrust_count_;
+ /* LS table allocation passed in from the pipeline handler. */
+ uint32_t lsTableHandle_;
+ void *lsTable_;
+};
+
+int IPARPi::init(const IPASettings &settings)
+{
+ tuningFile_ = settings.configurationFile;
+ return 0;
+}
+
+void IPARPi::setMode(const CameraSensorInfo &sensorInfo)
+{
+ mode_.bitdepth = sensorInfo.bitsPerPixel;
+ mode_.width = sensorInfo.outputSize.width;
+ mode_.height = sensorInfo.outputSize.height;
+ mode_.sensor_width = sensorInfo.activeAreaSize.width;
+ mode_.sensor_height = sensorInfo.activeAreaSize.height;
+ mode_.crop_x = sensorInfo.analogCrop.x;
+ mode_.crop_y = sensorInfo.analogCrop.y;
+
+ /*
+ * Calculate scaling parameters. The scale_[xy] factors are determined
+ * by the ratio between the crop rectangle size and the output size.
+ */
+ mode_.scale_x = sensorInfo.analogCrop.width / sensorInfo.outputSize.width;
+ mode_.scale_y = 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_.bin_y = std::min(2, static_cast<int>(mode_.scale_x));
+ mode_.bin_y = std::min(2, static_cast<int>(mode_.scale_y));
+
+ /* The noise factor is the square root of the total binning factor. */
+ mode_.noise_factor = sqrt(mode_.bin_x * mode_.bin_y);
+
+ /*
+ * Calculate the line length in nanoseconds as the ratio between
+ * the line length in pixels and the pixel rate.
+ */
+ mode_.line_length = 1e9 * sensorInfo.lineLength / sensorInfo.pixelRate;
+}
+
+void IPARPi::configure(const CameraSensorInfo &sensorInfo,
+ const std::map<unsigned int, IPAStream> &streamConfig,
+ const std::map<unsigned int, const ControlInfoMap &> &entityControls)
+{
+ if (entityControls.empty())
+ return;
+
+ unicam_ctrls_ = entityControls.at(0);
+ isp_ctrls_ = entityControls.at(1);
+ /* Setup a metadata ControlList to output metadata. */
+ libcameraMetadata_ = ControlList(controls::controls);
+
+ /*
+ * 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.
+ */
+ std::string cameraName(sensorInfo.model);
+ if (!helper_) {
+ helper_ = std::unique_ptr<RPi::CamHelper>(RPi::CamHelper::Create(cameraName));
+ /*
+ * Pass out the sensor config to the pipeline handler in order
+ * to setup the staggered writer class.
+ */
+ int gainDelay, exposureDelay, sensorMetadata;
+ helper_->GetDelays(exposureDelay, gainDelay);
+ sensorMetadata = helper_->SensorEmbeddedDataPresent();
+ RPi::CamTransform orientation = helper_->GetOrientation();
+
+ IPAOperationData op;
+ op.operation = RPI_IPA_ACTION_SET_SENSOR_CONFIG;
+ op.data.push_back(gainDelay);
+ op.data.push_back(exposureDelay);
+ op.data.push_back(sensorMetadata);
+
+ ControlList ctrls(unicam_ctrls_);
+ ctrls.set(V4L2_CID_HFLIP, (int32_t) !!(orientation & RPi::CamTransform_HFLIP));
+ ctrls.set(V4L2_CID_VFLIP, (int32_t) !!(orientation & RPi::CamTransform_VFLIP));
+ op.controls.push_back(ctrls);
+
+ queueFrameAction.emit(0, op);
+ }
+
+ /* Re-assemble camera mode using the sensor info. */
+ setMode(sensorInfo);
+
+ /* Pass the camera mode to the CamHelper to setup algorithms. */
+ helper_->SetCameraMode(mode_);
+
+ /*
+ * 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.
+ */
+ frame_count_ = 0;
+ check_count_ = 0;
+ if (controllerInit_) {
+ hide_count_ = helper_->HideFramesModeSwitch();
+ mistrust_count_ = helper_->MistrustFramesModeSwitch();
+ } else {
+ hide_count_ = helper_->HideFramesStartup();
+ mistrust_count_ = helper_->MistrustFramesStartup();
+ }
+
+ if (!controllerInit_) {
+ /* Load the tuning file for this sensor. */
+ controller_.Read(tuningFile_.c_str());
+ controller_.Initialise();
+ controllerInit_ = true;
+
+ /* Calculate initial values for gain and exposure. */
+ int32_t gain_code = helper_->GainCode(DEFAULT_ANALOGUE_GAIN);
+ int32_t exposure_lines = helper_->ExposureLines(DEFAULT_EXPOSURE_TIME);
+
+ ControlList ctrls(unicam_ctrls_);
+ ctrls.set(V4L2_CID_ANALOGUE_GAIN, gain_code);
+ ctrls.set(V4L2_CID_EXPOSURE, exposure_lines);
+
+ IPAOperationData op;
+ op.operation = RPI_IPA_ACTION_V4L2_SET_STAGGERED;
+ op.controls.push_back(ctrls);
+ queueFrameAction.emit(0, op);
+ }
+
+ controller_.SwitchMode(mode_);
+
+ lastMode_ = mode_;
+}
+
+void IPARPi::mapBuffers(const std::vector<IPABuffer> &buffers)
+{
+ for (const IPABuffer &buffer : buffers) {
+ auto elem = buffers_.emplace(std::piecewise_construct,
+ std::forward_as_tuple(buffer.id),
+ std::forward_as_tuple(buffer.planes));
+ const FrameBuffer &fb = elem.first->second;
+
+ buffersMemory_[buffer.id] = mmap(nullptr, fb.planes()[0].length,
+ PROT_READ | PROT_WRITE, MAP_SHARED,
+ fb.planes()[0].fd.fd(), 0);
+
+ if (buffersMemory_[buffer.id] == MAP_FAILED) {
+ int ret = -errno;
+ LOG(IPARPI, Fatal) << "Failed to mmap buffer: " << strerror(-ret);
+ }
+ }
+}
+
+void IPARPi::unmapBuffers(const std::vector<unsigned int> &ids)
+{
+ for (unsigned int id : ids) {
+ const auto fb = buffers_.find(id);
+ if (fb == buffers_.end())
+ continue;
+
+ munmap(buffersMemory_[id], fb->second.planes()[0].length);
+ buffersMemory_.erase(id);
+ buffers_.erase(id);
+ }
+}
+
+void IPARPi::processEvent(const IPAOperationData &event)
+{
+ switch (event.operation) {
+ case RPI_IPA_EVENT_SIGNAL_STAT_READY: {
+ unsigned int bufferId = event.data[0];
+
+ if (++check_count_ != frame_count_) /* assert here? */
+ LOG(IPARPI, Error) << "WARNING: Prepare/Process mismatch!!!";
+ if (frame_count_ > mistrust_count_)
+ processStats(bufferId);
+
+ IPAOperationData op;
+ op.operation = RPI_IPA_ACTION_STATS_METADATA_COMPLETE;
+ op.data = { bufferId & RPiIpaMask::ID };
+ op.controls = { libcameraMetadata_ };
+ queueFrameAction.emit(0, op);
+ break;
+ }
+
+ case RPI_IPA_EVENT_SIGNAL_ISP_PREPARE: {
+ unsigned int embeddedbufferId = event.data[0];
+ unsigned int bayerbufferId = event.data[1];
+
+ /*
+ * 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(embeddedbufferId);
+ reportMetadata();
+
+ /* Ready to push the input buffer into the ISP. */
+ IPAOperationData op;
+ if (++frame_count_ > hide_count_)
+ op.operation = RPI_IPA_ACTION_RUN_ISP;
+ else
+ op.operation = RPI_IPA_ACTION_RUN_ISP_AND_DROP_FRAME;
+ op.data = { bayerbufferId & RPiIpaMask::ID };
+ queueFrameAction.emit(0, op);
+ break;
+ }
+
+ case RPI_IPA_EVENT_QUEUE_REQUEST: {
+ queueRequest(event.controls[0]);
+ break;
+ }
+
+ case RPI_IPA_EVENT_LS_TABLE_ALLOCATION: {
+ lsTable_ = reinterpret_cast<void *>(event.data[0]);
+ lsTableHandle_ = event.data[1];
+ break;
+ }
+
+ default:
+ LOG(IPARPI, Error) << "Unknown event " << event.operation;
+ break;
+ }
+}
+
+void IPARPi::reportMetadata()
+{
+ std::unique_lock<RPi::Metadata> 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<DeviceStatus>("device.status");
+ if (deviceStatus) {
+ libcameraMetadata_.set(controls::ExposureTime, deviceStatus->shutter_speed);
+ libcameraMetadata_.set(controls::AnalogueGain, deviceStatus->analogue_gain);
+ }
+
+ AgcStatus *agcStatus = rpiMetadata_.GetLocked<AgcStatus>("agc.status");
+ if (agcStatus)
+ libcameraMetadata_.set(controls::AeLocked, agcStatus->locked);
+
+ LuxStatus *luxStatus = rpiMetadata_.GetLocked<LuxStatus>("lux.status");
+ if (luxStatus)
+ libcameraMetadata_.set(controls::Lux, luxStatus->lux);
+
+ AwbStatus *awbStatus = rpiMetadata_.GetLocked<AwbStatus>("awb.status");
+ if (awbStatus) {
+ libcameraMetadata_.set(controls::ColourGains, { static_cast<float>(awbStatus->gain_r),
+ static_cast<float>(awbStatus->gain_b) });
+ libcameraMetadata_.set(controls::ColourTemperature, awbStatus->temperature_K);
+ }
+
+ BlackLevelStatus *blackLevelStatus = rpiMetadata_.GetLocked<BlackLevelStatus>("black_level.status");
+ if (blackLevelStatus)
+ libcameraMetadata_.set(controls::SensorBlackLevels,
+ { static_cast<int32_t>(blackLevelStatus->black_level_r),
+ static_cast<int32_t>(blackLevelStatus->black_level_g),
+ static_cast<int32_t>(blackLevelStatus->black_level_g),
+ static_cast<int32_t>(blackLevelStatus->black_level_b) });
+}
+
+/*
+ * 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<int32_t, std::string> MeteringModeTable = {
+ { controls::MeteringCentreWeighted, "centre-weighted" },
+ { controls::MeteringSpot, "spot" },
+ { controls::MeteringMatrix, "matrix" },
+ { controls::MeteringCustom, "custom" },
+};
+
+static const std::map<int32_t, std::string> ConstraintModeTable = {
+ { controls::ConstraintNormal, "normal" },
+ { controls::ConstraintHighlight, "highlight" },
+ { controls::ConstraintCustom, "custom" },
+};
+
+static const std::map<int32_t, std::string> ExposureModeTable = {
+ { controls::ExposureNormal, "normal" },
+ { controls::ExposureShort, "short" },
+ { controls::ExposureLong, "long" },
+ { controls::ExposureCustom, "custom" },
+};
+
+static const std::map<int32_t, std::string> AwbModeTable = {
+ { controls::AwbAuto, "normal" },
+ { controls::AwbIncandescent, "incandescent" },
+ { controls::AwbTungsten, "tungsten" },
+ { controls::AwbFluorescent, "fluorescent" },
+ { controls::AwbIndoor, "indoor" },
+ { controls::AwbDaylight, "daylight" },
+ { controls::AwbCustom, "custom" },
+};
+
+void IPARPi::queueRequest(const ControlList &controls)
+{
+ /* Clear the return metadata buffer. */
+ libcameraMetadata_.clear();
+
+ for (auto const &ctrl : controls) {
+ LOG(IPARPI, Info) << "Request ctrl: "
+ << controls::controls.at(ctrl.first)->name()
+ << " = " << ctrl.second.toString();
+
+ switch (ctrl.first) {
+ case controls::AE_ENABLE: {
+ RPi::Algorithm *agc = controller_.GetAlgorithm("agc");
+ ASSERT(agc);
+ if (ctrl.second.get<bool>() == false)
+ agc->Pause();
+ else
+ agc->Resume();
+
+ libcameraMetadata_.set(controls::AeEnable, ctrl.second.get<bool>());
+ break;
+ }
+
+ case controls::EXPOSURE_TIME: {
+ RPi::AgcAlgorithm *agc = dynamic_cast<RPi::AgcAlgorithm *>(
+ controller_.GetAlgorithm("agc"));
+ ASSERT(agc);
+ /* This expects units of micro-seconds. */
+ agc->SetFixedShutter(ctrl.second.get<int32_t>());
+ /* For the manual values to take effect, AGC must be unpaused. */
+ if (agc->IsPaused())
+ agc->Resume();
+
+ libcameraMetadata_.set(controls::ExposureTime, ctrl.second.get<int32_t>());
+ break;
+ }
+
+ case controls::ANALOGUE_GAIN: {
+ RPi::AgcAlgorithm *agc = dynamic_cast<RPi::AgcAlgorithm *>(
+ controller_.GetAlgorithm("agc"));
+ ASSERT(agc);
+ agc->SetFixedAnalogueGain(ctrl.second.get<float>());
+ /* For the manual values to take effect, AGC must be unpaused. */
+ if (agc->IsPaused())
+ agc->Resume();
+
+ libcameraMetadata_.set(controls::AnalogueGain,
+ ctrl.second.get<float>());
+ break;
+ }
+
+ case controls::AE_METERING_MODE: {
+ RPi::AgcAlgorithm *agc = dynamic_cast<RPi::AgcAlgorithm *>(
+ controller_.GetAlgorithm("agc"));
+ ASSERT(agc);
+
+ int32_t idx = ctrl.second.get<int32_t>();
+ 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: {
+ RPi::AgcAlgorithm *agc = dynamic_cast<RPi::AgcAlgorithm *>(
+ controller_.GetAlgorithm("agc"));
+ ASSERT(agc);
+
+ int32_t idx = ctrl.second.get<int32_t>();
+ 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: {
+ RPi::AgcAlgorithm *agc = dynamic_cast<RPi::AgcAlgorithm *>(
+ controller_.GetAlgorithm("agc"));
+ ASSERT(agc);
+
+ int32_t idx = ctrl.second.get<int32_t>();
+ 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: {
+ RPi::AgcAlgorithm *agc = dynamic_cast<RPi::AgcAlgorithm *>(
+ controller_.GetAlgorithm("agc"));
+ ASSERT(agc);
+
+ /*
+ * The SetEv() method takes in a direct exposure multiplier.
+ * So convert to 2^EV
+ */
+ double ev = pow(2.0, ctrl.second.get<float>());
+ agc->SetEv(ev);
+ libcameraMetadata_.set(controls::ExposureValue,
+ ctrl.second.get<float>());
+ break;
+ }
+
+ case controls::AWB_ENABLE: {
+ RPi::Algorithm *awb = controller_.GetAlgorithm("awb");
+ ASSERT(awb);
+
+ if (ctrl.second.get<bool>() == false)
+ awb->Pause();
+ else
+ awb->Resume();
+
+ libcameraMetadata_.set(controls::AwbEnable,
+ ctrl.second.get<bool>());
+ break;
+ }
+
+ case controls::AWB_MODE: {
+ RPi::AwbAlgorithm *awb = dynamic_cast<RPi::AwbAlgorithm *>(
+ controller_.GetAlgorithm("awb"));
+ ASSERT(awb);
+
+ int32_t idx = ctrl.second.get<int32_t>();
+ 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<Span<const float>>();
+ RPi::AwbAlgorithm *awb = dynamic_cast<RPi::AwbAlgorithm *>(
+ controller_.GetAlgorithm("awb"));
+ ASSERT(awb);
+
+ 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: {
+ RPi::ContrastAlgorithm *contrast = dynamic_cast<RPi::ContrastAlgorithm *>(
+ controller_.GetAlgorithm("contrast"));
+ ASSERT(contrast);
+
+ contrast->SetBrightness(ctrl.second.get<float>() * 65536);
+ libcameraMetadata_.set(controls::Brightness,
+ ctrl.second.get<float>());
+ break;
+ }
+
+ case controls::CONTRAST: {
+ RPi::ContrastAlgorithm *contrast = dynamic_cast<RPi::ContrastAlgorithm *>(
+ controller_.GetAlgorithm("contrast"));
+ ASSERT(contrast);
+
+ contrast->SetContrast(ctrl.second.get<float>());
+ libcameraMetadata_.set(controls::Contrast,
+ ctrl.second.get<float>());
+ break;
+ }
+
+ case controls::SATURATION: {
+ RPi::CcmAlgorithm *ccm = dynamic_cast<RPi::CcmAlgorithm *>(
+ controller_.GetAlgorithm("ccm"));
+ ASSERT(ccm);
+
+ ccm->SetSaturation(ctrl.second.get<float>());
+ libcameraMetadata_.set(controls::Saturation,
+ ctrl.second.get<float>());
+ break;
+ }
+
+ default:
+ LOG(IPARPI, Warning)
+ << "Ctrl " << controls::controls.at(ctrl.first)->name()
+ << " is not handled.";
+ break;
+ }
+ }
+}
+
+void IPARPi::returnEmbeddedBuffer(unsigned int bufferId)
+{
+ IPAOperationData op;
+ op.operation = RPI_IPA_ACTION_EMBEDDED_COMPLETE;
+ op.data = { bufferId & RPiIpaMask::ID };
+ queueFrameAction.emit(0, op);
+}
+
+void IPARPi::prepareISP(unsigned int bufferId)
+{
+ struct DeviceStatus deviceStatus = {};
+ bool success = parseEmbeddedData(bufferId, deviceStatus);
+
+ /* Done with embedded data now, return to pipeline handler asap. */
+ returnEmbeddedBuffer(bufferId);
+
+ if (success) {
+ ControlList ctrls(isp_ctrls_);
+
+ rpiMetadata_.Clear();
+ rpiMetadata_.Set("device.status", deviceStatus);
+ controller_.Prepare(&rpiMetadata_);
+
+ /* Lock the metadata buffer to avoid constant locks/unlocks. */
+ std::unique_lock<RPi::Metadata> lock(rpiMetadata_);
+
+ AwbStatus *awbStatus = rpiMetadata_.GetLocked<AwbStatus>("awb.status");
+ if (awbStatus)
+ applyAWB(awbStatus, ctrls);
+
+ CcmStatus *ccmStatus = rpiMetadata_.GetLocked<CcmStatus>("ccm.status");
+ if (ccmStatus)
+ applyCCM(ccmStatus, ctrls);
+
+ AgcStatus *dgStatus = rpiMetadata_.GetLocked<AgcStatus>("agc.status");
+ if (dgStatus)
+ applyDG(dgStatus, ctrls);
+
+ AlscStatus *lsStatus = rpiMetadata_.GetLocked<AlscStatus>("alsc.status");
+ if (lsStatus)
+ applyLS(lsStatus, ctrls);
+
+ ContrastStatus *contrastStatus = rpiMetadata_.GetLocked<ContrastStatus>("contrast.status");
+ if (contrastStatus)
+ applyGamma(contrastStatus, ctrls);
+
+ BlackLevelStatus *blackLevelStatus = rpiMetadata_.GetLocked<BlackLevelStatus>("black_level.status");
+ if (blackLevelStatus)
+ applyBlackLevel(blackLevelStatus, ctrls);
+
+ GeqStatus *geqStatus = rpiMetadata_.GetLocked<GeqStatus>("geq.status");
+ if (geqStatus)
+ applyGEQ(geqStatus, ctrls);
+
+ SdnStatus *denoiseStatus = rpiMetadata_.GetLocked<SdnStatus>("sdn.status");
+ if (denoiseStatus)
+ applyDenoise(denoiseStatus, ctrls);
+
+ SharpenStatus *sharpenStatus = rpiMetadata_.GetLocked<SharpenStatus>("sharpen.status");
+ if (sharpenStatus)
+ applySharpen(sharpenStatus, ctrls);
+
+ DpcStatus *dpcStatus = rpiMetadata_.GetLocked<DpcStatus>("dpc.status");
+ if (dpcStatus)
+ applyDPC(dpcStatus, ctrls);
+
+ if (!ctrls.empty()) {
+ IPAOperationData op;
+ op.operation = RPI_IPA_ACTION_V4L2_SET_ISP;
+ op.controls.push_back(ctrls);
+ queueFrameAction.emit(0, op);
+ }
+ }
+}
+
+bool IPARPi::parseEmbeddedData(unsigned int bufferId, struct DeviceStatus &deviceStatus)
+{
+ auto it = buffersMemory_.find(bufferId);
+ if (it == buffersMemory_.end()) {
+ LOG(IPARPI, Error) << "Could not find embedded buffer!";
+ return false;
+ }
+
+ int size = buffers_.find(bufferId)->second.planes()[0].length;
+ helper_->Parser().SetBufferSize(size);
+ RPi::MdParser::Status status = helper_->Parser().Parse(it->second);
+ if (status != RPi::MdParser::Status::OK) {
+ LOG(IPARPI, Error) << "Embedded Buffer parsing failed, error " << status;
+ } else {
+ uint32_t exposure_lines, gain_code;
+ if (helper_->Parser().GetExposureLines(exposure_lines) != RPi::MdParser::Status::OK) {
+ LOG(IPARPI, Error) << "Exposure time failed";
+ return false;
+ }
+
+ deviceStatus.shutter_speed = helper_->Exposure(exposure_lines);
+ if (helper_->Parser().GetGainCode(gain_code) != RPi::MdParser::Status::OK) {
+ LOG(IPARPI, Error) << "Gain failed";
+ return false;
+ }
+
+ deviceStatus.analogue_gain = helper_->Gain(gain_code);
+ LOG(IPARPI, Debug) << "Metadata - Exposure : "
+ << deviceStatus.shutter_speed << " Gain : "
+ << deviceStatus.analogue_gain;
+ }
+
+ return true;
+}
+
+void IPARPi::processStats(unsigned int bufferId)
+{
+ auto it = buffersMemory_.find(bufferId);
+ if (it == buffersMemory_.end()) {
+ LOG(IPARPI, Error) << "Could not find stats buffer!";
+ return;
+ }
+
+ bcm2835_isp_stats *stats = static_cast<bcm2835_isp_stats *>(it->second);
+ RPi::StatisticsPtr statistics = std::make_shared<bcm2835_isp_stats>(*stats);
+ controller_.Process(statistics, &rpiMetadata_);
+
+ struct AgcStatus agcStatus;
+ if (rpiMetadata_.Get("agc.status", agcStatus) == 0)
+ applyAGC(&agcStatus);
+}
+
+void IPARPi::applyAWB(const struct AwbStatus *awbStatus, ControlList &ctrls)
+{
+ const auto gainR = isp_ctrls_.find(V4L2_CID_RED_BALANCE);
+ if (gainR == isp_ctrls_.end()) {
+ LOG(IPARPI, Error) << "Can't find red gain control";
+ return;
+ }
+
+ const auto gainB = isp_ctrls_.find(V4L2_CID_BLUE_BALANCE);
+ if (gainB == isp_ctrls_.end()) {
+ LOG(IPARPI, Error) << "Can't find blue gain control";
+ return;
+ }
+
+ LOG(IPARPI, Debug) << "Applying WB R: " << awbStatus->gain_r << " B: "
+ << awbStatus->gain_b;
+
+ ctrls.set(V4L2_CID_RED_BALANCE,
+ static_cast<int32_t>(awbStatus->gain_r * 1000));
+ ctrls.set(V4L2_CID_BLUE_BALANCE,
+ static_cast<int32_t>(awbStatus->gain_b * 1000));
+}
+
+void IPARPi::applyAGC(const struct AgcStatus *agcStatus)
+{
+ IPAOperationData op;
+ op.operation = RPI_IPA_ACTION_V4L2_SET_STAGGERED;
+
+ int32_t gain_code = helper_->GainCode(agcStatus->analogue_gain);
+ int32_t exposure_lines = helper_->ExposureLines(agcStatus->shutter_time);
+
+ if (unicam_ctrls_.find(V4L2_CID_ANALOGUE_GAIN) == unicam_ctrls_.end()) {
+ LOG(IPARPI, Error) << "Can't find analogue gain control";
+ return;
+ }
+
+ if (unicam_ctrls_.find(V4L2_CID_EXPOSURE) == unicam_ctrls_.end()) {
+ LOG(IPARPI, Error) << "Can't find exposure control";
+ return;
+ }
+
+ LOG(IPARPI, Debug) << "Applying AGC Exposure: " << agcStatus->shutter_time
+ << " (Shutter lines: " << exposure_lines << ") Gain: "
+ << agcStatus->analogue_gain << " (Gain Code: "
+ << gain_code << ")";
+
+ ControlList ctrls(unicam_ctrls_);
+ ctrls.set(V4L2_CID_ANALOGUE_GAIN, gain_code);
+ ctrls.set(V4L2_CID_EXPOSURE, exposure_lines);
+ op.controls.push_back(ctrls);
+ queueFrameAction.emit(0, op);
+}
+
+void IPARPi::applyDG(const struct AgcStatus *dgStatus, ControlList &ctrls)
+{
+ if (isp_ctrls_.find(V4L2_CID_DIGITAL_GAIN) == isp_ctrls_.end()) {
+ LOG(IPARPI, Error) << "Can't find digital gain control";
+ return;
+ }
+
+ ctrls.set(V4L2_CID_DIGITAL_GAIN,
+ static_cast<int32_t>(dgStatus->digital_gain * 1000));
+}
+
+void IPARPi::applyCCM(const struct CcmStatus *ccmStatus, ControlList &ctrls)
+{
+ if (isp_ctrls_.find(V4L2_CID_USER_BCM2835_ISP_CC_MATRIX) == isp_ctrls_.end()) {
+ LOG(IPARPI, Error) << "Can't find CCM control";
+ return;
+ }
+
+ 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<const uint8_t>{ reinterpret_cast<uint8_t *>(&ccm),
+ sizeof(ccm) });
+ ctrls.set(V4L2_CID_USER_BCM2835_ISP_CC_MATRIX, c);
+}
+
+void IPARPi::applyGamma(const struct ContrastStatus *contrastStatus, ControlList &ctrls)
+{
+ if (isp_ctrls_.find(V4L2_CID_USER_BCM2835_ISP_GAMMA) == isp_ctrls_.end()) {
+ LOG(IPARPI, Error) << "Can't find Gamma control";
+ return;
+ }
+
+ struct bcm2835_isp_gamma gamma;
+ gamma.enabled = 1;
+ for (int i = 0; i < CONTRAST_NUM_POINTS; i++) {
+ gamma.x[i] = contrastStatus->points[i].x;
+ gamma.y[i] = contrastStatus->points[i].y;
+ }
+
+ ControlValue c(Span<const uint8_t>{ reinterpret_cast<uint8_t *>(&gamma),
+ sizeof(gamma) });
+ ctrls.set(V4L2_CID_USER_BCM2835_ISP_GAMMA, c);
+}
+
+void IPARPi::applyBlackLevel(const struct BlackLevelStatus *blackLevelStatus, ControlList &ctrls)
+{
+ if (isp_ctrls_.find(V4L2_CID_USER_BCM2835_ISP_BLACK_LEVEL) == isp_ctrls_.end()) {
+ LOG(IPARPI, Error) << "Can't find black level control";
+ return;
+ }
+
+ bcm2835_isp_black_level blackLevel;
+ blackLevel.enabled = 1;
+ blackLevel.black_level_r = blackLevelStatus->black_level_r;
+ blackLevel.black_level_g = blackLevelStatus->black_level_g;
+ blackLevel.black_level_b = blackLevelStatus->black_level_b;
+
+ ControlValue c(Span<const uint8_t>{ reinterpret_cast<uint8_t *>(&blackLevel),
+ sizeof(blackLevel) });
+ ctrls.set(V4L2_CID_USER_BCM2835_ISP_BLACK_LEVEL, c);
+}
+
+void IPARPi::applyGEQ(const struct GeqStatus *geqStatus, ControlList &ctrls)
+{
+ if (isp_ctrls_.find(V4L2_CID_USER_BCM2835_ISP_GEQ) == isp_ctrls_.end()) {
+ LOG(IPARPI, Error) << "Can't find geq control";
+ return;
+ }
+
+ bcm2835_isp_geq geq;
+ geq.enabled = 1;
+ geq.offset = geqStatus->offset;
+ geq.slope.den = 1000;
+ geq.slope.num = 1000 * geqStatus->slope;
+
+ ControlValue c(Span<const uint8_t>{ reinterpret_cast<uint8_t *>(&geq),
+ sizeof(geq) });
+ ctrls.set(V4L2_CID_USER_BCM2835_ISP_GEQ, c);
+}
+
+void IPARPi::applyDenoise(const struct SdnStatus *denoiseStatus, ControlList &ctrls)
+{
+ if (isp_ctrls_.find(V4L2_CID_USER_BCM2835_ISP_DENOISE) == isp_ctrls_.end()) {
+ LOG(IPARPI, Error) << "Can't find denoise control";
+ return;
+ }
+
+ bcm2835_isp_denoise denoise;
+ denoise.enabled = 1;
+ denoise.constant = denoiseStatus->noise_constant;
+ denoise.slope.num = 1000 * denoiseStatus->noise_slope;
+ denoise.slope.den = 1000;
+ denoise.strength.num = 1000 * denoiseStatus->strength;
+ denoise.strength.den = 1000;
+
+ ControlValue c(Span<const uint8_t>{ reinterpret_cast<uint8_t *>(&denoise),
+ sizeof(denoise) });
+ ctrls.set(V4L2_CID_USER_BCM2835_ISP_DENOISE, c);
+}
+
+void IPARPi::applySharpen(const struct SharpenStatus *sharpenStatus, ControlList &ctrls)
+{
+ if (isp_ctrls_.find(V4L2_CID_USER_BCM2835_ISP_SHARPEN) == isp_ctrls_.end()) {
+ LOG(IPARPI, Error) << "Can't find sharpen control";
+ return;
+ }
+
+ 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<const uint8_t>{ reinterpret_cast<uint8_t *>(&sharpen),
+ sizeof(sharpen) });
+ ctrls.set(V4L2_CID_USER_BCM2835_ISP_SHARPEN, c);
+}
+
+void IPARPi::applyDPC(const struct DpcStatus *dpcStatus, ControlList &ctrls)
+{
+ if (isp_ctrls_.find(V4L2_CID_USER_BCM2835_ISP_DPC) == isp_ctrls_.end()) {
+ LOG(IPARPI, Error) << "Can't find DPC control";
+ return;
+ }
+
+ bcm2835_isp_dpc dpc;
+ dpc.enabled = 1;
+ dpc.strength = dpcStatus->strength;
+
+ ControlValue c(Span<const uint8_t>{ reinterpret_cast<uint8_t *>(&dpc),
+ sizeof(dpc) });
+ ctrls.set(V4L2_CID_USER_BCM2835_ISP_DPC, c);
+}
+
+void IPARPi::applyLS(const struct AlscStatus *lsStatus, ControlList &ctrls)
+{
+ if (isp_ctrls_.find(V4L2_CID_USER_BCM2835_ISP_LENS_SHADING) == isp_ctrls_.end()) {
+ LOG(IPARPI, Error) << "Can't find LS control";
+ return;
+ }
+
+ /*
+ * Program lens shading tables into pipeline.
+ * Choose smallest cell size that won't exceed 63x48 cells.
+ */
+ const int cell_sizes[] = { 16, 32, 64, 128, 256 };
+ unsigned int num_cells = ARRAY_SIZE(cell_sizes);
+ unsigned int i, w, h, cell_size;
+ for (i = 0; i < num_cells; i++) {
+ cell_size = cell_sizes[i];
+ w = (mode_.width + cell_size - 1) / cell_size;
+ h = (mode_.height + cell_size - 1) / cell_size;
+ if (w < 64 && h <= 48)
+ break;
+ }
+
+ if (i == num_cells) {
+ 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 = cell_size,
+ .grid_width = w,
+ .grid_stride = w,
+ .grid_height = h,
+ .mem_handle_table = lsTableHandle_,
+ .ref_transform = 0,
+ .corner_sampled = 1,
+ .gain_format = GAIN_FORMAT_U4P10
+ };
+
+ if (!lsTable_ || w * h * 4 * sizeof(uint16_t) > MAX_LS_GRID_SIZE) {
+ 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<uint16_t *>(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<const uint8_t>{ reinterpret_cast<uint8_t *>(&ls),
+ sizeof(ls) });
+ ctrls.set(V4L2_CID_USER_BCM2835_ISP_LENS_SHADING, c);
+}
+
+/*
+ * Resamples a 16x12 table with central sampling to dest_w x dest_h with corner
+ * sampling.
+ */
+void IPARPi::resampleTable(uint16_t dest[], double const src[12][16],
+ int dest_w, int dest_h)
+{
+ /*
+ * Precalculate and cache the x sampling locations and phases to
+ * save recomputing them on every row.
+ */
+ assert(dest_w > 1 && dest_h > 1 && dest_w <= 64);
+ int x_lo[64], x_hi[64];
+ double xf[64];
+ double x = -0.5, x_inc = 16.0 / (dest_w - 1);
+ for (int i = 0; i < dest_w; i++, x += x_inc) {
+ x_lo[i] = floor(x);
+ xf[i] = x - x_lo[i];
+ x_hi[i] = x_lo[i] < 15 ? x_lo[i] + 1 : 15;
+ x_lo[i] = x_lo[i] > 0 ? x_lo[i] : 0;
+ }
+
+ /* Now march over the output table generating the new values. */
+ double y = -0.5, y_inc = 12.0 / (dest_h - 1);
+ for (int j = 0; j < dest_h; j++, y += y_inc) {
+ int y_lo = floor(y);
+ double yf = y - y_lo;
+ int y_hi = y_lo < 11 ? y_lo + 1 : 11;
+ y_lo = y_lo > 0 ? y_lo : 0;
+ double const *row_above = src[y_lo];
+ double const *row_below = src[y_hi];
+ for (int i = 0; i < dest_w; i++) {
+ double above = row_above[x_lo[i]] * (1 - xf[i])
+ + row_above[x_hi[i]] * xf[i];
+ double below = row_below[x_lo[i]] * (1 - xf[i])
+ + row_below[x_hi[i]] * xf[i];
+ int result = floor(1024 * (above * (1 - yf) + below * yf) + .5);
+ *(dest++) = result > 16383 ? 16383 : result; /* want u4.10 */
+ }
+ }
+}
+
+/*
+ * External IPA module interface
+ */
+
+extern "C" {
+const struct IPAModuleInfo ipaModuleInfo = {
+ IPA_MODULE_API_VERSION,
+ 1,
+ "PipelineHandlerRPi",
+ "raspberrypi",
+};
+
+struct ipa_context *ipaCreate()
+{
+ return new IPAInterfaceWrapper(std::make_unique<IPARPi>());
+}
+
+}; /* extern "C" */
+
+} /* namespace libcamera */