The V4L2Device class uses V4L2ControlList as a controls container for the getControls() and setControls() operations. Having a distinct container from ControlList will makes the IPA API more complex, as it needs to explicitly transport both types of lists. This will become even more painful when implementing serialisation and deserialisation. To simplify the IPA API and ease the implementation of serialisation and deserialisation, replace usage of V4L2ControlList with ControlList in the V4L2Device (and thus CameraSensor) API. The V4L2ControlList class becomes a thin wrapper around ControlList that slightly simplifies the creation of control lists for V4L2 controls, and may be removed in the future. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Jacopo Mondi <jacopo@jmondi.org> Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se> Tested-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
452 lines
10 KiB
C++
452 lines
10 KiB
C++
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
/*
|
|
* Copyright (C) 2018, Google Inc.
|
|
*
|
|
* vimc.cpp - Pipeline handler for the vimc device
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <iomanip>
|
|
#include <tuple>
|
|
|
|
#include <linux/media-bus-format.h>
|
|
|
|
#include <ipa/ipa_interface.h>
|
|
#include <ipa/ipa_module_info.h>
|
|
#include <libcamera/camera.h>
|
|
#include <libcamera/control_ids.h>
|
|
#include <libcamera/controls.h>
|
|
#include <libcamera/request.h>
|
|
#include <libcamera/stream.h>
|
|
|
|
#include "camera_sensor.h"
|
|
#include "device_enumerator.h"
|
|
#include "ipa_manager.h"
|
|
#include "log.h"
|
|
#include "media_device.h"
|
|
#include "pipeline_handler.h"
|
|
#include "utils.h"
|
|
#include "v4l2_controls.h"
|
|
#include "v4l2_subdevice.h"
|
|
#include "v4l2_videodevice.h"
|
|
|
|
namespace libcamera {
|
|
|
|
LOG_DEFINE_CATEGORY(VIMC)
|
|
|
|
class VimcCameraData : public CameraData
|
|
{
|
|
public:
|
|
VimcCameraData(PipelineHandler *pipe)
|
|
: CameraData(pipe), sensor_(nullptr), debayer_(nullptr),
|
|
scaler_(nullptr), video_(nullptr), raw_(nullptr)
|
|
{
|
|
}
|
|
|
|
~VimcCameraData()
|
|
{
|
|
delete sensor_;
|
|
delete debayer_;
|
|
delete scaler_;
|
|
delete video_;
|
|
delete raw_;
|
|
}
|
|
|
|
int init(MediaDevice *media);
|
|
void bufferReady(Buffer *buffer);
|
|
|
|
CameraSensor *sensor_;
|
|
V4L2Subdevice *debayer_;
|
|
V4L2Subdevice *scaler_;
|
|
V4L2VideoDevice *video_;
|
|
V4L2VideoDevice *raw_;
|
|
Stream stream_;
|
|
};
|
|
|
|
class VimcCameraConfiguration : public CameraConfiguration
|
|
{
|
|
public:
|
|
VimcCameraConfiguration();
|
|
|
|
Status validate() override;
|
|
};
|
|
|
|
class PipelineHandlerVimc : public PipelineHandler
|
|
{
|
|
public:
|
|
PipelineHandlerVimc(CameraManager *manager);
|
|
|
|
CameraConfiguration *generateConfiguration(Camera *camera,
|
|
const StreamRoles &roles) override;
|
|
int configure(Camera *camera, CameraConfiguration *config) override;
|
|
|
|
int allocateBuffers(Camera *camera,
|
|
const std::set<Stream *> &streams) override;
|
|
int freeBuffers(Camera *camera,
|
|
const std::set<Stream *> &streams) override;
|
|
|
|
int start(Camera *camera) override;
|
|
void stop(Camera *camera) override;
|
|
|
|
int queueRequest(Camera *camera, Request *request) override;
|
|
|
|
bool match(DeviceEnumerator *enumerator) override;
|
|
|
|
private:
|
|
int processControls(VimcCameraData *data, Request *request);
|
|
|
|
VimcCameraData *cameraData(const Camera *camera)
|
|
{
|
|
return static_cast<VimcCameraData *>(
|
|
PipelineHandler::cameraData(camera));
|
|
}
|
|
};
|
|
|
|
VimcCameraConfiguration::VimcCameraConfiguration()
|
|
: CameraConfiguration()
|
|
{
|
|
}
|
|
|
|
CameraConfiguration::Status VimcCameraConfiguration::validate()
|
|
{
|
|
static const std::array<unsigned int, 3> formats{
|
|
V4L2_PIX_FMT_BGR24,
|
|
V4L2_PIX_FMT_RGB24,
|
|
V4L2_PIX_FMT_ARGB32,
|
|
};
|
|
|
|
Status status = Valid;
|
|
|
|
if (config_.empty())
|
|
return Invalid;
|
|
|
|
/* Cap the number of entries to the available streams. */
|
|
if (config_.size() > 1) {
|
|
config_.resize(1);
|
|
status = Adjusted;
|
|
}
|
|
|
|
StreamConfiguration &cfg = config_[0];
|
|
|
|
/* Adjust the pixel format. */
|
|
if (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==
|
|
formats.end()) {
|
|
LOG(VIMC, Debug) << "Adjusting format to RGB24";
|
|
cfg.pixelFormat = V4L2_PIX_FMT_RGB24;
|
|
status = Adjusted;
|
|
}
|
|
|
|
/* Clamp the size based on the device limits. */
|
|
const Size size = cfg.size;
|
|
|
|
/* The scaler hardcodes a x3 scale-up ratio. */
|
|
cfg.size.width = std::max(48U, std::min(4096U, cfg.size.width));
|
|
cfg.size.height = std::max(48U, std::min(2160U, cfg.size.height));
|
|
cfg.size.width -= cfg.size.width % 3;
|
|
cfg.size.height -= cfg.size.height % 3;
|
|
|
|
if (cfg.size != size) {
|
|
LOG(VIMC, Debug)
|
|
<< "Adjusting size to " << cfg.size.toString();
|
|
status = Adjusted;
|
|
}
|
|
|
|
cfg.bufferCount = 4;
|
|
|
|
return status;
|
|
}
|
|
|
|
PipelineHandlerVimc::PipelineHandlerVimc(CameraManager *manager)
|
|
: PipelineHandler(manager)
|
|
{
|
|
}
|
|
|
|
CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera,
|
|
const StreamRoles &roles)
|
|
{
|
|
CameraConfiguration *config = new VimcCameraConfiguration();
|
|
|
|
if (roles.empty())
|
|
return config;
|
|
|
|
StreamConfiguration cfg{};
|
|
cfg.pixelFormat = V4L2_PIX_FMT_RGB24;
|
|
cfg.size = { 1920, 1080 };
|
|
cfg.bufferCount = 4;
|
|
|
|
config->addConfiguration(cfg);
|
|
|
|
config->validate();
|
|
|
|
return config;
|
|
}
|
|
|
|
int PipelineHandlerVimc::configure(Camera *camera, CameraConfiguration *config)
|
|
{
|
|
VimcCameraData *data = cameraData(camera);
|
|
StreamConfiguration &cfg = config->at(0);
|
|
int ret;
|
|
|
|
/* The scaler hardcodes a x3 scale-up ratio. */
|
|
V4L2SubdeviceFormat subformat = {};
|
|
subformat.mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8;
|
|
subformat.size = { cfg.size.width / 3, cfg.size.height / 3 };
|
|
|
|
ret = data->sensor_->setFormat(&subformat);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = data->debayer_->setFormat(0, &subformat);
|
|
if (ret)
|
|
return ret;
|
|
|
|
subformat.mbus_code = MEDIA_BUS_FMT_RGB888_1X24;
|
|
ret = data->debayer_->setFormat(1, &subformat);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = data->scaler_->setFormat(0, &subformat);
|
|
if (ret)
|
|
return ret;
|
|
|
|
subformat.size = cfg.size;
|
|
ret = data->scaler_->setFormat(1, &subformat);
|
|
if (ret)
|
|
return ret;
|
|
|
|
V4L2DeviceFormat format = {};
|
|
format.fourcc = cfg.pixelFormat;
|
|
format.size = cfg.size;
|
|
|
|
ret = data->video_->setFormat(&format);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (format.size != cfg.size ||
|
|
format.fourcc != cfg.pixelFormat)
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* Format has to be set on the raw capture video node, otherwise the
|
|
* vimc driver will fail pipeline validation.
|
|
*/
|
|
format.fourcc = V4L2_PIX_FMT_SGRBG8;
|
|
format.size = { cfg.size.width / 3, cfg.size.height / 3 };
|
|
|
|
ret = data->raw_->setFormat(&format);
|
|
if (ret)
|
|
return ret;
|
|
|
|
cfg.setStream(&data->stream_);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int PipelineHandlerVimc::allocateBuffers(Camera *camera,
|
|
const std::set<Stream *> &streams)
|
|
{
|
|
VimcCameraData *data = cameraData(camera);
|
|
Stream *stream = *streams.begin();
|
|
const StreamConfiguration &cfg = stream->configuration();
|
|
|
|
LOG(VIMC, Debug) << "Requesting " << cfg.bufferCount << " buffers";
|
|
|
|
if (stream->memoryType() == InternalMemory)
|
|
return data->video_->exportBuffers(&stream->bufferPool());
|
|
else
|
|
return data->video_->importBuffers(&stream->bufferPool());
|
|
}
|
|
|
|
int PipelineHandlerVimc::freeBuffers(Camera *camera,
|
|
const std::set<Stream *> &streams)
|
|
{
|
|
VimcCameraData *data = cameraData(camera);
|
|
return data->video_->releaseBuffers();
|
|
}
|
|
|
|
int PipelineHandlerVimc::start(Camera *camera)
|
|
{
|
|
VimcCameraData *data = cameraData(camera);
|
|
return data->video_->streamOn();
|
|
}
|
|
|
|
void PipelineHandlerVimc::stop(Camera *camera)
|
|
{
|
|
VimcCameraData *data = cameraData(camera);
|
|
data->video_->streamOff();
|
|
}
|
|
|
|
int PipelineHandlerVimc::processControls(VimcCameraData *data, Request *request)
|
|
{
|
|
V4L2ControlList controls(data->sensor_->controls());
|
|
|
|
for (auto it : request->controls()) {
|
|
const ControlId &id = *it.first;
|
|
ControlValue &value = it.second;
|
|
|
|
if (id == controls::Brightness)
|
|
controls.set(V4L2_CID_BRIGHTNESS, value);
|
|
else if (id == controls::Contrast)
|
|
controls.set(V4L2_CID_CONTRAST, value);
|
|
else if (id == controls::Saturation)
|
|
controls.set(V4L2_CID_SATURATION, value);
|
|
}
|
|
|
|
for (const auto &ctrl : controls)
|
|
LOG(VIMC, Debug)
|
|
<< "Setting control " << ctrl.first->name()
|
|
<< " to " << ctrl.second.toString();
|
|
|
|
int ret = data->sensor_->setControls(&controls);
|
|
if (ret) {
|
|
LOG(VIMC, Error) << "Failed to set controls: " << ret;
|
|
return ret < 0 ? ret : -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int PipelineHandlerVimc::queueRequest(Camera *camera, Request *request)
|
|
{
|
|
VimcCameraData *data = cameraData(camera);
|
|
Buffer *buffer = request->findBuffer(&data->stream_);
|
|
if (!buffer) {
|
|
LOG(VIMC, Error)
|
|
<< "Attempt to queue request with invalid stream";
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
int ret = processControls(data, request);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = data->video_->queueBuffer(buffer);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
PipelineHandler::queueRequest(camera, request);
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool PipelineHandlerVimc::match(DeviceEnumerator *enumerator)
|
|
{
|
|
DeviceMatch dm("vimc");
|
|
|
|
dm.add("Raw Capture 0");
|
|
dm.add("Raw Capture 1");
|
|
dm.add("RGB/YUV Capture");
|
|
dm.add("Sensor A");
|
|
dm.add("Sensor B");
|
|
dm.add("Debayer A");
|
|
dm.add("Debayer B");
|
|
dm.add("RGB/YUV Input");
|
|
dm.add("Scaler");
|
|
|
|
MediaDevice *media = acquireMediaDevice(enumerator, dm);
|
|
if (!media)
|
|
return false;
|
|
|
|
std::unique_ptr<VimcCameraData> data = utils::make_unique<VimcCameraData>(this);
|
|
|
|
data->ipa_ = IPAManager::instance()->createIPA(this, 0, 0);
|
|
if (data->ipa_ == nullptr)
|
|
LOG(VIMC, Warning) << "no matching IPA found";
|
|
else
|
|
data->ipa_->init();
|
|
|
|
/* Locate and open the capture video node. */
|
|
if (data->init(media))
|
|
return false;
|
|
|
|
/* Create and register the camera. */
|
|
std::set<Stream *> streams{ &data->stream_ };
|
|
std::shared_ptr<Camera> camera = Camera::create(this, "VIMC Sensor B",
|
|
streams);
|
|
registerCamera(std::move(camera), std::move(data));
|
|
|
|
return true;
|
|
}
|
|
|
|
int VimcCameraData::init(MediaDevice *media)
|
|
{
|
|
int ret;
|
|
|
|
ret = media->disableLinks();
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
MediaLink *link = media->link("Debayer B", 1, "Scaler", 0);
|
|
if (!link)
|
|
return -ENODEV;
|
|
|
|
ret = link->setEnabled(true);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Create and open the camera sensor, debayer, scaler and video device. */
|
|
sensor_ = new CameraSensor(media->getEntityByName("Sensor B"));
|
|
ret = sensor_->init();
|
|
if (ret)
|
|
return ret;
|
|
|
|
debayer_ = new V4L2Subdevice(media->getEntityByName("Debayer B"));
|
|
if (debayer_->open())
|
|
return -ENODEV;
|
|
|
|
scaler_ = new V4L2Subdevice(media->getEntityByName("Scaler"));
|
|
if (scaler_->open())
|
|
return -ENODEV;
|
|
|
|
video_ = new V4L2VideoDevice(media->getEntityByName("RGB/YUV Capture"));
|
|
if (video_->open())
|
|
return -ENODEV;
|
|
|
|
video_->bufferReady.connect(this, &VimcCameraData::bufferReady);
|
|
|
|
raw_ = new V4L2VideoDevice(media->getEntityByName("Raw Capture 1"));
|
|
if (raw_->open())
|
|
return -ENODEV;
|
|
|
|
/* Initialise the supported controls. */
|
|
const V4L2ControlInfoMap &controls = sensor_->controls();
|
|
for (const auto &ctrl : controls) {
|
|
const V4L2ControlInfo &info = ctrl.second;
|
|
const ControlId *id;
|
|
|
|
switch (info.id().id()) {
|
|
case V4L2_CID_BRIGHTNESS:
|
|
id = &controls::Brightness;
|
|
break;
|
|
case V4L2_CID_CONTRAST:
|
|
id = &controls::Contrast;
|
|
break;
|
|
case V4L2_CID_SATURATION:
|
|
id = &controls::Saturation;
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
controlInfo_.emplace(std::piecewise_construct,
|
|
std::forward_as_tuple(id),
|
|
std::forward_as_tuple(info.range()));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void VimcCameraData::bufferReady(Buffer *buffer)
|
|
{
|
|
Request *request = buffer->request();
|
|
|
|
pipe_->completeBuffer(camera_, request, buffer);
|
|
pipe_->completeRequest(camera_, request);
|
|
}
|
|
|
|
REGISTER_PIPELINE_HANDLER(PipelineHandlerVimc);
|
|
|
|
} /* namespace libcamera */
|