Files
external_libcamera/src/libcamera/pipeline/ipu3/ipu3.cpp
T
Niklas Söderlund 82549ae253 libcamera: ipu3: Map buffers in IPA
Map and unmap the parameters and statistic buffers in the IPA when the
pipeline handler allocates and frees the buffers.

Signed-off-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
Reviewed-by: Jacopo Mondi <jacopo@jmondi.org>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
2021-02-05 01:20:32 +01:00

1121 lines
32 KiB
C++

/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* ipu3.cpp - Pipeline handler for Intel IPU3
*/
#include <algorithm>
#include <iomanip>
#include <memory>
#include <queue>
#include <vector>
#include <libcamera/camera.h>
#include <libcamera/control_ids.h>
#include <libcamera/formats.h>
#include <libcamera/ipa/ipu3.h>
#include <libcamera/request.h>
#include <libcamera/stream.h>
#include "libcamera/internal/camera_sensor.h"
#include "libcamera/internal/delayed_controls.h"
#include "libcamera/internal/device_enumerator.h"
#include "libcamera/internal/ipa_manager.h"
#include "libcamera/internal/log.h"
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/pipeline_handler.h"
#include "libcamera/internal/utils.h"
#include "libcamera/internal/v4l2_controls.h"
#include "cio2.h"
#include "imgu.h"
namespace libcamera {
LOG_DEFINE_CATEGORY(IPU3)
static constexpr unsigned int IPU3_BUFFER_COUNT = 4;
static constexpr unsigned int IPU3_MAX_STREAMS = 3;
static const Size IMGU_OUTPUT_MIN_SIZE = { 2, 2 };
static const Size IMGU_OUTPUT_MAX_SIZE = { 4480, 34004 };
static constexpr unsigned int IMGU_OUTPUT_WIDTH_ALIGN = 64;
static constexpr unsigned int IMGU_OUTPUT_HEIGHT_ALIGN = 4;
static constexpr unsigned int IMGU_OUTPUT_WIDTH_MARGIN = 64;
static constexpr unsigned int IMGU_OUTPUT_HEIGHT_MARGIN = 32;
static constexpr Size IPU3ViewfinderSize(1280, 720);
static const ControlInfoMap::Map IPU3Controls = {
{ &controls::draft::PipelineDepth, ControlInfo(2, 3) },
};
class IPU3CameraData : public CameraData
{
public:
IPU3CameraData(PipelineHandler *pipe)
: CameraData(pipe), exposureTime_(0)
{
}
int loadIPA();
void imguOutputBufferReady(FrameBuffer *buffer);
void cio2BufferReady(FrameBuffer *buffer);
CIO2Device cio2_;
ImgUDevice *imgu_;
Stream outStream_;
Stream vfStream_;
Stream rawStream_;
uint32_t exposureTime_;
std::unique_ptr<DelayedControls> delayedCtrls_;
private:
void queueFrameAction(unsigned int id, const IPAOperationData &op);
};
class IPU3CameraConfiguration : public CameraConfiguration
{
public:
IPU3CameraConfiguration(IPU3CameraData *data);
Status validate() override;
const StreamConfiguration &cio2Format() const { return cio2Configuration_; }
const ImgUDevice::PipeConfig imguConfig() const { return pipeConfig_; }
private:
/*
* The IPU3CameraData 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.
*/
const IPU3CameraData *data_;
StreamConfiguration cio2Configuration_;
ImgUDevice::PipeConfig pipeConfig_;
};
class PipelineHandlerIPU3 : public PipelineHandler
{
public:
static constexpr unsigned int V4L2_CID_IPU3_PIPE_MODE = 0x009819c1;
enum IPU3PipeModes {
IPU3PipeModeVideo = 0,
IPU3PipeModeStillCapture = 1,
};
PipelineHandlerIPU3(CameraManager *manager);
CameraConfiguration *generateConfiguration(Camera *camera,
const StreamRoles &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, ControlList *controls) override;
void stop(Camera *camera) override;
int queueRequestDevice(Camera *camera, Request *request) override;
bool match(DeviceEnumerator *enumerator) override;
private:
IPU3CameraData *cameraData(const Camera *camera)
{
return static_cast<IPU3CameraData *>(
PipelineHandler::cameraData(camera));
}
int initControls(IPU3CameraData *data);
int registerCameras();
int allocateBuffers(Camera *camera);
int freeBuffers(Camera *camera);
ImgUDevice imgu0_;
ImgUDevice imgu1_;
MediaDevice *cio2MediaDev_;
MediaDevice *imguMediaDev_;
std::vector<IPABuffer> ipaBuffers_;
};
IPU3CameraConfiguration::IPU3CameraConfiguration(IPU3CameraData *data)
: CameraConfiguration()
{
data_ = data;
}
CameraConfiguration::Status IPU3CameraConfiguration::validate()
{
Status status = Valid;
if (config_.empty())
return Invalid;
if (transform != Transform::Identity) {
transform = Transform::Identity;
status = Adjusted;
}
/* Cap the number of entries to the available streams. */
if (config_.size() > IPU3_MAX_STREAMS) {
config_.resize(IPU3_MAX_STREAMS);
status = Adjusted;
}
/* Validate the requested stream configuration */
unsigned int rawCount = 0;
unsigned int yuvCount = 0;
Size maxYuvSize;
for (const StreamConfiguration &cfg : config_) {
const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat);
if (info.colourEncoding == PixelFormatInfo::ColourEncodingRAW) {
rawCount++;
} else {
yuvCount++;
maxYuvSize.expandTo(cfg.size);
}
}
if (rawCount > 1 || yuvCount > 2) {
LOG(IPU3, Debug) << "Camera configuration not supported";
return Invalid;
}
/*
* Generate raw configuration from CIO2.
*
* \todo The image sensor frame size should be selected to optimize
* operations based on the sizes of the requested streams. However such
* a selection makes the pipeline configuration procedure fail for small
* resolutions (for example: 640x480 with OV5670) and causes the capture
* operations to stall for some stream size combinations (see the
* commit message of the patch that introduced this comment for more
* failure examples).
*
* Until the sensor frame size calculation criteria are clarified,
* always use the largest possible one which guarantees better results
* at the expense of the frame rate and CSI-2 bus bandwidth.
*/
cio2Configuration_ = data_->cio2_.generateConfiguration({});
if (!cio2Configuration_.pixelFormat.isValid())
return Invalid;
LOG(IPU3, Debug) << "CIO2 configuration: " << cio2Configuration_.toString();
ImgUDevice::Pipe pipe{};
pipe.input = cio2Configuration_.size;
/*
* Adjust the configurations if needed and assign streams while
* iterating them.
*/
bool mainOutputAvailable = true;
for (unsigned int i = 0; i < config_.size(); ++i) {
const PixelFormatInfo &info = PixelFormatInfo::info(config_[i].pixelFormat);
const StreamConfiguration originalCfg = config_[i];
StreamConfiguration *cfg = &config_[i];
LOG(IPU3, Debug) << "Validating stream: " << config_[i].toString();
if (info.colourEncoding == PixelFormatInfo::ColourEncodingRAW) {
/* Initialize the RAW stream with the CIO2 configuration. */
cfg->size = cio2Configuration_.size;
cfg->pixelFormat = cio2Configuration_.pixelFormat;
cfg->bufferCount = cio2Configuration_.bufferCount;
cfg->stride = info.stride(cfg->size.width, 0, 64);
cfg->frameSize = info.frameSize(cfg->size, 64);
cfg->setStream(const_cast<Stream *>(&data_->rawStream_));
LOG(IPU3, Debug) << "Assigned " << cfg->toString()
<< " to the raw stream";
} else {
/* Assign and configure the main and viewfinder outputs. */
/*
* Clamp the size to match the ImgU size limits and the
* margins from the CIO2 output frame size.
*
* The ImgU outputs needs to be strictly smaller than
* the CIO2 output frame and rounded down to 64 pixels
* in width and 32 pixels in height. This assumption
* comes from inspecting the pipe configuration script
* results and the available suggested configurations in
* the ChromeOS BSP .xml camera tuning files and shall
* be validated.
*
* \todo Clarify what are the hardware constraints
* that require this alignements, if any. It might
* depend on the BDS scaling factor of 1/32, as the main
* output has no YUV scaler as the viewfinder output has.
*/
unsigned int limit;
limit = utils::alignDown(cio2Configuration_.size.width - 1,
IMGU_OUTPUT_WIDTH_MARGIN);
cfg->size.width = std::clamp(cfg->size.width,
IMGU_OUTPUT_MIN_SIZE.width,
limit);
limit = utils::alignDown(cio2Configuration_.size.height - 1,
IMGU_OUTPUT_HEIGHT_MARGIN);
cfg->size.height = std::clamp(cfg->size.height,
IMGU_OUTPUT_MIN_SIZE.height,
limit);
cfg->size.alignDownTo(IMGU_OUTPUT_WIDTH_ALIGN,
IMGU_OUTPUT_HEIGHT_ALIGN);
cfg->pixelFormat = formats::NV12;
cfg->bufferCount = IPU3_BUFFER_COUNT;
cfg->stride = info.stride(cfg->size.width, 0, 1);
cfg->frameSize = info.frameSize(cfg->size, 1);
/*
* Use the main output stream in case only one stream is
* requested or if the current configuration is the one
* with the maximum YUV output size.
*/
if (mainOutputAvailable &&
(originalCfg.size == maxYuvSize || yuvCount == 1)) {
cfg->setStream(const_cast<Stream *>(&data_->outStream_));
mainOutputAvailable = false;
pipe.main = cfg->size;
if (yuvCount == 1)
pipe.viewfinder = pipe.main;
LOG(IPU3, Debug) << "Assigned " << cfg->toString()
<< " to the main output";
} else {
cfg->setStream(const_cast<Stream *>(&data_->vfStream_));
pipe.viewfinder = cfg->size;
LOG(IPU3, Debug) << "Assigned " << cfg->toString()
<< " to the viewfinder output";
}
}
if (cfg->pixelFormat != originalCfg.pixelFormat ||
cfg->size != originalCfg.size) {
LOG(IPU3, Debug)
<< "Stream " << i << " configuration adjusted to "
<< cfg->toString();
status = Adjusted;
}
}
/* Only compute the ImgU configuration if a YUV stream has been requested. */
if (yuvCount) {
pipeConfig_ = data_->imgu_->calculatePipeConfig(&pipe);
if (pipeConfig_.isNull()) {
LOG(IPU3, Error) << "Failed to calculate pipe configuration: "
<< "unsupported resolutions.";
return Invalid;
}
}
return status;
}
PipelineHandlerIPU3::PipelineHandlerIPU3(CameraManager *manager)
: PipelineHandler(manager), cio2MediaDev_(nullptr), imguMediaDev_(nullptr)
{
}
CameraConfiguration *PipelineHandlerIPU3::generateConfiguration(Camera *camera,
const StreamRoles &roles)
{
IPU3CameraData *data = cameraData(camera);
IPU3CameraConfiguration *config = new IPU3CameraConfiguration(data);
if (roles.empty())
return config;
Size sensorResolution = data->cio2_.sensor()->resolution();
for (const StreamRole role : roles) {
std::map<PixelFormat, std::vector<SizeRange>> streamFormats;
unsigned int bufferCount;
PixelFormat pixelFormat;
Size size;
switch (role) {
case StreamRole::StillCapture:
/*
* Use as default full-frame configuration a value
* strictly smaller than the sensor resolution (limited
* to the ImgU maximum output size) and aligned down to
* the required frame margin.
*
* \todo Clarify the alignment constraints as explained
* in validate()
*/
size = sensorResolution.boundedTo(IMGU_OUTPUT_MAX_SIZE);
size.width = utils::alignDown(size.width - 1,
IMGU_OUTPUT_WIDTH_MARGIN);
size.height = utils::alignDown(size.height - 1,
IMGU_OUTPUT_HEIGHT_MARGIN);
pixelFormat = formats::NV12;
bufferCount = IPU3_BUFFER_COUNT;
streamFormats[pixelFormat] = { { IMGU_OUTPUT_MIN_SIZE, size } };
break;
case StreamRole::Raw: {
StreamConfiguration cio2Config =
data->cio2_.generateConfiguration(sensorResolution);
pixelFormat = cio2Config.pixelFormat;
size = cio2Config.size;
bufferCount = cio2Config.bufferCount;
for (const PixelFormat &format : data->cio2_.formats())
streamFormats[format] = data->cio2_.sizes();
break;
}
case StreamRole::Viewfinder:
case StreamRole::VideoRecording: {
/*
* Default viewfinder and videorecording to 1280x720,
* capped to the maximum sensor resolution and aligned
* to the ImgU output constraints.
*/
size = sensorResolution.boundedTo(IPU3ViewfinderSize)
.alignedDownTo(IMGU_OUTPUT_WIDTH_ALIGN,
IMGU_OUTPUT_HEIGHT_ALIGN);
pixelFormat = formats::NV12;
bufferCount = IPU3_BUFFER_COUNT;
streamFormats[pixelFormat] = { { IMGU_OUTPUT_MIN_SIZE, size } };
break;
}
default:
LOG(IPU3, Error)
<< "Requested stream role not supported: " << role;
delete config;
return nullptr;
}
StreamFormats formats(streamFormats);
StreamConfiguration cfg(formats);
cfg.size = size;
cfg.pixelFormat = pixelFormat;
cfg.bufferCount = bufferCount;
config->addConfiguration(cfg);
}
if (config->validate() == CameraConfiguration::Invalid)
return {};
return config;
}
int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *c)
{
IPU3CameraConfiguration *config =
static_cast<IPU3CameraConfiguration *>(c);
IPU3CameraData *data = cameraData(camera);
Stream *outStream = &data->outStream_;
Stream *vfStream = &data->vfStream_;
CIO2Device *cio2 = &data->cio2_;
ImgUDevice *imgu = data->imgu_;
V4L2DeviceFormat outputFormat;
int ret;
/*
* FIXME: enabled links in one ImgU pipe interfere with capture
* operations on the other one. This can be easily triggered by
* capturing from one camera and then trying to capture from the other
* one right after, without disabling media links on the first used
* pipe.
*
* The tricky part here is where to disable links on the ImgU instance
* which is currently not in use:
* 1) Link enable/disable cannot be done at start()/stop() time as video
* devices needs to be linked first before format can be configured on
* them.
* 2) As link enable has to be done at the least in configure(),
* before configuring formats, the only place where to disable links
* would be 'stop()', but the Camera class state machine allows
* start()<->stop() sequences without any configure() in between.
*
* As of now, disable all links in the ImgU media graph before
* configuring the device, to allow alternate the usage of the two
* ImgU pipes.
*
* As a consequence, a Camera using an ImgU shall be configured before
* any start()/stop() sequence. An application that wants to
* pre-configure all the camera and then start/stop them alternatively
* without going through any re-configuration (a sequence that is
* allowed by the Camera state machine) would now fail on the IPU3.
*/
ret = imguMediaDev_->disableLinks();
if (ret)
return ret;
/*
* \todo: Enable links selectively based on the requested streams.
* As of now, enable all links unconditionally.
* \todo Don't configure the ImgU at all if we only have a single
* stream which is for raw capture, in which case no buffers will
* ever be queued to the ImgU.
*/
ret = data->imgu_->enableLinks(true);
if (ret)
return ret;
/*
* Pass the requested stream size to the CIO2 unit and get back the
* adjusted format to be propagated to the ImgU output devices.
*/
const Size &sensorSize = config->cio2Format().size;
V4L2DeviceFormat cio2Format;
ret = cio2->configure(sensorSize, &cio2Format);
if (ret)
return ret;
/*
* If the ImgU gets configured, its driver seems to expect that
* buffers will be queued to its outputs, as otherwise the next
* capture session that uses the ImgU fails when queueing
* buffers to its input.
*
* If no ImgU configuration has been computed, it means only a RAW
* stream has been requested: return here to skip the ImgU configuration
* part.
*/
ImgUDevice::PipeConfig imguConfig = config->imguConfig();
if (imguConfig.isNull())
return 0;
ret = imgu->configure(imguConfig, &cio2Format);
if (ret)
return ret;
/* Apply the format to the configured streams output devices. */
StreamConfiguration *mainCfg = nullptr;
StreamConfiguration *vfCfg = nullptr;
for (unsigned int i = 0; i < config->size(); ++i) {
StreamConfiguration &cfg = (*config)[i];
Stream *stream = cfg.stream();
if (stream == outStream) {
mainCfg = &cfg;
ret = imgu->configureOutput(cfg, &outputFormat);
if (ret)
return ret;
} else if (stream == vfStream) {
vfCfg = &cfg;
ret = imgu->configureViewfinder(cfg, &outputFormat);
if (ret)
return ret;
}
}
/*
* As we need to set format also on the non-active streams, use
* the configuration of the active one for that purpose (there should
* be at least one active stream in the configuration request).
*/
if (!vfCfg) {
ret = imgu->configureViewfinder(*mainCfg, &outputFormat);
if (ret)
return ret;
}
/* Apply the "pipe_mode" control to the ImgU subdevice. */
ControlList ctrls(imgu->imgu_->controls());
ctrls.set(V4L2_CID_IPU3_PIPE_MODE,
static_cast<int32_t>(vfCfg ? IPU3PipeModeVideo :
IPU3PipeModeStillCapture));
ret = imgu->imgu_->setControls(&ctrls);
if (ret) {
LOG(IPU3, Error) << "Unable to set pipe_mode control";
return ret;
}
return 0;
}
int PipelineHandlerIPU3::exportFrameBuffers(Camera *camera, Stream *stream,
std::vector<std::unique_ptr<FrameBuffer>> *buffers)
{
IPU3CameraData *data = cameraData(camera);
unsigned int count = stream->configuration().bufferCount;
if (stream == &data->outStream_)
return data->imgu_->output_->exportBuffers(count, buffers);
else if (stream == &data->vfStream_)
return data->imgu_->viewfinder_->exportBuffers(count, buffers);
else if (stream == &data->rawStream_)
return data->cio2_.exportBuffers(count, buffers);
return -EINVAL;
}
/**
* \todo Clarify if 'viewfinder' and 'stat' nodes have to be set up and
* started even if not in use. As of now, if not properly configured and
* enabled, the ImgU processing pipeline stalls.
*
* In order to be able to start the 'viewfinder' and 'stat' nodes, we need
* memory to be reserved.
*/
int PipelineHandlerIPU3::allocateBuffers(Camera *camera)
{
IPU3CameraData *data = cameraData(camera);
ImgUDevice *imgu = data->imgu_;
unsigned int bufferCount;
int ret;
bufferCount = std::max({
data->outStream_.configuration().bufferCount,
data->vfStream_.configuration().bufferCount,
data->rawStream_.configuration().bufferCount,
});
ret = imgu->allocateBuffers(bufferCount);
if (ret < 0)
return ret;
/* Map buffers to the IPA. */
unsigned int ipaBufferId = 1;
for (const std::unique_ptr<FrameBuffer> &buffer : imgu->paramBuffers_) {
buffer->setCookie(ipaBufferId++);
ipaBuffers_.push_back({
.id = buffer->cookie(),
.planes = buffer->planes()
});
}
for (const std::unique_ptr<FrameBuffer> &buffer : imgu->statBuffers_) {
buffer->setCookie(ipaBufferId++);
ipaBuffers_.push_back({
.id = buffer->cookie(),
.planes = buffer->planes()
});
}
data->ipa_->mapBuffers(ipaBuffers_);
return 0;
}
int PipelineHandlerIPU3::freeBuffers(Camera *camera)
{
IPU3CameraData *data = cameraData(camera);
std::vector<unsigned int> ids;
for (IPABuffer &ipabuf : ipaBuffers_)
ids.push_back(ipabuf.id);
data->ipa_->unmapBuffers(ids);
ipaBuffers_.clear();
data->imgu_->freeBuffers();
return 0;
}
int PipelineHandlerIPU3::start(Camera *camera, [[maybe_unused]] ControlList *controls)
{
IPU3CameraData *data = cameraData(camera);
CIO2Device *cio2 = &data->cio2_;
ImgUDevice *imgu = data->imgu_;
CameraSensorInfo sensorInfo = {};
std::map<unsigned int, IPAStream> streamConfig;
std::map<unsigned int, const ControlInfoMap &> entityControls;
IPAOperationData ipaConfig;
IPAOperationData result = {};
int ret;
/* Allocate buffers for internal pipeline usage. */
ret = allocateBuffers(camera);
if (ret)
return ret;
IPAOperationData ipaData = {};
ret = data->ipa_->start(ipaData, nullptr);
if (ret)
goto error;
/*
* Start the ImgU video devices, buffers will be queued to the
* ImgU output and viewfinder when requests will be queued.
*/
ret = cio2->start();
if (ret)
goto error;
ret = imgu->start();
if (ret)
goto error;
/* Inform IPA of stream configuration and sensor controls. */
ret = data->cio2_.sensor()->sensorInfo(&sensorInfo);
if (ret)
goto error;
streamConfig[0] = {
.pixelFormat = data->outStream_.configuration().pixelFormat,
.size = data->outStream_.configuration().size,
};
streamConfig[1] = {
.pixelFormat = data->vfStream_.configuration().pixelFormat,
.size = data->vfStream_.configuration().size,
};
entityControls.emplace(0, data->cio2_.sensor()->controls());
data->ipa_->configure(sensorInfo, streamConfig, entityControls,
ipaConfig, &result);
return 0;
error:
imgu->stop();
cio2->stop();
data->ipa_->stop();
freeBuffers(camera);
LOG(IPU3, Error) << "Failed to start camera " << camera->id();
return ret;
}
void PipelineHandlerIPU3::stop(Camera *camera)
{
IPU3CameraData *data = cameraData(camera);
int ret = 0;
ret |= data->imgu_->stop();
ret |= data->cio2_.stop();
if (ret)
LOG(IPU3, Warning) << "Failed to stop camera " << camera->id();
data->ipa_->stop();
freeBuffers(camera);
}
int PipelineHandlerIPU3::queueRequestDevice(Camera *camera, Request *request)
{
IPU3CameraData *data = cameraData(camera);
int error = 0;
/*
* Queue a buffer on the CIO2, using the raw stream buffer provided in
* the request, if any, or a CIO2 internal buffer otherwise.
*/
FrameBuffer *reqRawBuffer = request->findBuffer(&data->rawStream_);
FrameBuffer *rawBuffer = data->cio2_.queueBuffer(request, reqRawBuffer);
if (!rawBuffer)
return -ENOMEM;
/* Queue all buffers from the request aimed for the ImgU. */
for (auto it : request->buffers()) {
const Stream *stream = it.first;
FrameBuffer *buffer = it.second;
int ret;
if (stream == &data->outStream_)
ret = data->imgu_->output_->queueBuffer(buffer);
else if (stream == &data->vfStream_)
ret = data->imgu_->viewfinder_->queueBuffer(buffer);
else
continue;
if (ret < 0)
error = ret;
}
return error;
}
bool PipelineHandlerIPU3::match(DeviceEnumerator *enumerator)
{
int ret;
DeviceMatch cio2_dm("ipu3-cio2");
cio2_dm.add("ipu3-csi2 0");
cio2_dm.add("ipu3-cio2 0");
cio2_dm.add("ipu3-csi2 1");
cio2_dm.add("ipu3-cio2 1");
cio2_dm.add("ipu3-csi2 2");
cio2_dm.add("ipu3-cio2 2");
cio2_dm.add("ipu3-csi2 3");
cio2_dm.add("ipu3-cio2 3");
DeviceMatch imgu_dm("ipu3-imgu");
imgu_dm.add("ipu3-imgu 0");
imgu_dm.add("ipu3-imgu 0 input");
imgu_dm.add("ipu3-imgu 0 parameters");
imgu_dm.add("ipu3-imgu 0 output");
imgu_dm.add("ipu3-imgu 0 viewfinder");
imgu_dm.add("ipu3-imgu 0 3a stat");
imgu_dm.add("ipu3-imgu 1");
imgu_dm.add("ipu3-imgu 1 input");
imgu_dm.add("ipu3-imgu 1 parameters");
imgu_dm.add("ipu3-imgu 1 output");
imgu_dm.add("ipu3-imgu 1 viewfinder");
imgu_dm.add("ipu3-imgu 1 3a stat");
cio2MediaDev_ = acquireMediaDevice(enumerator, cio2_dm);
if (!cio2MediaDev_)
return false;
imguMediaDev_ = acquireMediaDevice(enumerator, imgu_dm);
if (!imguMediaDev_)
return false;
/*
* Disable all links that are enabled by default on CIO2, as camera
* creation enables all valid links it finds.
*/
if (cio2MediaDev_->disableLinks())
return false;
ret = imguMediaDev_->disableLinks();
if (ret)
return ret;
ret = registerCameras();
return ret == 0;
}
/**
* \brief Initialize the camera controls
* \param[in] data The camera data
*
* Initialize the camera controls as the union of the static pipeline handler
* controls (IPU3Controls) and controls created dynamically from the sensor
* capabilities.
*
* \return 0 on success or a negative error code otherwise
*/
int PipelineHandlerIPU3::initControls(IPU3CameraData *data)
{
CameraSensor *sensor = data->cio2_.sensor();
CameraSensorInfo sensorInfo{};
int ret = sensor->sensorInfo(&sensorInfo);
if (ret)
return ret;
ControlInfoMap::Map controls = IPU3Controls;
/*
* Compute exposure time limits.
*
* \todo The exposure limits depend on the sensor configuration.
* Initialize the control using the line length and pixel rate of the
* current configuration converted to microseconds. Use the
* V4L2_CID_EXPOSURE control to get exposure min, max and default and
* convert it from lines to microseconds.
*/
double lineDuration = sensorInfo.lineLength
/ (sensorInfo.pixelRate / 1e6);
const ControlInfoMap &sensorControls = sensor->controls();
const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second;
int32_t minExposure = v4l2Exposure.min().get<int32_t>() * lineDuration;
int32_t maxExposure = v4l2Exposure.max().get<int32_t>() * lineDuration;
int32_t defExposure = v4l2Exposure.def().get<int32_t>() * lineDuration;
/*
* \todo Report the actual exposure time, use the default for the
* moment.
*/
data->exposureTime_ = defExposure;
controls[&controls::ExposureTime] = ControlInfo(minExposure, maxExposure,
defExposure);
/*
* Compute the scaler crop limits.
*
* \todo The scaler crop limits depend on the sensor configuration. It
* should be updated when a new configuration is applied. To initialize
* the control use the 'Viewfinder' configuration (1280x720) as the
* pipeline output resolution and the full sensor size as input frame
* (see the todo note in the validate() function about the usage of the
* sensor's full frame as ImgU input).
*/
/* Re-fetch the sensor info updated to use the largest resolution. */
V4L2SubdeviceFormat sensorFormat;
sensorFormat.size = sensor->resolution();
ret = sensor->setFormat(&sensorFormat);
if (ret)
return ret;
ret = sensor->sensorInfo(&sensorInfo);
if (ret)
return ret;
/*
* The maximum scaler crop rectangle is the analogue crop used to
* produce the maximum frame size.
*/
const Rectangle &analogueCrop = sensorInfo.analogCrop;
Rectangle maxCrop = analogueCrop;
/*
* As the ImgU cannot up-scale, the minimum selection rectangle has to
* be as large as the pipeline output size. Use the default viewfinder
* configuration as the desired output size and calculate the minimum
* rectangle required to satisfy the ImgU processing margins, unless the
* sensor resolution is smaller.
*
* \todo This implementation is based on the same assumptions about the
* ImgU pipeline configuration described in then viewfinder and main
* output sizes calculation in the validate() function.
*/
/* The strictly smaller size than the sensor resolution, aligned to margins. */
Size minSize = Size(sensor->resolution().width - 1,
sensor->resolution().height - 1)
.alignedDownTo(IMGU_OUTPUT_WIDTH_MARGIN,
IMGU_OUTPUT_HEIGHT_MARGIN);
/*
* Either the smallest margin-aligned size larger than the viewfinder
* size or the adjusted sensor resolution.
*/
minSize = Size(IPU3ViewfinderSize.width + 1,
IPU3ViewfinderSize.height + 1)
.alignedUpTo(IMGU_OUTPUT_WIDTH_MARGIN,
IMGU_OUTPUT_HEIGHT_MARGIN)
.boundedTo(minSize);
/*
* Re-scale in the sensor's native coordinates. Report (0,0) as
* top-left corner as we allow application to freely pan the crop area.
*/
Rectangle minCrop = Rectangle(minSize).scaledBy(analogueCrop.size(),
sensorInfo.outputSize);
controls[&controls::ScalerCrop] = ControlInfo(minCrop, maxCrop, maxCrop);
data->controlInfo_ = std::move(controls);
return 0;
}
/**
* \brief Initialise ImgU and CIO2 devices associated with cameras
*
* Initialise the two ImgU instances and create cameras with an associated
* CIO2 device instance.
*
* \return 0 on success or a negative error code for error or if no camera
* has been created
* \retval -ENODEV no camera has been created
*/
int PipelineHandlerIPU3::registerCameras()
{
int ret;
ret = imgu0_.init(imguMediaDev_, 0);
if (ret)
return ret;
ret = imgu1_.init(imguMediaDev_, 1);
if (ret)
return ret;
/*
* For each CSI-2 receiver on the IPU3, create a Camera if an
* image sensor is connected to it and the sensor can produce images
* in a compatible format.
*/
unsigned int numCameras = 0;
for (unsigned int id = 0; id < 4 && numCameras < 2; ++id) {
std::unique_ptr<IPU3CameraData> data =
std::make_unique<IPU3CameraData>(this);
std::set<Stream *> streams = {
&data->outStream_,
&data->vfStream_,
&data->rawStream_,
};
CIO2Device *cio2 = &data->cio2_;
ret = cio2->init(cio2MediaDev_, id);
if (ret)
continue;
ret = data->loadIPA();
if (ret)
continue;
/* Initialize the camera properties. */
data->properties_ = cio2->sensor()->properties();
ret = initControls(data.get());
if (ret)
continue;
/*
* \todo Read delay values from the sensor itself or from a
* a sensor database. For now use generic values taken from
* the Raspberry Pi and listed as 'generic values'.
*/
std::unordered_map<uint32_t, unsigned int> delays = {
{ V4L2_CID_ANALOGUE_GAIN, 1 },
{ V4L2_CID_EXPOSURE, 2 },
};
data->delayedCtrls_ =
std::make_unique<DelayedControls>(cio2->sensor()->device(),
delays);
data->cio2_.frameStart().connect(data->delayedCtrls_.get(),
&DelayedControls::applyControls);
/**
* \todo Dynamically assign ImgU and output devices to each
* stream and camera; as of now, limit support to two cameras
* only, and assign imgu0 to the first one and imgu1 to the
* second.
*/
data->imgu_ = numCameras ? &imgu1_ : &imgu0_;
/*
* Connect video devices' 'bufferReady' signals to their
* slot to implement the image processing pipeline.
*
* Frames produced by the CIO2 unit are passed to the
* associated ImgU input where they get processed and
* returned through the ImgU main and secondary outputs.
*/
data->cio2_.bufferReady().connect(data.get(),
&IPU3CameraData::cio2BufferReady);
data->imgu_->input_->bufferReady.connect(&data->cio2_,
&CIO2Device::tryReturnBuffer);
data->imgu_->output_->bufferReady.connect(data.get(),
&IPU3CameraData::imguOutputBufferReady);
data->imgu_->viewfinder_->bufferReady.connect(data.get(),
&IPU3CameraData::imguOutputBufferReady);
/* Create and register the Camera instance. */
std::string cameraId = cio2->sensor()->id();
std::shared_ptr<Camera> camera =
Camera::create(this, cameraId, streams);
registerCamera(std::move(camera), std::move(data));
LOG(IPU3, Info)
<< "Registered Camera[" << numCameras << "] \""
<< cameraId << "\""
<< " connected to CSI-2 receiver " << id;
numCameras++;
}
return numCameras ? 0 : -ENODEV;
}
int IPU3CameraData::loadIPA()
{
ipa_ = IPAManager::createIPA(pipe_, 1, 1);
if (!ipa_)
return -ENOENT;
ipa_->queueFrameAction.connect(this, &IPU3CameraData::queueFrameAction);
ipa_->init(IPASettings{});
return 0;
}
void IPU3CameraData::queueFrameAction([[maybe_unused]] unsigned int id,
const IPAOperationData &action)
{
switch (action.operation) {
case IPU3_IPA_ACTION_SET_SENSOR_CONTROLS: {
const ControlList &controls = action.controls[0];
delayedCtrls_->push(controls);
break;
}
default:
LOG(IPU3, Error) << "Unknown action " << action.operation;
break;
}
}
/* -----------------------------------------------------------------------------
* Buffer Ready slots
*/
/**
* \brief Handle buffers completion at the ImgU output
* \param[in] buffer The completed buffer
*
* Buffers completed from the ImgU output are directed to the application.
*/
void IPU3CameraData::imguOutputBufferReady(FrameBuffer *buffer)
{
Request *request = buffer->request();
if (!pipe_->completeBuffer(request, buffer))
/* Request not completed yet, return here. */
return;
/* Mark the request as complete. */
request->metadata().set(controls::draft::PipelineDepth, 3);
/* \todo Move the ExposureTime control to the IPA. */
request->metadata().set(controls::ExposureTime, exposureTime_);
/* \todo Actually apply the scaler crop region to the ImgU. */
if (request->controls().contains(controls::ScalerCrop)) {
Rectangle cropRegion = request->controls().get(controls::ScalerCrop);
request->metadata().set(controls::ScalerCrop, cropRegion);
}
pipe_->completeRequest(request);
}
/**
* \brief Handle buffers completion at the CIO2 output
* \param[in] buffer The completed buffer
*
* Buffers completed from the CIO2 are immediately queued to the ImgU unit
* for further processing.
*/
void IPU3CameraData::cio2BufferReady(FrameBuffer *buffer)
{
/* \todo Handle buffer failures when state is set to BufferError. */
if (buffer->metadata().status == FrameMetadata::FrameCancelled)
return;
Request *request = buffer->request();
/*
* If the request contains a buffer for the RAW stream only, complete it
* now as there's no need for ImgU processing.
*/
if (request->findBuffer(&rawStream_)) {
bool isComplete = pipe_->completeBuffer(request, buffer);
if (isComplete) {
request->metadata().set(controls::draft::PipelineDepth, 2);
pipe_->completeRequest(request);
return;
}
}
imgu_->input_->queueBuffer(buffer);
}
REGISTER_PIPELINE_HANDLER(PipelineHandlerIPU3)
} /* namespace libcamera */