Files
external_libcamera/src/libcamera/pipeline/rkisp1/rkisp1.cpp
T
Stefan Klug 169b65ce16 pipeline: rkisp1: Split inputCrop and outputCrop
One Rectangle instance is used to calculate the inputCrop and the
outputCrop of the ISP in the rkisp1 pipeline. Split that into two
distinct variables, because both values will be needed in the upcoming
patches. This patch does not contain any functional changes.

Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
2024-12-17 10:31:44 +01:00

1559 lines
40 KiB
C++

/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Pipeline handler for Rockchip ISP1
*/
#include <algorithm>
#include <map>
#include <memory>
#include <numeric>
#include <optional>
#include <queue>
#include <vector>
#include <linux/media-bus-format.h>
#include <linux/rkisp1-config.h>
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>
#include <libcamera/camera.h>
#include <libcamera/color_space.h>
#include <libcamera/control_ids.h>
#include <libcamera/formats.h>
#include <libcamera/framebuffer.h>
#include <libcamera/request.h>
#include <libcamera/stream.h>
#include <libcamera/transform.h>
#include <libcamera/ipa/core_ipa_interface.h>
#include <libcamera/ipa/rkisp1_ipa_interface.h>
#include <libcamera/ipa/rkisp1_ipa_proxy.h>
#include "libcamera/internal/camera.h"
#include "libcamera/internal/camera_sensor.h"
#include "libcamera/internal/camera_sensor_properties.h"
#include "libcamera/internal/converter/converter_v4l2_m2m.h"
#include "libcamera/internal/delayed_controls.h"
#include "libcamera/internal/device_enumerator.h"
#include "libcamera/internal/framebuffer.h"
#include "libcamera/internal/ipa_manager.h"
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/pipeline_handler.h"
#include "libcamera/internal/v4l2_subdevice.h"
#include "libcamera/internal/v4l2_videodevice.h"
#include "rkisp1_path.h"
namespace libcamera {
LOG_DEFINE_CATEGORY(RkISP1)
class PipelineHandlerRkISP1;
class RkISP1CameraData;
struct RkISP1FrameInfo {
unsigned int frame;
Request *request;
FrameBuffer *paramBuffer;
FrameBuffer *statBuffer;
FrameBuffer *mainPathBuffer;
FrameBuffer *selfPathBuffer;
bool paramDequeued;
bool metadataProcessed;
};
class RkISP1Frames
{
public:
RkISP1Frames(PipelineHandler *pipe);
RkISP1FrameInfo *create(const RkISP1CameraData *data, Request *request,
bool isRaw);
int destroy(unsigned int frame);
void clear();
RkISP1FrameInfo *find(unsigned int frame);
RkISP1FrameInfo *find(FrameBuffer *buffer);
RkISP1FrameInfo *find(Request *request);
private:
PipelineHandlerRkISP1 *pipe_;
std::map<unsigned int, RkISP1FrameInfo *> frameInfo_;
};
class RkISP1CameraData : public Camera::Private
{
public:
RkISP1CameraData(PipelineHandler *pipe, RkISP1MainPath *mainPath,
RkISP1SelfPath *selfPath)
: Camera::Private(pipe), frame_(0), frameInfo_(pipe),
mainPath_(mainPath), selfPath_(selfPath)
{
}
PipelineHandlerRkISP1 *pipe();
int loadIPA(unsigned int hwRevision);
Stream mainPathStream_;
Stream selfPathStream_;
std::unique_ptr<CameraSensor> sensor_;
std::unique_ptr<DelayedControls> delayedCtrls_;
unsigned int frame_;
std::vector<IPABuffer> ipaBuffers_;
RkISP1Frames frameInfo_;
RkISP1MainPath *mainPath_;
RkISP1SelfPath *selfPath_;
std::unique_ptr<ipa::rkisp1::IPAProxyRkISP1> ipa_;
ControlInfoMap ipaControls_;
private:
void paramsComputed(unsigned int frame, unsigned int bytesused);
void setSensorControls(unsigned int frame,
const ControlList &sensorControls);
void metadataReady(unsigned int frame, const ControlList &metadata);
};
class RkISP1CameraConfiguration : public CameraConfiguration
{
public:
RkISP1CameraConfiguration(Camera *camera, RkISP1CameraData *data);
Status validate() override;
const V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }
const Transform &combinedTransform() { return combinedTransform_; }
private:
bool fitsAllPaths(const StreamConfiguration &cfg);
/*
* The RkISP1CameraData instance is guaranteed to be valid as long as the
* corresponding Camera instance is valid. In order to borrow a
* reference to the camera data, store a new reference to the camera.
*/
std::shared_ptr<Camera> camera_;
const RkISP1CameraData *data_;
V4L2SubdeviceFormat sensorFormat_;
Transform combinedTransform_;
};
class PipelineHandlerRkISP1 : public PipelineHandler
{
public:
PipelineHandlerRkISP1(CameraManager *manager);
std::unique_ptr<CameraConfiguration> generateConfiguration(Camera *camera,
Span<const StreamRole> roles) override;
int configure(Camera *camera, CameraConfiguration *config) override;
int exportFrameBuffers(Camera *camera, Stream *stream,
std::vector<std::unique_ptr<FrameBuffer>> *buffers) override;
int start(Camera *camera, const ControlList *controls) override;
void stopDevice(Camera *camera) override;
int queueRequestDevice(Camera *camera, Request *request) override;
bool match(DeviceEnumerator *enumerator) override;
private:
static constexpr Size kRkISP1PreviewSize = { 1920, 1080 };
RkISP1CameraData *cameraData(Camera *camera)
{
return static_cast<RkISP1CameraData *>(camera->_d());
}
friend RkISP1CameraData;
friend RkISP1Frames;
int initLinks(Camera *camera, const CameraSensor *sensor,
const RkISP1CameraConfiguration &config);
int createCamera(MediaEntity *sensor);
void tryCompleteRequest(RkISP1FrameInfo *info);
void imageBufferReady(FrameBuffer *buffer);
void paramBufferReady(FrameBuffer *buffer);
void statBufferReady(FrameBuffer *buffer);
void dewarpBufferReady(FrameBuffer *buffer);
void frameStart(uint32_t sequence);
int allocateBuffers(Camera *camera);
int freeBuffers(Camera *camera);
int updateControls(RkISP1CameraData *data);
MediaDevice *media_;
std::unique_ptr<V4L2Subdevice> isp_;
std::unique_ptr<V4L2VideoDevice> param_;
std::unique_ptr<V4L2VideoDevice> stat_;
std::unique_ptr<V4L2Subdevice> csi_;
bool hasSelfPath_;
bool isRaw_;
RkISP1MainPath mainPath_;
RkISP1SelfPath selfPath_;
std::unique_ptr<V4L2M2MConverter> dewarper_;
bool useDewarper_;
std::optional<Rectangle> activeCrop_;
/* Internal buffers used when dewarper is being used */
std::vector<std::unique_ptr<FrameBuffer>> mainPathBuffers_;
std::queue<FrameBuffer *> availableMainPathBuffers_;
std::vector<std::unique_ptr<FrameBuffer>> paramBuffers_;
std::vector<std::unique_ptr<FrameBuffer>> statBuffers_;
std::queue<FrameBuffer *> availableParamBuffers_;
std::queue<FrameBuffer *> availableStatBuffers_;
Camera *activeCamera_;
const MediaPad *ispSink_;
};
RkISP1Frames::RkISP1Frames(PipelineHandler *pipe)
: pipe_(static_cast<PipelineHandlerRkISP1 *>(pipe))
{
}
RkISP1FrameInfo *RkISP1Frames::create(const RkISP1CameraData *data, Request *request,
bool isRaw)
{
unsigned int frame = data->frame_;
FrameBuffer *paramBuffer = nullptr;
FrameBuffer *statBuffer = nullptr;
FrameBuffer *mainPathBuffer = nullptr;
FrameBuffer *selfPathBuffer = nullptr;
if (!isRaw) {
if (pipe_->availableParamBuffers_.empty()) {
LOG(RkISP1, Error) << "Parameters buffer underrun";
return nullptr;
}
if (pipe_->availableStatBuffers_.empty()) {
LOG(RkISP1, Error) << "Statistic buffer underrun";
return nullptr;
}
paramBuffer = pipe_->availableParamBuffers_.front();
pipe_->availableParamBuffers_.pop();
statBuffer = pipe_->availableStatBuffers_.front();
pipe_->availableStatBuffers_.pop();
if (pipe_->useDewarper_) {
mainPathBuffer = pipe_->availableMainPathBuffers_.front();
pipe_->availableMainPathBuffers_.pop();
}
}
if (!mainPathBuffer)
mainPathBuffer = request->findBuffer(&data->mainPathStream_);
selfPathBuffer = request->findBuffer(&data->selfPathStream_);
RkISP1FrameInfo *info = new RkISP1FrameInfo;
info->frame = frame;
info->request = request;
info->paramBuffer = paramBuffer;
info->mainPathBuffer = mainPathBuffer;
info->selfPathBuffer = selfPathBuffer;
info->statBuffer = statBuffer;
info->paramDequeued = false;
info->metadataProcessed = false;
frameInfo_[frame] = info;
return info;
}
int RkISP1Frames::destroy(unsigned int frame)
{
RkISP1FrameInfo *info = find(frame);
if (!info)
return -ENOENT;
pipe_->availableParamBuffers_.push(info->paramBuffer);
pipe_->availableStatBuffers_.push(info->statBuffer);
pipe_->availableMainPathBuffers_.push(info->mainPathBuffer);
frameInfo_.erase(info->frame);
delete info;
return 0;
}
void RkISP1Frames::clear()
{
for (const auto &entry : frameInfo_) {
RkISP1FrameInfo *info = entry.second;
pipe_->availableParamBuffers_.push(info->paramBuffer);
pipe_->availableStatBuffers_.push(info->statBuffer);
pipe_->availableMainPathBuffers_.push(info->mainPathBuffer);
delete info;
}
frameInfo_.clear();
}
RkISP1FrameInfo *RkISP1Frames::find(unsigned int frame)
{
auto itInfo = frameInfo_.find(frame);
if (itInfo != frameInfo_.end())
return itInfo->second;
LOG(RkISP1, Fatal) << "Can't locate info from frame";
return nullptr;
}
RkISP1FrameInfo *RkISP1Frames::find(FrameBuffer *buffer)
{
for (auto &itInfo : frameInfo_) {
RkISP1FrameInfo *info = itInfo.second;
if (info->paramBuffer == buffer ||
info->statBuffer == buffer ||
info->mainPathBuffer == buffer ||
info->selfPathBuffer == buffer)
return info;
}
LOG(RkISP1, Fatal) << "Can't locate info from buffer";
return nullptr;
}
RkISP1FrameInfo *RkISP1Frames::find(Request *request)
{
for (auto &itInfo : frameInfo_) {
RkISP1FrameInfo *info = itInfo.second;
if (info->request == request)
return info;
}
LOG(RkISP1, Fatal) << "Can't locate info from request";
return nullptr;
}
PipelineHandlerRkISP1 *RkISP1CameraData::pipe()
{
return static_cast<PipelineHandlerRkISP1 *>(Camera::Private::pipe());
}
int RkISP1CameraData::loadIPA(unsigned int hwRevision)
{
ipa_ = IPAManager::createIPA<ipa::rkisp1::IPAProxyRkISP1>(pipe(), 1, 1);
if (!ipa_)
return -ENOENT;
ipa_->setSensorControls.connect(this, &RkISP1CameraData::setSensorControls);
ipa_->paramsComputed.connect(this, &RkISP1CameraData::paramsComputed);
ipa_->metadataReady.connect(this, &RkISP1CameraData::metadataReady);
/*
* The API tuning file is made from the sensor name unless the
* environment variable overrides it.
*/
std::string ipaTuningFile;
char const *configFromEnv = utils::secure_getenv("LIBCAMERA_RKISP1_TUNING_FILE");
if (!configFromEnv || *configFromEnv == '\0') {
ipaTuningFile =
ipa_->configurationFile(sensor_->model() + ".yaml", "uncalibrated.yaml");
} else {
ipaTuningFile = std::string(configFromEnv);
}
IPACameraSensorInfo sensorInfo{};
int ret = sensor_->sensorInfo(&sensorInfo);
if (ret) {
LOG(RkISP1, Error) << "Camera sensor information not available";
return ret;
}
ret = ipa_->init({ ipaTuningFile, sensor_->model() }, hwRevision,
sensorInfo, sensor_->controls(), &ipaControls_);
if (ret < 0) {
LOG(RkISP1, Error) << "IPA initialization failure";
return ret;
}
return 0;
}
void RkISP1CameraData::paramsComputed(unsigned int frame, unsigned int bytesused)
{
PipelineHandlerRkISP1 *pipe = RkISP1CameraData::pipe();
RkISP1FrameInfo *info = frameInfo_.find(frame);
if (!info)
return;
info->paramBuffer->_d()->metadata().planes()[0].bytesused = bytesused;
pipe->param_->queueBuffer(info->paramBuffer);
pipe->stat_->queueBuffer(info->statBuffer);
if (info->mainPathBuffer)
mainPath_->queueBuffer(info->mainPathBuffer);
if (selfPath_ && info->selfPathBuffer)
selfPath_->queueBuffer(info->selfPathBuffer);
}
void RkISP1CameraData::setSensorControls([[maybe_unused]] unsigned int frame,
const ControlList &sensorControls)
{
delayedCtrls_->push(sensorControls);
}
void RkISP1CameraData::metadataReady(unsigned int frame, const ControlList &metadata)
{
RkISP1FrameInfo *info = frameInfo_.find(frame);
if (!info)
return;
info->request->metadata().merge(metadata);
info->metadataProcessed = true;
pipe()->tryCompleteRequest(info);
}
/* -----------------------------------------------------------------------------
* Camera Configuration
*/
namespace {
/* Keep in sync with the supported raw formats in rkisp1_path.cpp. */
const std::map<PixelFormat, uint32_t> rawFormats = {
{ formats::SBGGR8, MEDIA_BUS_FMT_SBGGR8_1X8 },
{ formats::SGBRG8, MEDIA_BUS_FMT_SGBRG8_1X8 },
{ formats::SGRBG8, MEDIA_BUS_FMT_SGRBG8_1X8 },
{ formats::SRGGB8, MEDIA_BUS_FMT_SRGGB8_1X8 },
{ formats::SBGGR10, MEDIA_BUS_FMT_SBGGR10_1X10 },
{ formats::SGBRG10, MEDIA_BUS_FMT_SGBRG10_1X10 },
{ formats::SGRBG10, MEDIA_BUS_FMT_SGRBG10_1X10 },
{ formats::SRGGB10, MEDIA_BUS_FMT_SRGGB10_1X10 },
{ formats::SBGGR12, MEDIA_BUS_FMT_SBGGR12_1X12 },
{ formats::SGBRG12, MEDIA_BUS_FMT_SGBRG12_1X12 },
{ formats::SGRBG12, MEDIA_BUS_FMT_SGRBG12_1X12 },
{ formats::SRGGB12, MEDIA_BUS_FMT_SRGGB12_1X12 },
};
} /* namespace */
RkISP1CameraConfiguration::RkISP1CameraConfiguration(Camera *camera,
RkISP1CameraData *data)
: CameraConfiguration()
{
camera_ = camera->shared_from_this();
data_ = data;
}
bool RkISP1CameraConfiguration::fitsAllPaths(const StreamConfiguration &cfg)
{
const CameraSensor *sensor = data_->sensor_.get();
StreamConfiguration config;
config = cfg;
if (data_->mainPath_->validate(sensor, sensorConfig, &config) != Valid)
return false;
config = cfg;
if (data_->selfPath_ &&
data_->selfPath_->validate(sensor, sensorConfig, &config) != Valid)
return false;
return true;
}
CameraConfiguration::Status RkISP1CameraConfiguration::validate()
{
const CameraSensor *sensor = data_->sensor_.get();
unsigned int pathCount = data_->selfPath_ ? 2 : 1;
Status status;
if (config_.empty())
return Invalid;
status = validateColorSpaces(ColorSpaceFlag::StreamsShareColorSpace);
/*
* Make sure that if a sensor configuration has been requested it
* is valid.
*/
if (sensorConfig) {
if (!sensorConfig->isValid()) {
LOG(RkISP1, Error)
<< "Invalid sensor configuration request";
return Invalid;
}
unsigned int bitDepth = sensorConfig->bitDepth;
if (bitDepth != 8 && bitDepth != 10 && bitDepth != 12) {
LOG(RkISP1, Error)
<< "Invalid sensor configuration bit depth";
return Invalid;
}
}
/* Cap the number of entries to the available streams. */
if (config_.size() > pathCount) {
config_.resize(pathCount);
status = Adjusted;
}
Orientation requestedOrientation = orientation;
combinedTransform_ = data_->sensor_->computeTransform(&orientation);
if (orientation != requestedOrientation)
status = Adjusted;
/*
* Simultaneous capture of raw and processed streams isn't possible. If
* there is any raw stream, cap the number of streams to one.
*/
if (config_.size() > 1) {
for (const auto &cfg : config_) {
if (PixelFormatInfo::info(cfg.pixelFormat).colourEncoding ==
PixelFormatInfo::ColourEncodingRAW) {
config_.resize(1);
status = Adjusted;
break;
}
}
}
/*
* If there are more than one stream in the configuration figure out the
* order to evaluate the streams. The first stream has the highest
* priority but if both main path and self path can satisfy it evaluate
* the second stream first as the first stream is guaranteed to work
* with whichever path is not used by the second one.
*/
std::vector<unsigned int> order(config_.size());
std::iota(order.begin(), order.end(), 0);
if (config_.size() == 2 && fitsAllPaths(config_[0]))
std::reverse(order.begin(), order.end());
bool mainPathAvailable = true;
bool selfPathAvailable = data_->selfPath_;
for (unsigned int index : order) {
StreamConfiguration &cfg = config_[index];
/* Try to match stream without adjusting configuration. */
if (mainPathAvailable) {
StreamConfiguration tryCfg = cfg;
if (data_->mainPath_->validate(sensor, sensorConfig, &tryCfg) == Valid) {
mainPathAvailable = false;
cfg = tryCfg;
cfg.setStream(const_cast<Stream *>(&data_->mainPathStream_));
continue;
}
}
if (selfPathAvailable) {
StreamConfiguration tryCfg = cfg;
if (data_->selfPath_->validate(sensor, sensorConfig, &tryCfg) == Valid) {
selfPathAvailable = false;
cfg = tryCfg;
cfg.setStream(const_cast<Stream *>(&data_->selfPathStream_));
continue;
}
}
/* Try to match stream allowing adjusting configuration. */
if (mainPathAvailable) {
StreamConfiguration tryCfg = cfg;
if (data_->mainPath_->validate(sensor, sensorConfig, &tryCfg) == Adjusted) {
mainPathAvailable = false;
cfg = tryCfg;
cfg.setStream(const_cast<Stream *>(&data_->mainPathStream_));
status = Adjusted;
continue;
}
}
if (selfPathAvailable) {
StreamConfiguration tryCfg = cfg;
if (data_->selfPath_->validate(sensor, sensorConfig, &tryCfg) == Adjusted) {
selfPathAvailable = false;
cfg = tryCfg;
cfg.setStream(const_cast<Stream *>(&data_->selfPathStream_));
status = Adjusted;
continue;
}
}
/* All paths rejected configuration. */
LOG(RkISP1, Debug) << "Camera configuration not supported "
<< cfg.toString();
return Invalid;
}
/* Select the sensor format. */
PixelFormat rawFormat;
Size maxSize;
for (const StreamConfiguration &cfg : config_) {
const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat);
if (info.colourEncoding == PixelFormatInfo::ColourEncodingRAW)
rawFormat = cfg.pixelFormat;
maxSize = std::max(maxSize, cfg.size);
}
std::vector<unsigned int> mbusCodes;
if (rawFormat.isValid()) {
mbusCodes = { rawFormats.at(rawFormat) };
} else {
std::transform(rawFormats.begin(), rawFormats.end(),
std::back_inserter(mbusCodes),
[](const auto &value) { return value.second; });
}
sensorFormat_ = sensor->getFormat(mbusCodes, maxSize);
if (sensorFormat_.size.isNull())
sensorFormat_.size = sensor->resolution();
return status;
}
/* -----------------------------------------------------------------------------
* Pipeline Operations
*/
PipelineHandlerRkISP1::PipelineHandlerRkISP1(CameraManager *manager)
: PipelineHandler(manager), hasSelfPath_(true), useDewarper_(false)
{
}
std::unique_ptr<CameraConfiguration>
PipelineHandlerRkISP1::generateConfiguration(Camera *camera,
Span<const StreamRole> roles)
{
RkISP1CameraData *data = cameraData(camera);
unsigned int pathCount = data->selfPath_ ? 2 : 1;
if (roles.size() > pathCount) {
LOG(RkISP1, Error) << "Too many stream roles requested";
return nullptr;
}
std::unique_ptr<CameraConfiguration> config =
std::make_unique<RkISP1CameraConfiguration>(camera, data);
if (roles.empty())
return config;
/*
* As the ISP can't output different color spaces for the main and self
* path, pick a sensible default color space based on the role of the
* first stream and use it for all streams.
*/
std::optional<ColorSpace> colorSpace;
bool mainPathAvailable = true;
for (const StreamRole role : roles) {
Size size;
switch (role) {
case StreamRole::StillCapture:
/* JPEG encoders typically expect sYCC. */
if (!colorSpace)
colorSpace = ColorSpace::Sycc;
size = data->sensor_->resolution();
break;
case StreamRole::Viewfinder:
/*
* sYCC is the YCbCr encoding of sRGB, which is commonly
* used by displays.
*/
if (!colorSpace)
colorSpace = ColorSpace::Sycc;
size = kRkISP1PreviewSize;
break;
case StreamRole::VideoRecording:
/* Rec. 709 is a good default for HD video recording. */
if (!colorSpace)
colorSpace = ColorSpace::Rec709;
size = kRkISP1PreviewSize;
break;
case StreamRole::Raw:
if (roles.size() > 1) {
LOG(RkISP1, Error)
<< "Can't capture both raw and processed streams";
return nullptr;
}
colorSpace = ColorSpace::Raw;
size = data->sensor_->resolution();
break;
default:
LOG(RkISP1, Warning)
<< "Requested stream role not supported: " << role;
return nullptr;
}
/*
* Prefer the main path if available, as it supports higher
* resolutions.
*
* \todo Using the main path unconditionally hides support for
* RGB (only available on the self path) in the streams formats
* exposed to applications. This likely calls for a better API
* to expose streams capabilities.
*/
RkISP1Path *path;
if (mainPathAvailable) {
path = data->mainPath_;
mainPathAvailable = false;
} else {
path = data->selfPath_;
}
StreamConfiguration cfg =
path->generateConfiguration(data->sensor_.get(), size, role);
if (!cfg.pixelFormat.isValid())
return nullptr;
cfg.colorSpace = colorSpace;
config->addConfiguration(cfg);
}
config->validate();
return config;
}
int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)
{
RkISP1CameraConfiguration *config =
static_cast<RkISP1CameraConfiguration *>(c);
RkISP1CameraData *data = cameraData(camera);
CameraSensor *sensor = data->sensor_.get();
int ret;
ret = initLinks(camera, sensor, *config);
if (ret)
return ret;
/*
* Configure the format on the sensor output and propagate it through
* the pipeline.
*/
V4L2SubdeviceFormat format = config->sensorFormat();
LOG(RkISP1, Debug) << "Configuring sensor with " << format;
if (config->sensorConfig)
ret = sensor->applyConfiguration(*config->sensorConfig,
config->combinedTransform(),
&format);
else
ret = sensor->setFormat(&format, config->combinedTransform());
if (ret < 0)
return ret;
LOG(RkISP1, Debug) << "Sensor configured with " << format;
if (csi_) {
ret = csi_->setFormat(0, &format);
if (ret < 0)
return ret;
}
ret = isp_->setFormat(0, &format);
if (ret < 0)
return ret;
Rectangle inputCrop(0, 0, format.size);
ret = isp_->setSelection(0, V4L2_SEL_TGT_CROP, &inputCrop);
if (ret < 0)
return ret;
LOG(RkISP1, Debug)
<< "ISP input pad configured with " << format
<< " crop " << inputCrop;
Rectangle outputCrop = inputCrop;
const PixelFormat &streamFormat = config->at(0).pixelFormat;
const PixelFormatInfo &info = PixelFormatInfo::info(streamFormat);
isRaw_ = info.colourEncoding == PixelFormatInfo::ColourEncodingRAW;
/* YUYV8_2X8 is required on the ISP source path pad for YUV output. */
if (!isRaw_)
format.code = MEDIA_BUS_FMT_YUYV8_2X8;
/*
* On devices without DUAL_CROP (like the imx8mp) cropping needs to be
* done on the ISP/IS output.
*/
if (media_->hwRevision() == RKISP1_V_IMX8MP) {
/* imx8mp has only a single path. */
const auto &cfg = config->at(0);
Size ispCrop = format.size.boundedToAspectRatio(cfg.size)
.alignedUpTo(2, 2);
outputCrop = ispCrop.centeredTo(Rectangle(format.size).center());
format.size = ispCrop;
}
LOG(RkISP1, Debug)
<< "Configuring ISP output pad with " << format
<< " crop " << outputCrop;
ret = isp_->setSelection(2, V4L2_SEL_TGT_CROP, &outputCrop);
if (ret < 0)
return ret;
format.colorSpace = config->at(0).colorSpace;
ret = isp_->setFormat(2, &format);
if (ret < 0)
return ret;
LOG(RkISP1, Debug)
<< "ISP output pad configured with " << format
<< " crop " << outputCrop;
std::map<unsigned int, IPAStream> streamConfig;
std::vector<std::reference_wrapper<StreamConfiguration>> outputCfgs;
for (const StreamConfiguration &cfg : *config) {
if (cfg.stream() == &data->mainPathStream_) {
ret = mainPath_.configure(cfg, format);
streamConfig[0] = IPAStream(cfg.pixelFormat,
cfg.size);
/* Configure dewarp */
if (dewarper_ && !isRaw_) {
outputCfgs.push_back(const_cast<StreamConfiguration &>(cfg));
ret = dewarper_->configure(cfg, outputCfgs);
useDewarper_ = ret ? false : true;
}
} else if (hasSelfPath_) {
ret = selfPath_.configure(cfg, format);
streamConfig[1] = IPAStream(cfg.pixelFormat,
cfg.size);
} else {
return -ENODEV;
}
if (ret)
return ret;
}
V4L2DeviceFormat paramFormat;
paramFormat.fourcc = V4L2PixelFormat(V4L2_META_FMT_RK_ISP1_EXT_PARAMS);
ret = param_->setFormat(&paramFormat);
if (ret)
return ret;
V4L2DeviceFormat statFormat;
statFormat.fourcc = V4L2PixelFormat(V4L2_META_FMT_RK_ISP1_STAT_3A);
ret = stat_->setFormat(&statFormat);
if (ret)
return ret;
/* Inform IPA of stream configuration and sensor controls. */
ipa::rkisp1::IPAConfigInfo ipaConfig{};
ret = data->sensor_->sensorInfo(&ipaConfig.sensorInfo);
if (ret)
return ret;
ipaConfig.sensorControls = data->sensor_->controls();
ipaConfig.paramFormat = paramFormat.fourcc;
ret = data->ipa_->configure(ipaConfig, streamConfig, &data->ipaControls_);
if (ret) {
LOG(RkISP1, Error) << "failed configuring IPA (" << ret << ")";
return ret;
}
return updateControls(data);
}
int PipelineHandlerRkISP1::exportFrameBuffers([[maybe_unused]] Camera *camera, Stream *stream,
std::vector<std::unique_ptr<FrameBuffer>> *buffers)
{
RkISP1CameraData *data = cameraData(camera);
unsigned int count = stream->configuration().bufferCount;
if (stream == &data->mainPathStream_) {
/*
* Currently, i.MX8MP is the only platform with DW100 dewarper.
* It has mainpath and no self path. Hence, export buffers from
* dewarper just for the main path stream, for now.
*/
if (useDewarper_)
return dewarper_->exportBuffers(&data->mainPathStream_, count, buffers);
else
return mainPath_.exportBuffers(count, buffers);
} else if (hasSelfPath_ && stream == &data->selfPathStream_) {
return selfPath_.exportBuffers(count, buffers);
}
return -EINVAL;
}
int PipelineHandlerRkISP1::allocateBuffers(Camera *camera)
{
RkISP1CameraData *data = cameraData(camera);
unsigned int ipaBufferId = 1;
int ret;
unsigned int maxCount = std::max({
data->mainPathStream_.configuration().bufferCount,
data->selfPathStream_.configuration().bufferCount,
});
if (!isRaw_) {
ret = param_->allocateBuffers(maxCount, &paramBuffers_);
if (ret < 0)
goto error;
ret = stat_->allocateBuffers(maxCount, &statBuffers_);
if (ret < 0)
goto error;
}
/* If the dewarper is being used, allocate internal buffers for ISP. */
if (useDewarper_) {
ret = mainPath_.exportBuffers(maxCount, &mainPathBuffers_);
if (ret < 0)
goto error;
for (std::unique_ptr<FrameBuffer> &buffer : mainPathBuffers_)
availableMainPathBuffers_.push(buffer.get());
}
for (std::unique_ptr<FrameBuffer> &buffer : paramBuffers_) {
buffer->setCookie(ipaBufferId++);
data->ipaBuffers_.emplace_back(buffer->cookie(),
buffer->planes());
availableParamBuffers_.push(buffer.get());
}
for (std::unique_ptr<FrameBuffer> &buffer : statBuffers_) {
buffer->setCookie(ipaBufferId++);
data->ipaBuffers_.emplace_back(buffer->cookie(),
buffer->planes());
availableStatBuffers_.push(buffer.get());
}
data->ipa_->mapBuffers(data->ipaBuffers_);
return 0;
error:
paramBuffers_.clear();
statBuffers_.clear();
mainPathBuffers_.clear();
return ret;
}
int PipelineHandlerRkISP1::freeBuffers(Camera *camera)
{
RkISP1CameraData *data = cameraData(camera);
while (!availableStatBuffers_.empty())
availableStatBuffers_.pop();
while (!availableParamBuffers_.empty())
availableParamBuffers_.pop();
while (!availableMainPathBuffers_.empty())
availableMainPathBuffers_.pop();
paramBuffers_.clear();
statBuffers_.clear();
mainPathBuffers_.clear();
std::vector<unsigned int> ids;
for (IPABuffer &ipabuf : data->ipaBuffers_)
ids.push_back(ipabuf.id);
data->ipa_->unmapBuffers(ids);
data->ipaBuffers_.clear();
if (param_->releaseBuffers())
LOG(RkISP1, Error) << "Failed to release parameters buffers";
if (stat_->releaseBuffers())
LOG(RkISP1, Error) << "Failed to release stat buffers";
return 0;
}
int PipelineHandlerRkISP1::start(Camera *camera, [[maybe_unused]] const ControlList *controls)
{
RkISP1CameraData *data = cameraData(camera);
utils::ScopeExitActions actions;
int ret;
/* Allocate buffers for internal pipeline usage. */
ret = allocateBuffers(camera);
if (ret)
return ret;
actions += [&]() { freeBuffers(camera); };
ret = data->ipa_->start();
if (ret) {
LOG(RkISP1, Error)
<< "Failed to start IPA " << camera->id();
return ret;
}
actions += [&]() { data->ipa_->stop(); };
data->frame_ = 0;
if (!isRaw_) {
ret = param_->streamOn();
if (ret) {
LOG(RkISP1, Error)
<< "Failed to start parameters " << camera->id();
return ret;
}
actions += [&]() { param_->streamOff(); };
ret = stat_->streamOn();
if (ret) {
LOG(RkISP1, Error)
<< "Failed to start statistics " << camera->id();
return ret;
}
actions += [&]() { stat_->streamOff(); };
if (useDewarper_) {
ret = dewarper_->start();
if (ret) {
LOG(RkISP1, Error) << "Failed to start dewarper";
return ret;
}
actions += [&]() { dewarper_->stop(); };
}
}
if (data->mainPath_->isEnabled()) {
ret = mainPath_.start();
if (ret)
return ret;
actions += [&]() { mainPath_.stop(); };
}
if (hasSelfPath_ && data->selfPath_->isEnabled()) {
ret = selfPath_.start();
if (ret)
return ret;
}
isp_->setFrameStartEnabled(true);
activeCamera_ = camera;
actions.release();
return 0;
}
void PipelineHandlerRkISP1::stopDevice(Camera *camera)
{
RkISP1CameraData *data = cameraData(camera);
int ret;
isp_->setFrameStartEnabled(false);
data->ipa_->stop();
if (hasSelfPath_)
selfPath_.stop();
mainPath_.stop();
if (!isRaw_) {
ret = stat_->streamOff();
if (ret)
LOG(RkISP1, Warning)
<< "Failed to stop statistics for " << camera->id();
ret = param_->streamOff();
if (ret)
LOG(RkISP1, Warning)
<< "Failed to stop parameters for " << camera->id();
if (useDewarper_)
dewarper_->stop();
}
ASSERT(data->queuedRequests_.empty());
data->frameInfo_.clear();
freeBuffers(camera);
activeCamera_ = nullptr;
}
int PipelineHandlerRkISP1::queueRequestDevice(Camera *camera, Request *request)
{
RkISP1CameraData *data = cameraData(camera);
RkISP1FrameInfo *info = data->frameInfo_.create(data, request, isRaw_);
if (!info)
return -ENOENT;
data->ipa_->queueRequest(data->frame_, request->controls());
if (isRaw_) {
if (info->mainPathBuffer)
data->mainPath_->queueBuffer(info->mainPathBuffer);
if (data->selfPath_ && info->selfPathBuffer)
data->selfPath_->queueBuffer(info->selfPathBuffer);
} else {
data->ipa_->computeParams(data->frame_,
info->paramBuffer->cookie());
}
data->frame_++;
return 0;
}
/* -----------------------------------------------------------------------------
* Match and Setup
*/
int PipelineHandlerRkISP1::initLinks(Camera *camera,
const CameraSensor *sensor,
const RkISP1CameraConfiguration &config)
{
RkISP1CameraData *data = cameraData(camera);
int ret;
ret = media_->disableLinks();
if (ret < 0)
return ret;
/*
* Configure the sensor links: enable the link corresponding to this
* camera.
*/
for (MediaLink *link : ispSink_->links()) {
if (link->source()->entity() != sensor->entity())
continue;
LOG(RkISP1, Debug)
<< "Enabling link from sensor '"
<< link->source()->entity()->name()
<< "' to ISP";
ret = link->setEnabled(true);
if (ret < 0)
return ret;
}
if (csi_) {
MediaLink *link = isp_->entity()->getPadByIndex(0)->links().at(0);
ret = link->setEnabled(true);
if (ret < 0)
return ret;
}
for (const StreamConfiguration &cfg : config) {
if (cfg.stream() == &data->mainPathStream_)
ret = data->mainPath_->setEnabled(true);
else if (hasSelfPath_ && cfg.stream() == &data->selfPathStream_)
ret = data->selfPath_->setEnabled(true);
else
return -EINVAL;
if (ret < 0)
return ret;
}
return 0;
}
/**
* \brief Update the camera controls
* \param[in] data The camera data
*
* Compute the camera controls by calculating controls which the pipeline
* is reponsible for and merge them with the controls computed by the IPA.
*
* This function needs data->ipaControls_ to be refreshed when a new
* configuration is applied to the camera by the IPA configure() function.
*
* Always call this function after IPA configure() to make sure to have a
* properly refreshed IPA controls list.
*
* \return 0 on success or a negative error code otherwise
*/
int PipelineHandlerRkISP1::updateControls(RkISP1CameraData *data)
{
ControlInfoMap::Map controls;
if (dewarper_) {
std::pair<Rectangle, Rectangle> cropLimits =
dewarper_->inputCropBounds(&data->mainPathStream_);
controls[&controls::ScalerCrop] = ControlInfo(cropLimits.first,
cropLimits.second,
cropLimits.second);
activeCrop_ = cropLimits.second;
}
/* Add the IPA registered controls to list of camera controls. */
for (const auto &ipaControl : data->ipaControls_)
controls[ipaControl.first] = ipaControl.second;
data->controlInfo_ = ControlInfoMap(std::move(controls),
controls::controls);
return 0;
}
int PipelineHandlerRkISP1::createCamera(MediaEntity *sensor)
{
int ret;
std::unique_ptr<RkISP1CameraData> data =
std::make_unique<RkISP1CameraData>(this, &mainPath_,
hasSelfPath_ ? &selfPath_ : nullptr);
data->sensor_ = CameraSensorFactoryBase::create(sensor);
if (!data->sensor_)
return -ENODEV;
/* Initialize the camera properties. */
data->properties_ = data->sensor_->properties();
const CameraSensorProperties::SensorDelays &delays = data->sensor_->sensorDelays();
std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
{ V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },
};
data->delayedCtrls_ =
std::make_unique<DelayedControls>(data->sensor_->device(),
params);
isp_->frameStart.connect(data->delayedCtrls_.get(),
&DelayedControls::applyControls);
ret = data->loadIPA(media_->hwRevision());
if (ret)
return ret;
updateControls(data.get());
std::set<Stream *> streams{
&data->mainPathStream_,
&data->selfPathStream_,
};
const std::string &id = data->sensor_->id();
std::shared_ptr<Camera> camera =
Camera::create(std::move(data), id, streams);
registerCamera(std::move(camera));
return 0;
}
bool PipelineHandlerRkISP1::match(DeviceEnumerator *enumerator)
{
const MediaPad *pad;
DeviceMatch dm("rkisp1");
dm.add("rkisp1_isp");
dm.add("rkisp1_resizer_mainpath");
dm.add("rkisp1_mainpath");
dm.add("rkisp1_stats");
dm.add("rkisp1_params");
media_ = acquireMediaDevice(enumerator, dm);
if (!media_)
return false;
if (!media_->hwRevision()) {
LOG(RkISP1, Error)
<< "The rkisp1 driver is too old, v5.11 or newer is required";
return false;
}
hasSelfPath_ = !!media_->getEntityByName("rkisp1_selfpath");
/* Create the V4L2 subdevices we will need. */
isp_ = V4L2Subdevice::fromEntityName(media_, "rkisp1_isp");
if (isp_->open() < 0)
return false;
/* Locate and open the optional CSI-2 receiver. */
ispSink_ = isp_->entity()->getPadByIndex(0);
if (!ispSink_ || ispSink_->links().empty())
return false;
pad = ispSink_->links().at(0)->source();
if (pad->entity()->function() == MEDIA_ENT_F_VID_IF_BRIDGE) {
csi_ = std::make_unique<V4L2Subdevice>(pad->entity());
if (csi_->open() < 0)
return false;
ispSink_ = csi_->entity()->getPadByIndex(0);
if (!ispSink_)
return false;
}
/* Locate and open the stats and params video nodes. */
stat_ = V4L2VideoDevice::fromEntityName(media_, "rkisp1_stats");
if (stat_->open() < 0)
return false;
param_ = V4L2VideoDevice::fromEntityName(media_, "rkisp1_params");
if (param_->open() < 0)
return false;
/* Locate and open the ISP main and self paths. */
if (!mainPath_.init(media_))
return false;
if (hasSelfPath_ && !selfPath_.init(media_))
return false;
mainPath_.bufferReady().connect(this, &PipelineHandlerRkISP1::imageBufferReady);
if (hasSelfPath_)
selfPath_.bufferReady().connect(this, &PipelineHandlerRkISP1::imageBufferReady);
stat_->bufferReady.connect(this, &PipelineHandlerRkISP1::statBufferReady);
param_->bufferReady.connect(this, &PipelineHandlerRkISP1::paramBufferReady);
/* If dewarper is present, create its instance. */
DeviceMatch dwp("dw100");
dwp.add("dw100-source");
dwp.add("dw100-sink");
std::shared_ptr<MediaDevice> dwpMediaDevice = enumerator->search(dwp);
if (dwpMediaDevice) {
dewarper_ = std::make_unique<V4L2M2MConverter>(dwpMediaDevice.get());
if (dewarper_->isValid()) {
dewarper_->outputBufferReady.connect(
this, &PipelineHandlerRkISP1::dewarpBufferReady);
LOG(RkISP1, Info)
<< "Using DW100 dewarper " << dewarper_->deviceNode();
} else {
LOG(RkISP1, Warning)
<< "Found DW100 dewarper " << dewarper_->deviceNode()
<< " but invalid";
dewarper_.reset();
}
}
/*
* Enumerate all sensors connected to the ISP and create one
* camera instance for each of them.
*/
bool registered = false;
for (MediaLink *link : ispSink_->links()) {
if (!createCamera(link->source()->entity()))
registered = true;
}
return registered;
}
/* -----------------------------------------------------------------------------
* Buffer Handling
*/
void PipelineHandlerRkISP1::tryCompleteRequest(RkISP1FrameInfo *info)
{
RkISP1CameraData *data = cameraData(activeCamera_);
Request *request = info->request;
if (request->hasPendingBuffers())
return;
if (!info->metadataProcessed)
return;
if (!isRaw_ && !info->paramDequeued)
return;
data->frameInfo_.destroy(info->frame);
completeRequest(request);
}
void PipelineHandlerRkISP1::imageBufferReady(FrameBuffer *buffer)
{
ASSERT(activeCamera_);
RkISP1CameraData *data = cameraData(activeCamera_);
RkISP1FrameInfo *info = data->frameInfo_.find(buffer);
if (!info)
return;
const FrameMetadata &metadata = buffer->metadata();
Request *request = info->request;
if (metadata.status != FrameMetadata::FrameCancelled) {
/*
* Record the sensor's timestamp in the request metadata.
*
* \todo The sensor timestamp should be better estimated by connecting
* to the V4L2Device::frameStart signal.
*/
request->metadata().set(controls::SensorTimestamp,
metadata.timestamp);
if (isRaw_) {
const ControlList &ctrls =
data->delayedCtrls_->get(metadata.sequence);
data->ipa_->processStats(info->frame, 0, ctrls);
}
} else {
if (isRaw_)
info->metadataProcessed = true;
}
if (!useDewarper_) {
completeBuffer(request, buffer);
tryCompleteRequest(info);
return;
}
/* Do not queue cancelled frames to dewarper. */
if (metadata.status == FrameMetadata::FrameCancelled) {
/*
* i.MX8MP is the only known platform with dewarper. It has
* no self path. Hence, only main path buffer completion is
* required.
*
* Also, we cannot completeBuffer(request, buffer) as buffer
* here, is an internal buffer (between ISP and dewarper) and
* is not associated to the any specific request. The request
* buffer associated with main path stream is the one that
* is required to be completed (not the internal buffer).
*/
for (auto it : request->buffers()) {
if (it.first == &data->mainPathStream_)
completeBuffer(request, it.second);
}
tryCompleteRequest(info);
return;
}
/* Handle scaler crop control. */
const auto &crop = request->controls().get(controls::ScalerCrop);
if (crop) {
Rectangle appliedRect = crop.value();
int ret = dewarper_->setInputCrop(&data->mainPathStream_,
&appliedRect);
if (!ret && appliedRect != crop.value()) {
/*
* If the rectangle is changed by setInputCrop on the
* dewarper, log a debug message and cache the actual
* applied rectangle for metadata reporting.
*/
LOG(RkISP1, Debug)
<< "Applied rectangle " << appliedRect.toString()
<< " differs from requested " << crop.value().toString();
}
activeCrop_ = appliedRect;
}
/*
* Queue input and output buffers to the dewarper. The output
* buffers for the dewarper are the buffers of the request, supplied
* by the application.
*/
int ret = dewarper_->queueBuffers(buffer, request->buffers());
if (ret < 0)
LOG(RkISP1, Error) << "Cannot queue buffers to dewarper: "
<< strerror(-ret);
request->metadata().set(controls::ScalerCrop, activeCrop_.value());
}
void PipelineHandlerRkISP1::dewarpBufferReady(FrameBuffer *buffer)
{
ASSERT(activeCamera_);
RkISP1CameraData *data = cameraData(activeCamera_);
Request *request = buffer->request();
RkISP1FrameInfo *info = data->frameInfo_.find(buffer->request());
if (!info)
return;
completeBuffer(request, buffer);
tryCompleteRequest(info);
}
void PipelineHandlerRkISP1::paramBufferReady(FrameBuffer *buffer)
{
ASSERT(activeCamera_);
RkISP1CameraData *data = cameraData(activeCamera_);
RkISP1FrameInfo *info = data->frameInfo_.find(buffer);
if (!info)
return;
info->paramDequeued = true;
tryCompleteRequest(info);
}
void PipelineHandlerRkISP1::statBufferReady(FrameBuffer *buffer)
{
ASSERT(activeCamera_);
RkISP1CameraData *data = cameraData(activeCamera_);
RkISP1FrameInfo *info = data->frameInfo_.find(buffer);
if (!info)
return;
if (buffer->metadata().status == FrameMetadata::FrameCancelled) {
info->metadataProcessed = true;
tryCompleteRequest(info);
return;
}
if (data->frame_ <= buffer->metadata().sequence)
data->frame_ = buffer->metadata().sequence + 1;
data->ipa_->processStats(info->frame, info->statBuffer->cookie(),
data->delayedCtrls_->get(buffer->metadata().sequence));
}
REGISTER_PIPELINE_HANDLER(PipelineHandlerRkISP1, "rkisp1")
} /* namespace libcamera */