The function takes a const std::vector reference, but it does not actually need an `std::vector`. So use a `libcamera::Span` instead to avoid forcing the caller to construct a vector. Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
1162 lines
31 KiB
C++
1162 lines
31 KiB
C++
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
/*
|
|
* Copyright (C) 2024, Ideas on Board Oy.
|
|
*
|
|
* A raw camera sensor using the V4L2 streams API
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <float.h>
|
|
#include <iomanip>
|
|
#include <limits.h>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string.h>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <libcamera/base/class.h>
|
|
#include <libcamera/base/log.h>
|
|
#include <libcamera/base/utils.h>
|
|
|
|
#include <libcamera/camera.h>
|
|
#include <libcamera/control_ids.h>
|
|
#include <libcamera/controls.h>
|
|
#include <libcamera/geometry.h>
|
|
#include <libcamera/orientation.h>
|
|
#include <libcamera/property_ids.h>
|
|
#include <libcamera/transform.h>
|
|
|
|
#include <libcamera/ipa/core_ipa_interface.h>
|
|
|
|
#include "libcamera/internal/bayer_format.h"
|
|
#include "libcamera/internal/camera_lens.h"
|
|
#include "libcamera/internal/camera_sensor.h"
|
|
#include "libcamera/internal/camera_sensor_properties.h"
|
|
#include "libcamera/internal/formats.h"
|
|
#include "libcamera/internal/media_device.h"
|
|
#include "libcamera/internal/sysfs.h"
|
|
#include "libcamera/internal/v4l2_subdevice.h"
|
|
|
|
namespace libcamera {
|
|
|
|
class BayerFormat;
|
|
class CameraLens;
|
|
class MediaEntity;
|
|
class SensorConfiguration;
|
|
|
|
struct CameraSensorProperties;
|
|
|
|
enum class Orientation;
|
|
|
|
LOG_DECLARE_CATEGORY(CameraSensor)
|
|
|
|
class CameraSensorRaw : public CameraSensor, protected Loggable
|
|
{
|
|
public:
|
|
CameraSensorRaw(const MediaEntity *entity);
|
|
~CameraSensorRaw();
|
|
|
|
static std::variant<std::unique_ptr<CameraSensor>, int>
|
|
match(MediaEntity *entity);
|
|
|
|
const std::string &model() const override { return model_; }
|
|
const std::string &id() const override { return id_; }
|
|
|
|
const MediaEntity *entity() const override { return entity_; }
|
|
V4L2Subdevice *device() override { return subdev_.get(); }
|
|
|
|
CameraLens *focusLens() override { return focusLens_.get(); }
|
|
|
|
const std::vector<unsigned int> &mbusCodes() const override { return mbusCodes_; }
|
|
std::vector<Size> sizes(unsigned int mbusCode) const override;
|
|
Size resolution() const override;
|
|
|
|
V4L2SubdeviceFormat getFormat(Span<const unsigned int> mbusCodes,
|
|
const Size &size,
|
|
const Size maxSize) const override;
|
|
int setFormat(V4L2SubdeviceFormat *format,
|
|
Transform transform = Transform::Identity) override;
|
|
int tryFormat(V4L2SubdeviceFormat *format) const override;
|
|
|
|
int applyConfiguration(const SensorConfiguration &config,
|
|
Transform transform = Transform::Identity,
|
|
V4L2SubdeviceFormat *sensorFormat = nullptr) override;
|
|
|
|
V4L2Subdevice::Stream imageStream() const override;
|
|
std::optional<V4L2Subdevice::Stream> embeddedDataStream() const override;
|
|
V4L2SubdeviceFormat embeddedDataFormat() const override;
|
|
int setEmbeddedDataEnabled(bool enable) override;
|
|
|
|
const ControlList &properties() const override { return properties_; }
|
|
int sensorInfo(IPACameraSensorInfo *info) const override;
|
|
Transform computeTransform(Orientation *orientation) const override;
|
|
BayerFormat::Order bayerOrder(Transform t) const override;
|
|
|
|
const ControlInfoMap &controls() const override;
|
|
ControlList getControls(Span<const uint32_t> ids) override;
|
|
int setControls(ControlList *ctrls) override;
|
|
|
|
const std::vector<controls::draft::TestPatternModeEnum> &
|
|
testPatternModes() const override { return testPatternModes_; }
|
|
int setTestPatternMode(controls::draft::TestPatternModeEnum mode) override;
|
|
const CameraSensorProperties::SensorDelays &sensorDelays() override;
|
|
|
|
protected:
|
|
std::string logPrefix() const override;
|
|
|
|
private:
|
|
LIBCAMERA_DISABLE_COPY(CameraSensorRaw)
|
|
|
|
std::optional<int> init();
|
|
int initProperties();
|
|
void initStaticProperties();
|
|
void initTestPatternModes();
|
|
int applyTestPatternMode(controls::draft::TestPatternModeEnum mode);
|
|
|
|
const MediaEntity *entity_;
|
|
std::unique_ptr<V4L2Subdevice> subdev_;
|
|
|
|
struct Streams {
|
|
V4L2Subdevice::Stream sink;
|
|
V4L2Subdevice::Stream source;
|
|
};
|
|
|
|
struct {
|
|
Streams image;
|
|
std::optional<Streams> edata;
|
|
} streams_;
|
|
|
|
const CameraSensorProperties *staticProps_;
|
|
|
|
std::string model_;
|
|
std::string id_;
|
|
|
|
V4L2Subdevice::Formats formats_;
|
|
std::vector<unsigned int> mbusCodes_;
|
|
std::vector<Size> sizes_;
|
|
std::vector<controls::draft::TestPatternModeEnum> testPatternModes_;
|
|
controls::draft::TestPatternModeEnum testPatternMode_;
|
|
|
|
Size pixelArraySize_;
|
|
Rectangle activeArea_;
|
|
BayerFormat::Order cfaPattern_;
|
|
bool supportFlips_;
|
|
bool flipsAlterBayerOrder_;
|
|
Orientation mountingOrientation_;
|
|
|
|
ControlList properties_;
|
|
|
|
std::unique_ptr<CameraLens> focusLens_;
|
|
};
|
|
|
|
/**
|
|
* \class CameraSensorRaw
|
|
* \brief A camera sensor based on V4L2 subdevices
|
|
*
|
|
* This class supports single-subdev sensors with a single source pad and one
|
|
* or two internal sink pads (for the image and embedded data streams).
|
|
*/
|
|
|
|
CameraSensorRaw::CameraSensorRaw(const MediaEntity *entity)
|
|
: entity_(entity), staticProps_(nullptr), supportFlips_(false),
|
|
flipsAlterBayerOrder_(false), properties_(properties::properties)
|
|
{
|
|
}
|
|
|
|
CameraSensorRaw::~CameraSensorRaw() = default;
|
|
|
|
std::variant<std::unique_ptr<CameraSensor>, int>
|
|
CameraSensorRaw::match(MediaEntity *entity)
|
|
{
|
|
/* Check the entity type. */
|
|
if (entity->type() != MediaEntity::Type::V4L2Subdevice ||
|
|
entity->function() != MEDIA_ENT_F_CAM_SENSOR) {
|
|
libcamera::LOG(CameraSensor, Debug)
|
|
<< entity->name() << ": unsupported entity type ("
|
|
<< utils::to_underlying(entity->type())
|
|
<< ") or function (" << utils::hex(entity->function()) << ")";
|
|
return { 0 };
|
|
}
|
|
|
|
/* Count and check the number of pads. */
|
|
static constexpr uint32_t kPadFlagsMask = MEDIA_PAD_FL_SINK
|
|
| MEDIA_PAD_FL_SOURCE
|
|
| MEDIA_PAD_FL_INTERNAL;
|
|
unsigned int numSinks = 0;
|
|
unsigned int numSources = 0;
|
|
|
|
for (const MediaPad *pad : entity->pads()) {
|
|
switch (pad->flags() & kPadFlagsMask) {
|
|
case MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_INTERNAL:
|
|
numSinks++;
|
|
break;
|
|
|
|
case MEDIA_PAD_FL_SOURCE:
|
|
numSources++;
|
|
break;
|
|
|
|
default:
|
|
libcamera::LOG(CameraSensor, Debug)
|
|
<< entity->name() << ": unsupported pad " << pad->index()
|
|
<< " type " << utils::hex(pad->flags());
|
|
return { 0 };
|
|
}
|
|
}
|
|
|
|
if (numSinks < 1 || numSinks > 2 || numSources != 1) {
|
|
libcamera::LOG(CameraSensor, Debug)
|
|
<< entity->name() << ": unsupported number of sinks ("
|
|
<< numSinks << ") or sources (" << numSources << ")";
|
|
return { 0 };
|
|
}
|
|
|
|
/*
|
|
* The entity matches. Create the camera sensor and initialize it. The
|
|
* init() function will perform further match checks.
|
|
*/
|
|
std::unique_ptr<CameraSensorRaw> sensor =
|
|
std::make_unique<CameraSensorRaw>(entity);
|
|
|
|
std::optional<int> err = sensor->init();
|
|
if (err)
|
|
return { *err };
|
|
|
|
return { std::move(sensor) };
|
|
}
|
|
|
|
std::optional<int> CameraSensorRaw::init()
|
|
{
|
|
/* Create and open the subdev. */
|
|
subdev_ = std::make_unique<V4L2Subdevice>(entity_);
|
|
int ret = subdev_->open();
|
|
if (ret)
|
|
return { ret };
|
|
|
|
/*
|
|
* 1. Identify the pads.
|
|
*/
|
|
|
|
/*
|
|
* First locate the source pad. The match() function guarantees there
|
|
* is one and only one source pad.
|
|
*/
|
|
unsigned int sourcePad = UINT_MAX;
|
|
|
|
for (const MediaPad *pad : entity_->pads()) {
|
|
if (pad->flags() & MEDIA_PAD_FL_SOURCE) {
|
|
sourcePad = pad->index();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Iterate over the routes to identify the streams on the source pad,
|
|
* and the internal sink pads.
|
|
*/
|
|
V4L2Subdevice::Routing routing = {};
|
|
ret = subdev_->getRouting(&routing, V4L2Subdevice::TryFormat);
|
|
if (ret)
|
|
return { ret };
|
|
|
|
bool imageStreamFound = false;
|
|
|
|
for (const V4L2Subdevice::Route &route : routing) {
|
|
if (route.source.pad != sourcePad) {
|
|
LOG(CameraSensor, Error) << "Invalid route " << route;
|
|
return { -EINVAL };
|
|
}
|
|
|
|
/* Identify the stream type based on the supported formats. */
|
|
V4L2Subdevice::Formats formats = subdev_->formats(route.source);
|
|
|
|
std::optional<MediaBusFormatInfo::Type> type;
|
|
|
|
for (const auto &[code, sizes] : formats) {
|
|
const MediaBusFormatInfo &info =
|
|
MediaBusFormatInfo::info(code);
|
|
if (info.isValid()) {
|
|
type = info.type;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!type) {
|
|
LOG(CameraSensor, Warning)
|
|
<< "No known format on pad " << route.source;
|
|
continue;
|
|
}
|
|
|
|
switch (*type) {
|
|
case MediaBusFormatInfo::Type::Image:
|
|
if (imageStreamFound) {
|
|
LOG(CameraSensor, Error)
|
|
<< "Multiple internal image streams ("
|
|
<< streams_.image.sink << " and "
|
|
<< route.sink << ")";
|
|
return { -EINVAL };
|
|
}
|
|
|
|
imageStreamFound = true;
|
|
streams_.image.sink = route.sink;
|
|
streams_.image.source = route.source;
|
|
break;
|
|
|
|
case MediaBusFormatInfo::Type::Metadata:
|
|
/*
|
|
* Skip metadata streams that are not sensor embedded
|
|
* data. The source stream reports a generic metadata
|
|
* format, check the sink stream for the exact format.
|
|
*/
|
|
formats = subdev_->formats(route.sink);
|
|
if (formats.size() != 1)
|
|
continue;
|
|
|
|
if (MediaBusFormatInfo::info(formats.cbegin()->first).type !=
|
|
MediaBusFormatInfo::Type::EmbeddedData)
|
|
continue;
|
|
|
|
if (streams_.edata) {
|
|
LOG(CameraSensor, Error)
|
|
<< "Multiple internal embedded data streams ("
|
|
<< streams_.edata->sink << " and "
|
|
<< route.sink << ")";
|
|
return { -EINVAL };
|
|
}
|
|
|
|
streams_.edata = { route.sink, route.source };
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!imageStreamFound) {
|
|
LOG(CameraSensor, Error) << "No image stream found";
|
|
return { -EINVAL };
|
|
}
|
|
|
|
LOG(CameraSensor, Debug)
|
|
<< "Found image stream " << streams_.image.sink
|
|
<< " -> " << streams_.image.source;
|
|
|
|
if (streams_.edata)
|
|
LOG(CameraSensor, Debug)
|
|
<< "Found embedded data stream " << streams_.edata->sink
|
|
<< " -> " << streams_.edata->source;
|
|
|
|
/*
|
|
* 2. Enumerate and cache the media bus codes, sizes and colour filter
|
|
* array order for the image stream.
|
|
*/
|
|
|
|
/*
|
|
* Get the native sensor CFA pattern. It is simpler to retrieve it from
|
|
* the internal image sink pad as it is guaranteed to expose a single
|
|
* format, and is not affected by flips.
|
|
*/
|
|
V4L2Subdevice::Formats formats = subdev_->formats(streams_.image.sink);
|
|
if (formats.size() != 1) {
|
|
LOG(CameraSensor, Error)
|
|
<< "Image pad has " << formats.size()
|
|
<< " formats, expected 1";
|
|
return { -EINVAL };
|
|
}
|
|
|
|
uint32_t nativeFormat = formats.cbegin()->first;
|
|
const BayerFormat &bayerFormat = BayerFormat::fromMbusCode(nativeFormat);
|
|
if (!bayerFormat.isValid()) {
|
|
LOG(CameraSensor, Error)
|
|
<< "Invalid native format " << nativeFormat;
|
|
return { 0 };
|
|
}
|
|
|
|
cfaPattern_ = bayerFormat.order;
|
|
|
|
/*
|
|
* Retrieve and cache the media bus codes and sizes on the source image
|
|
* stream.
|
|
*/
|
|
formats_ = subdev_->formats(streams_.image.source);
|
|
if (formats_.empty()) {
|
|
LOG(CameraSensor, Error) << "No image format found";
|
|
return { -EINVAL };
|
|
}
|
|
|
|
/* Populate and sort the media bus codes and the sizes. */
|
|
for (const auto &[code, ranges] : formats_) {
|
|
/* Drop non-raw formats (in case we have a hybrid sensor). */
|
|
const MediaBusFormatInfo &info = MediaBusFormatInfo::info(code);
|
|
if (info.colourEncoding != PixelFormatInfo::ColourEncodingRAW)
|
|
continue;
|
|
|
|
mbusCodes_.push_back(code);
|
|
std::transform(ranges.begin(), ranges.end(), std::back_inserter(sizes_),
|
|
[](const SizeRange &range) { return range.max; });
|
|
}
|
|
|
|
if (mbusCodes_.empty()) {
|
|
LOG(CameraSensor, Debug) << "No raw image formats found";
|
|
return { 0 };
|
|
}
|
|
|
|
std::sort(mbusCodes_.begin(), mbusCodes_.end());
|
|
std::sort(sizes_.begin(), sizes_.end());
|
|
|
|
/*
|
|
* Remove duplicate sizes. There are no duplicate media bus codes as
|
|
* they are the keys in the formats map.
|
|
*/
|
|
auto last = std::unique(sizes_.begin(), sizes_.end());
|
|
sizes_.erase(last, sizes_.end());
|
|
|
|
/*
|
|
* 3. Query selection rectangles. Retrieve properties, and verify that
|
|
* all the expected selection rectangles are supported.
|
|
*/
|
|
|
|
Rectangle rect;
|
|
ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP_BOUNDS,
|
|
&rect);
|
|
if (ret) {
|
|
LOG(CameraSensor, Error) << "No pixel array crop bounds";
|
|
return { ret };
|
|
}
|
|
|
|
pixelArraySize_ = rect.size();
|
|
|
|
ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP_DEFAULT,
|
|
&activeArea_);
|
|
if (ret) {
|
|
LOG(CameraSensor, Error) << "No pixel array crop default";
|
|
return { ret };
|
|
}
|
|
|
|
ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP,
|
|
&rect);
|
|
if (ret) {
|
|
LOG(CameraSensor, Error) << "No pixel array crop rectangle";
|
|
return { ret };
|
|
}
|
|
|
|
/*
|
|
* 4. Verify that all required controls are present.
|
|
*/
|
|
|
|
const ControlIdMap &controls = subdev_->controls().idmap();
|
|
|
|
static constexpr uint32_t mandatoryControls[] = {
|
|
V4L2_CID_ANALOGUE_GAIN,
|
|
V4L2_CID_CAMERA_ORIENTATION,
|
|
V4L2_CID_EXPOSURE,
|
|
V4L2_CID_HBLANK,
|
|
V4L2_CID_PIXEL_RATE,
|
|
V4L2_CID_VBLANK,
|
|
};
|
|
|
|
ret = 0;
|
|
|
|
for (uint32_t ctrl : mandatoryControls) {
|
|
if (!controls.count(ctrl)) {
|
|
LOG(CameraSensor, Error)
|
|
<< "Mandatory V4L2 control " << utils::hex(ctrl)
|
|
<< " not available";
|
|
ret = -EINVAL;
|
|
}
|
|
}
|
|
|
|
if (ret) {
|
|
LOG(CameraSensor, Error)
|
|
<< "The sensor kernel driver needs to be fixed";
|
|
LOG(CameraSensor, Error)
|
|
<< "See Documentation/sensor_driver_requirements.rst in the libcamera sources for more information";
|
|
return { ret };
|
|
}
|
|
|
|
/*
|
|
* Verify if sensor supports horizontal/vertical flips
|
|
*
|
|
* \todo Handle horizontal and vertical flips independently.
|
|
*/
|
|
const struct v4l2_query_ext_ctrl *hflipInfo = subdev_->controlInfo(V4L2_CID_HFLIP);
|
|
const struct v4l2_query_ext_ctrl *vflipInfo = subdev_->controlInfo(V4L2_CID_VFLIP);
|
|
if (hflipInfo && !(hflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY) &&
|
|
vflipInfo && !(vflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY)) {
|
|
supportFlips_ = true;
|
|
|
|
if (hflipInfo->flags & V4L2_CTRL_FLAG_MODIFY_LAYOUT ||
|
|
vflipInfo->flags & V4L2_CTRL_FLAG_MODIFY_LAYOUT)
|
|
flipsAlterBayerOrder_ = true;
|
|
}
|
|
|
|
if (!supportFlips_)
|
|
LOG(CameraSensor, Debug)
|
|
<< "Camera sensor does not support horizontal/vertical flip";
|
|
|
|
/*
|
|
* 5. Discover ancillary devices.
|
|
*
|
|
* \todo This code may be shared by different V4L2 sensor classes.
|
|
*/
|
|
for (MediaEntity *ancillary : entity_->ancillaryEntities()) {
|
|
switch (ancillary->function()) {
|
|
case MEDIA_ENT_F_LENS:
|
|
focusLens_ = std::make_unique<CameraLens>(ancillary);
|
|
ret = focusLens_->init();
|
|
if (ret) {
|
|
LOG(CameraSensor, Error)
|
|
<< "Lens initialisation failed, lens disabled";
|
|
focusLens_.reset();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
LOG(CameraSensor, Warning)
|
|
<< "Unsupported ancillary entity function "
|
|
<< ancillary->function();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 6. Initialize properties.
|
|
*/
|
|
|
|
ret = initProperties();
|
|
if (ret)
|
|
return { ret };
|
|
|
|
/*
|
|
* 7. Initialize controls.
|
|
*/
|
|
|
|
/*
|
|
* Set HBLANK to the minimum to start with a well-defined line length,
|
|
* allowing IPA modules that do not modify HBLANK to use the sensor
|
|
* minimum line length in their calculations.
|
|
*/
|
|
const struct v4l2_query_ext_ctrl *hblankInfo = subdev_->controlInfo(V4L2_CID_HBLANK);
|
|
if (hblankInfo && !(hblankInfo->flags & V4L2_CTRL_FLAG_READ_ONLY)) {
|
|
ControlList ctrl(subdev_->controls());
|
|
|
|
ctrl.set(V4L2_CID_HBLANK, static_cast<int32_t>(hblankInfo->minimum));
|
|
ret = subdev_->setControls(&ctrl);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
ret = applyTestPatternMode(controls::draft::TestPatternModeEnum::TestPatternModeOff);
|
|
if (ret)
|
|
return { ret };
|
|
|
|
return {};
|
|
}
|
|
|
|
int CameraSensorRaw::initProperties()
|
|
{
|
|
model_ = subdev_->model();
|
|
properties_.set(properties::Model, utils::toAscii(model_));
|
|
|
|
/* Generate a unique ID for the sensor. */
|
|
id_ = sysfs::firmwareNodePath(subdev_->devicePath());
|
|
if (id_.empty()) {
|
|
LOG(CameraSensor, Error) << "Can't generate sensor ID";
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Initialize the static properties from the sensor database. */
|
|
initStaticProperties();
|
|
|
|
/* Retrieve and register properties from the kernel interface. */
|
|
const ControlInfoMap &controls = subdev_->controls();
|
|
|
|
const auto &orientation = controls.find(V4L2_CID_CAMERA_ORIENTATION);
|
|
if (orientation != controls.end()) {
|
|
int32_t v4l2Orientation = orientation->second.def().get<int32_t>();
|
|
int32_t propertyValue;
|
|
|
|
switch (v4l2Orientation) {
|
|
default:
|
|
LOG(CameraSensor, Warning)
|
|
<< "Unsupported camera location "
|
|
<< v4l2Orientation << ", setting to External";
|
|
[[fallthrough]];
|
|
case V4L2_CAMERA_ORIENTATION_EXTERNAL:
|
|
propertyValue = properties::CameraLocationExternal;
|
|
break;
|
|
case V4L2_CAMERA_ORIENTATION_FRONT:
|
|
propertyValue = properties::CameraLocationFront;
|
|
break;
|
|
case V4L2_CAMERA_ORIENTATION_BACK:
|
|
propertyValue = properties::CameraLocationBack;
|
|
break;
|
|
}
|
|
properties_.set(properties::Location, propertyValue);
|
|
} else {
|
|
LOG(CameraSensor, Warning) << "Failed to retrieve the camera location";
|
|
}
|
|
|
|
const auto &rotationControl = controls.find(V4L2_CID_CAMERA_SENSOR_ROTATION);
|
|
if (rotationControl != controls.end()) {
|
|
int32_t propertyValue = rotationControl->second.def().get<int32_t>();
|
|
|
|
/*
|
|
* Cache the Transform associated with the camera mounting
|
|
* rotation for later use in computeTransform().
|
|
*/
|
|
bool success;
|
|
mountingOrientation_ = orientationFromRotation(propertyValue, &success);
|
|
if (!success) {
|
|
LOG(CameraSensor, Warning)
|
|
<< "Invalid rotation of " << propertyValue
|
|
<< " degrees - ignoring";
|
|
mountingOrientation_ = Orientation::Rotate0;
|
|
}
|
|
|
|
properties_.set(properties::Rotation, propertyValue);
|
|
} else {
|
|
LOG(CameraSensor, Warning)
|
|
<< "Rotation control not available, default to 0 degrees";
|
|
properties_.set(properties::Rotation, 0);
|
|
mountingOrientation_ = Orientation::Rotate0;
|
|
}
|
|
|
|
properties_.set(properties::PixelArraySize, pixelArraySize_);
|
|
properties_.set(properties::PixelArrayActiveAreas, { activeArea_ });
|
|
|
|
/* Color filter array pattern. */
|
|
uint32_t cfa;
|
|
|
|
switch (cfaPattern_) {
|
|
case BayerFormat::BGGR:
|
|
cfa = properties::draft::BGGR;
|
|
break;
|
|
case BayerFormat::GBRG:
|
|
cfa = properties::draft::GBRG;
|
|
break;
|
|
case BayerFormat::GRBG:
|
|
cfa = properties::draft::GRBG;
|
|
break;
|
|
case BayerFormat::RGGB:
|
|
cfa = properties::draft::RGGB;
|
|
break;
|
|
case BayerFormat::MONO:
|
|
default:
|
|
cfa = properties::draft::MONO;
|
|
break;
|
|
}
|
|
|
|
properties_.set(properties::draft::ColorFilterArrangement, cfa);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void CameraSensorRaw::initStaticProperties()
|
|
{
|
|
staticProps_ = CameraSensorProperties::get(model_);
|
|
if (!staticProps_)
|
|
return;
|
|
|
|
/* Register the properties retrieved from the sensor database. */
|
|
properties_.set(properties::UnitCellSize, staticProps_->unitCellSize);
|
|
|
|
initTestPatternModes();
|
|
}
|
|
|
|
const CameraSensorProperties::SensorDelays &CameraSensorRaw::sensorDelays()
|
|
{
|
|
static constexpr CameraSensorProperties::SensorDelays defaultSensorDelays = {
|
|
.exposureDelay = 2,
|
|
.gainDelay = 1,
|
|
.vblankDelay = 2,
|
|
.hblankDelay = 2,
|
|
};
|
|
|
|
if (!staticProps_ ||
|
|
(!staticProps_->sensorDelays.exposureDelay &&
|
|
!staticProps_->sensorDelays.gainDelay &&
|
|
!staticProps_->sensorDelays.vblankDelay &&
|
|
!staticProps_->sensorDelays.hblankDelay)) {
|
|
LOG(CameraSensor, Warning)
|
|
<< "No sensor delays found in static properties. "
|
|
"Assuming unverified defaults.";
|
|
|
|
return defaultSensorDelays;
|
|
}
|
|
|
|
return staticProps_->sensorDelays;
|
|
}
|
|
|
|
void CameraSensorRaw::initTestPatternModes()
|
|
{
|
|
const auto &v4l2TestPattern = controls().find(V4L2_CID_TEST_PATTERN);
|
|
if (v4l2TestPattern == controls().end()) {
|
|
LOG(CameraSensor, Debug) << "V4L2_CID_TEST_PATTERN is not supported";
|
|
return;
|
|
}
|
|
|
|
const auto &testPatternModes = staticProps_->testPatternModes;
|
|
if (testPatternModes.empty()) {
|
|
/*
|
|
* The camera sensor supports test patterns but we don't know
|
|
* how to map them so this should be fixed.
|
|
*/
|
|
LOG(CameraSensor, Debug) << "No static test pattern map for \'"
|
|
<< model() << "\'";
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Create a map that associates the V4L2 control index to the test
|
|
* pattern mode by reversing the testPatternModes map provided by the
|
|
* camera sensor properties. This makes it easier to verify if the
|
|
* control index is supported in the below for loop that creates the
|
|
* list of supported test patterns.
|
|
*/
|
|
std::map<int32_t, controls::draft::TestPatternModeEnum> indexToTestPatternMode;
|
|
for (const auto &it : testPatternModes)
|
|
indexToTestPatternMode[it.second] = it.first;
|
|
|
|
for (const ControlValue &value : v4l2TestPattern->second.values()) {
|
|
const int32_t index = value.get<int32_t>();
|
|
|
|
const auto it = indexToTestPatternMode.find(index);
|
|
if (it == indexToTestPatternMode.end()) {
|
|
LOG(CameraSensor, Debug)
|
|
<< "Test pattern mode " << index << " ignored";
|
|
continue;
|
|
}
|
|
|
|
testPatternModes_.push_back(it->second);
|
|
}
|
|
}
|
|
|
|
std::vector<Size> CameraSensorRaw::sizes(unsigned int mbusCode) const
|
|
{
|
|
std::vector<Size> sizes;
|
|
|
|
const auto &format = formats_.find(mbusCode);
|
|
if (format == formats_.end())
|
|
return sizes;
|
|
|
|
const std::vector<SizeRange> &ranges = format->second;
|
|
std::transform(ranges.begin(), ranges.end(), std::back_inserter(sizes),
|
|
[](const SizeRange &range) { return range.max; });
|
|
|
|
std::sort(sizes.begin(), sizes.end());
|
|
|
|
return sizes;
|
|
}
|
|
|
|
Size CameraSensorRaw::resolution() const
|
|
{
|
|
return std::min(sizes_.back(), activeArea_.size());
|
|
}
|
|
|
|
V4L2SubdeviceFormat
|
|
CameraSensorRaw::getFormat(Span<const unsigned int> mbusCodes,
|
|
const Size &size, Size maxSize) const
|
|
{
|
|
unsigned int desiredArea = size.width * size.height;
|
|
unsigned int bestArea = UINT_MAX;
|
|
float desiredRatio = static_cast<float>(size.width) / size.height;
|
|
float bestRatio = FLT_MAX;
|
|
const Size *bestSize = nullptr;
|
|
uint32_t bestCode = 0;
|
|
|
|
for (unsigned int code : mbusCodes) {
|
|
const auto formats = formats_.find(code);
|
|
if (formats == formats_.end())
|
|
continue;
|
|
|
|
for (const SizeRange &range : formats->second) {
|
|
const Size &sz = range.max;
|
|
|
|
if (!maxSize.isNull() &&
|
|
(sz.width > maxSize.width || sz.height > maxSize.height))
|
|
continue;
|
|
|
|
if (sz.width < size.width || sz.height < size.height)
|
|
continue;
|
|
|
|
float ratio = static_cast<float>(sz.width) / sz.height;
|
|
float ratioDiff = std::abs(ratio - desiredRatio);
|
|
unsigned int area = sz.width * sz.height;
|
|
unsigned int areaDiff = area - desiredArea;
|
|
|
|
if (ratioDiff > bestRatio)
|
|
continue;
|
|
|
|
if (ratioDiff < bestRatio || areaDiff < bestArea) {
|
|
bestRatio = ratioDiff;
|
|
bestArea = areaDiff;
|
|
bestSize = &sz;
|
|
bestCode = code;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bestSize) {
|
|
LOG(CameraSensor, Debug) << "No supported format or size found";
|
|
return {};
|
|
}
|
|
|
|
V4L2SubdeviceFormat format{
|
|
.code = bestCode,
|
|
.size = *bestSize,
|
|
.colorSpace = ColorSpace::Raw,
|
|
};
|
|
|
|
return format;
|
|
}
|
|
|
|
int CameraSensorRaw::setFormat(V4L2SubdeviceFormat *format, Transform transform)
|
|
{
|
|
/* Configure flips if the sensor supports that. */
|
|
if (supportFlips_) {
|
|
ControlList flipCtrls(subdev_->controls());
|
|
|
|
flipCtrls.set(V4L2_CID_HFLIP,
|
|
static_cast<int32_t>(!!(transform & Transform::HFlip)));
|
|
flipCtrls.set(V4L2_CID_VFLIP,
|
|
static_cast<int32_t>(!!(transform & Transform::VFlip)));
|
|
|
|
int ret = subdev_->setControls(&flipCtrls);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
/* Apply format on the subdev. */
|
|
int ret = subdev_->setFormat(streams_.image.source, format);
|
|
if (ret)
|
|
return ret;
|
|
|
|
subdev_->updateControlInfo();
|
|
return 0;
|
|
}
|
|
|
|
int CameraSensorRaw::tryFormat(V4L2SubdeviceFormat *format) const
|
|
{
|
|
return subdev_->setFormat(streams_.image.source, format,
|
|
V4L2Subdevice::Whence::TryFormat);
|
|
}
|
|
|
|
int CameraSensorRaw::applyConfiguration(const SensorConfiguration &config,
|
|
Transform transform,
|
|
V4L2SubdeviceFormat *sensorFormat)
|
|
{
|
|
if (!config.isValid()) {
|
|
LOG(CameraSensor, Error) << "Invalid sensor configuration";
|
|
return -EINVAL;
|
|
}
|
|
|
|
std::vector<unsigned int> filteredCodes;
|
|
std::copy_if(mbusCodes_.begin(), mbusCodes_.end(),
|
|
std::back_inserter(filteredCodes),
|
|
[&config](unsigned int mbusCode) {
|
|
BayerFormat bayer = BayerFormat::fromMbusCode(mbusCode);
|
|
if (bayer.bitDepth == config.bitDepth)
|
|
return true;
|
|
return false;
|
|
});
|
|
if (filteredCodes.empty()) {
|
|
LOG(CameraSensor, Error)
|
|
<< "Cannot find any format with bit depth "
|
|
<< config.bitDepth;
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Compute the sensor's data frame size by applying the cropping
|
|
* rectangle, subsampling and output crop to the sensor's pixel array
|
|
* size.
|
|
*
|
|
* \todo The actual size computation is for now ignored and only the
|
|
* output size is considered. This implies that resolutions obtained
|
|
* with two different cropping/subsampling will look identical and
|
|
* only the first found one will be considered.
|
|
*/
|
|
V4L2SubdeviceFormat subdevFormat = {};
|
|
for (unsigned int code : filteredCodes) {
|
|
for (const Size &size : sizes(code)) {
|
|
if (size.width != config.outputSize.width ||
|
|
size.height != config.outputSize.height)
|
|
continue;
|
|
|
|
subdevFormat.code = code;
|
|
subdevFormat.size = size;
|
|
break;
|
|
}
|
|
}
|
|
if (!subdevFormat.code) {
|
|
LOG(CameraSensor, Error) << "Invalid output size in sensor configuration";
|
|
return -EINVAL;
|
|
}
|
|
|
|
int ret = setFormat(&subdevFormat, transform);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* Return to the caller the format actually applied to the sensor.
|
|
* This is relevant if transform has changed the bayer pattern order.
|
|
*/
|
|
if (sensorFormat)
|
|
*sensorFormat = subdevFormat;
|
|
|
|
/* \todo Handle AnalogCrop. Most sensors do not support set_selection */
|
|
/* \todo Handle scaling in the digital domain. */
|
|
|
|
return 0;
|
|
}
|
|
|
|
V4L2Subdevice::Stream CameraSensorRaw::imageStream() const
|
|
{
|
|
return streams_.image.source;
|
|
}
|
|
|
|
std::optional<V4L2Subdevice::Stream> CameraSensorRaw::embeddedDataStream() const
|
|
{
|
|
if (!streams_.edata)
|
|
return {};
|
|
|
|
return { streams_.edata->source };
|
|
}
|
|
|
|
V4L2SubdeviceFormat CameraSensorRaw::embeddedDataFormat() const
|
|
{
|
|
if (!streams_.edata)
|
|
return {};
|
|
|
|
V4L2SubdeviceFormat format;
|
|
int ret = subdev_->getFormat(streams_.edata->source, &format);
|
|
if (ret)
|
|
return {};
|
|
|
|
return format;
|
|
}
|
|
|
|
int CameraSensorRaw::setEmbeddedDataEnabled(bool enable)
|
|
{
|
|
if (!streams_.edata)
|
|
return enable ? -ENOSTR : 0;
|
|
|
|
V4L2Subdevice::Routing routing{ 2 };
|
|
|
|
routing[0].sink = streams_.image.sink;
|
|
routing[0].source = streams_.image.source;
|
|
routing[0].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
|
|
|
|
routing[1].sink = streams_.edata->sink;
|
|
routing[1].source = streams_.edata->source;
|
|
routing[1].flags = enable ? V4L2_SUBDEV_ROUTE_FL_ACTIVE : 0;
|
|
|
|
int ret = subdev_->setRouting(&routing);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* Check if the embedded data stream has been enabled or disabled
|
|
* correctly. Assume at least one route will match the embedded data
|
|
* source stream, as there would be something seriously wrong
|
|
* otherwise.
|
|
*/
|
|
bool enabled = false;
|
|
|
|
for (const V4L2Subdevice::Route &route : routing) {
|
|
if (route.source != streams_.edata->source)
|
|
continue;
|
|
|
|
enabled = route.flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE;
|
|
break;
|
|
}
|
|
|
|
if (enabled != enable)
|
|
return enabled ? -EISCONN : -ENOSTR;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int CameraSensorRaw::sensorInfo(IPACameraSensorInfo *info) const
|
|
{
|
|
info->model = model();
|
|
|
|
/*
|
|
* The active area size is a static property, while the crop
|
|
* rectangle needs to be re-read as it depends on the sensor
|
|
* configuration.
|
|
*/
|
|
info->activeAreaSize = { activeArea_.width, activeArea_.height };
|
|
|
|
int ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP,
|
|
&info->analogCrop);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* IPACameraSensorInfo::analogCrop::x and IPACameraSensorInfo::analogCrop::y
|
|
* are defined relatively to the active pixel area, while V4L2's
|
|
* TGT_CROP target is defined in respect to the full pixel array.
|
|
*
|
|
* Compensate it by subtracting the active area offset.
|
|
*/
|
|
info->analogCrop.x -= activeArea_.x;
|
|
info->analogCrop.y -= activeArea_.y;
|
|
|
|
/* The bit depth and image size depend on the currently applied format. */
|
|
V4L2SubdeviceFormat format{};
|
|
ret = subdev_->getFormat(streams_.image.source, &format);
|
|
if (ret)
|
|
return ret;
|
|
info->bitsPerPixel = MediaBusFormatInfo::info(format.code).bitsPerPixel;
|
|
info->outputSize = format.size;
|
|
|
|
std::optional<int32_t> cfa = properties_.get(properties::draft::ColorFilterArrangement);
|
|
info->cfaPattern = cfa ? *cfa : properties::draft::RGB;
|
|
|
|
/*
|
|
* Retrieve the pixel rate, line length and minimum/maximum frame
|
|
* duration through V4L2 controls. Support for the V4L2_CID_PIXEL_RATE,
|
|
* V4L2_CID_HBLANK and V4L2_CID_VBLANK controls is mandatory.
|
|
*/
|
|
static constexpr uint32_t cids[] = {
|
|
V4L2_CID_PIXEL_RATE,
|
|
V4L2_CID_HBLANK,
|
|
V4L2_CID_VBLANK,
|
|
};
|
|
|
|
ControlList ctrls = subdev_->getControls(cids);
|
|
if (ctrls.empty()) {
|
|
LOG(CameraSensor, Error)
|
|
<< "Failed to retrieve camera info controls";
|
|
return -EINVAL;
|
|
}
|
|
|
|
info->pixelRate = ctrls.get(V4L2_CID_PIXEL_RATE).get<int64_t>();
|
|
|
|
const ControlInfo hblank = ctrls.infoMap()->at(V4L2_CID_HBLANK);
|
|
info->minLineLength = info->outputSize.width + hblank.min().get<int32_t>();
|
|
info->maxLineLength = info->outputSize.width + hblank.max().get<int32_t>();
|
|
|
|
const ControlInfo vblank = ctrls.infoMap()->at(V4L2_CID_VBLANK);
|
|
info->minFrameLength = info->outputSize.height + vblank.min().get<int32_t>();
|
|
info->maxFrameLength = info->outputSize.height + vblank.max().get<int32_t>();
|
|
|
|
return 0;
|
|
}
|
|
|
|
Transform CameraSensorRaw::computeTransform(Orientation *orientation) const
|
|
{
|
|
/*
|
|
* If we cannot do any flips we cannot change the native camera mounting
|
|
* orientation.
|
|
*/
|
|
if (!supportFlips_) {
|
|
*orientation = mountingOrientation_;
|
|
return Transform::Identity;
|
|
}
|
|
|
|
/*
|
|
* Now compute the required transform to obtain 'orientation' starting
|
|
* from the mounting rotation.
|
|
*
|
|
* As a note:
|
|
* orientation / mountingOrientation_ = transform
|
|
* mountingOrientation_ * transform = orientation
|
|
*/
|
|
Transform transform = *orientation / mountingOrientation_;
|
|
|
|
/*
|
|
* If transform contains any Transpose we cannot do it, so adjust
|
|
* 'orientation' to report the image native orientation and return Identity.
|
|
*/
|
|
if (!!(transform & Transform::Transpose)) {
|
|
*orientation = mountingOrientation_;
|
|
return Transform::Identity;
|
|
}
|
|
|
|
return transform;
|
|
}
|
|
|
|
BayerFormat::Order CameraSensorRaw::bayerOrder(Transform t) const
|
|
{
|
|
if (!flipsAlterBayerOrder_)
|
|
return cfaPattern_;
|
|
|
|
/*
|
|
* Apply the transform to the native (i.e. untransformed) Bayer order,
|
|
* using the rest of the Bayer format supplied by the caller.
|
|
*/
|
|
BayerFormat format{ cfaPattern_, 8, BayerFormat::Packing::None };
|
|
return format.transform(t).order;
|
|
}
|
|
|
|
const ControlInfoMap &CameraSensorRaw::controls() const
|
|
{
|
|
return subdev_->controls();
|
|
}
|
|
|
|
ControlList CameraSensorRaw::getControls(Span<const uint32_t> ids)
|
|
{
|
|
return subdev_->getControls(ids);
|
|
}
|
|
|
|
int CameraSensorRaw::setControls(ControlList *ctrls)
|
|
{
|
|
return subdev_->setControls(ctrls);
|
|
}
|
|
|
|
int CameraSensorRaw::setTestPatternMode(controls::draft::TestPatternModeEnum mode)
|
|
{
|
|
if (testPatternMode_ == mode)
|
|
return 0;
|
|
|
|
if (testPatternModes_.empty()) {
|
|
LOG(CameraSensor, Error)
|
|
<< "Camera sensor does not support test pattern modes.";
|
|
return -EINVAL;
|
|
}
|
|
|
|
return applyTestPatternMode(mode);
|
|
}
|
|
|
|
int CameraSensorRaw::applyTestPatternMode(controls::draft::TestPatternModeEnum mode)
|
|
{
|
|
if (testPatternModes_.empty())
|
|
return 0;
|
|
|
|
auto it = std::find(testPatternModes_.begin(), testPatternModes_.end(),
|
|
mode);
|
|
if (it == testPatternModes_.end()) {
|
|
LOG(CameraSensor, Error) << "Unsupported test pattern mode "
|
|
<< mode;
|
|
return -EINVAL;
|
|
}
|
|
|
|
LOG(CameraSensor, Debug) << "Apply test pattern mode " << mode;
|
|
|
|
int32_t index = staticProps_->testPatternModes.at(mode);
|
|
ControlList ctrls{ controls() };
|
|
ctrls.set(V4L2_CID_TEST_PATTERN, index);
|
|
|
|
int ret = setControls(&ctrls);
|
|
if (ret)
|
|
return ret;
|
|
|
|
testPatternMode_ = mode;
|
|
|
|
return 0;
|
|
}
|
|
|
|
std::string CameraSensorRaw::logPrefix() const
|
|
{
|
|
return "'" + entity_->name() + "'";
|
|
}
|
|
|
|
REGISTER_CAMERA_SENSOR(CameraSensorRaw, 0)
|
|
|
|
} /* namespace libcamera */
|