Files
external_libcamera/src/libcamera/pipeline/ipu3/imgu.cpp
Laurent Pinchart f2734ff3ab libcamera: Replace utils::clamp() with std::clamp()
Now that libcamera uses C++17, the C++ standard library provides
std::clamp(). Drop our custom utils::clamp() function.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
2020-08-25 02:03:16 +03:00

697 lines
18 KiB
C++

/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* imgu.cpp - Intel IPU3 ImgU
*/
#include "imgu.h"
#include <algorithm>
#include <cmath>
#include <limits>
#include <linux/media-bus-format.h>
#include <libcamera/formats.h>
#include <libcamera/stream.h>
#include "libcamera/internal/log.h"
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/utils.h"
namespace libcamera {
LOG_DECLARE_CATEGORY(IPU3)
namespace {
/*
* The procedure to calculate the ImgU pipe configuration has been ported
* from the pipe_config.py python script, available at:
* https://github.com/intel/intel-ipu3-pipecfg
* at revision: 61e83f2f7606 ("Add more information into README")
*/
static constexpr unsigned int FILTER_H = 4;
static constexpr unsigned int IF_ALIGN_W = 2;
static constexpr unsigned int IF_ALIGN_H = 4;
static constexpr unsigned int BDS_ALIGN_W = 2;
static constexpr unsigned int BDS_ALIGN_H = 4;
static constexpr unsigned int IF_CROP_MAX_W = 40;
static constexpr unsigned int IF_CROP_MAX_H = 540;
static constexpr float BDS_SF_MAX = 2.5;
static constexpr float BDS_SF_MIN = 1.0;
static constexpr float BDS_SF_STEP = 0.03125;
/* BSD scaling factors: min=1, max=2.5, step=1/32 */
const std::vector<float> bdsScalingFactors = {
1, 1.03125, 1.0625, 1.09375, 1.125, 1.15625, 1.1875, 1.21875, 1.25,
1.28125, 1.3125, 1.34375, 1.375, 1.40625, 1.4375, 1.46875, 1.5, 1.53125,
1.5625, 1.59375, 1.625, 1.65625, 1.6875, 1.71875, 1.75, 1.78125, 1.8125,
1.84375, 1.875, 1.90625, 1.9375, 1.96875, 2, 2.03125, 2.0625, 2.09375,
2.125, 2.15625, 2.1875, 2.21875, 2.25, 2.28125, 2.3125, 2.34375, 2.375,
2.40625, 2.4375, 2.46875, 2.5
};
/* GDC scaling factors: min=1, max=16, step=1/4 */
const std::vector<float> gdcScalingFactors = {
1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3, 3.25, 3.5, 3.75, 4, 4.25,
4.5, 4.75, 5, 5.25, 5.5, 5.75, 6, 6.25, 6.5, 6.75, 7, 7.25, 7.5, 7.75,
8, 8.25, 8.5, 8.75, 9, 9.25, 9.5, 9.75, 10, 10.25, 10.5, 10.75, 11,
11.25, 11.5, 11.75, 12, 12.25, 12.5, 12.75, 13, 13.25, 13.5, 13.75, 14,
14.25, 14.5, 14.75, 15, 15.25, 15.5, 15.75, 16,
};
std::vector<ImgUDevice::PipeConfig> pipeConfigs;
struct FOV {
float w;
float h;
bool isLarger(const FOV &other)
{
if (w > other.w)
return true;
if (w == other.w && h > other.h)
return true;
return false;
}
};
/* Approximate a scaling factor sf to the closest one available in a range. */
float findScaleFactor(float sf, const std::vector<float> &range,
bool roundDown = false)
{
if (sf <= range[0])
return range[0];
if (sf >= range[range.size() - 1])
return range[range.size() - 1];
float bestDiff = std::numeric_limits<float>::max();
unsigned int index = 0;
for (unsigned int i = 0; i < range.size(); ++i) {
float diff = std::abs(sf - range[i]);
if (diff < bestDiff) {
bestDiff = diff;
index = i;
}
}
if (roundDown && index > 0 && sf < range[index])
index--;
return range[index];
}
bool isSameRatio(const Size &in, const Size &out)
{
float inRatio = static_cast<float>(in.width) / in.height;
float outRatio = static_cast<float>(out.width) / out.height;
if (std::abs(inRatio - outRatio) > 0.1)
return false;
return true;
}
void calculateBDSHeight(ImgUDevice::Pipe *pipe, const Size &iif, const Size &gdc,
unsigned int bdsWidth, float bdsSF)
{
unsigned int minIFHeight = iif.height - IF_CROP_MAX_H;
unsigned int minBDSHeight = gdc.height + FILTER_H * 2;
unsigned int ifHeight;
float bdsHeight;
if (!isSameRatio(pipe->input, gdc)) {
float estIFHeight = (iif.width * gdc.height) /
static_cast<float>(gdc.width);
estIFHeight = std::clamp<float>(estIFHeight, minIFHeight, iif.height);
bool found = false;
ifHeight = utils::alignUp(estIFHeight, IF_ALIGN_H);
while (ifHeight >= minIFHeight && ifHeight / bdsSF >= minBDSHeight) {
bdsHeight = ifHeight / bdsSF;
if (std::fmod(bdsHeight, 1.0) == 0) {
unsigned int bdsIntHeight = static_cast<unsigned int>(bdsHeight);
if (!(bdsIntHeight % BDS_ALIGN_H)) {
found = true;
break;
}
}
ifHeight -= IF_ALIGN_H;
}
ifHeight = utils::alignUp(estIFHeight, IF_ALIGN_H);
while (ifHeight <= iif.height && ifHeight / bdsSF >= minBDSHeight) {
bdsHeight = ifHeight / bdsSF;
if (std::fmod(bdsHeight, 1.0) == 0) {
unsigned int bdsIntHeight = static_cast<unsigned int>(bdsHeight);
if (!(bdsIntHeight % BDS_ALIGN_H)) {
found = true;
break;
}
}
ifHeight += IF_ALIGN_H;
}
if (found) {
unsigned int bdsIntHeight = static_cast<unsigned int>(bdsHeight);
pipeConfigs.push_back({ bdsSF, { iif.width, ifHeight },
{ bdsWidth, bdsIntHeight }, gdc });
return;
}
} else {
ifHeight = utils::alignUp(iif.height, IF_ALIGN_H);
while (ifHeight > minIFHeight && ifHeight / bdsSF >= minBDSHeight) {
bdsHeight = ifHeight / bdsSF;
if (std::fmod(ifHeight, 1.0) == 0 && std::fmod(bdsHeight, 1.0) == 0) {
unsigned int bdsIntHeight = static_cast<unsigned int>(bdsHeight);
if (!(ifHeight % IF_ALIGN_H) &&
!(bdsIntHeight % BDS_ALIGN_H)) {
pipeConfigs.push_back({ bdsSF, { iif.width, ifHeight },
{ bdsWidth, bdsIntHeight }, gdc });
}
}
ifHeight -= IF_ALIGN_H;
}
}
}
void calculateBDS(ImgUDevice::Pipe *pipe, const Size &iif, const Size &gdc, float bdsSF)
{
unsigned int minBDSWidth = gdc.width + FILTER_H * 2;
float sf = bdsSF;
while (sf <= BDS_SF_MAX && sf >= BDS_SF_MIN) {
float bdsWidth = static_cast<float>(iif.width) / sf;
if (std::fmod(bdsWidth, 1.0) == 0) {
unsigned int bdsIntWidth = static_cast<unsigned int>(bdsWidth);
if (!(bdsIntWidth % BDS_ALIGN_W) && bdsWidth >= minBDSWidth)
calculateBDSHeight(pipe, iif, gdc, bdsIntWidth, sf);
}
sf += BDS_SF_STEP;
}
sf = bdsSF;
while (sf <= BDS_SF_MAX && sf >= BDS_SF_MIN) {
float bdsWidth = static_cast<float>(iif.width) / sf;
if (std::fmod(bdsWidth, 1.0) == 0) {
unsigned int bdsIntWidth = static_cast<unsigned int>(bdsWidth);
if (!(bdsIntWidth % BDS_ALIGN_W) && bdsWidth >= minBDSWidth)
calculateBDSHeight(pipe, iif, gdc, bdsIntWidth, sf);
}
sf -= BDS_SF_STEP;
}
}
Size calculateGDC(ImgUDevice::Pipe *pipe)
{
const Size &in = pipe->input;
const Size &main = pipe->main;
const Size &vf = pipe->viewfinder;
Size gdc;
if (!vf.isNull()) {
gdc.width = main.width;
float ratio = (main.width * vf.height) / static_cast<float>(vf.width);
gdc.height = std::max(static_cast<float>(main.height), ratio);
return gdc;
}
if (!isSameRatio(in, main)) {
gdc = main;
return gdc;
}
float totalSF = static_cast<float>(in.width) / main.width;
float bdsSF = totalSF > 2 ? 2 : 1;
float yuvSF = totalSF / bdsSF;
float sf = findScaleFactor(yuvSF, gdcScalingFactors);
gdc.width = main.width * sf;
gdc.height = main.height * sf;
return gdc;
}
FOV calcFOV(const Size &in, const ImgUDevice::PipeConfig &pipe)
{
FOV fov{};
float inW = static_cast<float>(in.width);
float inH = static_cast<float>(in.height);
float ifCropW = static_cast<float>(in.width - pipe.iif.width);
float ifCropH = static_cast<float>(in.height - pipe.iif.height);
float gdcCropW = static_cast<float>(pipe.bds.width - pipe.gdc.width) * pipe.bds_sf;
float gdcCropH = static_cast<float>(pipe.bds.height - pipe.gdc.height) * pipe.bds_sf;
fov.w = (inW - (ifCropW + gdcCropW)) / inW;
fov.h = (inH - (ifCropH + gdcCropH)) / inH;
return fov;
}
} /* namespace */
/**
* \struct PipeConfig
* \brief The ImgU pipe configuration parameters
*
* The ImgU image pipeline is composed of several hardware blocks that crop
* and scale the input image to obtain the desired output sizes. The
* scaling/cropping operations of those components is configured though the
* V4L2 selection API and the V4L2 subdev API applied to the ImgU media entity.
*
* The configurable components in the pipeline are:
* - IF: image feeder
* - BDS: bayer downscaler
* - GDC: geometric distorsion correction
*
* The IF crop rectangle is controlled by the V4L2_SEL_TGT_CROP selection target
* applied to the ImgU media entity sink pad number 0. The BDS scaler is
* controlled by the V4L2_SEL_TGT_COMPOSE target on the same pad, while the GDC
* output size is configured with the VIDIOC_SUBDEV_S_FMT IOCTL, again on pad
* number 0.
*
* The PipeConfig structure collects the sizes of each of those components
* plus the BDS scaling factor used to calculate the field of view
* of the final images.
*/
/**
* \struct Pipe
* \brief Describe the ImgU requested configuration
*
* The ImgU unit processes images through several components, which have
* to be properly configured inspecting the input image size and the desired
* output sizes. This structure collects the ImgU input configuration and the
* requested main output and viewfinder configurations.
*
* \var Pipe::input
* \brief The input image size
*
* \var Pipe::main
* \brief The requested main output size
*
* \var Pipe::viewfinder
* \brief The requested viewfinder output size
*/
/**
* \brief Initialize components of the ImgU instance
* \param[in] mediaDevice The ImgU instance media device
* \param[in] index The ImgU instance index
*
* Create and open the V4L2 devices and subdevices of the ImgU instance
* with \a index.
*
* In case of errors the created V4L2VideoDevice and V4L2Subdevice instances
* are destroyed at pipeline handler delete time.
*
* \return 0 on success or a negative error code otherwise
*/
int ImgUDevice::init(MediaDevice *media, unsigned int index)
{
int ret;
name_ = "ipu3-imgu " + std::to_string(index);
media_ = media;
/*
* The media entities presence in the media device has been verified
* by the match() function: no need to check for newly created
* video devices and subdevice validity here.
*/
imgu_.reset(V4L2Subdevice::fromEntityName(media, name_));
ret = imgu_->open();
if (ret)
return ret;
input_.reset(V4L2VideoDevice::fromEntityName(media, name_ + " input"));
ret = input_->open();
if (ret)
return ret;
output_.reset(V4L2VideoDevice::fromEntityName(media,
name_ + " output"));
ret = output_->open();
if (ret)
return ret;
viewfinder_.reset(V4L2VideoDevice::fromEntityName(media,
name_ + " viewfinder"));
ret = viewfinder_->open();
if (ret)
return ret;
stat_.reset(V4L2VideoDevice::fromEntityName(media, name_ + " 3a stat"));
ret = stat_->open();
if (ret)
return ret;
return 0;
}
/**
* \brief Calculate the ImgU pipe configuration parameters
* \param[in] pipe The requested ImgU configuration
* \return An ImgUDevice::PipeConfig instance on success, an empty configuration
* otherwise
*/
ImgUDevice::PipeConfig ImgUDevice::calculatePipeConfig(Pipe *pipe)
{
pipeConfigs.clear();
LOG(IPU3, Debug) << "Calculating pipe configuration for: ";
LOG(IPU3, Debug) << "input: " << pipe->input.toString();
LOG(IPU3, Debug) << "main: " << pipe->main.toString();
LOG(IPU3, Debug) << "vf: " << pipe->viewfinder.toString();
const Size &in = pipe->input;
Size gdc = calculateGDC(pipe);
unsigned int ifWidth = utils::alignUp(in.width, IF_ALIGN_W);
unsigned int ifHeight = in.height;
unsigned int minIfWidth = in.width - IF_CROP_MAX_W;
float bdsSF = static_cast<float>(in.width) / gdc.width;
float sf = findScaleFactor(bdsSF, bdsScalingFactors, true);
while (ifWidth >= minIfWidth) {
Size iif{ ifWidth, ifHeight };
calculateBDS(pipe, iif, gdc, sf);
ifWidth -= IF_ALIGN_W;
}
if (pipeConfigs.size() == 0) {
LOG(IPU3, Error) << "Failed to calculate pipe configuration";
return {};
}
FOV bestFov = calcFOV(pipe->input, pipeConfigs[0]);
unsigned int bestIndex = 0;
unsigned int p = 0;
for (auto pipeConfig : pipeConfigs) {
FOV fov = calcFOV(pipe->input, pipeConfig);
if (fov.isLarger(bestFov)) {
bestFov = fov;
bestIndex = p;
}
++p;
}
LOG(IPU3, Debug) << "Computed pipe configuration: ";
LOG(IPU3, Debug) << "IF: " << pipeConfigs[bestIndex].iif.toString();
LOG(IPU3, Debug) << "BDS: " << pipeConfigs[bestIndex].bds.toString();
LOG(IPU3, Debug) << "GDC: " << pipeConfigs[bestIndex].gdc.toString();
return pipeConfigs[bestIndex];
}
/**
* \brief Configure the ImgU pipeline
* \param[in] config The ImgU pipe configuration parameters
* \param[in] inputFormat The format to be applied to ImgU input
* \return 0 on success or a negative error code otherwise
*/
int ImgUDevice::configure(const PipeConfig &pipeConfig, V4L2DeviceFormat *inputFormat)
{
/* Configure the ImgU input video device with the requested sizes. */
int ret = input_->setFormat(inputFormat);
if (ret)
return ret;
LOG(IPU3, Debug) << "ImgU input format = " << inputFormat->toString();
/*
* \todo The IPU3 driver implementation shall be changed to use the
* input sizes as 'ImgU Input' subdevice sizes, and use the desired
* GDC output sizes to configure the crop/compose rectangles.
*
* The current IPU3 driver implementation uses GDC sizes as the
* 'ImgU Input' subdevice sizes, and the input video device sizes
* to configure the crop/compose rectangles, contradicting the
* V4L2 specification.
*/
Rectangle iif{ 0, 0, pipeConfig.iif };
ret = imgu_->setSelection(PAD_INPUT, V4L2_SEL_TGT_CROP, &iif);
if (ret)
return ret;
LOG(IPU3, Debug) << "ImgU IF rectangle = " << iif.toString();
Rectangle bds{ 0, 0, pipeConfig.bds };
ret = imgu_->setSelection(PAD_INPUT, V4L2_SEL_TGT_COMPOSE, &bds);
if (ret)
return ret;
LOG(IPU3, Debug) << "ImgU BDS rectangle = " << bds.toString();
V4L2SubdeviceFormat gdcFormat = {};
gdcFormat.mbus_code = MEDIA_BUS_FMT_FIXED;
gdcFormat.size = pipeConfig.gdc;
ret = imgu_->setFormat(PAD_INPUT, &gdcFormat);
if (ret)
return ret;
LOG(IPU3, Debug) << "ImgU GDC format = " << gdcFormat.toString();
return 0;
}
/**
* \brief Configure a video device on the ImgU
* \param[in] dev The video device to configure
* \param[in] pad The pad of the ImgU subdevice
* \param[in] cfg The requested configuration
* \param[out] outputFormat The format set on the video device
* \return 0 on success or a negative error code otherwise
*/
int ImgUDevice::configureVideoDevice(V4L2VideoDevice *dev, unsigned int pad,
const StreamConfiguration &cfg,
V4L2DeviceFormat *outputFormat)
{
V4L2SubdeviceFormat imguFormat = {};
imguFormat.mbus_code = MEDIA_BUS_FMT_FIXED;
imguFormat.size = cfg.size;
int ret = imgu_->setFormat(pad, &imguFormat);
if (ret)
return ret;
/* No need to apply format to the stat node. */
if (dev == stat_.get())
return 0;
*outputFormat = {};
outputFormat->fourcc = dev->toV4L2PixelFormat(formats::NV12);
outputFormat->size = cfg.size;
outputFormat->planesCount = 2;
ret = dev->setFormat(outputFormat);
if (ret)
return ret;
const char *name = dev == output_.get() ? "output" : "viewfinder";
LOG(IPU3, Debug) << "ImgU " << name << " format = "
<< outputFormat->toString();
return 0;
}
/**
* \brief Allocate buffers for all the ImgU video devices
*/
int ImgUDevice::allocateBuffers(unsigned int bufferCount)
{
/* Share buffers between CIO2 output and ImgU input. */
int ret = input_->importBuffers(bufferCount);
if (ret) {
LOG(IPU3, Error) << "Failed to import ImgU input buffers";
return ret;
}
/*
* The kernel fails to start if buffers are not either imported or
* allocated for the statistics video device. As statistics buffers are
* not yet used by the pipeline import buffers to save memory.
*
* \todo To be revised when we'll actually use the stat node.
*/
ret = stat_->importBuffers(bufferCount);
if (ret < 0) {
LOG(IPU3, Error) << "Failed to allocate ImgU stat buffers";
goto error;
}
/*
* Import buffers for all outputs, regardless of whether the
* corresponding stream is active or inactive, as the driver needs
* buffers to be requested on the V4L2 devices in order to operate.
*/
ret = output_->importBuffers(bufferCount);
if (ret < 0) {
LOG(IPU3, Error) << "Failed to import ImgU output buffers";
goto error;
}
ret = viewfinder_->importBuffers(bufferCount);
if (ret < 0) {
LOG(IPU3, Error) << "Failed to import ImgU viewfinder buffers";
goto error;
}
return 0;
error:
freeBuffers();
return ret;
}
/**
* \brief Release buffers for all the ImgU video devices
*/
void ImgUDevice::freeBuffers()
{
int ret;
ret = output_->releaseBuffers();
if (ret)
LOG(IPU3, Error) << "Failed to release ImgU output buffers";
ret = stat_->releaseBuffers();
if (ret)
LOG(IPU3, Error) << "Failed to release ImgU stat buffers";
ret = viewfinder_->releaseBuffers();
if (ret)
LOG(IPU3, Error) << "Failed to release ImgU viewfinder buffers";
ret = input_->releaseBuffers();
if (ret)
LOG(IPU3, Error) << "Failed to release ImgU input buffers";
}
int ImgUDevice::start()
{
int ret;
/* Start the ImgU video devices. */
ret = output_->streamOn();
if (ret) {
LOG(IPU3, Error) << "Failed to start ImgU output";
return ret;
}
ret = viewfinder_->streamOn();
if (ret) {
LOG(IPU3, Error) << "Failed to start ImgU viewfinder";
return ret;
}
ret = stat_->streamOn();
if (ret) {
LOG(IPU3, Error) << "Failed to start ImgU stat";
return ret;
}
ret = input_->streamOn();
if (ret) {
LOG(IPU3, Error) << "Failed to start ImgU input";
return ret;
}
return 0;
}
int ImgUDevice::stop()
{
int ret;
ret = output_->streamOff();
ret |= viewfinder_->streamOff();
ret |= stat_->streamOff();
ret |= input_->streamOff();
return ret;
}
/**
* \brief Enable or disable a single link on the ImgU instance
*
* This method assumes the media device associated with the ImgU instance
* is open.
*
* \return 0 on success or a negative error code otherwise
*/
int ImgUDevice::linkSetup(const std::string &source, unsigned int sourcePad,
const std::string &sink, unsigned int sinkPad,
bool enable)
{
MediaLink *link = media_->link(source, sourcePad, sink, sinkPad);
if (!link) {
LOG(IPU3, Error)
<< "Failed to get link: '" << source << "':"
<< sourcePad << " -> '" << sink << "':" << sinkPad;
return -ENODEV;
}
return link->setEnabled(enable);
}
/**
* \brief Enable or disable all media links in the ImgU instance to prepare
* for capture operations
*
* \todo This method will probably be removed or changed once links will be
* enabled or disabled selectively.
*
* \return 0 on success or a negative error code otherwise
*/
int ImgUDevice::enableLinks(bool enable)
{
std::string viewfinderName = name_ + " viewfinder";
std::string outputName = name_ + " output";
std::string statName = name_ + " 3a stat";
std::string inputName = name_ + " input";
int ret;
ret = linkSetup(inputName, 0, name_, PAD_INPUT, enable);
if (ret)
return ret;
ret = linkSetup(name_, PAD_OUTPUT, outputName, 0, enable);
if (ret)
return ret;
ret = linkSetup(name_, PAD_VF, viewfinderName, 0, enable);
if (ret)
return ret;
return linkSetup(name_, PAD_STAT, statName, 0, enable);
}
} /* namespace libcamera */