/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2020, Google Inc. * * ipu3.cpp - IPU3 Image Processing Algorithms */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libcamera/internal/mapped_framebuffer.h" #include "algorithms/agc.h" #include "algorithms/algorithm.h" #include "algorithms/awb.h" #include "algorithms/blc.h" #include "algorithms/tone_mapping.h" #include "libipa/camera_sensor_helper.h" /* Minimum grid width, expressed as a number of cells */ static constexpr uint32_t kMinGridWidth = 16; /* Maximum grid width, expressed as a number of cells */ static constexpr uint32_t kMaxGridWidth = 80; /* Minimum grid height, expressed as a number of cells */ static constexpr uint32_t kMinGridHeight = 16; /* Maximum grid height, expressed as a number of cells */ static constexpr uint32_t kMaxGridHeight = 60; /* log2 of the minimum grid cell width and height, in pixels */ static constexpr uint32_t kMinCellSizeLog2 = 3; /* log2 of the maximum grid cell width and height, in pixels */ static constexpr uint32_t kMaxCellSizeLog2 = 6; namespace libcamera { LOG_DEFINE_CATEGORY(IPAIPU3) using namespace std::literals::chrono_literals; namespace ipa::ipu3 { /** * \brief The IPU3 IPA implementation * * The IPU3 Pipeline defines an IPU3-specific interface for communication * between the PipelineHandler and the IPA module. * * We extend the IPAIPU3Interface to implement our algorithms and handle events * from the IPU3 PipelineHandler to satisfy requests from the application. * * At initialisation time, a CameraSensorHelper is instantiated to support * camera-specific calculations, while the default controls are computed, and * the algorithms are constructed and placed in an ordered list. * * The IPU3 ImgU operates with a grid layout to divide the overall frame into * rectangular cells of pixels. When the IPA is configured, we determine the * best grid for the statistics based on the pipeline handler Bayer Down Scaler * output size. * * Two main events are then handled to operate the IPU3 ImgU by populating its * parameter buffer, and adapting the settings of the sensor attached to the * IPU3 CIO2 through sensor-specific V4L2 controls. * * When the event \a EventFillParams occurs we populate the ImgU parameter * buffer with settings to configure the device in preparation for handling the * frame queued in the Request. * * When the frame has completed processing, the ImgU will generate a statistics * buffer which is given to the IPA as part of the \a EventStatReady event. At * this event we run the algorithms to parse the statistics and cache any * results for the next \a EventFillParams event. * * The individual algorithms are split into modular components that are called * iteratively to allow them to process statistics from the ImgU in a defined * order. * * The current implementation supports three core algorithms: * - Automatic white balance (AWB) * - Automatic gain and exposure control (AGC) * - Black level correction (BLC) * - Tone mapping (Gamma) * * AWB is implemented using a Greyworld algorithm, and calculates the red and * blue gains to apply to generate a neutral grey frame overall. * * AGC is handled by calculating a histogram of the green channel to estimate an * analogue gain and shutter time which will provide a well exposed frame. A * low-pass IIR filter is used to smooth the changes to the sensor to reduce * perceivable steps. * * The tone mapping algorithm provides a gamma correction table to improve the * contrast of the scene. * * The black level compensation algorithm subtracts a hardcoded black level from * all pixels. * * The IPU3 ImgU has further processing blocks to support image quality * improvements through bayer and temporal noise reductions, however those are * not supported in the current implementation, and will use default settings as * provided by the kernel driver. * * Demosaicing is operating with the default parameters and could be further * optimised to provide improved sharpening coefficients, checker artifact * removal, and false color correction. * * Additional image enhancements can be made by providing lens and * sensor-specific tuning to adapt for Black Level compensation (BLC), Lens * shading correction (SHD) and Color correction (CCM). */ class IPAIPU3 : public IPAIPU3Interface { public: int init(const IPASettings &settings, const IPACameraSensorInfo &sensorInfo, const ControlInfoMap &sensorControls, ControlInfoMap *ipaControls) override; int start() override; void stop() override; int configure(const IPAConfigInfo &configInfo, ControlInfoMap *ipaControls) override; void mapBuffers(const std::vector &buffers) override; void unmapBuffers(const std::vector &ids) override; void processEvent(const IPU3Event &event) override; private: void updateControls(const IPACameraSensorInfo &sensorInfo, const ControlInfoMap &sensorControls, ControlInfoMap *ipaControls); void updateSessionConfiguration(const IPACameraSensorInfo &sensorInfo, const ControlInfoMap &sensorControls); void processControls(unsigned int frame, const ControlList &controls); void fillParams(unsigned int frame, ipu3_uapi_params *params); void parseStatistics(unsigned int frame, int64_t frameTimestamp, const ipu3_uapi_stats_3a *stats); void setControls(unsigned int frame); void calculateBdsGrid(const Size &bdsOutputSize); std::map buffers_; ControlInfoMap ctrls_; IPACameraSensorInfo sensorInfo_; /* Camera sensor controls. */ uint32_t defVBlank_; uint32_t exposure_; uint32_t minExposure_; uint32_t maxExposure_; uint32_t gain_; uint32_t minGain_; uint32_t maxGain_; /* Interface to the Camera Helper */ std::unique_ptr camHelper_; /* Maintain the algorithms used by the IPA */ std::list> algorithms_; /* Local parameter storage */ struct IPAContext context_; }; /** * \brief Compute IPASessionConfiguration using the sensor information and the * sensor V4L2 controls */ void IPAIPU3::updateSessionConfiguration(const IPACameraSensorInfo &sensorInfo, const ControlInfoMap &sensorControls) { const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second; int32_t minExposure = v4l2Exposure.min().get(); int32_t maxExposure = v4l2Exposure.max().get(); utils::Duration lineDuration = sensorInfo.lineLength * 1.0s / sensorInfo.pixelRate; const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; int32_t minGain = v4l2Gain.min().get(); int32_t maxGain = v4l2Gain.max().get(); /* * When the AGC computes the new exposure values for a frame, it needs * to know the limits for shutter speed and analogue gain. * As it depends on the sensor, update it with the controls. * * \todo take VBLANK into account for maximum shutter speed */ context_.configuration.agc.minShutterSpeed = minExposure * lineDuration; context_.configuration.agc.maxShutterSpeed = maxExposure * lineDuration; context_.configuration.agc.minAnalogueGain = camHelper_->gain(minGain); context_.configuration.agc.maxAnalogueGain = camHelper_->gain(maxGain); } /** * \brief Compute camera controls using the sensor information and the sensor * V4L2 controls * * Some of the camera controls are computed by the pipeline handler, some others * by the IPA module which is in charge of handling, for example, the exposure * time and the frame duration. * * This function computes: * - controls::ExposureTime * - controls::FrameDurationLimits */ void IPAIPU3::updateControls(const IPACameraSensorInfo &sensorInfo, const ControlInfoMap &sensorControls, ControlInfoMap *ipaControls) { ControlInfoMap::Map controls{}; /* * Compute exposure time limits by using line length and pixel rate * converted to microseconds. Use the V4L2_CID_EXPOSURE control to get * exposure min, max and default and convert it from lines to * microseconds. */ double lineDuration = sensorInfo.lineLength / (sensorInfo.pixelRate / 1e6); const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second; int32_t minExposure = v4l2Exposure.min().get() * lineDuration; int32_t maxExposure = v4l2Exposure.max().get() * lineDuration; int32_t defExposure = v4l2Exposure.def().get() * lineDuration; controls[&controls::ExposureTime] = ControlInfo(minExposure, maxExposure, defExposure); /* * Compute the frame duration limits. * * The frame length is computed assuming a fixed line length combined * with the vertical frame sizes. */ const ControlInfo &v4l2HBlank = sensorControls.find(V4L2_CID_HBLANK)->second; uint32_t hblank = v4l2HBlank.def().get(); uint32_t lineLength = sensorInfo.outputSize.width + hblank; const ControlInfo &v4l2VBlank = sensorControls.find(V4L2_CID_VBLANK)->second; std::array frameHeights{ v4l2VBlank.min().get() + sensorInfo.outputSize.height, v4l2VBlank.max().get() + sensorInfo.outputSize.height, v4l2VBlank.def().get() + sensorInfo.outputSize.height, }; std::array frameDurations; for (unsigned int i = 0; i < frameHeights.size(); ++i) { uint64_t frameSize = lineLength * frameHeights[i]; frameDurations[i] = frameSize / (sensorInfo.pixelRate / 1000000U); } controls[&controls::FrameDurationLimits] = ControlInfo(frameDurations[0], frameDurations[1], frameDurations[2]); *ipaControls = ControlInfoMap(std::move(controls), controls::controls); } /** * \brief Initialize the IPA module and its controls * * This function receives the camera sensor information from the pipeline * handler, computes the limits of the controls it handles and returns * them in the \a ipaControls output parameter. */ int IPAIPU3::init(const IPASettings &settings, const IPACameraSensorInfo &sensorInfo, const ControlInfoMap &sensorControls, ControlInfoMap *ipaControls) { camHelper_ = CameraSensorHelperFactory::create(settings.sensorModel); if (camHelper_ == nullptr) { LOG(IPAIPU3, Error) << "Failed to create camera sensor helper for " << settings.sensorModel; return -ENODEV; } /* Construct our Algorithms */ algorithms_.push_back(std::make_unique()); algorithms_.push_back(std::make_unique()); algorithms_.push_back(std::make_unique()); algorithms_.push_back(std::make_unique()); /* Initialize controls. */ updateControls(sensorInfo, sensorControls, ipaControls); return 0; } /** * \brief Perform any processing required before the first frame */ int IPAIPU3::start() { /* * Set the sensors V4L2 controls before the first frame to ensure that * we have an expected and known configuration from the start. */ setControls(0); return 0; } /** * \brief Ensure that all processing has completed */ void IPAIPU3::stop() { } /** * \brief Calculate a grid for the AWB statistics * * This function calculates a grid for the AWB algorithm in the IPU3 firmware. * Its input is the BDS output size calculated in the ImgU. * It is limited for now to the simplest method: find the lesser error * with the width/height and respective log2 width/height of the cells. * * \todo The frame is divided into cells which can be 8x8 => 64x64. * As a smaller cell improves the algorithm precision, adapting the * x_start and y_start parameters of the grid would provoke a loss of * some pixels but would also result in more accurate algorithms. */ void IPAIPU3::calculateBdsGrid(const Size &bdsOutputSize) { Size best; Size bestLog2; /* Set the BDS output size in the IPAConfiguration structure */ context_.configuration.grid.bdsOutputSize = bdsOutputSize; uint32_t minError = std::numeric_limits::max(); for (uint32_t shift = kMinCellSizeLog2; shift <= kMaxCellSizeLog2; ++shift) { uint32_t width = std::clamp(bdsOutputSize.width >> shift, kMinGridWidth, kMaxGridWidth); width = width << shift; uint32_t error = std::abs(static_cast(width - bdsOutputSize.width)); if (error >= minError) continue; minError = error; best.width = width; bestLog2.width = shift; } minError = std::numeric_limits::max(); for (uint32_t shift = kMinCellSizeLog2; shift <= kMaxCellSizeLog2; ++shift) { uint32_t height = std::clamp(bdsOutputSize.height >> shift, kMinGridHeight, kMaxGridHeight); height = height << shift; uint32_t error = std::abs(static_cast(height - bdsOutputSize.height)); if (error >= minError) continue; minError = error; best.height = height; bestLog2.height = shift; } struct ipu3_uapi_grid_config &bdsGrid = context_.configuration.grid.bdsGrid; bdsGrid.x_start = 0; bdsGrid.y_start = 0; bdsGrid.width = best.width >> bestLog2.width; bdsGrid.block_width_log2 = bestLog2.width; bdsGrid.height = best.height >> bestLog2.height; bdsGrid.block_height_log2 = bestLog2.height; /* The ImgU pads the lines to a multiple of 4 cells. */ context_.configuration.grid.stride = utils::alignUp(bdsGrid.width, 4); LOG(IPAIPU3, Debug) << "Best grid found is: (" << (int)bdsGrid.width << " << " << (int)bdsGrid.block_width_log2 << ") x (" << (int)bdsGrid.height << " << " << (int)bdsGrid.block_height_log2 << ")"; } /** * \brief Configure the IPU3 IPA * \param[in] configInfo The IPA configuration data, received from the pipeline * handler * \param[in] ipaControls The IPA controls to update * * Calculate the best grid for the statistics based on the pipeline handler BDS * output, and parse the minimum and maximum exposure and analogue gain control * values. * * \todo Document what the BDS is, ideally in a block diagram of the ImgU. * * All algorithm modules are called to allow them to prepare the * \a IPASessionConfiguration structure for the \a IPAContext. */ int IPAIPU3::configure(const IPAConfigInfo &configInfo, ControlInfoMap *ipaControls) { if (configInfo.sensorControls.empty()) { LOG(IPAIPU3, Error) << "No sensor controls provided"; return -ENODATA; } sensorInfo_ = configInfo.sensorInfo; /* * Compute the sensor V4L2 controls to be used by the algorithms and * to be set on the sensor. */ ctrls_ = configInfo.sensorControls; const auto itExp = ctrls_.find(V4L2_CID_EXPOSURE); if (itExp == ctrls_.end()) { LOG(IPAIPU3, Error) << "Can't find exposure control"; return -EINVAL; } const auto itGain = ctrls_.find(V4L2_CID_ANALOGUE_GAIN); if (itGain == ctrls_.end()) { LOG(IPAIPU3, Error) << "Can't find gain control"; return -EINVAL; } const auto itVBlank = ctrls_.find(V4L2_CID_VBLANK); if (itVBlank == ctrls_.end()) { LOG(IPAIPU3, Error) << "Can't find VBLANK control"; return -EINVAL; } minExposure_ = std::max(itExp->second.min().get(), 1); maxExposure_ = itExp->second.max().get(); exposure_ = minExposure_; minGain_ = std::max(itGain->second.min().get(), 1); maxGain_ = itGain->second.max().get(); gain_ = minGain_; defVBlank_ = itVBlank->second.def().get(); /* Clean context at configuration */ context_ = {}; calculateBdsGrid(configInfo.bdsOutputSize); /* Update the camera controls using the new sensor settings. */ updateControls(sensorInfo_, ctrls_, ipaControls); /* Update the IPASessionConfiguration using the sensor settings. */ updateSessionConfiguration(sensorInfo_, ctrls_); for (auto const &algo : algorithms_) { int ret = algo->configure(context_, configInfo); if (ret) return ret; } return 0; } /** * \brief Map the parameters and stats buffers allocated in the pipeline handler * \param[in] buffers The buffers to map */ void IPAIPU3::mapBuffers(const std::vector &buffers) { for (const IPABuffer &buffer : buffers) { const FrameBuffer fb(buffer.planes); buffers_.emplace(buffer.id, MappedFrameBuffer(&fb, MappedFrameBuffer::MapFlag::ReadWrite)); } } /** * \brief Unmap the parameters and stats buffers * \param[in] ids The IDs of the buffers to unmap */ void IPAIPU3::unmapBuffers(const std::vector &ids) { for (unsigned int id : ids) { auto it = buffers_.find(id); if (it == buffers_.end()) continue; buffers_.erase(it); } } /** * \brief Process an event generated by the pipeline handler * \param[in] event The event sent from pipeline handler */ void IPAIPU3::processEvent(const IPU3Event &event) { switch (event.op) { case EventProcessControls: { processControls(event.frame, event.controls); break; } case EventStatReady: { auto it = buffers_.find(event.bufferId); if (it == buffers_.end()) { LOG(IPAIPU3, Error) << "Could not find stats buffer!"; return; } Span mem = it->second.planes()[0]; const ipu3_uapi_stats_3a *stats = reinterpret_cast(mem.data()); parseStatistics(event.frame, event.frameTimestamp, stats); break; } case EventFillParams: { auto it = buffers_.find(event.bufferId); if (it == buffers_.end()) { LOG(IPAIPU3, Error) << "Could not find param buffer!"; return; } Span mem = it->second.planes()[0]; ipu3_uapi_params *params = reinterpret_cast(mem.data()); fillParams(event.frame, params); break; } default: LOG(IPAIPU3, Error) << "Unknown event " << event.op; break; } } /** * \brief Process a control list for a request from the application * \param[in] frame The number of the frame which will be processed next * \param[in] controls The controls for the \a frame * * Parse the request to handle any IPA-managed controls that were set from the * application such as manual sensor settings. */ void IPAIPU3::processControls([[maybe_unused]] unsigned int frame, [[maybe_unused]] const ControlList &controls) { /* \todo Start processing for 'frame' based on 'controls'. */ } /** * \brief Fill the ImgU parameter buffer for the next frame * \param[in] frame The number of the latest frame processed * \param[out] params The parameter buffer to fill * * Algorithms are expected to fill the IPU3 parameter buffer for the next * frame given their most recent processing of the ImgU statistics. */ void IPAIPU3::fillParams(unsigned int frame, ipu3_uapi_params *params) { /* * The incoming params buffer may contain uninitialised data, or the * parameters of previously queued frames. Clearing the entire buffer * may be an expensive operation, and the kernel will only read from * structures which have their associated use-flag set. * * It is the responsibility of the algorithms to set the use flags * accordingly for any data structure they update during prepare(). */ params->use = {}; for (auto const &algo : algorithms_) algo->prepare(context_, params); IPU3Action op; op.op = ActionParamFilled; queueFrameAction.emit(frame, op); } /** * \brief Process the statistics generated by the ImgU * \param[in] frame The number of the latest frame processed * \param[in] frameTimestamp The current frame timestamp * \param[in] stats The IPU3 statistics and ISP results * * Parse the most recently processed image statistics from the ImgU. The * statistics are passed to each algorithm module to run their calculations and * update their state accordingly. */ void IPAIPU3::parseStatistics(unsigned int frame, [[maybe_unused]] int64_t frameTimestamp, const ipu3_uapi_stats_3a *stats) { ControlList ctrls(controls::controls); for (auto const &algo : algorithms_) algo->process(context_, stats); setControls(frame); /* \todo Use VBlank value calculated from each frame exposure. */ int64_t frameDuration = sensorInfo_.lineLength * (defVBlank_ + sensorInfo_.outputSize.height) / (sensorInfo_.pixelRate / 1e6); ctrls.set(controls::FrameDuration, frameDuration); IPU3Action op; op.op = ActionMetadataReady; op.controls = ctrls; queueFrameAction.emit(frame, op); } /** * \brief Handle sensor controls for a given \a frame number * \param[in] frame The frame on which the sensor controls should be set * * Send the desired sensor control values to the pipeline handler to request * that they are applied on the camera sensor. */ void IPAIPU3::setControls(unsigned int frame) { IPU3Action op; op.op = ActionSetSensorControls; exposure_ = context_.frameContext.agc.exposure; gain_ = camHelper_->gainCode(context_.frameContext.agc.gain); ControlList ctrls(ctrls_); ctrls.set(V4L2_CID_EXPOSURE, static_cast(exposure_)); ctrls.set(V4L2_CID_ANALOGUE_GAIN, static_cast(gain_)); op.controls = ctrls; queueFrameAction.emit(frame, op); } } /* namespace ipa::ipu3 */ /** * \brief External IPA module interface * * The IPAModuleInfo is required to match an IPA module construction against the * intented pipeline handler with the module. The API and pipeline handler * versions must match the corresponding IPA interface and pipeline handler. * * \sa struct IPAModuleInfo */ extern "C" { const struct IPAModuleInfo ipaModuleInfo = { IPA_MODULE_API_VERSION, 1, "PipelineHandlerIPU3", "ipu3", }; /** * \brief Create an instance of the IPA interface * * This function is the entry point of the IPA module. It is called by the IPA * manager to create an instance of the IPA interface for each camera. When * matched against with a pipeline handler, the IPAManager will construct an IPA * instance for each associated Camera. */ IPAInterface *ipaCreate() { return new ipa::ipu3::IPAIPU3(); } } } /* namespace libcamera */