libcamera/src/ipa/rpi/common/ipa_base.cpp
Laurent Pinchart d0478c41f4 libcamera: Rename "shutter speed" to "exposure time"
The terms "shutter" and "shutter speed" are used through libcamera to
mean "exposure time". This is confusing, both due to "speed" being used
as "time" while it should be the inverse (i.e. a maximum speed should
correspond to the minimum time), and due to "shutter speed" and
"exposure time" being used in different places with the same meaning.

To improve clarity of the code base and the documentation, use "exposure
time" consistently to replace "shutter speed".

This rename highlighted another vocabulary issue in libcamera. The
ExposureModeHelper::splitExposure() function used to document that it
splits "exposure time into shutter time and gain". It has been reworded
to "split exposure into exposure time and gain". That is not entirely
satisfactory, as "exposure" has a defined meaning in photography (see
https://en.wikipedia.org/wiki/Exposure_(photography)) that is not
expressed as a duration. This issue if left to be addressed separately.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Naushir Patuck <naush@raspberrypi.com>
Acked-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
2024-11-26 19:05:17 +02:00

1531 lines
49 KiB
C++

/* SPDX-License-Identifier: BSD-2-Clause */
/*
* Copyright (C) 2019-2023, Raspberry Pi Ltd
*
* Raspberry Pi IPA base class
*/
#include "ipa_base.h"
#include <cmath>
#include <libcamera/base/log.h>
#include <libcamera/base/span.h>
#include <libcamera/control_ids.h>
#include <libcamera/property_ids.h>
#include "controller/af_algorithm.h"
#include "controller/af_status.h"
#include "controller/agc_algorithm.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/denoise_algorithm.h"
#include "controller/hdr_algorithm.h"
#include "controller/lux_status.h"
#include "controller/sharpen_algorithm.h"
#include "controller/statistics.h"
namespace libcamera {
using namespace std::literals::chrono_literals;
using utils::Duration;
namespace {
/* 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 */
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::AeFlickerMode, ControlInfo(static_cast<int>(controls::FlickerOff),
static_cast<int>(controls::FlickerManual),
static_cast<int>(controls::FlickerOff)) },
{ &controls::AeFlickerPeriod, ControlInfo(100, 1000000) },
{ &controls::Brightness, ControlInfo(-1.0f, 1.0f, 0.0f) },
{ &controls::Contrast, ControlInfo(0.0f, 32.0f, 1.0f) },
{ &controls::HdrMode, ControlInfo(controls::HdrModeValues) },
{ &controls::Sharpness, ControlInfo(0.0f, 16.0f, 1.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) },
{ &controls::rpi::StatsOutputEnable, ControlInfo(false, true, false) },
};
/* IPA controls handled conditionally, if the sensor is not mono */
const ControlInfoMap::Map ipaColourControls{
{ &controls::AwbEnable, ControlInfo(false, true) },
{ &controls::AwbMode, ControlInfo(controls::AwbModeValues) },
{ &controls::ColourGains, ControlInfo(0.0f, 32.0f) },
{ &controls::Saturation, ControlInfo(0.0f, 32.0f, 1.0f) },
};
/* IPA controls handled conditionally, if the lens has a focus control */
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) }
};
/* Platform specific controls */
const std::map<const std::string, ControlInfoMap::Map> platformControls {
{ "pisp", {
{ &controls::rpi::ScalerCrops, ControlInfo(Rectangle{}, Rectangle(65535, 65535, 65535, 65535), Rectangle{}) }
} },
};
} /* namespace */
LOG_DEFINE_CATEGORY(IPARPI)
namespace ipa::RPi {
IpaBase::IpaBase()
: controller_(), frameLengths_(FrameLengthsQueueSize, 0s), statsMetadataOutput_(false),
stitchSwapBuffers_(false), frameCount_(0), mistrustCount_(0), lastRunTimestamp_(0),
firstStart_(true), flickerState_({ 0, 0s })
{
}
IpaBase::~IpaBase()
{
}
int32_t IpaBase::init(const IPASettings &settings, const InitParams &params, InitResult *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>(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;
}
lensPresent_ = params.lensPresent;
controller_.initialise();
/* Return the controls handled by the IPA */
ControlInfoMap::Map ctrlMap = ipaControls;
if (lensPresent_)
ctrlMap.merge(ControlInfoMap::Map(ipaAfControls));
auto platformCtrlsIt = platformControls.find(controller_.getTarget());
if (platformCtrlsIt != platformControls.end())
ctrlMap.merge(ControlInfoMap::Map(platformCtrlsIt->second));
monoSensor_ = params.sensorInfo.cfaPattern == properties::draft::ColorFilterArrangementEnum::MONO;
if (!monoSensor_)
ctrlMap.merge(ControlInfoMap::Map(ipaColourControls));
result->controlInfo = ControlInfoMap(std::move(ctrlMap), controls::controls);
return platformInit(params, result);
}
int32_t IpaBase::configure(const IPACameraSensorInfo &sensorInfo, const ConfigParams &params,
ConfigResult *result)
{
sensorCtrls_ = params.sensorControls;
if (!validateSensorControls()) {
LOG(IPARPI, Error) << "Sensor control validation failed.";
return -1;
}
if (lensPresent_) {
lensCtrls_ = params.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<libcamera::Transform>(params.transform);
/* 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.exposureTime = defaultExposureTime;
agcStatus.analogueGain = defaultAnalogueGain;
applyAGC(&agcStatus, ctrls);
/*
* Set the lens to the default (typically hyperfocal) position
* on first start.
*/
if (lensPresent_) {
RPiController::AfAlgorithm *af =
dynamic_cast<RPiController::AfAlgorithm *>(controller_.getAlgorithm("af"));
if (af) {
float defaultPos =
ipaAfControls.at(&controls::LensPosition).def().get<float>();
ControlList lensCtrl(lensCtrls_);
int32_t hwpos;
af->setLensPosition(defaultPos, &hwpos);
lensCtrl.set(V4L2_CID_FOCUS_ABSOLUTE, hwpos);
result->lensControls = std::move(lensCtrl);
}
}
}
result->sensorControls = 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<int64_t>(mode_.minFrameDuration.get<std::micro>()),
static_cast<int64_t>(mode_.maxFrameDuration.get<std::micro>()));
ctrlMap[&controls::AnalogueGain] =
ControlInfo(static_cast<float>(mode_.minAnalogueGain),
static_cast<float>(mode_.maxAnalogueGain));
ctrlMap[&controls::ExposureTime] =
ControlInfo(static_cast<int32_t>(mode_.minExposureTime.get<std::micro>()),
static_cast<int32_t>(mode_.maxExposureTime.get<std::micro>()));
/* Declare colour processing related controls for non-mono sensors. */
if (!monoSensor_)
ctrlMap.merge(ControlInfoMap::Map(ipaColourControls));
/* 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 platformConfigure(params, result);
}
void IpaBase::start(const ControlList &controls, StartResult *result)
{
RPiController::Metadata metadata;
if (!controls.empty()) {
/* We have been given some controls to action before start. */
applyControls(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.exposureTime = 0.0s;
agcStatus.analogueGain = 0.0;
metadata.get("agc.status", agcStatus);
if (agcStatus.exposureTime && agcStatus.analogueGain) {
ControlList ctrls(sensorCtrls_);
applyAGC(&agcStatus, ctrls);
result->controls = std::move(ctrls);
setCameraTimeoutValue();
}
/* Make a note of this as it tells us the HDR status of the first few frames. */
hdrStatus_ = agcStatus.hdr;
/*
* 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;
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<RPiController::AgcAlgorithm *>(
controller_.getAlgorithm("agc"));
if (agc) {
agcConvergenceFrames = agc->getConvergenceFrames();
if (agcConvergenceFrames)
agcConvergenceFrames += mistrustCount_;
}
unsigned int awbConvergenceFrames = 0;
RPiController::AwbAlgorithm *awb = dynamic_cast<RPiController::AwbAlgorithm *>(
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();
}
result->dropFrameCount = dropFrameCount_;
firstStart_ = false;
lastRunTimestamp_ = 0;
platformStart(controls, result);
}
void IpaBase::mapBuffers(const std::vector<IPABuffer> &buffers)
{
for (const IPABuffer &buffer : buffers) {
const FrameBuffer fb(buffer.planes);
buffers_.emplace(buffer.id,
MappedFrameBuffer(&fb, MappedFrameBuffer::MapFlag::ReadWrite));
}
}
void IpaBase::unmapBuffers(const std::vector<unsigned int> &ids)
{
for (unsigned int id : ids) {
auto it = buffers_.find(id);
if (it == buffers_.end())
continue;
buffers_.erase(id);
}
}
void IpaBase::prepareIsp(const PrepareParams &params)
{
applyControls(params.requestControls);
/*
* 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".
*/
int64_t frameTimestamp = params.sensorControls.get(controls::SensorTimestamp).value_or(0);
unsigned int ipaContext = params.ipaContext % rpiMetadata_.size();
RPiController::Metadata &rpiMetadata = rpiMetadata_[ipaContext];
Span<uint8_t> embeddedBuffer;
rpiMetadata.clear();
fillDeviceStatus(params.sensorControls, ipaContext);
if (params.buffers.embedded) {
/*
* Pipeline handler has supplied us with an embedded data buffer,
* we must pass it to the CamHelper for parsing.
*/
auto it = buffers_.find(params.buffers.embedded);
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.
*
* Note if the HDR mode has changed, as things like tonemaps may need updating.
*/
AgcStatus agcStatus;
bool hdrChange = false;
RPiController::Metadata &delayedMetadata = rpiMetadata_[params.delayContext];
if (!delayedMetadata.get<AgcStatus>("agc.status", agcStatus)) {
rpiMetadata.set("agc.delayed_status", agcStatus);
hdrChange = agcStatus.hdr.mode != hdrStatus_.mode;
hdrStatus_ = agcStatus.hdr;
}
/*
* This may overwrite the DeviceStatus using values from the sensor
* metadata, and may also do additional custom processing.
*/
helper_->prepare(embeddedBuffer, rpiMetadata);
/* Allow a 10% margin on the comparison below. */
Duration delta = (frameTimestamp - lastRunTimestamp_) * 1.0ns;
if (lastRunTimestamp_ && frameCount_ > dropFrameCount_ &&
delta < controllerMinFrameDuration * 0.9 && !hdrChange) {
/*
* 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;
} else {
processPending_ = true;
lastRunTimestamp_ = frameTimestamp;
}
/*
* If the statistics are inline (i.e. already available with the Bayer
* frame), call processStats() now before prepare().
*/
if (controller_.getHardwareConfig().statsInline)
processStats({ params.buffers, params.ipaContext });
/* Do we need/want to call prepare? */
if (processPending_) {
controller_.prepare(&rpiMetadata);
/* Actually prepare the ISP parameters for the frame. */
platformPrepareIsp(params, rpiMetadata);
}
frameCount_++;
/* If the statistics are inline the metadata can be returned early. */
if (controller_.getHardwareConfig().statsInline)
reportMetadata(ipaContext);
/* Ready to push the input buffer into the ISP. */
prepareIspComplete.emit(params.buffers, stitchSwapBuffers_);
}
void IpaBase::processStats(const ProcessParams &params)
{
unsigned int ipaContext = params.ipaContext % rpiMetadata_.size();
if (processPending_ && frameCount_ >= mistrustCount_) {
RPiController::Metadata &rpiMetadata = rpiMetadata_[ipaContext];
auto it = buffers_.find(params.buffers.stats);
if (it == buffers_.end()) {
LOG(IPARPI, Error) << "Could not find stats buffer!";
return;
}
RPiController::StatisticsPtr statistics = platformProcessStats(it->second.planes()[0]);
/* reportMetadata() will pick this up and set the FocusFoM metadata */
rpiMetadata.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();
}
}
/*
* If the statistics are not inline the metadata must be returned now,
* before the processStatsComplete signal.
*/
if (!controller_.getHardwareConfig().statsInline)
reportMetadata(ipaContext);
processStatsComplete.emit(params.buffers);
}
void IpaBase::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<int>(mode_.scaleX));
mode_.binY = std::min(2, static_cast<int>(mode_.scaleY));
/* The noise factor is the square root of the total binning factor. */
mode_.noiseFactor = std::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);
/*
* Ensure that the maximum pixel processing rate does not exceed the ISP
* hardware capabilities. If it does, try adjusting the minimum line
* length to compensate if possible.
*/
Duration minPixelTime = controller_.getHardwareConfig().minPixelProcessingTime;
Duration pixelTime = mode_.minLineLength / mode_.width;
if (minPixelTime && pixelTime < minPixelTime) {
Duration adjustedLineLength = minPixelTime * mode_.width;
if (adjustedLineLength <= mode_.maxLineLength) {
LOG(IPARPI, Info)
<< "Adjusting mode minimum line length from " << mode_.minLineLength
<< " to " << adjustedLineLength << " because of ISP constraints.";
mode_.minLineLength = adjustedLineLength;
} else {
LOG(IPARPI, Error)
<< "Sensor minimum line length of " << pixelTime * mode_.width
<< " (" << 1us / pixelTime << " MPix/s)"
<< " is below the minimum allowable ISP limit of "
<< adjustedLineLength
<< " (" << 1us / minPixelTime << " MPix/s) ";
LOG(IPARPI, Error)
<< "THIS WILL CAUSE IMAGE CORRUPTION!!! "
<< "Please update the camera sensor driver to allow more horizontal blanking control.";
}
}
/*
* 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 &exposureTimeCtrl = sensorCtrls_.at(V4L2_CID_EXPOSURE);
mode_.minAnalogueGain = helper_->gain(gainCtrl.min().get<int32_t>());
mode_.maxAnalogueGain = helper_->gain(gainCtrl.max().get<int32_t>());
/*
* We need to give the helper the min/max frame durations so it can calculate
* the correct exposure limits below.
*/
helper_->setCameraMode(mode_);
/*
* Exposure time is calculated based on the limits of the frame
* durations.
*/
mode_.minExposureTime = helper_->exposure(exposureTimeCtrl.min().get<int32_t>(),
mode_.minLineLength);
mode_.maxExposureTime = Duration::max();
helper_->getBlanking(mode_.maxExposureTime, mode_.minFrameDuration,
mode_.maxFrameDuration);
}
void IpaBase::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<std::milli>());
lastTimeout_ = *max;
}
}
bool IpaBase::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 IpaBase::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<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::ConstraintShadows, "shadows" },
{ 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, "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<int32_t, RPiController::AfAlgorithm::AfMode> AfModeTable = {
{ controls::AfModeManual, RPiController::AfAlgorithm::AfModeManual },
{ controls::AfModeAuto, RPiController::AfAlgorithm::AfModeAuto },
{ controls::AfModeContinuous, RPiController::AfAlgorithm::AfModeContinuous },
};
static const std::map<int32_t, RPiController::AfAlgorithm::AfRange> AfRangeTable = {
{ controls::AfRangeNormal, RPiController::AfAlgorithm::AfRangeNormal },
{ controls::AfRangeMacro, RPiController::AfAlgorithm::AfRangeMacro },
{ controls::AfRangeFull, RPiController::AfAlgorithm::AfRangeFull },
};
static const std::map<int32_t, RPiController::AfAlgorithm::AfPause> AfPauseTable = {
{ controls::AfPauseImmediate, RPiController::AfAlgorithm::AfPauseImmediate },
{ controls::AfPauseDeferred, RPiController::AfAlgorithm::AfPauseDeferred },
{ controls::AfPauseResume, RPiController::AfAlgorithm::AfPauseResume },
};
static const std::map<int32_t, std::string> HdrModeTable = {
{ controls::HdrModeOff, "Off" },
{ controls::HdrModeMultiExposureUnmerged, "MultiExposureUnmerged" },
{ controls::HdrModeMultiExposure, "MultiExposure" },
{ controls::HdrModeSingleExposure, "SingleExposure" },
{ controls::HdrModeNight, "Night" },
};
void IpaBase::applyControls(const ControlList &controls)
{
using RPiController::AgcAlgorithm;
using RPiController::AfAlgorithm;
using RPiController::ContrastAlgorithm;
using RPiController::DenoiseAlgorithm;
using RPiController::HdrAlgorithm;
/* 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<AfAlgorithm *>(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<int32_t>();
auto mode = AfModeTable.find(idx);
if (mode == AfModeTable.end()) {
LOG(IPARPI, Error) << "AF mode " << idx
<< " not recognised";
} else if (af)
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<RPiController::AgcAlgorithm *>(
controller_.getAlgorithm("agc"));
if (!agc) {
LOG(IPARPI, Warning)
<< "Could not set AE_ENABLE - no AGC algorithm";
break;
}
if (ctrl.second.get<bool>() == false)
agc->disableAuto();
else
agc->enableAuto();
libcameraMetadata_.set(controls::AeEnable, ctrl.second.get<bool>());
break;
}
case controls::EXPOSURE_TIME: {
RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
controller_.getAlgorithm("agc"));
if (!agc) {
LOG(IPARPI, Warning)
<< "Could not set EXPOSURE_TIME - no AGC algorithm";
break;
}
/* The control provides units of microseconds. */
agc->setFixedExposureTime(0, ctrl.second.get<int32_t>() * 1.0us);
libcameraMetadata_.set(controls::ExposureTime, ctrl.second.get<int32_t>());
break;
}
case controls::ANALOGUE_GAIN: {
RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
controller_.getAlgorithm("agc"));
if (!agc) {
LOG(IPARPI, Warning)
<< "Could not set ANALOGUE_GAIN - no AGC algorithm";
break;
}
agc->setFixedAnalogueGain(0, ctrl.second.get<float>());
libcameraMetadata_.set(controls::AnalogueGain,
ctrl.second.get<float>());
break;
}
case controls::AE_METERING_MODE: {
RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
controller_.getAlgorithm("agc"));
if (!agc) {
LOG(IPARPI, Warning)
<< "Could not set AE_METERING_MODE - no AGC algorithm";
break;
}
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: {
RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
controller_.getAlgorithm("agc"));
if (!agc) {
LOG(IPARPI, Warning)
<< "Could not set AE_CONSTRAINT_MODE - no AGC algorithm";
break;
}
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: {
RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
controller_.getAlgorithm("agc"));
if (!agc) {
LOG(IPARPI, Warning)
<< "Could not set AE_EXPOSURE_MODE - no AGC algorithm";
break;
}
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: {
RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
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<float>());
agc->setEv(0, ev);
libcameraMetadata_.set(controls::ExposureValue,
ctrl.second.get<float>());
break;
}
case controls::AE_FLICKER_MODE: {
RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
controller_.getAlgorithm("agc"));
if (!agc) {
LOG(IPARPI, Warning)
<< "Could not set AeFlickerMode - no AGC algorithm";
break;
}
int32_t mode = ctrl.second.get<int32_t>();
bool modeValid = true;
switch (mode) {
case controls::FlickerOff:
agc->setFlickerPeriod(0us);
break;
case controls::FlickerManual:
agc->setFlickerPeriod(flickerState_.manualPeriod);
break;
default:
LOG(IPARPI, Error) << "Flicker mode " << mode << " is not supported";
modeValid = false;
break;
}
if (modeValid)
flickerState_.mode = mode;
break;
}
case controls::AE_FLICKER_PERIOD: {
RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
controller_.getAlgorithm("agc"));
if (!agc) {
LOG(IPARPI, Warning)
<< "Could not set AeFlickerPeriod - no AGC algorithm";
break;
}
uint32_t manualPeriod = ctrl.second.get<int32_t>();
flickerState_.manualPeriod = manualPeriod * 1.0us;
/*
* We note that it makes no difference if the mode gets set to "manual"
* first, and the period updated after, or vice versa.
*/
if (flickerState_.mode == controls::FlickerManual)
agc->setFlickerPeriod(flickerState_.manualPeriod);
break;
}
case controls::AWB_ENABLE: {
/* Silently ignore this control for a mono sensor. */
if (monoSensor_)
break;
RPiController::AwbAlgorithm *awb = dynamic_cast<RPiController::AwbAlgorithm *>(
controller_.getAlgorithm("awb"));
if (!awb) {
LOG(IPARPI, Warning)
<< "Could not set AWB_ENABLE - no AWB algorithm";
break;
}
if (ctrl.second.get<bool>() == false)
awb->disableAuto();
else
awb->enableAuto();
libcameraMetadata_.set(controls::AwbEnable,
ctrl.second.get<bool>());
break;
}
case controls::AWB_MODE: {
/* Silently ignore this control for a mono sensor. */
if (monoSensor_)
break;
RPiController::AwbAlgorithm *awb = dynamic_cast<RPiController::AwbAlgorithm *>(
controller_.getAlgorithm("awb"));
if (!awb) {
LOG(IPARPI, Warning)
<< "Could not set AWB_MODE - no AWB algorithm";
break;
}
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: {
/* Silently ignore this control for a mono sensor. */
if (monoSensor_)
break;
auto gains = ctrl.second.get<Span<const float>>();
RPiController::AwbAlgorithm *awb = dynamic_cast<RPiController::AwbAlgorithm *>(
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<RPiController::ContrastAlgorithm *>(
controller_.getAlgorithm("contrast"));
if (!contrast) {
LOG(IPARPI, Warning)
<< "Could not set BRIGHTNESS - no contrast algorithm";
break;
}
contrast->setBrightness(ctrl.second.get<float>() * 65536);
libcameraMetadata_.set(controls::Brightness,
ctrl.second.get<float>());
break;
}
case controls::CONTRAST: {
RPiController::ContrastAlgorithm *contrast = dynamic_cast<RPiController::ContrastAlgorithm *>(
controller_.getAlgorithm("contrast"));
if (!contrast) {
LOG(IPARPI, Warning)
<< "Could not set CONTRAST - no contrast algorithm";
break;
}
contrast->setContrast(ctrl.second.get<float>());
libcameraMetadata_.set(controls::Contrast,
ctrl.second.get<float>());
break;
}
case controls::SATURATION: {
/* Silently ignore this control for a mono sensor. */
if (monoSensor_)
break;
RPiController::CcmAlgorithm *ccm = dynamic_cast<RPiController::CcmAlgorithm *>(
controller_.getAlgorithm("ccm"));
if (!ccm) {
LOG(IPARPI, Warning)
<< "Could not set SATURATION - no ccm algorithm";
break;
}
ccm->setSaturation(ctrl.second.get<float>());
libcameraMetadata_.set(controls::Saturation,
ctrl.second.get<float>());
break;
}
case controls::SHARPNESS: {
RPiController::SharpenAlgorithm *sharpen = dynamic_cast<RPiController::SharpenAlgorithm *>(
controller_.getAlgorithm("sharpen"));
if (!sharpen) {
LOG(IPARPI, Warning)
<< "Could not set SHARPNESS - no sharpen algorithm";
break;
}
sharpen->setStrength(ctrl.second.get<float>());
libcameraMetadata_.set(controls::Sharpness,
ctrl.second.get<float>());
break;
}
case controls::rpi::SCALER_CROPS:
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<Span<const int64_t>>();
applyFrameDurations(frameDurations[0] * 1.0us, frameDurations[1] * 1.0us);
break;
}
case controls::draft::NOISE_REDUCTION_MODE:
/* Handled below in handleControls() */
libcameraMetadata_.set(controls::draft::NoiseReductionMode,
ctrl.second.get<int32_t>());
break;
case controls::AF_MODE:
break; /* We already handled this one above */
case controls::AF_RANGE: {
AfAlgorithm *af = dynamic_cast<AfAlgorithm *>(controller_.getAlgorithm("af"));
if (!af) {
LOG(IPARPI, Warning)
<< "Could not set AF_RANGE - no focus algorithm";
break;
}
auto range = AfRangeTable.find(ctrl.second.get<int32_t>());
if (range == AfRangeTable.end()) {
LOG(IPARPI, Error) << "AF range " << ctrl.second.get<int32_t>()
<< " not recognised";
break;
}
af->setRange(range->second);
break;
}
case controls::AF_SPEED: {
AfAlgorithm *af = dynamic_cast<AfAlgorithm *>(controller_.getAlgorithm("af"));
if (!af) {
LOG(IPARPI, Warning)
<< "Could not set AF_SPEED - no focus algorithm";
break;
}
AfAlgorithm::AfSpeed speed = ctrl.second.get<int32_t>() == controls::AfSpeedFast ?
AfAlgorithm::AfSpeedFast : AfAlgorithm::AfSpeedNormal;
af->setSpeed(speed);
break;
}
case controls::AF_METERING: {
AfAlgorithm *af = dynamic_cast<AfAlgorithm *>(controller_.getAlgorithm("af"));
if (!af) {
LOG(IPARPI, Warning)
<< "Could not set AF_METERING - no AF algorithm";
break;
}
af->setMetering(ctrl.second.get<int32_t>() == controls::AfMeteringWindows);
break;
}
case controls::AF_WINDOWS: {
AfAlgorithm *af = dynamic_cast<AfAlgorithm *>(controller_.getAlgorithm("af"));
if (!af) {
LOG(IPARPI, Warning)
<< "Could not set AF_WINDOWS - no AF algorithm";
break;
}
af->setWindows(ctrl.second.get<Span<const Rectangle>>());
break;
}
case controls::AF_PAUSE: {
AfAlgorithm *af = dynamic_cast<AfAlgorithm *>(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<int32_t>());
if (pause == AfPauseTable.end()) {
LOG(IPARPI, Error) << "AF pause " << ctrl.second.get<int32_t>()
<< " not recognised";
break;
}
af->pause(pause->second);
break;
}
case controls::AF_TRIGGER: {
AfAlgorithm *af = dynamic_cast<AfAlgorithm *>(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<int32_t>() == controls::AfTriggerStart)
af->triggerScan();
else
af->cancelScan();
}
break;
}
case controls::LENS_POSITION: {
AfAlgorithm *af = dynamic_cast<AfAlgorithm *>(controller_.getAlgorithm("af"));
if (af) {
int32_t hwpos;
if (af->setLensPosition(ctrl.second.get<float>(), &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;
}
case controls::HDR_MODE: {
HdrAlgorithm *hdr = dynamic_cast<HdrAlgorithm *>(controller_.getAlgorithm("hdr"));
if (!hdr) {
LOG(IPARPI, Warning) << "No HDR algorithm available";
break;
}
auto mode = HdrModeTable.find(ctrl.second.get<int32_t>());
if (mode == HdrModeTable.end()) {
LOG(IPARPI, Warning) << "Unrecognised HDR mode";
break;
}
AgcAlgorithm *agc = dynamic_cast<AgcAlgorithm *>(controller_.getAlgorithm("agc"));
if (!agc) {
LOG(IPARPI, Warning) << "HDR requires an AGC algorithm";
break;
}
if (hdr->setMode(mode->second) == 0) {
agc->setActiveChannels(hdr->getChannels());
/* We also disable adpative contrast enhancement if HDR is running. */
ContrastAlgorithm *contrast =
dynamic_cast<ContrastAlgorithm *>(controller_.getAlgorithm("contrast"));
if (contrast) {
if (mode->second == "Off")
contrast->restoreCe();
else
contrast->enableCe(false);
}
DenoiseAlgorithm *denoise =
dynamic_cast<DenoiseAlgorithm *>(controller_.getAlgorithm("denoise"));
if (denoise) {
/* \todo - make the HDR mode say what denoise it wants? */
if (mode->second == "Night")
denoise->setConfig("night");
else if (mode->second == "SingleExposure")
denoise->setConfig("hdr");
/* MultiExposure doesn't need extra extra denoise. */
else
denoise->setConfig("normal");
}
} else
LOG(IPARPI, Warning)
<< "HDR mode " << mode->second << " not supported";
break;
}
case controls::rpi::STATS_OUTPUT_ENABLE:
statsMetadataOutput_ = ctrl.second.get<bool>();
break;
default:
LOG(IPARPI, Warning)
<< "Ctrl " << controls::controls.at(ctrl.first)->name()
<< " is not handled.";
break;
}
}
/* Give derived classes a chance to examine the new controls. */
handleControls(controls);
}
void IpaBase::fillDeviceStatus(const ControlList &sensorControls, unsigned int ipaContext)
{
DeviceStatus deviceStatus = {};
int32_t exposureLines = sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();
int32_t gainCode = sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();
int32_t vblank = sensorControls.get(V4L2_CID_VBLANK).get<int32_t>();
int32_t hblank = sensorControls.get(V4L2_CID_HBLANK).get<int32_t>();
deviceStatus.lineLength = helper_->hblankToLineLength(hblank);
deviceStatus.exposureTime = helper_->exposure(exposureLines, deviceStatus.lineLength);
deviceStatus.analogueGain = helper_->gain(gainCode);
deviceStatus.frameLength = mode_.height + vblank;
RPiController::AfAlgorithm *af = dynamic_cast<RPiController::AfAlgorithm *>(
controller_.getAlgorithm("af"));
if (af)
deviceStatus.lensPosition = af->getLensPosition();
LOG(IPARPI, Debug) << "Metadata - " << deviceStatus;
rpiMetadata_[ipaContext].set("device.status", deviceStatus);
}
void IpaBase::reportMetadata(unsigned int ipaContext)
{
RPiController::Metadata &rpiMetadata = rpiMetadata_[ipaContext];
std::unique_lock<RPiController::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->exposureTime.get<std::micro>());
libcameraMetadata_.set(controls::AnalogueGain, deviceStatus->analogueGain);
libcameraMetadata_.set(controls::FrameDuration,
helper_->exposure(deviceStatus->frameLength, deviceStatus->lineLength).get<std::micro>());
if (deviceStatus->sensorTemperature)
libcameraMetadata_.set(controls::SensorTemperature, *deviceStatus->sensorTemperature);
if (deviceStatus->lensPosition)
libcameraMetadata_.set(controls::LensPosition, *deviceStatus->lensPosition);
}
AgcPrepareStatus *agcPrepareStatus = rpiMetadata.getLocked<AgcPrepareStatus>("agc.prepare_status");
if (agcPrepareStatus) {
libcameraMetadata_.set(controls::AeLocked, agcPrepareStatus->locked);
libcameraMetadata_.set(controls::DigitalGain, agcPrepareStatus->digitalGain);
}
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->gainR),
static_cast<float>(awbStatus->gainB) });
libcameraMetadata_.set(controls::ColourTemperature, awbStatus->temperatureK);
}
BlackLevelStatus *blackLevelStatus = rpiMetadata.getLocked<BlackLevelStatus>("black_level.status");
if (blackLevelStatus)
libcameraMetadata_.set(controls::SensorBlackLevels,
{ static_cast<int32_t>(blackLevelStatus->blackLevelR),
static_cast<int32_t>(blackLevelStatus->blackLevelG),
static_cast<int32_t>(blackLevelStatus->blackLevelG),
static_cast<int32_t>(blackLevelStatus->blackLevelB) });
RPiController::FocusRegions *focusStatus =
rpiMetadata.getLocked<RPiController::FocusRegions>("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;
libcameraMetadata_.set(controls::FocusFoM, focusFoM);
}
CcmStatus *ccmStatus = rpiMetadata.getLocked<CcmStatus>("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<AfStatus>("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);
}
/*
* THe HDR algorithm sets the HDR channel into the agc.status at the time that those
* AGC parameters were calculated several frames ago, so it comes back to us now in
* the delayed_status. If this frame is too soon after a mode switch for the
* delayed_status to be available, we use the HDR status that came out of the
* switchMode call.
*/
const AgcStatus *agcStatus = rpiMetadata.getLocked<AgcStatus>("agc.delayed_status");
const HdrStatus &hdrStatus = agcStatus ? agcStatus->hdr : hdrStatus_;
if (!hdrStatus.mode.empty() && hdrStatus.mode != "Off") {
int32_t hdrMode = controls::HdrModeOff;
for (auto const &[mode, name] : HdrModeTable) {
if (hdrStatus.mode == name) {
hdrMode = mode;
break;
}
}
libcameraMetadata_.set(controls::HdrMode, hdrMode);
if (hdrStatus.channel == "short")
libcameraMetadata_.set(controls::HdrChannel, controls::HdrChannelShort);
else if (hdrStatus.channel == "long")
libcameraMetadata_.set(controls::HdrChannel, controls::HdrChannelLong);
else if (hdrStatus.channel == "medium")
libcameraMetadata_.set(controls::HdrChannel, controls::HdrChannelMedium);
else
libcameraMetadata_.set(controls::HdrChannel, controls::HdrChannelNone);
}
metadataReady.emit(libcameraMetadata_);
}
void IpaBase::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<int64_t>(minFrameDuration_.get<std::micro>()),
static_cast<int64_t>(maxFrameDuration_.get<std::micro>()) });
/*
* Calculate the maximum exposure time possible for the AGC to use.
* getBlanking() will update maxExposureTime with the largest exposure
* value possible.
*/
Duration maxExposureTime = Duration::max();
helper_->getBlanking(maxExposureTime, minFrameDuration_, maxFrameDuration_);
RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
controller_.getAlgorithm("agc"));
agc->setMaxExposureTime(maxExposureTime);
}
void IpaBase::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<int32_t>(gainCode, minGainCode, maxGainCode);
/* getBlanking might clip exposure time to the fps limits. */
Duration exposure = agcStatus->exposureTime;
auto [vblank, hblank] = helper_->getBlanking(exposure, minFrameDuration_, maxFrameDuration_);
int32_t exposureLines = helper_->exposureLines(exposure,
helper_->hblankToLineLength(hblank));
LOG(IPARPI, Debug) << "Applying AGC Exposure: " << exposure
<< " (Exposure lines: " << exposureLines << ", AGC requested "
<< agcStatus->exposureTime << ") Gain: "
<< agcStatus->analogueGain << " (Gain Code: "
<< gainCode << ")";
ctrls.set(V4L2_CID_VBLANK, static_cast<int32_t>(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<int32_t>(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)));
}
} /* namespace ipa::RPi */
} /* namespace libcamera */