/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2023, Linaro Ltd * * Simple Software Image Processing Algorithm module */ #include #include #include #include #include #include #include #include #include #include #include #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) 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 ControlInfoMap &sensorInfoMap) override; int configure(const IPAConfigInfo &configInfo) override; int start() override; void stop() override; void queueRequest(const uint32_t frame, const ControlList &controls) override; void fillParamsBuffer(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 camHelper_; ControlInfoMap sensorInfoMap_; /* 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 ControlInfoMap &sensorInfoMap) { camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); if (!camHelper_) { LOG(IPASoft, Warning) << "Failed to create camera sensor helper for " << settings.sensorModel; } /* 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 data = YamlParser::parse(file); if (!data) return -EINVAL; /* \todo Use the IPA configuration file for real. */ unsigned int version = (*data)["version"].get(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; 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(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(mem); } /* * 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 (sensorInfoMap.find(V4L2_CID_EXPOSURE) == sensorInfoMap.end()) { LOG(IPASoft, Error) << "Don't have exposure control"; return -EINVAL; } if (sensorInfoMap.find(V4L2_CID_ANALOGUE_GAIN) == sensorInfoMap.end()) { LOG(IPASoft, Error) << "Don't have gain control"; return -EINVAL; } return 0; } int IPASoftSimple::configure(const IPAConfigInfo &configInfo) { sensorInfoMap_ = configInfo.sensorControls; 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(); context_.configuration.agc.exposureMin = exposureInfo.min().get(); context_.configuration.agc.exposureMax = exposureInfo.max().get(); 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 againMax = gainInfo.max().get(); 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 (!context_.configuration.black.level.has_value() && 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_.con/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2020, Umang Jain <email@uajain.com> * * hotplug-cameras.cpp - Test cameraAdded/cameraRemoved signals in CameraManager */ #include <dirent.h> #include <fstream> #include <iostream> #include <string.h> #include <unistd.h> #include <libcamera/camera.h> #include <libcamera/camera_manager.h> #include "libcamera/internal/event_dispatcher.h" #include "libcamera/internal/file.h" #include "libcamera/internal/thread.h" #include "libcamera/internal/timer.h" #include "test.h" using namespace libcamera; class HotplugTest : public Test { protected: void cameraAddedHandler([[maybe_unused]] std::shared_ptr<Camera> cam) { cameraAdded_ = true; } void cameraRemovedHandler([[maybe_unused]] std::shared_ptr<Camera> cam) { cameraRemoved_ = true; } int init() { if (!File::exists("/sys/module/uvcvideo")) { std::cout << "uvcvideo driver is not loaded, skipping" << std::endl; return TestSkip; } if (geteuid() != 0) { std::cout << "This test requires root permissions, skipping" << std::endl; return TestSkip; } cm_ = new CameraManager(); if (cm_->start()) { std::cout << "Failed to start camera manager" << std::endl; return TestFail; } cameraAdded_ = false; cameraRemoved_ = false; cm_->cameraAdded.connect(this, &HotplugTest::cameraAddedHandler); cm_->cameraRemoved.connect(this, &HotplugTest::cameraRemovedHandler); return 0; } int run() { DIR *dir; struct dirent *dirent; std::string uvcDeviceDir; dir = opendir(uvcDriverDir_.c_str()); /* Find a UVC device directory, which we can bind/unbind. */ while ((dirent = readdir(dir)) != nullptr) { if (!File::exists(uvcDriverDir_ + dirent->d_name + "/video4linux")) continue; uvcDeviceDir = dirent->d_name; break; } closedir(dir); /* If no UVC device found, skip the test. */ if (uvcDeviceDir.empty()) return TestSkip; /* Unbind a camera and process events. */ std::ofstream(uvcDriverDir_ + "unbind", std::ios::binary) << uvcDeviceDir; Timer timer; timer.start(1000); while (timer.isRunning() && !cameraRemoved_) Thread::current()->eventDispatcher()->processEvents(); if (!cameraRemoved_) { std::cout << "Camera unplug not detected" << std::endl; return TestFail; } /* Bind the camera again and process events. */ std::ofstream(uvcDriverDir_ + "bind", std::ios::binary) << uvcDeviceDir; timer.start(1000); while (timer.isRunning() && !cameraAdded_) Thread::current()->eventDispatcher()->processEvents(); if (!cameraAdded_) { std::cout << "Camera plug not detected" << std::endl; return TestFail; } return TestPass; } void cleanup() { cm_->stop(); delete cm_; } private: CameraManager *cm_; static const std::string uvcDriverDir_; bool cameraRemoved_; bool cameraAdded_; }; const std::string HotplugTest::uvcDriverDir_ = "/sys/bus/usb/drivers/uvcvideo/"; TEST_REGISTER(HotplugTest)