libcamera/src/ipa/simple/soft_simple.cpp
Vasiliy Doylov 73cdcfb5c6
libcamera: software_isp: Add focus control
Signed-off-by: Vasiliy Doylov <nekocwd@mainlining.org>
2025-06-07 21:08:17 +03:00

374 lines
10 KiB
C++

/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2023, Linaro Ltd
*
* Simple Software Image Processing Algorithm module
*/
#include <chrono>
#include <stdint.h>
#include <sys/mman.h>
#include <linux/v4l2-controls.h>
#include <libcamera/base/file.h>
#include <libcamera/base/log.h>
#include <libcamera/base/shared_fd.h>
#include <libcamera/control_ids.h>
#include <libcamera/controls.h>
#include <libcamera/ipa/ipa_interface.h>
#include <libcamera/ipa/ipa_module_info.h>
#include <libcamera/ipa/soft_ipa_interface.h>
#include "libcamera/internal/software_isp/debayer_params.h"
#include "libcamera/internal/software_isp/swisp_stats.h"
#include "libcamera/internal/yaml_parser.h"
#include "libipa/camera_sensor_helper.h"
#include "module.h"
namespace libcamera {
LOG_DEFINE_CATEGORY(IPASoft)
using namespace std::literals::chrono_literals;
namespace ipa::soft {
/* Maximum number of frame contexts to be held */
static constexpr uint32_t kMaxFrameContexts = 16;
class IPASoftSimple : public ipa::soft::IPASoftInterface, public Module
{
public:
IPASoftSimple()
: context_(kMaxFrameContexts)
{
}
~IPASoftSimple();
int init(const IPASettings &settings,
const SharedFD &fdStats,
const SharedFD &fdParams,
const IPACameraSensorInfo &sensorInfo,
const ControlInfoMap &sensorControls,
ControlInfoMap *ipaControls,
bool *ccmEnabled) override;
int configure(const IPAConfigInfo &configInfo) override;
int start() override;
void stop() override;
void queueRequest(const uint32_t frame, const ControlList &controls) override;
void computeParams(const uint32_t frame) override;
void processStats(const uint32_t frame, const uint32_t bufferId,
const ControlList &sensorControls) override;
protected:
std::string logPrefix() const override;
private:
void updateExposure(double exposureMSV);
DebayerParams *params_;
SwIspStats *stats_;
std::unique_ptr<CameraSensorHelper> camHelper_;
ControlInfoMap sensorInfoMap_;
ControlInfoMap lensInfoMap_;
/* Local parameter storage */
struct IPAContext context_;
};
IPASoftSimple::~IPASoftSimple()
{
if (stats_)
munmap(stats_, sizeof(SwIspStats));
if (params_)
munmap(params_, sizeof(DebayerParams));
}
int IPASoftSimple::init(const IPASettings &settings,
const SharedFD &fdStats,
const SharedFD &fdParams,
const IPACameraSensorInfo &sensorInfo,
const ControlInfoMap &sensorControls,
ControlInfoMap *ipaControls,
bool *ccmEnabled)
{
camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel);
if (!camHelper_) {
LOG(IPASoft, Warning)
<< "Failed to create camera sensor helper for "
<< settings.sensorModel;
}
context_.sensorInfo = sensorInfo;
/* Load the tuning data file */
File file(settings.configurationFile);
if (!file.open(File::OpenModeFlag::ReadOnly)) {
int ret = file.error();
LOG(IPASoft, Error)
<< "Failed to open configuration file "
<< settings.configurationFile << ": " << strerror(-ret);
return ret;
}
std::unique_ptr<libcamera::YamlObject> data = YamlParser::parse(file);
if (!data)
return -EINVAL;
/* \todo Use the IPA configuration file for real. */
unsigned int version = (*data)["version"].get<uint32_t>(0);
LOG(IPASoft, Debug) << "Tuning file version " << version;
if (!data->contains("algorithms")) {
LOG(IPASoft, Error) << "Tuning file doesn't contain algorithms";
return -EINVAL;
}
int ret = createAlgorithms(context_, (*data)["algorithms"]);
if (ret)
return ret;
*ccmEnabled = context_.ccmEnabled;
params_ = nullptr;
stats_ = nullptr;
if (!fdStats.isValid()) {
LOG(IPASoft, Error) << "Invalid Statistics handle";
return -ENODEV;
}
if (!fdParams.isValid()) {
LOG(IPASoft, Error) << "Invalid Parameters handle";
return -ENODEV;
}
{
void *mem = mmap(nullptr, sizeof(DebayerParams), PROT_WRITE,
MAP_SHARED, fdParams.get(), 0);
if (mem == MAP_FAILED) {
LOG(IPASoft, Error) << "Unable to map Parameters";
return -errno;
}
params_ = static_cast<DebayerParams *>(mem);
}
{
void *mem = mmap(nullptr, sizeof(SwIspStats), PROT_READ,
MAP_SHARED, fdStats.get(), 0);
if (mem == MAP_FAILED) {
LOG(IPASoft, Error) << "Unable to map Statistics";
return -errno;
}
stats_ = static_cast<SwIspStats *>(mem);
}
ControlInfoMap::Map ctrlMap = context_.ctrlMap;
*ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls);
/*
* Check if the sensor driver supports the controls required by the
* Soft IPA.
* Don't save the min and max control values yet, as e.g. the limits
* for V4L2_CID_EXPOSURE depend on the configured sensor resolution.
*/
if (sensorControls.find(V4L2_CID_EXPOSURE) == sensorControls.end()) {
LOG(IPASoft, Error) << "Don't have exposure control";
return -EINVAL;
}
if (sensorControls.find(V4L2_CID_ANALOGUE_GAIN) == sensorControls.end()) {
LOG(IPASoft, Error) << "Don't have gain control";
return -EINVAL;
}
return 0;
}
int IPASoftSimple::configure(const IPAConfigInfo &configInfo)
{
sensorInfoMap_ = configInfo.sensorControls;
lensInfoMap_ = configInfo.lensControls;
const ControlInfo &exposureInfo = sensorInfoMap_.find(V4L2_CID_EXPOSURE)->second;
const ControlInfo &gainInfo = sensorInfoMap_.find(V4L2_CID_ANALOGUE_GAIN)->second;
/* Clear the IPA context before the streaming session. */
context_.configuration = {};
context_.activeState = {};
context_.frameContexts.clear();
if (lensInfoMap_.empty()) {
LOG(IPASoft, Warning) << "No camera leans found! Focus control disabled.";
context_.configuration.focus.focus_min = 0;
context_.configuration.focus.focus_max = 0;
} else {
const ControlInfo &lensInfo = lensInfoMap_.find(V4L2_CID_FOCUS_ABSOLUTE)->second;
context_.configuration.focus.focus_min = lensInfo.min().get<int32_t>();
context_.configuration.focus.focus_max = lensInfo.max().get<int32_t>();
LOG(IPASoft, Warning) << "Camera leans found! Focus: " << context_.configuration.focus.focus_min << "-" << context_.configuration.focus.focus_max;
}
context_.configuration.agc.lineDuration =
context_.sensorInfo.minLineLength * 1.0s / context_.sensorInfo.pixelRate;
context_.configuration.agc.exposureMin = exposureInfo.min().get<int32_t>();
context_.configuration.agc.exposureMax = exposureInfo.max().get<int32_t>();
if (!context_.configuration.agc.exposureMin) {
LOG(IPASoft, Warning) << "Minimum exposure is zero, that can't be linear";
context_.configuration.agc.exposureMin = 1;
}
int32_t againMin = gainInfo.min().get<int32_t>();
int32_t againMax = gainInfo.max().get<int32_t>();
if (camHelper_) {
context_.configuration.agc.againMin = camHelper_->gain(againMin);
context_.configuration.agc.againMax = camHelper_->gain(againMax);
context_.configuration.agc.againMinStep =
(context_.configuration.agc.againMax -
context_.configuration.agc.againMin) /
100.0;
if (camHelper_->blackLevel().has_value()) {
/*
* The black level from camHelper_ is a 16 bit value, software ISP
* works with 8 bit pixel values, both regardless of the actual
* sensor pixel width. Hence we obtain the pixel-based black value
* by dividing the value from the helper by 256.
*/
context_.configuration.black.level =
camHelper_->blackLevel().value() / 256;
}
} else {
/*
* The camera sensor gain (g) is usually not equal to the value written
* into the gain register (x). But the way how the AGC algorithm changes
* the gain value to make the total exposure closer to the optimum
* assumes that g(x) is not too far from linear function. If the minimal
* gain is 0, the g(x) is likely to be far from the linear, like
* g(x) = a / (b * x + c). To avoid unexpected changes to the gain by
* the AGC algorithm (abrupt near one edge, and very small near the
* other) we limit the range of the gain values used.
*/
context_.configuration.agc.againMax = againMax;
if (!againMin) {
LOG(IPASoft, Warning)
<< "Minimum gain is zero, that can't be linear";
context_.configuration.agc.againMin =
std::min(100, againMin / 2 + againMax / 2);
}
context_.configuration.agc.againMinStep = 1.0;
}
for (auto const &algo : algorithms()) {
int ret = algo->configure(context_, configInfo);
if (ret)
return ret;
}
LOG(IPASoft, Info)
<< "Exposure " << context_.configuration.agc.exposureMin << "-"
<< context_.configuration.agc.exposureMax
<< ", gain " << context_.configuration.agc.againMin << "-"
<< context_.configuration.agc.againMax
<< " (" << context_.configuration.agc.againMinStep << ")";
return 0;
}
int IPASoftSimple::start()
{
return 0;
}
void IPASoftSimple::stop()
{
context_.frameContexts.clear();
}
void IPASoftSimple::queueRequest(const uint32_t frame, const ControlList &controls)
{
IPAFrameContext &frameContext = context_.frameContexts.alloc(frame);
for (auto const &algo : algorithms())
algo->queueRequest(context_, frame, frameContext, controls);
}
void IPASoftSimple::computeParams(const uint32_t frame)
{
IPAFrameContext &frameContext = context_.frameContexts.get(frame);
for (auto const &algo : algorithms())
algo->prepare(context_, frame, frameContext, params_);
setIspParams.emit();
}
void IPASoftSimple::processStats(const uint32_t frame,
[[maybe_unused]] const uint32_t bufferId,
const ControlList &sensorControls)
{
IPAFrameContext &frameContext = context_.frameContexts.get(frame);
frameContext.sensor.exposure =
sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();
int32_t again = sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();
frameContext.sensor.gain = camHelper_ ? camHelper_->gain(again) : again;
ControlList metadata(controls::controls);
for (auto const &algo : algorithms())
algo->process(context_, frame, frameContext, stats_, metadata);
metadataReady.emit(frame, metadata);
/* Sanity check */
if (!sensorControls.contains(V4L2_CID_EXPOSURE) ||
!sensorControls.contains(V4L2_CID_ANALOGUE_GAIN)) {
LOG(IPASoft, Error) << "Control(s) missing";
return;
}
ControlList ctrls(sensorInfoMap_);
auto &againNew = frameContext.sensor.gain;
ctrls.set(V4L2_CID_EXPOSURE, frameContext.sensor.exposure);
ctrls.set(V4L2_CID_ANALOGUE_GAIN,
static_cast<int32_t>(camHelper_ ? camHelper_->gainCode(againNew) : againNew));
ControlList lens_ctrls(lensInfoMap_);
lens_ctrls.set(V4L2_CID_FOCUS_ABSOLUTE, frameContext.lens.focus_pos);
setSensorControls.emit(ctrls, lens_ctrls);
}
std::string IPASoftSimple::logPrefix() const
{
return "IPASoft";
}
} /* namespace ipa::soft */
/*
* External IPA module interface
*/
extern "C" {
const struct IPAModuleInfo ipaModuleInfo = {
IPA_MODULE_API_VERSION,
0,
"simple",
"simple",
};
IPAInterface *ipaCreate()
{
return new ipa::soft::IPASoftSimple();
}
} /* extern "C" */
} /* namespace libcamera */