summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/jpeg/encoder_libjpeg.cpp6
-rw-r--r--src/apps/cam/camera_session.cpp45
-rw-r--r--src/apps/cam/file_sink.cpp60
-rw-r--r--src/apps/cam/file_sink.h18
-rw-r--r--src/apps/common/dng_writer.cpp39
-rw-r--r--src/apps/common/dng_writer.h1
-rw-r--r--src/apps/common/options.cpp8
-rw-r--r--src/apps/qcam/assets/shader/bayer_8.vert4
-rw-r--r--src/apps/qcam/assets/shader/identity.vert3
-rw-r--r--src/apps/qcam/cam_select_dialog.cpp10
-rw-r--r--src/apps/qcam/main_window.cpp14
-rw-r--r--src/apps/qcam/meson.build49
-rw-r--r--src/apps/qcam/viewfinder_gl.cpp52
-rw-r--r--src/apps/qcam/viewfinder_gl.h2
-rw-r--r--src/apps/qcam/viewfinder_qt.cpp51
-rw-r--r--src/apps/qcam/viewfinder_qt.h2
-rw-r--r--src/gstreamer/gstlibcamera-utils.cpp18
-rw-r--r--src/gstreamer/gstlibcameraallocator.cpp16
-rw-r--r--src/gstreamer/gstlibcamerapool.cpp41
-rw-r--r--src/gstreamer/gstlibcamerasrc.cpp47
-rw-r--r--src/ipa/ipu3/algorithms/af.cpp3
-rw-r--r--src/ipa/ipu3/algorithms/agc.cpp3
-rw-r--r--src/ipa/ipu3/algorithms/blc.cpp6
-rw-r--r--src/ipa/ipu3/ipu3.cpp20
-rw-r--r--src/ipa/ipu3/meson.build3
-rw-r--r--src/ipa/libipa/camera_sensor_helper.cpp155
-rw-r--r--src/ipa/libipa/camera_sensor_helper.h6
-rw-r--r--src/ipa/libipa/histogram.h1
-rw-r--r--src/ipa/libipa/interpolator.cpp157
-rw-r--r--src/ipa/libipa/interpolator.h131
-rw-r--r--src/ipa/libipa/lsc_polynomial.cpp81
-rw-r--r--src/ipa/libipa/lsc_polynomial.h105
-rw-r--r--src/ipa/libipa/matrix.h1
-rw-r--r--src/ipa/libipa/matrix_interpolator.cpp110
-rw-r--r--src/ipa/libipa/matrix_interpolator.h122
-rw-r--r--src/ipa/libipa/meson.build6
-rw-r--r--src/ipa/libipa/pwl.cpp2
-rw-r--r--src/ipa/libipa/pwl.h3
-rw-r--r--src/ipa/libipa/vector.h4
-rw-r--r--src/ipa/rkisp1/algorithms/agc.cpp48
-rw-r--r--src/ipa/rkisp1/algorithms/agc.h3
-rw-r--r--src/ipa/rkisp1/algorithms/awb.cpp87
-rw-r--r--src/ipa/rkisp1/algorithms/awb.h2
-rw-r--r--src/ipa/rkisp1/algorithms/blc.cpp135
-rw-r--r--src/ipa/rkisp1/algorithms/blc.h11
-rw-r--r--src/ipa/rkisp1/algorithms/ccm.cpp46
-rw-r--r--src/ipa/rkisp1/algorithms/ccm.h10
-rw-r--r--src/ipa/rkisp1/algorithms/cproc.cpp14
-rw-r--r--src/ipa/rkisp1/algorithms/cproc.h2
-rw-r--r--src/ipa/rkisp1/algorithms/dpcc.cpp10
-rw-r--r--src/ipa/rkisp1/algorithms/dpcc.h2
-rw-r--r--src/ipa/rkisp1/algorithms/dpf.cpp31
-rw-r--r--src/ipa/rkisp1/algorithms/dpf.h2
-rw-r--r--src/ipa/rkisp1/algorithms/filter.cpp56
-rw-r--r--src/ipa/rkisp1/algorithms/filter.h2
-rw-r--r--src/ipa/rkisp1/algorithms/goc.cpp17
-rw-r--r--src/ipa/rkisp1/algorithms/goc.h2
-rw-r--r--src/ipa/rkisp1/algorithms/gsl.cpp20
-rw-r--r--src/ipa/rkisp1/algorithms/gsl.h2
-rw-r--r--src/ipa/rkisp1/algorithms/lsc.cpp436
-rw-r--r--src/ipa/rkisp1/algorithms/lsc.h17
-rw-r--r--src/ipa/rkisp1/data/imx219.yaml4
-rw-r--r--src/ipa/rkisp1/data/imx258.yaml1
-rw-r--r--src/ipa/rkisp1/data/meson.build4
-rw-r--r--src/ipa/rkisp1/data/ov4689.yaml4
-rw-r--r--src/ipa/rkisp1/data/ov5640.yaml4
-rw-r--r--src/ipa/rkisp1/data/uncalibrated.yaml1
-rw-r--r--src/ipa/rkisp1/ipa_context.cpp8
-rw-r--r--src/ipa/rkisp1/ipa_context.h15
-rw-r--r--src/ipa/rkisp1/meson.build4
-rw-r--r--src/ipa/rkisp1/module.h3
-rw-r--r--src/ipa/rkisp1/params.cpp222
-rw-r--r--src/ipa/rkisp1/params.h163
-rw-r--r--src/ipa/rkisp1/rkisp1.cpp69
-rw-r--r--src/ipa/rkisp1/utils.h1
-rw-r--r--src/ipa/rpi/cam_helper/cam_helper_imx283.cpp73
-rw-r--r--src/ipa/rpi/cam_helper/cam_helper_imx290.cpp6
-rw-r--r--src/ipa/rpi/cam_helper/meson.build1
-rw-r--r--src/ipa/rpi/controller/histogram.cpp6
-rw-r--r--src/ipa/rpi/controller/rpi/af.cpp2
-rw-r--r--src/ipa/rpi/controller/rpi/agc_channel.cpp7
-rw-r--r--src/ipa/rpi/controller/rpi/alsc.cpp24
-rw-r--r--src/ipa/rpi/controller/rpi/awb.cpp3
-rw-r--r--src/ipa/rpi/controller/rpi/black_level.cpp1
-rw-r--r--src/ipa/rpi/controller/rpi/lux.cpp1
-rw-r--r--src/ipa/rpi/controller/rpi/noise.cpp4
-rw-r--r--src/ipa/rpi/controller/rpi/sharpen.cpp4
-rw-r--r--src/ipa/rpi/vc4/data/imx283.json313
-rw-r--r--src/ipa/rpi/vc4/data/meson.build1
-rw-r--r--src/ipa/rpi/vc4/meson.build3
-rw-r--r--src/ipa/simple/algorithms/agc.cpp139
-rw-r--r--src/ipa/simple/algorithms/agc.h33
-rw-r--r--src/ipa/simple/algorithms/algorithm.h22
-rw-r--r--src/ipa/simple/algorithms/awb.cpp69
-rw-r--r--src/ipa/simple/algorithms/awb.h32
-rw-r--r--src/ipa/simple/algorithms/blc.cpp78
-rw-r--r--src/ipa/simple/algorithms/blc.h35
-rw-r--r--src/ipa/simple/algorithms/lut.cpp86
-rw-r--r--src/ipa/simple/algorithms/lut.h34
-rw-r--r--src/ipa/simple/algorithms/meson.build8
-rw-r--r--src/ipa/simple/black_level.cpp88
-rw-r--r--src/ipa/simple/black_level.h29
-rw-r--r--src/ipa/simple/data/uncalibrated.yaml5
-rw-r--r--src/ipa/simple/ipa_context.cpp102
-rw-r--r--src/ipa/simple/ipa_context.h65
-rw-r--r--src/ipa/simple/meson.build12
-rw-r--r--src/ipa/simple/module.h30
-rw-r--r--src/ipa/simple/soft_simple.cpp283
-rw-r--r--src/ipa/vimc/meson.build3
-rw-r--r--src/ipa/vimc/vimc.cpp1
-rw-r--r--src/libcamera/base/event_dispatcher_poll.cpp3
-rw-r--r--src/libcamera/base/memfd.cpp123
-rw-r--r--src/libcamera/base/meson.build26
-rw-r--r--src/libcamera/base/thread.cpp36
-rw-r--r--src/libcamera/base/utils.cpp132
-rw-r--r--src/libcamera/bayer_format.cpp2
-rw-r--r--src/libcamera/camera.cpp22
-rw-r--r--src/libcamera/camera_manager.cpp12
-rw-r--r--src/libcamera/control_ids.cpp.in103
-rw-r--r--src/libcamera/control_ids_core.yaml472
-rw-r--r--src/libcamera/control_ids_rpi.yaml16
-rw-r--r--src/libcamera/controls.cpp32
-rw-r--r--src/libcamera/converter.cpp8
-rw-r--r--src/libcamera/converter/converter_v4l2_m2m.cpp82
-rw-r--r--src/libcamera/converter/meson.build2
-rw-r--r--src/libcamera/device_enumerator_udev.cpp8
-rw-r--r--src/libcamera/dma_buf_allocator.cpp46
-rw-r--r--src/libcamera/fence.cpp2
-rw-r--r--src/libcamera/formats.cpp15
-rw-r--r--src/libcamera/formats.yaml3
-rw-r--r--src/libcamera/framebuffer.cpp7
-rw-r--r--src/libcamera/ipa_data_serializer.cpp5
-rw-r--r--src/libcamera/ipa_manager.cpp10
-rw-r--r--src/libcamera/ipa_module.cpp5
-rw-r--r--src/libcamera/ipa_proxy.cpp23
-rw-r--r--src/libcamera/ipc_unixsocket.cpp11
-rw-r--r--src/libcamera/mapped_framebuffer.cpp2
-rw-r--r--src/libcamera/media_device.cpp12
-rw-r--r--src/libcamera/media_object.cpp50
-rw-r--r--src/libcamera/meson.build86
-rw-r--r--src/libcamera/orientation.cpp15
-rw-r--r--src/libcamera/pipeline/imx8-isi/meson.build2
-rw-r--r--src/libcamera/pipeline/ipu3/cio2.cpp4
-rw-r--r--src/libcamera/pipeline/ipu3/frames.cpp3
-rw-r--r--src/libcamera/pipeline/ipu3/ipu3.cpp25
-rw-r--r--src/libcamera/pipeline/ipu3/meson.build2
-rw-r--r--src/libcamera/pipeline/mali-c55/meson.build2
-rw-r--r--src/libcamera/pipeline/rkisp1/meson.build2
-rw-r--r--src/libcamera/pipeline/rkisp1/rkisp1.cpp53
-rw-r--r--src/libcamera/pipeline/rkisp1/rkisp1_path.cpp63
-rw-r--r--src/libcamera/pipeline/rkisp1/rkisp1_path.h11
-rw-r--r--src/libcamera/pipeline/rpi/common/meson.build2
-rw-r--r--src/libcamera/pipeline/rpi/vc4/meson.build2
-rw-r--r--src/libcamera/pipeline/simple/meson.build2
-rw-r--r--src/libcamera/pipeline/simple/simple.cpp101
-rw-r--r--src/libcamera/pipeline/uvcvideo/meson.build2
-rw-r--r--src/libcamera/pipeline/uvcvideo/uvcvideo.cpp68
-rw-r--r--src/libcamera/pipeline/vimc/meson.build2
-rw-r--r--src/libcamera/pipeline/vimc/vimc.cpp7
-rw-r--r--src/libcamera/pipeline_handler.cpp73
-rw-r--r--src/libcamera/process.cpp6
-rw-r--r--src/libcamera/property_ids.cpp.in48
-rw-r--r--src/libcamera/proxy/meson.build5
-rw-r--r--src/libcamera/proxy/worker/meson.build6
-rw-r--r--src/libcamera/request.cpp8
-rw-r--r--src/libcamera/sensor/camera_sensor.cpp12
-rw-r--r--src/libcamera/sensor/camera_sensor_properties.cpp19
-rw-r--r--src/libcamera/sensor/meson.build2
-rw-r--r--src/libcamera/shared_mem_object.cpp23
-rw-r--r--src/libcamera/software_isp/TODO58
-rw-r--r--src/libcamera/software_isp/debayer.cpp51
-rw-r--r--src/libcamera/software_isp/debayer.h2
-rw-r--r--src/libcamera/software_isp/debayer_cpu.cpp71
-rw-r--r--src/libcamera/software_isp/debayer_cpu.h4
-rw-r--r--src/libcamera/software_isp/meson.build2
-rw-r--r--src/libcamera/software_isp/software_isp.cpp71
-rw-r--r--src/libcamera/software_isp/swstats_cpu.cpp6
-rw-r--r--src/libcamera/software_isp/swstats_cpu.h4
-rw-r--r--src/libcamera/stream.cpp8
-rw-r--r--src/libcamera/v4l2_device.cpp2
-rw-r--r--src/libcamera/v4l2_pixelformat.cpp2
-rw-r--r--src/libcamera/v4l2_subdevice.cpp5
-rw-r--r--src/libcamera/v4l2_videodevice.cpp93
-rw-r--r--src/libcamera/yaml_parser.cpp29
-rw-r--r--src/py/cam/cam_qt.py6
-rw-r--r--src/py/cam/cam_qtgl.py12
-rwxr-xr-xsrc/py/libcamera/gen-py-controls.py126
-rw-r--r--src/py/libcamera/meson.build27
-rw-r--r--src/py/libcamera/py_controls_generated.cpp.in35
-rw-r--r--src/py/libcamera/py_main.cpp3
-rw-r--r--src/py/libcamera/py_properties_generated.cpp.in30
-rw-r--r--src/v4l2/v4l2_camera.cpp12
-rw-r--r--src/v4l2/v4l2_camera.h8
-rw-r--r--src/v4l2/v4l2_camera_proxy.cpp27
-rw-r--r--src/v4l2/v4l2_camera_proxy.h1
-rw-r--r--src/v4l2/v4l2_compat.cpp5
-rw-r--r--src/v4l2/v4l2_compat_manager.cpp1
197 files changed, 4903 insertions, 2190 deletions
diff --git a/src/android/jpeg/encoder_libjpeg.cpp b/src/android/jpeg/encoder_libjpeg.cpp
index 7fc6287e..cb242b5e 100644
--- a/src/android/jpeg/encoder_libjpeg.cpp
+++ b/src/android/jpeg/encoder_libjpeg.cpp
@@ -125,7 +125,7 @@ void EncoderLibJpeg::compressRGB(const std::vector<Span<uint8_t>> &planes)
*/
void EncoderLibJpeg::compressNV(const std::vector<Span<uint8_t>> &planes)
{
- uint8_t tmprowbuf[compress_.image_width * 3];
+ std::vector<uint8_t> tmprowbuf(compress_.image_width * 3);
/*
* \todo Use the raw api, and only unpack the cb/cr samples to new line
@@ -149,10 +149,10 @@ void EncoderLibJpeg::compressNV(const std::vector<Span<uint8_t>> &planes)
const unsigned char *src_c = planes[1].data();
JSAMPROW row_pointer[1];
- row_pointer[0] = &tmprowbuf[0];
+ row_pointer[0] = tmprowbuf.data();
for (unsigned int y = 0; y < compress_.image_height; y++) {
- unsigned char *dst = &tmprowbuf[0];
+ unsigned char *dst = tmprowbuf.data();
const unsigned char *src_y = src + y * y_stride;
const unsigned char *src_cb = src_c + (y / vertSubSample) * c_stride + cb_pos;
diff --git a/src/apps/cam/camera_session.cpp b/src/apps/cam/camera_session.cpp
index 097dc479..edc49b87 100644
--- a/src/apps/cam/camera_session.cpp
+++ b/src/apps/cam/camera_session.cpp
@@ -159,8 +159,34 @@ CameraSession::~CameraSession()
void CameraSession::listControls() const
{
for (const auto &[id, info] : camera_->controls()) {
- std::cout << "Control: " << id->name() << ": "
- << info.toString() << std::endl;
+ if (info.values().empty()) {
+ std::cout << "Control: " << id->name() << ": "
+ << info.toString() << std::endl;
+ } else {
+ std::cout << "Control: " << id->name() << ":" << std::endl;
+ for (const auto &value : info.values()) {
+ int32_t val = value.get<int32_t>();
+ const auto &it = id->enumerators().find(val);
+
+ std::cout << " - ";
+ if (it == id->enumerators().end())
+ std::cout << "UNKNOWN";
+ else
+ std::cout << it->second;
+ std::cout << " (" << val << ")" << std::endl;
+ }
+ }
+
+ if (id->isArray()) {
+ std::size_t size = id->size();
+
+ std::cout << " Size: ";
+ if (size == std::numeric_limits<std::size_t>::max())
+ std::cout << "n";
+ else
+ std::cout << std::to_string(size);
+ std::cout << std::endl;
+ }
}
}
@@ -230,11 +256,16 @@ int CameraSession::start()
#endif
if (options_.isSet(OptFile)) {
- if (!options_[OptFile].toString().empty())
- sink_ = std::make_unique<FileSink>(camera_.get(), streamNames_,
- options_[OptFile]);
- else
- sink_ = std::make_unique<FileSink>(camera_.get(), streamNames_);
+ std::unique_ptr<FileSink> sink =
+ std::make_unique<FileSink>(camera_.get(), streamNames_);
+
+ if (!options_[OptFile].toString().empty()) {
+ ret = sink->setFilePattern(options_[OptFile]);
+ if (ret)
+ return ret;
+ }
+
+ sink_ = std::move(sink);
}
if (sink_) {
diff --git a/src/apps/cam/file_sink.cpp b/src/apps/cam/file_sink.cpp
index 3e000d2f..76e21db9 100644
--- a/src/apps/cam/file_sink.cpp
+++ b/src/apps/cam/file_sink.cpp
@@ -5,6 +5,7 @@
* File Sink
*/
+#include <array>
#include <assert.h>
#include <fcntl.h>
#include <iomanip>
@@ -12,6 +13,7 @@
#include <sstream>
#include <string.h>
#include <unistd.h>
+#include <utility>
#include <libcamera/camera.h>
@@ -24,13 +26,13 @@
using namespace libcamera;
FileSink::FileSink([[maybe_unused]] const libcamera::Camera *camera,
- const std::map<const libcamera::Stream *, std::string> &streamNames,
- const std::string &pattern)
+ const std::map<const libcamera::Stream *, std::string> &streamNames)
:
#ifdef HAVE_TIFF
camera_(camera),
#endif
- streamNames_(streamNames), pattern_(pattern)
+ pattern_(kDefaultFilePattern), fileType_(FileType::Binary),
+ streamNames_(streamNames)
{
}
@@ -38,6 +40,41 @@ FileSink::~FileSink()
{
}
+int FileSink::setFilePattern(const std::string &pattern)
+{
+ static const std::array<std::pair<std::string, FileType>, 2> types{{
+ { ".dng", FileType::Dng },
+ { ".ppm", FileType::Ppm },
+ }};
+
+ pattern_ = pattern;
+
+ if (pattern_.empty() || pattern_.back() == '/')
+ pattern_ += kDefaultFilePattern;
+
+ fileType_ = FileType::Binary;
+
+ for (const auto &type : types) {
+ if (pattern_.size() < type.first.size())
+ continue;
+
+ if (pattern_.find(type.first, pattern_.size() - type.first.size()) !=
+ std::string::npos) {
+ fileType_ = type.second;
+ break;
+ }
+ }
+
+#ifndef HAVE_TIFF
+ if (fileType_ == FileType::Dng) {
+ std::cerr << "DNG support not available" << std::endl;
+ return -EINVAL;
+ }
+#endif /* HAVE_TIFF */
+
+ return 0;
+}
+
int FileSink::configure(const libcamera::CameraConfiguration &config)
{
int ret = FrameSink::configure(config);
@@ -67,21 +104,10 @@ bool FileSink::processRequest(Request *request)
void FileSink::writeBuffer(const Stream *stream, FrameBuffer *buffer,
[[maybe_unused]] const ControlList &metadata)
{
- std::string filename;
+ std::string filename = pattern_;
size_t pos;
int fd, ret = 0;
- if (!pattern_.empty())
- filename = pattern_;
-
-#ifdef HAVE_TIFF
- bool dng = filename.find(".dng", filename.size() - 4) != std::string::npos;
-#endif /* HAVE_TIFF */
- bool ppm = filename.find(".ppm", filename.size() - 4) != std::string::npos;
-
- if (filename.empty() || filename.back() == '/')
- filename += "frame-#.bin";
-
pos = filename.find_first_of('#');
if (pos != std::string::npos) {
std::stringstream ss;
@@ -93,7 +119,7 @@ void FileSink::writeBuffer(const Stream *stream, FrameBuffer *buffer,
Image *image = mappedBuffers_[buffer].get();
#ifdef HAVE_TIFF
- if (dng) {
+ if (fileType_ == FileType::Dng) {
ret = DNGWriter::write(filename.c_str(), camera_,
stream->configuration(), metadata,
buffer, image->data(0).data());
@@ -104,7 +130,7 @@ void FileSink::writeBuffer(const Stream *stream, FrameBuffer *buffer,
return;
}
#endif /* HAVE_TIFF */
- if (ppm) {
+ if (fileType_ == FileType::Ppm) {
ret = PPMWriter::write(filename.c_str(), stream->configuration(),
image->data(0));
if (ret < 0)
diff --git a/src/apps/cam/file_sink.h b/src/apps/cam/file_sink.h
index 9d560783..71b7fe0f 100644
--- a/src/apps/cam/file_sink.h
+++ b/src/apps/cam/file_sink.h
@@ -21,10 +21,11 @@ class FileSink : public FrameSink
{
public:
FileSink(const libcamera::Camera *camera,
- const std::map<const libcamera::Stream *, std::string> &streamNames,
- const std::string &pattern = "");
+ const std::map<const libcamera::Stream *, std::string> &streamNames);
~FileSink();
+ int setFilePattern(const std::string &pattern);
+
int configure(const libcamera::CameraConfiguration &config) override;
void mapBuffer(libcamera::FrameBuffer *buffer) override;
@@ -32,6 +33,14 @@ public:
bool processRequest(libcamera::Request *request) override;
private:
+ static constexpr const char *kDefaultFilePattern = "frame-#.bin";
+
+ enum class FileType {
+ Binary,
+ Dng,
+ Ppm,
+ };
+
void writeBuffer(const libcamera::Stream *stream,
libcamera::FrameBuffer *buffer,
const libcamera::ControlList &metadata);
@@ -39,7 +48,10 @@ private:
#ifdef HAVE_TIFF
const libcamera::Camera *camera_;
#endif
- std::map<const libcamera::Stream *, std::string> streamNames_;
+
std::string pattern_;
+ FileType fileType_;
+
+ std::map<const libcamera::Stream *, std::string> streamNames_;
std::map<libcamera::FrameBuffer *, std::unique_ptr<Image>> mappedBuffers_;
};
diff --git a/src/apps/common/dng_writer.cpp b/src/apps/common/dng_writer.cpp
index 9241f23f..ac461951 100644
--- a/src/apps/common/dng_writer.cpp
+++ b/src/apps/common/dng_writer.cpp
@@ -8,8 +8,10 @@
#include "dng_writer.h"
#include <algorithm>
+#include <endian.h>
#include <iostream>
#include <map>
+#include <vector>
#include <tiffio.h>
@@ -138,29 +140,29 @@ void packScanlineRaw8(void *output, const void *input, unsigned int width)
void packScanlineRaw10(void *output, const void *input, unsigned int width)
{
- const uint16_t *in = static_cast<const uint16_t *>(input);
+ const uint8_t *in = static_cast<const uint8_t *>(input);
uint8_t *out = static_cast<uint8_t *>(output);
for (unsigned int i = 0; i < width; i += 4) {
- *out++ = (in[0] & 0x3fc) >> 2;
- *out++ = (in[0] & 0x003) << 6 | (in[1] & 0x3f0) >> 4;
- *out++ = (in[1] & 0x00f) << 4 | (in[2] & 0x3c0) >> 6;
- *out++ = (in[2] & 0x03f) << 2 | (in[3] & 0x300) >> 8;
- *out++ = (in[3] & 0x0ff);
- in += 4;
+ *out++ = in[1] << 6 | in[0] >> 2;
+ *out++ = in[0] << 6 | (in[3] & 0x03) << 4 | in[2] >> 4;
+ *out++ = in[2] << 4 | (in[5] & 0x03) << 2 | in[4] >> 6;
+ *out++ = in[4] << 2 | (in[7] & 0x03) << 0;
+ *out++ = in[6];
+ in += 8;
}
}
void packScanlineRaw12(void *output, const void *input, unsigned int width)
{
- const uint16_t *in = static_cast<const uint16_t *>(input);
+ const uint8_t *in = static_cast<const uint8_t *>(input);
uint8_t *out = static_cast<uint8_t *>(output);
for (unsigned int i = 0; i < width; i += 2) {
- *out++ = (in[0] & 0xff0) >> 4;
- *out++ = (in[0] & 0x00f) << 4 | (in[1] & 0xf00) >> 8;
- *out++ = (in[1] & 0x0ff);
- in += 2;
+ *out++ = in[1] << 4 | in[0] >> 4;
+ *out++ = in[0] << 4 | (in[3] & 0x0f);
+ *out++ = in[2];
+ in += 4;
}
}
@@ -185,7 +187,8 @@ void thumbScanlineRaw(const FormatInfo &info, void *output, const void *input,
/* Simple averaging that produces greyscale RGB values. */
for (unsigned int x = 0; x < width; x++) {
- uint16_t value = (in[0] + in[1] + in2[0] + in2[1]) >> 2;
+ uint16_t value = (le16toh(in[0]) + le16toh(in[1]) +
+ le16toh(in2[0]) + le16toh(in2[1])) >> 2;
value = value >> shift;
*out++ = value;
*out++ = value;
@@ -542,7 +545,7 @@ int DNGWriter::write(const char *filename, const Camera *camera,
* or a thumbnail scanline. The latter will always be much smaller than
* the former as we downscale by 16 in both directions.
*/
- uint8_t scanline[(config.size.width * info->bitsPerSample + 7) / 8];
+ std::vector<uint8_t> scanline((config.size.width * info->bitsPerSample + 7) / 8);
toff_t rawIFDOffset = 0;
toff_t exifIFDOffset = 0;
@@ -642,10 +645,10 @@ int DNGWriter::write(const char *filename, const Camera *camera,
/* Write the thumbnail. */
const uint8_t *row = static_cast<const uint8_t *>(data);
for (unsigned int y = 0; y < config.size.height / 16; y++) {
- info->thumbScanline(*info, &scanline, row,
+ info->thumbScanline(*info, scanline.data(), row,
config.size.width / 16, config.stride);
- if (TIFFWriteScanline(tif, &scanline, y, 0) != 1) {
+ if (TIFFWriteScanline(tif, scanline.data(), y, 0) != 1) {
std::cerr << "Failed to write thumbnail scanline"
<< std::endl;
TIFFClose(tif);
@@ -745,9 +748,9 @@ int DNGWriter::write(const char *filename, const Camera *camera,
/* Write RAW content. */
row = static_cast<const uint8_t *>(data);
for (unsigned int y = 0; y < config.size.height; y++) {
- info->packScanline(&scanline, row, config.size.width);
+ info->packScanline(scanline.data(), row, config.size.width);
- if (TIFFWriteScanline(tif, &scanline, y, 0) != 1) {
+ if (TIFFWriteScanline(tif, scanline.data(), y, 0) != 1) {
std::cerr << "Failed to write RAW scanline"
<< std::endl;
TIFFClose(tif);
diff --git a/src/apps/common/dng_writer.h b/src/apps/common/dng_writer.h
index 917713e6..aaa8a852 100644
--- a/src/apps/common/dng_writer.h
+++ b/src/apps/common/dng_writer.h
@@ -8,7 +8,6 @@
#pragma once
#ifdef HAVE_TIFF
-#define HAVE_DNG
#include <libcamera/camera.h>
#include <libcamera/controls.h>
diff --git a/src/apps/common/options.cpp b/src/apps/common/options.cpp
index ab19aa3d..ece268d0 100644
--- a/src/apps/common/options.cpp
+++ b/src/apps/common/options.cpp
@@ -10,6 +10,7 @@
#include <iomanip>
#include <iostream>
#include <string.h>
+#include <vector>
#include "options.h"
@@ -879,8 +880,8 @@ OptionsParser::Options OptionsParser::parse(int argc, char **argv)
* Allocate short and long options arrays large enough to contain all
* options.
*/
- char shortOptions[optionsMap_.size() * 3 + 2];
- struct option longOptions[optionsMap_.size() + 1];
+ std::vector<char> shortOptions(optionsMap_.size() * 3 + 2);
+ std::vector<struct option> longOptions(optionsMap_.size() + 1);
unsigned int ids = 0;
unsigned int idl = 0;
@@ -922,7 +923,8 @@ OptionsParser::Options OptionsParser::parse(int argc, char **argv)
opterr = 0;
while (true) {
- int c = getopt_long(argc, argv, shortOptions, longOptions, nullptr);
+ int c = getopt_long(argc, argv, shortOptions.data(),
+ longOptions.data(), nullptr);
if (c == -1)
break;
diff --git a/src/apps/qcam/assets/shader/bayer_8.vert b/src/apps/qcam/assets/shader/bayer_8.vert
index 3695a5e9..fb5109ee 100644
--- a/src/apps/qcam/assets/shader/bayer_8.vert
+++ b/src/apps/qcam/assets/shader/bayer_8.vert
@@ -19,6 +19,8 @@ Copyright (C) 2021, Linaro
attribute vec4 vertexIn;
attribute vec2 textureIn;
+uniform mat4 proj_matrix;
+
uniform vec2 tex_size; /* The texture size in pixels */
uniform vec2 tex_step;
@@ -47,5 +49,5 @@ void main(void) {
yCoord = center.y + vec4(-2.0 * tex_step.y,
-tex_step.y, tex_step.y, 2.0 * tex_step.y);
- gl_Position = vertexIn;
+ gl_Position = proj_matrix * vertexIn;
}
diff --git a/src/apps/qcam/assets/shader/identity.vert b/src/apps/qcam/assets/shader/identity.vert
index 12c41377..907e8741 100644
--- a/src/apps/qcam/assets/shader/identity.vert
+++ b/src/apps/qcam/assets/shader/identity.vert
@@ -9,10 +9,11 @@ attribute vec4 vertexIn;
attribute vec2 textureIn;
varying vec2 textureOut;
+uniform mat4 proj_matrix;
uniform float stride_factor;
void main(void)
{
- gl_Position = vertexIn;
+ gl_Position = proj_matrix * vertexIn;
textureOut = vec2(textureIn.x * stride_factor, textureIn.y);
}
diff --git a/src/apps/qcam/cam_select_dialog.cpp b/src/apps/qcam/cam_select_dialog.cpp
index c51f5974..6b6d0713 100644
--- a/src/apps/qcam/cam_select_dialog.cpp
+++ b/src/apps/qcam/cam_select_dialog.cpp
@@ -15,7 +15,9 @@
#include <QComboBox>
#include <QDialogButtonBox>
#include <QFormLayout>
+#include <QGuiApplication>
#include <QLabel>
+#include <QScreen>
#include <QString>
CameraSelectorDialog::CameraSelectorDialog(libcamera::CameraManager *cameraManager,
@@ -53,6 +55,14 @@ CameraSelectorDialog::CameraSelectorDialog(libcamera::CameraManager *cameraManag
layout->addRow("Location:", cameraLocation_);
layout->addRow("Model:", cameraModel_);
layout->addWidget(buttonBox);
+
+ /*
+ * Decrease the minimum width of dialog to fit on narrow screens, with a
+ * 20 pixels margin.
+ */
+ QRect screenGeometry = qGuiApp->primaryScreen()->availableGeometry();
+ if (screenGeometry.width() < minimumWidth())
+ setMinimumWidth(screenGeometry.width() - 20);
}
CameraSelectorDialog::~CameraSelectorDialog() = default;
diff --git a/src/apps/qcam/main_window.cpp b/src/apps/qcam/main_window.cpp
index d515beed..5144c6b3 100644
--- a/src/apps/qcam/main_window.cpp
+++ b/src/apps/qcam/main_window.cpp
@@ -37,16 +37,6 @@
using namespace libcamera;
-#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
-/*
- * Qt::fixed was introduced in v5.14, and ::fixed deprecated in v5.15. Allow
- * usage of Qt::fixed unconditionally.
- */
-namespace Qt {
-constexpr auto fixed = ::fixed;
-} /* namespace Qt */
-#endif
-
/**
* \brief Custom QEvent to signal capture completion
*/
@@ -221,7 +211,7 @@ int MainWindow::createToolbars()
action->setShortcut(QKeySequence::SaveAs);
connect(action, &QAction::triggered, this, &MainWindow::saveImageAs);
-#ifdef HAVE_DNG
+#ifdef HAVE_TIFF
/* Save Raw action. */
action = toolbar_->addAction(QIcon::fromTheme("camera-photo",
QIcon(":aperture.svg")),
@@ -656,7 +646,7 @@ void MainWindow::captureRaw()
void MainWindow::processRaw(FrameBuffer *buffer,
[[maybe_unused]] const ControlList &metadata)
{
-#ifdef HAVE_DNG
+#ifdef HAVE_TIFF
QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
QString filename = QFileDialog::getSaveFileName(this, "Save DNG", defaultPath,
"DNG Files (*.dng)");
diff --git a/src/apps/qcam/meson.build b/src/apps/qcam/meson.build
index 6cf4c171..f7c14064 100644
--- a/src/apps/qcam/meson.build
+++ b/src/apps/qcam/meson.build
@@ -1,13 +1,13 @@
# SPDX-License-Identifier: CC0-1.0
-qt5 = import('qt5')
-qt5_dep = dependency('qt5',
+qt6 = import('qt6')
+qt6_dep = dependency('qt6',
method : 'pkg-config',
- modules : ['Core', 'Gui', 'Widgets'],
+ modules : ['Core', 'Gui', 'OpenGL', 'OpenGLWidgets', 'Widgets'],
required : get_option('qcam'),
- version : '>=5.4')
+ version : '>=6.2')
-if not qt5_dep.found()
+if not qt6_dep.found()
qcam_enabled = false
subdir_done()
endif
@@ -20,46 +20,31 @@ qcam_sources = files([
'main.cpp',
'main_window.cpp',
'message_handler.cpp',
+ 'viewfinder_gl.cpp',
'viewfinder_qt.cpp',
])
qcam_moc_headers = files([
'cam_select_dialog.h',
'main_window.h',
+ 'viewfinder_gl.h',
'viewfinder_qt.h',
])
qcam_resources = files([
'assets/feathericons/feathericons.qrc',
+ 'assets/shader/shaders.qrc',
])
-qt5_cpp_args = [apps_cpp_args, '-DQT_NO_KEYWORDS']
+qt6_cpp_args = [
+ apps_cpp_args,
+ '-DQT_NO_KEYWORDS',
+ '-Wno-extra-semi',
+]
-if cxx.has_header_symbol('QOpenGLWidget', 'QOpenGLWidget',
- dependencies : qt5_dep, args : '-fPIC')
- qcam_sources += files([
- 'viewfinder_gl.cpp',
- ])
- qcam_moc_headers += files([
- 'viewfinder_gl.h',
- ])
- qcam_resources += files([
- 'assets/shader/shaders.qrc'
- ])
-endif
-
-# gcc 9 introduced a deprecated-copy warning that is triggered by Qt until
-# Qt 5.13. clang 10 introduced the same warning, but detects more issues
-# that are not fixed in Qt yet. Disable the warning manually in both cases.
-if ((cc.get_id() == 'gcc' and cc.version().version_compare('>=9.0') and
- qt5_dep.version().version_compare('<5.13')) or
- (cc.get_id() == 'clang' and cc.version().version_compare('>=10.0')))
- qt5_cpp_args += ['-Wno-deprecated-copy']
-endif
-
-resources = qt5.preprocess(moc_headers : qcam_moc_headers,
+resources = qt6.preprocess(moc_headers : qcam_moc_headers,
qresources : qcam_resources,
- dependencies : qt5_dep)
+ dependencies : qt6_dep)
qcam = executable('qcam', qcam_sources, resources,
install : true,
@@ -69,6 +54,6 @@ qcam = executable('qcam', qcam_sources, resources,
libatomic,
libcamera_public,
libtiff,
- qt5_dep,
+ qt6_dep,
],
- cpp_args : qt5_cpp_args)
+ cpp_args : qt6_cpp_args)
diff --git a/src/apps/qcam/viewfinder_gl.cpp b/src/apps/qcam/viewfinder_gl.cpp
index 9d2a6960..f31956ff 100644
--- a/src/apps/qcam/viewfinder_gl.cpp
+++ b/src/apps/qcam/viewfinder_gl.cpp
@@ -12,6 +12,7 @@
#include <QByteArray>
#include <QFile>
#include <QImage>
+#include <QMatrix4x4>
#include <QStringList>
#include <libcamera/formats.h>
@@ -443,15 +444,11 @@ bool ViewFinderGL::createFragmentShader()
close();
}
- /* Bind shader pipeline for use */
- if (!shaderProgram_.bind()) {
- qWarning() << "[ViewFinderGL]:" << shaderProgram_.log();
- close();
- }
-
attributeVertex = shaderProgram_.attributeLocation("vertexIn");
attributeTexture = shaderProgram_.attributeLocation("textureIn");
+ vertexBuffer_.bind();
+
shaderProgram_.enableAttributeArray(attributeVertex);
shaderProgram_.setAttributeBuffer(attributeVertex,
GL_FLOAT,
@@ -466,6 +463,9 @@ bool ViewFinderGL::createFragmentShader()
2,
2 * sizeof(GLfloat));
+ vertexBuffer_.release();
+
+ projMatrixUniform_ = shaderProgram_.uniformLocation("proj_matrix");
textureUniformY_ = shaderProgram_.uniformLocation("tex_y");
textureUniformU_ = shaderProgram_.uniformLocation("tex_u");
textureUniformV_ = shaderProgram_.uniformLocation("tex_v");
@@ -511,7 +511,7 @@ void ViewFinderGL::initializeGL()
glEnable(GL_TEXTURE_2D);
glDisable(GL_DEPTH_TEST);
- static const GLfloat coordinates[2][4][2]{
+ const GLfloat coordinates[2][4][2]{
{
/* Vertex coordinates */
{ -1.0f, -1.0f },
@@ -535,8 +535,6 @@ void ViewFinderGL::initializeGL()
/* Create Vertex Shader */
if (!createVertexShader())
qWarning() << "[ViewFinderGL]: create vertex shader failed.";
-
- glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
}
void ViewFinderGL::doRender()
@@ -805,28 +803,42 @@ void ViewFinderGL::doRender()
shaderProgram_.setUniformValue(textureUniformStrideFactor_,
static_cast<float>(size_.width() - 1) /
(stridePixels - 1));
+
+ /*
+ * Place the viewfinder in the centre of the widget, preserving the
+ * aspect ratio of the image.
+ */
+ QMatrix4x4 projMatrix;
+ QSizeF scaledSize = size_.scaled(size(), Qt::KeepAspectRatio);
+ projMatrix.scale(scaledSize.width() / size().width(),
+ scaledSize.height() / size().height());
+
+ shaderProgram_.setUniformValue(projMatrixUniform_, projMatrix);
}
void ViewFinderGL::paintGL()
{
- if (!fragmentShader_)
+ if (!fragmentShader_) {
if (!createFragmentShader()) {
qWarning() << "[ViewFinderGL]:"
<< "create fragment shader failed.";
}
+ }
- if (image_) {
- glClearColor(0.0, 0.0, 0.0, 1.0);
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-
- doRender();
- glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+ /* Bind shader pipeline for use. */
+ if (!shaderProgram_.bind()) {
+ qWarning() << "[ViewFinderGL]:" << shaderProgram_.log();
+ close();
}
-}
-void ViewFinderGL::resizeGL(int w, int h)
-{
- glViewport(0, 0, w, h);
+ if (!image_)
+ return;
+
+ glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ doRender();
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
QSize ViewFinderGL::sizeHint() const
diff --git a/src/apps/qcam/viewfinder_gl.h b/src/apps/qcam/viewfinder_gl.h
index 23744b41..23c657bc 100644
--- a/src/apps/qcam/viewfinder_gl.h
+++ b/src/apps/qcam/viewfinder_gl.h
@@ -51,7 +51,6 @@ Q_SIGNALS:
protected:
void initializeGL() override;
void paintGL() override;
- void resizeGL(int w, int h) override;
QSize sizeHint() const override;
private:
@@ -88,6 +87,7 @@ private:
/* Common texture parameters */
GLuint textureMinMagFilters_;
+ GLuint projMatrixUniform_;
/* YUV texture parameters */
GLuint textureUniformU_;
diff --git a/src/apps/qcam/viewfinder_qt.cpp b/src/apps/qcam/viewfinder_qt.cpp
index 4821c27d..1a238922 100644
--- a/src/apps/qcam/viewfinder_qt.cpp
+++ b/src/apps/qcam/viewfinder_qt.cpp
@@ -18,6 +18,7 @@
#include <QMap>
#include <QMutexLocker>
#include <QPainter>
+#include <QResizeEvent>
#include <QtDebug>
#include "../common/image.h"
@@ -26,23 +27,23 @@
static const QMap<libcamera::PixelFormat, QImage::Format> nativeFormats
{
-#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
{ libcamera::formats::ABGR8888, QImage::Format_RGBX8888 },
{ libcamera::formats::XBGR8888, QImage::Format_RGBX8888 },
-#endif
{ libcamera::formats::ARGB8888, QImage::Format_RGB32 },
{ libcamera::formats::XRGB8888, QImage::Format_RGB32 },
-#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
{ libcamera::formats::RGB888, QImage::Format_BGR888 },
-#endif
{ libcamera::formats::BGR888, QImage::Format_RGB888 },
{ libcamera::formats::RGB565, QImage::Format_RGB16 },
};
ViewFinderQt::ViewFinderQt(QWidget *parent)
- : QWidget(parent), buffer_(nullptr)
+ : QWidget(parent), place_(rect()), buffer_(nullptr)
{
icon_ = QIcon(":camera-off.svg");
+
+ QPalette pal = palette();
+ pal.setColor(QPalette::Window, Qt::black);
+ setPalette(pal);
}
ViewFinderQt::~ViewFinderQt()
@@ -117,6 +118,11 @@ void ViewFinderQt::render(libcamera::FrameBuffer *buffer, Image *image)
}
}
+ /*
+ * Indicate the widget paints all its pixels, to optimize rendering by
+ * skipping erasing the widget before painting.
+ */
+ setAttribute(Qt::WA_OpaquePaintEvent, true);
update();
if (buffer)
@@ -132,6 +138,11 @@ void ViewFinderQt::stop()
buffer_ = nullptr;
}
+ /*
+ * The logo has a transparent background, reenable erasing the widget
+ * before painting.
+ */
+ setAttribute(Qt::WA_OpaquePaintEvent, false);
update();
}
@@ -146,9 +157,23 @@ void ViewFinderQt::paintEvent(QPaintEvent *)
{
QPainter painter(this);
- /* If we have an image, draw it. */
+ painter.setBrush(palette().window());
+
+ /* If we have an image, draw it, with black letterbox rectangles. */
if (!image_.isNull()) {
- painter.drawImage(rect(), image_, image_.rect());
+ if (place_.width() < width()) {
+ QRect rect{ 0, 0, (width() - place_.width()) / 2, height() };
+ painter.drawRect(rect);
+ rect.moveLeft(place_.right());
+ painter.drawRect(rect);
+ } else {
+ QRect rect{ 0, 0, width(), (height() - place_.height()) / 2 };
+ painter.drawRect(rect);
+ rect.moveTop(place_.bottom());
+ painter.drawRect(rect);
+ }
+
+ painter.drawImage(place_, image_, image_.rect());
return;
}
@@ -173,7 +198,6 @@ void ViewFinderQt::paintEvent(QPaintEvent *)
else
point.setY((height() - pixmap_.height()) / 2);
- painter.setBackgroundMode(Qt::OpaqueMode);
painter.drawPixmap(point, pixmap_);
}
@@ -181,3 +205,14 @@ QSize ViewFinderQt::sizeHint() const
{
return size_.isValid() ? size_ : QSize(640, 480);
}
+
+void ViewFinderQt::resizeEvent(QResizeEvent *event)
+{
+ if (!size_.isValid())
+ return;
+
+ place_.setSize(size_.scaled(event->size(), Qt::KeepAspectRatio));
+ place_.moveCenter(rect().center());
+
+ QWidget::resizeEvent(event);
+}
diff --git a/src/apps/qcam/viewfinder_qt.h b/src/apps/qcam/viewfinder_qt.h
index 4f4b9f11..50fde88e 100644
--- a/src/apps/qcam/viewfinder_qt.h
+++ b/src/apps/qcam/viewfinder_qt.h
@@ -44,6 +44,7 @@ Q_SIGNALS:
protected:
void paintEvent(QPaintEvent *) override;
+ void resizeEvent(QResizeEvent *) override;
QSize sizeHint() const override;
private:
@@ -51,6 +52,7 @@ private:
libcamera::PixelFormat format_;
QSize size_;
+ QRect place_;
/* Camera stopped icon */
QSize vfSize_;
diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp
index ec4da435..79f71246 100644
--- a/src/gstreamer/gstlibcamera-utils.cpp
+++ b/src/gstreamer/gstlibcamera-utils.cpp
@@ -359,13 +359,21 @@ gst_libcamera_stream_formats_to_caps(const StreamFormats &formats)
GValue val = G_VALUE_INIT;
g_value_init(&val, GST_TYPE_INT_RANGE);
- gst_value_set_int_range_step(&val, range.min.width, range.max.width, range.hStep);
- gst_structure_set_value(s, "width", &val);
- gst_value_set_int_range_step(&val, range.min.height, range.max.height, range.vStep);
- gst_structure_set_value(s, "height", &val);
+ if (range.min.width == range.max.width) {
+ gst_structure_set(s, "width", G_TYPE_INT, range.min.width, nullptr);
+ } else {
+ gst_value_set_int_range_step(&val, range.min.width, range.max.width, range.hStep);
+ gst_structure_set_value(s, "width", &val);
+ }
+ if (range.min.height == range.max.height) {
+ gst_structure_set(s, "height", G_TYPE_INT, range.min.height, nullptr);
+ } else {
+ gst_value_set_int_range_step(&val, range.min.height, range.max.height, range.vStep);
+ gst_structure_set_value(s, "height", &val);
+ }
g_value_unset(&val);
- gst_caps_append_structure(caps, s);
+ caps = gst_caps_merge_structure(caps, s);
}
}
diff --git a/src/gstreamer/gstlibcameraallocator.cpp b/src/gstreamer/gstlibcameraallocator.cpp
index 741ed592..7e4c904d 100644
--- a/src/gstreamer/gstlibcameraallocator.cpp
+++ b/src/gstreamer/gstlibcameraallocator.cpp
@@ -100,6 +100,11 @@ struct _GstLibcameraAllocator {
* FrameWrap.
*/
GHashTable *pools;
+ /*
+ * The camera manager represents the library, which needs to be kept
+ * alive until all the memory has been released.
+ */
+ std::shared_ptr<CameraManager> *cm_ptr;
};
G_DEFINE_TYPE(GstLibcameraAllocator, gst_libcamera_allocator,
@@ -173,6 +178,9 @@ gst_libcamera_allocator_finalize(GObject *object)
delete self->fb_allocator;
+ /* Keep last. */
+ delete self->cm_ptr;
+
G_OBJECT_CLASS(gst_libcamera_allocator_parent_class)->finalize(object);
}
@@ -193,11 +201,17 @@ gst_libcamera_allocator_new(std::shared_ptr<Camera> camera,
{
auto *self = GST_LIBCAMERA_ALLOCATOR(g_object_new(GST_TYPE_LIBCAMERA_ALLOCATOR,
nullptr));
+ gint ret;
+
+ self->cm_ptr = new std::shared_ptr<CameraManager>(gst_libcamera_get_camera_manager(ret));
+ if (ret) {
+ g_object_unref(self);
+ return nullptr;
+ }
self->fb_allocator = new FrameBufferAllocator(camera);
for (StreamConfiguration &streamCfg : *config_) {
Stream *stream = streamCfg.stream();
- gint ret;
ret = self->fb_allocator->allocate(stream);
if (ret == 0)
diff --git a/src/gstreamer/gstlibcamerapool.cpp b/src/gstreamer/gstlibcamerapool.cpp
index 9661c67a..9cd7eccb 100644
--- a/src/gstreamer/gstlibcamerapool.cpp
+++ b/src/gstreamer/gstlibcamerapool.cpp
@@ -8,6 +8,8 @@
#include "gstlibcamerapool.h"
+#include <deque>
+
#include <libcamera/stream.h>
#include "gstlibcamera-utils.h"
@@ -24,24 +26,41 @@ static guint signals[N_SIGNALS];
struct _GstLibcameraPool {
GstBufferPool parent;
- GstAtomicQueue *queue;
+ std::deque<GstBuffer *> *queue;
GstLibcameraAllocator *allocator;
Stream *stream;
};
G_DEFINE_TYPE(GstLibcameraPool, gst_libcamera_pool, GST_TYPE_BUFFER_POOL)
+static GstBuffer *
+gst_libcamera_pool_pop_buffer(GstLibcameraPool *self)
+{
+ GLibLocker lock(GST_OBJECT(self));
+ GstBuffer *buf;
+
+ if (self->queue->empty())
+ return nullptr;
+
+ buf = self->queue->front();
+ self->queue->pop_front();
+
+ return buf;
+}
+
static GstFlowReturn
gst_libcamera_pool_acquire_buffer(GstBufferPool *pool, GstBuffer **buffer,
[[maybe_unused]] GstBufferPoolAcquireParams *params)
{
GstLibcameraPool *self = GST_LIBCAMERA_POOL(pool);
- GstBuffer *buf = GST_BUFFER(gst_atomic_queue_pop(self->queue));
+ GstBuffer *buf = gst_libcamera_pool_pop_buffer(self);
+
if (!buf)
return GST_FLOW_ERROR;
if (!gst_libcamera_allocator_prepare_buffer(self->allocator, self->stream, buf)) {
- gst_atomic_queue_push(self->queue, buf);
+ GLibLocker lock(GST_OBJECT(self));
+ self->queue->push_back(buf);
return GST_FLOW_ERROR;
}
@@ -64,9 +83,13 @@ static void
gst_libcamera_pool_release_buffer(GstBufferPool *pool, GstBuffer *buffer)
{
GstLibcameraPool *self = GST_LIBCAMERA_POOL(pool);
- bool do_notify = gst_atomic_queue_length(self->queue) == 0;
+ bool do_notify;
- gst_atomic_queue_push(self->queue, buffer);
+ {
+ GLibLocker lock(GST_OBJECT(self));
+ do_notify = self->queue->empty();
+ self->queue->push_back(buffer);
+ }
if (do_notify)
g_signal_emit(self, signals[SIGNAL_BUFFER_NOTIFY], 0);
@@ -75,7 +98,7 @@ gst_libcamera_pool_release_buffer(GstBufferPool *pool, GstBuffer *buffer)
static void
gst_libcamera_pool_init(GstLibcameraPool *self)
{
- self->queue = gst_atomic_queue_new(4);
+ self->queue = new std::deque<GstBuffer *>();
}
static void
@@ -84,10 +107,10 @@ gst_libcamera_pool_finalize(GObject *object)
GstLibcameraPool *self = GST_LIBCAMERA_POOL(object);
GstBuffer *buf;
- while ((buf = GST_BUFFER(gst_atomic_queue_pop(self->queue))))
+ while ((buf = gst_libcamera_pool_pop_buffer(self)))
gst_buffer_unref(buf);
- gst_atomic_queue_unref(self->queue);
+ delete self->queue;
g_object_unref(self->allocator);
G_OBJECT_CLASS(gst_libcamera_pool_parent_class)->finalize(object);
@@ -122,7 +145,7 @@ gst_libcamera_pool_new(GstLibcameraAllocator *allocator, Stream *stream)
gsize pool_size = gst_libcamera_allocator_get_pool_size(allocator, stream);
for (gsize i = 0; i < pool_size; i++) {
GstBuffer *buffer = gst_buffer_new();
- gst_atomic_queue_push(pool->queue, buffer);
+ pool->queue->push_back(buffer);
}
return pool;
diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp
index 9680d809..912a8d55 100644
--- a/src/gstreamer/gstlibcamerasrc.cpp
+++ b/src/gstreamer/gstlibcamerasrc.cpp
@@ -157,7 +157,12 @@ enum {
PROP_AUTO_FOCUS_MODE,
};
+static void gst_libcamera_src_child_proxy_init(gpointer g_iface,
+ gpointer iface_data);
+
G_DEFINE_TYPE_WITH_CODE(GstLibcameraSrc, gst_libcamera_src, GST_TYPE_ELEMENT,
+ G_IMPLEMENT_INTERFACE(GST_TYPE_CHILD_PROXY,
+ gst_libcamera_src_child_proxy_init)
GST_DEBUG_CATEGORY_INIT(source_debug, "libcamerasrc", 0,
"libcamera Source"))
@@ -865,8 +870,10 @@ gst_libcamera_src_init(GstLibcameraSrc *self)
g_mutex_init(&state->lock_);
- state->srcpads_.push_back(gst_pad_new_from_template(templ, "src"));
- gst_element_add_pad(GST_ELEMENT(self), state->srcpads_.back());
+ GstPad *pad = gst_pad_new_from_template(templ, "src");
+ state->srcpads_.push_back(pad);
+ gst_element_add_pad(GST_ELEMENT(self), pad);
+ gst_child_proxy_child_added(GST_CHILD_PROXY(self), G_OBJECT(pad), GST_OBJECT_NAME(pad));
GST_OBJECT_FLAG_SET(self, GST_ELEMENT_FLAG_SOURCE);
@@ -897,6 +904,8 @@ gst_libcamera_src_request_new_pad(GstElement *element, GstPadTemplate *templ,
return NULL;
}
+ gst_child_proxy_child_added(GST_CHILD_PROXY(self), G_OBJECT(pad), GST_OBJECT_NAME(pad));
+
return reinterpret_cast<GstPad *>(g_steal_pointer(&pad));
}
@@ -905,6 +914,8 @@ gst_libcamera_src_release_pad(GstElement *element, GstPad *pad)
{
GstLibcameraSrc *self = GST_LIBCAMERA_SRC(element);
+ gst_child_proxy_child_removed(GST_CHILD_PROXY(self), G_OBJECT(pad), GST_OBJECT_NAME(pad));
+
GST_DEBUG_OBJECT(self, "Pad %" GST_PTR_FORMAT " being released", pad);
{
@@ -940,7 +951,7 @@ gst_libcamera_src_class_init(GstLibcameraSrcClass *klass)
gst_element_class_set_metadata(element_class,
"libcamera Source", "Source/Video",
"Linux Camera source using libcamera",
- "Nicolas Dufresne <nicolas.dufresne@collabora.com");
+ "Nicolas Dufresne <nicolas.dufresne@collabora.com>");
gst_element_class_add_static_pad_template_with_gtype(element_class,
&src_template,
GST_TYPE_LIBCAMERA_PAD);
@@ -965,3 +976,33 @@ gst_libcamera_src_class_init(GstLibcameraSrcClass *klass)
G_PARAM_WRITABLE);
g_object_class_install_property(object_class, PROP_AUTO_FOCUS_MODE, spec);
}
+
+/* GstChildProxy implementation */
+static GObject *
+gst_libcamera_src_child_proxy_get_child_by_index(GstChildProxy *child_proxy,
+ guint index)
+{
+ GLibLocker lock(GST_OBJECT(child_proxy));
+ GObject *obj = nullptr;
+
+ obj = reinterpret_cast<GObject *>(g_list_nth_data(GST_ELEMENT(child_proxy)->srcpads, index));
+ if (obj)
+ gst_object_ref(obj);
+
+ return obj;
+}
+
+static guint
+gst_libcamera_src_child_proxy_get_children_count(GstChildProxy *child_proxy)
+{
+ GLibLocker lock(GST_OBJECT(child_proxy));
+ return GST_ELEMENT_CAST(child_proxy)->numsrcpads;
+}
+
+static void
+gst_libcamera_src_child_proxy_init(gpointer g_iface, [[maybe_unused]] gpointer iface_data)
+{
+ GstChildProxyInterface *iface = reinterpret_cast<GstChildProxyInterface *>(g_iface);
+ iface->get_child_by_index = gst_libcamera_src_child_proxy_get_child_by_index;
+ iface->get_children_count = gst_libcamera_src_child_proxy_get_children_count;
+}
diff --git a/src/ipa/ipu3/algorithms/af.cpp b/src/ipa/ipu3/algorithms/af.cpp
index 29eb7355..cf68fb59 100644
--- a/src/ipa/ipu3/algorithms/af.cpp
+++ b/src/ipa/ipu3/algorithms/af.cpp
@@ -11,7 +11,6 @@
#include <chrono>
#include <cmath>
#include <fcntl.h>
-#include <numeric>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
@@ -23,8 +22,6 @@
#include <libcamera/ipa/core_ipa_interface.h>
-#include "libipa/histogram.h"
-
/**
* \file af.h
*/
diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp
index 0e0114f6..c5f3d8f0 100644
--- a/src/ipa/ipu3/algorithms/agc.cpp
+++ b/src/ipa/ipu3/algorithms/agc.cpp
@@ -9,12 +9,12 @@
#include <algorithm>
#include <chrono>
-#include <cmath>
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>
#include <libcamera/control_ids.h>
+
#include <libcamera/ipa/core_ipa_interface.h>
#include "libipa/histogram.h"
@@ -247,7 +247,6 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
utils::Duration frameDuration = context.configuration.sensor.lineDuration
* vTotal;
metadata.set(controls::FrameDuration, frameDuration.get<std::micro>());
-
}
REGISTER_IPA_ALGORITHM(Agc, "Agc")
diff --git a/src/ipa/ipu3/algorithms/blc.cpp b/src/ipa/ipu3/algorithms/blc.cpp
index 257f40e2..35748fb2 100644
--- a/src/ipa/ipu3/algorithms/blc.cpp
+++ b/src/ipa/ipu3/algorithms/blc.cpp
@@ -7,8 +7,6 @@
#include "blc.h"
-#include <string.h>
-
/**
* \file blc.h
* \brief IPU3 Black Level Correction control
@@ -57,8 +55,8 @@ void BlackLevelCorrection::prepare([[maybe_unused]] IPAContext &context,
* tuning processes. This is a first rough approximation.
*/
params->obgrid_param.gr = 64;
- params->obgrid_param.r = 64;
- params->obgrid_param.b = 64;
+ params->obgrid_param.r = 64;
+ params->obgrid_param.b = 64;
params->obgrid_param.gb = 64;
/* Enable the custom black level correction processing */
diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp
index cdcdf1fb..10a8c86d 100644
--- a/src/ipa/ipu3/ipu3.cpp
+++ b/src/ipa/ipu3/ipu3.cpp
@@ -23,24 +23,22 @@
#include <libcamera/base/utils.h>
#include <libcamera/control_ids.h>
+#include <libcamera/controls.h>
#include <libcamera/framebuffer.h>
+#include <libcamera/geometry.h>
+#include <libcamera/request.h>
+
#include <libcamera/ipa/ipa_interface.h>
#include <libcamera/ipa/ipa_module_info.h>
#include <libcamera/ipa/ipu3_ipa_interface.h>
-#include <libcamera/request.h>
#include "libcamera/internal/mapped_framebuffer.h"
#include "libcamera/internal/yaml_parser.h"
-#include "algorithms/af.h"
-#include "algorithms/agc.h"
-#include "algorithms/algorithm.h"
-#include "algorithms/awb.h"
-#include "algorithms/blc.h"
-#include "algorithms/tone_mapping.h"
#include "libipa/camera_sensor_helper.h"
#include "ipa_context.h"
+#include "module.h"
/* Minimum grid width, expressed as a number of cells */
static constexpr uint32_t kMinGridWidth = 16;
@@ -313,8 +311,8 @@ int IPAIPU3::init(const IPASettings &settings,
/* Clean context */
context_.configuration = {};
- context_.configuration.sensor.lineDuration = sensorInfo.minLineLength
- * 1.0s / sensorInfo.pixelRate;
+ context_.configuration.sensor.lineDuration =
+ sensorInfo.minLineLength * 1.0s / sensorInfo.pixelRate;
/* Load the tuning data file. */
File file(settings.configurationFile);
@@ -477,8 +475,8 @@ int IPAIPU3::configure(const IPAConfigInfo &configInfo,
context_.frameContexts.clear();
/* Initialise the sensor configuration. */
- context_.configuration.sensor.lineDuration = sensorInfo_.minLineLength
- * 1.0s / sensorInfo_.pixelRate;
+ context_.configuration.sensor.lineDuration =
+ sensorInfo_.minLineLength * 1.0s / sensorInfo_.pixelRate;
context_.configuration.sensor.size = sensorInfo_.outputSize;
/*
diff --git a/src/ipa/ipu3/meson.build b/src/ipa/ipu3/meson.build
index e76f97c0..34de6213 100644
--- a/src/ipa/ipu3/meson.build
+++ b/src/ipa/ipu3/meson.build
@@ -12,8 +12,7 @@ ipu3_ipa_sources = files([
ipu3_ipa_sources += ipu3_ipa_algorithms
-mod = shared_module(ipa_name,
- [ipu3_ipa_sources, libcamera_generated_ipa_headers],
+mod = shared_module(ipa_name, ipu3_ipa_sources,
name_prefix : '',
include_directories : [ipa_includes],
dependencies : [libcamera_private, libipa_dep],
diff --git a/src/ipa/libipa/camera_sensor_helper.cpp b/src/ipa/libipa/camera_sensor_helper.cpp
index 782ff990..c6169bdc 100644
--- a/src/ipa/libipa/camera_sensor_helper.cpp
+++ b/src/ipa/libipa/camera_sensor_helper.cpp
@@ -8,6 +8,7 @@
#include "camera_sensor_helper.h"
#include <cmath>
+#include <limits>
#include <libcamera/base/log.h>
@@ -40,6 +41,7 @@ namespace ipa {
*/
/**
+ * \fn CameraSensorHelper::CameraSensorHelper()
* \brief Construct a CameraSensorHelper instance
*
* CameraSensorHelper derived class instances shall never be constructed
@@ -48,6 +50,33 @@ namespace ipa {
*/
/**
+ * \fn CameraSensorHelper::blackLevel()
+ * \brief Fetch the black level of the sensor
+ *
+ * This function returns the black level of the sensor scaled to a 16bit pixel
+ * width. If it is unknown an empty optional is returned.
+ *
+ * \todo Fill the blanks and add pedestal values for all supported sensors. Once
+ * done, drop the std::optional<>.
+ *
+ * Black levels are typically the result of the following phenomena:
+ * - Pedestal added by the sensor to pixel values. They are typically fixed,
+ * sometimes programmable and should be reported in datasheets (but
+ * documentation is not always available).
+ * - Dark currents and other physical effects that add charge to pixels in the
+ * absence of light. Those can depend on the integration time and the sensor
+ * die temperature, and their contribution to pixel values depend on the
+ * sensor gains.
+ *
+ * The pedestal is usually the value with the biggest contribution to the
+ * overall black level. In most cases it is either known before or in rare cases
+ * (there is not a single driver with such a control in the linux kernel) can be
+ * queried from the sensor. This function provides that fixed, known value.
+ *
+ * \return The black level of the sensor, or std::nullopt if not known
+ */
+
+/**
* \brief Compute gain code from the analogue gain absolute value
* \param[in] gain The real gain to pass
*
@@ -205,6 +234,12 @@ double CameraSensorHelper::gain(uint32_t gainCode) const
*/
/**
+ * \var CameraSensorHelper::blackLevel_
+ * \brief The black level of the sensor
+ * \sa CameraSensorHelper::blackLevel()
+ */
+
+/**
* \var CameraSensorHelper::gainType_
* \brief The analogue gain model type
*/
@@ -366,6 +401,99 @@ static constexpr double expGainDb(double step)
return log2_10 * step / 20;
}
+class CameraSensorHelperAr0144 : public CameraSensorHelper
+{
+public:
+ CameraSensorHelperAr0144()
+ {
+ /* Power-on default value: 168 at 12bits. */
+ blackLevel_ = 2688;
+ }
+
+ uint32_t gainCode(double gain) const override
+ {
+ /* The recommended minimum gain is 1.6842 to avoid artifacts. */
+ gain = std::clamp(gain, 1.0 / (1.0 - 13.0 / 32.0), 18.45);
+
+ /*
+ * The analogue gain is made of a coarse exponential gain in
+ * the range [2^0, 2^4] and a fine inversely linear gain in the
+ * range [1.0, 2.0[. There is an additional fixed 1.153125
+ * multiplier when the coarse gain reaches 2^2.
+ */
+
+ if (gain > 4.0)
+ gain /= 1.153125;
+
+ unsigned int coarse = std::log2(gain);
+ unsigned int fine = (1 - (1 << coarse) / gain) * 32;
+
+ /* The fine gain rounding depends on the coarse gain. */
+ if (coarse == 1 || coarse == 3)
+ fine &= ~1;
+ else if (coarse == 4)
+ fine &= ~3;
+
+ return (coarse << 4) | (fine & 0xf);
+ }
+
+ double gain(uint32_t gainCode) const override
+ {
+ unsigned int coarse = gainCode >> 4;
+ unsigned int fine = gainCode & 0xf;
+ unsigned int d1;
+ double d2, m;
+
+ switch (coarse) {
+ default:
+ case 0:
+ d1 = 1;
+ d2 = 32.0;
+ m = 1.0;
+ break;
+ case 1:
+ d1 = 2;
+ d2 = 16.0;
+ m = 1.0;
+ break;
+ case 2:
+ d1 = 1;
+ d2 = 32.0;
+ m = 1.153125;
+ break;
+ case 3:
+ d1 = 2;
+ d2 = 16.0;
+ m = 1.153125;
+ break;
+ case 4:
+ d1 = 4;
+ d2 = 8.0;
+ m = 1.153125;
+ break;
+ }
+
+ /*
+ * With infinite precision, the calculated gain would be exact,
+ * and the reverse conversion with gainCode() would produce the
+ * same gain code. In the real world, rounding errors may cause
+ * the calculated gain to be lower by an amount negligible for
+ * all purposes, except for the reverse conversion. Converting
+ * the gain to a gain code could then return the quantized value
+ * just lower than the original gain code. To avoid this, tests
+ * showed that adding the machine epsilon to the multiplier m is
+ * sufficient.
+ */
+ m += std::numeric_limits<decltype(m)>::epsilon();
+
+ return m * (1 << coarse) / (1.0 - (fine / d1) / d2);
+ }
+
+private:
+ static constexpr double kStep_ = 16;
+};
+REGISTER_CAMERA_SENSOR_HELPER("ar0144", CameraSensorHelperAr0144)
+
class CameraSensorHelperAr0521 : public CameraSensorHelper
{
public:
@@ -391,11 +519,26 @@ private:
};
REGISTER_CAMERA_SENSOR_HELPER("ar0521", CameraSensorHelperAr0521)
+class CameraSensorHelperImx214 : public CameraSensorHelper
+{
+public:
+ CameraSensorHelperImx214()
+ {
+ /* From datasheet: 64 at 10bits. */
+ blackLevel_ = 4096;
+ gainType_ = AnalogueGainLinear;
+ gainConstants_.linear = { 0, 512, -1, 512 };
+ }
+};
+REGISTER_CAMERA_SENSOR_HELPER("imx214", CameraSensorHelperImx214)
+
class CameraSensorHelperImx219 : public CameraSensorHelper
{
public:
CameraSensorHelperImx219()
{
+ /* From datasheet: 64 at 10bits. */
+ blackLevel_ = 4096;
gainType_ = AnalogueGainLinear;
gainConstants_.linear = { 0, 256, -1, 256 };
}
@@ -407,6 +550,8 @@ class CameraSensorHelperImx258 : public CameraSensorHelper
public:
CameraSensorHelperImx258()
{
+ /* From datasheet: 0x40 at 10bits. */
+ blackLevel_ = 4096;
gainType_ = AnalogueGainLinear;
gainConstants_.linear = { 0, 512, -1, 512 };
}
@@ -418,6 +563,8 @@ class CameraSensorHelperImx283 : public CameraSensorHelper
public:
CameraSensorHelperImx283()
{
+ /* From datasheet: 0x32 at 10bits. */
+ blackLevel_ = 3200;
gainType_ = AnalogueGainLinear;
gainConstants_.linear = { 0, 2048, -1, 2048 };
}
@@ -456,6 +603,8 @@ class CameraSensorHelperImx335 : public CameraSensorHelper
public:
CameraSensorHelperImx335()
{
+ /* From datasheet: 0x32 at 10bits. */
+ blackLevel_ = 3200;
gainType_ = AnalogueGainExponential;
gainConstants_.exp = { 1.0, expGainDb(0.3) };
}
@@ -515,6 +664,8 @@ class CameraSensorHelperOv4689 : public CameraSensorHelper
public:
CameraSensorHelperOv4689()
{
+ /* From datasheet: 0x40 at 12bits. */
+ blackLevel_ = 1024;
gainType_ = AnalogueGainLinear;
gainConstants_.linear = { 1, 0, 0, 128 };
}
@@ -526,6 +677,8 @@ class CameraSensorHelperOv5640 : public CameraSensorHelper
public:
CameraSensorHelperOv5640()
{
+ /* From datasheet: 0x10 at 10bits. */
+ blackLevel_ = 1024;
gainType_ = AnalogueGainLinear;
gainConstants_.linear = { 1, 0, 0, 16 };
}
@@ -559,6 +712,8 @@ class CameraSensorHelperOv5675 : public CameraSensorHelper
public:
CameraSensorHelperOv5675()
{
+ /* From Linux kernel driver: 0x40 at 10bits. */
+ blackLevel_ = 4096;
gainType_ = AnalogueGainLinear;
gainConstants_.linear = { 1, 0, 0, 128 };
}
diff --git a/src/ipa/libipa/camera_sensor_helper.h b/src/ipa/libipa/camera_sensor_helper.h
index 0d99073b..75868205 100644
--- a/src/ipa/libipa/camera_sensor_helper.h
+++ b/src/ipa/libipa/camera_sensor_helper.h
@@ -7,9 +7,9 @@
#pragma once
-#include <stdint.h>
-
#include <memory>
+#include <optional>
+#include <stdint.h>
#include <string>
#include <vector>
@@ -25,6 +25,7 @@ public:
CameraSensorHelper() = default;
virtual ~CameraSensorHelper() = default;
+ std::optional<int16_t> blackLevel() const { return blackLevel_; }
virtual uint32_t gainCode(double gain) const;
virtual double gain(uint32_t gainCode) const;
@@ -51,6 +52,7 @@ protected:
AnalogueGainExpConstants exp;
};
+ std::optional<int16_t> blackLevel_;
AnalogueGainType gainType_;
AnalogueGainConstants gainConstants_;
diff --git a/src/ipa/libipa/histogram.h b/src/ipa/libipa/histogram.h
index 032adca0..6fd64168 100644
--- a/src/ipa/libipa/histogram.h
+++ b/src/ipa/libipa/histogram.h
@@ -7,7 +7,6 @@
#pragma once
-#include <assert.h>
#include <limits.h>
#include <stdint.h>
#include <type_traits>
diff --git a/src/ipa/libipa/interpolator.cpp b/src/ipa/libipa/interpolator.cpp
new file mode 100644
index 00000000..73e8d3b7
--- /dev/null
+++ b/src/ipa/libipa/interpolator.cpp
@@ -0,0 +1,157 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>
+ *
+ * Helper class for interpolating objects
+ */
+#include "interpolator.h"
+
+#include <algorithm>
+#include <string>
+
+#include <libcamera/base/log.h>
+
+#include "libcamera/internal/yaml_parser.h"
+
+#include "interpolator.h"
+
+/**
+ * \file interpolator.h
+ * \brief Helper class for linear interpolating a set of objects
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(Interpolator)
+
+namespace ipa {
+
+/**
+ * \class Interpolator
+ * \brief Class for storing, retrieving, and interpolating objects
+ * \tparam T Type of objects stored in the interpolator
+ *
+ * The main use case is to pass a map from color temperatures to corresponding
+ * objects (eg. matrices for color correction), and then requesting a
+ * interpolated object for a specific color temperature. This class will
+ * abstract away the interpolation portion.
+ */
+
+/**
+ * \fn Interpolator::Interpolator()
+ * \brief Construct an empty interpolator
+ */
+
+/**
+ * \fn Interpolator::Interpolator(const std::map<unsigned int, T> &data)
+ * \brief Construct an interpolator from a map of objects
+ * \param data Map from which to construct the interpolator
+ */
+
+/**
+ * \fn Interpolator::Interpolator(std::map<unsigned int, T> &&data)
+ * \brief Construct an interpolator from a map of objects
+ * \param data Map from which to construct the interpolator
+ */
+
+/**
+ * \fn int Interpolator<T>::readYaml(const libcamera::YamlObject &yaml,
+ const std::string &key_name,
+ const std::string &value_name)
+ * \brief Initialize an Interpolator instance from yaml
+ * \tparam T Type of data stored in the interpolator
+ * \param[in] yaml The yaml object that contains the map of unsigned integers to
+ * objects
+ * \param[in] key_name The name of the key in the yaml object
+ * \param[in] value_name The name of the value in the yaml object
+ *
+ * The yaml object is expected to be a list of maps. Each map has two or more
+ * pairs: one of \a key_name to the key value (usually color temperature), and
+ * one or more of \a value_name to the object. This is a bit difficult to
+ * explain, so here is an example (in python, as it is easier to parse than
+ * yaml):
+ * [
+ * {
+ * 'ct': 2860,
+ * 'ccm': [ 2.12089, -0.52461, -0.59629,
+ * -0.85342, 2.80445, -0.95103,
+ * -0.26897, -1.14788, 2.41685 ],
+ * 'offsets': [ 0, 0, 0 ]
+ * },
+ *
+ * {
+ * 'ct': 2960,
+ * 'ccm': [ 2.26962, -0.54174, -0.72789,
+ * -0.77008, 2.60271, -0.83262,
+ * -0.26036, -1.51254, 2.77289 ],
+ * 'offsets': [ 0, 0, 0 ]
+ * },
+ *
+ * {
+ * 'ct': 3603,
+ * 'ccm': [ 2.18644, -0.66148, -0.52496,
+ * -0.77828, 2.69474, -0.91645,
+ * -0.25239, -0.83059, 2.08298 ],
+ * 'offsets': [ 0, 0, 0 ]
+ * },
+ * ]
+ *
+ * In this case, \a key_name would be 'ct', and \a value_name can be either
+ * 'ccm' or 'offsets'. This way multiple interpolators can be defined in
+ * one set of color temperature ranges in the tuning file, and they can be
+ * retrieved separately with the \a value_name parameter.
+ *
+ * \return Zero on success, negative error code otherwise
+ */
+
+/**
+ * \fn void Interpolator<T>::setQuantization(const unsigned int q)
+ * \brief Set the quantization value
+ * \param[in] q The quantization value
+ *
+ * Sets the quantization value. When this is set, 'key' gets quantized to this
+ * size, before doing the interpolation. This can help in reducing the number of
+ * updates pushed to the hardware.
+ *
+ * Note that normally a threshold needs to be combined with quantization.
+ * Otherwise a value that swings around the edge of the quantization step will
+ * lead to constant updates.
+ */
+
+/**
+ * \fn void Interpolator<T>::setData(std::map<unsigned int, T> &&data)
+ * \brief Set the internal map
+ *
+ * Overwrites the internal map using move semantics.
+ */
+
+/**
+ * \fn const T& Interpolator<T>::getInterpolated()
+ * \brief Retrieve an interpolated value for the given key
+ * \param[in] key The unsigned integer key of the object to retrieve
+ * \param[out] quantizedKey If provided, the key value after quantization
+ * \return The object corresponding to the key. The object is cached internally,
+ * so on successive calls with the same key (after quantization) interpolation
+ * is not recalculated.
+ */
+
+/**
+ * \fn void Interpolator<T>::interpolate(const T &a, const T &b, T &dest, double
+ * lambda)
+ * \brief Interpolate between two instances of T
+ * \param a The first value to interpolate
+ * \param b The second value to interpolate
+ * \param dest The destination for the interpolated value
+ * \param lambda The interpolation factor (0..1)
+ *
+ * Interpolates between \a a and \a b according to \a lambda. It calculates
+ * dest = a * (1.0 - lambda) + b * lambda;
+ *
+ * If T supports multiplication with double and addition, this function can be
+ * used as is. For other types this function can be overwritten using partial
+ * template specialization.
+ */
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
diff --git a/src/ipa/libipa/interpolator.h b/src/ipa/libipa/interpolator.h
new file mode 100644
index 00000000..fffce214
--- /dev/null
+++ b/src/ipa/libipa/interpolator.h
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>
+ *
+ * Helper class for interpolating maps of objects
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <cmath>
+#include <map>
+#include <string>
+#include <tuple>
+
+#include <libcamera/base/log.h>
+
+#include "libcamera/internal/yaml_parser.h"
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(Interpolator)
+
+namespace ipa {
+
+template<typename T>
+class Interpolator
+{
+public:
+ Interpolator() = default;
+ Interpolator(const std::map<unsigned int, T> &data)
+ : data_(data)
+ {
+ }
+ Interpolator(std::map<unsigned int, T> &&data)
+ : data_(std::move(data))
+ {
+ }
+
+ ~Interpolator() = default;
+
+ int readYaml(const libcamera::YamlObject &yaml,
+ const std::string &key_name,
+ const std::string &value_name)
+ {
+ data_.clear();
+ lastInterpolatedKey_.reset();
+
+ if (!yaml.isList()) {
+ LOG(Interpolator, Error) << "yaml object must be a list";
+ return -EINVAL;
+ }
+
+ for (const auto &value : yaml.asList()) {
+ unsigned int ct = std::stoul(value[key_name].get<std::string>(""));
+ std::optional<T> data =
+ value[value_name].get<T>();
+ if (!data) {
+ return -EINVAL;
+ }
+
+ data_[ct] = *data;
+ }
+
+ if (data_.size() < 1) {
+ LOG(Interpolator, Error) << "Need at least one element";
+ return -EINVAL;
+ }
+
+ return 0;
+ }
+
+ void setQuantization(const unsigned int q)
+ {
+ quantization_ = q;
+ }
+
+ void setData(std::map<unsigned int, T> &&data)
+ {
+ data_ = std::move(data);
+ lastInterpolatedKey_.reset();
+ }
+
+ const T &getInterpolated(unsigned int key, unsigned int *quantizedKey = nullptr)
+ {
+ ASSERT(data_.size() > 0);
+
+ if (quantization_ > 0)
+ key = std::lround(key / static_cast<double>(quantization_)) * quantization_;
+
+ if (quantizedKey)
+ *quantizedKey = key;
+
+ if (lastInterpolatedKey_.has_value() &&
+ *lastInterpolatedKey_ == key)
+ return lastInterpolatedValue_;
+
+ auto it = data_.lower_bound(key);
+
+ if (it == data_.begin())
+ return it->second;
+
+ if (it == data_.end())
+ return std::prev(it)->second;
+
+ if (it->first == key)
+ return it->second;
+
+ auto it2 = std::prev(it);
+ double lambda = (key - it2->first) / static_cast<double>(it->first - it2->first);
+ interpolate(it2->second, it->second, lastInterpolatedValue_, lambda);
+ lastInterpolatedKey_ = key;
+
+ return lastInterpolatedValue_;
+ }
+
+ void interpolate(const T &a, const T &b, T &dest, double lambda)
+ {
+ dest = a * (1.0 - lambda) + b * lambda;
+ }
+
+private:
+ std::map<unsigned int, T> data_;
+ T lastInterpolatedValue_;
+ std::optional<unsigned int> lastInterpolatedKey_;
+ unsigned int quantization_ = 0;
+};
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp
new file mode 100644
index 00000000..f607d86c
--- /dev/null
+++ b/src/ipa/libipa/lsc_polynomial.cpp
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Ideas On Board
+ *
+ * Polynomial class to represent lens shading correction
+ */
+
+#include "lsc_polynomial.h"
+
+#include <libcamera/base/log.h>
+
+/**
+ * \file lsc_polynomial.h
+ * \brief LscPolynomial class
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(LscPolynomial)
+
+namespace ipa {
+
+/**
+ * \class LscPolynomial
+ * \brief Class for handling even polynomials used in lens shading correction
+ *
+ * Shading artifacts of camera lenses can be modeled using even radial
+ * polynomials. This class implements a polynomial with 5 coefficients which
+ * follows the definition of the FixVignetteRadial opcode in the Adobe DNG
+ * specification.
+ */
+
+/**
+ * \fn LscPolynomial::LscPolynomial(double cx = 0.0, double cy = 0.0, double k0 = 0.0,
+ double k1 = 0.0, double k2 = 0.0, double k3 = 0.0,
+ double k4 = 0.0)
+ * \brief Construct a polynomial using the given coefficients
+ * \param cx Center-x relative to the image in normalized coordinates (0..1)
+ * \param cy Center-y relative to the image in normalized coordinates (0..1)
+ * \param k0 Coefficient of the polynomial
+ * \param k1 Coefficient of the polynomial
+ * \param k2 Coefficient of the polynomial
+ * \param k3 Coefficient of the polynomial
+ * \param k4 Coefficient of the polynomial
+ */
+
+/**
+ * \fn LscPolynomial::sampleAtNormalizedPixelPos(double x, double y)
+ * \brief Sample the polynomial at the given normalized pixel position
+ *
+ * This functions samples the polynomial at the given pixel position divided by
+ * the value returned by getM().
+ *
+ * \param x x position in normalized coordinates
+ * \param y y position in normalized coordinates
+ * \return The sampled value
+ */
+
+/**
+ * \fn LscPolynomial::getM()
+ * \brief Get the value m as described in the dng specification
+ *
+ * Returns m according to dng spec. m represents the Euclidean distance
+ * (in pixels) from the optical center to the farthest pixel in the
+ * image.
+ *
+ * \return The sampled value
+ */
+
+/**
+ * \fn LscPolynomial::setReferenceImageSize(const Size &size)
+ * \brief Set the reference image size
+ *
+ * Set the reference image size that is used for subsequent calls to getM() and
+ * sampleAtNormalizedPixelPos()
+ *
+ * \param size The size of the reference image
+ */
+
+} // namespace ipa
+} // namespace libcamera
diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h
new file mode 100644
index 00000000..c898faeb
--- /dev/null
+++ b/src/ipa/libipa/lsc_polynomial.h
@@ -0,0 +1,105 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Ideas On Board
+ *
+ * Helper for radial polynomial used in lens shading correction.
+ */
+#pragma once
+
+#include <algorithm>
+#include <array>
+#include <assert.h>
+#include <cmath>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/span.h>
+
+#include "libcamera/internal/yaml_parser.h"
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(LscPolynomial)
+
+namespace ipa {
+
+class LscPolynomial
+{
+public:
+ LscPolynomial(double cx = 0.0, double cy = 0.0, double k0 = 0.0,
+ double k1 = 0.0, double k2 = 0.0, double k3 = 0.0,
+ double k4 = 0.0)
+ : cx_(cx), cy_(cy), cnx_(0), cny_(0),
+ coefficients_({ k0, k1, k2, k3, k4 })
+ {
+ }
+
+ double sampleAtNormalizedPixelPos(double x, double y) const
+ {
+ double dx = x - cnx_;
+ double dy = y - cny_;
+ double r = sqrt(dx * dx + dy * dy);
+ double res = 1.0;
+ for (unsigned int i = 0; i < coefficients_.size(); i++) {
+ res += coefficients_[i] * std::pow(r, (i + 1) * 2);
+ }
+ return res;
+ }
+
+ double getM() const
+ {
+ double cpx = imageSize_.width * cx_;
+ double cpy = imageSize_.height * cy_;
+ double mx = std::max(cpx, std::fabs(imageSize_.width - cpx));
+ double my = std::max(cpy, std::fabs(imageSize_.height - cpy));
+
+ return sqrt(mx * mx + my * my);
+ }
+
+ void setReferenceImageSize(const Size &size)
+ {
+ assert(!size.isNull());
+ imageSize_ = size;
+
+ /* Calculate normalized centers */
+ double m = getM();
+ cnx_ = (size.width * cx_) / m;
+ cny_ = (size.height * cy_) / m;
+ }
+
+private:
+ double cx_;
+ double cy_;
+ double cnx_;
+ double cny_;
+ std::array<double, 5> coefficients_;
+
+ Size imageSize_;
+};
+
+} /* namespace ipa */
+
+#ifndef __DOXYGEN__
+
+template<>
+struct YamlObject::Getter<ipa::LscPolynomial> {
+ std::optional<ipa::LscPolynomial> get(const YamlObject &obj) const
+ {
+ std::optional<double> cx = obj["cx"].get<double>();
+ std::optional<double> cy = obj["cy"].get<double>();
+ std::optional<double> k0 = obj["k0"].get<double>();
+ std::optional<double> k1 = obj["k1"].get<double>();
+ std::optional<double> k2 = obj["k2"].get<double>();
+ std::optional<double> k3 = obj["k3"].get<double>();
+ std::optional<double> k4 = obj["k4"].get<double>();
+
+ if (!(cx && cy && k0 && k1 && k2 && k3 && k4))
+ LOG(LscPolynomial, Error)
+ << "Polynomial is missing a parameter";
+
+ return ipa::LscPolynomial(*cx, *cy, *k0, *k1, *k2, *k3, *k4);
+ }
+};
+
+#endif
+
+} /* namespace libcamera */
diff --git a/src/ipa/libipa/matrix.h b/src/ipa/libipa/matrix.h
index 8aa8f343..5471e697 100644
--- a/src/ipa/libipa/matrix.h
+++ b/src/ipa/libipa/matrix.h
@@ -7,7 +7,6 @@
#pragma once
#include <algorithm>
-#include <cmath>
#include <sstream>
#include <vector>
diff --git a/src/ipa/libipa/matrix_interpolator.cpp b/src/ipa/libipa/matrix_interpolator.cpp
deleted file mode 100644
index 04ca177f..00000000
--- a/src/ipa/libipa/matrix_interpolator.cpp
+++ /dev/null
@@ -1,110 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>
- *
- * Helper class for interpolating maps of matrices
- */
-#include "matrix_interpolator.h"
-
-#include <algorithm>
-#include <string>
-
-#include <libcamera/base/log.h>
-
-#include "libcamera/internal/yaml_parser.h"
-
-#include "matrix.h"
-
-/**
- * \file matrix_interpolator.h
- * \brief Helper class for interpolating maps of matrices
- */
-
-namespace libcamera {
-
-LOG_DEFINE_CATEGORY(MatrixInterpolator)
-
-namespace ipa {
-
-/**
- * \class MatrixInterpolator
- * \brief Class for storing, retrieving, and interpolating matrices
- * \tparam T Type of numerical values to be stored in the matrices
- * \tparam R Number of rows in the matrices
- * \tparam C Number of columns in the matrices
- *
- * The main use case is to pass a map from color temperatures to corresponding
- * matrices (eg. color correction), and then requesting a matrix for a specific
- * color temperature. This class will abstract away the interpolation portion.
- */
-
-/**
- * \fn MatrixInterpolator::MatrixInterpolator(const std::map<unsigned int, Matrix<T, R, C>> &matrices)
- * \brief Construct a matrix interpolator from a map of matrices
- * \param matrices Map from which to construct the matrix interpolator
- */
-
-/**
- * \fn MatrixInterpolator::reset()
- * \brief Reset the matrix interpolator content to a single identity matrix
- */
-
-/**
- * \fn int MatrixInterpolator<T, R, C>::readYaml()
- * \brief Initialize an MatrixInterpolator instance from yaml
- * \tparam T Type of data stored in the matrices
- * \tparam R Number of rows of the matrices
- * \tparam C Number of columns of the matrices
- * \param[in] yaml The yaml object that contains the map of unsigned integers to matrices
- * \param[in] key_name The name of the key in the yaml object
- * \param[in] matrix_name The name of the matrix in the yaml object
- *
- * The yaml object is expected to be a list of maps. Each map has two or more
- * pairs: one of \a key_name to the key value (usually color temperature), and
- * one or more of \a matrix_name to the matrix. This is a bit difficult to
- * explain, so here is an example (in python, as it is easier to parse than
- * yaml):
- * [
- * {
- * 'ct': 2860,
- * 'ccm': [ 2.12089, -0.52461, -0.59629,
- * -0.85342, 2.80445, -0.95103,
- * -0.26897, -1.14788, 2.41685 ],
- * 'offsets': [ 0, 0, 0 ]
- * },
- *
- * {
- * 'ct': 2960,
- * 'ccm': [ 2.26962, -0.54174, -0.72789,
- * -0.77008, 2.60271, -0.83262,
- * -0.26036, -1.51254, 2.77289 ],
- * 'offsets': [ 0, 0, 0 ]
- * },
- *
- * {
- * 'ct': 3603,
- * 'ccm': [ 2.18644, -0.66148, -0.52496,
- * -0.77828, 2.69474, -0.91645,
- * -0.25239, -0.83059, 2.08298 ],
- * 'offsets': [ 0, 0, 0 ]
- * },
- * ]
- *
- * In this case, \a key_name would be 'ct', and \a matrix_name can be either
- * 'ccm' or 'offsets'. This way multiple matrix interpolators can be defined in
- * one set of color temperature ranges in the tuning file, and they can be
- * retrieved separately with the \a matrix_name parameter.
- *
- * \return Zero on success, negative error code otherwise
- */
-
-/**
- * \fn Matrix<T, R, C> MatrixInterpolator<T, R, C>::get(unsigned int key)
- * \brief Retrieve a matrix from the list of matrices, interpolating if necessary
- * \param[in] key The unsigned integer key of the matrix to retrieve
- * \return The matrix corresponding to the color temperature
- */
-
-} /* namespace ipa */
-
-} /* namespace libcamera */
diff --git a/src/ipa/libipa/matrix_interpolator.h b/src/ipa/libipa/matrix_interpolator.h
deleted file mode 100644
index 087c4fd1..00000000
--- a/src/ipa/libipa/matrix_interpolator.h
+++ /dev/null
@@ -1,122 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>
- *
- * Helper class for interpolating maps of matrices
- */
-
-#pragma once
-
-#include <algorithm>
-#include <map>
-#include <string>
-#include <tuple>
-
-#include <libcamera/base/log.h>
-
-#include "libcamera/internal/yaml_parser.h"
-
-#include "matrix.h"
-
-namespace libcamera {
-
-LOG_DECLARE_CATEGORY(MatrixInterpolator)
-
-namespace ipa {
-
-#ifndef __DOXYGEN__
-template<typename T, unsigned int R, unsigned int C,
- std::enable_if_t<std::is_arithmetic_v<T>> * = nullptr>
-#else
-template<typename T, unsigned int R, unsigned int C>
-#endif /* __DOXYGEN__ */
-class MatrixInterpolator
-{
-public:
- MatrixInterpolator()
- {
- reset();
- }
-
- MatrixInterpolator(const std::map<unsigned int, Matrix<T, R, C>> &matrices)
- {
- for (const auto &pair : matrices)
- matrices_[pair.first] = pair.second;
- }
-
- ~MatrixInterpolator() {}
-
- void reset()
- {
- matrices_.clear();
- matrices_[0] = Matrix<T, R, C>::identity();
- }
-
- int readYaml(const libcamera::YamlObject &yaml,
- const std::string &key_name,
- const std::string &matrix_name)
- {
- matrices_.clear();
-
- if (!yaml.isList()) {
- LOG(MatrixInterpolator, Error) << "yaml object must be a list";
- return -EINVAL;
- }
-
- for (const auto &value : yaml.asList()) {
- unsigned int ct = std::stoul(value[key_name].get<std::string>(""));
- std::optional<Matrix<T, R, C>> matrix =
- value[matrix_name].get<Matrix<T, R, C>>();
- if (!matrix) {
- LOG(MatrixInterpolator, Error) << "Failed to read matrix";
- return -EINVAL;
- }
-
- matrices_[ct] = *matrix;
-
- LOG(MatrixInterpolator, Debug)
- << "Read matrix '" << matrix_name << "' for key '"
- << key_name << "' " << ct << ": "
- << matrices_[ct].toString();
- }
-
- if (matrices_.size() < 1) {
- LOG(MatrixInterpolator, Error) << "Need at least one matrix";
- return -EINVAL;
- }
-
- return 0;
- }
-
- Matrix<T, R, C> get(unsigned int ct)
- {
- ASSERT(matrices_.size() > 0);
-
- if (matrices_.size() == 1 ||
- ct <= matrices_.begin()->first)
- return matrices_.begin()->second;
-
- if (ct >= matrices_.rbegin()->first)
- return matrices_.rbegin()->second;
-
- if (matrices_.find(ct) != matrices_.end())
- return matrices_[ct];
-
- /* The above four guarantee that this will succeed */
- auto iter = matrices_.upper_bound(ct);
- unsigned int ctUpper = iter->first;
- unsigned int ctLower = (--iter)->first;
-
- double lambda = (ct - ctLower) / static_cast<double>(ctUpper - ctLower);
- Matrix<T, R, C> ret =
- lambda * matrices_[ctUpper] + (1.0 - lambda) * matrices_[ctLower];
- return ret;
- }
-
-private:
- std::map<unsigned int, Matrix<T, R, C>> matrices_;
-};
-
-} /* namespace ipa */
-
-} /* namespace libcamera */
diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build
index eff8ce26..e78cbcd6 100644
--- a/src/ipa/libipa/meson.build
+++ b/src/ipa/libipa/meson.build
@@ -7,8 +7,9 @@ libipa_headers = files([
'exposure_mode_helper.h',
'fc_queue.h',
'histogram.h',
+ 'interpolator.h',
+ 'lsc_polynomial.h',
'matrix.h',
- 'matrix_interpolator.h',
'module.h',
'pwl.h',
'vector.h',
@@ -21,8 +22,9 @@ libipa_sources = files([
'exposure_mode_helper.cpp',
'fc_queue.cpp',
'histogram.cpp',
+ 'interpolator.cpp',
+ 'lsc_polynomial.cpp',
'matrix.cpp',
- 'matrix_interpolator.cpp',
'module.cpp',
'pwl.cpp',
'vector.cpp',
diff --git a/src/ipa/libipa/pwl.cpp b/src/ipa/libipa/pwl.cpp
index 9b213754..88fe2022 100644
--- a/src/ipa/libipa/pwl.cpp
+++ b/src/ipa/libipa/pwl.cpp
@@ -8,10 +8,8 @@
#include "pwl.h"
-#include <assert.h>
#include <cmath>
#include <sstream>
-#include <stdexcept>
/**
* \file pwl.h
diff --git a/src/ipa/libipa/pwl.h b/src/ipa/libipa/pwl.h
index b6f93494..d4ec9f4f 100644
--- a/src/ipa/libipa/pwl.h
+++ b/src/ipa/libipa/pwl.h
@@ -7,14 +7,11 @@
#pragma once
#include <algorithm>
-#include <cmath>
#include <functional>
#include <string>
#include <utility>
#include <vector>
-#include "libcamera/internal/yaml_parser.h"
-
#include "vector.h"
namespace libcamera {
diff --git a/src/ipa/libipa/vector.h b/src/ipa/libipa/vector.h
index 556e0967..8612a06a 100644
--- a/src/ipa/libipa/vector.h
+++ b/src/ipa/libipa/vector.h
@@ -6,10 +6,10 @@
*/
#pragma once
-#include <algorithm>
#include <array>
#include <cmath>
-#include <sstream>
+#include <optional>
+#include <ostream>
#include <libcamera/base/log.h>
#include <libcamera/base/span.h>
diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
index f12f8b60..17d074d9 100644
--- a/src/ipa/rkisp1/algorithms/agc.cpp
+++ b/src/ipa/rkisp1/algorithms/agc.cpp
@@ -281,7 +281,7 @@ void Agc::queueRequest(IPAContext &context,
* \copydoc libcamera::ipa::Algorithm::prepare
*/
void Agc::prepare(IPAContext &context, const uint32_t frame,
- IPAFrameContext &frameContext, rkisp1_params_cfg *params)
+ IPAFrameContext &frameContext, RkISP1Params *params)
{
if (frameContext.agc.autoEnabled) {
frameContext.agc.exposure = context.activeState.agc.automatic.exposure;
@@ -291,41 +291,39 @@ void Agc::prepare(IPAContext &context, const uint32_t frame,
if (frame > 0 && !frameContext.agc.updateMetering)
return;
- /* Configure the measurement window. */
- params->meas.aec_config.meas_window = context.configuration.agc.measureWindow;
- /* Use a continuous method for measure. */
- params->meas.aec_config.autostop = RKISP1_CIF_ISP_EXP_CTRL_AUTOSTOP_0;
- /* Estimate Y as (R + G + B) x (85/256). */
- params->meas.aec_config.mode = RKISP1_CIF_ISP_EXP_MEASURING_MODE_1;
+ /*
+ * Configure the AEC measurements. Set the window, measure
+ * continuously, and estimate Y as (R + G + B) x (85/256).
+ */
+ auto aecConfig = params->block<BlockType::Aec>();
+ aecConfig.setEnabled(true);
+
+ aecConfig->meas_window = context.configuration.agc.measureWindow;
+ aecConfig->autostop = RKISP1_CIF_ISP_EXP_CTRL_AUTOSTOP_0;
+ aecConfig->mode = RKISP1_CIF_ISP_EXP_MEASURING_MODE_1;
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_AEC;
- params->module_ens |= RKISP1_CIF_ISP_MODULE_AEC;
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_AEC;
+ /*
+ * Configure the histogram measurement. Set the window, produce a
+ * luminance histogram, and set the weights and predivider.
+ */
+ auto hstConfig = params->block<BlockType::Hst>();
+ hstConfig.setEnabled(true);
- /* Configure histogram. */
- params->meas.hst_config.meas_window = context.configuration.agc.measureWindow;
- /* Produce the luminance histogram. */
- params->meas.hst_config.mode = RKISP1_CIF_ISP_HISTOGRAM_MODE_Y_HISTOGRAM;
+ hstConfig->meas_window = context.configuration.agc.measureWindow;
+ hstConfig->mode = RKISP1_CIF_ISP_HISTOGRAM_MODE_Y_HISTOGRAM;
- /* Set an average weighted histogram. */
Span<uint8_t> weights{
- params->meas.hst_config.hist_weight,
+ hstConfig->hist_weight,
context.hw->numHistogramWeights
};
std::vector<uint8_t> &modeWeights = meteringModes_.at(frameContext.agc.meteringMode);
std::copy(modeWeights.begin(), modeWeights.end(), weights.begin());
- struct rkisp1_cif_isp_window window = params->meas.hst_config.meas_window;
+ struct rkisp1_cif_isp_window window = hstConfig->meas_window;
Size windowSize = { window.h_size, window.v_size };
- params->meas.hst_config.histogram_predivider =
+ hstConfig->histogram_predivider =
computeHistogramPredivider(windowSize,
- static_cast<rkisp1_cif_isp_histogram_mode>(params->meas.hst_config.mode));
-
- /* Update the configuration for histogram. */
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_HST;
- /* Enable the histogram measure unit. */
- params->module_ens |= RKISP1_CIF_ISP_MODULE_HST;
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_HST;
+ static_cast<rkisp1_cif_isp_histogram_mode>(hstConfig->mode));
}
void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext,
diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h
index 9ceaa82b..aa86f2c5 100644
--- a/src/ipa/rkisp1/algorithms/agc.h
+++ b/src/ipa/rkisp1/algorithms/agc.h
@@ -15,7 +15,6 @@
#include <libcamera/geometry.h>
#include "libipa/agc_mean_luminance.h"
-#include "libipa/histogram.h"
#include "algorithm.h"
@@ -37,7 +36,7 @@ public:
const ControlList &controls) override;
void prepare(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params) override;
+ RkISP1Params *params) override;
void process(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
const rkisp1_stat_buffer *stats,
diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp
index a01fe5d9..955a9ff4 100644
--- a/src/ipa/rkisp1/algorithms/awb.cpp
+++ b/src/ipa/rkisp1/algorithms/awb.cpp
@@ -8,12 +8,12 @@
#include "awb.h"
#include <algorithm>
-#include <cmath>
-#include <iomanip>
+#include <ios>
#include <libcamera/base/log.h>
#include <libcamera/control_ids.h>
+
#include <libcamera/ipa/core_ipa_interface.h>
/**
@@ -108,7 +108,7 @@ void Awb::queueRequest(IPAContext &context,
* \copydoc libcamera::ipa::Algorithm::prepare
*/
void Awb::prepare(IPAContext &context, const uint32_t frame,
- IPAFrameContext &frameContext, rkisp1_params_cfg *params)
+ IPAFrameContext &frameContext, RkISP1Params *params)
{
/*
* This is the latest time we can read the active state. This is the
@@ -120,29 +120,30 @@ void Awb::prepare(IPAContext &context, const uint32_t frame,
frameContext.awb.gains.blue = context.activeState.awb.gains.automatic.blue;
}
- params->others.awb_gain_config.gain_green_b = 256 * frameContext.awb.gains.green;
- params->others.awb_gain_config.gain_blue = 256 * frameContext.awb.gains.blue;
- params->others.awb_gain_config.gain_red = 256 * frameContext.awb.gains.red;
- params->others.awb_gain_config.gain_green_r = 256 * frameContext.awb.gains.green;
+ auto gainConfig = params->block<BlockType::AwbGain>();
+ gainConfig.setEnabled(true);
- /* Update the gains. */
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_AWB_GAIN;
+ gainConfig->gain_green_b = std::clamp<int>(256 * frameContext.awb.gains.green, 0, 0x3ff);
+ gainConfig->gain_blue = std::clamp<int>(256 * frameContext.awb.gains.blue, 0, 0x3ff);
+ gainConfig->gain_red = std::clamp<int>(256 * frameContext.awb.gains.red, 0, 0x3ff);
+ gainConfig->gain_green_r = std::clamp<int>(256 * frameContext.awb.gains.green, 0, 0x3ff);
/* If we have already set the AWB measurement parameters, return. */
if (frame > 0)
return;
- rkisp1_cif_isp_awb_meas_config &awb_config = params->meas.awb_meas_config;
+ auto awbConfig = params->block<BlockType::Awb>();
+ awbConfig.setEnabled(true);
/* Configure the measure window for AWB. */
- awb_config.awb_wnd = context.configuration.awb.measureWindow;
+ awbConfig->awb_wnd = context.configuration.awb.measureWindow;
/* Number of frames to use to estimate the means (0 means 1 frame). */
- awb_config.frames = 0;
+ awbConfig->frames = 0;
/* Select RGB or YCbCr means measurement. */
if (rgbMode_) {
- awb_config.awb_mode = RKISP1_CIF_ISP_AWB_MODE_RGB;
+ awbConfig->awb_mode = RKISP1_CIF_ISP_AWB_MODE_RGB;
/*
* For RGB-based measurements, pixels are selected with maximum
@@ -150,19 +151,19 @@ void Awb::prepare(IPAContext &context, const uint32_t frame,
* awb_ref_cr, awb_min_y and awb_ref_cb respectively. The other
* values are not used, set them to 0.
*/
- awb_config.awb_ref_cr = 250;
- awb_config.min_y = 250;
- awb_config.awb_ref_cb = 250;
+ awbConfig->awb_ref_cr = 250;
+ awbConfig->min_y = 250;
+ awbConfig->awb_ref_cb = 250;
- awb_config.max_y = 0;
- awb_config.min_c = 0;
- awb_config.max_csum = 0;
+ awbConfig->max_y = 0;
+ awbConfig->min_c = 0;
+ awbConfig->max_csum = 0;
} else {
- awb_config.awb_mode = RKISP1_CIF_ISP_AWB_MODE_YCBCR;
+ awbConfig->awb_mode = RKISP1_CIF_ISP_AWB_MODE_YCBCR;
/* Set the reference Cr and Cb (AWB target) to white. */
- awb_config.awb_ref_cb = 128;
- awb_config.awb_ref_cr = 128;
+ awbConfig->awb_ref_cb = 128;
+ awbConfig->awb_ref_cr = 128;
/*
* Filter out pixels based on luminance and chrominance values.
@@ -170,20 +171,11 @@ void Awb::prepare(IPAContext &context, const uint32_t frame,
* range, while the acceptable chroma values are specified with
* a minimum of 16 and a maximum Cb+Cr sum of 250.
*/
- awb_config.min_y = 16;
- awb_config.max_y = 250;
- awb_config.min_c = 16;
- awb_config.max_csum = 250;
+ awbConfig->min_y = 16;
+ awbConfig->max_y = 250;
+ awbConfig->min_c = 16;
+ awbConfig->max_csum = 250;
}
-
- /* Enable the AWB gains. */
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_AWB_GAIN;
- params->module_ens |= RKISP1_CIF_ISP_MODULE_AWB_GAIN;
-
- /* Update the AWB measurement parameters and enable the AWB module. */
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_AWB;
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_AWB;
- params->module_ens |= RKISP1_CIF_ISP_MODULE_AWB;
}
uint32_t Awb::estimateCCT(double red, double green, double blue)
@@ -218,6 +210,12 @@ void Awb::process(IPAContext &context,
double redMean;
double blueMean;
+ metadata.set(controls::AwbEnable, frameContext.awb.autoEnabled);
+ metadata.set(controls::ColourGains, {
+ static_cast<float>(frameContext.awb.gains.red),
+ static_cast<float>(frameContext.awb.gains.blue)
+ });
+
if (rgbMode_) {
greenMean = awb->awb_mean[0].mean_y_or_g;
redMean = awb->awb_mean[0].mean_cr_or_r;
@@ -273,12 +271,15 @@ void Awb::process(IPAContext &context,
*/
if (redMean < kMeanMinThreshold && greenMean < kMeanMinThreshold &&
blueMean < kMeanMinThreshold) {
- frameContext.awb.temperatureK = activeState.awb.temperatureK;
+ metadata.set(controls::ColourTemperature, activeState.awb.temperatureK);
return;
}
activeState.awb.temperatureK = estimateCCT(redMean, greenMean, blueMean);
+ /* Metadata shall contain the up to date measurement */
+ metadata.set(controls::ColourTemperature, activeState.awb.temperatureK);
+
/*
* Estimate the red and blue gains to apply in a grey world. The green
* gain is hardcoded to 1.0. Avoid divisions by zero by clamping the
@@ -305,21 +306,13 @@ void Awb::process(IPAContext &context,
activeState.awb.gains.automatic.blue = blueGain;
activeState.awb.gains.automatic.green = 1.0;
- frameContext.awb.temperatureK = activeState.awb.temperatureK;
-
- metadata.set(controls::AwbEnable, frameContext.awb.autoEnabled);
- metadata.set(controls::ColourGains, {
- static_cast<float>(frameContext.awb.gains.red),
- static_cast<float>(frameContext.awb.gains.blue)
- });
- metadata.set(controls::ColourTemperature, frameContext.awb.temperatureK);
-
- LOG(RkISP1Awb, Debug) << std::showpoint
+ LOG(RkISP1Awb, Debug)
+ << std::showpoint
<< "Means [" << redMean << ", " << greenMean << ", " << blueMean
<< "], gains [" << activeState.awb.gains.automatic.red << ", "
<< activeState.awb.gains.automatic.green << ", "
<< activeState.awb.gains.automatic.blue << "], temp "
- << frameContext.awb.temperatureK << "K";
+ << activeState.awb.temperatureK << "K";
}
REGISTER_IPA_ALGORITHM(Awb, "Awb")
diff --git a/src/ipa/rkisp1/algorithms/awb.h b/src/ipa/rkisp1/algorithms/awb.h
index 06c92896..b3b2c0bb 100644
--- a/src/ipa/rkisp1/algorithms/awb.h
+++ b/src/ipa/rkisp1/algorithms/awb.h
@@ -25,7 +25,7 @@ public:
const ControlList &controls) override;
void prepare(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params) override;
+ RkISP1Params *params) override;
void process(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
const rkisp1_stat_buffer *stats,
diff --git a/src/ipa/rkisp1/algorithms/blc.cpp b/src/ipa/rkisp1/algorithms/blc.cpp
index d2e74354..98cb7145 100644
--- a/src/ipa/rkisp1/algorithms/blc.cpp
+++ b/src/ipa/rkisp1/algorithms/blc.cpp
@@ -7,8 +7,12 @@
#include "blc.h"
+#include <linux/videodev2.h>
+
#include <libcamera/base/log.h>
+#include <libcamera/control_ids.h>
+
#include "libcamera/internal/yaml_parser.h"
/**
@@ -36,22 +40,62 @@ namespace ipa::rkisp1::algorithms {
LOG_DEFINE_CATEGORY(RkISP1Blc)
BlackLevelCorrection::BlackLevelCorrection()
- : tuningParameters_(false)
{
+ /*
+ * This is a bit of a hack. In raw mode no black level correction
+ * happens. This flag is used to ensure the metadata gets populated with
+ * the black level which is needed to capture proper raw images for
+ * tuning.
+ */
+ supportsRaw_ = true;
}
/**
* \copydoc libcamera::ipa::Algorithm::init
*/
-int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context,
- const YamlObject &tuningData)
+int BlackLevelCorrection::init(IPAContext &context, const YamlObject &tuningData)
{
- blackLevelRed_ = tuningData["R"].get<int16_t>(256);
- blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
- blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
- blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
-
- tuningParameters_ = true;
+ std::optional<int16_t> levelRed = tuningData["R"].get<int16_t>();
+ std::optional<int16_t> levelGreenR = tuningData["Gr"].get<int16_t>();
+ std::optional<int16_t> levelGreenB = tuningData["Gb"].get<int16_t>();
+ std::optional<int16_t> levelBlue = tuningData["B"].get<int16_t>();
+ bool tuningHasLevels = levelRed && levelGreenR && levelGreenB && levelBlue;
+
+ auto blackLevel = context.camHelper->blackLevel();
+ if (!blackLevel) {
+ /*
+ * Not all camera sensor helpers have been updated with black
+ * levels. Print a warning and fall back to the levels from the
+ * tuning data to preserve backward compatibility. This should
+ * be removed once all helpers provide the data.
+ */
+ LOG(RkISP1Blc, Warning)
+ << "No black levels provided by camera sensor helper"
+ << ", please fix";
+
+ blackLevelRed_ = levelRed.value_or(4096);
+ blackLevelGreenR_ = levelGreenR.value_or(4096);
+ blackLevelGreenB_ = levelGreenB.value_or(4096);
+ blackLevelBlue_ = levelBlue.value_or(4096);
+ } else if (tuningHasLevels) {
+ /*
+ * If black levels are provided in the tuning file, use them to
+ * avoid breaking existing camera tuning. This is deprecated and
+ * will be removed.
+ */
+ LOG(RkISP1Blc, Warning)
+ << "Deprecated: black levels overwritten by tuning file";
+
+ blackLevelRed_ = *levelRed;
+ blackLevelGreenR_ = *levelGreenR;
+ blackLevelGreenB_ = *levelGreenB;
+ blackLevelBlue_ = *levelBlue;
+ } else {
+ blackLevelRed_ = *blackLevel;
+ blackLevelGreenR_ = *blackLevel;
+ blackLevelGreenB_ = *blackLevel;
+ blackLevelBlue_ = *blackLevel;
+ }
LOG(RkISP1Blc, Debug)
<< "Black levels: red " << blackLevelRed_
@@ -62,29 +106,80 @@ int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context,
return 0;
}
+int BlackLevelCorrection::configure(IPAContext &context,
+ [[maybe_unused]] const IPACameraSensorInfo &configInfo)
+{
+ /*
+ * BLC on ISP versions that include the companding block requires usage
+ * of the extensible parameters format.
+ */
+ supported_ = context.configuration.paramFormat == V4L2_META_FMT_RK_ISP1_EXT_PARAMS ||
+ !context.hw->compand;
+
+ if (!supported_)
+ LOG(RkISP1Blc, Warning)
+ << "BLC in companding block requires extensible parameters";
+
+ return 0;
+}
+
/**
* \copydoc libcamera::ipa::Algorithm::prepare
*/
-void BlackLevelCorrection::prepare([[maybe_unused]] IPAContext &context,
+void BlackLevelCorrection::prepare(IPAContext &context,
const uint32_t frame,
[[maybe_unused]] IPAFrameContext &frameContext,
- rkisp1_params_cfg *params)
+ RkISP1Params *params)
{
+ if (context.configuration.raw)
+ return;
+
if (frame > 0)
return;
- if (!tuningParameters_)
+ if (!supported_)
return;
- params->others.bls_config.enable_auto = 0;
- params->others.bls_config.fixed_val.r = blackLevelRed_;
- params->others.bls_config.fixed_val.gr = blackLevelGreenR_;
- params->others.bls_config.fixed_val.gb = blackLevelGreenB_;
- params->others.bls_config.fixed_val.b = blackLevelBlue_;
+ if (context.hw->compand) {
+ auto config = params->block<BlockType::CompandBls>();
+ config.setEnabled(true);
+
+ /*
+ * Scale up to the 20-bit black levels used by the companding
+ * block.
+ */
+ config->r = blackLevelRed_ << 4;
+ config->gr = blackLevelGreenR_ << 4;
+ config->gb = blackLevelGreenB_ << 4;
+ config->b = blackLevelBlue_ << 4;
+ } else {
+ auto config = params->block<BlockType::Bls>();
+ config.setEnabled(true);
+
+ config->enable_auto = 0;
+
+ /* Scale down to the 12-bit black levels used by the BLS block. */
+ config->fixed_val.r = blackLevelRed_ >> 4;
+ config->fixed_val.gr = blackLevelGreenR_ >> 4;
+ config->fixed_val.gb = blackLevelGreenB_ >> 4;
+ config->fixed_val.b = blackLevelBlue_ >> 4;
+ }
+}
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_BLS;
- params->module_ens |= RKISP1_CIF_ISP_MODULE_BLS;
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_BLS;
+/**
+ * \copydoc libcamera::ipa::Algorithm::process
+ */
+void BlackLevelCorrection::process([[maybe_unused]] IPAContext &context,
+ [[maybe_unused]] const uint32_t frame,
+ [[maybe_unused]] IPAFrameContext &frameContext,
+ [[maybe_unused]] const rkisp1_stat_buffer *stats,
+ ControlList &metadata)
+{
+ metadata.set(controls::SensorBlackLevels,
+ { static_cast<int32_t>(blackLevelRed_),
+ static_cast<int32_t>(blackLevelGreenR_),
+ static_cast<int32_t>(blackLevelGreenB_),
+ static_cast<int32_t>(blackLevelBlue_) });
}
REGISTER_IPA_ALGORITHM(BlackLevelCorrection, "BlackLevelCorrection")
diff --git a/src/ipa/rkisp1/algorithms/blc.h b/src/ipa/rkisp1/algorithms/blc.h
index 460ebcc1..f797ae44 100644
--- a/src/ipa/rkisp1/algorithms/blc.h
+++ b/src/ipa/rkisp1/algorithms/blc.h
@@ -20,12 +20,19 @@ public:
~BlackLevelCorrection() = default;
int init(IPAContext &context, const YamlObject &tuningData) override;
+ int configure(IPAContext &context,
+ const IPACameraSensorInfo &configInfo) override;
void prepare(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params) override;
+ RkISP1Params *params) override;
+ void process(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const rkisp1_stat_buffer *stats,
+ ControlList &metadata) override;
private:
- bool tuningParameters_;
+ bool supported_;
+
int16_t blackLevelRed_;
int16_t blackLevelGreenR_;
int16_t blackLevelGreenB_;
diff --git a/src/ipa/rkisp1/algorithms/ccm.cpp b/src/ipa/rkisp1/algorithms/ccm.cpp
index c1f5403a..6b7d2e2c 100644
--- a/src/ipa/rkisp1/algorithms/ccm.cpp
+++ b/src/ipa/rkisp1/algorithms/ccm.cpp
@@ -7,11 +7,7 @@
#include "ccm.h"
-#include <algorithm>
-#include <chrono>
-#include <cmath>
-#include <tuple>
-#include <vector>
+#include <map>
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>
@@ -23,7 +19,7 @@
#include "libcamera/internal/yaml_parser.h"
#include "../utils.h"
-#include "libipa/matrix_interpolator.h"
+#include "libipa/interpolator.h"
/**
* \file ccm.h
@@ -50,7 +46,7 @@ int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData
LOG(RkISP1Ccm, Warning)
<< "Failed to parse 'ccm' "
<< "parameter from tuning file; falling back to unit matrix";
- ccm_.reset();
+ ccm_.setData({ { 0, Matrix<float, 3, 3>::identity() } });
}
ret = offsets_.readYaml(tuningData["ccms"], "ct", "offsets");
@@ -58,25 +54,17 @@ int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData
LOG(RkISP1Ccm, Warning)
<< "Failed to parse 'offsets' "
<< "parameter from tuning file; falling back to zero offsets";
- /*
- * MatrixInterpolator::reset() resets to identity matrices
- * while here we need zero matrices so we need to construct it
- * ourselves.
- */
- Matrix<int16_t, 3, 1> m({ 0, 0, 0 });
- std::map<unsigned int, Matrix<int16_t, 3, 1>> matrices = { { 0, m } };
- offsets_ = MatrixInterpolator<int16_t, 3, 1>(matrices);
+
+ offsets_.setData({ { 0, Matrix<int16_t, 3, 1>({ 0, 0, 0 }) } });
}
return 0;
}
-void Ccm::setParameters(rkisp1_params_cfg *params,
+void Ccm::setParameters(struct rkisp1_cif_isp_ctk_config &config,
const Matrix<float, 3, 3> &matrix,
const Matrix<int16_t, 3, 1> &offsets)
{
- struct rkisp1_cif_isp_ctk_config &config = params->others.ctk_config;
-
/*
* 4 bit integer and 7 bit fractional, ranging from -8 (0x400) to
* +7.992 (0x3ff)
@@ -92,18 +80,13 @@ void Ccm::setParameters(rkisp1_params_cfg *params,
LOG(RkISP1Ccm, Debug) << "Setting matrix " << matrix;
LOG(RkISP1Ccm, Debug) << "Setting offsets " << offsets;
-
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_CTK;
- params->module_ens |= RKISP1_CIF_ISP_MODULE_CTK;
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_CTK;
}
/**
* \copydoc libcamera::ipa::Algorithm::prepare
*/
void Ccm::prepare(IPAContext &context, const uint32_t frame,
- IPAFrameContext &frameContext,
- rkisp1_params_cfg *params)
+ IPAFrameContext &frameContext, RkISP1Params *params)
{
uint32_t ct = context.activeState.awb.temperatureK;
@@ -111,16 +94,21 @@ void Ccm::prepare(IPAContext &context, const uint32_t frame,
* \todo The colour temperature will likely be noisy, add filtering to
* avoid updating the CCM matrix all the time.
*/
- if (frame > 0 && ct == ct_)
+ if (frame > 0 && ct == ct_) {
+ frameContext.ccm.ccm = context.activeState.ccm.ccm;
return;
+ }
ct_ = ct;
- Matrix<float, 3, 3> ccm = ccm_.get(ct);
- Matrix<int16_t, 3, 1> offsets = offsets_.get(ct);
+ Matrix<float, 3, 3> ccm = ccm_.getInterpolated(ct);
+ Matrix<int16_t, 3, 1> offsets = offsets_.getInterpolated(ct);
+ context.activeState.ccm.ccm = ccm;
frameContext.ccm.ccm = ccm;
- setParameters(params, ccm, offsets);
+ auto config = params->block<BlockType::Ctk>();
+ config.setEnabled(true);
+ setParameters(*config, ccm, offsets);
}
/**
@@ -135,7 +123,7 @@ void Ccm::process([[maybe_unused]] IPAContext &context,
float m[9];
for (unsigned int i = 0; i < 3; i++) {
for (unsigned int j = 0; j < 3; j++)
- m[i] = frameContext.ccm.ccm[i][j];
+ m[i * 3 + j] = frameContext.ccm.ccm[i][j];
}
metadata.set(controls::ColourCorrectionMatrix, m);
}
diff --git a/src/ipa/rkisp1/algorithms/ccm.h b/src/ipa/rkisp1/algorithms/ccm.h
index 30cb8821..46a1416e 100644
--- a/src/ipa/rkisp1/algorithms/ccm.h
+++ b/src/ipa/rkisp1/algorithms/ccm.h
@@ -9,8 +9,8 @@
#include <linux/rkisp1-config.h>
+#include "libipa/interpolator.h"
#include "libipa/matrix.h"
-#include "libipa/matrix_interpolator.h"
#include "algorithm.h"
@@ -27,7 +27,7 @@ public:
int init(IPAContext &context, const YamlObject &tuningData) override;
void prepare(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params) override;
+ RkISP1Params *params) override;
void process(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
const rkisp1_stat_buffer *stats,
@@ -35,13 +35,13 @@ public:
private:
void parseYaml(const YamlObject &tuningData);
- void setParameters(rkisp1_params_cfg *params,
+ void setParameters(struct rkisp1_cif_isp_ctk_config &config,
const Matrix<float, 3, 3> &matrix,
const Matrix<int16_t, 3, 1> &offsets);
unsigned int ct_;
- MatrixInterpolator<float, 3, 3> ccm_;
- MatrixInterpolator<int16_t, 3, 1> offsets_;
+ Interpolator<Matrix<float, 3, 3>> ccm_;
+ Interpolator<Matrix<int16_t, 3, 1>> offsets_;
};
} /* namespace ipa::rkisp1::algorithms */
diff --git a/src/ipa/rkisp1/algorithms/cproc.cpp b/src/ipa/rkisp1/algorithms/cproc.cpp
index ef0931b2..d1fff699 100644
--- a/src/ipa/rkisp1/algorithms/cproc.cpp
+++ b/src/ipa/rkisp1/algorithms/cproc.cpp
@@ -140,19 +140,17 @@ void ColorProcessing::queueRequest(IPAContext &context,
void ColorProcessing::prepare([[maybe_unused]] IPAContext &context,
[[maybe_unused]] const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params)
+ RkISP1Params *params)
{
/* Check if the algorithm configuration has been updated. */
if (!frameContext.cproc.update)
return;
- params->others.cproc_config.brightness = frameContext.cproc.brightness;
- params->others.cproc_config.contrast = frameContext.cproc.contrast;
- params->others.cproc_config.sat = frameContext.cproc.saturation;
-
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_CPROC;
- params->module_ens |= RKISP1_CIF_ISP_MODULE_CPROC;
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_CPROC;
+ auto config = params->block<BlockType::Cproc>();
+ config.setEnabled(true);
+ config->brightness = frameContext.cproc.brightness;
+ config->contrast = frameContext.cproc.contrast;
+ config->sat = frameContext.cproc.saturation;
}
REGISTER_IPA_ALGORITHM(ColorProcessing, "ColorProcessing")
diff --git a/src/ipa/rkisp1/algorithms/cproc.h b/src/ipa/rkisp1/algorithms/cproc.h
index e50e7200..fd38fd17 100644
--- a/src/ipa/rkisp1/algorithms/cproc.h
+++ b/src/ipa/rkisp1/algorithms/cproc.h
@@ -29,7 +29,7 @@ public:
const ControlList &controls) override;
void prepare(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params) override;
+ RkISP1Params *params) override;
};
} /* namespace ipa::rkisp1::algorithms */
diff --git a/src/ipa/rkisp1/algorithms/dpcc.cpp b/src/ipa/rkisp1/algorithms/dpcc.cpp
index b5a339e9..78946281 100644
--- a/src/ipa/rkisp1/algorithms/dpcc.cpp
+++ b/src/ipa/rkisp1/algorithms/dpcc.cpp
@@ -232,16 +232,14 @@ int DefectPixelClusterCorrection::init([[maybe_unused]] IPAContext &context,
void DefectPixelClusterCorrection::prepare([[maybe_unused]] IPAContext &context,
const uint32_t frame,
[[maybe_unused]] IPAFrameContext &frameContext,
- rkisp1_params_cfg *params)
+ RkISP1Params *params)
{
if (frame > 0)
return;
- params->others.dpcc_config = config_;
-
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_DPCC;
- params->module_ens |= RKISP1_CIF_ISP_MODULE_DPCC;
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_DPCC;
+ auto config = params->block<BlockType::Dpcc>();
+ config.setEnabled(true);
+ *config = config_;
}
REGISTER_IPA_ALGORITHM(DefectPixelClusterCorrection, "DefectPixelClusterCorrection")
diff --git a/src/ipa/rkisp1/algorithms/dpcc.h b/src/ipa/rkisp1/algorithms/dpcc.h
index d39b7bed..b77766c3 100644
--- a/src/ipa/rkisp1/algorithms/dpcc.h
+++ b/src/ipa/rkisp1/algorithms/dpcc.h
@@ -22,7 +22,7 @@ public:
int init(IPAContext &context, const YamlObject &tuningData) override;
void prepare(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params) override;
+ RkISP1Params *params) override;
private:
rkisp1_cif_isp_dpcc_config config_;
diff --git a/src/ipa/rkisp1/algorithms/dpf.cpp b/src/ipa/rkisp1/algorithms/dpf.cpp
index abf95728..cb6095da 100644
--- a/src/ipa/rkisp1/algorithms/dpf.cpp
+++ b/src/ipa/rkisp1/algorithms/dpf.cpp
@@ -7,7 +7,9 @@
#include "dpf.h"
-#include <cmath>
+#include <algorithm>
+#include <string>
+#include <vector>
#include <libcamera/base/log.h>
@@ -215,15 +217,21 @@ void Dpf::queueRequest(IPAContext &context,
* \copydoc libcamera::ipa::Algorithm::prepare
*/
void Dpf::prepare(IPAContext &context, const uint32_t frame,
- IPAFrameContext &frameContext, rkisp1_params_cfg *params)
+ IPAFrameContext &frameContext, RkISP1Params *params)
{
- if (frame == 0) {
- params->others.dpf_config = config_;
- params->others.dpf_strength_config = strengthConfig_;
+ if (!frameContext.dpf.update && frame > 0)
+ return;
+
+ auto config = params->block<BlockType::Dpf>();
+ config.setEnabled(frameContext.dpf.denoise);
+
+ if (frameContext.dpf.denoise) {
+ *config = config_;
const auto &awb = context.configuration.awb;
const auto &lsc = context.configuration.lsc;
- auto &mode = params->others.dpf_config.gain.mode;
+
+ auto &mode = config->gain.mode;
/*
* The DPF needs to take into account the total amount of
@@ -241,15 +249,12 @@ void Dpf::prepare(IPAContext &context, const uint32_t frame,
mode = RKISP1_CIF_ISP_DPF_GAIN_USAGE_LSC_GAINS;
else
mode = RKISP1_CIF_ISP_DPF_GAIN_USAGE_DISABLED;
-
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_DPF |
- RKISP1_CIF_ISP_MODULE_DPF_STRENGTH;
}
- if (frameContext.dpf.update) {
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_DPF;
- if (frameContext.dpf.denoise)
- params->module_ens |= RKISP1_CIF_ISP_MODULE_DPF;
+ if (frame == 0) {
+ auto strengthConfig = params->block<BlockType::DpfStrength>();
+ strengthConfig.setEnabled(true);
+ *strengthConfig = strengthConfig_;
}
}
diff --git a/src/ipa/rkisp1/algorithms/dpf.h b/src/ipa/rkisp1/algorithms/dpf.h
index da0115ba..2dd8cd36 100644
--- a/src/ipa/rkisp1/algorithms/dpf.h
+++ b/src/ipa/rkisp1/algorithms/dpf.h
@@ -27,7 +27,7 @@ public:
const ControlList &controls) override;
void prepare(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params) override;
+ RkISP1Params *params) override;
private:
struct rkisp1_cif_isp_dpf_config config_;
diff --git a/src/ipa/rkisp1/algorithms/filter.cpp b/src/ipa/rkisp1/algorithms/filter.cpp
index 9752248a..7598ef8a 100644
--- a/src/ipa/rkisp1/algorithms/filter.cpp
+++ b/src/ipa/rkisp1/algorithms/filter.cpp
@@ -104,7 +104,7 @@ void Filter::queueRequest(IPAContext &context,
*/
void Filter::prepare([[maybe_unused]] IPAContext &context,
[[maybe_unused]] const uint32_t frame,
- IPAFrameContext &frameContext, rkisp1_params_cfg *params)
+ IPAFrameContext &frameContext, RkISP1Params *params)
{
/* Check if the algorithm configuration has been updated. */
if (!frameContext.filter.update)
@@ -160,23 +160,25 @@ void Filter::prepare([[maybe_unused]] IPAContext &context,
uint8_t denoise = frameContext.filter.denoise;
uint8_t sharpness = frameContext.filter.sharpness;
- auto &flt_config = params->others.flt_config;
-
- flt_config.fac_sh0 = filt_fac_sh0[sharpness];
- flt_config.fac_sh1 = filt_fac_sh1[sharpness];
- flt_config.fac_mid = filt_fac_mid[sharpness];
- flt_config.fac_bl0 = filt_fac_bl0[sharpness];
- flt_config.fac_bl1 = filt_fac_bl1[sharpness];
-
- flt_config.lum_weight = kFiltLumWeightDefault;
- flt_config.mode = kFiltModeDefault;
- flt_config.thresh_sh0 = filt_thresh_sh0[denoise];
- flt_config.thresh_sh1 = filt_thresh_sh1[denoise];
- flt_config.thresh_bl0 = filt_thresh_bl0[denoise];
- flt_config.thresh_bl1 = filt_thresh_bl1[denoise];
- flt_config.grn_stage1 = stage1_select[denoise];
- flt_config.chr_v_mode = filt_chr_v_mode[denoise];
- flt_config.chr_h_mode = filt_chr_h_mode[denoise];
+
+ auto config = params->block<BlockType::Flt>();
+ config.setEnabled(true);
+
+ config->fac_sh0 = filt_fac_sh0[sharpness];
+ config->fac_sh1 = filt_fac_sh1[sharpness];
+ config->fac_mid = filt_fac_mid[sharpness];
+ config->fac_bl0 = filt_fac_bl0[sharpness];
+ config->fac_bl1 = filt_fac_bl1[sharpness];
+
+ config->lum_weight = kFiltLumWeightDefault;
+ config->mode = kFiltModeDefault;
+ config->thresh_sh0 = filt_thresh_sh0[denoise];
+ config->thresh_sh1 = filt_thresh_sh1[denoise];
+ config->thresh_bl0 = filt_thresh_bl0[denoise];
+ config->thresh_bl1 = filt_thresh_bl1[denoise];
+ config->grn_stage1 = stage1_select[denoise];
+ config->chr_v_mode = filt_chr_v_mode[denoise];
+ config->chr_h_mode = filt_chr_h_mode[denoise];
/*
* Combined high denoising and high sharpening requires some
@@ -186,27 +188,23 @@ void Filter::prepare([[maybe_unused]] IPAContext &context,
*/
if (denoise == 9) {
if (sharpness > 3)
- flt_config.grn_stage1 = 2;
+ config->grn_stage1 = 2;
} else if (denoise == 10) {
if (sharpness > 5)
- flt_config.grn_stage1 = 2;
+ config->grn_stage1 = 2;
else if (sharpness > 3)
- flt_config.grn_stage1 = 1;
+ config->grn_stage1 = 1;
}
if (denoise > 7) {
if (sharpness > 7) {
- flt_config.fac_bl0 /= 2;
- flt_config.fac_bl1 /= 4;
+ config->fac_bl0 /= 2;
+ config->fac_bl1 /= 4;
} else if (sharpness > 4) {
- flt_config.fac_bl0 = flt_config.fac_bl0 * 3 / 4;
- flt_config.fac_bl1 /= 2;
+ config->fac_bl0 = config->fac_bl0 * 3 / 4;
+ config->fac_bl1 /= 2;
}
}
-
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_FLT;
- params->module_ens |= RKISP1_CIF_ISP_MODULE_FLT;
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_FLT;
}
REGISTER_IPA_ALGORITHM(Filter, "Filter")
diff --git a/src/ipa/rkisp1/algorithms/filter.h b/src/ipa/rkisp1/algorithms/filter.h
index d595811d..8f858e57 100644
--- a/src/ipa/rkisp1/algorithms/filter.h
+++ b/src/ipa/rkisp1/algorithms/filter.h
@@ -26,7 +26,7 @@ public:
const ControlList &controls) override;
void prepare(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params) override;
+ RkISP1Params *params) override;
};
} /* namespace ipa::rkisp1::algorithms */
diff --git a/src/ipa/rkisp1/algorithms/goc.cpp b/src/ipa/rkisp1/algorithms/goc.cpp
index a82cee3b..a9493678 100644
--- a/src/ipa/rkisp1/algorithms/goc.cpp
+++ b/src/ipa/rkisp1/algorithms/goc.cpp
@@ -99,11 +99,14 @@ void GammaOutCorrection::queueRequest(IPAContext &context, const uint32_t frame,
void GammaOutCorrection::prepare(IPAContext &context,
[[maybe_unused]] const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params)
+ RkISP1Params *params)
{
ASSERT(context.hw->numGammaOutSamples ==
RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10);
+ if (!frameContext.goc.update)
+ return;
+
/*
* The logarithmic segments as specified in the reference.
* Plus an additional 0 to make the loop easier
@@ -112,10 +115,11 @@ void GammaOutCorrection::prepare(IPAContext &context,
64, 64, 64, 64, 128, 128, 128, 128, 256,
256, 256, 512, 512, 512, 512, 512, 0
};
- __u16 *gamma_y = params->others.goc_config.gamma_y;
- if (!frameContext.goc.update)
- return;
+ auto config = params->block<BlockType::Goc>();
+ config.setEnabled(true);
+
+ __u16 *gamma_y = config->gamma_y;
unsigned x = 0;
for (const auto [i, size] : utils::enumerate(segments)) {
@@ -123,10 +127,7 @@ void GammaOutCorrection::prepare(IPAContext &context,
x += size;
}
- params->others.goc_config.mode = RKISP1_CIF_ISP_GOC_MODE_LOGARITHMIC;
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_GOC;
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_GOC;
- params->module_ens |= RKISP1_CIF_ISP_MODULE_GOC;
+ config->mode = RKISP1_CIF_ISP_GOC_MODE_LOGARITHMIC;
}
/**
diff --git a/src/ipa/rkisp1/algorithms/goc.h b/src/ipa/rkisp1/algorithms/goc.h
index 0e05d7ce..bb2ddfc9 100644
--- a/src/ipa/rkisp1/algorithms/goc.h
+++ b/src/ipa/rkisp1/algorithms/goc.h
@@ -28,7 +28,7 @@ public:
const ControlList &controls) override;
void prepare(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params) override;
+ RkISP1Params *params) override;
void process(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
const rkisp1_stat_buffer *stats,
diff --git a/src/ipa/rkisp1/algorithms/gsl.cpp b/src/ipa/rkisp1/algorithms/gsl.cpp
index 9b056c6e..9604c0ac 100644
--- a/src/ipa/rkisp1/algorithms/gsl.cpp
+++ b/src/ipa/rkisp1/algorithms/gsl.cpp
@@ -119,24 +119,20 @@ int GammaSensorLinearization::init([[maybe_unused]] IPAContext &context,
void GammaSensorLinearization::prepare([[maybe_unused]] IPAContext &context,
const uint32_t frame,
[[maybe_unused]] IPAFrameContext &frameContext,
- rkisp1_params_cfg *params)
+ RkISP1Params *params)
{
if (frame > 0)
return;
- params->others.sdg_config.xa_pnts.gamma_dx0 = gammaDx_[0];
- params->others.sdg_config.xa_pnts.gamma_dx1 = gammaDx_[1];
+ auto config = params->block<BlockType::Sdg>();
+ config.setEnabled(true);
- std::copy(curveYr_.begin(), curveYr_.end(),
- params->others.sdg_config.curve_r.gamma_y);
- std::copy(curveYg_.begin(), curveYg_.end(),
- params->others.sdg_config.curve_g.gamma_y);
- std::copy(curveYb_.begin(), curveYb_.end(),
- params->others.sdg_config.curve_b.gamma_y);
+ config->xa_pnts.gamma_dx0 = gammaDx_[0];
+ config->xa_pnts.gamma_dx1 = gammaDx_[1];
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_SDG;
- params->module_ens |= RKISP1_CIF_ISP_MODULE_SDG;
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_SDG;
+ std::copy(curveYr_.begin(), curveYr_.end(), config->curve_r.gamma_y);
+ std::copy(curveYg_.begin(), curveYg_.end(), config->curve_g.gamma_y);
+ std::copy(curveYb_.begin(), curveYb_.end(), config->curve_b.gamma_y);
}
REGISTER_IPA_ALGORITHM(GammaSensorLinearization, "GammaSensorLinearization")
diff --git a/src/ipa/rkisp1/algorithms/gsl.h b/src/ipa/rkisp1/algorithms/gsl.h
index c404105e..91cf6efa 100644
--- a/src/ipa/rkisp1/algorithms/gsl.h
+++ b/src/ipa/rkisp1/algorithms/gsl.h
@@ -22,7 +22,7 @@ public:
int init(IPAContext &context, const YamlObject &tuningData) override;
void prepare(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params) override;
+ RkISP1Params *params) override;
private:
uint32_t gammaDx_[2];
diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp
index 161183fc..e47aa2f0 100644
--- a/src/ipa/rkisp1/algorithms/lsc.cpp
+++ b/src/ipa/rkisp1/algorithms/lsc.cpp
@@ -16,6 +16,7 @@
#include "libcamera/internal/yaml_parser.h"
+#include "libipa/lsc_polynomial.h"
#include "linux/rkisp1-config.h"
/**
@@ -24,6 +25,36 @@
namespace libcamera {
+namespace ipa {
+
+constexpr int kColourTemperatureChangeThreshhold = 10;
+
+template<typename T>
+void interpolateVector(const std::vector<T> &a, const std::vector<T> &b,
+ std::vector<T> &dest, double lambda)
+{
+ assert(a.size() == b.size());
+ dest.resize(a.size());
+ for (size_t i = 0; i < a.size(); i++) {
+ dest[i] = a[i] * (1.0 - lambda) + b[i] * lambda;
+ }
+}
+
+template<>
+void Interpolator<rkisp1::algorithms::LensShadingCorrection::Components>::
+ interpolate(const rkisp1::algorithms::LensShadingCorrection::Components &a,
+ const rkisp1::algorithms::LensShadingCorrection::Components &b,
+ rkisp1::algorithms::LensShadingCorrection::Components &dest,
+ double lambda)
+{
+ interpolateVector(a.r, b.r, dest.r, lambda);
+ interpolateVector(a.gr, b.gr, dest.gr, lambda);
+ interpolateVector(a.gb, b.gb, dest.gb, lambda);
+ interpolateVector(a.b, b.b, dest.b, lambda);
+}
+
+} /* namespace ipa */
+
namespace ipa::rkisp1::algorithms {
/**
@@ -40,6 +71,200 @@ namespace ipa::rkisp1::algorithms {
LOG_DEFINE_CATEGORY(RkISP1Lsc)
+class LscPolynomialLoader
+{
+public:
+ LscPolynomialLoader(const Size &sensorSize,
+ const Rectangle &cropRectangle,
+ const std::vector<double> &xSizes,
+ const std::vector<double> &ySizes)
+ : sensorSize_(sensorSize),
+ cropRectangle_(cropRectangle),
+ xSizes_(xSizes),
+ ySizes_(ySizes)
+ {
+ }
+
+ int parseLscData(const YamlObject &yamlSets,
+ std::map<unsigned int, LensShadingCorrection::Components> &lscData)
+ {
+ const auto &sets = yamlSets.asList();
+ for (const auto &yamlSet : sets) {
+ std::optional<LscPolynomial> pr, pgr, pgb, pb;
+ uint32_t ct = yamlSet["ct"].get<uint32_t>(0);
+
+ if (lscData.count(ct)) {
+ LOG(RkISP1Lsc, Error)
+ << "Multiple sets found for "
+ << "color temperature " << ct;
+ return -EINVAL;
+ }
+
+ LensShadingCorrection::Components &set = lscData[ct];
+ pr = yamlSet["r"].get<LscPolynomial>();
+ pgr = yamlSet["gr"].get<LscPolynomial>();
+ pgb = yamlSet["gb"].get<LscPolynomial>();
+ pb = yamlSet["b"].get<LscPolynomial>();
+
+ if (!(pr || pgr || pgb || pb)) {
+ LOG(RkISP1Lsc, Error)
+ << "Failed to parse polynomial for "
+ << "colour temperature " << ct;
+ return -EINVAL;
+ }
+
+ set.ct = ct;
+ pr->setReferenceImageSize(sensorSize_);
+ pgr->setReferenceImageSize(sensorSize_);
+ pgb->setReferenceImageSize(sensorSize_);
+ pb->setReferenceImageSize(sensorSize_);
+ set.r = samplePolynomial(*pr);
+ set.gr = samplePolynomial(*pgr);
+ set.gb = samplePolynomial(*pgb);
+ set.b = samplePolynomial(*pb);
+ }
+
+ if (lscData.empty()) {
+ LOG(RkISP1Lsc, Error) << "Failed to load any sets";
+ return -EINVAL;
+ }
+
+ return 0;
+ }
+
+private:
+ /*
+ * The lsc grid has custom spacing defined on half the range (see
+ * parseSizes() for details). For easier handling this function converts
+ * the spaces vector to positions and mirrors them. E.g.:
+ *
+ * input: | 0.2 | 0.3 |
+ * output: 0.0 0.2 0.5 0.8 1.0
+ */
+ std::vector<double> sizesListToPositions(const std::vector<double> &sizes)
+ {
+ const int half = sizes.size();
+ std::vector<double> res(half * 2 + 1);
+ double x = 0.0;
+
+ res[half] = 0.5;
+ for (int i = 1; i <= half; i++) {
+ x += sizes[half - i];
+ res[half - i] = 0.5 - x;
+ res[half + i] = 0.5 + x;
+ }
+
+ return res;
+ }
+
+ std::vector<uint16_t> samplePolynomial(const LscPolynomial &poly)
+ {
+ constexpr int k = RKISP1_CIF_ISP_LSC_SAMPLES_MAX;
+
+ double m = poly.getM();
+ double x0 = cropRectangle_.x / m;
+ double y0 = cropRectangle_.y / m;
+ double w = cropRectangle_.width / m;
+ double h = cropRectangle_.height / m;
+ std::vector<uint16_t> res;
+
+ assert(xSizes_.size() * 2 + 1 == k);
+ assert(ySizes_.size() * 2 + 1 == k);
+
+ res.reserve(k * k);
+
+ std::vector<double> xPos(sizesListToPositions(xSizes_));
+ std::vector<double> yPos(sizesListToPositions(ySizes_));
+
+ for (int y = 0; y < k; y++) {
+ for (int x = 0; x < k; x++) {
+ double xp = x0 + xPos[x] * w;
+ double yp = y0 + yPos[y] * h;
+ /*
+ * The hardware uses 2.10 fixed point format and
+ * limits the legal values to [1..3.999]. Scale
+ * and clamp the sampled value accordingly.
+ */
+ int v = static_cast<int>(
+ poly.sampleAtNormalizedPixelPos(xp, yp) *
+ 1024);
+ v = std::min(std::max(v, 1024), 4095);
+ res.push_back(v);
+ }
+ }
+ return res;
+ }
+
+ Size sensorSize_;
+ Rectangle cropRectangle_;
+ const std::vector<double> &xSizes_;
+ const std::vector<double> &ySizes_;
+};
+
+class LscTableLoader
+{
+public:
+ int parseLscData(const YamlObject &yamlSets,
+ std::map<unsigned int, LensShadingCorrection::Components> &lscData)
+ {
+ const auto &sets = yamlSets.asList();
+
+ for (const auto &yamlSet : sets) {
+ uint32_t ct = yamlSet["ct"].get<uint32_t>(0);
+
+ if (lscData.count(ct)) {
+ LOG(RkISP1Lsc, Error)
+ << "Multiple sets found for color temperature "
+ << ct;
+ return -EINVAL;
+ }
+
+ LensShadingCorrection::Components &set = lscData[ct];
+
+ set.ct = ct;
+ set.r = parseTable(yamlSet, "r");
+ set.gr = parseTable(yamlSet, "gr");
+ set.gb = parseTable(yamlSet, "gb");
+ set.b = parseTable(yamlSet, "b");
+
+ if (set.r.empty() || set.gr.empty() ||
+ set.gb.empty() || set.b.empty()) {
+ LOG(RkISP1Lsc, Error)
+ << "Set for color temperature " << ct
+ << " is missing tables";
+ return -EINVAL;
+ }
+ }
+
+ if (lscData.empty()) {
+ LOG(RkISP1Lsc, Error) << "Failed to load any sets";
+ return -EINVAL;
+ }
+
+ return 0;
+ }
+
+private:
+ std::vector<uint16_t> parseTable(const YamlObject &tuningData,
+ const char *prop)
+ {
+ static constexpr unsigned int kLscNumSamples =
+ RKISP1_CIF_ISP_LSC_SAMPLES_MAX * RKISP1_CIF_ISP_LSC_SAMPLES_MAX;
+
+ std::vector<uint16_t> table =
+ tuningData[prop].getList<uint16_t>().value_or(std::vector<uint16_t>{});
+ if (table.size() != kLscNumSamples) {
+ LOG(RkISP1Lsc, Error)
+ << "Invalid '" << prop << "' values: expected "
+ << kLscNumSamples
+ << " elements, got " << table.size();
+ return {};
+ }
+
+ return table;
+ }
+};
+
static std::vector<double> parseSizes(const YamlObject &tuningData,
const char *prop)
{
@@ -70,28 +295,10 @@ static std::vector<double> parseSizes(const YamlObject &tuningData,
return sizes;
}
-static std::vector<uint16_t> parseTable(const YamlObject &tuningData,
- const char *prop)
-{
- static constexpr unsigned int kLscNumSamples =
- RKISP1_CIF_ISP_LSC_SAMPLES_MAX * RKISP1_CIF_ISP_LSC_SAMPLES_MAX;
-
- std::vector<uint16_t> table =
- tuningData[prop].getList<uint16_t>().value_or(std::vector<uint16_t>{});
- if (table.size() != kLscNumSamples) {
- LOG(RkISP1Lsc, Error)
- << "Invalid '" << prop << "' values: expected "
- << kLscNumSamples
- << " elements, got " << table.size();
- return {};
- }
-
- return table;
-}
-
LensShadingCorrection::LensShadingCorrection()
- : lastCt_({ 0, 0 })
+ : lastAppliedCt_(0), lastAppliedQuantizedCt_(0)
{
+ sets_.setQuantization(kColourTemperatureChangeThreshhold);
}
/**
@@ -114,38 +321,30 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context,
return -EINVAL;
}
- const auto &sets = yamlSets.asList();
- for (const auto &yamlSet : sets) {
- uint32_t ct = yamlSet["ct"].get<uint32_t>(0);
-
- if (sets_.count(ct)) {
- LOG(RkISP1Lsc, Error)
- << "Multiple sets found for color temperature "
- << ct;
- return -EINVAL;
- }
-
- Components &set = sets_[ct];
-
- set.ct = ct;
- set.r = parseTable(yamlSet, "r");
- set.gr = parseTable(yamlSet, "gr");
- set.gb = parseTable(yamlSet, "gb");
- set.b = parseTable(yamlSet, "b");
-
- if (set.r.empty() || set.gr.empty() ||
- set.gb.empty() || set.b.empty()) {
- LOG(RkISP1Lsc, Error)
- << "Set for color temperature " << ct
- << " is missing tables";
- return -EINVAL;
- }
+ std::map<unsigned int, Components> lscData;
+ int res = 0;
+ std::string type = tuningData["type"].get<std::string>("table");
+ if (type == "table") {
+ LOG(RkISP1Lsc, Debug) << "Loading tabular LSC data.";
+ auto loader = LscTableLoader();
+ res = loader.parseLscData(yamlSets, lscData);
+ } else if (type == "polynomial") {
+ LOG(RkISP1Lsc, Debug) << "Loading polynomial LSC data.";
+ auto loader = LscPolynomialLoader(context.sensorInfo.activeAreaSize,
+ context.sensorInfo.analogCrop,
+ xSize_,
+ ySize_);
+ res = loader.parseLscData(yamlSets, lscData);
+ } else {
+ LOG(RkISP1Lsc, Error) << "Unsupported LSC data type '"
+ << type << "'";
+ res = -EINVAL;
}
- if (sets_.empty()) {
- LOG(RkISP1Lsc, Error) << "Failed to load any sets";
- return -EINVAL;
- }
+ if (res)
+ return res;
+
+ sets_.setData(std::move(lscData));
return 0;
}
@@ -185,18 +384,12 @@ int LensShadingCorrection::configure(IPAContext &context,
return 0;
}
-void LensShadingCorrection::setParameters(rkisp1_params_cfg *params)
+void LensShadingCorrection::setParameters(rkisp1_cif_isp_lsc_config &config)
{
- struct rkisp1_cif_isp_lsc_config &config = params->others.lsc_config;
-
memcpy(config.x_grad_tbl, xGrad_, sizeof(config.x_grad_tbl));
memcpy(config.y_grad_tbl, yGrad_, sizeof(config.y_grad_tbl));
memcpy(config.x_size_tbl, xSizes_, sizeof(config.x_size_tbl));
memcpy(config.y_size_tbl, ySizes_, sizeof(config.y_size_tbl));
-
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_LSC;
- params->module_ens |= RKISP1_CIF_ISP_MODULE_LSC;
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_LSC;
}
void LensShadingCorrection::copyTable(rkisp1_cif_isp_lsc_config &config,
@@ -208,131 +401,34 @@ void LensShadingCorrection::copyTable(rkisp1_cif_isp_lsc_config &config,
std::copy(set.b.begin(), set.b.end(), &config.b_data_tbl[0][0]);
}
-/*
- * Interpolate LSC parameters based on color temperature value.
- */
-void LensShadingCorrection::interpolateTable(rkisp1_cif_isp_lsc_config &config,
- const Components &set0,
- const Components &set1,
- const uint32_t ct)
-{
- double coeff0 = (set1.ct - ct) / static_cast<double>(set1.ct - set0.ct);
- double coeff1 = (ct - set0.ct) / static_cast<double>(set1.ct - set0.ct);
-
- for (unsigned int i = 0; i < RKISP1_CIF_ISP_LSC_SAMPLES_MAX; ++i) {
- for (unsigned int j = 0; j < RKISP1_CIF_ISP_LSC_SAMPLES_MAX; ++j) {
- unsigned int sample = i * RKISP1_CIF_ISP_LSC_SAMPLES_MAX + j;
-
- config.r_data_tbl[i][j] =
- set0.r[sample] * coeff0 +
- set1.r[sample] * coeff1;
-
- config.gr_data_tbl[i][j] =
- set0.gr[sample] * coeff0 +
- set1.gr[sample] * coeff1;
-
- config.gb_data_tbl[i][j] =
- set0.gb[sample] * coeff0 +
- set1.gb[sample] * coeff1;
-
- config.b_data_tbl[i][j] =
- set0.b[sample] * coeff0 +
- set1.b[sample] * coeff1;
- }
- }
-}
-
/**
* \copydoc libcamera::ipa::Algorithm::prepare
*/
void LensShadingCorrection::prepare(IPAContext &context,
- const uint32_t frame,
+ [[maybe_unused]] const uint32_t frame,
[[maybe_unused]] IPAFrameContext &frameContext,
- rkisp1_params_cfg *params)
+ RkISP1Params *params)
{
- struct rkisp1_cif_isp_lsc_config &config = params->others.lsc_config;
-
- /*
- * If there is only one set, the configuration has already been done
- * for first frame.
- */
- if (sets_.size() == 1 && frame > 0)
- return;
-
- /*
- * If there is only one set, pick it. We can ignore lastCt_, as it will
- * never be relevant.
- */
- if (sets_.size() == 1) {
- setParameters(params);
- copyTable(config, sets_.cbegin()->second);
- return;
- }
-
uint32_t ct = context.activeState.awb.temperatureK;
- ct = std::clamp(ct, sets_.cbegin()->first, sets_.crbegin()->first);
-
- /*
- * If the original is the same, then it means the same adjustment would
- * be made. If the adjusted is the same, then it means that it's the
- * same as what was actually applied. Thus in these cases we can skip
- * reprogramming the LSC.
- *
- * original == adjusted can only happen if an interpolation
- * happened, or if original has an exact entry in sets_. This means
- * that if original != adjusted, then original was adjusted to
- * the nearest available entry in sets_, resulting in adjusted.
- * Clearly, any ct value that is in between original and adjusted
- * will be adjusted to the same adjusted value, so we can skip
- * reprogramming the LSC table.
- *
- * We also skip updating the original value, as the last one had a
- * larger bound and thus a larger range of ct values that will be
- * adjusted to the same adjusted.
- */
- if ((lastCt_.original <= ct && ct <= lastCt_.adjusted) ||
- (lastCt_.adjusted <= ct && ct <= lastCt_.original))
+ if (std::abs(static_cast<int>(ct) - static_cast<int>(lastAppliedCt_)) <
+ kColourTemperatureChangeThreshhold)
return;
-
- setParameters(params);
-
- /*
- * The color temperature matches exactly one of the available LSC tables.
- */
- if (sets_.count(ct)) {
- copyTable(config, sets_[ct]);
- lastCt_ = { ct, ct };
+ unsigned int quantizedCt;
+ const Components &set = sets_.getInterpolated(ct, &quantizedCt);
+ if (lastAppliedQuantizedCt_ == quantizedCt)
return;
- }
- /* No shortcuts left; we need to round or interpolate */
- auto iter = sets_.upper_bound(ct);
- const Components &set1 = iter->second;
- const Components &set0 = (--iter)->second;
- uint32_t ct0 = set0.ct;
- uint32_t ct1 = set1.ct;
- uint32_t diff0 = ct - ct0;
- uint32_t diff1 = ct1 - ct;
- static constexpr double kThreshold = 0.1;
- float threshold = kThreshold * (ct1 - ct0);
-
- if (diff0 < threshold || diff1 < threshold) {
- const Components &set = diff0 < diff1 ? set0 : set1;
- LOG(RkISP1Lsc, Debug) << "using LSC table for " << set.ct;
- copyTable(config, set);
- lastCt_ = { ct, set.ct };
- return;
- }
+ auto config = params->block<BlockType::Lsc>();
+ config.setEnabled(true);
+ setParameters(*config);
+ copyTable(*config, set);
+
+ lastAppliedCt_ = ct;
+ lastAppliedQuantizedCt_ = quantizedCt;
- /*
- * ct is not within 10% of the difference between the neighbouring
- * color temperatures, so we need to interpolate.
- */
LOG(RkISP1Lsc, Debug)
- << "ct is " << ct << ", interpolating between "
- << ct0 << " and " << ct1;
- interpolateTable(config, set0, set1, ct);
- lastCt_ = { ct, ct };
+ << "ct is " << ct << ", quantized to "
+ << quantizedCt;
}
REGISTER_IPA_ALGORITHM(LensShadingCorrection, "LensShadingCorrection")
diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h
index 5baf5927..5a0824e3 100644
--- a/src/ipa/rkisp1/algorithms/lsc.h
+++ b/src/ipa/rkisp1/algorithms/lsc.h
@@ -9,6 +9,8 @@
#include <map>
+#include "libipa/interpolator.h"
+
#include "algorithm.h"
namespace libcamera {
@@ -25,9 +27,8 @@ public:
int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override;
void prepare(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params) override;
+ RkISP1Params *params) override;
-private:
struct Components {
uint32_t ct;
std::vector<uint16_t> r;
@@ -36,23 +37,23 @@ private:
std::vector<uint16_t> b;
};
- void setParameters(rkisp1_params_cfg *params);
+private:
+ void setParameters(rkisp1_cif_isp_lsc_config &config);
void copyTable(rkisp1_cif_isp_lsc_config &config, const Components &set0);
void interpolateTable(rkisp1_cif_isp_lsc_config &config,
const Components &set0, const Components &set1,
const uint32_t ct);
- std::map<uint32_t, Components> sets_;
+ ipa::Interpolator<Components> sets_;
std::vector<double> xSize_;
std::vector<double> ySize_;
uint16_t xGrad_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE];
uint16_t yGrad_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE];
uint16_t xSizes_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE];
uint16_t ySizes_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE];
- struct {
- uint32_t original;
- uint32_t adjusted;
- } lastCt_;
+
+ unsigned int lastAppliedCt_;
+ unsigned int lastAppliedQuantizedCt_;
};
} /* namespace ipa::rkisp1::algorithms */
diff --git a/src/ipa/rkisp1/data/imx219.yaml b/src/ipa/rkisp1/data/imx219.yaml
index cbcc43b8..0d99cb52 100644
--- a/src/ipa/rkisp1/data/imx219.yaml
+++ b/src/ipa/rkisp1/data/imx219.yaml
@@ -6,10 +6,6 @@ algorithms:
- Agc:
- Awb:
- BlackLevelCorrection:
- R: 256
- Gr: 256
- Gb: 256
- B: 256
- LensShadingCorrection:
x-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]
y-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]
diff --git a/src/ipa/rkisp1/data/imx258.yaml b/src/ipa/rkisp1/data/imx258.yaml
index 43dddf20..202af36a 100644
--- a/src/ipa/rkisp1/data/imx258.yaml
+++ b/src/ipa/rkisp1/data/imx258.yaml
@@ -5,6 +5,7 @@ version: 1
algorithms:
- Agc:
- Awb:
+ - BlackLevelCorrection:
- LensShadingCorrection:
x-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]
y-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]
diff --git a/src/ipa/rkisp1/data/meson.build b/src/ipa/rkisp1/data/meson.build
index 7150e155..1e3522b2 100644
--- a/src/ipa/rkisp1/data/meson.build
+++ b/src/ipa/rkisp1/data/meson.build
@@ -2,8 +2,12 @@
conf_files = files([
'imx219.yaml',
+ 'imx258.yaml',
+ 'ov2685.yaml',
'ov4689.yaml',
'ov5640.yaml',
+ 'ov5695.yaml',
+ 'ov8858.yaml',
'uncalibrated.yaml',
])
diff --git a/src/ipa/rkisp1/data/ov4689.yaml b/src/ipa/rkisp1/data/ov4689.yaml
index 2068684c..60901296 100644
--- a/src/ipa/rkisp1/data/ov4689.yaml
+++ b/src/ipa/rkisp1/data/ov4689.yaml
@@ -6,8 +6,4 @@ algorithms:
- Agc:
- Awb:
- BlackLevelCorrection:
- R: 66
- Gr: 66
- Gb: 66
- B: 66
...
diff --git a/src/ipa/rkisp1/data/ov5640.yaml b/src/ipa/rkisp1/data/ov5640.yaml
index 897b83cb..4b21d412 100644
--- a/src/ipa/rkisp1/data/ov5640.yaml
+++ b/src/ipa/rkisp1/data/ov5640.yaml
@@ -6,10 +6,6 @@ algorithms:
- Agc:
- Awb:
- BlackLevelCorrection:
- R: 256
- Gr: 256
- Gb: 256
- B: 256
- ColorProcessing:
- GammaSensorLinearization:
x-intervals: [ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ]
diff --git a/src/ipa/rkisp1/data/uncalibrated.yaml b/src/ipa/rkisp1/data/uncalibrated.yaml
index a7bbd8d8..60901296 100644
--- a/src/ipa/rkisp1/data/uncalibrated.yaml
+++ b/src/ipa/rkisp1/data/uncalibrated.yaml
@@ -5,4 +5,5 @@ version: 1
algorithms:
- Agc:
- Awb:
+ - BlackLevelCorrection:
...
diff --git a/src/ipa/rkisp1/ipa_context.cpp b/src/ipa/rkisp1/ipa_context.cpp
index 9f3f576a..14d0c02a 100644
--- a/src/ipa/rkisp1/ipa_context.cpp
+++ b/src/ipa/rkisp1/ipa_context.cpp
@@ -106,6 +106,11 @@ namespace libcamera::ipa::rkisp1 {
*/
/**
+ * \var IPASessionConfiguration::paramFormat
+ * \brief The fourcc of the parameters buffers format
+ */
+
+/**
* \struct IPAActiveState
* \brief Active state for algorithms
*
@@ -419,6 +424,9 @@ namespace libcamera::ipa::rkisp1 {
* \var IPAContext::hw
* \brief RkISP1 version-specific hardware parameters
*
+ * \var IPAContext::sensorInfo
+ * \brief The IPA session sensorInfo, immutable during the session
+ *
* \var IPAContext::configuration
* \brief The IPA session configuration, immutable during the session
*
diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
index 8602b408..e274d9b0 100644
--- a/src/ipa/rkisp1/ipa_context.h
+++ b/src/ipa/rkisp1/ipa_context.h
@@ -8,6 +8,8 @@
#pragma once
+#include <memory>
+
#include <linux/rkisp1-config.h>
#include <libcamera/base/utils.h>
@@ -15,7 +17,9 @@
#include <libcamera/control_ids.h>
#include <libcamera/controls.h>
#include <libcamera/geometry.h>
+#include <libcamera/ipa/core_ipa_interface.h>
+#include <libipa/camera_sensor_helper.h>
#include <libipa/fc_queue.h>
#include <libipa/matrix.h>
@@ -28,6 +32,7 @@ struct IPAHwSettings {
unsigned int numHistogramBins;
unsigned int numHistogramWeights;
unsigned int numGammaOutSamples;
+ bool compand;
};
struct IPASessionConfiguration {
@@ -56,6 +61,7 @@ struct IPASessionConfiguration {
} sensor;
bool raw;
+ uint32_t paramFormat;
};
struct IPAActiveState {
@@ -95,6 +101,10 @@ struct IPAActiveState {
} awb;
struct {
+ Matrix<float, 3, 3> ccm;
+ } ccm;
+
+ struct {
int8_t brightness;
uint8_t contrast;
uint8_t saturation;
@@ -133,7 +143,6 @@ struct IPAFrameContext : public FrameContext {
double blue;
} gains;
- unsigned int temperatureK;
bool autoEnabled;
} awb;
@@ -172,12 +181,16 @@ struct IPAFrameContext : public FrameContext {
struct IPAContext {
const IPAHwSettings *hw;
+ IPACameraSensorInfo sensorInfo;
IPASessionConfiguration configuration;
IPAActiveState activeState;
FCQueue<IPAFrameContext> frameContexts;
ControlInfoMap::Map ctrlMap;
+
+ /* Interface to the Camera Helper */
+ std::unique_ptr<CameraSensorHelper> camHelper;
};
} /* namespace ipa::rkisp1 */
diff --git a/src/ipa/rkisp1/meson.build b/src/ipa/rkisp1/meson.build
index e8b266f1..34844f14 100644
--- a/src/ipa/rkisp1/meson.build
+++ b/src/ipa/rkisp1/meson.build
@@ -7,14 +7,14 @@ ipa_name = 'ipa_rkisp1'
rkisp1_ipa_sources = files([
'ipa_context.cpp',
+ 'params.cpp',
'rkisp1.cpp',
'utils.cpp',
])
rkisp1_ipa_sources += rkisp1_ipa_algorithms
-mod = shared_module(ipa_name,
- [rkisp1_ipa_sources, libcamera_generated_ipa_headers],
+mod = shared_module(ipa_name, rkisp1_ipa_sources,
name_prefix : '',
include_directories : [ipa_includes],
dependencies : [libcamera_private, libipa_dep],
diff --git a/src/ipa/rkisp1/module.h b/src/ipa/rkisp1/module.h
index 16c3e43e..69e9bc82 100644
--- a/src/ipa/rkisp1/module.h
+++ b/src/ipa/rkisp1/module.h
@@ -14,13 +14,14 @@
#include <libipa/module.h>
#include "ipa_context.h"
+#include "params.h"
namespace libcamera {
namespace ipa::rkisp1 {
using Module = ipa::Module<IPAContext, IPAFrameContext, IPACameraSensorInfo,
- rkisp1_params_cfg, rkisp1_stat_buffer>;
+ RkISP1Params, rkisp1_stat_buffer>;
} /* namespace ipa::rkisp1 */
diff --git a/src/ipa/rkisp1/params.cpp b/src/ipa/rkisp1/params.cpp
new file mode 100644
index 00000000..4c0b051c
--- /dev/null
+++ b/src/ipa/rkisp1/params.cpp
@@ -0,0 +1,222 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Ideas On Board
+ *
+ * RkISP1 ISP Parameters
+ */
+
+#include "params.h"
+
+#include <map>
+#include <stddef.h>
+#include <string.h>
+
+#include <linux/rkisp1-config.h>
+#include <linux/videodev2.h>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/utils.h>
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(RkISP1Params)
+
+namespace ipa::rkisp1 {
+
+namespace {
+
+struct BlockTypeInfo {
+ enum rkisp1_ext_params_block_type type;
+ size_t size;
+ size_t offset;
+ uint32_t enableBit;
+};
+
+#define RKISP1_BLOCK_TYPE_ENTRY(block, id, type, category, bit) \
+ { BlockType::block, { \
+ RKISP1_EXT_PARAMS_BLOCK_TYPE_##id, \
+ sizeof(struct rkisp1_cif_isp_##type##_config), \
+ offsetof(struct rkisp1_params_cfg, category.type##_config), \
+ RKISP1_CIF_ISP_MODULE_##bit, \
+ } }
+
+#define RKISP1_BLOCK_TYPE_ENTRY_MEAS(block, id, type) \
+ RKISP1_BLOCK_TYPE_ENTRY(block, id##_MEAS, type, meas, id)
+
+#define RKISP1_BLOCK_TYPE_ENTRY_OTHERS(block, id, type) \
+ RKISP1_BLOCK_TYPE_ENTRY(block, id, type, others, id)
+
+#define RKISP1_BLOCK_TYPE_ENTRY_EXT(block, id, type) \
+ { BlockType::block, { \
+ RKISP1_EXT_PARAMS_BLOCK_TYPE_##id, \
+ sizeof(struct rkisp1_cif_isp_##type##_config), \
+ 0, 0, \
+ } }
+
+const std::map<BlockType, BlockTypeInfo> kBlockTypeInfo = {
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Bls, BLS, bls),
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Dpcc, DPCC, dpcc),
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Sdg, SDG, sdg),
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(AwbGain, AWB_GAIN, awb_gain),
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Flt, FLT, flt),
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Bdm, BDM, bdm),
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Ctk, CTK, ctk),
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Goc, GOC, goc),
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Dpf, DPF, dpf),
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(DpfStrength, DPF_STRENGTH, dpf_strength),
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Cproc, CPROC, cproc),
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Ie, IE, ie),
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Lsc, LSC, lsc),
+ RKISP1_BLOCK_TYPE_ENTRY_MEAS(Awb, AWB, awb_meas),
+ RKISP1_BLOCK_TYPE_ENTRY_MEAS(Hst, HST, hst),
+ RKISP1_BLOCK_TYPE_ENTRY_MEAS(Aec, AEC, aec),
+ RKISP1_BLOCK_TYPE_ENTRY_MEAS(Afc, AFC, afc),
+ RKISP1_BLOCK_TYPE_ENTRY_EXT(CompandBls, COMPAND_BLS, compand_bls),
+ RKISP1_BLOCK_TYPE_ENTRY_EXT(CompandExpand, COMPAND_EXPAND, compand_curve),
+ RKISP1_BLOCK_TYPE_ENTRY_EXT(CompandCompress, COMPAND_COMPRESS, compand_curve),
+};
+
+} /* namespace */
+
+RkISP1ParamsBlockBase::RkISP1ParamsBlockBase(RkISP1Params *params, BlockType type,
+ const Span<uint8_t> &data)
+ : params_(params), type_(type)
+{
+ if (params_->format() == V4L2_META_FMT_RK_ISP1_EXT_PARAMS) {
+ header_ = data.subspan(0, sizeof(rkisp1_ext_params_block_header));
+ data_ = data.subspan(sizeof(rkisp1_ext_params_block_header));
+ } else {
+ data_ = data;
+ }
+}
+
+void RkISP1ParamsBlockBase::setEnabled(bool enabled)
+{
+ /*
+ * For the legacy fixed format, blocks are enabled in the top-level
+ * header. Delegate to the RkISP1Params class.
+ */
+ if (params_->format() == V4L2_META_FMT_RK_ISP1_PARAMS)
+ return params_->setBlockEnabled(type_, enabled);
+
+ /*
+ * For the extensible format, set the enable and disable flags in the
+ * block header directly.
+ */
+ struct rkisp1_ext_params_block_header *header =
+ reinterpret_cast<struct rkisp1_ext_params_block_header *>(header_.data());
+ header->flags &= ~(RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE |
+ RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE);
+ header->flags |= enabled ? RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE
+ : RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE;
+}
+
+RkISP1Params::RkISP1Params(uint32_t format, Span<uint8_t> data)
+ : format_(format), data_(data), used_(0)
+{
+ if (format_ == V4L2_META_FMT_RK_ISP1_EXT_PARAMS) {
+ struct rkisp1_ext_params_cfg *cfg =
+ reinterpret_cast<struct rkisp1_ext_params_cfg *>(data.data());
+
+ cfg->version = RKISP1_EXT_PARAM_BUFFER_V1;
+ cfg->data_size = 0;
+
+ used_ += offsetof(struct rkisp1_ext_params_cfg, data);
+ } else {
+ memset(data.data(), 0, data.size());
+ used_ = sizeof(struct rkisp1_params_cfg);
+ }
+}
+
+void RkISP1Params::setBlockEnabled(BlockType type, bool enabled)
+{
+ const BlockTypeInfo &info = kBlockTypeInfo.at(type);
+
+ struct rkisp1_params_cfg *cfg =
+ reinterpret_cast<struct rkisp1_params_cfg *>(data_.data());
+ if (enabled)
+ cfg->module_ens |= info.enableBit;
+ else
+ cfg->module_ens &= ~info.enableBit;
+}
+
+Span<uint8_t> RkISP1Params::block(BlockType type)
+{
+ auto infoIt = kBlockTypeInfo.find(type);
+ if (infoIt == kBlockTypeInfo.end()) {
+ LOG(RkISP1Params, Error)
+ << "Invalid parameters block type "
+ << utils::to_underlying(type);
+ return {};
+ }
+
+ const BlockTypeInfo &info = infoIt->second;
+
+ /*
+ * For the legacy format, return a block referencing the fixed location
+ * of the data.
+ */
+ if (format_ == V4L2_META_FMT_RK_ISP1_PARAMS) {
+ /*
+ * Blocks available only in extended parameters have an offset
+ * of 0. Return nullptr in that case.
+ */
+ if (info.offset == 0) {
+ LOG(RkISP1Params, Error)
+ << "Block type " << utils::to_underlying(type)
+ << " unavailable in fixed parameters format";
+ return {};
+ }
+
+ struct rkisp1_params_cfg *cfg =
+ reinterpret_cast<struct rkisp1_params_cfg *>(data_.data());
+
+ cfg->module_cfg_update |= info.enableBit;
+ cfg->module_en_update |= info.enableBit;
+
+ return data_.subspan(info.offset, info.size);
+ }
+
+ /*
+ * For the extensible format, allocate memory for the block, including
+ * the header. Look up the block in the cache first. If an algorithm
+ * requests the same block type twice, it should get the same block.
+ */
+ auto cacheIt = blocks_.find(type);
+ if (cacheIt != blocks_.end())
+ return cacheIt->second;
+
+ /* Make sure we don't run out of space. */
+ size_t size = sizeof(struct rkisp1_ext_params_block_header)
+ + ((info.size + 7) & ~7);
+ if (size > data_.size() - used_) {
+ LOG(RkISP1Params, Error)
+ << "Out of memory to allocate block type "
+ << utils::to_underlying(type);
+ return {};
+ }
+
+ /* Allocate a new block, clear its memory, and initialize its header. */
+ Span<uint8_t> block = data_.subspan(used_, size);
+ used_ += size;
+
+ struct rkisp1_ext_params_cfg *cfg =
+ reinterpret_cast<struct rkisp1_ext_params_cfg *>(data_.data());
+ cfg->data_size += size;
+
+ memset(block.data(), 0, block.size());
+
+ struct rkisp1_ext_params_block_header *header =
+ reinterpret_cast<struct rkisp1_ext_params_block_header *>(block.data());
+ header->type = info.type;
+ header->size = block.size();
+
+ /* Update the cache. */
+ blocks_[type] = block;
+
+ return block;
+}
+
+} /* namespace ipa::rkisp1 */
+
+} /* namespace libcamera */
diff --git a/src/ipa/rkisp1/params.h b/src/ipa/rkisp1/params.h
new file mode 100644
index 00000000..40450e34
--- /dev/null
+++ b/src/ipa/rkisp1/params.h
@@ -0,0 +1,163 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Ideas On Board
+ *
+ * RkISP1 ISP Parameters
+ */
+
+#pragma once
+
+#include <map>
+#include <stdint.h>
+
+#include <linux/rkisp1-config.h>
+
+#include <libcamera/base/class.h>
+#include <libcamera/base/span.h>
+
+namespace libcamera {
+
+namespace ipa::rkisp1 {
+
+enum class BlockType {
+ Bls,
+ Dpcc,
+ Sdg,
+ AwbGain,
+ Flt,
+ Bdm,
+ Ctk,
+ Goc,
+ Dpf,
+ DpfStrength,
+ Cproc,
+ Ie,
+ Lsc,
+ Awb,
+ Hst,
+ Aec,
+ Afc,
+ CompandBls,
+ CompandExpand,
+ CompandCompress,
+};
+
+namespace details {
+
+template<BlockType B>
+struct block_type {
+};
+
+#define RKISP1_DEFINE_BLOCK_TYPE(blockType, blockStruct) \
+template<> \
+struct block_type<BlockType::blockType> { \
+ using type = struct rkisp1_cif_isp_##blockStruct##_config; \
+};
+
+RKISP1_DEFINE_BLOCK_TYPE(Bls, bls)
+RKISP1_DEFINE_BLOCK_TYPE(Dpcc, dpcc)
+RKISP1_DEFINE_BLOCK_TYPE(Sdg, sdg)
+RKISP1_DEFINE_BLOCK_TYPE(AwbGain, awb_gain)
+RKISP1_DEFINE_BLOCK_TYPE(Flt, flt)
+RKISP1_DEFINE_BLOCK_TYPE(Bdm, bdm)
+RKISP1_DEFINE_BLOCK_TYPE(Ctk, ctk)
+RKISP1_DEFINE_BLOCK_TYPE(Goc, goc)
+RKISP1_DEFINE_BLOCK_TYPE(Dpf, dpf)
+RKISP1_DEFINE_BLOCK_TYPE(DpfStrength, dpf_strength)
+RKISP1_DEFINE_BLOCK_TYPE(Cproc, cproc)
+RKISP1_DEFINE_BLOCK_TYPE(Ie, ie)
+RKISP1_DEFINE_BLOCK_TYPE(Lsc, lsc)
+RKISP1_DEFINE_BLOCK_TYPE(Awb, awb_meas)
+RKISP1_DEFINE_BLOCK_TYPE(Hst, hst)
+RKISP1_DEFINE_BLOCK_TYPE(Aec, aec)
+RKISP1_DEFINE_BLOCK_TYPE(Afc, afc)
+RKISP1_DEFINE_BLOCK_TYPE(CompandBls, compand_bls)
+RKISP1_DEFINE_BLOCK_TYPE(CompandExpand, compand_curve)
+RKISP1_DEFINE_BLOCK_TYPE(CompandCompress, compand_curve)
+
+} /* namespace details */
+
+class RkISP1Params;
+
+class RkISP1ParamsBlockBase
+{
+public:
+ RkISP1ParamsBlockBase(RkISP1Params *params, BlockType type,
+ const Span<uint8_t> &data);
+
+ Span<uint8_t> data() const { return data_; }
+
+ void setEnabled(bool enabled);
+
+private:
+ LIBCAMERA_DISABLE_COPY(RkISP1ParamsBlockBase)
+
+ RkISP1Params *params_;
+ BlockType type_;
+ Span<uint8_t> header_;
+ Span<uint8_t> data_;
+};
+
+template<BlockType B>
+class RkISP1ParamsBlock : public RkISP1ParamsBlockBase
+{
+public:
+ using Type = typename details::block_type<B>::type;
+
+ RkISP1ParamsBlock(RkISP1Params *params, const Span<uint8_t> &data)
+ : RkISP1ParamsBlockBase(params, B, data)
+ {
+ }
+
+ const Type *operator->() const
+ {
+ return reinterpret_cast<const Type *>(data().data());
+ }
+
+ Type *operator->()
+ {
+ return reinterpret_cast<Type *>(data().data());
+ }
+
+ const Type &operator*() const &
+ {
+ return *reinterpret_cast<const Type *>(data().data());
+ }
+
+ Type &operator*() &
+ {
+ return *reinterpret_cast<Type *>(data().data());
+ }
+};
+
+class RkISP1Params
+{
+public:
+ RkISP1Params(uint32_t format, Span<uint8_t> data);
+
+ template<BlockType B>
+ RkISP1ParamsBlock<B> block()
+ {
+ return RkISP1ParamsBlock<B>(this, block(B));
+ }
+
+ uint32_t format() const { return format_; }
+ size_t size() const { return used_; }
+
+private:
+ friend class RkISP1ParamsBlockBase;
+
+ Span<uint8_t> block(BlockType type);
+ void setBlockEnabled(BlockType type, bool enabled);
+
+ uint32_t format_;
+
+ Span<uint8_t> data_;
+ size_t used_;
+
+ std::map<BlockType, Span<uint8_t>> blocks_;
+};
+
+} /* namespace ipa::rkisp1 */
+
+} /* namespace libcamera*/
diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp
index d31cdbab..9e161cab 100644
--- a/src/ipa/rkisp1/rkisp1.cpp
+++ b/src/ipa/rkisp1/rkisp1.cpp
@@ -6,8 +6,8 @@
*/
#include <algorithm>
-#include <math.h>
-#include <queue>
+#include <array>
+#include <chrono>
#include <stdint.h>
#include <string.h>
@@ -18,20 +18,22 @@
#include <libcamera/base/log.h>
#include <libcamera/control_ids.h>
+#include <libcamera/controls.h>
#include <libcamera/framebuffer.h>
+#include <libcamera/request.h>
+
#include <libcamera/ipa/ipa_interface.h>
#include <libcamera/ipa/ipa_module_info.h>
#include <libcamera/ipa/rkisp1_ipa_interface.h>
-#include <libcamera/request.h>
#include "libcamera/internal/formats.h"
#include "libcamera/internal/mapped_framebuffer.h"
#include "libcamera/internal/yaml_parser.h"
#include "algorithms/algorithm.h"
-#include "libipa/camera_sensor_helper.h"
#include "ipa_context.h"
+#include "params.h"
namespace libcamera {
@@ -81,9 +83,6 @@ private:
ControlInfoMap sensorControls_;
- /* Interface to the Camera Helper */
- std::unique_ptr<CameraSensorHelper> camHelper_;
-
/* Local parameter storage */
struct IPAContext context_;
};
@@ -95,6 +94,15 @@ const IPAHwSettings ipaHwSettingsV10{
RKISP1_CIF_ISP_HIST_BIN_N_MAX_V10,
RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE_V10,
RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10,
+ false,
+};
+
+const IPAHwSettings ipaHwSettingsIMX8MP{
+ RKISP1_CIF_ISP_AE_MEAN_MAX_V10,
+ RKISP1_CIF_ISP_HIST_BIN_N_MAX_V10,
+ RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE_V10,
+ RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10,
+ true,
};
const IPAHwSettings ipaHwSettingsV12{
@@ -102,6 +110,7 @@ const IPAHwSettings ipaHwSettingsV12{
RKISP1_CIF_ISP_HIST_BIN_N_MAX_V12,
RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE_V12,
RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V12,
+ false,
};
/* List of controls handled by the RkISP1 IPA */
@@ -115,7 +124,7 @@ const ControlInfoMap::Map rkisp1Controls{
} /* namespace */
IPARkISP1::IPARkISP1()
- : context_({ {}, {}, {}, { kMaxFrameContexts }, {} })
+ : context_({ {}, {}, {}, {}, { kMaxFrameContexts }, {}, {} })
{
}
@@ -132,9 +141,11 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision,
/* \todo Add support for other revisions */
switch (hwRevision) {
case RKISP1_V10:
- case RKISP1_V_IMX8MP:
context_.hw = &ipaHwSettingsV10;
break;
+ case RKISP1_V_IMX8MP:
+ context_.hw = &ipaHwSettingsIMX8MP;
+ break;
case RKISP1_V12:
context_.hw = &ipaHwSettingsV12;
break;
@@ -147,16 +158,18 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision,
LOG(IPARkISP1, Debug) << "Hardware revision is " << hwRevision;
- camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel);
- if (!camHelper_) {
+ context_.sensorInfo = sensorInfo;
+
+ context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel);
+ if (!context_.camHelper) {
LOG(IPARkISP1, Error)
<< "Failed to create camera sensor helper for "
<< settings.sensorModel;
return -ENODEV;
}
- context_.configuration.sensor.lineDuration = sensorInfo.minLineLength
- * 1.0s / sensorInfo.pixelRate;
+ context_.configuration.sensor.lineDuration =
+ sensorInfo.minLineLength * 1.0s / sensorInfo.pixelRate;
/* Load the tuning data file. */
File file(settings.configurationFile);
@@ -230,6 +243,8 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig,
context_.activeState = {};
context_.frameContexts.clear();
+ context_.configuration.paramFormat = ipaConfig.paramFormat;
+
const IPACameraSensorInfo &info = ipaConfig.sensorInfo;
const ControlInfo vBlank = sensorControls_.find(V4L2_CID_VBLANK)->second;
context_.configuration.sensor.defVBlank = vBlank.def().get<int32_t>();
@@ -250,8 +265,10 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig,
minExposure * context_.configuration.sensor.lineDuration;
context_.configuration.sensor.maxShutterSpeed =
maxExposure * context_.configuration.sensor.lineDuration;
- context_.configuration.sensor.minAnalogueGain = camHelper_->gain(minGain);
- context_.configuration.sensor.maxAnalogueGain = camHelper_->gain(maxGain);
+ context_.configuration.sensor.minAnalogueGain =
+ context_.camHelper->gain(minGain);
+ context_.configuration.sensor.maxAnalogueGain =
+ context_.camHelper->gain(maxGain);
context_.configuration.raw = std::any_of(streamConfig.begin(), streamConfig.end(),
[](auto &cfg) -> bool {
@@ -322,17 +339,13 @@ void IPARkISP1::fillParamsBuffer(const uint32_t frame, const uint32_t bufferId)
{
IPAFrameContext &frameContext = context_.frameContexts.get(frame);
- rkisp1_params_cfg *params =
- reinterpret_cast<rkisp1_params_cfg *>(
- mappedBuffers_.at(bufferId).planes()[0].data());
-
- /* Prepare parameters buffer. */
- memset(params, 0, sizeof(*params));
+ RkISP1Params params(context_.configuration.paramFormat,
+ mappedBuffers_.at(bufferId).planes()[0]);
for (auto const &algo : algorithms())
- algo->prepare(context_, frame, frameContext, params);
+ algo->prepare(context_, frame, frameContext, &params);
- paramsBufferReady.emit(frame);
+ paramsBufferReady.emit(frame, params.size());
}
void IPARkISP1::processStatsBuffer(const uint32_t frame, const uint32_t bufferId,
@@ -352,7 +365,7 @@ void IPARkISP1::processStatsBuffer(const uint32_t frame, const uint32_t bufferId
frameContext.sensor.exposure =
sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();
frameContext.sensor.gain =
- camHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>());
+ context_.camHelper->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>());
ControlList metadata(controls::controls);
@@ -389,9 +402,9 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo,
/* Compute the analogue gain limits. */
const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second;
- float minGain = camHelper_->gain(v4l2Gain.min().get<int32_t>());
- float maxGain = camHelper_->gain(v4l2Gain.max().get<int32_t>());
- float defGain = camHelper_->gain(v4l2Gain.def().get<int32_t>());
+ float minGain = context_.camHelper->gain(v4l2Gain.min().get<int32_t>());
+ float maxGain = context_.camHelper->gain(v4l2Gain.max().get<int32_t>());
+ float defGain = context_.camHelper->gain(v4l2Gain.def().get<int32_t>());
ctrlMap.emplace(std::piecewise_construct,
std::forward_as_tuple(&controls::AnalogueGain),
std::forward_as_tuple(minGain, maxGain, defGain));
@@ -436,7 +449,7 @@ void IPARkISP1::setControls(unsigned int frame)
IPAFrameContext &frameContext = context_.frameContexts.get(frame);
uint32_t exposure = frameContext.agc.exposure;
- uint32_t gain = camHelper_->gainCode(frameContext.agc.gain);
+ uint32_t gain = context_.camHelper->gainCode(frameContext.agc.gain);
ControlList ctrls(sensorControls_);
ctrls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(exposure));
diff --git a/src/ipa/rkisp1/utils.h b/src/ipa/rkisp1/utils.h
index 450f2244..5f38b50b 100644
--- a/src/ipa/rkisp1/utils.h
+++ b/src/ipa/rkisp1/utils.h
@@ -8,7 +8,6 @@
#pragma once
#include <cmath>
-#include <limits>
#include <type_traits>
namespace libcamera {
diff --git a/src/ipa/rpi/cam_helper/cam_helper_imx283.cpp b/src/ipa/rpi/cam_helper/cam_helper_imx283.cpp
new file mode 100644
index 00000000..cb0be72a
--- /dev/null
+++ b/src/ipa/rpi/cam_helper/cam_helper_imx283.cpp
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2024, Raspberry Pi Ltd
+ *
+ * cam_helper_Imx283.cpp - camera information for Imx283 sensor
+ */
+
+#include <assert.h>
+
+#include "cam_helper.h"
+
+using namespace RPiController;
+
+class CamHelperImx283 : public CamHelper
+{
+public:
+ CamHelperImx283();
+ uint32_t gainCode(double gain) const override;
+ double gain(uint32_t gainCode) const override;
+ void getDelays(int &exposureDelay, int &gainDelay,
+ int &vblankDelay, int &hblankDelay) const override;
+ unsigned int hideFramesModeSwitch() const override;
+
+private:
+ /*
+ * Smallest difference between the frame length and integration time,
+ * in units of lines.
+ */
+ static constexpr int frameIntegrationDiff = 4;
+};
+
+/*
+ * Imx283 doesn't output metadata, so we have to use the delayed controls which
+ * works by counting frames.
+ */
+
+CamHelperImx283::CamHelperImx283()
+ : CamHelper({}, frameIntegrationDiff)
+{
+}
+
+uint32_t CamHelperImx283::gainCode(double gain) const
+{
+ return static_cast<uint32_t>(2048.0 - 2048.0 / gain);
+}
+
+double CamHelperImx283::gain(uint32_t gainCode) const
+{
+ return static_cast<double>(2048.0 / (2048 - gainCode));
+}
+
+void CamHelperImx283::getDelays(int &exposureDelay, int &gainDelay,
+ int &vblankDelay, int &hblankDelay) const
+{
+ /* The driver appears to behave as follows: */
+ exposureDelay = 2;
+ gainDelay = 2;
+ vblankDelay = 2;
+ hblankDelay = 2;
+}
+
+unsigned int CamHelperImx283::hideFramesModeSwitch() const
+{
+ /* After a mode switch, we seem to get 1 bad frame. */
+ return 1;
+}
+
+static CamHelper *create()
+{
+ return new CamHelperImx283();
+}
+
+static RegisterCamHelper reg("imx283", &create);
diff --git a/src/ipa/rpi/cam_helper/cam_helper_imx290.cpp b/src/ipa/rpi/cam_helper/cam_helper_imx290.cpp
index 24275e12..e57ab538 100644
--- a/src/ipa/rpi/cam_helper/cam_helper_imx290.cpp
+++ b/src/ipa/rpi/cam_helper/cam_helper_imx290.cpp
@@ -5,7 +5,7 @@
* camera helper for imx290 sensor
*/
-#include <math.h>
+#include <cmath>
#include "cam_helper.h"
@@ -37,13 +37,13 @@ CamHelperImx290::CamHelperImx290()
uint32_t CamHelperImx290::gainCode(double gain) const
{
- int code = 66.6667 * log10(gain);
+ int code = 66.6667 * std::log10(gain);
return std::max(0, std::min(code, 0xf0));
}
double CamHelperImx290::gain(uint32_t gainCode) const
{
- return pow(10, 0.015 * gainCode);
+ return std::pow(10, 0.015 * gainCode);
}
void CamHelperImx290::getDelays(int &exposureDelay, int &gainDelay,
diff --git a/src/ipa/rpi/cam_helper/meson.build b/src/ipa/rpi/cam_helper/meson.build
index 72625057..1e43f1da 100644
--- a/src/ipa/rpi/cam_helper/meson.build
+++ b/src/ipa/rpi/cam_helper/meson.build
@@ -4,6 +4,7 @@ rpi_ipa_cam_helper_sources = files([
'cam_helper.cpp',
'cam_helper_ov5647.cpp',
'cam_helper_imx219.cpp',
+ 'cam_helper_imx283.cpp',
'cam_helper_imx290.cpp',
'cam_helper_imx296.cpp',
'cam_helper_imx477.cpp',
diff --git a/src/ipa/rpi/controller/histogram.cpp b/src/ipa/rpi/controller/histogram.cpp
index ba5b25dd..13089839 100644
--- a/src/ipa/rpi/controller/histogram.cpp
+++ b/src/ipa/rpi/controller/histogram.cpp
@@ -4,7 +4,7 @@
*
* histogram calculations
*/
-#include <math.h>
+#include <cmath>
#include <stdio.h>
#include "histogram.h"
@@ -49,9 +49,9 @@ double Histogram::interBinMean(double binLo, double binHi) const
{
assert(binHi >= binLo);
double sumBinFreq = 0, cumulFreq = 0;
- for (double binNext = floor(binLo) + 1.0; binNext <= ceil(binHi);
+ for (double binNext = std::floor(binLo) + 1.0; binNext <= std::ceil(binHi);
binLo = binNext, binNext += 1.0) {
- int bin = floor(binLo);
+ int bin = std::floor(binLo);
double freq = (cumulative_[bin + 1] - cumulative_[bin]) *
(std::min(binNext, binHi) - binLo);
sumBinFreq += bin * freq;
diff --git a/src/ipa/rpi/controller/rpi/af.cpp b/src/ipa/rpi/controller/rpi/af.cpp
index 5ca76dd9..2157eb94 100644
--- a/src/ipa/rpi/controller/rpi/af.cpp
+++ b/src/ipa/rpi/controller/rpi/af.cpp
@@ -7,8 +7,8 @@
#include "af.h"
+#include <cmath>
#include <iomanip>
-#include <math.h>
#include <stdlib.h>
#include <libcamera/base/log.h>
diff --git a/src/ipa/rpi/controller/rpi/agc_channel.cpp b/src/ipa/rpi/controller/rpi/agc_channel.cpp
index cf2565a8..c9df9b5b 100644
--- a/src/ipa/rpi/controller/rpi/agc_channel.cpp
+++ b/src/ipa/rpi/controller/rpi/agc_channel.cpp
@@ -883,11 +883,14 @@ void AgcChannel::filterExposure()
/*
* AGC adapts instantly if both shutter and gain are directly specified
- * or we're in the startup phase.
+ * or we're in the startup phase. Also disable the stable region, because we want
+ * to reflect any user exposure/gain updates, however small.
*/
if ((status_.fixedShutter && status_.fixedAnalogueGain) ||
- frameCount_ <= config_.startupFrames)
+ frameCount_ <= config_.startupFrames) {
speed = 1.0;
+ stableRegion = 0.0;
+ }
if (!filtered_.totalExposure) {
filtered_.totalExposure = target_.totalExposure;
} else if (filtered_.totalExposure * (1.0 - stableRegion) < target_.totalExposure &&
diff --git a/src/ipa/rpi/controller/rpi/alsc.cpp b/src/ipa/rpi/controller/rpi/alsc.cpp
index 67029fc3..21edb819 100644
--- a/src/ipa/rpi/controller/rpi/alsc.cpp
+++ b/src/ipa/rpi/controller/rpi/alsc.cpp
@@ -6,9 +6,10 @@
*/
#include <algorithm>
+#include <cmath>
#include <functional>
-#include <math.h>
#include <numeric>
+#include <vector>
#include <libcamera/base/log.h>
#include <libcamera/base/span.h>
@@ -251,12 +252,12 @@ static bool compareModes(CameraMode const &cm0, CameraMode const &cm1)
*/
if (cm0.transform != cm1.transform)
return true;
- int leftDiff = abs(cm0.cropX - cm1.cropX);
- int topDiff = abs(cm0.cropY - cm1.cropY);
- int rightDiff = fabs(cm0.cropX + cm0.scaleX * cm0.width -
- cm1.cropX - cm1.scaleX * cm1.width);
- int bottomDiff = fabs(cm0.cropY + cm0.scaleY * cm0.height -
- cm1.cropY - cm1.scaleY * cm1.height);
+ int leftDiff = std::abs(cm0.cropX - cm1.cropX);
+ int topDiff = std::abs(cm0.cropY - cm1.cropY);
+ int rightDiff = std::abs(cm0.cropX + cm0.scaleX * cm0.width -
+ cm1.cropX - cm1.scaleX * cm1.width);
+ int bottomDiff = std::abs(cm0.cropY + cm0.scaleY * cm0.height -
+ cm1.cropY - cm1.scaleY * cm1.height);
/*
* These thresholds are a rather arbitrary amount chosen to trigger
* when carrying on with the previously calculated tables might be
@@ -496,8 +497,9 @@ void resampleCalTable(const Array2D<double> &calTableIn,
* Precalculate and cache the x sampling locations and phases to save
* recomputing them on every row.
*/
- int xLo[X], xHi[X];
- double xf[X];
+ std::vector<int> xLo(X);
+ std::vector<int> xHi(X);
+ std::vector<double> xf(X);
double scaleX = cameraMode.sensorWidth /
(cameraMode.width * cameraMode.scaleX);
double xOff = cameraMode.cropX / (double)cameraMode.sensorWidth;
@@ -730,7 +732,7 @@ static double gaussSeidel2Sor(const SparseArray<double> &M, double omega,
double maxDiff = 0;
for (i = 0; i < XY; i++) {
lambda[i] = oldLambda[i] + (lambda[i] - oldLambda[i]) * omega;
- if (fabs(lambda[i] - oldLambda[i]) > fabs(maxDiff))
+ if (std::abs(lambda[i] - oldLambda[i]) > std::abs(maxDiff))
maxDiff = lambda[i] - oldLambda[i];
}
return maxDiff;
@@ -762,7 +764,7 @@ static void runMatrixIterations(const Array2D<double> &C,
constructM(C, W, M);
double lastMaxDiff = std::numeric_limits<double>::max();
for (unsigned int i = 0; i < nIter; i++) {
- double maxDiff = fabs(gaussSeidel2Sor(M, omega, lambda, lambdaBound));
+ double maxDiff = std::abs(gaussSeidel2Sor(M, omega, lambda, lambdaBound));
if (maxDiff < threshold) {
LOG(RPiAlsc, Debug)
<< "Stop after " << i + 1 << " iterations";
diff --git a/src/ipa/rpi/controller/rpi/awb.cpp b/src/ipa/rpi/controller/rpi/awb.cpp
index f45525bc..e5d51092 100644
--- a/src/ipa/rpi/controller/rpi/awb.cpp
+++ b/src/ipa/rpi/controller/rpi/awb.cpp
@@ -6,6 +6,7 @@
*/
#include <assert.h>
+#include <cmath>
#include <functional>
#include <libcamera/base/log.h>
@@ -505,7 +506,7 @@ static double interpolateQuadatric(ipa::Pwl::Point const &a, ipa::Pwl::Point con
const double eps = 1e-3;
ipa::Pwl::Point ca = c - a, ba = b - a;
double denominator = 2 * (ba.y() * ca.x() - ca.y() * ba.x());
- if (abs(denominator) > eps) {
+ if (std::abs(denominator) > eps) {
double numerator = ba.y() * ca.x() * ca.x() - ca.y() * ba.x() * ba.x();
double result = numerator / denominator + a.x();
return std::max(a.x(), std::min(c.x(), result));
diff --git a/src/ipa/rpi/controller/rpi/black_level.cpp b/src/ipa/rpi/controller/rpi/black_level.cpp
index ea991df9..4c968f14 100644
--- a/src/ipa/rpi/controller/rpi/black_level.cpp
+++ b/src/ipa/rpi/controller/rpi/black_level.cpp
@@ -5,7 +5,6 @@
* black level control algorithm
*/
-#include <math.h>
#include <stdint.h>
#include <libcamera/base/log.h>
diff --git a/src/ipa/rpi/controller/rpi/lux.cpp b/src/ipa/rpi/controller/rpi/lux.cpp
index 7b31faab..652d85d7 100644
--- a/src/ipa/rpi/controller/rpi/lux.cpp
+++ b/src/ipa/rpi/controller/rpi/lux.cpp
@@ -4,7 +4,6 @@
*
* Lux control algorithm
*/
-#include <math.h>
#include <libcamera/base/log.h>
diff --git a/src/ipa/rpi/controller/rpi/noise.cpp b/src/ipa/rpi/controller/rpi/noise.cpp
index 3f1c62cf..145175fb 100644
--- a/src/ipa/rpi/controller/rpi/noise.cpp
+++ b/src/ipa/rpi/controller/rpi/noise.cpp
@@ -5,7 +5,7 @@
* Noise control algorithm
*/
-#include <math.h>
+#include <cmath>
#include <libcamera/base/log.h>
@@ -69,7 +69,7 @@ void Noise::prepare(Metadata *imageMetadata)
* make some adjustments based on the camera mode (such as
* binning), if we knew how to discover it...
*/
- double factor = sqrt(deviceStatus.analogueGain) / modeFactor_;
+ double factor = std::sqrt(deviceStatus.analogueGain) / modeFactor_;
struct NoiseStatus status;
status.noiseConstant = referenceConstant_ * factor;
status.noiseSlope = referenceSlope_ * factor;
diff --git a/src/ipa/rpi/controller/rpi/sharpen.cpp b/src/ipa/rpi/controller/rpi/sharpen.cpp
index 39537f4a..1d143ff5 100644
--- a/src/ipa/rpi/controller/rpi/sharpen.cpp
+++ b/src/ipa/rpi/controller/rpi/sharpen.cpp
@@ -5,7 +5,7 @@
* sharpening control algorithm
*/
-#include <math.h>
+#include <cmath>
#include <libcamera/base/log.h>
@@ -68,7 +68,7 @@ void Sharpen::prepare(Metadata *imageMetadata)
* we adjust the limit and threshold less aggressively. Using a sqrt
* function is an arbitrary but gentle way of accomplishing this.
*/
- double userStrengthSqrt = sqrt(userStrength_);
+ double userStrengthSqrt = std::sqrt(userStrength_);
struct SharpenStatus status;
/*
* Binned modes seem to need the sharpening toned down with this
diff --git a/src/ipa/rpi/vc4/data/imx283.json b/src/ipa/rpi/vc4/data/imx283.json
new file mode 100644
index 00000000..bfacecc8
--- /dev/null
+++ b/src/ipa/rpi/vc4/data/imx283.json
@@ -0,0 +1,313 @@
+{
+ "version": 2.0,
+ "target": "bcm2835",
+ "algorithms": [
+ {
+ "rpi.black_level":
+ {
+ "black_level": 3200
+ }
+ },
+ {
+ "rpi.dpc": { }
+ },
+ {
+ "rpi.lux":
+ {
+ "reference_shutter_speed": 2461,
+ "reference_gain": 1.0,
+ "reference_aperture": 1.0,
+ "reference_lux": 1148,
+ "reference_Y": 13314
+ }
+ },
+ {
+ "rpi.noise":
+ {
+ "reference_constant": 0,
+ "reference_slope": 2.204
+ }
+ },
+ {
+ "rpi.geq":
+ {
+ "offset": 199,
+ "slope": 0.01947
+ }
+ },
+ {
+ "rpi.sdn": { }
+ },
+ {
+ "rpi.awb":
+ {
+ "priors": [
+ {
+ "lux": 0,
+ "prior":
+ [
+ 2000, 1.0,
+ 3000, 0.0,
+ 13000, 0.0
+ ]
+ },
+ {
+ "lux": 800,
+ "prior":
+ [
+ 2000, 0.0,
+ 6000, 2.0,
+ 13000, 2.0
+ ]
+ },
+ {
+ "lux": 1500,
+ "prior":
+ [
+ 2000, 0.0,
+ 4000, 1.0,
+ 6000, 6.0,
+ 6500, 7.0,
+ 7000, 1.0,
+ 13000, 1.0
+ ]
+ }
+ ],
+ "modes":
+ {
+ "auto":
+ {
+ "lo": 2500,
+ "hi": 8000
+ },
+ "incandescent":
+ {
+ "lo": 2500,
+ "hi": 3000
+ },
+ "tungsten":
+ {
+ "lo": 3000,
+ "hi": 3500
+ },
+ "fluorescent":
+ {
+ "lo": 4000,
+ "hi": 4700
+ },
+ "indoor":
+ {
+ "lo": 3000,
+ "hi": 5000
+ },
+ "daylight":
+ {
+ "lo": 5500,
+ "hi": 6500
+ }
+ },
+ "bayes": 1,
+ "ct_curve":
+ [
+ 2213.0, 0.9607, 0.2593,
+ 5313.0, 0.4822, 0.5909,
+ 6237.0, 0.4739, 0.6308
+ ],
+ "sensitivity_r": 1.0,
+ "sensitivity_b": 1.0,
+ "transverse_pos": 0.0144,
+ "transverse_neg": 0.01
+ }
+ },
+ {
+ "rpi.agc":
+ {
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights":
+ [
+ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0
+ ]
+ },
+ "spot":
+ {
+ "weights":
+ [
+ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ ]
+ },
+ "matrix":
+ {
+ "weights":
+ [
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+ ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ]
+ }
+ },
+ {
+ "rpi.alsc":
+ {
+ "omega": 1.3,
+ "n_iter": 100,
+ "luminance_strength": 0.7
+ }
+ },
+ {
+ "rpi.contrast":
+ {
+ "ce_enable": 1,
+ "gamma_curve":
+ [
+ 0, 0,
+ 1024, 5040,
+ 2048, 9338,
+ 3072, 12356,
+ 4096, 15312,
+ 5120, 18051,
+ 6144, 20790,
+ 7168, 23193,
+ 8192, 25744,
+ 9216, 27942,
+ 10240, 30035,
+ 11264, 32005,
+ 12288, 33975,
+ 13312, 35815,
+ 14336, 37600,
+ 15360, 39168,
+ 16384, 40642,
+ 18432, 43379,
+ 20480, 45749,
+ 22528, 47753,
+ 24576, 49621,
+ 26624, 51253,
+ 28672, 52698,
+ 30720, 53796,
+ 32768, 54876,
+ 36864, 57012,
+ 40960, 58656,
+ 45056, 59954,
+ 49152, 61183,
+ 53248, 62355,
+ 57344, 63419,
+ 61440, 64476,
+ 65535, 65535
+ ]
+ }
+ },
+ {
+ "rpi.ccm":
+ {
+ "ccms": [
+ {
+ "ct": 2213,
+ "ccm":
+ [
+ 1.91264, -0.27609, -0.63655,
+ -0.65708, 2.11718, -0.46009,
+ 0.03629, -1.38441, 2.34811
+ ]
+ },
+ {
+ "ct": 2255,
+ "ccm":
+ [
+ 1.90369, -0.29309, -0.61059,
+ -0.64693, 2.08169, -0.43476,
+ 0.04086, -1.29999, 2.25914
+ ]
+ },
+ {
+ "ct": 2259,
+ "ccm":
+ [
+ 1.92762, -0.35134, -0.57628,
+ -0.63523, 2.08481, -0.44958,
+ 0.06754, -1.32953, 2.26199
+ ]
+ },
+ {
+ "ct": 5313,
+ "ccm":
+ [
+ 1.75924, -0.54053, -0.21871,
+ -0.38159, 1.88671, -0.50511,
+ -0.00747, -0.53492, 1.54239
+ ]
+ },
+ {
+ "ct": 6237,
+ "ccm":
+ [
+ 2.19299, -0.74764, -0.44536,
+ -0.51678, 2.27651, -0.75972,
+ -0.06498, -0.74269, 1.80767
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "rpi.sharpen": { }
+ }
+ ]
+}
diff --git a/src/ipa/rpi/vc4/data/meson.build b/src/ipa/rpi/vc4/data/meson.build
index afbf875a..60477c11 100644
--- a/src/ipa/rpi/vc4/data/meson.build
+++ b/src/ipa/rpi/vc4/data/meson.build
@@ -3,6 +3,7 @@
conf_files = files([
'imx219.json',
'imx219_noir.json',
+ 'imx283.json',
'imx290.json',
'imx296.json',
'imx296_mono.json',
diff --git a/src/ipa/rpi/vc4/meson.build b/src/ipa/rpi/vc4/meson.build
index 63fc5925..c10fa17e 100644
--- a/src/ipa/rpi/vc4/meson.build
+++ b/src/ipa/rpi/vc4/meson.build
@@ -23,8 +23,7 @@ vc4_ipa_sources = files([
vc4_ipa_includes += include_directories('..')
-mod = shared_module(ipa_name,
- [vc4_ipa_sources, libcamera_generated_ipa_headers],
+mod = shared_module(ipa_name, vc4_ipa_sources,
name_prefix : '',
include_directories : vc4_ipa_includes,
dependencies : [vc4_ipa_deps, libipa_dep],
diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp
new file mode 100644
index 00000000..783dfb8b
--- /dev/null
+++ b/src/ipa/simple/algorithms/agc.cpp
@@ -0,0 +1,139 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Red Hat Inc.
+ *
+ * Exposure and gain
+ */
+
+#include "agc.h"
+
+#include <stdint.h>
+
+#include <libcamera/base/log.h>
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(IPASoftExposure)
+
+namespace ipa::soft::algorithms {
+
+/*
+ * The number of bins to use for the optimal exposure calculations.
+ */
+static constexpr unsigned int kExposureBinsCount = 5;
+
+/*
+ * The exposure is optimal when the mean sample value of the histogram is
+ * in the middle of the range.
+ */
+static constexpr float kExposureOptimal = kExposureBinsCount / 2.0;
+
+/*
+ * The below value implements the hysteresis for the exposure adjustment.
+ * It is small enough to have the exposure close to the optimal, and is big
+ * enough to prevent the exposure from wobbling around the optimal value.
+ */
+static constexpr float kExposureSatisfactory = 0.2;
+
+Agc::Agc()
+{
+}
+
+void Agc::updateExposure(IPAContext &context, double exposureMSV)
+{
+ /*
+ * kExpDenominator of 10 gives ~10% increment/decrement;
+ * kExpDenominator of 5 - about ~20%
+ */
+ static constexpr uint8_t kExpDenominator = 10;
+ static constexpr uint8_t kExpNumeratorUp = kExpDenominator + 1;
+ static constexpr uint8_t kExpNumeratorDown = kExpDenominator - 1;
+
+ double next;
+ int32_t &exposure = context.activeState.agc.exposure;
+ double &again = context.activeState.agc.again;
+
+ if (exposureMSV < kExposureOptimal - kExposureSatisfactory) {
+ next = exposure * kExpNumeratorUp / kExpDenominator;
+ if (next - exposure < 1)
+ exposure += 1;
+ else
+ exposure = next;
+ if (exposure >= context.configuration.agc.exposureMax) {
+ next = again * kExpNumeratorUp / kExpDenominator;
+ if (next - again < context.configuration.agc.againMinStep)
+ again += context.configuration.agc.againMinStep;
+ else
+ again = next;
+ }
+ }
+
+ if (exposureMSV > kExposureOptimal + kExposureSatisfactory) {
+ if (exposure == context.configuration.agc.exposureMax &&
+ again > context.configuration.agc.againMin) {
+ next = again * kExpNumeratorDown / kExpDenominator;
+ if (again - next < context.configuration.agc.againMinStep)
+ again -= context.configuration.agc.againMinStep;
+ else
+ again = next;
+ } else {
+ next = exposure * kExpNumeratorDown / kExpDenominator;
+ if (exposure - next < 1)
+ exposure -= 1;
+ else
+ exposure = next;
+ }
+ }
+
+ exposure = std::clamp(exposure, context.configuration.agc.exposureMin,
+ context.configuration.agc.exposureMax);
+ again = std::clamp(again, context.configuration.agc.againMin,
+ context.configuration.agc.againMax);
+
+ LOG(IPASoftExposure, Debug)
+ << "exposureMSV " << exposureMSV
+ << " exp " << exposure << " again " << again;
+}
+
+void Agc::process(IPAContext &context,
+ [[maybe_unused]] const uint32_t frame,
+ [[maybe_unused]] IPAFrameContext &frameContext,
+ const SwIspStats *stats,
+ [[maybe_unused]] ControlList &metadata)
+{
+ /*
+ * Calculate Mean Sample Value (MSV) according to formula from:
+ * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf
+ */
+ const auto &histogram = stats->yHistogram;
+ const unsigned int blackLevelHistIdx =
+ context.activeState.blc.level / (256 / SwIspStats::kYHistogramSize);
+ const unsigned int histogramSize =
+ SwIspStats::kYHistogramSize - blackLevelHistIdx;
+ const unsigned int yHistValsPerBin = histogramSize / kExposureBinsCount;
+ const unsigned int yHistValsPerBinMod =
+ histogramSize / (histogramSize % kExposureBinsCount + 1);
+ int exposureBins[kExposureBinsCount] = {};
+ unsigned int denom = 0;
+ unsigned int num = 0;
+
+ for (unsigned int i = 0; i < histogramSize; i++) {
+ unsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin;
+ exposureBins[idx] += histogram[blackLevelHistIdx + i];
+ }
+
+ for (unsigned int i = 0; i < kExposureBinsCount; i++) {
+ LOG(IPASoftExposure, Debug) << i << ": " << exposureBins[i];
+ denom += exposureBins[i];
+ num += exposureBins[i] * (i + 1);
+ }
+
+ float exposureMSV = (denom == 0 ? 0 : static_cast<float>(num) / denom);
+ updateExposure(context, exposureMSV);
+}
+
+REGISTER_IPA_ALGORITHM(Agc, "Agc")
+
+} /* namespace ipa::soft::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/algorithms/agc.h b/src/ipa/simple/algorithms/agc.h
new file mode 100644
index 00000000..ad5fca9f
--- /dev/null
+++ b/src/ipa/simple/algorithms/agc.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Red Hat Inc.
+ *
+ * Exposure and gain
+ */
+
+#pragma once
+
+#include "algorithm.h"
+
+namespace libcamera {
+
+namespace ipa::soft::algorithms {
+
+class Agc : public Algorithm
+{
+public:
+ Agc();
+ ~Agc() = default;
+
+ void process(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const SwIspStats *stats,
+ ControlList &metadata) override;
+
+private:
+ void updateExposure(IPAContext &context, double exposureMSV);
+};
+
+} /* namespace ipa::soft::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/algorithms/algorithm.h b/src/ipa/simple/algorithms/algorithm.h
new file mode 100644
index 00000000..41f63170
--- /dev/null
+++ b/src/ipa/simple/algorithms/algorithm.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * Software ISP control algorithm interface
+ */
+
+#pragma once
+
+#include <libipa/algorithm.h>
+
+#include "module.h"
+
+namespace libcamera {
+
+namespace ipa::soft {
+
+using Algorithm = libcamera::ipa::Algorithm<Module>;
+
+} /* namespace ipa::soft */
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp
new file mode 100644
index 00000000..195de41d
--- /dev/null
+++ b/src/ipa/simple/algorithms/awb.cpp
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Red Hat Inc.
+ *
+ * Auto white balance
+ */
+
+#include "awb.h"
+
+#include <numeric>
+#include <stdint.h>
+
+#include <libcamera/base/log.h>
+
+#include "simple/ipa_context.h"
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(IPASoftAwb)
+
+namespace ipa::soft::algorithms {
+
+int Awb::configure(IPAContext &context,
+ [[maybe_unused]] const IPAConfigInfo &configInfo)
+{
+ auto &gains = context.activeState.gains;
+ gains.red = gains.green = gains.blue = 1.0;
+
+ return 0;
+}
+
+void Awb::process(IPAContext &context,
+ [[maybe_unused]] const uint32_t frame,
+ [[maybe_unused]] IPAFrameContext &frameContext,
+ const SwIspStats *stats,
+ [[maybe_unused]] ControlList &metadata)
+{
+ const SwIspStats::Histogram &histogram = stats->yHistogram;
+ const uint8_t blackLevel = context.activeState.blc.level;
+
+ /*
+ * Black level must be subtracted to get the correct AWB ratios, they
+ * would be off if they were computed from the whole brightness range
+ * rather than from the sensor range.
+ */
+ const uint64_t nPixels = std::accumulate(
+ histogram.begin(), histogram.end(), 0);
+ const uint64_t offset = blackLevel * nPixels;
+ const uint64_t sumR = stats->sumR_ - offset / 4;
+ const uint64_t sumG = stats->sumG_ - offset / 2;
+ const uint64_t sumB = stats->sumB_ - offset / 4;
+
+ /*
+ * Calculate red and blue gains for AWB.
+ * Clamp max gain at 4.0, this also avoids 0 division.
+ */
+ auto &gains = context.activeState.gains;
+ gains.red = sumR <= sumG / 4 ? 4.0 : static_cast<double>(sumG) / sumR;
+ gains.blue = sumB <= sumG / 4 ? 4.0 : static_cast<double>(sumG) / sumB;
+ /* Green gain is fixed to 1.0 */
+
+ LOG(IPASoftAwb, Debug) << "gain R/B " << gains.red << "/" << gains.blue;
+}
+
+REGISTER_IPA_ALGORITHM(Awb, "Awb")
+
+} /* namespace ipa::soft::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/algorithms/awb.h b/src/ipa/simple/algorithms/awb.h
new file mode 100644
index 00000000..db1496cd
--- /dev/null
+++ b/src/ipa/simple/algorithms/awb.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Red Hat Inc.
+ *
+ * Auto white balance
+ */
+
+#pragma once
+
+#include "algorithm.h"
+
+namespace libcamera {
+
+namespace ipa::soft::algorithms {
+
+class Awb : public Algorithm
+{
+public:
+ Awb() = default;
+ ~Awb() = default;
+
+ int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
+ void process(IPAContext &context,
+ const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const SwIspStats *stats,
+ ControlList &metadata) override;
+};
+
+} /* namespace ipa::soft::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/algorithms/blc.cpp b/src/ipa/simple/algorithms/blc.cpp
new file mode 100644
index 00000000..b9f2aaa6
--- /dev/null
+++ b/src/ipa/simple/algorithms/blc.cpp
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Red Hat Inc.
+ *
+ * Black level handling
+ */
+
+#include "blc.h"
+
+#include <numeric>
+
+#include <libcamera/base/log.h>
+
+namespace libcamera {
+
+namespace ipa::soft::algorithms {
+
+LOG_DEFINE_CATEGORY(IPASoftBL)
+
+BlackLevel::BlackLevel()
+{
+}
+
+int BlackLevel::configure(IPAContext &context,
+ [[maybe_unused]] const IPAConfigInfo &configInfo)
+{
+ context.activeState.blc.level = 255;
+ return 0;
+}
+
+void BlackLevel::process(IPAContext &context,
+ [[maybe_unused]] const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const SwIspStats *stats,
+ [[maybe_unused]] ControlList &metadata)
+{
+ if (frameContext.sensor.exposure == exposure_ &&
+ frameContext.sensor.gain == gain_) {
+ return;
+ }
+
+ const SwIspStats::Histogram &histogram = stats->yHistogram;
+
+ /*
+ * The constant is selected to be "good enough", not overly
+ * conservative or aggressive. There is no magic about the given value.
+ */
+ constexpr float ignoredPercentage = 0.02;
+ const unsigned int total =
+ std::accumulate(begin(histogram), end(histogram), 0);
+ const unsigned int pixelThreshold = ignoredPercentage * total;
+ const unsigned int histogramRatio = 256 / SwIspStats::kYHistogramSize;
+ const unsigned int currentBlackIdx =
+ context.activeState.blc.level / histogramRatio;
+
+ for (unsigned int i = 0, seen = 0;
+ i < currentBlackIdx && i < SwIspStats::kYHistogramSize;
+ i++) {
+ seen += histogram[i];
+ if (seen >= pixelThreshold) {
+ context.activeState.blc.level = i * histogramRatio;
+ exposure_ = frameContext.sensor.exposure;
+ gain_ = frameContext.sensor.gain;
+ LOG(IPASoftBL, Debug)
+ << "Auto-set black level: "
+ << i << "/" << SwIspStats::kYHistogramSize
+ << " (" << 100 * (seen - histogram[i]) / total << "% below, "
+ << 100 * seen / total << "% at or below)";
+ break;
+ }
+ };
+}
+
+REGISTER_IPA_ALGORITHM(BlackLevel, "BlackLevel")
+
+} /* namespace ipa::soft::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/algorithms/blc.h b/src/ipa/simple/algorithms/blc.h
new file mode 100644
index 00000000..828ad8b1
--- /dev/null
+++ b/src/ipa/simple/algorithms/blc.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Red Hat Inc.
+ *
+ * Black level handling
+ */
+
+#pragma once
+
+#include "algorithm.h"
+
+namespace libcamera {
+
+namespace ipa::soft::algorithms {
+
+class BlackLevel : public Algorithm
+{
+public:
+ BlackLevel();
+ ~BlackLevel() = default;
+
+ int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
+ void process(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const SwIspStats *stats,
+ ControlList &metadata) override;
+
+private:
+ uint32_t exposure_;
+ double gain_;
+};
+
+} /* namespace ipa::soft::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/algorithms/lut.cpp b/src/ipa/simple/algorithms/lut.cpp
new file mode 100644
index 00000000..9744e773
--- /dev/null
+++ b/src/ipa/simple/algorithms/lut.cpp
@@ -0,0 +1,86 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Red Hat Inc.
+ *
+ * Color lookup tables construction
+ */
+
+#include "lut.h"
+
+#include <algorithm>
+#include <cmath>
+#include <stdint.h>
+
+#include <libcamera/base/log.h>
+
+#include "simple/ipa_context.h"
+
+namespace libcamera {
+
+namespace ipa::soft::algorithms {
+
+int Lut::configure(IPAContext &context,
+ [[maybe_unused]] const IPAConfigInfo &configInfo)
+{
+ /* Gamma value is fixed */
+ context.configuration.gamma = 0.5;
+ updateGammaTable(context);
+
+ return 0;
+}
+
+void Lut::updateGammaTable(IPAContext &context)
+{
+ auto &gammaTable = context.activeState.gamma.gammaTable;
+ auto blackLevel = context.activeState.blc.level;
+ const unsigned int blackIndex = blackLevel * gammaTable.size() / 256;
+
+ std::fill(gammaTable.begin(), gammaTable.begin() + blackIndex, 0);
+ const float divisor = gammaTable.size() - blackIndex - 1.0;
+ for (unsigned int i = blackIndex; i < gammaTable.size(); i++)
+ gammaTable[i] = UINT8_MAX * std::pow((i - blackIndex) / divisor,
+ context.configuration.gamma);
+
+ context.activeState.gamma.blackLevel = blackLevel;
+}
+
+void Lut::prepare(IPAContext &context,
+ [[maybe_unused]] const uint32_t frame,
+ [[maybe_unused]] IPAFrameContext &frameContext,
+ [[maybe_unused]] DebayerParams *params)
+{
+ /*
+ * Update the gamma table if needed. This means if black level changes
+ * and since the black level gets updated only if a lower value is
+ * observed, it's not permanently prone to minor fluctuations or
+ * rounding errors.
+ */
+ if (context.activeState.gamma.blackLevel != context.activeState.blc.level)
+ updateGammaTable(context);
+
+ auto &gains = context.activeState.gains;
+ auto &gammaTable = context.activeState.gamma.gammaTable;
+ const unsigned int gammaTableSize = gammaTable.size();
+
+ for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {
+ const double div = static_cast<double>(DebayerParams::kRGBLookupSize) /
+ gammaTableSize;
+ /* Apply gamma after gain! */
+ unsigned int idx;
+ idx = std::min({ static_cast<unsigned int>(i * gains.red / div),
+ gammaTableSize - 1 });
+ params->red[i] = gammaTable[idx];
+ idx = std::min({ static_cast<unsigned int>(i * gains.green / div),
+ gammaTableSize - 1 });
+ params->green[i] = gammaTable[idx];
+ idx = std::min({ static_cast<unsigned int>(i * gains.blue / div),
+ gammaTableSize - 1 });
+ params->blue[i] = gammaTable[idx];
+ }
+}
+
+REGISTER_IPA_ALGORITHM(Lut, "Lut")
+
+} /* namespace ipa::soft::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/algorithms/lut.h b/src/ipa/simple/algorithms/lut.h
new file mode 100644
index 00000000..b635987d
--- /dev/null
+++ b/src/ipa/simple/algorithms/lut.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Red Hat Inc.
+ *
+ * Color lookup tables construction
+ */
+
+#pragma once
+
+#include "algorithm.h"
+
+namespace libcamera {
+
+namespace ipa::soft::algorithms {
+
+class Lut : public Algorithm
+{
+public:
+ Lut() = default;
+ ~Lut() = default;
+
+ int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
+ void prepare(IPAContext &context,
+ const uint32_t frame,
+ IPAFrameContext &frameContext,
+ DebayerParams *params) override;
+
+private:
+ void updateGammaTable(IPAContext &context);
+};
+
+} /* namespace ipa::soft::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/algorithms/meson.build b/src/ipa/simple/algorithms/meson.build
new file mode 100644
index 00000000..37a2eb53
--- /dev/null
+++ b/src/ipa/simple/algorithms/meson.build
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: CC0-1.0
+
+soft_simple_ipa_algorithms = files([
+ 'awb.cpp',
+ 'agc.cpp',
+ 'blc.cpp',
+ 'lut.cpp',
+])
diff --git a/src/ipa/simple/black_level.cpp b/src/ipa/simple/black_level.cpp
deleted file mode 100644
index cc490eb5..00000000
--- a/src/ipa/simple/black_level.cpp
+++ /dev/null
@@ -1,88 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2024, Red Hat Inc.
- *
- * black level handling
- */
-
-#include "black_level.h"
-
-#include <numeric>
-
-#include <libcamera/base/log.h>
-
-namespace libcamera {
-
-LOG_DEFINE_CATEGORY(IPASoftBL)
-
-/**
- * \class BlackLevel
- * \brief Object providing black point level for software ISP
- *
- * Black level can be provided in hardware tuning files or, if no tuning file is
- * available for the given hardware, guessed automatically, with less accuracy.
- * As tuning files are not yet implemented for software ISP, BlackLevel
- * currently provides only guessed black levels.
- *
- * This class serves for tracking black level as a property of the underlying
- * hardware, not as means of enhancing a particular scene or image.
- *
- * The class is supposed to be instantiated for the given camera stream.
- * The black level can be retrieved using BlackLevel::get() method. It is
- * initially 0 and may change when updated using BlackLevel::update() method.
- */
-
-BlackLevel::BlackLevel()
- : blackLevel_(255), blackLevelSet_(false)
-{
-}
-
-/**
- * \brief Return the current black level
- *
- * \return The black level, in the range from 0 (minimum) to 255 (maximum).
- * If the black level couldn't be determined yet, return 0.
- */
-uint8_t BlackLevel::get() const
-{
- return blackLevelSet_ ? blackLevel_ : 0;
-}
-
-/**
- * \brief Update black level from the provided histogram
- * \param[in] yHistogram The histogram to be used for updating black level
- *
- * The black level is property of the given hardware, not image. It is updated
- * only if it has not been yet set or if it is lower than the lowest value seen
- * so far.
- */
-void BlackLevel::update(SwIspStats::Histogram &yHistogram)
-{
- /*
- * The constant is selected to be "good enough", not overly conservative or
- * aggressive. There is no magic about the given value.
- */
- constexpr float ignoredPercentage_ = 0.02;
- const unsigned int total =
- std::accumulate(begin(yHistogram), end(yHistogram), 0);
- const unsigned int pixelThreshold = ignoredPercentage_ * total;
- const unsigned int histogramRatio = 256 / SwIspStats::kYHistogramSize;
- const unsigned int currentBlackIdx = blackLevel_ / histogramRatio;
-
- for (unsigned int i = 0, seen = 0;
- i < currentBlackIdx && i < SwIspStats::kYHistogramSize;
- i++) {
- seen += yHistogram[i];
- if (seen >= pixelThreshold) {
- blackLevel_ = i * histogramRatio;
- blackLevelSet_ = true;
- LOG(IPASoftBL, Debug)
- << "Auto-set black level: "
- << i << "/" << SwIspStats::kYHistogramSize
- << " (" << 100 * (seen - yHistogram[i]) / total << "% below, "
- << 100 * seen / total << "% at or below)";
- break;
- }
- };
-}
-} /* namespace libcamera */
diff --git a/src/ipa/simple/black_level.h b/src/ipa/simple/black_level.h
deleted file mode 100644
index 5e032f9f..00000000
--- a/src/ipa/simple/black_level.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2024, Red Hat Inc.
- *
- * black level handling
- */
-
-#pragma once
-
-#include <array>
-#include <stdint.h>
-
-#include "libcamera/internal/software_isp/swisp_stats.h"
-
-namespace libcamera {
-
-class BlackLevel
-{
-public:
- BlackLevel();
- uint8_t get() const;
- void update(SwIspStats::Histogram &yHistogram);
-
-private:
- uint8_t blackLevel_;
- bool blackLevelSet_;
-};
-
-} /* namespace libcamera */
diff --git a/src/ipa/simple/data/uncalibrated.yaml b/src/ipa/simple/data/uncalibrated.yaml
index ff981a1a..3f147112 100644
--- a/src/ipa/simple/data/uncalibrated.yaml
+++ b/src/ipa/simple/data/uncalibrated.yaml
@@ -2,4 +2,9 @@
%YAML 1.1
---
version: 1
+algorithms:
+ - BlackLevel:
+ - Awb:
+ - Lut:
+ - Agc:
...
diff --git a/src/ipa/simple/ipa_context.cpp b/src/ipa/simple/ipa_context.cpp
new file mode 100644
index 00000000..3f94bbeb
--- /dev/null
+++ b/src/ipa/simple/ipa_context.cpp
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2021, Google Inc.
+ * Copyright (C) 2024 Red Hat Inc.
+ *
+ * Software ISP IPA Context
+ */
+
+#include "ipa_context.h"
+
+/**
+ * \file ipa_context.h
+ * \brief Context and state information shared between the algorithms
+ */
+
+namespace libcamera::ipa::soft {
+
+/**
+ * \struct IPASessionConfiguration
+ * \brief Session configuration for the IPA module
+ *
+ * The session configuration contains all IPA configuration parameters that
+ * remain constant during the capture session, from IPA module start to stop.
+ * It is typically set during the configure() operation of the IPA module, but
+ * may also be updated in the start() operation.
+ */
+
+/**
+ * \struct IPAActiveState
+ * \brief The active state of the IPA algorithms
+ *
+ * The IPA is fed with the statistics generated from the latest frame processed.
+ * The statistics are then processed by the IPA algorithms to compute parameters
+ * required for the next frame capture and processing. The current state of the
+ * algorithms is reflected through the IPAActiveState to store the values most
+ * recently computed by the IPA algorithms.
+ */
+
+/**
+ * \struct IPAContext
+ * \brief Global IPA context data shared between all algorithms
+ *
+ * \var IPAContext::configuration
+ * \brief The IPA session configuration, immutable during the session
+ *
+ * \var IPAContext::frameContexts
+ * \brief Ring buffer of the IPAFrameContext(s)
+ *
+ * \var IPAContext::activeState
+ * \brief The current state of IPA algorithms
+ */
+
+/**
+ * \var IPASessionConfiguration::gamma
+ * \brief Gamma value to be used in the raw image processing
+ */
+
+/**
+ * \var IPAActiveState::black
+ * \brief Context for the Black Level algorithm
+ *
+ * \var IPAActiveState::black.level
+ * \brief Current determined black level
+ */
+
+/**
+ * \var IPAActiveState::gains
+ * \brief Context for gains in the Colors algorithm
+ *
+ * \var IPAActiveState::gains.red
+ * \brief Gain of red color
+ *
+ * \var IPAActiveState::gains.green
+ * \brief Gain of green color
+ *
+ * \var IPAActiveState::gains.blue
+ * \brief Gain of blue color
+ */
+
+/**
+ * \var IPAActiveState::agc
+ * \brief Context for the AGC algorithm
+ *
+ * \var IPAActiveState::agc.exposure
+ * \brief Current exposure value
+ *
+ * \var IPAActiveState::agc.again
+ * \brief Current analog gain value
+ */
+
+/**
+ * \var IPAActiveState::gamma
+ * \brief Context for gamma in the Colors algorithm
+ *
+ * \var IPAActiveState::gamma.gammaTable
+ * \brief Computed gamma table
+ *
+ * \var IPAActiveState::gamma.blackLevel
+ * \brief Black level used for the gamma table computation
+ */
+
+} /* namespace libcamera::ipa::soft */
diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h
new file mode 100644
index 00000000..3519f20f
--- /dev/null
+++ b/src/ipa/simple/ipa_context.h
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * Simple pipeline IPA Context
+ */
+
+#pragma once
+
+#include <array>
+#include <stdint.h>
+
+#include <libipa/fc_queue.h>
+
+namespace libcamera {
+
+namespace ipa::soft {
+
+struct IPASessionConfiguration {
+ float gamma;
+ struct {
+ int32_t exposureMin, exposureMax;
+ double againMin, againMax, againMinStep;
+ } agc;
+};
+
+struct IPAActiveState {
+ struct {
+ uint8_t level;
+ } blc;
+
+ struct {
+ double red;
+ double green;
+ double blue;
+ } gains;
+
+ struct {
+ int32_t exposure;
+ double again;
+ } agc;
+
+ static constexpr unsigned int kGammaLookupSize = 1024;
+ struct {
+ std::array<double, kGammaLookupSize> gammaTable;
+ uint8_t blackLevel;
+ } gamma;
+};
+
+struct IPAFrameContext : public FrameContext {
+ struct {
+ uint32_t exposure;
+ double gain;
+ } sensor;
+};
+
+struct IPAContext {
+ IPASessionConfiguration configuration;
+ IPAActiveState activeState;
+ FCQueue<IPAFrameContext> frameContexts;
+};
+
+} /* namespace ipa::soft */
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/meson.build b/src/ipa/simple/meson.build
index 33d1c96a..2f9f15f4 100644
--- a/src/ipa/simple/meson.build
+++ b/src/ipa/simple/meson.build
@@ -1,14 +1,18 @@
# SPDX-License-Identifier: CC0-1.0
+subdir('algorithms')
+subdir('data')
+
ipa_name = 'ipa_soft_simple'
soft_simple_sources = files([
+ 'ipa_context.cpp',
'soft_simple.cpp',
- 'black_level.cpp',
])
-mod = shared_module(ipa_name,
- [soft_simple_sources, libcamera_generated_ipa_headers],
+soft_simple_sources += soft_simple_ipa_algorithms
+
+mod = shared_module(ipa_name, soft_simple_sources,
name_prefix : '',
include_directories : [ipa_includes],
dependencies : [libcamera_private, libipa_dep],
@@ -24,6 +28,4 @@ if ipa_sign_module
build_by_default : true)
endif
-subdir('data')
-
ipa_names += ipa_name
diff --git a/src/ipa/simple/module.h b/src/ipa/simple/module.h
new file mode 100644
index 00000000..8d4d53fb
--- /dev/null
+++ b/src/ipa/simple/module.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024 Red Hat, Inc.
+ *
+ * Software ISP IPA Module
+ */
+
+#pragma once
+
+#include <libcamera/controls.h>
+
+#include <libcamera/ipa/soft_ipa_interface.h>
+
+#include "libcamera/internal/software_isp/debayer_params.h"
+#include "libcamera/internal/software_isp/swisp_stats.h"
+
+#include <libipa/module.h>
+
+#include "ipa_context.h"
+
+namespace libcamera {
+
+namespace ipa::soft {
+
+using Module = ipa::Module<IPAContext, IPAFrameContext, IPAConfigInfo,
+ DebayerParams, SwIspStats>;
+
+} /* namespace ipa::soft */
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp
index b7746ce0..b28c7039 100644
--- a/src/ipa/simple/soft_simple.cpp
+++ b/src/ipa/simple/soft_simple.cpp
@@ -5,8 +5,6 @@
* Simple Software Image Processing Algorithm module
*/
-#include <cmath>
-#include <numeric>
#include <stdint.h>
#include <sys/mman.h>
@@ -29,37 +27,21 @@
#include "libipa/camera_sensor_helper.h"
-#include "black_level.h"
+#include "module.h"
namespace libcamera {
LOG_DEFINE_CATEGORY(IPASoft)
namespace ipa::soft {
-/*
- * The number of bins to use for the optimal exposure calculations.
- */
-static constexpr unsigned int kExposureBinsCount = 5;
-
-/*
- * The exposure is optimal when the mean sample value of the histogram is
- * in the middle of the range.
- */
-static constexpr float kExposureOptimal = kExposureBinsCount / 2.0;
-
-/*
- * The below value implements the hysteresis for the exposure adjustment.
- * It is small enough to have the exposure close to the optimal, and is big
- * enough to prevent the exposure from wobbling around the optimal value.
- */
-static constexpr float kExposureSatisfactory = 0.2;
+/* Maximum number of frame contexts to be held */
+static constexpr uint32_t kMaxFrameContexts = 16;
-class IPASoftSimple : public ipa::soft::IPASoftInterface
+class IPASoftSimple : public ipa::soft::IPASoftInterface, public Module
{
public:
IPASoftSimple()
- : params_(nullptr), stats_(nullptr), blackLevel_(BlackLevel()),
- ignoreUpdates_(0)
+ : context_({ {}, {}, { kMaxFrameContexts } })
{
}
@@ -69,12 +51,18 @@ public:
const SharedFD &fdStats,
const SharedFD &fdParams,
const ControlInfoMap &sensorInfoMap) override;
- int configure(const ControlInfoMap &sensorInfoMap) override;
+ int configure(const IPAConfigInfo &configInfo) override;
int start() override;
void stop() override;
- void processStats(const ControlList &sensorControls) override;
+ void queueRequest(const uint32_t frame, const ControlList &controls) override;
+ void fillParamsBuffer(const uint32_t frame) override;
+ void processStats(const uint32_t frame, const uint32_t bufferId,
+ const ControlList &sensorControls) override;
+
+protected:
+ std::string logPrefix() const override;
private:
void updateExposure(double exposureMSV);
@@ -83,17 +71,9 @@ private:
SwIspStats *stats_;
std::unique_ptr<CameraSensorHelper> camHelper_;
ControlInfoMap sensorInfoMap_;
- BlackLevel blackLevel_;
- static constexpr unsigned int kGammaLookupSize = 1024;
- std::array<uint8_t, kGammaLookupSize> gammaTable_;
- int lastBlackLevel_ = -1;
-
- int32_t exposureMin_, exposureMax_;
- int32_t exposure_;
- double againMin_, againMax_, againMinStep_;
- double again_;
- unsigned int ignoreUpdates_;
+ /* Local parameter storage */
+ struct IPAContext context_;
};
IPASoftSimple::~IPASoftSimple()
@@ -134,6 +114,15 @@ int IPASoftSimple::init(const IPASettings &settings,
unsigned int version = (*data)["version"].get<uint32_t>(0);
LOG(IPASoft, Debug) << "Tuning file version " << version;
+ if (!data->contains("algorithms")) {
+ LOG(IPASoft, Error) << "Tuning file doesn't contain algorithms";
+ return -EINVAL;
+ }
+
+ int ret = createAlgorithms(context_, (*data)["algorithms"]);
+ if (ret)
+ return ret;
+
params_ = nullptr;
stats_ = nullptr;
@@ -188,27 +177,30 @@ int IPASoftSimple::init(const IPASettings &settings,
return 0;
}
-int IPASoftSimple::configure(const ControlInfoMap &sensorInfoMap)
+int IPASoftSimple::configure(const IPAConfigInfo &configInfo)
{
- sensorInfoMap_ = sensorInfoMap;
+ sensorInfoMap_ = configInfo.sensorControls;
const ControlInfo &exposureInfo = sensorInfoMap_.find(V4L2_CID_EXPOSURE)->second;
const ControlInfo &gainInfo = sensorInfoMap_.find(V4L2_CID_ANALOGUE_GAIN)->second;
- exposureMin_ = exposureInfo.min().get<int32_t>();
- exposureMax_ = exposureInfo.max().get<int32_t>();
- if (!exposureMin_) {
+ context_.configuration.agc.exposureMin = exposureInfo.min().get<int32_t>();
+ context_.configuration.agc.exposureMax = exposureInfo.max().get<int32_t>();
+ if (!context_.configuration.agc.exposureMin) {
LOG(IPASoft, Warning) << "Minimum exposure is zero, that can't be linear";
- exposureMin_ = 1;
+ context_.configuration.agc.exposureMin = 1;
}
int32_t againMin = gainInfo.min().get<int32_t>();
int32_t againMax = gainInfo.max().get<int32_t>();
if (camHelper_) {
- againMin_ = camHelper_->gain(againMin);
- againMax_ = camHelper_->gain(againMax);
- againMinStep_ = (againMax_ - againMin_) / 100.0;
+ context_.configuration.agc.againMin = camHelper_->gain(againMin);
+ context_.configuration.agc.againMax = camHelper_->gain(againMax);
+ context_.configuration.agc.againMinStep =
+ (context_.configuration.agc.againMax -
+ context_.configuration.agc.againMin) /
+ 100.0;
} else {
/*
* The camera sensor gain (g) is usually not equal to the value written
@@ -220,18 +212,28 @@ int IPASoftSimple::configure(const ControlInfoMap &sensorInfoMap)
* the AGC algorithm (abrupt near one edge, and very small near the
* other) we limit the range of the gain values used.
*/
- againMax_ = againMax;
+ context_.configuration.agc.againMax = againMax;
if (!againMin) {
LOG(IPASoft, Warning)
<< "Minimum gain is zero, that can't be linear";
- againMin_ = std::min(100, againMin / 2 + againMax / 2);
+ context_.configuration.agc.againMin =
+ std::min(100, againMin / 2 + againMax / 2);
}
- againMinStep_ = 1.0;
+ context_.configuration.agc.againMinStep = 1.0;
+ }
+
+ for (auto const &algo : algorithms()) {
+ int ret = algo->configure(context_, configInfo);
+ if (ret)
+ return ret;
}
- LOG(IPASoft, Info) << "Exposure " << exposureMin_ << "-" << exposureMax_
- << ", gain " << againMin_ << "-" << againMax_
- << " (" << againMinStep_ << ")";
+ LOG(IPASoft, Info)
+ << "Exposure " << context_.configuration.agc.exposureMin << "-"
+ << context_.configuration.agc.exposureMax
+ << ", gain " << context_.configuration.agc.againMin << "-"
+ << context_.configuration.agc.againMax
+ << " (" << context_.configuration.agc.againMinStep << ")";
return 0;
}
@@ -245,105 +247,42 @@ void IPASoftSimple::stop()
{
}
-void IPASoftSimple::processStats(const ControlList &sensorControls)
+void IPASoftSimple::queueRequest(const uint32_t frame, const ControlList &controls)
{
- SwIspStats::Histogram histogram = stats_->yHistogram;
- if (ignoreUpdates_ > 0)
- blackLevel_.update(histogram);
- const uint8_t blackLevel = blackLevel_.get();
-
- /*
- * Black level must be subtracted to get the correct AWB ratios, they
- * would be off if they were computed from the whole brightness range
- * rather than from the sensor range.
- */
- const uint64_t nPixels = std::accumulate(
- histogram.begin(), histogram.end(), 0);
- const uint64_t offset = blackLevel * nPixels;
- const uint64_t sumR = stats_->sumR_ - offset / 4;
- const uint64_t sumG = stats_->sumG_ - offset / 2;
- const uint64_t sumB = stats_->sumB_ - offset / 4;
-
- /*
- * Calculate red and blue gains for AWB.
- * Clamp max gain at 4.0, this also avoids 0 division.
- * Gain: 128 = 0.5, 256 = 1.0, 512 = 2.0, etc.
- */
- const unsigned int gainR = sumR <= sumG / 4 ? 1024 : 256 * sumG / sumR;
- const unsigned int gainB = sumB <= sumG / 4 ? 1024 : 256 * sumG / sumB;
- /* Green gain and gamma values are fixed */
- constexpr unsigned int gainG = 256;
-
- /* Update the gamma table if needed */
- if (blackLevel != lastBlackLevel_) {
- constexpr float gamma = 0.5;
- const unsigned int blackIndex = blackLevel * kGammaLookupSize / 256;
- std::fill(gammaTable_.begin(), gammaTable_.begin() + blackIndex, 0);
- const float divisor = kGammaLookupSize - blackIndex - 1.0;
- for (unsigned int i = blackIndex; i < kGammaLookupSize; i++)
- gammaTable_[i] = UINT8_MAX *
- std::pow((i - blackIndex) / divisor, gamma);
-
- lastBlackLevel_ = blackLevel;
- }
-
- for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {
- constexpr unsigned int div =
- DebayerParams::kRGBLookupSize * 256 / kGammaLookupSize;
- unsigned int idx;
-
- /* Apply gamma after gain! */
- idx = std::min({ i * gainR / div, (kGammaLookupSize - 1) });
- params_->red[i] = gammaTable_[idx];
-
- idx = std::min({ i * gainG / div, (kGammaLookupSize - 1) });
- params_->green[i] = gammaTable_[idx];
+ IPAFrameContext &frameContext = context_.frameContexts.alloc(frame);
- idx = std::min({ i * gainB / div, (kGammaLookupSize - 1) });
- params_->blue[i] = gammaTable_[idx];
- }
+ for (auto const &algo : algorithms())
+ algo->queueRequest(context_, frame, frameContext, controls);
+}
+void IPASoftSimple::fillParamsBuffer(const uint32_t frame)
+{
+ IPAFrameContext &frameContext = context_.frameContexts.get(frame);
+ for (auto const &algo : algorithms())
+ algo->prepare(context_, frame, frameContext, params_);
setIspParams.emit();
+}
- /* \todo Switch to the libipa/algorithm.h API someday. */
+void IPASoftSimple::processStats(const uint32_t frame,
+ [[maybe_unused]] const uint32_t bufferId,
+ const ControlList &sensorControls)
+{
+ IPAFrameContext &frameContext = context_.frameContexts.get(frame);
- /*
- * AE / AGC, use 2 frames delay to make sure that the exposure and
- * the gain set have applied to the camera sensor.
- * \todo This could be handled better with DelayedControls.
- */
- if (ignoreUpdates_ > 0) {
- --ignoreUpdates_;
- return;
- }
+ frameContext.sensor.exposure =
+ sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();
+ int32_t again = sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();
+ frameContext.sensor.gain = camHelper_ ? camHelper_->gain(again) : again;
/*
- * Calculate Mean Sample Value (MSV) according to formula from:
- * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf
+ * Software ISP currently does not produce any metadata. Use an empty
+ * ControlList for now.
+ *
+ * \todo Implement proper metadata handling
*/
- const unsigned int blackLevelHistIdx =
- blackLevel / (256 / SwIspStats::kYHistogramSize);
- const unsigned int histogramSize =
- SwIspStats::kYHistogramSize - blackLevelHistIdx;
- const unsigned int yHistValsPerBin = histogramSize / kExposureBinsCount;
- const unsigned int yHistValsPerBinMod =
- histogramSize / (histogramSize % kExposureBinsCount + 1);
- int exposureBins[kExposureBinsCount] = {};
- unsigned int denom = 0;
- unsigned int num = 0;
-
- for (unsigned int i = 0; i < histogramSize; i++) {
- unsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin;
- exposureBins[idx] += stats_->yHistogram[blackLevelHistIdx + i];
- }
-
- for (unsigned int i = 0; i < kExposureBinsCount; i++) {
- LOG(IPASoft, Debug) << i << ": " << exposureBins[i];
- denom += exposureBins[i];
- num += exposureBins[i] * (i + 1);
- }
-
- float exposureMSV = static_cast<float>(num) / denom;
+ ControlList metadata(controls::controls);
+ for (auto const &algo : algorithms())
+ algo->process(context_, frame, frameContext, stats_, metadata);
/* Sanity check */
if (!sensorControls.contains(V4L2_CID_EXPOSURE) ||
@@ -352,73 +291,19 @@ void IPASoftSimple::processStats(const ControlList &sensorControls)
return;
}
- exposure_ = sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();
- int32_t again = sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();
- again_ = camHelper_ ? camHelper_->gain(again) : again;
-
- updateExposure(exposureMSV);
-
ControlList ctrls(sensorInfoMap_);
- ctrls.set(V4L2_CID_EXPOSURE, exposure_);
+ auto &againNew = context_.activeState.agc.again;
+ ctrls.set(V4L2_CID_EXPOSURE, context_.activeState.agc.exposure);
ctrls.set(V4L2_CID_ANALOGUE_GAIN,
- static_cast<int32_t>(camHelper_ ? camHelper_->gainCode(again_) : again_));
-
- ignoreUpdates_ = 2;
+ static_cast<int32_t>(camHelper_ ? camHelper_->gainCode(againNew) : againNew));
setSensorControls.emit(ctrls);
-
- LOG(IPASoft, Debug) << "exposureMSV " << exposureMSV
- << " exp " << exposure_ << " again " << again_
- << " gain R/B " << gainR << "/" << gainB
- << " black level " << static_cast<unsigned int>(blackLevel);
}
-void IPASoftSimple::updateExposure(double exposureMSV)
+std::string IPASoftSimple::logPrefix() const
{
- /*
- * kExpDenominator of 10 gives ~10% increment/decrement;
- * kExpDenominator of 5 - about ~20%
- */
- static constexpr uint8_t kExpDenominator = 10;
- static constexpr uint8_t kExpNumeratorUp = kExpDenominator + 1;
- static constexpr uint8_t kExpNumeratorDown = kExpDenominator - 1;
-
- double next;
-
- if (exposureMSV < kExposureOptimal - kExposureSatisfactory) {
- next = exposure_ * kExpNumeratorUp / kExpDenominator;
- if (next - exposure_ < 1)
- exposure_ += 1;
- else
- exposure_ = next;
- if (exposure_ >= exposureMax_) {
- next = again_ * kExpNumeratorUp / kExpDenominator;
- if (next - again_ < againMinStep_)
- again_ += againMinStep_;
- else
- again_ = next;
- }
- }
-
- if (exposureMSV > kExposureOptimal + kExposureSatisfactory) {
- if (exposure_ == exposureMax_ && again_ > againMin_) {
- next = again_ * kExpNumeratorDown / kExpDenominator;
- if (again_ - next < againMinStep_)
- again_ -= againMinStep_;
- else
- again_ = next;
- } else {
- next = exposure_ * kExpNumeratorDown / kExpDenominator;
- if (exposure_ - next < 1)
- exposure_ -= 1;
- else
- exposure_ = next;
- }
- }
-
- exposure_ = std::clamp(exposure_, exposureMin_, exposureMax_);
- again_ = std::clamp(again_, againMin_, againMax_);
+ return "IPASoft";
}
} /* namespace ipa::soft */
diff --git a/src/ipa/vimc/meson.build b/src/ipa/vimc/meson.build
index d0b63edd..2cc5f80b 100644
--- a/src/ipa/vimc/meson.build
+++ b/src/ipa/vimc/meson.build
@@ -2,8 +2,7 @@
ipa_name = 'ipa_vimc'
-mod = shared_module(ipa_name,
- ['vimc.cpp', libcamera_generated_ipa_headers],
+mod = shared_module(ipa_name, 'vimc.cpp',
name_prefix : '',
include_directories : [ipa_includes],
dependencies : [libcamera_private, libipa_dep],
diff --git a/src/ipa/vimc/vimc.cpp b/src/ipa/vimc/vimc.cpp
index ebd63fa6..5495401f 100644
--- a/src/ipa/vimc/vimc.cpp
+++ b/src/ipa/vimc/vimc.cpp
@@ -14,6 +14,7 @@
#include <iostream>
#include <libcamera/base/file.h>
+#include <libcamera/base/flags.h>
#include <libcamera/base/log.h>
#include <libcamera/ipa/ipa_interface.h>
diff --git a/src/libcamera/base/event_dispatcher_poll.cpp b/src/libcamera/base/event_dispatcher_poll.cpp
index b737ca7a..52bfb34e 100644
--- a/src/libcamera/base/event_dispatcher_poll.cpp
+++ b/src/libcamera/base/event_dispatcher_poll.cpp
@@ -7,14 +7,13 @@
#include <libcamera/base/event_dispatcher_poll.h>
-#include <algorithm>
-#include <chrono>
#include <iomanip>
#include <poll.h>
#include <stdint.h>
#include <string.h>
#include <sys/eventfd.h>
#include <unistd.h>
+#include <vector>
#include <libcamera/base/event_notifier.h>
#include <libcamera/base/log.h>
diff --git a/src/libcamera/base/memfd.cpp b/src/libcamera/base/memfd.cpp
new file mode 100644
index 00000000..ed0b299b
--- /dev/null
+++ b/src/libcamera/base/memfd.cpp
@@ -0,0 +1,123 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Ideas on Board Oy
+ *
+ * Anonymous file creation
+ */
+
+#include <libcamera/base/memfd.h>
+
+#include <fcntl.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#include <libcamera/base/log.h>
+
+/**
+ * \file base/memfd.h
+ * \brief Anonymous file creation
+ */
+
+#ifndef __DOXYGEN__
+namespace {
+
+/* uClibc doesn't provide the file sealing API. */
+#if not HAVE_FILE_SEALS
+#define F_ADD_SEALS 1033
+#define F_SEAL_SHRINK 0x0002
+#define F_SEAL_GROW 0x0004
+#endif
+
+#if not HAVE_MEMFD_CREATE
+int memfd_create(const char *name, unsigned int flags)
+{
+ return syscall(SYS_memfd_create, name, flags);
+}
+#endif
+
+} /* namespace */
+#endif /* __DOXYGEN__ */
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(File)
+
+/**
+ * \class MemFd
+ * \brief Helper class to create anonymous files
+ *
+ * Anonymous files behave like regular files, and can be modified, truncated,
+ * memory-mapped and so on. Unlike regular files, they however live in RAM and
+ * don't have permanent backing storage.
+ */
+
+/**
+ * \enum MemFd::Seal
+ * \brief Seals for the MemFd::create() function
+ * \var MemFd::Seal::None
+ * \brief No seals (used as default value)
+ * \var MemFd::Seal::Shrink
+ * \brief Prevent the memfd from shrinking
+ * \var MemFd::Seal::Grow
+ * \brief Prevent the memfd from growing
+ */
+
+/**
+ * \typedef MemFd::Seals
+ * \brief A bitwise combination of MemFd::Seal values
+ */
+
+/**
+ * \brief Create an anonymous file
+ * \param[in] name The file name (displayed in symbolic links in /proc/self/fd/)
+ * \param[in] size The file size
+ * \param[in] seals The file seals
+ *
+ * This function is a helper that wraps anonymous file (memfd) creation and
+ * sets the file size and optional seals.
+ *
+ * \return The descriptor of the anonymous file if creation succeeded, or an
+ * invalid UniqueFD otherwise
+ */
+UniqueFD MemFd::create(const char *name, std::size_t size, Seals seals)
+{
+ int ret = memfd_create(name, MFD_ALLOW_SEALING | MFD_CLOEXEC);
+ if (ret < 0) {
+ ret = errno;
+ LOG(File, Error)
+ << "Failed to allocate memfd storage for " << name
+ << ": " << strerror(ret);
+ return {};
+ }
+
+ UniqueFD memfd(ret);
+
+ ret = ftruncate(memfd.get(), size);
+ if (ret < 0) {
+ ret = errno;
+ LOG(File, Error)
+ << "Failed to set memfd size for " << name
+ << ": " << strerror(ret);
+ return {};
+ }
+
+ if (seals) {
+ int fileSeals = (seals & Seal::Shrink ? F_SEAL_SHRINK : 0)
+ | (seals & Seal::Grow ? F_SEAL_GROW : 0);
+
+ ret = fcntl(memfd.get(), F_ADD_SEALS, fileSeals);
+ if (ret < 0) {
+ ret = errno;
+ LOG(File, Error)
+ << "Failed to seal the memfd for " << name
+ << ": " << strerror(ret);
+ return {};
+ }
+ }
+
+ return memfd;
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/base/meson.build b/src/libcamera/base/meson.build
index 7a7fd7e4..a742dfdf 100644
--- a/src/libcamera/base/meson.build
+++ b/src/libcamera/base/meson.build
@@ -1,24 +1,28 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_base_sources = files([
- 'backtrace.cpp',
- 'class.cpp',
+libcamera_base_public_sources = files([
'bound_method.cpp',
+ 'class.cpp',
+ 'flags.cpp',
+ 'object.cpp',
+ 'shared_fd.cpp',
+ 'signal.cpp',
+ 'unique_fd.cpp',
+])
+
+libcamera_base_internal_sources = files([
+ 'backtrace.cpp',
'event_dispatcher.cpp',
'event_dispatcher_poll.cpp',
'event_notifier.cpp',
'file.cpp',
- 'flags.cpp',
'log.cpp',
+ 'memfd.cpp',
'message.cpp',
'mutex.cpp',
- 'object.cpp',
'semaphore.cpp',
- 'shared_fd.cpp',
- 'signal.cpp',
'thread.cpp',
'timer.cpp',
- 'unique_fd.cpp',
'utils.cpp',
])
@@ -49,7 +53,11 @@ libcamera_base_deps = [
libcamera_base_args = [ '-DLIBCAMERA_BASE_PRIVATE' ]
libcamera_base_lib = shared_library('libcamera-base',
- [libcamera_base_sources, libcamera_base_headers],
+ [
+ libcamera_base_public_sources,
+ libcamera_base_internal_sources,
+ libcamera_base_headers,
+ ],
version : libcamera_version,
soversion : libcamera_soversion,
name_prefix : '',
diff --git a/src/libcamera/base/thread.cpp b/src/libcamera/base/thread.cpp
index 72733431..8735670b 100644
--- a/src/libcamera/base/thread.cpp
+++ b/src/libcamera/base/thread.cpp
@@ -64,42 +64,6 @@
* receiver's event loop, running in the receiver's thread. This mechanism can
* be overridden by selecting a different connection type when calling
* Signal::connect().
- *
- * \section thread-reentrancy Reentrancy and Thread-Safety
- *
- * Through the documentation, several terms are used to define how classes and
- * their member functions can be used from multiple threads.
- *
- * - A **reentrant** function may be called simultaneously from multiple
- * threads if and only if each invocation uses a different instance of the
- * class. This is the default for all member functions not explictly marked
- * otherwise.
- *
- * - \anchor thread-safe A **thread-safe** function may be called
- * simultaneously from multiple threads on the same instance of a class. A
- * thread-safe function is thus reentrant. Thread-safe functions may also be
- * called simultaneously with any other reentrant function of the same class
- * on the same instance.
- *
- * - \anchor thread-bound A **thread-bound** function may be called only from
- * the thread that the class instances lives in (see section \ref
- * thread-objects). For instances of classes that do not derive from the
- * Object class, this is the thread in which the instance was created. A
- * thread-bound function is not thread-safe, and may or may not be reentrant.
- *
- * Neither reentrancy nor thread-safety, in this context, mean that a function
- * may be called simultaneously from the same thread, for instance from a
- * callback invoked by the function. This may deadlock and isn't allowed unless
- * separately documented.
- *
- * A class is defined as reentrant, thread-safe or thread-bound if all its
- * member functions are reentrant, thread-safe or thread-bound respectively.
- * Some member functions may additionally be documented as having additional
- * thread-related attributes.
- *
- * Most classes are reentrant but not thread-safe, as making them fully
- * thread-safe would incur locking costs considered prohibitive for the
- * expected use cases.
*/
/**
diff --git a/src/libcamera/base/utils.cpp b/src/libcamera/base/utils.cpp
index ccb31063..67e5a896 100644
--- a/src/libcamera/base/utils.cpp
+++ b/src/libcamera/base/utils.cpp
@@ -531,6 +531,138 @@ double strtod(const char *__restrict nptr, char **__restrict endptr)
* \return The value of e converted to its underlying type
*/
+/**
+ * \class ScopeExitActions
+ * \brief An object that performs actions upon destruction
+ *
+ * The ScopeExitActions class is a simple object that performs user-provided
+ * actions upon destruction. It is meant to simplify cleanup tasks in error
+ * handling paths.
+ *
+ * When the code flow performs multiple sequential actions that each need a
+ * corresponding cleanup action, error handling quickly become tedious:
+ *
+ * \code{.cpp}
+ * {
+ * int ret = allocateMemory();
+ * if (ret)
+ * return ret;
+ *
+ * ret = startProducer();
+ * if (ret) {
+ * freeMemory();
+ * return ret;
+ * }
+ *
+ * ret = startConsumer();
+ * if (ret) {
+ * stopProducer();
+ * freeMemory();
+ * return ret;
+ * }
+ *
+ * return 0;
+ * }
+ * \endcode
+ *
+ * This is prone to programming mistakes, as cleanup actions can easily be
+ * forgotten or ordered incorrectly. One strategy to simplify error handling is
+ * to use goto statements:
+ *
+ * \code{.cpp}
+ * {
+ * int ret = allocateMemory();
+ * if (ret)
+ * return ret;
+ *
+ * ret = startProducer();
+ * if (ret)
+ * goto error_free;
+ *
+ * ret = startConsumer();
+ * if (ret)
+ * goto error_stop;
+ *
+ * return 0;
+ *
+ * error_stop:
+ * stopProducer();
+ * error_free:
+ * freeMemory();
+ * return ret;
+ * }
+ * \endcode
+ *
+ * While this may be considered better, this solution is still quite
+ * error-prone. Beside the risk of picking the wrong error label, the error
+ * handling logic is separated from the normal code flow, which increases the
+ * risk of error when refactoring the code. Additionally, C++ doesn't allow
+ * goto statements to jump over local variable declarations, which can make
+ * usage of this pattern more difficult.
+ *
+ * The ScopeExitActions class solves these issues by allowing code that
+ * requires cleanup actions to be grouped with its corresponding error handling
+ * code:
+ *
+ * \code{.cpp}
+ * {
+ * ScopeExitActions actions;
+ *
+ * int ret = allocateMemory();
+ * if (ret)
+ * return ret;
+ *
+ * actions += [&]() { freeMemory(); };
+ *
+ * ret = startProducer();
+ * if (ret)
+ * return ret;
+ *
+ * actions += [&]() { stopProducer(); };
+ *
+ * ret = startConsumer();
+ * if (ret)
+ * return ret;
+ *
+ * actions.release();
+ * return 0;
+ * }
+ * \endcode
+ *
+ * Error handlers are executed when the ScopeExitActions instance is destroyed,
+ * in the reverse order of their addition.
+ */
+
+ScopeExitActions::~ScopeExitActions()
+{
+ for (const auto &action : utils::reverse(actions_))
+ action();
+}
+
+/**
+ * \brief Add an exit action
+ * \param[in] action The action
+ *
+ * Add an exit action to the ScopeExitActions. Actions will be called upon
+ * destruction in the reverse order of their addition.
+ */
+void ScopeExitActions::operator+=(std::function<void()> &&action)
+{
+ actions_.push_back(std::move(action));
+}
+
+/**
+ * \brief Remove all exit actions
+ *
+ * This function should be called in scope exit paths that don't need the
+ * actions to be executed, such as success return paths from a function when
+ * the ScopeExitActions is used for error cleanup.
+ */
+void ScopeExitActions::release()
+{
+ actions_.clear();
+}
+
} /* namespace utils */
#ifndef __DOXYGEN__
diff --git a/src/libcamera/bayer_format.cpp b/src/libcamera/bayer_format.cpp
index 014f716d..c2120d1c 100644
--- a/src/libcamera/bayer_format.cpp
+++ b/src/libcamera/bayer_format.cpp
@@ -184,6 +184,8 @@ const std::map<BayerFormat, Formats, BayerFormatComparator> bayerToFormat{
{ formats::R10_CSI2P, V4L2PixelFormat(V4L2_PIX_FMT_Y10P) } },
{ { BayerFormat::MONO, 12, BayerFormat::Packing::None },
{ formats::R12, V4L2PixelFormat(V4L2_PIX_FMT_Y12) } },
+ { { BayerFormat::MONO, 12, BayerFormat::Packing::CSI2 },
+ { formats::R12_CSI2P, V4L2PixelFormat(V4L2_PIX_FMT_Y12P) } },
{ { BayerFormat::MONO, 16, BayerFormat::Packing::None },
{ formats::R16, V4L2PixelFormat(V4L2_PIX_FMT_Y16) } },
{ { BayerFormat::MONO, 16, BayerFormat::Packing::PISP1 },
diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp
index 67f34901..a86f552a 100644
--- a/src/libcamera/camera.cpp
+++ b/src/libcamera/camera.cpp
@@ -9,7 +9,11 @@
#include <array>
#include <atomic>
-#include <iomanip>
+#include <ios>
+#include <memory>
+#include <optional>
+#include <set>
+#include <sstream>
#include <libcamera/base/log.h>
#include <libcamera/base/thread.h>
@@ -21,7 +25,6 @@
#include "libcamera/internal/camera.h"
#include "libcamera/internal/camera_controls.h"
-#include "libcamera/internal/formats.h"
#include "libcamera/internal/pipeline_handler.h"
#include "libcamera/internal/request.h"
@@ -117,6 +120,12 @@
* of view is affected by the pipeline.
*/
+/**
+ * \internal
+ * \file libcamera/internal/camera.h
+ * \brief Internal camera device handling
+ */
+
namespace libcamera {
LOG_DECLARE_CATEGORY(Camera)
@@ -559,6 +568,7 @@ CameraConfiguration::Status CameraConfiguration::validateColorSpaces(ColorSpaceF
* \brief The vector of stream configurations
*/
+#ifndef __DOXYGEN_PUBLIC__
/**
* \class Camera::Private
* \brief Base class for camera private data
@@ -719,6 +729,7 @@ void Camera::Private::setState(State state)
{
state_.store(state, std::memory_order_release);
}
+#endif /* __DOXYGEN_PUBLIC__ */
/**
* \class Camera
@@ -813,6 +824,7 @@ void Camera::Private::setState(State state)
*/
/**
+ * \internal
* \brief Create a camera instance
* \param[in] d Camera private data
* \param[in] id The ID of the camera device
@@ -986,7 +998,8 @@ int Camera::acquire()
if (ret < 0)
return ret == -EACCES ? -EBUSY : ret;
- if (!d->pipe_->acquire()) {
+ if (!d->pipe_->invokeMethod(&PipelineHandler::acquire,
+ ConnectionTypeBlocking, this)) {
LOG(Camera, Info)
<< "Pipeline handler in use by another process";
return -EBUSY;
@@ -1021,7 +1034,8 @@ int Camera::release()
return ret == -EACCES ? -EBUSY : ret;
if (d->isAcquired())
- d->pipe_->release(this);
+ d->pipe_->invokeMethod(&PipelineHandler::release,
+ ConnectionTypeBlocking, this);
d->setState(Private::CameraAvailable);
diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp
index 95a9e326..09f58776 100644
--- a/src/libcamera/camera_manager.cpp
+++ b/src/libcamera/camera_manager.cpp
@@ -15,6 +15,7 @@
#include "libcamera/internal/camera.h"
#include "libcamera/internal/device_enumerator.h"
+#include "libcamera/internal/ipa_manager.h"
#include "libcamera/internal/pipeline_handler.h"
/**
@@ -23,6 +24,7 @@
*/
/**
+ * \internal
* \file libcamera/internal/camera_manager.h
* \brief Internal camera manager support
*/
@@ -34,9 +36,11 @@ namespace libcamera {
LOG_DEFINE_CATEGORY(Camera)
+#ifndef __DOXYGEN_PUBLIC__
CameraManager::Private::Private()
: initialized_(false)
{
+ ipaManager_ = std::make_unique<IPAManager>();
}
int CameraManager::Private::start()
@@ -250,6 +254,14 @@ void CameraManager::Private::removeCamera(std::shared_ptr<Camera> camera)
}
/**
+ * \fn CameraManager::Private::ipaManager() const
+ * \brief Retrieve the IPAManager
+ * \context This function is \threadsafe.
+ * \return The IPAManager for this CameraManager
+ */
+#endif /* __DOXYGEN_PUBLIC__ */
+
+/**
* \class CameraManager
* \brief Provide access and manage all cameras in the system
*
diff --git a/src/libcamera/control_ids.cpp.in b/src/libcamera/control_ids.cpp.in
index d283c1c1..3a204931 100644
--- a/src/libcamera/control_ids.cpp.in
+++ b/src/libcamera/control_ids.cpp.in
@@ -2,51 +2,122 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * control_ids.cpp : Control ID list
+ * {{mode}} ID list
*
* This file is auto-generated. Do not edit.
*/
-#include <libcamera/control_ids.h>
+#include <libcamera/{{filename}}.h>
#include <libcamera/controls.h>
/**
- * \file control_ids.h
- * \brief Camera control identifiers
+ * \file {{filename}}.h
+ * \brief Camera {{mode}} identifiers
*/
namespace libcamera {
/**
- * \brief Namespace for libcamera controls
+ * \brief Namespace for libcamera {{mode}}
*/
-namespace controls {
+namespace {{mode}} {
-${controls_doc}
+{%- for vendor, ctrls in controls -%}
-${vendor_controls_doc}
+{%- if vendor != 'libcamera' %}
+/**
+ * \brief Namespace for {{vendor}} {{mode}}
+ */
+namespace {{vendor}} {
+{%- endif -%}
+
+{% for ctrl in ctrls %}
+
+{% if ctrl.is_enum -%}
+/**
+ * \enum {{ctrl.name}}Enum
+ * \brief Supported {{ctrl.name}} values
+{%- for enum in ctrl.enum_values %}
+ *
+ * \var {{enum.name}}
+ * \brief {{enum.description|format_description}}
+{%- endfor %}
+ */
+
+/**
+ * \var {{ctrl.name}}Values
+ * \brief List of all {{ctrl.name}} supported values
+ */
+
+/**
+ * \var {{ctrl.name}}NameValueMap
+ * \brief Map of all {{ctrl.name}} supported value names (in std::string format) to value
+ */
+
+{% endif -%}
+/**
+ * \var {{ctrl.name}}
+ * \brief {{ctrl.description|format_description}}
+ */
+{%- endfor %}
+{% if vendor != 'libcamera' %}
+} /* namespace {{vendor}} */
+{% endif -%}
+
+{%- endfor %}
#ifndef __DOXYGEN__
/*
- * Keep the controls definitions hidden from doxygen as it incorrectly parses
+ * Keep the {{mode}} definitions hidden from doxygen as it incorrectly parses
* them as functions.
*/
-${controls_def}
+{% for vendor, ctrls in controls -%}
+
+{% if vendor != 'libcamera' %}
+namespace {{vendor}} {
+{% endif %}
+
+{%- for ctrl in ctrls %}
+{% if ctrl.is_enum -%}
+extern const std::array<const ControlValue, {{ctrl.enum_values_count}}> {{ctrl.name}}Values = {
+{%- for enum in ctrl.enum_values %}
+ static_cast<{{ctrl.type}}>({{enum.name}}),
+{%- endfor %}
+};
+extern const std::map<std::string, {{ctrl.type}}> {{ctrl.name}}NameValueMap = {
+{%- for enum in ctrl.enum_values %}
+ { "{{enum.name}}", {{enum.name}} },
+{%- endfor %}
+};
+extern const Control<{{ctrl.type}}> {{ctrl.name}}({{ctrl.name|snake_case|upper}}, "{{ctrl.name}}", {{ctrl.name}}NameValueMap);
+{% else -%}
+extern const Control<{{ctrl.type}}> {{ctrl.name}}({{ctrl.name|snake_case|upper}}, "{{ctrl.name}}");
+{% endif -%}
+{%- endfor %}
-${vendor_controls_def}
+{% if vendor != 'libcamera' %}
+} /* namespace {{vendor}} */
+{% endif -%}
-#endif
+{%- endfor %}
+#endif /* __DOXYGEN__ */
/**
- * \brief List of all supported libcamera controls
+ * \brief List of all supported libcamera {{mode}}
+{%- if mode == 'controls' %}
*
* Unless otherwise stated, all controls are bi-directional, i.e. they can be
* set through Request::controls() and returned out through Request::metadata().
+{%- endif %}
*/
-extern const ControlIdMap controls {
-${controls_map}
+extern const ControlIdMap {{mode}} {
+{%- for vendor, ctrls in controls -%}
+{%- for ctrl in ctrls %}
+ { {{ctrl.namespace}}{{ctrl.name|snake_case|upper}}, &{{ctrl.namespace}}{{ctrl.name}} },
+{%- endfor -%}
+{%- endfor %}
};
-} /* namespace controls */
+} /* namespace {{mode}} */
} /* namespace libcamera */
diff --git a/src/libcamera/control_ids_core.yaml b/src/libcamera/control_ids_core.yaml
index 9d413a94..1b1bd950 100644
--- a/src/libcamera/control_ids_core.yaml
+++ b/src/libcamera/control_ids_core.yaml
@@ -32,10 +32,11 @@ controls:
- AeMeteringMode:
type: int32_t
description: |
- Specify a metering mode for the AE algorithm to use. The metering
- modes determine which parts of the image are used to determine the
- scene brightness. Metering modes may be platform specific and not
- all metering modes may be supported.
+ Specify a metering mode for the AE algorithm to use.
+
+ The metering modes determine which parts of the image are used to
+ determine the scene brightness. Metering modes may be platform specific
+ and not all metering modes may be supported.
enum:
- name: MeteringCentreWeighted
value: 0
@@ -56,33 +57,41 @@ controls:
- AeConstraintMode:
type: int32_t
description: |
- Specify a constraint mode for the AE algorithm to use. These determine
- how the measured scene brightness is adjusted to reach the desired
- target exposure. Constraint modes may be platform specific, and not
- all constraint modes may be supported.
+ Specify a constraint mode for the AE algorithm to use.
+
+ The constraint modes determine how the measured scene brightness is
+ adjusted to reach the desired target exposure. Constraint modes may be
+ platform specific, and not all constraint modes may be supported.
enum:
- name: ConstraintNormal
value: 0
- description: Default constraint mode.
+ description: |
+ Default constraint mode.
+
This mode aims to balance the exposure of different parts of the
image so as to reach a reasonable average level. However, highlights
in the image may appear over-exposed and lowlights may appear
under-exposed.
- name: ConstraintHighlight
value: 1
- description: Highlight constraint mode.
+ description: |
+ Highlight constraint mode.
+
This mode adjusts the exposure levels in order to try and avoid
over-exposing the brightest parts (highlights) of an image.
Other non-highlight parts of the image may appear under-exposed.
- name: ConstraintShadows
value: 2
- description: Shadows constraint mode.
+ description: |
+ Shadows constraint mode.
+
This mode adjusts the exposure levels in order to try and avoid
under-exposing the dark parts (shadows) of an image. Other normally
exposed parts of the image may appear over-exposed.
- name: ConstraintCustom
value: 3
- description: Custom constraint mode.
+ description: |
+ Custom constraint mode.
# AeExposureMode needs further attention:
# - Auto-generate max enum value.
@@ -90,10 +99,11 @@ controls:
- AeExposureMode:
type: int32_t
description: |
- Specify an exposure mode for the AE algorithm to use. These specify
- how the desired total exposure is divided between the shutter time
- and the sensor's analogue gain. The exposure modes are platform
- specific, and not all exposure modes may be supported.
+ Specify an exposure mode for the AE algorithm to use.
+
+ The exposure modes specify how the desired total exposure is divided
+ between the shutter time and the sensor's analogue gain. They are
+ platform specific, and not all exposure modes may be supported.
enum:
- name: ExposureNormal
value: 0
@@ -111,11 +121,13 @@ controls:
- ExposureValue:
type: float
description: |
- Specify an Exposure Value (EV) parameter. The EV parameter will only be
- applied if the AE algorithm is currently enabled.
+ Specify an Exposure Value (EV) parameter.
+
+ The EV parameter will only be applied if the AE algorithm is currently
+ enabled.
By convention EV adjusts the exposure as log2. For example
- EV = [-2, -1, 0.5, 0, 0.5, 1, 2] results in an exposure adjustment
+ EV = [-2, -1, -0.5, 0, 0.5, 1, 2] results in an exposure adjustment
of [1/4x, 1/2x, 1/sqrt(2)x, 1x, sqrt(2)x, 2x, 4x].
\sa AeEnable
@@ -124,7 +136,9 @@ controls:
type: int32_t
description: |
Exposure time (shutter speed) for the frame applied in the sensor
- device. This value is specified in micro-seconds.
+ device.
+
+ This value is specified in micro-seconds.
Setting this value means that it is now fixed and the AE algorithm may
not change it. Setting it back to zero returns it to the control of the
@@ -142,6 +156,7 @@ controls:
type: float
description: |
Analogue gain value applied in the sensor device.
+
The value of the control specifies the gain multiplier applied to all
colour channels. This value cannot be lower than 1.0.
@@ -160,9 +175,11 @@ controls:
- AeFlickerMode:
type: int32_t
description: |
- Set the flicker mode, which determines whether, and how, the AGC/AEC
- algorithm attempts to hide flicker effects caused by the duty cycle of
- artificial lighting.
+ Set the flicker avoidance mode for AGC/AEC.
+
+ The flicker mode determines whether, and how, the AGC/AEC algorithm
+ attempts to hide flicker effects caused by the duty cycle of artificial
+ lighting.
Although implementation dependent, many algorithms for "flicker
avoidance" work by restricting this exposure time to integer multiples
@@ -176,16 +193,21 @@ controls:
enum:
- name: FlickerOff
value: 0
- description: No flicker avoidance is performed.
+ description: |
+ No flicker avoidance is performed.
- name: FlickerManual
value: 1
- description: Manual flicker avoidance.
+ description: |
+ Manual flicker avoidance.
+
Suppress flicker effects caused by lighting running with a period
specified by the AeFlickerPeriod control.
\sa AeFlickerPeriod
- name: FlickerAuto
value: 2
- description: Automatic flicker period detection and avoidance.
+ description: |
+ Automatic flicker period detection and avoidance.
+
The system will automatically determine the most likely value of
flicker period, and avoid flicker of this frequency. Once flicker
is being corrected, it is implementation dependent whether the
@@ -194,7 +216,9 @@ controls:
- AeFlickerPeriod:
type: int32_t
- description: Manual flicker period in microseconds.
+ description: |
+ Manual flicker period in microseconds.
+
This value sets the current flicker period to avoid. It is used when
AeFlickerMode is set to FlickerManual.
@@ -212,7 +236,9 @@ controls:
- AeFlickerDetected:
type: int32_t
- description: Flicker period detected in microseconds.
+ description: |
+ Flicker period detected in microseconds.
+
The value reported here indicates the currently detected flicker
period, or zero if no flicker at all is detected.
@@ -233,21 +259,25 @@ controls:
- Brightness:
type: float
description: |
- Specify a fixed brightness parameter. Positive values (up to 1.0)
- produce brighter images; negative values (up to -1.0) produce darker
- images and 0.0 leaves pixels unchanged.
+ Specify a fixed brightness parameter.
+
+ Positive values (up to 1.0) produce brighter images; negative values
+ (up to -1.0) produce darker images and 0.0 leaves pixels unchanged.
- Contrast:
type: float
description: |
- Specify a fixed contrast parameter. Normal contrast is given by the
- value 1.0; larger values produce images with more contrast.
+ Specify a fixed contrast parameter.
+
+ Normal contrast is given by the value 1.0; larger values produce images
+ with more contrast.
- Lux:
type: float
description: |
- Report an estimate of the current illuminance level in lux. The Lux
- control can only be returned in metadata.
+ Report an estimate of the current illuminance level in lux.
+
+ The Lux control can only be returned in metadata.
- AwbEnable:
type: bool
@@ -262,8 +292,10 @@ controls:
- AwbMode:
type: int32_t
description: |
- Specify the range of illuminants to use for the AWB algorithm. The modes
- supported are platform specific, and not all modes may be supported.
+ Specify the range of illuminants to use for the AWB algorithm.
+
+ The modes supported are platform specific, and not all modes may be
+ supported.
enum:
- name: AwbAuto
value: 0
@@ -305,37 +337,43 @@ controls:
type: float
description: |
Pair of gain values for the Red and Blue colour channels, in that
- order. ColourGains can only be applied in a Request when the AWB is
- disabled.
+ order.
+
+ ColourGains can only be applied in a Request when the AWB is disabled.
\sa AwbEnable
size: [2]
- ColourTemperature:
type: int32_t
- description: Report the current estimate of the colour temperature, in
- kelvin, for this frame. The ColourTemperature control can only be
- returned in metadata.
+ description: |
+ Report the estimate of the colour temperature for the frame, in kelvin.
+
+ The ColourTemperature control can only be returned in metadata.
- Saturation:
type: float
description: |
- Specify a fixed saturation parameter. Normal saturation is given by
- the value 1.0; larger values produce more saturated colours; 0.0
- produces a greyscale image.
+ Specify a fixed saturation parameter.
+
+ Normal saturation is given by the value 1.0; larger values produce more
+ saturated colours; 0.0 produces a greyscale image.
- SensorBlackLevels:
type: int32_t
description: |
- Reports the sensor black levels used for processing a frame, in the
- order R, Gr, Gb, B. These values are returned as numbers out of a 16-bit
- pixel range (as if pixels ranged from 0 to 65535). The SensorBlackLevels
- control can only be returned in metadata.
+ Reports the sensor black levels used for processing a frame.
+
+ The values are in the order R, Gr, Gb, B. They are returned as numbers
+ out of a 16-bit pixel range (as if pixels ranged from 0 to 65535). The
+ SensorBlackLevels control can only be returned in metadata.
size: [4]
- Sharpness:
type: float
description: |
+ Intensity of the sharpening applied to the image.
+
A value of 0.0 means no sharpening. The minimum value means
minimal sharpening, and shall be 0.0 unless the camera can't
disable sharpening completely. The default value shall give a
@@ -349,6 +387,7 @@ controls:
type: int32_t
description: |
Reports a Figure of Merit (FoM) to indicate how in-focus the frame is.
+
A larger FocusFoM value indicates a more in-focus frame. This singular
value may be based on a combination of statistics gathered from
multiple focus regions within an image. The number of focus regions and
@@ -359,11 +398,13 @@ controls:
- ColourCorrectionMatrix:
type: float
description: |
- The 3x3 matrix that converts camera RGB to sRGB within the
- imaging pipeline. This should describe the matrix that is used
- after pixels have been white-balanced, but before any gamma
- transformation. The 3x3 matrix is stored in conventional reading
- order in an array of 9 floating point values.
+ The 3x3 matrix that converts camera RGB to sRGB within the imaging
+ pipeline.
+
+ This should describe the matrix that is used after pixels have been
+ white-balanced, but before any gamma transformation. The 3x3 matrix is
+ stored in conventional reading order in an array of 9 floating point
+ values.
size: [3,3]
@@ -371,10 +412,12 @@ controls:
type: Rectangle
description: |
Sets the image portion that will be scaled to form the whole of
- the final output image. The (x,y) location of this rectangle is
- relative to the PixelArrayActiveAreas that is being used. The units
- remain native sensor pixels, even if the sensor is being used in
- a binning or skipping mode.
+ the final output image.
+
+ The (x,y) location of this rectangle is relative to the
+ PixelArrayActiveAreas that is being used. The units remain native
+ sensor pixels, even if the sensor is being used in a binning or
+ skipping mode.
This control is only present when the pipeline supports scaling. Its
maximum valid value is given by the properties::ScalerCropMaximum
@@ -401,8 +444,9 @@ controls:
type: int64_t
description: |
The instantaneous frame duration from start of frame exposure to start
- of next exposure, expressed in microseconds. This control is meant to
- be returned in metadata.
+ of next exposure, expressed in microseconds.
+
+ This control is meant to be returned in metadata.
- FrameDurationLimits:
type: int64_t
@@ -444,9 +488,11 @@ controls:
- SensorTemperature:
type: float
description: |
- Temperature measure from the camera sensor in Celsius. This is typically
- obtained by a thermal sensor present on-die or in the camera module. The
- range of reported temperatures is device dependent.
+ Temperature measure from the camera sensor in Celsius.
+
+ This value is typically obtained by a thermal sensor present on-die or
+ in the camera module. The range of reported temperatures is device
+ dependent.
The SensorTemperature control will only be returned in metadata if a
thermal sensor is present.
@@ -468,7 +514,7 @@ controls:
- AfMode:
type: int32_t
description: |
- Control to set the mode of the AF (autofocus) algorithm.
+ The mode of the AF (autofocus) algorithm.
An implementation may choose not to implement all the modes.
@@ -476,12 +522,12 @@ controls:
- name: AfModeManual
value: 0
description: |
- The AF algorithm is in manual mode. In this mode it will never
- perform any action nor move the lens of its own accord, but an
- application can specify the desired lens position using the
- LensPosition control.
+ The AF algorithm is in manual mode.
- In this mode the AfState will always report AfStateIdle.
+ In this mode it will never perform any action nor move the lens of
+ its own accord, but an application can specify the desired lens
+ position using the LensPosition control. The AfState will always
+ report AfStateIdle.
If the camera is started in AfModeManual, it will move the focus
lens to the position specified by the LensPosition control.
@@ -492,31 +538,34 @@ controls:
- name: AfModeAuto
value: 1
description: |
- The AF algorithm is in auto mode. This means that the algorithm
- will never move the lens or change state unless the AfTrigger
- control is used. The AfTrigger control can be used to initiate a
- focus scan, the results of which will be reported by AfState.
-
- If the autofocus algorithm is moved from AfModeAuto to another
- mode while a scan is in progress, the scan is cancelled
- immediately, without waiting for the scan to finish.
-
- When first entering this mode the AfState will report
- AfStateIdle. When a trigger control is sent, AfState will
- report AfStateScanning for a period before spontaneously
- changing to AfStateFocused or AfStateFailed, depending on
- the outcome of the scan. It will remain in this state until
- another scan is initiated by the AfTrigger control. If a scan is
- cancelled (without changing to another mode), AfState will return
- to AfStateIdle.
+ The AF algorithm is in auto mode.
+
+ In this mode the algorithm will never move the lens or change state
+ unless the AfTrigger control is used. The AfTrigger control can be
+ used to initiate a focus scan, the results of which will be
+ reported by AfState.
+
+ If the autofocus algorithm is moved from AfModeAuto to another mode
+ while a scan is in progress, the scan is cancelled immediately,
+ without waiting for the scan to finish.
+
+ When first entering this mode the AfState will report AfStateIdle.
+ When a trigger control is sent, AfState will report AfStateScanning
+ for a period before spontaneously changing to AfStateFocused or
+ AfStateFailed, depending on the outcome of the scan. It will remain
+ in this state until another scan is initiated by the AfTrigger
+ control. If a scan is cancelled (without changing to another mode),
+ AfState will return to AfStateIdle.
- name: AfModeContinuous
value: 2
description: |
- The AF algorithm is in continuous mode. This means that the lens can
- re-start a scan spontaneously at any moment, without any user
- intervention. The AfState still reports whether the algorithm is
- currently scanning or not, though the application has no ability to
- initiate or cancel scans, nor to move the lens for itself.
+ The AF algorithm is in continuous mode.
+
+ In this mode the lens can re-start a scan spontaneously at any
+ moment, without any user intervention. The AfState still reports
+ whether the algorithm is currently scanning or not, though the
+ application has no ability to initiate or cancel scans, nor to move
+ the lens for itself.
However, applications can pause the AF algorithm from continuously
scanning by using the AfPause control. This allows video or still
@@ -529,34 +578,40 @@ controls:
- AfRange:
type: int32_t
description: |
- Control to set the range of focus distances that is scanned. An
- implementation may choose not to implement all the options here.
+ The range of focus distances that is scanned.
+
+ An implementation may choose not to implement all the options here.
enum:
- name: AfRangeNormal
value: 0
description: |
- A wide range of focus distances is scanned, all the way from
- infinity down to close distances, though depending on the
- implementation, possibly not including the very closest macro
- positions.
+ A wide range of focus distances is scanned.
+
+ Scanned distances cover all the way from infinity down to close
+ distances, though depending on the implementation, possibly not
+ including the very closest macro positions.
- name: AfRangeMacro
value: 1
- description: Only close distances are scanned.
+ description: |
+ Only close distances are scanned.
- name: AfRangeFull
value: 2
description: |
- The full range of focus distances is scanned just as with
- AfRangeNormal but this time including the very closest macro
- positions.
+ The full range of focus distances is scanned.
+
+ This range is similar to AfRangeNormal but includes the very
+ closest macro positions.
- AfSpeed:
type: int32_t
description: |
- Control that determines whether the AF algorithm is to move the lens
- as quickly as possible or more steadily. For example, during video
- recording it may be desirable not to move the lens too abruptly, but
- when in a preview mode (waiting for a still capture) it may be
- helpful to move the lens as quickly as is reasonably possible.
+ Determine whether the AF is to move the lens as quickly as possible or
+ more steadily.
+
+ For example, during video recording it may be desirable not to move the
+ lens too abruptly, but when in a preview mode (waiting for a still
+ capture) it may be helpful to move the lens as quickly as is reasonably
+ possible.
enum:
- name: AfSpeedNormal
value: 0
@@ -568,25 +623,27 @@ controls:
- AfMetering:
type: int32_t
description: |
- Instruct the AF algorithm how it should decide which parts of the image
- should be used to measure focus.
+ The parts of the image used by the AF algorithm to measure focus.
enum:
- name: AfMeteringAuto
value: 0
- description: The AF algorithm should decide for itself where it will
- measure focus.
+ description: |
+ Let the AF algorithm decide for itself where it will measure focus.
- name: AfMeteringWindows
value: 1
- description: The AF algorithm should use the rectangles defined by
- the AfWindows control to measure focus. If no windows are specified
- the behaviour is platform dependent.
+ description: |
+ Use the rectangles defined by the AfWindows control to measure focus.
+
+ If no windows are specified the behaviour is platform dependent.
- AfWindows:
type: Rectangle
description: |
- Sets the focus windows used by the AF algorithm when AfMetering is set
- to AfMeteringWindows. The units used are pixels within the rectangle
- returned by the ScalerCropMaximum property.
+ The focus windows used by the AF algorithm when AfMetering is set to
+ AfMeteringWindows.
+
+ The units used are pixels within the rectangle returned by the
+ ScalerCropMaximum property.
In order to be activated, a rectangle must be programmed with non-zero
width and height. Internally, these rectangles are intersected with the
@@ -611,23 +668,33 @@ controls:
- AfTrigger:
type: int32_t
description: |
- This control starts an autofocus scan when AfMode is set to AfModeAuto,
- and can also be used to terminate a scan early.
+ Start an autofocus scan.
- It is ignored if AfMode is set to AfModeManual or AfModeContinuous.
+ This control starts an autofocus scan when AfMode is set to AfModeAuto,
+ and is ignored if AfMode is set to AfModeManual or AfModeContinuous. It
+ can also be used to terminate a scan early.
enum:
- name: AfTriggerStart
value: 0
- description: Start an AF scan. Ignored if a scan is in progress.
+ description: |
+ Start an AF scan.
+
+ Setting the control to AfTriggerStart is ignored if a scan is in
+ progress.
- name: AfTriggerCancel
value: 1
- description: Cancel an AF scan. This does not cause the lens to move
- anywhere else. Ignored if no scan is in progress.
+ description: |
+ Cancel an AF scan.
+
+ This does not cause the lens to move anywhere else. Ignored if no
+ scan is in progress.
- AfPause:
type: int32_t
description: |
+ Pause lens movements when in continuous autofocus mode.
+
This control has no effect except when in continuous autofocus mode
(AfModeContinuous). It can be used to pause any lens movements while
(for example) images are captured. The algorithm remains inactive
@@ -637,17 +704,21 @@ controls:
- name: AfPauseImmediate
value: 0
description: |
- Pause the continuous autofocus algorithm immediately, whether or not
- any kind of scan is underway. AfPauseState will subsequently report
+ Pause the continuous autofocus algorithm immediately.
+
+ The autofocus algorithm is paused whether or not any kind of scan
+ is underway. AfPauseState will subsequently report
AfPauseStatePaused. AfState may report any of AfStateScanning,
AfStateFocused or AfStateFailed, depending on the algorithm's state
when it received this control.
- name: AfPauseDeferred
value: 1
description: |
- This is similar to AfPauseImmediate, and if the AfState is currently
- reporting AfStateFocused or AfStateFailed it will remain in that
- state and AfPauseState will report AfPauseStatePaused.
+ Pause the continuous autofocus algorithm at the end of the scan.
+
+ This is similar to AfPauseImmediate, and if the AfState is
+ currently reporting AfStateFocused or AfStateFailed it will remain
+ in that state and AfPauseState will report AfPauseStatePaused.
However, if the algorithm is scanning (AfStateScanning),
AfPauseState will report AfPauseStatePausing until the scan is
@@ -658,15 +729,18 @@ controls:
- name: AfPauseResume
value: 2
description: |
- Resume continuous autofocus operation. The algorithm starts again
- from exactly where it left off, and AfPauseState will report
- AfPauseStateRunning.
+ Resume continuous autofocus operation.
+
+ The algorithm starts again from exactly where it left off, and
+ AfPauseState will report AfPauseStateRunning.
- LensPosition:
type: float
description: |
- Acts as a control to instruct the lens to move to a particular position
- and also reports back the position of the lens for each frame.
+ Set and report the focus lens position.
+
+ This control instructs the lens to move to a particular position and
+ also reports back the position of the lens for each frame.
The LensPosition control is ignored unless the AfMode is set to
AfModeManual, though the value is reported back unconditionally in all
@@ -680,16 +754,16 @@ controls:
For example:
- 0 moves the lens to infinity.
- 0.5 moves the lens to focus on objects 2m away.
- 2 moves the lens to focus on objects 50cm away.
- And larger values will focus the lens closer.
+ - 0 moves the lens to infinity.
+ - 0.5 moves the lens to focus on objects 2m away.
+ - 2 moves the lens to focus on objects 50cm away.
+ - And larger values will focus the lens closer.
- The default value of the control should indicate a good general position
- for the lens, often corresponding to the hyperfocal distance (the
- closest position for which objects at infinity are still acceptably
- sharp). The minimum will often be zero (meaning infinity), and the
- maximum value defines the closest focus position.
+ The default value of the control should indicate a good general
+ position for the lens, often corresponding to the hyperfocal distance
+ (the closest position for which objects at infinity are still
+ acceptably sharp). The minimum will often be zero (meaning infinity),
+ and the maximum value defines the closest focus position.
\todo Define a property to report the Hyperfocal distance of calibrated
lenses.
@@ -697,22 +771,25 @@ controls:
- AfState:
type: int32_t
description: |
- Reports the current state of the AF algorithm in conjunction with the
- reported AfMode value and (in continuous AF mode) the AfPauseState
- value. The possible state changes are described below, though we note
- the following state transitions that occur when the AfMode is changed.
+ The current state of the AF algorithm.
+
+ This control reports the current state of the AF algorithm in
+ conjunction with the reported AfMode value and (in continuous AF mode)
+ the AfPauseState value. The possible state changes are described below,
+ though we note the following state transitions that occur when the
+ AfMode is changed.
If the AfMode is set to AfModeManual, then the AfState will always
- report AfStateIdle (even if the lens is subsequently moved). Changing to
- the AfModeManual state does not initiate any lens movement.
+ report AfStateIdle (even if the lens is subsequently moved). Changing
+ to the AfModeManual state does not initiate any lens movement.
If the AfMode is set to AfModeAuto then the AfState will report
- AfStateIdle. However, if AfModeAuto and AfTriggerStart are sent together
- then AfState will omit AfStateIdle and move straight to AfStateScanning
- (and start a scan).
+ AfStateIdle. However, if AfModeAuto and AfTriggerStart are sent
+ together then AfState will omit AfStateIdle and move straight to
+ AfStateScanning (and start a scan).
- If the AfMode is set to AfModeContinuous then the AfState will initially
- report AfStateScanning.
+ If the AfMode is set to AfModeContinuous then the AfState will
+ initially report AfStateScanning.
enum:
- name: AfStateIdle
@@ -725,11 +802,12 @@ controls:
value: 1
description: |
The AF algorithm is in auto mode (AfModeAuto), and a scan has been
- started using the AfTrigger control. The scan can be cancelled by
- sending AfTriggerCancel at which point the algorithm will either
- move back to AfStateIdle or, if the scan actually completes before
- the cancel request is processed, to one of AfStateFocused or
- AfStateFailed.
+ started using the AfTrigger control.
+
+ The scan can be cancelled by sending AfTriggerCancel at which point
+ the algorithm will either move back to AfStateIdle or, if the scan
+ actually completes before the cancel request is processed, to one
+ of AfStateFocused or AfStateFailed.
Alternatively the AF algorithm could be in continuous mode
(AfModeContinuous) at which point it may enter this state
@@ -750,9 +828,12 @@ controls:
- AfPauseState:
type: int32_t
description: |
- Only applicable in continuous (AfModeContinuous) mode, this reports
- whether the algorithm is currently running, paused or pausing (that is,
- will pause as soon as any in-progress scan completes).
+ Report whether the autofocus is currently running, paused or pausing.
+
+ This control is only applicable in continuous (AfModeContinuous) mode,
+ and reports whether the algorithm is currently running, paused or
+ pausing (that is, will pause as soon as any in-progress scan
+ completes).
Any change to AfMode will cause AfPauseStateRunning to be reported.
@@ -766,22 +847,27 @@ controls:
value: 1
description: |
Continuous AF has been sent an AfPauseDeferred control, and will
- pause as soon as any in-progress scan completes (and then report
- AfPauseStatePaused). No new scans will be start spontaneously until
+ pause as soon as any in-progress scan completes.
+
+ When the scan completes, the AfPauseState control will report
+ AfPauseStatePaused. No new scans will be start spontaneously until
the AfPauseResume control is sent.
- name: AfPauseStatePaused
value: 2
description: |
- Continuous AF is paused. No further state changes or lens movements
- will occur until the AfPauseResume control is sent.
+ Continuous AF is paused.
+
+ No further state changes or lens movements will occur until the
+ AfPauseResume control is sent.
- HdrMode:
type: int32_t
description: |
- Control to set the mode to be used for High Dynamic Range (HDR)
- imaging. HDR techniques typically include multiple exposure, image
- fusion and tone mapping techniques to improve the dynamic range of the
- resulting images.
+ Set the mode to be used for High Dynamic Range (HDR) imaging.
+
+ HDR techniques typically include multiple exposure, image fusion and
+ tone mapping techniques to improve the dynamic range of the resulting
+ images.
When using an HDR mode, images are captured with different sets of AGC
settings called HDR channels. Channels indicate in particular the type
@@ -795,16 +881,18 @@ controls:
- name: HdrModeOff
value: 0
description: |
- HDR is disabled. Metadata for this frame will not include the
- HdrChannel control.
+ HDR is disabled.
+
+ Metadata for this frame will not include the HdrChannel control.
- name: HdrModeMultiExposureUnmerged
value: 1
description: |
Multiple exposures will be generated in an alternating fashion.
- However, they will not be merged together and will be returned to
- the application as they are. Each image will be tagged with the
- correct HDR channel, indicating what kind of exposure it is. The
- tag should be the same as in the HdrModeMultiExposure case.
+
+ The multiple exposures will not be merged together and will be
+ returned to the application as they are. Each image will be tagged
+ with the correct HDR channel, indicating what kind of exposure it
+ is. The tag should be the same as in the HdrModeMultiExposure case.
The expectation is that an application using this mode would merge
the frames to create HDR images for itself if it requires them.
@@ -812,8 +900,10 @@ controls:
value: 2
description: |
Multiple exposures will be generated and merged to create HDR
- images. Each image will be tagged with the HDR channel (long, medium
- or short) that arrived and which caused this image to be output.
+ images.
+
+ Each image will be tagged with the HDR channel (long, medium or
+ short) that arrived and which caused this image to be output.
Systems that use two channels for HDR will return images tagged
alternately as the short and long channel. Systems that use three
@@ -823,24 +913,29 @@ controls:
value: 3
description: |
Multiple frames all at a single exposure will be used to create HDR
- images. These images should be reported as all corresponding to the
- HDR short channel.
+ images.
+
+ These images should be reported as all corresponding to the HDR
+ short channel.
- name: HdrModeNight
value: 4
description: |
- Multiple frames will be combined to produce "night mode" images. It
- is up to the implementation exactly which HDR channels it uses, and
- the images will all be tagged accordingly with the correct HDR
+ Multiple frames will be combined to produce "night mode" images.
+
+ It is up to the implementation exactly which HDR channels it uses,
+ and the images will all be tagged accordingly with the correct HDR
channel information.
- HdrChannel:
type: int32_t
description: |
+ The HDR channel used to capture the frame.
+
This value is reported back to the application so that it can discover
- whether this capture corresponds to the short or long exposure image (or
- any other image used by the HDR procedure). An application can monitor
- the HDR channel to discover when the differently exposed images have
- arrived.
+ whether this capture corresponds to the short or long exposure image
+ (or any other image used by the HDR procedure). An application can
+ monitor the HDR channel to discover when the differently exposed images
+ have arrived.
This metadata is only available when an HDR mode has been enabled.
@@ -868,8 +963,9 @@ controls:
- Gamma:
type: float
description: |
- Specify a fixed gamma value. Default must be 2.2 which closely mimics
- sRGB gamma. Note that this is camera gamma, so it is applied as
- 1.0/gamma.
+ Specify a fixed gamma value.
+
+ The default gamma value must be 2.2 which closely mimics sRGB gamma.
+ Note that this is camera gamma, so it is applied as 1.0/gamma.
...
diff --git a/src/libcamera/control_ids_rpi.yaml b/src/libcamera/control_ids_rpi.yaml
index cb097f88..42c4bf5d 100644
--- a/src/libcamera/control_ids_rpi.yaml
+++ b/src/libcamera/control_ids_rpi.yaml
@@ -10,9 +10,11 @@ controls:
- StatsOutputEnable:
type: bool
description: |
- Toggles the Raspberry Pi IPA to output a binary dump of the hardware
- generated statistics through the Request metadata in the Bcm2835StatsOutput
- control.
+ Toggles the Raspberry Pi IPA to output the hardware generated statistics.
+
+ When this control is set to true, the IPA outputs a binary dump of the
+ hardware generated statistics through the Request metadata in the
+ Bcm2835StatsOutput control.
\sa Bcm2835StatsOutput
@@ -20,9 +22,11 @@ controls:
type: uint8_t
size: [n]
description: |
- Span of the BCM2835 ISP generated statistics for the current frame. This
- is sent in the Request metadata if the StatsOutputEnable is set to true.
- The statistics struct definition can be found in include/linux/bcm2835-isp.h.
+ Span of the BCM2835 ISP generated statistics for the current frame.
+
+ This is sent in the Request metadata if the StatsOutputEnable is set to
+ true. The statistics struct definition can be found in
+ include/linux/bcm2835-isp.h.
\sa StatsOutputEnable
diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp
index 11d35321..ea439730 100644
--- a/src/libcamera/controls.cpp
+++ b/src/libcamera/controls.cpp
@@ -7,10 +7,9 @@
#include <libcamera/controls.h>
-#include <iomanip>
#include <sstream>
-#include <string>
#include <string.h>
+#include <string>
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>
@@ -390,7 +389,16 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen
* \param[in] id The control numerical ID
* \param[in] name The control name
* \param[in] type The control data type
+ * \param[in] size The size of the array control, or 0 if scalar control
+ * \param[in] enumStrMap The map from enum names to values (optional)
*/
+ControlId::ControlId(unsigned int id, const std::string &name, ControlType type,
+ std::size_t size, const std::map<std::string, int32_t> &enumStrMap)
+ : id_(id), name_(name), type_(type), size_(size), enumStrMap_(enumStrMap)
+{
+ for (const auto &pair : enumStrMap_)
+ reverseMap_[pair.second] = pair.first;
+}
/**
* \fn unsigned int ControlId::id() const
@@ -411,6 +419,25 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen
*/
/**
+ * \fn bool ControlId::isArray() const
+ * \brief Determine if the control is an array control
+ * \return True if the control is an array control, false otherwise
+ */
+
+/**
+ * \fn std::size_t ControlId::size() const
+ * \brief Retrieve the size of the control if it is an array control
+ * \return The size of the array control, size_t::max for dynamic extent, or 0
+ * for non-array
+ */
+
+/**
+ * \fn const std::map<int32_t, std::string> &ControlId::enumerators() const
+ * \brief Retrieve the map of enum values to enum names
+ * \return The map of enum values to enum names
+ */
+
+/**
* \fn bool operator==(unsigned int lhs, const ControlId &rhs)
* \brief Compare a ControlId with a control numerical ID
* \param[in] lhs Left-hand side numerical ID
@@ -460,6 +487,7 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen
* \brief Construct a Control instance
* \param[in] id The control numerical ID
* \param[in] name The control name
+ * \param[in] enumStrMap The map from enum names to values (optional)
*
* The control data type is automatically deduced from the template type T.
*/
diff --git a/src/libcamera/converter.cpp b/src/libcamera/converter.cpp
index d3d38c1b..8237998f 100644
--- a/src/libcamera/converter.cpp
+++ b/src/libcamera/converter.cpp
@@ -14,7 +14,7 @@
#include "libcamera/internal/media_device.h"
/**
- * \file internal/converter.h
+ * \file converter.h
* \brief Abstract converter
*/
@@ -111,12 +111,12 @@ Converter::~Converter()
/**
* \fn Converter::exportBuffers()
* \brief Export buffers from the converter device
- * \param[in] output Output stream index exporting the buffers
+ * \param[in] stream Output stream pointer exporting the buffers
* \param[in] count Number of buffers to allocate
* \param[out] buffers Vector to store allocated buffers
*
* This function operates similarly to V4L2VideoDevice::exportBuffers() on the
- * output stream indicated by the \a output index.
+ * output stream indicated by the \a output.
*
* \return The number of allocated buffers on success or a negative error code
* otherwise
@@ -137,7 +137,7 @@ Converter::~Converter()
* \fn Converter::queueBuffers()
* \brief Queue buffers to converter device
* \param[in] input The frame buffer to apply the conversion
- * \param[out] outputs The container holding the output stream indexes and
+ * \param[out] outputs The container holding the output stream pointers and
* their respective frame buffer outputs.
*
* This function queues the \a input frame buffer on the output streams of the
diff --git a/src/libcamera/converter/converter_v4l2_m2m.cpp b/src/libcamera/converter/converter_v4l2_m2m.cpp
index d8929fc5..e4f656da 100644
--- a/src/libcamera/converter/converter_v4l2_m2m.cpp
+++ b/src/libcamera/converter/converter_v4l2_m2m.cpp
@@ -8,7 +8,6 @@
#include "libcamera/internal/converter/converter_v4l2_m2m.h"
-#include <algorithm>
#include <limits.h>
#include <libcamera/base/log.h>
@@ -23,7 +22,7 @@
#include "libcamera/internal/v4l2_videodevice.h"
/**
- * \file internal/converter/converter_v4l2_m2m.h
+ * \file converter/converter_v4l2_m2m.h
* \brief V4L2 M2M based converter
*/
@@ -32,24 +31,24 @@ namespace libcamera {
LOG_DECLARE_CATEGORY(Converter)
/* -----------------------------------------------------------------------------
- * V4L2M2MConverter::Stream
+ * V4L2M2MConverter::V4L2M2MStream
*/
-V4L2M2MConverter::Stream::Stream(V4L2M2MConverter *converter, unsigned int index)
- : converter_(converter), index_(index)
+V4L2M2MConverter::V4L2M2MStream::V4L2M2MStream(V4L2M2MConverter *converter, const Stream *stream)
+ : converter_(converter), stream_(stream)
{
m2m_ = std::make_unique<V4L2M2MDevice>(converter->deviceNode());
- m2m_->output()->bufferReady.connect(this, &Stream::outputBufferReady);
- m2m_->capture()->bufferReady.connect(this, &Stream::captureBufferReady);
+ m2m_->output()->bufferReady.connect(this, &V4L2M2MStream::outputBufferReady);
+ m2m_->capture()->bufferReady.connect(this, &V4L2M2MStream::captureBufferReady);
int ret = m2m_->open();
if (ret < 0)
m2m_.reset();
}
-int V4L2M2MConverter::Stream::configure(const StreamConfiguration &inputCfg,
- const StreamConfiguration &outputCfg)
+int V4L2M2MConverter::V4L2M2MStream::configure(const StreamConfiguration &inputCfg,
+ const StreamConfiguration &outputCfg)
{
V4L2PixelFormat videoFormat =
m2m_->output()->toV4L2PixelFormat(inputCfg.pixelFormat);
@@ -101,13 +100,13 @@ int V4L2M2MConverter::Stream::configure(const StreamConfiguration &inputCfg,
return 0;
}
-int V4L2M2MConverter::Stream::exportBuffers(unsigned int count,
- std::vector<std::unique_ptr<FrameBuffer>> *buffers)
+int V4L2M2MConverter::V4L2M2MStream::exportBuffers(unsigned int count,
+ std::vector<std::unique_ptr<FrameBuffer>> *buffers)
{
return m2m_->capture()->exportBuffers(count, buffers);
}
-int V4L2M2MConverter::Stream::start()
+int V4L2M2MConverter::V4L2M2MStream::start()
{
int ret = m2m_->output()->importBuffers(inputBufferCount_);
if (ret < 0)
@@ -134,7 +133,7 @@ int V4L2M2MConverter::Stream::start()
return 0;
}
-void V4L2M2MConverter::Stream::stop()
+void V4L2M2MConverter::V4L2M2MStream::stop()
{
m2m_->capture()->streamOff();
m2m_->output()->streamOff();
@@ -142,7 +141,7 @@ void V4L2M2MConverter::Stream::stop()
m2m_->output()->releaseBuffers();
}
-int V4L2M2MConverter::Stream::queueBuffers(FrameBuffer *input, FrameBuffer *output)
+int V4L2M2MConverter::V4L2M2MStream::queueBuffers(FrameBuffer *input, FrameBuffer *output)
{
int ret = m2m_->output()->queueBuffer(input);
if (ret < 0)
@@ -155,12 +154,12 @@ int V4L2M2MConverter::Stream::queueBuffers(FrameBuffer *input, FrameBuffer *outp
return 0;
}
-std::string V4L2M2MConverter::Stream::logPrefix() const
+std::string V4L2M2MConverter::V4L2M2MStream::logPrefix() const
{
- return "stream" + std::to_string(index_);
+ return stream_->configuration().toString();
}
-void V4L2M2MConverter::Stream::outputBufferReady(FrameBuffer *buffer)
+void V4L2M2MConverter::V4L2M2MStream::outputBufferReady(FrameBuffer *buffer)
{
auto it = converter_->queue_.find(buffer);
if (it == converter_->queue_.end())
@@ -172,7 +171,7 @@ void V4L2M2MConverter::Stream::outputBufferReady(FrameBuffer *buffer)
}
}
-void V4L2M2MConverter::Stream::captureBufferReady(FrameBuffer *buffer)
+void V4L2M2MConverter::V4L2M2MStream::captureBufferReady(FrameBuffer *buffer)
{
converter_->outputBufferReady.emit(buffer);
}
@@ -333,21 +332,24 @@ int V4L2M2MConverter::configure(const StreamConfiguration &inputCfg,
int ret = 0;
streams_.clear();
- streams_.reserve(outputCfgs.size());
for (unsigned int i = 0; i < outputCfgs.size(); ++i) {
- Stream &stream = streams_.emplace_back(this, i);
+ const StreamConfiguration &cfg = outputCfgs[i];
+ std::unique_ptr<V4L2M2MStream> stream =
+ std::make_unique<V4L2M2MStream>(this, cfg.stream());
- if (!stream.isValid()) {
+ if (!stream->isValid()) {
LOG(Converter, Error)
<< "Failed to create stream " << i;
ret = -EINVAL;
break;
}
- ret = stream.configure(inputCfg, outputCfgs[i]);
+ ret = stream->configure(inputCfg, cfg);
if (ret < 0)
break;
+
+ streams_.emplace(cfg.stream(), std::move(stream));
}
if (ret < 0) {
@@ -361,13 +363,14 @@ int V4L2M2MConverter::configure(const StreamConfiguration &inputCfg,
/**
* \copydoc libcamera::Converter::exportBuffers
*/
-int V4L2M2MConverter::exportBuffers(unsigned int output, unsigned int count,
+int V4L2M2MConverter::exportBuffers(const Stream *stream, unsigned int count,
std::vector<std::unique_ptr<FrameBuffer>> *buffers)
{
- if (output >= streams_.size())
+ auto iter = streams_.find(stream);
+ if (iter == streams_.end())
return -EINVAL;
- return streams_[output].exportBuffers(count, buffers);
+ return iter->second->exportBuffers(count, buffers);
}
/**
@@ -377,8 +380,8 @@ int V4L2M2MConverter::start()
{
int ret;
- for (Stream &stream : streams_) {
- ret = stream.start();
+ for (auto &iter : streams_) {
+ ret = iter.second->start();
if (ret < 0) {
stop();
return ret;
@@ -393,41 +396,40 @@ int V4L2M2MConverter::start()
*/
void V4L2M2MConverter::stop()
{
- for (Stream &stream : utils::reverse(streams_))
- stream.stop();
+ for (auto &iter : streams_)
+ iter.second->stop();
}
/**
* \copydoc libcamera::Converter::queueBuffers
*/
int V4L2M2MConverter::queueBuffers(FrameBuffer *input,
- const std::map<unsigned int, FrameBuffer *> &outputs)
+ const std::map<const Stream *, FrameBuffer *> &outputs)
{
- unsigned int mask = 0;
+ std::set<FrameBuffer *> outputBufs;
int ret;
/*
* Validate the outputs as a sanity check: at least one output is
* required, all outputs must reference a valid stream and no two
- * outputs can reference the same stream.
+ * streams can reference same output framebuffers.
*/
if (outputs.empty())
return -EINVAL;
- for (auto [index, buffer] : outputs) {
+ for (auto [stream, buffer] : outputs) {
if (!buffer)
return -EINVAL;
- if (index >= streams_.size())
- return -EINVAL;
- if (mask & (1 << index))
- return -EINVAL;
- mask |= 1 << index;
+ outputBufs.insert(buffer);
}
+ if (outputBufs.size() != streams_.size())
+ return -EINVAL;
+
/* Queue the input and output buffers to all the streams. */
- for (auto [index, buffer] : outputs) {
- ret = streams_[index].queueBuffers(input, buffer);
+ for (auto [stream, buffer] : outputs) {
+ ret = streams_.at(stream)->queueBuffers(input, buffer);
if (ret < 0)
return ret;
}
diff --git a/src/libcamera/converter/meson.build b/src/libcamera/converter/meson.build
index 2aa72fe4..af1a80fe 100644
--- a/src/libcamera/converter/meson.build
+++ b/src/libcamera/converter/meson.build
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_sources += files([
+libcamera_internal_sources += files([
'converter_v4l2_m2m.cpp'
])
diff --git a/src/libcamera/device_enumerator_udev.cpp b/src/libcamera/device_enumerator_udev.cpp
index 01c70b6d..4e20a3cc 100644
--- a/src/libcamera/device_enumerator_udev.cpp
+++ b/src/libcamera/device_enumerator_udev.cpp
@@ -332,6 +332,14 @@ int DeviceEnumeratorUdev::addV4L2Device(dev_t devnum)
void DeviceEnumeratorUdev::udevNotify()
{
struct udev_device *dev = udev_monitor_receive_device(monitor_);
+ if (!dev) {
+ int err = errno;
+ LOG(DeviceEnumerator, Warning)
+ << "Ignoring notfication received without a device: "
+ << strerror(err);
+ return;
+ }
+
std::string_view action(udev_device_get_action(dev));
std::string_view deviceNode(udev_device_get_devnode(dev));
diff --git a/src/libcamera/dma_buf_allocator.cpp b/src/libcamera/dma_buf_allocator.cpp
index c06eca7d..be6efb89 100644
--- a/src/libcamera/dma_buf_allocator.cpp
+++ b/src/libcamera/dma_buf_allocator.cpp
@@ -13,7 +13,6 @@
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
-#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
@@ -22,6 +21,7 @@
#include <linux/udmabuf.h>
#include <libcamera/base/log.h>
+#include <libcamera/base/memfd.h>
/**
* \file dma_buf_allocator.cpp
@@ -126,54 +126,16 @@ DmaBufAllocator::~DmaBufAllocator() = default;
* \brief Check if the DmaBufAllocator instance is valid
* \return True if the DmaBufAllocator is valid, false otherwise
*/
-
-/* uClibc doesn't provide the file sealing API. */
-#ifndef __DOXYGEN__
-#if not HAVE_FILE_SEALS
-#define F_ADD_SEALS 1033
-#define F_SEAL_SHRINK 0x0002
-#endif
-#endif
-
UniqueFD DmaBufAllocator::allocFromUDmaBuf(const char *name, std::size_t size)
{
/* Size must be a multiple of the page size. Round it up. */
std::size_t pageMask = sysconf(_SC_PAGESIZE) - 1;
size = (size + pageMask) & ~pageMask;
-#if HAVE_MEMFD_CREATE
- int ret = memfd_create(name, MFD_ALLOW_SEALING | MFD_CLOEXEC);
-#else
- int ret = syscall(SYS_memfd_create, name, MFD_ALLOW_SEALING | MFD_CLOEXEC);
-#endif
- if (ret < 0) {
- ret = errno;
- LOG(DmaBufAllocator, Error)
- << "Failed to allocate memfd storage for " << name
- << ": " << strerror(ret);
- return {};
- }
-
- UniqueFD memfd(ret);
-
- ret = ftruncate(memfd.get(), size);
- if (ret < 0) {
- ret = errno;
- LOG(DmaBufAllocator, Error)
- << "Failed to set memfd size for " << name
- << ": " << strerror(ret);
- return {};
- }
-
/* udmabuf dma-buffers *must* have the F_SEAL_SHRINK seal. */
- ret = fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_SHRINK);
- if (ret < 0) {
- ret = errno;
- LOG(DmaBufAllocator, Error)
- << "Failed to seal the memfd for " << name
- << ": " << strerror(ret);
+ UniqueFD memfd = MemFd::create(name, size, MemFd::Seal::Shrink);
+ if (!memfd.isValid())
return {};
- }
struct udmabuf_create create;
@@ -182,7 +144,7 @@ UniqueFD DmaBufAllocator::allocFromUDmaBuf(const char *name, std::size_t size)
create.offset = 0;
create.size = size;
- ret = ::ioctl(providerHandle_.get(), UDMABUF_CREATE, &create);
+ int ret = ::ioctl(providerHandle_.get(), UDMABUF_CREATE, &create);
if (ret < 0) {
ret = errno;
LOG(DmaBufAllocator, Error)
diff --git a/src/libcamera/fence.cpp b/src/libcamera/fence.cpp
index 634c74f8..73299b40 100644
--- a/src/libcamera/fence.cpp
+++ b/src/libcamera/fence.cpp
@@ -11,7 +11,7 @@ namespace libcamera {
/**
*
- * \file libcamera/fence.h
+ * \file fence.h
* \brief Definition of the Fence class
*/
diff --git a/src/libcamera/formats.cpp b/src/libcamera/formats.cpp
index cf41f2c2..dbefb094 100644
--- a/src/libcamera/formats.cpp
+++ b/src/libcamera/formats.cpp
@@ -7,8 +7,7 @@
#include "libcamera/internal/formats.h"
-#include <algorithm>
-#include <errno.h>
+#include <map>
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>
@@ -16,7 +15,7 @@
#include <libcamera/formats.h>
/**
- * \file internal/formats.h
+ * \file libcamera/internal/formats.h
* \brief Types and helper functions to handle libcamera image formats
*/
@@ -527,6 +526,16 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
.pixelsPerGroup = 4,
.planes = {{ { 5, 1 }, { 0, 0 }, { 0, 0 } }},
} },
+ { formats::R12_CSI2P, {
+ .name = "R12_CSI2P",
+ .format = formats::R12_CSI2P,
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_Y12P), },
+ .bitsPerPixel = 12,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ .packed = true,
+ .pixelsPerGroup = 2,
+ .planes = {{ { 3, 1 }, { 0, 0 }, { 0, 0 } }},
+ } },
{ formats::R12, {
.name = "R12",
.format = formats::R12,
diff --git a/src/libcamera/formats.yaml b/src/libcamera/formats.yaml
index fe027a7c..2d54d391 100644
--- a/src/libcamera/formats.yaml
+++ b/src/libcamera/formats.yaml
@@ -138,6 +138,9 @@ formats:
- R10_CSI2P:
fourcc: DRM_FORMAT_R10
mod: MIPI_FORMAT_MOD_CSI2_PACKED
+ - R12_CSI2P:
+ fourcc: DRM_FORMAT_R12
+ mod: MIPI_FORMAT_MOD_CSI2_PACKED
- SRGGB10_CSI2P:
fourcc: DRM_FORMAT_SRGGB10
diff --git a/src/libcamera/framebuffer.cpp b/src/libcamera/framebuffer.cpp
index 63d679cb..826848f7 100644
--- a/src/libcamera/framebuffer.cpp
+++ b/src/libcamera/framebuffer.cpp
@@ -16,7 +16,10 @@
/**
* \file libcamera/framebuffer.h
* \brief Frame buffer handling
- *
+ */
+
+/**
+ * \internal
* \file libcamera/internal/framebuffer.h
* \brief Internal frame buffer handling support
*/
@@ -104,6 +107,7 @@ LOG_DEFINE_CATEGORY(Buffer)
* \return The array of per-plane metadata
*/
+#ifndef __DOXYGEN_PUBLIC__
/**
* \class FrameBuffer::Private
* \brief Base class for FrameBuffer private data
@@ -206,6 +210,7 @@ FrameBuffer::Private::~Private()
* \brief Retrieve the dynamic metadata
* \return Dynamic metadata for the frame contained in the buffer
*/
+#endif /* __DOXYGEN_PUBLIC__ */
/**
* \class FrameBuffer
diff --git a/src/libcamera/ipa_data_serializer.cpp b/src/libcamera/ipa_data_serializer.cpp
index 3e9bef08..2189a246 100644
--- a/src/libcamera/ipa_data_serializer.cpp
+++ b/src/libcamera/ipa_data_serializer.cpp
@@ -11,6 +11,8 @@
#include <libcamera/base/log.h>
+#include "libcamera/internal/byte_stream_buffer.h"
+
/**
* \file ipa_data_serializer.h
* \brief IPA Data Serializer
@@ -537,7 +539,6 @@ IPADataSerializer<SharedFD>::serialize(const SharedFD &data,
if (data.isValid())
fdVec.push_back(data);
-
return { dataVec, fdVec };
}
@@ -604,7 +605,7 @@ IPADataSerializer<FrameBuffer::Plane>::deserialize(std::vector<uint8_t>::const_i
FrameBuffer::Plane ret;
ret.fd = IPADataSerializer<SharedFD>::deserialize(dataBegin, dataBegin + 4,
- fdsBegin, fdsBegin + 1);
+ fdsBegin, fdsBegin + 1);
ret.offset = readPOD<uint32_t>(dataBegin, 4, dataEnd);
ret.length = readPOD<uint32_t>(dataBegin, 8, dataEnd);
diff --git a/src/libcamera/ipa_manager.cpp b/src/libcamera/ipa_manager.cpp
index f4e0b633..cfc24d38 100644
--- a/src/libcamera/ipa_manager.cpp
+++ b/src/libcamera/ipa_manager.cpp
@@ -95,8 +95,6 @@ LOG_DEFINE_CATEGORY(IPAManager)
* IPC.
*/
-IPAManager *IPAManager::self_ = nullptr;
-
/**
* \brief Construct an IPAManager instance
*
@@ -105,10 +103,6 @@ IPAManager *IPAManager::self_ = nullptr;
*/
IPAManager::IPAManager()
{
- if (self_)
- LOG(IPAManager, Fatal)
- << "Multiple IPAManager objects are not allowed";
-
#if HAVE_IPA_PUBKEY
if (!pubKey_.isValid())
LOG(IPAManager, Warning) << "Public key not valid";
@@ -153,16 +147,12 @@ IPAManager::IPAManager()
if (!ipaCount)
LOG(IPAManager, Warning)
<< "No IPA found in '" IPA_MODULE_DIR "'";
-
- self_ = this;
}
IPAManager::~IPAManager()
{
for (IPAModule *module : modules_)
delete module;
-
- self_ = nullptr;
}
/**
diff --git a/src/libcamera/ipa_module.cpp b/src/libcamera/ipa_module.cpp
index 0756b691..9ca74be6 100644
--- a/src/libcamera/ipa_module.cpp
+++ b/src/libcamera/ipa_module.cpp
@@ -8,7 +8,6 @@
#include "libcamera/internal/ipa_module.h"
#include <algorithm>
-#include <array>
#include <ctype.h>
#include <dlfcn.h>
#include <elf.h>
@@ -51,8 +50,8 @@ typename std::remove_extent_t<T> *elfPointer(Span<const uint8_t> elf,
if (size > elf.size() || size < objSize)
return nullptr;
- return reinterpret_cast<typename std::remove_extent_t<T> *>
- (reinterpret_cast<const char *>(elf.data()) + offset);
+ return reinterpret_cast<typename std::remove_extent_t<T> *>(
+ reinterpret_cast<const char *>(elf.data()) + offset);
}
template<typename T>
diff --git a/src/libcamera/ipa_proxy.cpp b/src/libcamera/ipa_proxy.cpp
index 6c17c456..85004737 100644
--- a/src/libcamera/ipa_proxy.cpp
+++ b/src/libcamera/ipa_proxy.cpp
@@ -7,7 +7,6 @@
#include "libcamera/internal/ipa_proxy.h"
-#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
@@ -72,6 +71,7 @@ IPAProxy::~IPAProxy()
/**
* \brief Retrieve the absolute path to an IPA configuration file
* \param[in] name The configuration file name
+ * \param[in] fallbackName The name of a fallback configuration file
*
* This function locates the configuration file for an IPA and returns its
* absolute path. It searches the following directories, in order:
@@ -89,10 +89,14 @@ IPAProxy::~IPAProxy()
* named after the IPA module name, as reported in IPAModuleInfo::name, and for
* a file named \a name within that directory. The \a name is IPA-specific.
*
+ * If the file named \a name is not found and \a fallbackName is non-empty then
+ * the whole search is repeated for \a fallbackName.
+ *
* \return The full path to the IPA configuration file, or an empty string if
* no configuration file can be found
*/
-std::string IPAProxy::configurationFile(const std::string &name) const
+std::string IPAProxy::configurationFile(const std::string &name,
+ const std::string &fallbackName) const
{
struct stat statbuf;
int ret;
@@ -146,11 +150,18 @@ std::string IPAProxy::configurationFile(const std::string &name) const
}
}
- LOG(IPAProxy, Error)
- << "Configuration file '" << name
- << "' not found for IPA module '" << ipaName << "'";
+ if (fallbackName.empty()) {
+ LOG(IPAProxy, Error)
+ << "Configuration file '" << name
+ << "' not found for IPA module '" << ipaName << "'";
+ return std::string();
+ }
- return std::string();
+ LOG(IPAProxy, Warning)
+ << "Configuration file '" << name
+ << "' not found for IPA module '" << ipaName
+ << "', falling back to '" << fallbackName << "'";
+ return configurationFile(fallbackName);
}
/**
diff --git a/src/libcamera/ipc_unixsocket.cpp b/src/libcamera/ipc_unixsocket.cpp
index 75285b67..002053e3 100644
--- a/src/libcamera/ipc_unixsocket.cpp
+++ b/src/libcamera/ipc_unixsocket.cpp
@@ -12,6 +12,7 @@
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
+#include <vector>
#include <libcamera/base/event_notifier.h>
#include <libcamera/base/log.h>
@@ -247,10 +248,9 @@ int IPCUnixSocket::sendData(const void *buffer, size_t length,
iov[0].iov_base = const_cast<void *>(buffer);
iov[0].iov_len = length;
- char buf[CMSG_SPACE(num * sizeof(uint32_t))];
- memset(buf, 0, sizeof(buf));
+ std::vector<uint8_t> buf(CMSG_SPACE(num * sizeof(uint32_t)));
- struct cmsghdr *cmsg = (struct cmsghdr *)buf;
+ struct cmsghdr *cmsg = reinterpret_cast<struct cmsghdr *>(buf.data());
cmsg->cmsg_len = CMSG_LEN(num * sizeof(uint32_t));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
@@ -283,10 +283,9 @@ int IPCUnixSocket::recvData(void *buffer, size_t length,
iov[0].iov_base = buffer;
iov[0].iov_len = length;
- char buf[CMSG_SPACE(num * sizeof(uint32_t))];
- memset(buf, 0, sizeof(buf));
+ std::vector<uint8_t> buf(CMSG_SPACE(num * sizeof(uint32_t)));
- struct cmsghdr *cmsg = (struct cmsghdr *)buf;
+ struct cmsghdr *cmsg = reinterpret_cast<struct cmsghdr *>(buf.data());
cmsg->cmsg_len = CMSG_LEN(num * sizeof(uint32_t));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
diff --git a/src/libcamera/mapped_framebuffer.cpp b/src/libcamera/mapped_framebuffer.cpp
index b3104e05..ad82e1f6 100644
--- a/src/libcamera/mapped_framebuffer.cpp
+++ b/src/libcamera/mapped_framebuffer.cpp
@@ -16,7 +16,7 @@
#include <libcamera/base/log.h>
/**
- * \file libcamera/internal/mapped_framebuffer.h
+ * \file mapped_framebuffer.h
* \brief Frame buffer memory mapping support
*/
diff --git a/src/libcamera/media_device.cpp b/src/libcamera/media_device.cpp
index bd054552..d71dad74 100644
--- a/src/libcamera/media_device.cpp
+++ b/src/libcamera/media_device.cpp
@@ -818,20 +818,12 @@ int MediaDevice::setupLink(const MediaLink *link, unsigned int flags)
if (ret) {
ret = -errno;
LOG(MediaDevice, Error)
- << "Failed to setup link "
- << source->entity()->name() << "["
- << source->index() << "] -> "
- << sink->entity()->name() << "["
- << sink->index() << "]: "
+ << "Failed to setup link " << *link << ": "
<< strerror(-ret);
return ret;
}
- LOG(MediaDevice, Debug)
- << source->entity()->name() << "["
- << source->index() << "] -> "
- << sink->entity()->name() << "["
- << sink->index() << "]: " << flags;
+ LOG(MediaDevice, Debug) << *link << ": " << flags;
return 0;
}
diff --git a/src/libcamera/media_object.cpp b/src/libcamera/media_object.cpp
index 1b191a1e..3e3772a6 100644
--- a/src/libcamera/media_object.cpp
+++ b/src/libcamera/media_object.cpp
@@ -147,6 +147,31 @@ MediaLink::MediaLink(const struct media_v2_link *link, MediaPad *source,
}
/**
+ * \brief Generate a string representation of the MediaLink
+ * \return A string representing the MediaLink
+ */
+std::string MediaLink::toString() const
+{
+ std::stringstream ss;
+ ss << *this;
+
+ return ss.str();
+}
+
+/**
+ * \brief Insert a text representation of a Link into an output stream
+ * \param[in] out The output stream
+ * \param[in] link The MediaLink
+ * \return The output stream \a out
+ */
+std::ostream &operator<<(std::ostream &out, const MediaLink &link)
+{
+ out << *link.source() << " -> " << *link.sink();
+
+ return out;
+}
+
+/**
* \fn MediaLink::source()
* \brief Retrieve the link's source pad
* \return The source pad at the origin of the link
@@ -236,6 +261,31 @@ void MediaPad::addLink(MediaLink *link)
}
/**
+ * \brief Generate a string representation of the MediaPad
+ * \return A string representing the MediaPad
+ */
+std::string MediaPad::toString() const
+{
+ std::stringstream ss;
+ ss << *this;
+
+ return ss.str();
+}
+
+/**
+ * \brief Insert a text representation of a MediaPad into an output stream
+ * \param[in] out The output stream
+ * \param[in] pad The MediaPad
+ * \return The output stream \a out
+ */
+std::ostream &operator<<(std::ostream &out, const MediaPad &pad)
+{
+ out << "'" << pad.entity()->name() << "'[" << pad.index() << "]";
+
+ return out;
+}
+
+/**
* \class MediaEntity
* \brief The MediaEntity represents an entity in the media graph
*
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index 89504cee..aa9ab029 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -1,14 +1,26 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_sources = files([
- 'bayer_format.cpp',
- 'byte_stream_buffer.cpp',
+libcamera_public_sources = files([
'camera.cpp',
- 'camera_controls.cpp',
- 'camera_lens.cpp',
'camera_manager.cpp',
'color_space.cpp',
'controls.cpp',
+ 'fence.cpp',
+ 'framebuffer.cpp',
+ 'framebuffer_allocator.cpp',
+ 'geometry.cpp',
+ 'orientation.cpp',
+ 'pixel_format.cpp',
+ 'request.cpp',
+ 'stream.cpp',
+ 'transform.cpp',
+])
+
+libcamera_internal_sources = files([
+ 'bayer_format.cpp',
+ 'byte_stream_buffer.cpp',
+ 'camera_controls.cpp',
+ 'camera_lens.cpp',
'control_serializer.cpp',
'control_validator.cpp',
'converter.cpp',
@@ -16,11 +28,7 @@ libcamera_sources = files([
'device_enumerator.cpp',
'device_enumerator_sysfs.cpp',
'dma_buf_allocator.cpp',
- 'fence.cpp',
'formats.cpp',
- 'framebuffer.cpp',
- 'framebuffer_allocator.cpp',
- 'geometry.cpp',
'ipa_controls.cpp',
'ipa_data_serializer.cpp',
'ipa_interface.cpp',
@@ -33,17 +41,12 @@ libcamera_sources = files([
'mapped_framebuffer.cpp',
'media_device.cpp',
'media_object.cpp',
- 'orientation.cpp',
'pipeline_handler.cpp',
- 'pixel_format.cpp',
'process.cpp',
'pub_key.cpp',
- 'request.cpp',
'shared_mem_object.cpp',
'source_paths.cpp',
- 'stream.cpp',
'sysfs.cpp',
- 'transform.cpp',
'v4l2_device.cpp',
'v4l2_pixelformat.cpp',
'v4l2_subdevice.cpp',
@@ -51,10 +54,6 @@ libcamera_sources = files([
'yaml_parser.cpp',
])
-libcamera_sources += libcamera_public_headers
-libcamera_sources += libcamera_generated_ipa_headers
-libcamera_sources += libcamera_tracepoint_header
-
includes = [
libcamera_includes,
]
@@ -104,14 +103,14 @@ endif
if liblttng.found()
tracing_enabled = true
config_h.set('HAVE_TRACING', 1)
- libcamera_sources += files(['tracepoints.cpp'])
+ libcamera_internal_sources += files(['tracepoints.cpp'])
else
tracing_enabled = false
endif
if libudev.found()
config_h.set('HAVE_LIBUDEV', 1)
- libcamera_sources += files([
+ libcamera_internal_sources += files([
'device_enumerator_udev.cpp',
])
endif
@@ -130,29 +129,33 @@ endif
control_sources = []
controls_mode_files = {
- 'controls' : controls_files,
- 'properties' : properties_files,
+ 'controls': [
+ controls_files,
+ 'control_ids.cpp',
+ ],
+ 'properties': [
+ properties_files,
+ 'property_ids.cpp',
+ ],
}
-foreach mode, input_files : controls_mode_files
- input_files = files(input_files)
-
- if mode == 'controls'
- template_file = files('control_ids.cpp.in')
- else
- template_file = files('property_ids.cpp.in')
- endif
+foreach mode, inout_files : controls_mode_files
+ input_files = inout_files[0]
+ output_file = inout_files[1]
+ template_file = files('control_ids.cpp.in')
ranges_file = files('control_ranges.yaml')
- control_sources += custom_target(mode + '_cpp',
+
+ control_sources += custom_target(mode + '_ids_cpp',
input : input_files,
- output : mode + '_ids.cpp',
+ output : output_file,
command : [gen_controls, '-o', '@OUTPUT@',
'--mode', mode, '-t', template_file,
- '-r', ranges_file, '@INPUT@'])
+ '-r', ranges_file, '@INPUT@'],
+ env : py_build_env)
endforeach
-libcamera_sources += control_sources
+libcamera_public_sources += control_sources
gen_version = meson.project_source_root() / 'utils' / 'gen-version.sh'
@@ -163,7 +166,7 @@ version_cpp = vcs_tag(command : [gen_version, meson.project_build_root(), meson.
output : 'version.cpp',
fallback : meson.project_version())
-libcamera_sources += version_cpp
+libcamera_public_sources += version_cpp
if ipa_sign_module
ipa_pub_key_cpp = custom_target('ipa_pub_key_cpp',
@@ -171,7 +174,7 @@ if ipa_sign_module
output : 'ipa_pub_key.cpp',
command : [gen_ipa_pub_key, '@INPUT@', '@OUTPUT@'])
- libcamera_sources += ipa_pub_key_cpp
+ libcamera_internal_sources += ipa_pub_key_cpp
endif
libcamera_deps += [
@@ -191,7 +194,13 @@ libcamera_deps += [
# for the presence or abscence of the dynamic tag.
libcamera = shared_library('libcamera',
- libcamera_sources,
+ [
+ libcamera_public_headers,
+ libcamera_public_sources,
+ libcamera_ipa_headers,
+ libcamera_internal_headers,
+ libcamera_internal_sources,
+ ],
version : libcamera_version,
soversion : libcamera_soversion,
name_prefix : '',
@@ -201,7 +210,6 @@ libcamera = shared_library('libcamera',
dependencies : libcamera_deps)
libcamera_public = declare_dependency(sources : [
- libcamera_ipa_headers,
libcamera_public_headers,
],
include_directories : libcamera_includes,
@@ -210,7 +218,7 @@ libcamera_public = declare_dependency(sources : [
# Internal dependency for components and plugins which can use private APIs
libcamera_private = declare_dependency(sources : [
- libcamera_generated_ipa_headers,
+ libcamera_ipa_headers,
],
dependencies : [
libcamera_public,
diff --git a/src/libcamera/orientation.cpp b/src/libcamera/orientation.cpp
index 47fd6a32..7d7d21ae 100644
--- a/src/libcamera/orientation.cpp
+++ b/src/libcamera/orientation.cpp
@@ -8,10 +8,9 @@
#include <libcamera/orientation.h>
#include <array>
-#include <string>
/**
- * \file libcamera/orientation.h
+ * \file orientation.h
* \brief Image orientation definition
*/
@@ -102,10 +101,14 @@ std::ostream &operator<<(std::ostream &out, const Orientation &orientation)
{
constexpr std::array<const char *, 9> orientationNames = {
"", /* Orientation starts counting from 1. */
- "Rotate0", "Rotate0Mirror",
- "Rotate180", "Rotate180Mirror",
- "Rotate90Mirror", "Rotate270",
- "Rotate270Mirror", "Rotate90",
+ "Rotate0",
+ "Rotate0Mirror",
+ "Rotate180",
+ "Rotate180Mirror",
+ "Rotate90Mirror",
+ "Rotate270",
+ "Rotate270Mirror",
+ "Rotate90",
};
out << orientationNames[static_cast<unsigned int>(orientation)];
diff --git a/src/libcamera/pipeline/imx8-isi/meson.build b/src/libcamera/pipeline/imx8-isi/meson.build
index ffd0ce54..b369b031 100644
--- a/src/libcamera/pipeline/imx8-isi/meson.build
+++ b/src/libcamera/pipeline/imx8-isi/meson.build
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_sources += files([
+libcamera_internal_sources += files([
'imx8-isi.cpp'
])
diff --git a/src/libcamera/pipeline/ipu3/cio2.cpp b/src/libcamera/pipeline/ipu3/cio2.cpp
index 81a7a8ab..74a5d93f 100644
--- a/src/libcamera/pipeline/ipu3/cio2.cpp
+++ b/src/libcamera/pipeline/ipu3/cio2.cpp
@@ -7,8 +7,8 @@
#include "cio2.h"
+#include <cmath>
#include <limits>
-#include <math.h>
#include <linux/media-bus-format.h>
@@ -304,7 +304,7 @@ V4L2SubdeviceFormat CIO2Device::getSensorFormat(const std::vector<unsigned int>
* comparing it with a single precision digit is enough.
*/
ratio = static_cast<unsigned int>(ratio * 10) / 10.0;
- float ratioDiff = fabsf(ratio - desiredRatio);
+ float ratioDiff = std::abs(ratio - desiredRatio);
unsigned int area = sz.width * sz.height;
unsigned int areaDiff = area - desiredArea;
diff --git a/src/libcamera/pipeline/ipu3/frames.cpp b/src/libcamera/pipeline/ipu3/frames.cpp
index 88eb9d05..bc0526a7 100644
--- a/src/libcamera/pipeline/ipu3/frames.cpp
+++ b/src/libcamera/pipeline/ipu3/frames.cpp
@@ -7,12 +7,13 @@
#include "frames.h"
+#include <libcamera/base/log.h>
+
#include <libcamera/framebuffer.h>
#include <libcamera/request.h>
#include "libcamera/internal/framebuffer.h"
#include "libcamera/internal/pipeline_handler.h"
-#include "libcamera/internal/v4l2_videodevice.h"
namespace libcamera {
diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
index 066fd4a2..29d3c6c1 100644
--- a/src/libcamera/pipeline/ipu3/ipu3.cpp
+++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
@@ -6,7 +6,6 @@
*/
#include <algorithm>
-#include <iomanip>
#include <memory>
#include <queue>
#include <vector>
@@ -19,12 +18,13 @@
#include <libcamera/camera.h>
#include <libcamera/control_ids.h>
#include <libcamera/formats.h>
-#include <libcamera/ipa/ipu3_ipa_interface.h>
-#include <libcamera/ipa/ipu3_ipa_proxy.h>
#include <libcamera/property_ids.h>
#include <libcamera/request.h>
#include <libcamera/stream.h>
+#include <libcamera/ipa/ipu3_ipa_interface.h>
+#include <libcamera/ipa/ipu3_ipa_proxy.h>
+
#include "libcamera/internal/camera.h"
#include "libcamera/internal/camera_lens.h"
#include "libcamera/internal/camera_sensor.h"
@@ -958,7 +958,7 @@ int PipelineHandlerIPU3::updateControls(IPU3CameraData *data)
values.reserve(testPatternModes.size());
for (auto pattern : testPatternModes)
- values.emplace_back(static_cast<int32_t>(pattern));
+ values.emplace_back(pattern);
controls[&controls::draft::TestPatternMode] = ControlInfo(values);
}
@@ -1117,19 +1117,19 @@ int PipelineHandlerIPU3::registerCameras()
* returned through the ImgU main and secondary outputs.
*/
data->cio2_.bufferReady().connect(data.get(),
- &IPU3CameraData::cio2BufferReady);
+ &IPU3CameraData::cio2BufferReady);
data->cio2_.bufferAvailable.connect(
data.get(), &IPU3CameraData::queuePendingRequests);
data->imgu_->input_->bufferReady.connect(&data->cio2_,
- &CIO2Device::tryReturnBuffer);
+ &CIO2Device::tryReturnBuffer);
data->imgu_->output_->bufferReady.connect(data.get(),
- &IPU3CameraData::imguOutputBufferReady);
+ &IPU3CameraData::imguOutputBufferReady);
data->imgu_->viewfinder_->bufferReady.connect(data.get(),
- &IPU3CameraData::imguOutputBufferReady);
+ &IPU3CameraData::imguOutputBufferReady);
data->imgu_->param_->bufferReady.connect(data.get(),
- &IPU3CameraData::paramBufferReady);
+ &IPU3CameraData::paramBufferReady);
data->imgu_->stat_->bufferReady.connect(data.get(),
- &IPU3CameraData::statBufferReady);
+ &IPU3CameraData::statBufferReady);
/* Create and register the Camera instance. */
const std::string &cameraId = cio2->sensor()->id();
@@ -1186,9 +1186,8 @@ int IPU3CameraData::loadIPA()
* The API tuning file is made from the sensor name. If the tuning file
* isn't found, fall back to the 'uncalibrated' file.
*/
- std::string ipaTuningFile = ipa_->configurationFile(sensor->model() + ".yaml");
- if (ipaTuningFile.empty())
- ipaTuningFile = ipa_->configurationFile("uncalibrated.yaml");
+ std::string ipaTuningFile =
+ ipa_->configurationFile(sensor->model() + ".yaml", "uncalibrated.yaml");
ret = ipa_->init(IPASettings{ ipaTuningFile, sensor->model() },
sensorInfo, sensor->controls(), &ipaControls_);
diff --git a/src/libcamera/pipeline/ipu3/meson.build b/src/libcamera/pipeline/ipu3/meson.build
index a1b0b31a..f2904b4a 100644
--- a/src/libcamera/pipeline/ipu3/meson.build
+++ b/src/libcamera/pipeline/ipu3/meson.build
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_sources += files([
+libcamera_internal_sources += files([
'cio2.cpp',
'frames.cpp',
'imgu.cpp',
diff --git a/src/libcamera/pipeline/mali-c55/meson.build b/src/libcamera/pipeline/mali-c55/meson.build
index 30fd29b9..eba8e5a3 100644
--- a/src/libcamera/pipeline/mali-c55/meson.build
+++ b/src/libcamera/pipeline/mali-c55/meson.build
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_sources += files([
+libcamera_internal_sources += files([
'mali-c55.cpp'
])
diff --git a/src/libcamera/pipeline/rkisp1/meson.build b/src/libcamera/pipeline/rkisp1/meson.build
index cad66535..d21a6ef9 100644
--- a/src/libcamera/pipeline/rkisp1/meson.build
+++ b/src/libcamera/pipeline/rkisp1/meson.build
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_sources += files([
+libcamera_internal_sources += files([
'rkisp1.cpp',
'rkisp1_path.cpp',
])
diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
index 4cbf105d..c02c7cf3 100644
--- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp
+++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
@@ -6,10 +6,9 @@
*/
#include <algorithm>
-#include <array>
-#include <iomanip>
#include <memory>
#include <numeric>
+#include <optional>
#include <queue>
#include <linux/media-bus-format.h>
@@ -110,7 +109,7 @@ public:
std::unique_ptr<ipa::rkisp1::IPAProxyRkISP1> ipa_;
private:
- void paramFilled(unsigned int frame);
+ void paramFilled(unsigned int frame, unsigned int bytesused);
void setSensorControls(unsigned int frame,
const ControlList &sensorControls);
@@ -351,13 +350,8 @@ int RkISP1CameraData::loadIPA(unsigned int hwRevision)
std::string ipaTuningFile;
char const *configFromEnv = utils::secure_getenv("LIBCAMERA_RKISP1_TUNING_FILE");
if (!configFromEnv || *configFromEnv == '\0') {
- ipaTuningFile = ipa_->configurationFile(sensor_->model() + ".yaml");
- /*
- * If the tuning file isn't found, fall back to the
- * 'uncalibrated' configuration file.
- */
- if (ipaTuningFile.empty())
- ipaTuningFile = ipa_->configurationFile("uncalibrated.yaml");
+ ipaTuningFile =
+ ipa_->configurationFile(sensor_->model() + ".yaml", "uncalibrated.yaml");
} else {
ipaTuningFile = std::string(configFromEnv);
}
@@ -379,15 +373,14 @@ int RkISP1CameraData::loadIPA(unsigned int hwRevision)
return 0;
}
-void RkISP1CameraData::paramFilled(unsigned int frame)
+void RkISP1CameraData::paramFilled(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 =
- sizeof(struct rkisp1_params_cfg);
+ info->paramBuffer->_d()->metadata().planes()[0].bytesused = bytesused;
pipe->param_->queueBuffer(info->paramBuffer);
pipe->stat_->queueBuffer(info->statBuffer);
@@ -800,7 +793,7 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)
}
V4L2DeviceFormat paramFormat;
- paramFormat.fourcc = V4L2PixelFormat(V4L2_META_FMT_RK_ISP1_PARAMS);
+ paramFormat.fourcc = V4L2PixelFormat(V4L2_META_FMT_RK_ISP1_EXT_PARAMS);
ret = param_->setFormat(&paramFormat);
if (ret)
return ret;
@@ -819,6 +812,7 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)
return ret;
ipaConfig.sensorControls = data->sensor_->controls();
+ ipaConfig.paramFormat = paramFormat.fourcc;
ret = data->ipa_->configure(ipaConfig, streamConfig, &data->controlInfo_);
if (ret) {
@@ -920,71 +914,62 @@ int PipelineHandlerRkISP1::freeBuffers(Camera *camera)
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) {
- freeBuffers(camera);
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) {
- data->ipa_->stop();
- freeBuffers(camera);
LOG(RkISP1, Error)
<< "Failed to start parameters " << camera->id();
return ret;
}
+ actions += [&]() { param_->streamOff(); };
ret = stat_->streamOn();
if (ret) {
- param_->streamOff();
- data->ipa_->stop();
- freeBuffers(camera);
LOG(RkISP1, Error)
<< "Failed to start statistics " << camera->id();
return ret;
}
+ actions += [&]() { stat_->streamOff(); };
}
if (data->mainPath_->isEnabled()) {
ret = mainPath_.start();
- if (ret) {
- param_->streamOff();
- stat_->streamOff();
- data->ipa_->stop();
- freeBuffers(camera);
+ if (ret)
return ret;
- }
+ actions += [&]() { mainPath_.stop(); };
}
if (hasSelfPath_ && data->selfPath_->isEnabled()) {
ret = selfPath_.start();
- if (ret) {
- mainPath_.stop();
- param_->streamOff();
- stat_->streamOff();
- data->ipa_->stop();
- freeBuffers(camera);
+ if (ret)
return ret;
- }
}
isp_->setFrameStartEnabled(true);
activeCamera_ = camera;
- return ret;
+
+ actions.release();
+ return 0;
}
void PipelineHandlerRkISP1::stopDevice(Camera *camera)
diff --git a/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp b/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp
index c49017d1..da8d25c3 100644
--- a/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp
+++ b/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp
@@ -54,11 +54,8 @@ const std::map<PixelFormat, uint32_t> formatToMediaBus = {
} /* namespace */
-RkISP1Path::RkISP1Path(const char *name, const Span<const PixelFormat> &formats,
- const Size &minResolution, const Size &maxResolution)
- : name_(name), running_(false), formats_(formats),
- minResolution_(minResolution), maxResolution_(maxResolution),
- link_(nullptr)
+RkISP1Path::RkISP1Path(const char *name, const Span<const PixelFormat> &formats)
+ : name_(name), running_(false), formats_(formats), link_(nullptr)
{
}
@@ -126,12 +123,50 @@ void RkISP1Path::populateFormats()
}
}
+/**
+ * \brief Filter the sensor resolutions that can be supported
+ * \param[in] sensor The camera sensor
+ *
+ * This function retrieves all the sizes supported by the sensor and
+ * filters all the resolutions that can be supported on the pipeline.
+ * It is possible that the sensor's maximum output resolution is higher
+ * than the ISP maximum input. In that case, this function filters out all
+ * the resolution incapable of being supported and returns the maximum
+ * sensor resolution that can be supported by the pipeline.
+ *
+ * \return Maximum sensor size supported on the pipeline
+ */
+Size RkISP1Path::filterSensorResolution(const CameraSensor *sensor)
+{
+ auto iter = sensorSizesMap_.find(sensor);
+ if (iter != sensorSizesMap_.end())
+ return iter->second.back();
+
+ std::vector<Size> &sizes = sensorSizesMap_[sensor];
+ for (unsigned int code : sensor->mbusCodes()) {
+ for (const Size &size : sensor->sizes(code)) {
+ if (size.width > maxResolution_.width ||
+ size.height > maxResolution_.height)
+ continue;
+
+ sizes.push_back(size);
+ }
+ }
+
+ /* Sort in increasing order and remove duplicates. */
+ std::sort(sizes.begin(), sizes.end());
+ auto last = std::unique(sizes.begin(), sizes.end());
+ sizes.erase(last, sizes.end());
+
+ return sizes.back();
+}
+
StreamConfiguration
RkISP1Path::generateConfiguration(const CameraSensor *sensor, const Size &size,
StreamRole role)
{
const std::vector<unsigned int> &mbusCodes = sensor->mbusCodes();
- const Size &resolution = sensor->resolution();
+ Size resolution = filterSensorResolution(sensor);
/* Min and max resolutions to populate the available stream formats. */
Size maxResolution = maxResolution_.boundedToAspectRatio(resolution)
@@ -220,7 +255,7 @@ CameraConfiguration::Status RkISP1Path::validate(const CameraSensor *sensor,
StreamConfiguration *cfg)
{
const std::vector<unsigned int> &mbusCodes = sensor->mbusCodes();
- const Size &resolution = sensor->resolution();
+ Size resolution = filterSensorResolution(sensor);
const StreamConfiguration reqCfg = *cfg;
CameraConfiguration::Status status = CameraConfiguration::Valid;
@@ -435,12 +470,10 @@ void RkISP1Path::stop()
}
/*
- * \todo Remove the hardcoded resolutions and formats once all users will have
- * migrated to a recent enough kernel.
+ * \todo Remove the hardcoded formats once all users will have migrated to a
+ * recent enough kernel.
*/
namespace {
-constexpr Size RKISP1_RSZ_MP_SRC_MIN{ 32, 16 };
-constexpr Size RKISP1_RSZ_MP_SRC_MAX{ 4416, 3312 };
constexpr std::array<PixelFormat, 18> RKISP1_RSZ_MP_FORMATS{
formats::YUYV,
formats::NV16,
@@ -462,8 +495,6 @@ constexpr std::array<PixelFormat, 18> RKISP1_RSZ_MP_FORMATS{
formats::SRGGB12,
};
-constexpr Size RKISP1_RSZ_SP_SRC_MIN{ 32, 16 };
-constexpr Size RKISP1_RSZ_SP_SRC_MAX{ 1920, 1920 };
constexpr std::array<PixelFormat, 8> RKISP1_RSZ_SP_FORMATS{
formats::YUYV,
formats::NV16,
@@ -477,14 +508,12 @@ constexpr std::array<PixelFormat, 8> RKISP1_RSZ_SP_FORMATS{
} /* namespace */
RkISP1MainPath::RkISP1MainPath()
- : RkISP1Path("main", RKISP1_RSZ_MP_FORMATS,
- RKISP1_RSZ_MP_SRC_MIN, RKISP1_RSZ_MP_SRC_MAX)
+ : RkISP1Path("main", RKISP1_RSZ_MP_FORMATS)
{
}
RkISP1SelfPath::RkISP1SelfPath()
- : RkISP1Path("self", RKISP1_RSZ_SP_FORMATS,
- RKISP1_RSZ_SP_SRC_MIN, RKISP1_RSZ_SP_SRC_MAX)
+ : RkISP1Path("self", RKISP1_RSZ_SP_FORMATS)
{
}
diff --git a/src/libcamera/pipeline/rkisp1/rkisp1_path.h b/src/libcamera/pipeline/rkisp1/rkisp1_path.h
index 08edefec..777fcae7 100644
--- a/src/libcamera/pipeline/rkisp1/rkisp1_path.h
+++ b/src/libcamera/pipeline/rkisp1/rkisp1_path.h
@@ -7,6 +7,7 @@
#pragma once
+#include <map>
#include <memory>
#include <set>
#include <vector>
@@ -32,8 +33,7 @@ struct V4L2SubdeviceFormat;
class RkISP1Path
{
public:
- RkISP1Path(const char *name, const Span<const PixelFormat> &formats,
- const Size &minResolution, const Size &maxResolution);
+ RkISP1Path(const char *name, const Span<const PixelFormat> &formats);
bool init(MediaDevice *media);
@@ -63,6 +63,7 @@ public:
private:
void populateFormats();
+ Size filterSensorResolution(const CameraSensor *sensor);
static constexpr unsigned int RKISP1_BUFFER_COUNT = 4;
@@ -77,6 +78,12 @@ private:
std::unique_ptr<V4L2Subdevice> resizer_;
std::unique_ptr<V4L2VideoDevice> video_;
MediaLink *link_;
+
+ /*
+ * Map from camera sensors to the sizes (in increasing order),
+ * which are guaranteed to be supported by the pipeline.
+ */
+ std::map<const CameraSensor *, std::vector<Size>> sensorSizesMap_;
};
class RkISP1MainPath : public RkISP1Path
diff --git a/src/libcamera/pipeline/rpi/common/meson.build b/src/libcamera/pipeline/rpi/common/meson.build
index 8fb7e823..b2b1a0a6 100644
--- a/src/libcamera/pipeline/rpi/common/meson.build
+++ b/src/libcamera/pipeline/rpi/common/meson.build
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_sources += files([
+libcamera_internal_sources += files([
'delayed_controls.cpp',
'pipeline_base.cpp',
'rpi_stream.cpp',
diff --git a/src/libcamera/pipeline/rpi/vc4/meson.build b/src/libcamera/pipeline/rpi/vc4/meson.build
index 386e2296..9b37c2f0 100644
--- a/src/libcamera/pipeline/rpi/vc4/meson.build
+++ b/src/libcamera/pipeline/rpi/vc4/meson.build
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_sources += files([
+libcamera_internal_sources += files([
'vc4.cpp',
])
diff --git a/src/libcamera/pipeline/simple/meson.build b/src/libcamera/pipeline/simple/meson.build
index 42b0896d..dda3de97 100644
--- a/src/libcamera/pipeline/simple/meson.build
+++ b/src/libcamera/pipeline/simple/meson.build
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_sources += files([
+libcamera_internal_sources += files([
'simple.cpp',
])
diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
index eb36578e..3ddce71d 100644
--- a/src/libcamera/pipeline/simple/simple.cpp
+++ b/src/libcamera/pipeline/simple/simple.cpp
@@ -13,6 +13,7 @@
#include <memory>
#include <queue>
#include <set>
+#include <stdint.h>
#include <string.h>
#include <string>
#include <unordered_map>
@@ -31,6 +32,7 @@
#include "libcamera/internal/camera.h"
#include "libcamera/internal/camera_sensor.h"
#include "libcamera/internal/converter.h"
+#include "libcamera/internal/delayed_controls.h"
#include "libcamera/internal/device_enumerator.h"
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/pipeline_handler.h"
@@ -163,7 +165,7 @@ LOG_DEFINE_CATEGORY(SimplePipeline)
* handler has no a priori knowledge of. The pipeline handler thus implements a
* heuristic to handle sharing of hardware resources in a generic fashion.
*
- * Two cameras are considered to be mutually exclusive if their share common
+ * Two cameras are considered to be mutually exclusive if they share common
* pads along the pipeline from the camera sensor to the video node. An entity
* can thus be used concurrently by multiple cameras, as long as pads are
* distinct.
@@ -198,7 +200,8 @@ namespace {
static const SimplePipelineInfo supportedDevices[] = {
{ "dcmipp", {}, false },
{ "imx7-csi", { { "pxp", 1 } }, false },
- { "j721e-csi2rx", {}, false },
+ { "intel-ipu6", {}, true },
+ { "j721e-csi2rx", {}, true },
{ "mtk-seninf", { { "mtk-mdp", 3 } }, false },
{ "mxc-isi", {}, false },
{ "qcom-camss", {}, true },
@@ -276,8 +279,10 @@ public:
std::vector<Configuration> configs_;
std::map<PixelFormat, std::vector<const Configuration *>> formats_;
+ std::unique_ptr<DelayedControls> delayedCtrls_;
+
std::vector<std::unique_ptr<FrameBuffer>> conversionBuffers_;
- std::queue<std::map<unsigned int, FrameBuffer *>> conversionQueue_;
+ std::queue<std::map<const Stream *, FrameBuffer *>> conversionQueue_;
bool useConversion_;
std::unique_ptr<Converter> converter_;
@@ -290,7 +295,7 @@ private:
void conversionInputDone(FrameBuffer *buffer);
void conversionOutputDone(FrameBuffer *buffer);
- void ispStatsReady();
+ void ispStatsReady(uint32_t frame, uint32_t bufferId);
void setSensorControls(const ControlList &sensorControls);
};
@@ -362,13 +367,12 @@ private:
return static_cast<SimpleCameraData *>(camera->_d());
}
- std::vector<MediaEntity *> locateSensors();
+ std::vector<MediaEntity *> locateSensors(MediaDevice *media);
static int resetRoutingTable(V4L2Subdevice *subdev);
const MediaPad *acquirePipeline(SimpleCameraData *data);
void releasePipeline(SimpleCameraData *data);
- MediaDevice *media_;
std::map<const MediaEntity *, EntityData> entities_;
MediaDevice *converter_;
@@ -773,11 +777,8 @@ int SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format,
}
LOG(SimplePipeline, Debug)
- << "Link '" << source->entity()->name()
- << "':" << source->index()
- << " -> '" << sink->entity()->name()
- << "':" << sink->index()
- << " configured with format " << *format;
+ << "Link " << *link << ": configured with format "
+ << *format;
}
return 0;
@@ -836,7 +837,7 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)
Request *request = buffer->request();
if (useConversion_ && !conversionQueue_.empty()) {
- const std::map<unsigned int, FrameBuffer *> &outputs =
+ const std::map<const Stream *, FrameBuffer *> &outputs =
conversionQueue_.front();
if (!outputs.empty()) {
FrameBuffer *outputBuffer = outputs.begin()->second;
@@ -863,7 +864,13 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)
if (converter_)
converter_->queueBuffers(buffer, conversionQueue_.front());
else
- swIsp_->queueBuffers(buffer, conversionQueue_.front());
+ /*
+ * request->sequence() cannot be retrieved from `buffer' inside
+ * queueBuffers because unique_ptr's make buffer->request() invalid
+ * already here.
+ */
+ swIsp_->queueBuffers(request->sequence(), buffer,
+ conversionQueue_.front());
conversionQueue_.pop();
return;
@@ -890,15 +897,15 @@ void SimpleCameraData::conversionOutputDone(FrameBuffer *buffer)
pipe->completeRequest(request);
}
-void SimpleCameraData::ispStatsReady()
+void SimpleCameraData::ispStatsReady(uint32_t frame, uint32_t bufferId)
{
- /* \todo Use the DelayedControls class */
- swIsp_->processStats(sensor_->getControls({ V4L2_CID_ANALOGUE_GAIN,
- V4L2_CID_EXPOSURE }));
+ swIsp_->processStats(frame, bufferId,
+ delayedCtrls_->get(frame));
}
void SimpleCameraData::setSensorControls(const ControlList &sensorControls)
{
+ delayedCtrls_->push(sensorControls);
ControlList ctrls(sensorControls);
sensor_->setControls(&ctrls);
}
@@ -1138,7 +1145,7 @@ CameraConfiguration::Status SimpleCameraConfiguration::validate()
cfg.frameSize = format.planes[0].size;
}
- cfg.bufferCount = 3;
+ cfg.bufferCount = 4;
}
return status;
@@ -1279,16 +1286,29 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c)
if (outputCfgs.empty())
return 0;
+ std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
+ { V4L2_CID_ANALOGUE_GAIN, { 2, false } },
+ { V4L2_CID_EXPOSURE, { 2, false } },
+ };
+ data->delayedCtrls_ =
+ std::make_unique<DelayedControls>(data->sensor_->device(),
+ params);
+ data->video_->frameStart.connect(data->delayedCtrls_.get(),
+ &DelayedControls::applyControls);
+
StreamConfiguration inputCfg;
inputCfg.pixelFormat = pipeConfig->captureFormat;
inputCfg.size = pipeConfig->captureSize;
inputCfg.stride = captureFormat.planes[0].bpl;
inputCfg.bufferCount = kNumInternalBuffers;
- return data->converter_
- ? data->converter_->configure(inputCfg, outputCfgs)
- : data->swIsp_->configure(inputCfg, outputCfgs,
- data->sensor_->controls());
+ if (data->converter_) {
+ return data->converter_->configure(inputCfg, outputCfgs);
+ } else {
+ ipa::soft::IPAConfigInfo configInfo;
+ configInfo.sensorControls = data->sensor_->controls();
+ return data->swIsp_->configure(inputCfg, outputCfgs, configInfo);
+ }
}
int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream,
@@ -1303,10 +1323,8 @@ int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream,
*/
if (data->useConversion_)
return data->converter_
- ? data->converter_->exportBuffers(data->streamIndex(stream),
- count, buffers)
- : data->swIsp_->exportBuffers(data->streamIndex(stream),
- count, buffers);
+ ? data->converter_->exportBuffers(stream, count, buffers)
+ : data->swIsp_->exportBuffers(stream, count, buffers);
else
return data->video_->exportBuffers(count, buffers);
}
@@ -1398,7 +1416,7 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request)
SimpleCameraData *data = cameraData(camera);
int ret;
- std::map<unsigned int, FrameBuffer *> buffers;
+ std::map<const Stream *, FrameBuffer *> buffers;
for (auto &[stream, buffer] : request->buffers()) {
/*
@@ -1407,7 +1425,7 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request)
* completion handler.
*/
if (data->useConversion_) {
- buffers.emplace(data->streamIndex(stream), buffer);
+ buffers.emplace(stream, buffer);
} else {
ret = data->video_->queueBuffer(buffer);
if (ret < 0)
@@ -1415,8 +1433,11 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request)
}
}
- if (data->useConversion_)
+ if (data->useConversion_) {
data->conversionQueue_.push(std::move(buffers));
+ if (data->swIsp_)
+ data->swIsp_->queueRequest(request->sequence(), request->controls());
+ }
return 0;
}
@@ -1425,7 +1446,8 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request)
* Match and Setup
*/
-std::vector<MediaEntity *> SimplePipelineHandler::locateSensors()
+std::vector<MediaEntity *>
+SimplePipelineHandler::locateSensors(MediaDevice *media)
{
std::vector<MediaEntity *> entities;
@@ -1433,7 +1455,7 @@ std::vector<MediaEntity *> SimplePipelineHandler::locateSensors()
* Gather all the camera sensor entities based on the function they
* expose.
*/
- for (MediaEntity *entity : media_->entities()) {
+ for (MediaEntity *entity : media->entities()) {
if (entity->function() == MEDIA_ENT_F_CAM_SENSOR)
entities.push_back(entity);
}
@@ -1521,17 +1543,18 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)
{
const SimplePipelineInfo *info = nullptr;
unsigned int numStreams = 1;
+ MediaDevice *media;
for (const SimplePipelineInfo &inf : supportedDevices) {
DeviceMatch dm(inf.driver);
- media_ = acquireMediaDevice(enumerator, dm);
- if (media_) {
+ media = acquireMediaDevice(enumerator, dm);
+ if (media) {
info = &inf;
break;
}
}
- if (!media_)
+ if (!media)
return false;
for (const auto &[name, streams] : info->converters) {
@@ -1546,12 +1569,14 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)
swIspEnabled_ = info->swIspEnabled;
/* Locate the sensors. */
- std::vector<MediaEntity *> sensors = locateSensors();
+ std::vector<MediaEntity *> sensors = locateSensors(media);
if (sensors.empty()) {
- LOG(SimplePipeline, Error) << "No sensor found";
+ LOG(SimplePipeline, Info) << "No sensor found for " << media->deviceNode();
return false;
}
+ LOG(SimplePipeline, Debug) << "Sensor found for " << media->deviceNode();
+
/*
* Create one camera data instance for each sensor and gather all
* entities in all pipelines.
@@ -1615,8 +1640,8 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)
if (subdev->caps().hasStreams()) {
/*
* Reset the routing table to its default state
- * to make sure entities are enumerate according
- * to the defaul routing configuration.
+ * to make sure entities are enumerated according
+ * to the default routing configuration.
*/
ret = resetRoutingTable(subdev.get());
if (ret) {
diff --git a/src/libcamera/pipeline/uvcvideo/meson.build b/src/libcamera/pipeline/uvcvideo/meson.build
index a3c2efd4..a3a91074 100644
--- a/src/libcamera/pipeline/uvcvideo/meson.build
+++ b/src/libcamera/pipeline/uvcvideo/meson.build
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_sources += files([
+libcamera_internal_sources += files([
'uvcvideo.cpp',
])
diff --git a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
index 8a7409fc..7fa01bb7 100644
--- a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
+++ b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
@@ -6,13 +6,16 @@
*/
#include <algorithm>
+#include <cmath>
#include <fstream>
-#include <iomanip>
-#include <math.h>
+#include <map>
#include <memory>
-#include <tuple>
+#include <set>
+#include <string>
+#include <vector>
#include <libcamera/base/log.h>
+#include <libcamera/base/mutex.h>
#include <libcamera/base/utils.h>
#include <libcamera/camera.h>
@@ -48,6 +51,7 @@ public:
const std::string &id() const { return id_; }
+ Mutex openLock_;
std::unique_ptr<V4L2VideoDevice> video_;
Stream stream_;
std::map<PixelFormat, std::vector<SizeRange>> formats_;
@@ -93,6 +97,9 @@ private:
const ControlValue &value);
int processControls(UVCCameraData *data, Request *request);
+ bool acquireDevice(Camera *camera) override;
+ void releaseDevice(Camera *camera) override;
+
UVCCameraData *cameraData(Camera *camera)
{
return static_cast<UVCCameraData *>(camera->_d());
@@ -158,9 +165,29 @@ CameraConfiguration::Status UVCCameraConfiguration::validate()
format.fourcc = data_->video_->toV4L2PixelFormat(cfg.pixelFormat);
format.size = cfg.size;
- int ret = data_->video_->tryFormat(&format);
- if (ret)
- return Invalid;
+ /*
+ * For power-consumption reasons video_ is closed when the camera is not
+ * acquired. Open it here if necessary.
+ */
+ {
+ bool opened = false;
+
+ MutexLocker locker(data_->openLock_);
+
+ if (!data_->video_->isOpen()) {
+ int ret = data_->video_->open();
+ if (ret)
+ return Invalid;
+
+ opened = true;
+ }
+
+ int ret = data_->video_->tryFormat(&format);
+ if (opened)
+ data_->video_->close();
+ if (ret)
+ return Invalid;
+ }
cfg.stride = format.planes[0].bpl;
cfg.frameSize = format.planes[0].size;
@@ -293,14 +320,14 @@ int PipelineHandlerUVC::processControl(ControlList *controls, unsigned int id,
case V4L2_CID_BRIGHTNESS: {
float scale = std::max(max - def, def - min);
float fvalue = value.get<float>() * scale + def;
- controls->set(cid, static_cast<int32_t>(lroundf(fvalue)));
+ controls->set(cid, static_cast<int32_t>(std::lround(fvalue)));
break;
}
case V4L2_CID_SATURATION: {
float scale = def - min;
float fvalue = value.get<float>() * scale + min;
- controls->set(cid, static_cast<int32_t>(lroundf(fvalue)));
+ controls->set(cid, static_cast<int32_t>(std::lround(fvalue)));
break;
}
@@ -327,7 +354,7 @@ int PipelineHandlerUVC::processControl(ControlList *controls, unsigned int id,
}
float fvalue = (value.get<float>() - p) / m;
- controls->set(cid, static_cast<int32_t>(lroundf(fvalue)));
+ controls->set(cid, static_cast<int32_t>(std::lround(fvalue)));
break;
}
@@ -411,6 +438,23 @@ bool PipelineHandlerUVC::match(DeviceEnumerator *enumerator)
return true;
}
+bool PipelineHandlerUVC::acquireDevice(Camera *camera)
+{
+ UVCCameraData *data = cameraData(camera);
+
+ MutexLocker locker(data->openLock_);
+
+ return data->video_->open() == 0;
+}
+
+void PipelineHandlerUVC::releaseDevice(Camera *camera)
+{
+ UVCCameraData *data = cameraData(camera);
+
+ MutexLocker locker(data->openLock_);
+ data->video_->close();
+}
+
int UVCCameraData::init(MediaDevice *media)
{
int ret;
@@ -512,6 +556,12 @@ int UVCCameraData::init(MediaDevice *media)
controlInfo_ = ControlInfoMap(std::move(ctrls), controls::controls);
+ /*
+ * Close to allow camera to go into runtime-suspend, video_ will be
+ * re-opened from acquireDevice() and validate().
+ */
+ video_->close();
+
return 0;
}
diff --git a/src/libcamera/pipeline/vimc/meson.build b/src/libcamera/pipeline/vimc/meson.build
index 290eefb5..868e2546 100644
--- a/src/libcamera/pipeline/vimc/meson.build
+++ b/src/libcamera/pipeline/vimc/meson.build
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_sources += files([
+libcamera_internal_sources += files([
'vimc.cpp',
])
diff --git a/src/libcamera/pipeline/vimc/vimc.cpp b/src/libcamera/pipeline/vimc/vimc.cpp
index 0ec9928e..2165bae8 100644
--- a/src/libcamera/pipeline/vimc/vimc.cpp
+++ b/src/libcamera/pipeline/vimc/vimc.cpp
@@ -6,14 +6,15 @@
*/
#include <algorithm>
+#include <cmath>
#include <iomanip>
#include <map>
-#include <math.h>
#include <tuple>
#include <linux/media-bus-format.h>
#include <linux/version.h>
+#include <libcamera/base/flags.h>
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>
@@ -21,6 +22,8 @@
#include <libcamera/control_ids.h>
#include <libcamera/controls.h>
#include <libcamera/formats.h>
+#include <libcamera/framebuffer.h>
+#include <libcamera/geometry.h>
#include <libcamera/request.h>
#include <libcamera/stream.h>
@@ -417,7 +420,7 @@ int PipelineHandlerVimc::processControls(VimcCameraData *data, Request *request)
continue;
}
- int32_t value = lroundf(it.second.get<float>() * 128 + offset);
+ int32_t value = std::lround(it.second.get<float>() * 128 + offset);
controls.set(cid, std::clamp(value, 0, 255));
}
diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp
index 5ea2ca78..e5940469 100644
--- a/src/libcamera/pipeline_handler.cpp
+++ b/src/libcamera/pipeline_handler.cpp
@@ -22,7 +22,6 @@
#include "libcamera/internal/camera.h"
#include "libcamera/internal/camera_manager.h"
#include "libcamera/internal/device_enumerator.h"
-#include "libcamera/internal/framebuffer.h"
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/request.h"
#include "libcamera/internal/tracepoints.h"
@@ -157,26 +156,28 @@ MediaDevice *PipelineHandler::acquireMediaDevice(DeviceEnumerator *enumerator,
* Pipeline handlers shall not call this function directly as the Camera class
* handles access internally.
*
- * \context This function is \threadsafe.
+ * \context This function is called from the CameraManager thread.
*
* \return True if the pipeline handler was acquired, false if another process
* has already acquired it
* \sa release()
*/
-bool PipelineHandler::acquire()
+bool PipelineHandler::acquire(Camera *camera)
{
- MutexLocker locker(lock_);
-
- if (useCount_) {
- ++useCount_;
- return true;
+ if (useCount_ == 0) {
+ for (std::shared_ptr<MediaDevice> &media : mediaDevices_) {
+ if (!media->lock()) {
+ unlockMediaDevices();
+ return false;
+ }
+ }
}
- for (std::shared_ptr<MediaDevice> &media : mediaDevices_) {
- if (!media->lock()) {
+ if (!acquireDevice(camera)) {
+ if (useCount_ == 0)
unlockMediaDevices();
- return false;
- }
+
+ return false;
}
++useCount_;
@@ -195,29 +196,60 @@ bool PipelineHandler::acquire()
* Pipeline handlers shall not call this function directly as the Camera class
* handles access internally.
*
- * \context This function is \threadsafe.
+ * \context This function is called from the CameraManager thread.
*
* \sa acquire()
*/
void PipelineHandler::release(Camera *camera)
{
- MutexLocker locker(lock_);
-
ASSERT(useCount_);
- unlockMediaDevices();
-
releaseDevice(camera);
+ if (useCount_ == 1)
+ unlockMediaDevices();
+
--useCount_;
}
/**
+ * \brief Acquire resources associated with this camera
+ * \param[in] camera The camera for which to acquire resources
+ *
+ * Pipeline handlers may override this in order to get resources such as opening
+ * devices and allocating buffers when a camera is acquired.
+ *
+ * This is used by the uvcvideo pipeline handler to delay opening /dev/video#
+ * until the camera is acquired to avoid excess power consumption. The delayed
+ * opening of /dev/video# is a special case because the kernel uvcvideo driver
+ * powers on the USB device as soon as /dev/video# is opened. This behavior
+ * should *not* be copied by other pipeline handlers.
+ *
+ * \context This function is called from the CameraManager thread.
+ *
+ * \return True on success, false on failure
+ * \sa releaseDevice()
+ */
+bool PipelineHandler::acquireDevice([[maybe_unused]] Camera *camera)
+{
+ return true;
+}
+
+/**
* \brief Release resources associated with this camera
* \param[in] camera The camera for which to release resources
*
* Pipeline handlers may override this in order to perform cleanup operations
* when a camera is released, such as freeing memory.
+ *
+ * This is called once for every camera that is released. If there are resources
+ * shared by multiple cameras then the pipeline handler must take care to not
+ * release them until releaseDevice() has been called for all previously
+ * acquired cameras.
+ *
+ * \context This function is called from the CameraManager thread.
+ *
+ * \sa acquireDevice()
*/
void PipelineHandler::releaseDevice([[maybe_unused]] Camera *camera)
{
@@ -720,6 +752,13 @@ void PipelineHandler::disconnect()
*/
/**
+ * \fn PipelineHandler::cameraManager() const
+ * \brief Retrieve the CameraManager that this pipeline handler belongs to
+ * \context This function is \threadsafe.
+ * \return The CameraManager for this pipeline handler
+ */
+
+/**
* \class PipelineHandlerFactoryBase
* \brief Base class for pipeline handler factories
*
diff --git a/src/libcamera/process.cpp b/src/libcamera/process.cpp
index 86d27b2d..bc9833f4 100644
--- a/src/libcamera/process.cpp
+++ b/src/libcamera/process.cpp
@@ -10,7 +10,6 @@
#include <algorithm>
#include <dirent.h>
#include <fcntl.h>
-#include <iostream>
#include <list>
#include <signal.h>
#include <string.h>
@@ -189,7 +188,6 @@ const struct sigaction &ProcessManager::oldsa() const
return oldsa_;
}
-
/**
* \class Process
* \brief Process object
@@ -271,8 +269,8 @@ int Process::start(const std::string &path,
unsigned int len = args.size();
argv[0] = path.c_str();
for (unsigned int i = 0; i < len; i++)
- argv[i+1] = args[i].c_str();
- argv[len+1] = nullptr;
+ argv[i + 1] = args[i].c_str();
+ argv[len + 1] = nullptr;
execv(path.c_str(), (char **)argv);
diff --git a/src/libcamera/property_ids.cpp.in b/src/libcamera/property_ids.cpp.in
deleted file mode 100644
index 8b274c38..00000000
--- a/src/libcamera/property_ids.cpp.in
+++ /dev/null
@@ -1,48 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2019, Google Inc.
- *
- * property_ids.cpp : Property ID list
- *
- * This file is auto-generated. Do not edit.
- */
-
-#include <libcamera/property_ids.h>
-
-/**
- * \file property_ids.h
- * \brief Camera property identifiers
- */
-
-namespace libcamera {
-
-/**
- * \brief Namespace for libcamera properties
- */
-namespace properties {
-
-${controls_doc}
-
-${vendor_controls_doc}
-
-#ifndef __DOXYGEN__
-/*
- * Keep the properties definitions hidden from doxygen as it incorrectly parses
- * them as functions.
- */
-${controls_def}
-
-${vendor_controls_def}
-
-#endif
-
-/**
- * \brief List of all supported libcamera properties
- */
-extern const ControlIdMap properties {
-${controls_map}
-};
-
-} /* namespace properties */
-
-} /* namespace libcamera */
diff --git a/src/libcamera/proxy/meson.build b/src/libcamera/proxy/meson.build
index 00ae5a8f..8bd1b135 100644
--- a/src/libcamera/proxy/meson.build
+++ b/src/libcamera/proxy/meson.build
@@ -13,7 +13,8 @@ foreach mojom : ipa_mojoms
'--libcamera_generate_proxy_cpp',
'--libcamera_output_path=@OUTPUT@',
'./' + '@INPUT@'
- ])
+ ],
+ env : py_build_env)
- libcamera_sources += proxy
+ libcamera_internal_sources += proxy
endforeach
diff --git a/src/libcamera/proxy/worker/meson.build b/src/libcamera/proxy/worker/meson.build
index aa4d9cd7..8c54a2e2 100644
--- a/src/libcamera/proxy/worker/meson.build
+++ b/src/libcamera/proxy/worker/meson.build
@@ -15,10 +15,10 @@ foreach mojom : ipa_mojoms
'--libcamera_generate_proxy_worker',
'--libcamera_output_path=@OUTPUT@',
'./' + '@INPUT@'
- ])
+ ],
+ env : py_build_env)
- proxy = executable(mojom['name'] + '_ipa_proxy',
- [worker, libcamera_generated_ipa_headers],
+ proxy = executable(mojom['name'] + '_ipa_proxy', worker,
install : true,
install_dir : proxy_install_dir,
dependencies : libcamera_private)
diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp
index cfb451e9..8c56ed30 100644
--- a/src/libcamera/request.cpp
+++ b/src/libcamera/request.cpp
@@ -28,10 +28,17 @@
* \brief Describes a frame capture request to be processed by a camera
*/
+/**
+ * \internal
+ * \file libcamera/internal/request.h
+ * \brief Internal support for request handling
+ */
+
namespace libcamera {
LOG_DEFINE_CATEGORY(Request)
+#ifndef __DOXYGEN_PUBLIC__
/**
* \class Request::Private
* \brief Request private data
@@ -300,6 +307,7 @@ void Request::Private::timeout()
emitPrepareCompleted();
}
+#endif /* __DOXYGEN_PUBLIC__ */
/**
* \enum Request::Status
diff --git a/src/libcamera/sensor/camera_sensor.cpp b/src/libcamera/sensor/camera_sensor.cpp
index c6d7f801..1b224f19 100644
--- a/src/libcamera/sensor/camera_sensor.cpp
+++ b/src/libcamera/sensor/camera_sensor.cpp
@@ -6,25 +6,23 @@
*/
#include "libcamera/internal/camera_sensor.h"
-#include "libcamera/internal/media_device.h"
#include <algorithm>
+#include <cmath>
#include <float.h>
-#include <iomanip>
#include <limits.h>
-#include <math.h>
#include <string.h>
+#include <libcamera/base/utils.h>
+
#include <libcamera/camera.h>
#include <libcamera/orientation.h>
#include <libcamera/property_ids.h>
-#include <libcamera/base/utils.h>
-
#include "libcamera/internal/bayer_format.h"
#include "libcamera/internal/camera_lens.h"
#include "libcamera/internal/camera_sensor_properties.h"
-#include "libcamera/internal/formats.h"
+#include "libcamera/internal/media_device.h"
#include "libcamera/internal/sysfs.h"
/**
@@ -721,7 +719,7 @@ V4L2SubdeviceFormat CameraSensor::getFormat(const std::vector<unsigned int> &mbu
continue;
float ratio = static_cast<float>(sz.width) / sz.height;
- float ratioDiff = fabsf(ratio - desiredRatio);
+ float ratioDiff = std::abs(ratio - desiredRatio);
unsigned int area = sz.width * sz.height;
unsigned int areaDiff = area - desiredArea;
diff --git a/src/libcamera/sensor/camera_sensor_properties.cpp b/src/libcamera/sensor/camera_sensor_properties.cpp
index b18524d8..6d4136d0 100644
--- a/src/libcamera/sensor/camera_sensor_properties.cpp
+++ b/src/libcamera/sensor/camera_sensor_properties.cpp
@@ -52,6 +52,15 @@ LOG_DEFINE_CATEGORY(CameraSensorProperties)
const CameraSensorProperties *CameraSensorProperties::get(const std::string &sensor)
{
static const std::map<std::string, const CameraSensorProperties> sensorProps = {
+ { "ar0144", {
+ .unitCellSize = { 3000, 3000 },
+ .testPatternModes = {
+ { controls::draft::TestPatternModeOff, 0 },
+ { controls::draft::TestPatternModeSolidColor, 1 },
+ { controls::draft::TestPatternModeColorBars, 2 },
+ { controls::draft::TestPatternModeColorBarsFadeToGray, 3 },
+ },
+ } },
{ "ar0521", {
.unitCellSize = { 2200, 2200 },
.testPatternModes = {
@@ -79,6 +88,16 @@ const CameraSensorProperties *CameraSensorProperties::get(const std::string &sen
*/
},
} },
+ { "imx214", {
+ .unitCellSize = { 1120, 1120 },
+ .testPatternModes = {
+ { controls::draft::TestPatternModeOff, 0 },
+ { controls::draft::TestPatternModeColorBars, 1 },
+ { controls::draft::TestPatternModeSolidColor, 2 },
+ { controls::draft::TestPatternModeColorBarsFadeToGray, 3 },
+ { controls::draft::TestPatternModePn9, 4 },
+ },
+ } },
{ "imx219", {
.unitCellSize = { 1120, 1120 },
.testPatternModes = {
diff --git a/src/libcamera/sensor/meson.build b/src/libcamera/sensor/meson.build
index bf4b131a..61234e95 100644
--- a/src/libcamera/sensor/meson.build
+++ b/src/libcamera/sensor/meson.build
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_sources += files([
+libcamera_internal_sources += files([
'camera_sensor.cpp',
'camera_sensor_properties.cpp',
])
diff --git a/src/libcamera/shared_mem_object.cpp b/src/libcamera/shared_mem_object.cpp
index 809fbdaf..d9b61d37 100644
--- a/src/libcamera/shared_mem_object.cpp
+++ b/src/libcamera/shared_mem_object.cpp
@@ -10,12 +10,11 @@
#include "libcamera/internal/shared_mem_object.h"
-#include <stddef.h>
#include <stdint.h>
#include <sys/mman.h>
-#include <sys/syscall.h>
#include <sys/types.h>
-#include <unistd.h>
+
+#include <libcamera/base/memfd.h>
/**
* \file shared_mem_object.cpp
@@ -58,22 +57,12 @@ SharedMem::SharedMem() = default;
*/
SharedMem::SharedMem(const std::string &name, std::size_t size)
{
-#if HAVE_MEMFD_CREATE
- int fd = memfd_create(name.c_str(), MFD_CLOEXEC);
-#else
- int fd = syscall(SYS_memfd_create, name.c_str(), MFD_CLOEXEC);
-#endif
- if (fd < 0)
- return;
-
- fd_ = SharedFD(std::move(fd));
- if (!fd_.isValid())
+ UniqueFD memfd = MemFd::create(name.c_str(), size,
+ MemFd::Seal::Shrink | MemFd::Seal::Grow);
+ if (!memfd.isValid())
return;
- if (ftruncate(fd_.get(), size) < 0) {
- fd_ = SharedFD();
- return;
- }
+ fd_ = SharedFD(std::move(memfd));
void *mem = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED,
fd_.get(), 0);
diff --git a/src/libcamera/software_isp/TODO b/src/libcamera/software_isp/TODO
index 6bdc5905..a50db668 100644
--- a/src/libcamera/software_isp/TODO
+++ b/src/libcamera/software_isp/TODO
@@ -1,22 +1,3 @@
-1. Setting F_SEAL_SHRINK and F_SEAL_GROW after ftruncate()
-
->> SharedMem::SharedMem(const std::string &name, std::size_t size)
->> : name_(name), size_(size), mem_(nullptr)
->>
->> ...
->>
->> if (ftruncate(fd_.get(), size_) < 0)
->> return;
->
-> Should we set the GROW and SHRINK seals (in a separate patch) ?
-
-Yes, this can be done.
-Setting F_SEAL_SHRINK and F_SEAL_GROW after the ftruncate() call above could catch
-some potential errors related to improper access to the shared memory allocated by
-the SharedMemObject.
-
----
-
2. Reconsider stats sharing
>>> +void SwStatsCpu::finishFrame(void)
@@ -218,45 +199,6 @@ Yes, because, well... all the other IPAs were doing that...
---
-10. Switch to libipa/algorithm.h API in processStats
-
->> void IPASoftSimple::processStats(const ControlList &sensorControls)
->>
-> Do you envision switching to the libipa/algorithm.h API at some point ?
-
-At some point, yes.
-
----
-
-11. Improve handling the sensor controls which take effect with a delay
-
-> void IPASoftSimple::processStats(const ControlList &sensorControls)
-> {
-> ...
-> /*
-> * AE / AGC, use 2 frames delay to make sure that the exposure and
-> * the gain set have applied to the camera sensor.
-> */
-> if (ignore_updates_ > 0) {
-> --ignore_updates_;
-> return;
-> }
-
-This could be handled better with DelayedControls.
-
----
-
-12. Use DelayedControls class in ispStatsReady()
-
-> void SimpleCameraData::ispStatsReady()
-> {
-> swIsp_->processStats(sensor_->getControls({ V4L2_CID_ANALOGUE_GAIN,
-> V4L2_CID_EXPOSURE }));
-
-You should use the DelayedControls class.
-
----
-
13. Improve black level and colour gains application
I think the black level should eventually be moved before debayering, and
diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp
index f4a299d5..f0b83261 100644
--- a/src/libcamera/software_isp/debayer.cpp
+++ b/src/libcamera/software_isp/debayer.cpp
@@ -58,47 +58,48 @@ Debayer::~Debayer()
/**
* \fn int Debayer::configure(const StreamConfiguration &inputCfg, const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs)
- * \brief Configure the debayer object according to the passed in parameters.
- * \param[in] inputCfg The input configuration.
- * \param[in] outputCfgs The output configurations.
+ * \brief Configure the debayer object according to the passed in parameters
+ * \param[in] inputCfg The input configuration
+ * \param[in] outputCfgs The output configurations
*
- * \return 0 on success, a negative errno on failure.
+ * \return 0 on success, a negative errno on failure
*/
/**
* \fn Size Debayer::patternSize(PixelFormat inputFormat)
- * \brief Get the width and height at which the bayer pattern repeats.
- * \param[in] inputFormat The input format.
+ * \brief Get the width and height at which the bayer pattern repeats
+ * \param[in] inputFormat The input format
*
* Valid sizes are: 2x2, 4x2 or 4x4.
*
- * \return Pattern size or an empty size for unsupported inputFormats.
+ * \return Pattern size or an empty size for unsupported inputFormats
*/
/**
* \fn std::vector<PixelFormat> Debayer::formats(PixelFormat inputFormat)
- * \brief Get the supported output formats.
- * \param[in] inputFormat The input format.
+ * \brief Get the supported output formats
+ * \param[in] inputFormat The input format
*
- * \return All supported output formats or an empty vector if there are none.
+ * \return All supported output formats or an empty vector if there are none
*/
/**
* \fn std::tuple<unsigned int, unsigned int> Debayer::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size)
- * \brief Get the stride and the frame size.
- * \param[in] outputFormat The output format.
- * \param[in] size The output size.
+ * \brief Get the stride and the frame size
+ * \param[in] outputFormat The output format
+ * \param[in] size The output size
*
* \return A tuple of the stride and the frame size, or a tuple with 0,0 if
- * there is no valid output config.
+ * there is no valid output config
*/
/**
- * \fn void Debayer::process(FrameBuffer *input, FrameBuffer *output, DebayerParams params)
- * \brief Process the bayer data into the requested format.
- * \param[in] input The input buffer.
- * \param[in] output The output buffer.
- * \param[in] params The parameters to be used in debayering.
+ * \fn void Debayer::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, DebayerParams params)
+ * \brief Process the bayer data into the requested format
+ * \param[in] frame The frame number
+ * \param[in] input The input buffer
+ * \param[in] output The output buffer
+ * \param[in] params The parameters to be used in debayering
*
* \note DebayerParams is passed by value deliberately so that a copy is passed
* when this is run in another thread by invokeMethod().
@@ -106,21 +107,21 @@ Debayer::~Debayer()
/**
* \fn virtual SizeRange Debayer::sizes(PixelFormat inputFormat, const Size &inputSize)
- * \brief Get the supported output sizes for the given input format and size.
- * \param[in] inputFormat The input format.
- * \param[in] inputSize The input size.
+ * \brief Get the supported output sizes for the given input format and size
+ * \param[in] inputFormat The input format
+ * \param[in] inputSize The input size
*
- * \return The valid size ranges or an empty range if there are none.
+ * \return The valid size ranges or an empty range if there are none
*/
/**
* \var Signal<FrameBuffer *> Debayer::inputBufferReady
- * \brief Signals when the input buffer is ready.
+ * \brief Signals when the input buffer is ready
*/
/**
* \var Signal<FrameBuffer *> Debayer::outputBufferReady
- * \brief Signals when the output buffer is ready.
+ * \brief Signals when the output buffer is ready
*/
} /* namespace libcamera */
diff --git a/src/libcamera/software_isp/debayer.h b/src/libcamera/software_isp/debayer.h
index c151fe5d..d7ca060d 100644
--- a/src/libcamera/software_isp/debayer.h
+++ b/src/libcamera/software_isp/debayer.h
@@ -40,7 +40,7 @@ public:
virtual std::tuple<unsigned int, unsigned int>
strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) = 0;
- virtual void process(FrameBuffer *input, FrameBuffer *output, DebayerParams params) = 0;
+ virtual void process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, DebayerParams params) = 0;
virtual SizeRange sizes(PixelFormat inputFormat, const Size &inputSize) = 0;
diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp
index f8d2677d..cf5ecdf7 100644
--- a/src/libcamera/software_isp/debayer_cpu.cpp
+++ b/src/libcamera/software_isp/debayer_cpu.cpp
@@ -12,8 +12,11 @@
#include "debayer_cpu.h"
#include <stdlib.h>
+#include <sys/ioctl.h>
#include <time.h>
+#include <linux/dma-buf.h>
+
#include <libcamera/formats.h>
#include "libcamera/internal/bayer_format.h"
@@ -49,16 +52,9 @@ DebayerCpu::DebayerCpu(std::unique_ptr<SwStatsCpu> stats)
/* Initialize color lookup tables */
for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++)
red_[i] = green_[i] = blue_[i] = i;
-
- for (unsigned int i = 0; i < kMaxLineBuffers; i++)
- lineBuffers_[i] = nullptr;
}
-DebayerCpu::~DebayerCpu()
-{
- for (unsigned int i = 0; i < kMaxLineBuffers; i++)
- free(lineBuffers_[i]);
-}
+DebayerCpu::~DebayerCpu() = default;
#define DECLARE_SRC_POINTERS(pixel_t) \
const pixel_t *prev = (const pixel_t *)src[0] + xShift_; \
@@ -526,13 +522,10 @@ int DebayerCpu::configure(const StreamConfiguration &inputCfg,
lineBufferPadding_ = inputConfig_.patternSize.width * inputConfig_.bpp / 8;
lineBufferLength_ = window_.width * inputConfig_.bpp / 8 +
2 * lineBufferPadding_;
- for (unsigned int i = 0;
- i < (inputConfig_.patternSize.height + 1) && enableInputMemcpy_;
- i++) {
- free(lineBuffers_[i]);
- lineBuffers_[i] = (uint8_t *)malloc(lineBufferLength_);
- if (!lineBuffers_[i])
- return -ENOMEM;
+
+ if (enableInputMemcpy_) {
+ for (unsigned int i = 0; i <= inputConfig_.patternSize.height; i++)
+ lineBuffers_[i].resize(lineBufferLength_);
}
measuredFrames_ = 0;
@@ -587,9 +580,10 @@ void DebayerCpu::setupInputMemcpy(const uint8_t *linePointers[])
return;
for (unsigned int i = 0; i < patternHeight; i++) {
- memcpy(lineBuffers_[i], linePointers[i + 1] - lineBufferPadding_,
+ memcpy(lineBuffers_[i].data(),
+ linePointers[i + 1] - lineBufferPadding_,
lineBufferLength_);
- linePointers[i + 1] = lineBuffers_[i] + lineBufferPadding_;
+ linePointers[i + 1] = lineBuffers_[i].data() + lineBufferPadding_;
}
/* Point lineBufferIndex_ to first unused lineBuffer */
@@ -614,9 +608,10 @@ void DebayerCpu::memcpyNextLine(const uint8_t *linePointers[])
if (!enableInputMemcpy_)
return;
- memcpy(lineBuffers_[lineBufferIndex_], linePointers[patternHeight] - lineBufferPadding_,
+ memcpy(lineBuffers_[lineBufferIndex_].data(),
+ linePointers[patternHeight] - lineBufferPadding_,
lineBufferLength_);
- linePointers[patternHeight] = lineBuffers_[lineBufferIndex_] + lineBufferPadding_;
+ linePointers[patternHeight] = lineBuffers_[lineBufferIndex_].data() + lineBufferPadding_;
lineBufferIndex_ = (lineBufferIndex_ + 1) % (patternHeight + 1);
}
@@ -725,13 +720,34 @@ void DebayerCpu::process4(const uint8_t *src, uint8_t *dst)
}
}
-static inline int64_t timeDiff(timespec &after, timespec &before)
+namespace {
+
+void syncBufferForCPU(FrameBuffer *buffer, uint64_t syncFlags)
+{
+ for (const FrameBuffer::Plane &plane : buffer->planes()) {
+ const int fd = plane.fd.get();
+ struct dma_buf_sync sync = { syncFlags };
+ int ret;
+
+ ret = ioctl(fd, DMA_BUF_IOCTL_SYNC, &sync);
+ if (ret < 0) {
+ ret = errno;
+ LOG(Debayer, Error)
+ << "Syncing buffer FD " << fd << " with flags "
+ << syncFlags << " failed: " << strerror(ret);
+ }
+ }
+}
+
+inline int64_t timeDiff(timespec &after, timespec &before)
{
return (after.tv_sec - before.tv_sec) * 1000000000LL +
(int64_t)after.tv_nsec - (int64_t)before.tv_nsec;
}
-void DebayerCpu::process(FrameBuffer *input, FrameBuffer *output, DebayerParams params)
+} /* namespace */
+
+void DebayerCpu::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, DebayerParams params)
{
timespec frameStartTime;
@@ -740,6 +756,9 @@ void DebayerCpu::process(FrameBuffer *input, FrameBuffer *output, DebayerParams
clock_gettime(CLOCK_MONOTONIC_RAW, &frameStartTime);
}
+ syncBufferForCPU(input, DMA_BUF_SYNC_START | DMA_BUF_SYNC_READ);
+ syncBufferForCPU(output, DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE);
+
green_ = params.green;
red_ = swapRedBlueGains_ ? params.blue : params.red;
blue_ = swapRedBlueGains_ ? params.red : params.blue;
@@ -767,6 +786,9 @@ void DebayerCpu::process(FrameBuffer *input, FrameBuffer *output, DebayerParams
metadata.planes()[0].bytesused = out.planes()[0].size();
+ syncBufferForCPU(output, DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE);
+ syncBufferForCPU(input, DMA_BUF_SYNC_END | DMA_BUF_SYNC_READ);
+
/* Measure before emitting signals */
if (measuredFrames_ < DebayerCpu::kLastFrameToMeasure &&
++measuredFrames_ > DebayerCpu::kFramesToSkip) {
@@ -784,7 +806,12 @@ void DebayerCpu::process(FrameBuffer *input, FrameBuffer *output, DebayerParams
}
}
- stats_->finishFrame();
+ /*
+ * Buffer ids are currently not used, so pass zeros as its parameter.
+ *
+ * \todo Pass real bufferId once stats buffer passing is changed.
+ */
+ stats_->finishFrame(frame, 0);
outputBufferReady.emit(output);
inputBufferReady.emit(input);
}
diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h
index 1dac6435..2c47e7c6 100644
--- a/src/libcamera/software_isp/debayer_cpu.h
+++ b/src/libcamera/software_isp/debayer_cpu.h
@@ -36,7 +36,7 @@ public:
std::vector<PixelFormat> formats(PixelFormat input);
std::tuple<unsigned int, unsigned int>
strideAndFrameSize(const PixelFormat &outputFormat, const Size &size);
- void process(FrameBuffer *input, FrameBuffer *output, DebayerParams params);
+ void process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, DebayerParams params);
SizeRange sizes(PixelFormat inputFormat, const Size &inputSize);
/**
@@ -146,7 +146,7 @@ private:
DebayerInputConfig inputConfig_;
DebayerOutputConfig outputConfig_;
std::unique_ptr<SwStatsCpu> stats_;
- uint8_t *lineBuffers_[kMaxLineBuffers];
+ std::vector<uint8_t> lineBuffers_[kMaxLineBuffers];
unsigned int lineBufferLength_;
unsigned int lineBufferPadding_;
unsigned int lineBufferIndex_;
diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build
index f7c66e28..aac7eda7 100644
--- a/src/libcamera/software_isp/meson.build
+++ b/src/libcamera/software_isp/meson.build
@@ -7,7 +7,7 @@ if not softisp_enabled
subdir_done()
endif
-libcamera_sources += files([
+libcamera_internal_sources += files([
'debayer.cpp',
'debayer_cpu.cpp',
'software_isp.cpp',
diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
index 20fb6f48..47677784 100644
--- a/src/libcamera/software_isp/software_isp.cpp
+++ b/src/libcamera/software_isp/software_isp.cpp
@@ -16,10 +16,7 @@
#include <libcamera/formats.h>
#include <libcamera/stream.h>
-#include "libcamera/internal/bayer_format.h"
-#include "libcamera/internal/framebuffer.h"
#include "libcamera/internal/ipa_manager.h"
-#include "libcamera/internal/mapped_framebuffer.h"
#include "libcamera/internal/software_isp/debayer_params.h"
#include "debayer_cpu.h"
@@ -121,9 +118,8 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor)
* The API tuning file is made from the sensor name. If the tuning file
* isn't found, fall back to the 'uncalibrated' file.
*/
- std::string ipaTuningFile = ipa_->configurationFile(sensor->model() + ".yaml");
- if (ipaTuningFile.empty())
- ipaTuningFile = ipa_->configurationFile("uncalibrated.yaml");
+ std::string ipaTuningFile =
+ ipa_->configurationFile(sensor->model() + ".yaml", "uncalibrated.yaml");
int ret = ipa_->init(IPASettings{ ipaTuningFile, sensor->model() },
debayer_->getStatsFD(),
@@ -159,15 +155,18 @@ SoftwareIsp::~SoftwareIsp()
/**
* \brief Process the statistics gathered
+ * \param[in] frame The frame number
+ * \param[in] bufferId ID of the statistics buffer
* \param[in] sensorControls The sensor controls
*
* Requests the IPA to calculate new parameters for ISP and new control
* values for the sensor.
*/
-void SoftwareIsp::processStats(const ControlList &sensorControls)
+void SoftwareIsp::processStats(const uint32_t frame, const uint32_t bufferId,
+ const ControlList &sensorControls)
{
ASSERT(ipa_);
- ipa_->processStats(sensorControls);
+ ipa_->processStats(frame, bufferId, sensorControls);
}
/**
@@ -223,16 +222,17 @@ SoftwareIsp::strideAndFrameSize(const PixelFormat &outputFormat, const Size &siz
* \brief Configure the SoftwareIsp object according to the passed in parameters
* \param[in] inputCfg The input configuration
* \param[in] outputCfgs The output configurations
- * \param[in] sensorControls ControlInfoMap of the controls supported by the sensor
+ * \param[in] configInfo The IPA configuration data, received from the pipeline
+ * handler
* \return 0 on success, a negative errno on failure
*/
int SoftwareIsp::configure(const StreamConfiguration &inputCfg,
const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs,
- const ControlInfoMap &sensorControls)
+ const ipa::soft::IPAConfigInfo &configInfo)
{
ASSERT(ipa_ && debayer_);
- int ret = ipa_->configure(sensorControls);
+ int ret = ipa_->configure(configInfo);
if (ret < 0)
return ret;
@@ -241,19 +241,19 @@ int SoftwareIsp::configure(const StreamConfiguration &inputCfg,
/**
* \brief Export the buffers from the Software ISP
- * \param[in] output Output stream index exporting the buffers
+ * \param[in] stream Output stream exporting the buffers
* \param[in] count Number of buffers to allocate
* \param[out] buffers Vector to store the allocated buffers
* \return The number of allocated buffers on success or a negative error code
* otherwise
*/
-int SoftwareIsp::exportBuffers(unsigned int output, unsigned int count,
+int SoftwareIsp::exportBuffers(const Stream *stream, unsigned int count,
std::vector<std::unique_ptr<FrameBuffer>> *buffers)
{
ASSERT(debayer_ != nullptr);
/* single output for now */
- if (output >= 1)
+ if (stream == nullptr)
return -EINVAL;
for (unsigned int i = 0; i < count; i++) {
@@ -278,37 +278,42 @@ int SoftwareIsp::exportBuffers(unsigned int output, unsigned int count,
}
/**
+ * \brief Queue a request and process the control list from the application
+ * \param[in] frame The number of the frame which will be processed next
+ * \param[in] controls The controls for the \a frame
+ */
+void SoftwareIsp::queueRequest(const uint32_t frame, const ControlList &controls)
+{
+ ipa_->queueRequest(frame, controls);
+}
+
+/**
* \brief Queue buffers to Software ISP
+ * \param[in] frame The frame number
* \param[in] input The input framebuffer
- * \param[in] outputs The container holding the output stream indexes and
+ * \param[in] outputs The container holding the output stream pointers and
* their respective frame buffer outputs
* \return 0 on success, a negative errno on failure
*/
-int SoftwareIsp::queueBuffers(FrameBuffer *input,
- const std::map<unsigned int, FrameBuffer *> &outputs)
+int SoftwareIsp::queueBuffers(uint32_t frame, FrameBuffer *input,
+ const std::map<const Stream *, FrameBuffer *> &outputs)
{
- unsigned int mask = 0;
-
/*
* Validate the outputs as a sanity check: at least one output is
- * required, all outputs must reference a valid stream and no two
- * outputs can reference the same stream.
+ * required, all outputs must reference a valid stream.
*/
if (outputs.empty())
return -EINVAL;
- for (auto [index, buffer] : outputs) {
+ for (auto [stream, buffer] : outputs) {
if (!buffer)
return -EINVAL;
- if (index >= 1) /* only single stream atm */
+ if (outputs.size() != 1) /* only single stream atm */
return -EINVAL;
- if (mask & (1 << index))
- return -EINVAL;
-
- mask |= 1 << index;
}
- process(input, outputs.at(0));
+ for (auto iter = outputs.begin(); iter != outputs.end(); iter++)
+ process(frame, input, iter->second);
return 0;
}
@@ -340,13 +345,15 @@ void SoftwareIsp::stop()
/**
* \brief Passes the input framebuffer to the ISP worker to process
+ * \param[in] frame The frame number
* \param[in] input The input framebuffer
* \param[out] output The framebuffer to write the processed frame to
*/
-void SoftwareIsp::process(FrameBuffer *input, FrameBuffer *output)
+void SoftwareIsp::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output)
{
+ ipa_->fillParamsBuffer(frame);
debayer_->invokeMethod(&DebayerCpu::process,
- ConnectionTypeQueued, input, output, debayerParams_);
+ ConnectionTypeQueued, frame, input, output, debayerParams_);
}
void SoftwareIsp::saveIspParams()
@@ -359,9 +366,9 @@ void SoftwareIsp::setSensorCtrls(const ControlList &sensorControls)
setSensorControls.emit(sensorControls);
}
-void SoftwareIsp::statsReady()
+void SoftwareIsp::statsReady(uint32_t frame, uint32_t bufferId)
{
- ispStatsReady.emit();
+ ispStatsReady.emit(frame, bufferId);
}
void SoftwareIsp::inputReady(FrameBuffer *input)
diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp
index 815c4d4f..c520c806 100644
--- a/src/libcamera/software_isp/swstats_cpu.cpp
+++ b/src/libcamera/software_isp/swstats_cpu.cpp
@@ -311,13 +311,15 @@ void SwStatsCpu::startFrame(void)
/**
* \brief Finish statistics calculation for the current frame
+ * \param[in] frame The frame number
+ * \param[in] bufferId ID of the statistics buffer
*
* This may only be called after a successful setWindow() call.
*/
-void SwStatsCpu::finishFrame(void)
+void SwStatsCpu::finishFrame(uint32_t frame, uint32_t bufferId)
{
*sharedStats_ = stats_;
- statsReady.emit();
+ statsReady.emit(frame, bufferId);
}
/**
diff --git a/src/libcamera/software_isp/swstats_cpu.h b/src/libcamera/software_isp/swstats_cpu.h
index 363e326f..26a2f462 100644
--- a/src/libcamera/software_isp/swstats_cpu.h
+++ b/src/libcamera/software_isp/swstats_cpu.h
@@ -41,7 +41,7 @@ public:
int configure(const StreamConfiguration &inputCfg);
void setWindow(const Rectangle &window);
void startFrame();
- void finishFrame();
+ void finishFrame(uint32_t frame, uint32_t bufferId);
void processLine0(unsigned int y, const uint8_t *src[])
{
@@ -61,7 +61,7 @@ public:
(this->*stats2_)(src);
}
- Signal<> statsReady;
+ Signal<uint32_t, uint32_t> statsReady;
private:
using statsProcessFn = void (SwStatsCpu::*)(const uint8_t *src[]);
diff --git a/src/libcamera/stream.cpp b/src/libcamera/stream.cpp
index 053cc4b8..1f75dbbc 100644
--- a/src/libcamera/stream.cpp
+++ b/src/libcamera/stream.cpp
@@ -9,15 +9,15 @@
#include <algorithm>
#include <array>
-#include <iomanip>
#include <limits.h>
-#include <sstream>
-
-#include <libcamera/request.h>
+#include <ostream>
+#include <string>
+#include <vector>
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>
+#include <libcamera/request.h>
/**
* \file stream.h
diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
index 4a2048cf..68add4f2 100644
--- a/src/libcamera/v4l2_device.cpp
+++ b/src/libcamera/v4l2_device.cpp
@@ -8,8 +8,6 @@
#include "libcamera/internal/v4l2_device.h"
#include <fcntl.h>
-#include <iomanip>
-#include <limits.h>
#include <map>
#include <stdlib.h>
#include <string.h>
diff --git a/src/libcamera/v4l2_pixelformat.cpp b/src/libcamera/v4l2_pixelformat.cpp
index 70568335..eb9ac222 100644
--- a/src/libcamera/v4l2_pixelformat.cpp
+++ b/src/libcamera/v4l2_pixelformat.cpp
@@ -139,6 +139,8 @@ const std::map<V4L2PixelFormat, V4L2PixelFormat::Info> vpf2pf{
{ formats::R10_CSI2P, "10-bit Greyscale Packed" } },
{ V4L2PixelFormat(V4L2_PIX_FMT_Y12),
{ formats::R12, "12-bit Greyscale" } },
+ { V4L2PixelFormat(V4L2_PIX_FMT_Y12P),
+ { formats::R12_CSI2P, "12-bit Greyscale Packed" } },
{ V4L2PixelFormat(V4L2_PIX_FMT_Y16),
{ formats::R16, "16-bit Greyscale" } },
diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp
index 82824433..9f2ec479 100644
--- a/src/libcamera/v4l2_subdevice.cpp
+++ b/src/libcamera/v4l2_subdevice.cpp
@@ -8,7 +8,6 @@
#include "libcamera/internal/v4l2_subdevice.h"
#include <fcntl.h>
-#include <iomanip>
#include <regex>
#include <sstream>
#include <string.h>
@@ -18,11 +17,11 @@
#include <linux/media-bus-format.h>
#include <linux/v4l2-subdev.h>
-#include <libcamera/geometry.h>
-
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>
+#include <libcamera/geometry.h>
+
#include "libcamera/internal/formats.h"
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/media_object.h"
diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp
index 4947aa3d..14eba056 100644
--- a/src/libcamera/v4l2_videodevice.cpp
+++ b/src/libcamera/v4l2_videodevice.cpp
@@ -10,7 +10,6 @@
#include <algorithm>
#include <array>
#include <fcntl.h>
-#include <iomanip>
#include <sstream>
#include <string.h>
#include <sys/ioctl.h>
@@ -803,12 +802,19 @@ std::string V4L2VideoDevice::logPrefix() const
*/
int V4L2VideoDevice::getFormat(V4L2DeviceFormat *format)
{
- if (caps_.isMeta())
- return getFormatMeta(format);
- else if (caps_.isMultiplanar())
- return getFormatMultiplane(format);
- else
+ switch (bufferType_) {
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT:
return getFormatSingleplane(format);
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+ return getFormatMultiplane(format);
+ case V4L2_BUF_TYPE_META_CAPTURE:
+ case V4L2_BUF_TYPE_META_OUTPUT:
+ return getFormatMeta(format);
+ default:
+ return -EINVAL;
+ }
}
/**
@@ -823,12 +829,19 @@ int V4L2VideoDevice::getFormat(V4L2DeviceFormat *format)
*/
int V4L2VideoDevice::tryFormat(V4L2DeviceFormat *format)
{
- if (caps_.isMeta())
- return trySetFormatMeta(format, false);
- else if (caps_.isMultiplanar())
- return trySetFormatMultiplane(format, false);
- else
+ switch (bufferType_) {
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT:
return trySetFormatSingleplane(format, false);
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+ return trySetFormatMultiplane(format, false);
+ case V4L2_BUF_TYPE_META_CAPTURE:
+ case V4L2_BUF_TYPE_META_OUTPUT:
+ return trySetFormatMeta(format, false);
+ default:
+ return -EINVAL;
+ }
}
/**
@@ -842,13 +855,25 @@ int V4L2VideoDevice::tryFormat(V4L2DeviceFormat *format)
*/
int V4L2VideoDevice::setFormat(V4L2DeviceFormat *format)
{
- int ret = 0;
- if (caps_.isMeta())
- ret = trySetFormatMeta(format, true);
- else if (caps_.isMultiplanar())
- ret = trySetFormatMultiplane(format, true);
- else
+ int ret;
+
+ switch (bufferType_) {
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT:
ret = trySetFormatSingleplane(format, true);
+ break;
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+ ret = trySetFormatMultiplane(format, true);
+ break;
+ case V4L2_BUF_TYPE_META_CAPTURE:
+ case V4L2_BUF_TYPE_META_OUTPUT:
+ ret = trySetFormatMeta(format, true);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
/* Cache the set format on success. */
if (ret)
@@ -1190,6 +1215,38 @@ std::vector<SizeRange> V4L2VideoDevice::enumSizes(V4L2PixelFormat pixelFormat)
}
/**
+ * \brief Get the selection rectangle for \a target
+ * \param[in] target The selection target defined by the V4L2_SEL_TGT_* flags
+ * \param[out] rect The selection rectangle to retrieve
+ *
+ * \todo Define a V4L2SelectionTarget enum for the selection target
+ *
+ * \return 0 on success or a negative error code otherwise
+ */
+int V4L2VideoDevice::getSelection(unsigned int target, Rectangle *rect)
+{
+ struct v4l2_selection sel = {};
+
+ sel.type = bufferType_;
+ sel.target = target;
+ sel.flags = 0;
+
+ int ret = ioctl(VIDIOC_G_SELECTION, &sel);
+ if (ret < 0) {
+ LOG(V4L2, Error) << "Unable to get rectangle " << target
+ << ": " << strerror(-ret);
+ return ret;
+ }
+
+ rect->x = sel.r.left;
+ rect->y = sel.r.top;
+ rect->width = sel.r.width;
+ rect->height = sel.r.height;
+
+ return 0;
+}
+
+/**
* \brief Set a selection rectangle \a rect for \a target
* \param[in] target The selection target defined by the V4L2_SEL_TGT_* flags
* \param[inout] rect The selection rectangle to be applied
@@ -1815,7 +1872,7 @@ FrameBuffer *V4L2VideoDevice::dequeueBuffer()
* Detect kernel drivers which do not reset the sequence number to zero
* on stream start.
*/
- if (!firstFrame_) {
+ if (!firstFrame_.has_value()) {
if (buf.sequence)
LOG(V4L2, Info)
<< "Zero sequence expected for first frame (got "
diff --git a/src/libcamera/yaml_parser.cpp b/src/libcamera/yaml_parser.cpp
index 025006bc..4784f2dc 100644
--- a/src/libcamera/yaml_parser.cpp
+++ b/src/libcamera/yaml_parser.cpp
@@ -18,7 +18,7 @@
#include <yaml.h>
/**
- * \file libcamera/internal/yaml_parser.h
+ * \file yaml_parser.h
* \brief A YAML parser helper
*/
@@ -38,12 +38,12 @@ static const YamlObject empty;
* \brief A class representing the tree structure of the YAML content
*
* The YamlObject class represents the tree structure of YAML content. A
- * YamlObject can be a dictionary or list of YamlObjects or a value if a tree
- * leaf.
+ * YamlObject can be empty, a dictionary or list of YamlObjects, or a value if a
+ * tree leaf.
*/
YamlObject::YamlObject()
- : type_(Type::Value)
+ : type_(Type::Empty)
{
}
@@ -71,6 +71,20 @@ YamlObject::~YamlObject() = default;
*/
/**
+ * \fn YamlObject::isEmpty()
+ * \brief Return whether the YamlObject is an empty
+ *
+ * \return True if the YamlObject is empty, false otherwise
+ */
+
+/**
+ * \fn YamlObject::operator bool()
+ * \brief Return whether the YamlObject is a non-empty
+ *
+ * \return False if the YamlObject is empty, true otherwise
+ */
+
+/**
* \fn YamlObject::size()
* \brief Retrieve the number of elements in a dictionary or list YamlObject
*
@@ -443,7 +457,8 @@ template std::optional<std::vector<Size>> YamlObject::getList<Size>() const;
*
* This function retrieves an element of the YamlObject. Only YamlObject
* instances of List type associate elements with index, calling this function
- * on other types of instances is invalid and results in undefined behaviour.
+ * on other types of instances or with an invalid index results in an empty
+ * object.
*
* \return The YamlObject as an element of the list
*/
@@ -480,8 +495,8 @@ bool YamlObject::contains(const std::string &key) const
*
* This function retrieve a member of a YamlObject by name. Only YamlObject
* instances of Dictionary type associate elements with names, calling this
- * function on other types of instances is invalid and results in undefined
- * behaviour.
+ * function on other types of instances or with a nonexistent key results in an
+ * empty object.
*
* \return The YamlObject corresponding to the \a key member
*/
diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py
index c1723b44..22d8c4da 100644
--- a/src/py/cam/cam_qt.py
+++ b/src/py/cam/cam_qt.py
@@ -2,7 +2,7 @@
# Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
from helpers import mfb_to_rgb
-from PyQt5 import QtCore, QtGui, QtWidgets
+from PyQt6 import QtCore, QtGui, QtWidgets
import libcamera as libcam
import libcamera.utils
import sys
@@ -63,10 +63,10 @@ class QtRenderer:
self.buf_mmap_map = buf_mmap_map
def run(self):
- camnotif = QtCore.QSocketNotifier(self.cm.event_fd, QtCore.QSocketNotifier.Read)
+ camnotif = QtCore.QSocketNotifier(self.cm.event_fd, QtCore.QSocketNotifier.Type.Read)
camnotif.activated.connect(lambda _: self.readcam())
- keynotif = QtCore.QSocketNotifier(sys.stdin.fileno(), QtCore.QSocketNotifier.Read)
+ keynotif = QtCore.QSocketNotifier(sys.stdin.fileno(), QtCore.QSocketNotifier.Type.Read)
keynotif.activated.connect(lambda _: self.readkey())
print('Capturing...')
diff --git a/src/py/cam/cam_qtgl.py b/src/py/cam/cam_qtgl.py
index 6cfbd347..35b4b06b 100644
--- a/src/py/cam/cam_qtgl.py
+++ b/src/py/cam/cam_qtgl.py
@@ -1,8 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
-from PyQt5 import QtCore, QtWidgets
-from PyQt5.QtCore import Qt
+from PyQt6 import QtCore, QtWidgets
+from PyQt6.QtCore import Qt
import math
import os
@@ -142,10 +142,10 @@ class QtRenderer:
self.window = window
def run(self):
- camnotif = QtCore.QSocketNotifier(self.state.cm.event_fd, QtCore.QSocketNotifier.Read)
+ camnotif = QtCore.QSocketNotifier(self.state.cm.event_fd, QtCore.QSocketNotifier.Type.Read)
camnotif.activated.connect(lambda _: self.readcam())
- keynotif = QtCore.QSocketNotifier(sys.stdin.fileno(), QtCore.QSocketNotifier.Read)
+ keynotif = QtCore.QSocketNotifier(sys.stdin.fileno(), QtCore.QSocketNotifier.Type.Read)
keynotif.activated.connect(lambda _: self.readkey())
print('Capturing...')
@@ -175,8 +175,8 @@ class MainWindow(QtWidgets.QWidget):
def __init__(self, state):
super().__init__()
- self.setAttribute(Qt.WA_PaintOnScreen)
- self.setAttribute(Qt.WA_NativeWindow)
+ self.setAttribute(Qt.WidgetAttribute.WA_PaintOnScreen)
+ self.setAttribute(Qt.WidgetAttribute.WA_NativeWindow)
self.state = state
diff --git a/src/py/libcamera/gen-py-controls.py b/src/py/libcamera/gen-py-controls.py
index 8efbf95b..cf09c146 100755
--- a/src/py/libcamera/gen-py-controls.py
+++ b/src/py/libcamera/gen-py-controls.py
@@ -4,10 +4,12 @@
# Generate Python bindings controls from YAML
import argparse
-import string
+import jinja2
import sys
import yaml
+from controls import Control
+
def find_common_prefix(strings):
prefix = strings[0]
@@ -21,70 +23,39 @@ def find_common_prefix(strings):
return prefix
-def generate_py(controls, mode):
- out = ''
-
- vendors_class_def = []
- vendor_defs = []
- vendors = []
- for vendor, ctrl_list in controls.items():
- for ctrls in ctrl_list:
- name, ctrl = ctrls.popitem()
-
- if vendor not in vendors and vendor != 'libcamera':
- vendor_mode_str = f'{vendor.capitalize()}{mode.capitalize()}'
- vendors_class_def.append('class Py{}\n{{\n}};\n'.format(vendor_mode_str))
- vendor_defs.append('\tauto {} = py::class_<Py{}>(controls, \"{}\");'.format(vendor, vendor_mode_str, vendor))
- vendors.append(vendor)
-
- if vendor != 'libcamera':
- ns = 'libcamera::{}::{}::'.format(mode, vendor)
- container = vendor
- else:
- ns = 'libcamera::{}::'.format(mode)
- container = 'controls'
-
- out += f'\t{container}.def_readonly_static("{name}", static_cast<const libcamera::ControlId *>(&{ns}{name}));\n\n'
-
- enum = ctrl.get('enum')
- if not enum:
- continue
-
- cpp_enum = name + 'Enum'
-
- out += '\tpy::enum_<{}{}>({}, \"{}\")\n'.format(ns, cpp_enum, container, cpp_enum)
-
- if mode == 'controls':
- # Adjustments for controls
- if name == 'LensShadingMapMode':
- prefix = 'LensShadingMapMode'
- else:
- prefix = find_common_prefix([e['name'] for e in enum])
- else:
- # Adjustments for properties
- prefix = find_common_prefix([e['name'] for e in enum])
-
- for entry in enum:
- cpp_enum = entry['name']
- py_enum = entry['name'][len(prefix):]
-
- out += '\t\t.value(\"{}\", {}{})\n'.format(py_enum, ns, cpp_enum)
-
- out += '\t;\n\n'
-
- return {'controls': out,
- 'vendors_class_def': '\n'.join(vendors_class_def),
- 'vendors_defs': '\n'.join(vendor_defs)}
+def extend_control(ctrl, mode):
+ if ctrl.vendor != 'libcamera':
+ ctrl.klass = ctrl.vendor
+ ctrl.namespace = f'{ctrl.vendor}::'
+ else:
+ ctrl.klass = mode
+ ctrl.namespace = ''
+
+ if not ctrl.is_enum:
+ return ctrl
+
+ if mode == 'controls':
+ # Adjustments for controls
+ if ctrl.name == 'LensShadingMapMode':
+ prefix = 'LensShadingMapMode'
+ else:
+ prefix = find_common_prefix([e.name for e in ctrl.enum_values])
+ else:
+ # Adjustments for properties
+ prefix = find_common_prefix([e.name for e in ctrl.enum_values])
+ for enum in ctrl.enum_values:
+ enum.py_name = enum.name[len(prefix):]
-def fill_template(template, data):
- template = open(template, 'rb').read()
- template = template.decode('utf-8')
- template = string.Template(template)
- return template.substitute(data)
+ return ctrl
def main(argv):
+ headers = {
+ 'controls': 'control_ids.h',
+ 'properties': 'property_ids.h',
+ }
+
# Parse command line arguments
parser = argparse.ArgumentParser()
parser.add_argument('--mode', '-m', type=str, required=True,
@@ -97,26 +68,41 @@ def main(argv):
help='Input file name.')
args = parser.parse_args(argv[1:])
- if args.mode not in ['controls', 'properties']:
+ if not headers.get(args.mode):
print(f'Invalid mode option "{args.mode}"', file=sys.stderr)
return -1
- controls = {}
+ controls = []
+ vendors = []
+
for input in args.input:
- data = open(input, 'rb').read()
- vendor = yaml.safe_load(data)['vendor']
- controls[vendor] = yaml.safe_load(data)['controls']
+ data = yaml.safe_load(open(input, 'rb').read())
+
+ vendor = data['vendor']
+ if vendor != 'libcamera':
+ vendors.append(vendor)
+
+ for ctrl in data['controls']:
+ ctrl = Control(*ctrl.popitem(), vendor)
+ controls.append(extend_control(ctrl, args.mode))
- data = generate_py(controls, args.mode)
+ data = {
+ 'mode': args.mode,
+ 'header': headers[args.mode],
+ 'vendors': vendors,
+ 'controls': controls,
+ }
- data = fill_template(args.template, data)
+ env = jinja2.Environment()
+ template = env.from_string(open(args.template, 'r', encoding='utf-8').read())
+ string = template.render(data)
if args.output:
- output = open(args.output, 'wb')
- output.write(data.encode('utf-8'))
+ output = open(args.output, 'w', encoding='utf-8')
+ output.write(string)
output.close()
else:
- sys.stdout.write(data)
+ sys.stdout.write(string)
return 0
diff --git a/src/py/libcamera/meson.build b/src/py/libcamera/meson.build
index 4807ca7d..596a203c 100644
--- a/src/py/libcamera/meson.build
+++ b/src/py/libcamera/meson.build
@@ -26,37 +26,24 @@ pycamera_sources = files([
'py_transform.cpp',
])
-# Generate controls
+# Generate controls and properties
-gen_py_controls_input_files = []
gen_py_controls_template = files('py_controls_generated.cpp.in')
-
gen_py_controls = files('gen-py-controls.py')
-foreach file : controls_files
- gen_py_controls_input_files += files('../../libcamera/' + file)
-endforeach
-
pycamera_sources += custom_target('py_gen_controls',
- input : gen_py_controls_input_files,
+ input : controls_files,
output : ['py_controls_generated.cpp'],
command : [gen_py_controls, '--mode', 'controls', '-o', '@OUTPUT@',
- '-t', gen_py_controls_template, '@INPUT@'])
-
-# Generate properties
-
-gen_py_property_enums_input_files = []
-gen_py_properties_template = files('py_properties_generated.cpp.in')
-
-foreach file : properties_files
- gen_py_property_enums_input_files += files('../../libcamera/' + file)
-endforeach
+ '-t', gen_py_controls_template, '@INPUT@'],
+ env : py_build_env)
pycamera_sources += custom_target('py_gen_properties',
- input : gen_py_property_enums_input_files,
+ input : properties_files,
output : ['py_properties_generated.cpp'],
command : [gen_py_controls, '--mode', 'properties', '-o', '@OUTPUT@',
- '-t', gen_py_properties_template, '@INPUT@'])
+ '-t', gen_py_controls_template, '@INPUT@'],
+ env : py_build_env)
# Generate formats
diff --git a/src/py/libcamera/py_controls_generated.cpp.in b/src/py/libcamera/py_controls_generated.cpp.in
index 26d5a104..22a132d1 100644
--- a/src/py/libcamera/py_controls_generated.cpp.in
+++ b/src/py/libcamera/py_controls_generated.cpp.in
@@ -2,12 +2,12 @@
/*
* Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
*
- * Python bindings - Auto-generated controls
+ * Python bindings - Auto-generated {{mode}}
*
* This file is auto-generated. Do not edit.
*/
-#include <libcamera/control_ids.h>
+#include <libcamera/{{header}}>
#include <pybind11/pybind11.h>
@@ -15,16 +15,33 @@
namespace py = pybind11;
-class PyControls
+class Py{{mode|capitalize}}
{
};
-${vendors_class_def}
-
-void init_py_controls_generated(py::module& m)
+{% for vendor in vendors -%}
+class Py{{vendor|capitalize}}{{mode|capitalize}}
{
- auto controls = py::class_<PyControls>(m, "controls");
-${vendors_defs}
+};
-${controls}
+{% endfor -%}
+
+void init_py_{{mode}}_generated(py::module& m)
+{
+ auto {{mode}} = py::class_<Py{{mode|capitalize}}>(m, "{{mode}}");
+{%- for vendor in vendors %}
+ auto {{vendor}} = py::class_<Py{{vendor|capitalize}}{{mode|capitalize}}>({{mode}}, "{{vendor}}");
+{%- endfor %}
+
+{% for ctrl in controls %}
+ {{ctrl.klass}}.def_readonly_static("{{ctrl.name}}", static_cast<const libcamera::ControlId *>(&libcamera::{{mode}}::{{ctrl.namespace}}{{ctrl.name}}));
+{%- if ctrl.is_enum %}
+
+ py::enum_<libcamera::{{mode}}::{{ctrl.namespace}}{{ctrl.name}}Enum>({{ctrl.klass}}, "{{ctrl.name}}Enum")
+{%- for enum in ctrl.enum_values %}
+ .value("{{enum.py_name}}", libcamera::{{mode}}::{{ctrl.namespace}}{{enum.name}})
+{%- endfor %}
+ ;
+{%- endif %}
+{% endfor -%}
}
diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp
index ab33f38a..983b76f6 100644
--- a/src/py/libcamera/py_main.cpp
+++ b/src/py/libcamera/py_main.cpp
@@ -404,7 +404,8 @@ PYBIND11_MODULE(_libcamera, m)
.def("__repr__", [](const ControlId &self) {
return py::str("libcamera.ControlId({}, {}, {})")
.format(self.id(), self.name(), self.type());
- });
+ })
+ .def("enumerators", &ControlId::enumerators);
pyControlInfo
.def_property_readonly("min", [](const ControlInfo &self) {
diff --git a/src/py/libcamera/py_properties_generated.cpp.in b/src/py/libcamera/py_properties_generated.cpp.in
deleted file mode 100644
index d28f1ab8..00000000
--- a/src/py/libcamera/py_properties_generated.cpp.in
+++ /dev/null
@@ -1,30 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
- *
- * Python bindings - Auto-generated properties
- *
- * This file is auto-generated. Do not edit.
- */
-
-#include <libcamera/property_ids.h>
-
-#include <pybind11/pybind11.h>
-
-#include "py_main.h"
-
-namespace py = pybind11;
-
-class PyProperties
-{
-};
-
-${vendors_class_def}
-
-void init_py_properties_generated(py::module& m)
-{
- auto controls = py::class_<PyProperties>(m, "properties");
-${vendors_defs}
-
-${controls}
-}
diff --git a/src/v4l2/v4l2_camera.cpp b/src/v4l2/v4l2_camera.cpp
index 0f3b862f..94d138cd 100644
--- a/src/v4l2/v4l2_camera.cpp
+++ b/src/v4l2/v4l2_camera.cpp
@@ -12,13 +12,15 @@
#include <libcamera/base/log.h>
+#include <libcamera/control_ids.h>
+
using namespace libcamera;
LOG_DECLARE_CATEGORY(V4L2Compat)
V4L2Camera::V4L2Camera(std::shared_ptr<Camera> camera)
- : camera_(camera), isRunning_(false), bufferAllocator_(nullptr),
- efd_(-1), bufferAvailableCount_(0)
+ : camera_(camera), controls_(controls::controls), isRunning_(false),
+ bufferAllocator_(nullptr), efd_(-1), bufferAvailableCount_(0)
{
camera_->requestCompleted.connect(this, &V4L2Camera::requestComplete);
}
@@ -202,10 +204,12 @@ int V4L2Camera::streamOn()
if (isRunning_)
return 0;
- int ret = camera_->start();
+ int ret = camera_->start(&controls_);
if (ret < 0)
return ret == -EACCES ? -EBUSY : ret;
+ controls_.clear();
+
isRunning_ = true;
for (Request *req : pendingRequests_) {
@@ -265,6 +269,8 @@ int V4L2Camera::qbuf(unsigned int index)
return 0;
}
+ request->controls().merge(std::move(controls_));
+
ret = camera_->queueRequest(request);
if (ret < 0) {
LOG(V4L2Compat, Error) << "Can't queue request";
diff --git a/src/v4l2/v4l2_camera.h b/src/v4l2/v4l2_camera.h
index 278cc33e..e54371fb 100644
--- a/src/v4l2/v4l2_camera.h
+++ b/src/v4l2/v4l2_camera.h
@@ -8,13 +8,15 @@
#pragma once
#include <deque>
-#include <utility>
+#include <memory>
+#include <vector>
#include <libcamera/base/mutex.h>
#include <libcamera/base/semaphore.h>
#include <libcamera/base/shared_fd.h>
#include <libcamera/camera.h>
+#include <libcamera/controls.h>
#include <libcamera/framebuffer.h>
#include <libcamera/framebuffer_allocator.h>
@@ -49,6 +51,8 @@ public:
const libcamera::Size &size,
libcamera::StreamConfiguration *streamConfigOut);
+ libcamera::ControlList &controls() { return controls_; }
+
int allocBuffers(unsigned int count);
void freeBuffers();
int getBufferFd(unsigned int index);
@@ -70,6 +74,8 @@ private:
std::shared_ptr<libcamera::Camera> camera_;
std::unique_ptr<libcamera::CameraConfiguration> config_;
+ libcamera::ControlList controls_;
+
bool isRunning_;
libcamera::Mutex bufferLock_;
diff --git a/src/v4l2/v4l2_camera_proxy.cpp b/src/v4l2/v4l2_camera_proxy.cpp
index 3f7c00a2..5ac8df4c 100644
--- a/src/v4l2/v4l2_camera_proxy.cpp
+++ b/src/v4l2/v4l2_camera_proxy.cpp
@@ -8,7 +8,6 @@
#include "v4l2_camera_proxy.h"
#include <algorithm>
-#include <array>
#include <errno.h>
#include <numeric>
#include <set>
@@ -23,9 +22,11 @@
#include <libcamera/base/utils.h>
#include <libcamera/camera.h>
+#include <libcamera/control_ids.h>
+#include <libcamera/controls.h>
#include <libcamera/formats.h>
-#include "libcamera/internal/formats.h"
+#include "libcamera/internal/v4l2_pixelformat.h"
#include "v4l2_camera.h"
#include "v4l2_camera_file.h"
@@ -34,6 +35,7 @@
#define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c))
using namespace libcamera;
+using namespace std::literals::chrono_literals;
LOG_DECLARE_CATEGORY(V4L2Compat)
@@ -756,6 +758,23 @@ int V4L2CameraProxy::vidioc_streamoff(V4L2CameraFile *file, int *arg)
return ret;
}
+int V4L2CameraProxy::vidioc_s_parm(V4L2CameraFile *file, struct v4l2_streamparm *arg)
+{
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
+
+ if (!validateBufferType(arg->type))
+ return -EINVAL;
+
+ struct v4l2_fract *timeperframe = &arg->parm.capture.timeperframe;
+ utils::Duration frameDuration = 1.0s * timeperframe->numerator / timeperframe->denominator;
+
+ int64_t uDuration = frameDuration.get<std::micro>();
+ vcam_->controls().set(controls::FrameDurationLimits, { uDuration, uDuration });
+
+ return 0;
+}
+
const std::set<unsigned long> V4L2CameraProxy::supportedIoctls_ = {
VIDIOC_QUERYCAP,
VIDIOC_ENUM_FRAMESIZES,
@@ -776,6 +795,7 @@ const std::set<unsigned long> V4L2CameraProxy::supportedIoctls_ = {
VIDIOC_EXPBUF,
VIDIOC_STREAMON,
VIDIOC_STREAMOFF,
+ VIDIOC_S_PARM,
};
int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long longRequest, void *arg)
@@ -863,6 +883,9 @@ int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long longRequest, void
case VIDIOC_STREAMOFF:
ret = vidioc_streamoff(file, static_cast<int *>(arg));
break;
+ case VIDIOC_S_PARM:
+ ret = vidioc_s_parm(file, static_cast<struct v4l2_streamparm *>(arg));
+ break;
default:
ret = -ENOTTY;
break;
diff --git a/src/v4l2/v4l2_camera_proxy.h b/src/v4l2/v4l2_camera_proxy.h
index 3d8784df..c957db53 100644
--- a/src/v4l2/v4l2_camera_proxy.h
+++ b/src/v4l2/v4l2_camera_proxy.h
@@ -67,6 +67,7 @@ private:
int vidioc_expbuf(V4L2CameraFile *file, struct v4l2_exportbuffer *arg);
int vidioc_streamon(V4L2CameraFile *file, int *arg);
int vidioc_streamoff(V4L2CameraFile *file, int *arg);
+ int vidioc_s_parm(V4L2CameraFile *file, struct v4l2_streamparm *arg);
bool hasOwnership(V4L2CameraFile *file);
int acquire(V4L2CameraFile *file);
diff --git a/src/v4l2/v4l2_compat.cpp b/src/v4l2/v4l2_compat.cpp
index 66468bf3..ff833f57 100644
--- a/src/v4l2/v4l2_compat.cpp
+++ b/src/v4l2/v4l2_compat.cpp
@@ -8,7 +8,6 @@
#include "v4l2_compat_manager.h"
#include <assert.h>
-#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <sys/ioctl.h>
@@ -154,7 +153,11 @@ LIBCAMERA_PUBLIC int munmap(void *addr, size_t length)
return V4L2CompatManager::instance()->munmap(addr, length);
}
+#if HAVE_POSIX_IOCTL
+LIBCAMERA_PUBLIC int ioctl(int fd, int request, ...)
+#else
LIBCAMERA_PUBLIC int ioctl(int fd, unsigned long request, ...)
+#endif
{
void *arg;
extract_va_arg(void *, arg, request);
diff --git a/src/v4l2/v4l2_compat_manager.cpp b/src/v4l2/v4l2_compat_manager.cpp
index 6a00afb5..f53fb300 100644
--- a/src/v4l2/v4l2_compat_manager.cpp
+++ b/src/v4l2/v4l2_compat_manager.cpp
@@ -10,7 +10,6 @@
#include <dlfcn.h>
#include <fcntl.h>
#include <map>
-#include <stdarg.h>
#include <string.h>
#include <sys/eventfd.h>
#include <sys/mman.h>