summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/apps/common/event_loop.cpp64
-rw-r--r--src/apps/common/event_loop.h23
-rw-r--r--src/apps/common/options.cpp2
-rw-r--r--src/apps/common/ppm_writer.cpp6
-rw-r--r--src/apps/lc-compliance/environment.h2
-rw-r--r--src/apps/lc-compliance/helpers/capture.cpp162
-rw-r--r--src/apps/lc-compliance/helpers/capture.h51
-rw-r--r--src/apps/lc-compliance/main.cpp42
-rw-r--r--src/apps/lc-compliance/tests/capture_test.cpp21
-rw-r--r--src/apps/qcam/format_converter.cpp2
-rw-r--r--src/gstreamer/gstlibcamera-controls.cpp.in2
-rw-r--r--src/ipa/ipu3/algorithms/awb.h2
-rw-r--r--src/ipa/libipa/awb.cpp265
-rw-r--r--src/ipa/libipa/awb.h66
-rw-r--r--src/ipa/libipa/awb_bayes.cpp505
-rw-r--r--src/ipa/libipa/awb_bayes.h59
-rw-r--r--src/ipa/libipa/awb_grey.cpp114
-rw-r--r--src/ipa/libipa/awb_grey.h36
-rw-r--r--src/ipa/libipa/colours.h2
-rw-r--r--src/ipa/libipa/exposure_mode_helper.cpp4
-rw-r--r--src/ipa/libipa/interpolator.cpp10
-rw-r--r--src/ipa/libipa/interpolator.h5
-rw-r--r--src/ipa/libipa/lux.cpp20
-rw-r--r--src/ipa/libipa/lux.h3
-rw-r--r--src/ipa/libipa/meson.build8
-rw-r--r--src/ipa/libipa/pwl.cpp5
-rw-r--r--src/ipa/libipa/pwl.h3
-rw-r--r--src/ipa/libipa/vector.h370
-rw-r--r--src/ipa/rkisp1/algorithms/agc.cpp87
-rw-r--r--src/ipa/rkisp1/algorithms/agc.h3
-rw-r--r--src/ipa/rkisp1/algorithms/awb.cpp176
-rw-r--r--src/ipa/rkisp1/algorithms/awb.h10
-rw-r--r--src/ipa/rkisp1/algorithms/ccm.cpp7
-rw-r--r--src/ipa/rkisp1/algorithms/lux.cpp4
-rw-r--r--src/ipa/rkisp1/ipa_context.cpp16
-rw-r--r--src/ipa/rkisp1/ipa_context.h6
-rw-r--r--src/ipa/rkisp1/rkisp1.cpp8
-rw-r--r--src/ipa/rpi/common/ipa_base.cpp18
-rw-r--r--src/ipa/rpi/controller/rpi/agc_channel.cpp7
-rw-r--r--src/libcamera/base/log.cpp155
-rw-r--r--src/libcamera/base/object.cpp2
-rw-r--r--src/libcamera/base/signal.cpp2
-rw-r--r--src/libcamera/base/thread.cpp23
-rw-r--r--src/libcamera/converter/converter_v4l2_m2m.cpp2
-rw-r--r--src/libcamera/ipa_proxy.cpp2
-rw-r--r--src/libcamera/matrix.cpp11
-rw-r--r--src/libcamera/meson.build1
-rw-r--r--src/libcamera/pipeline/mali-c55/mali-c55.cpp2
-rw-r--r--src/libcamera/pipeline/rkisp1/rkisp1.cpp1
-rw-r--r--src/libcamera/pipeline/simple/simple.cpp16
-rw-r--r--src/libcamera/pipeline/virtual/config_parser.cpp2
-rw-r--r--src/libcamera/pipeline/virtual/virtual.cpp23
-rw-r--r--src/libcamera/pipeline/virtual/virtual.h1
-rw-r--r--src/libcamera/process.cpp2
-rw-r--r--src/libcamera/request.cpp18
-rw-r--r--src/libcamera/software_isp/software_isp.cpp36
-rw-r--r--src/libcamera/vector.cpp (renamed from src/ipa/libipa/vector.cpp)6
-rw-r--r--src/meson.build14
58 files changed, 1617 insertions, 898 deletions
diff --git a/src/apps/common/event_loop.cpp b/src/apps/common/event_loop.cpp
index f7f9afa0..b6230f4b 100644
--- a/src/apps/common/event_loop.cpp
+++ b/src/apps/common/event_loop.cpp
@@ -21,12 +21,35 @@ EventLoop::EventLoop()
evthread_use_pthreads();
base_ = event_base_new();
instance_ = this;
+
+ callsTrigger_ = event_new(base_, -1, EV_PERSIST, [](evutil_socket_t, short, void *closure) {
+ auto *self = static_cast<EventLoop *>(closure);
+
+ for (;;) {
+ std::function<void()> call;
+
+ {
+ std::lock_guard locker(self->lock_);
+ if (self->calls_.empty())
+ break;
+
+ call = std::move(self->calls_.front());
+ self->calls_.pop_front();
+ }
+
+ call();
+ }
+ }, this);
+ assert(callsTrigger_);
+ event_add(callsTrigger_, nullptr);
}
EventLoop::~EventLoop()
{
instance_ = nullptr;
+ event_free(callsTrigger_);
+
events_.clear();
event_base_free(base_);
libevent_global_shutdown();
@@ -50,20 +73,20 @@ void EventLoop::exit(int code)
event_base_loopbreak(base_);
}
-void EventLoop::callLater(const std::function<void()> &func)
+void EventLoop::callLater(std::function<void()> &&func)
{
{
std::unique_lock<std::mutex> locker(lock_);
- calls_.push_back(func);
+ calls_.push_back(std::move(func));
}
- event_base_once(base_, -1, EV_TIMEOUT, dispatchCallback, this, nullptr);
+ event_active(callsTrigger_, 0, 0);
}
void EventLoop::addFdEvent(int fd, EventType type,
- const std::function<void()> &callback)
+ std::function<void()> &&callback)
{
- std::unique_ptr<Event> event = std::make_unique<Event>(callback);
+ std::unique_ptr<Event> event = std::make_unique<Event>(std::move(callback));
short events = (type & Read ? EV_READ : 0)
| (type & Write ? EV_WRITE : 0)
| EV_PERSIST;
@@ -85,9 +108,9 @@ void EventLoop::addFdEvent(int fd, EventType type,
}
void EventLoop::addTimerEvent(const std::chrono::microseconds period,
- const std::function<void()> &callback)
+ std::function<void()> &&callback)
{
- std::unique_ptr<Event> event = std::make_unique<Event>(callback);
+ std::unique_ptr<Event> event = std::make_unique<Event>(std::move(callback));
event->event_ = event_new(base_, -1, EV_PERSIST, &EventLoop::Event::dispatch,
event.get());
if (!event->event_) {
@@ -108,31 +131,8 @@ void EventLoop::addTimerEvent(const std::chrono::microseconds period,
events_.push_back(std::move(event));
}
-void EventLoop::dispatchCallback([[maybe_unused]] evutil_socket_t fd,
- [[maybe_unused]] short flags, void *param)
-{
- EventLoop *loop = static_cast<EventLoop *>(param);
- loop->dispatchCall();
-}
-
-void EventLoop::dispatchCall()
-{
- std::function<void()> call;
-
- {
- std::unique_lock<std::mutex> locker(lock_);
- if (calls_.empty())
- return;
-
- call = calls_.front();
- calls_.pop_front();
- }
-
- call();
-}
-
-EventLoop::Event::Event(const std::function<void()> &callback)
- : callback_(callback), event_(nullptr)
+EventLoop::Event::Event(std::function<void()> &&callback)
+ : callback_(std::move(callback)), event_(nullptr)
{
}
diff --git a/src/apps/common/event_loop.h b/src/apps/common/event_loop.h
index ef129b9a..d8b6df2f 100644
--- a/src/apps/common/event_loop.h
+++ b/src/apps/common/event_loop.h
@@ -8,11 +8,14 @@
#pragma once
#include <chrono>
+#include <deque>
#include <functional>
#include <list>
#include <memory>
#include <mutex>
+#include <libcamera/base/class.h>
+
#include <event2/util.h>
struct event_base;
@@ -33,18 +36,20 @@ public:
int exec();
void exit(int code = 0);
- void callLater(const std::function<void()> &func);
+ void callLater(std::function<void()> &&func);
void addFdEvent(int fd, EventType type,
- const std::function<void()> &handler);
+ std::function<void()> &&handler);
- using duration = std::chrono::steady_clock::duration;
void addTimerEvent(const std::chrono::microseconds period,
- const std::function<void()> &handler);
+ std::function<void()> &&handler);
private:
+ LIBCAMERA_DISABLE_COPY_AND_MOVE(EventLoop)
+
struct Event {
- Event(const std::function<void()> &callback);
+ Event(std::function<void()> &&callback);
+ LIBCAMERA_DISABLE_COPY_AND_MOVE(Event)
~Event();
static void dispatch(int fd, short events, void *arg);
@@ -58,11 +63,9 @@ private:
struct event_base *base_;
int exitCode_;
- std::list<std::function<void()>> calls_;
+ std::deque<std::function<void()>> calls_;
+ struct event *callsTrigger_ = nullptr;
+
std::list<std::unique_ptr<Event>> events_;
std::mutex lock_;
-
- static void dispatchCallback(evutil_socket_t fd, short flags,
- void *param);
- void dispatchCall();
};
diff --git a/src/apps/common/options.cpp b/src/apps/common/options.cpp
index ece268d0..cae193cc 100644
--- a/src/apps/common/options.cpp
+++ b/src/apps/common/options.cpp
@@ -1040,7 +1040,7 @@ void OptionsParser::usageOptions(const std::list<Option> &options,
std::cerr << std::setw(indent) << argument;
- for (const char *help = option.help, *end = help; end; ) {
+ for (const char *help = option.help, *end = help; end;) {
end = strchr(help, '\n');
if (end) {
std::cerr << std::string(help, end - help + 1);
diff --git a/src/apps/common/ppm_writer.cpp b/src/apps/common/ppm_writer.cpp
index 2e9378aa..368de8bf 100644
--- a/src/apps/common/ppm_writer.cpp
+++ b/src/apps/common/ppm_writer.cpp
@@ -29,7 +29,7 @@ int PPMWriter::write(const char *filename,
std::ofstream output(filename, std::ios::binary);
if (!output) {
std::cerr << "Failed to open ppm file: " << filename << std::endl;
- return -EINVAL;
+ return -EIO;
}
output << "P6" << std::endl
@@ -37,7 +37,7 @@ int PPMWriter::write(const char *filename,
<< "255" << std::endl;
if (!output) {
std::cerr << "Failed to write the file header" << std::endl;
- return -EINVAL;
+ return -EIO;
}
const unsigned int rowLength = config.size.width * 3;
@@ -46,7 +46,7 @@ int PPMWriter::write(const char *filename,
output.write(row, rowLength);
if (!output) {
std::cerr << "Failed to write image data at row " << y << std::endl;
- return -EINVAL;
+ return -EIO;
}
}
diff --git a/src/apps/lc-compliance/environment.h b/src/apps/lc-compliance/environment.h
index 543e5372..834c722e 100644
--- a/src/apps/lc-compliance/environment.h
+++ b/src/apps/lc-compliance/environment.h
@@ -23,5 +23,5 @@ private:
Environment() = default;
std::string cameraId_;
- libcamera::CameraManager *cm_;
+ libcamera::CameraManager *cm_ = nullptr;
};
diff --git a/src/apps/lc-compliance/helpers/capture.cpp b/src/apps/lc-compliance/helpers/capture.cpp
index 90c1530b..f2c6d58c 100644
--- a/src/apps/lc-compliance/helpers/capture.cpp
+++ b/src/apps/lc-compliance/helpers/capture.cpp
@@ -7,13 +7,14 @@
#include "capture.h"
+#include <assert.h>
+
#include <gtest/gtest.h>
using namespace libcamera;
Capture::Capture(std::shared_ptr<Camera> camera)
- : loop_(nullptr), camera_(camera),
- allocator_(std::make_unique<FrameBufferAllocator>(camera))
+ : camera_(std::move(camera)), allocator_(camera_)
{
}
@@ -26,10 +27,8 @@ void Capture::configure(StreamRole role)
{
config_ = camera_->generateConfiguration({ role });
- if (!config_) {
- std::cout << "Role not supported by camera" << std::endl;
- GTEST_SKIP();
- }
+ if (!config_)
+ GTEST_SKIP() << "Role not supported by camera";
if (config_->validate() != CameraConfiguration::Valid) {
config_.reset();
@@ -42,155 +41,106 @@ void Capture::configure(StreamRole role)
}
}
-void Capture::start()
+void Capture::run(unsigned int captureLimit, std::optional<unsigned int> queueLimit)
{
- Stream *stream = config_->at(0).stream();
- int count = allocator_->allocate(stream);
-
- ASSERT_GE(count, 0) << "Failed to allocate buffers";
- EXPECT_EQ(count, config_->at(0).bufferCount) << "Allocated less buffers than expected";
-
- camera_->requestCompleted.connect(this, &Capture::requestComplete);
-
- ASSERT_EQ(camera_->start(), 0) << "Failed to start camera";
-}
+ assert(!queueLimit || captureLimit <= *queueLimit);
-void Capture::stop()
-{
- if (!config_ || !allocator_->allocated())
- return;
+ captureLimit_ = captureLimit;
+ queueLimit_ = queueLimit;
- camera_->stop();
+ captureCount_ = queueCount_ = 0;
- camera_->requestCompleted.disconnect(this);
+ EventLoop loop;
+ loop_ = &loop;
- Stream *stream = config_->at(0).stream();
- requests_.clear();
- allocator_->free(stream);
-}
-
-/* CaptureBalanced */
-
-CaptureBalanced::CaptureBalanced(std::shared_ptr<Camera> camera)
- : Capture(camera)
-{
-}
-
-void CaptureBalanced::capture(unsigned int numRequests)
-{
start();
- Stream *stream = config_->at(0).stream();
- const std::vector<std::unique_ptr<FrameBuffer>> &buffers = allocator_->buffers(stream);
-
- /* No point in testing less requests then the camera depth. */
- if (buffers.size() > numRequests) {
- std::cout << "Camera needs " + std::to_string(buffers.size())
- + " requests, can't test only "
- + std::to_string(numRequests) << std::endl;
- GTEST_SKIP();
- }
+ for (const auto &request : requests_)
+ queueRequest(request.get());
- queueCount_ = 0;
- captureCount_ = 0;
- captureLimit_ = numRequests;
+ EXPECT_EQ(loop_->exec(), 0);
- /* Queue the recommended number of requests. */
- for (const std::unique_ptr<FrameBuffer> &buffer : buffers) {
- std::unique_ptr<Request> request = camera_->createRequest();
- ASSERT_TRUE(request) << "Can't create request";
-
- ASSERT_EQ(request->addBuffer(stream, buffer.get()), 0) << "Can't set buffer for request";
-
- ASSERT_EQ(queueRequest(request.get()), 0) << "Failed to queue request";
-
- requests_.push_back(std::move(request));
- }
-
- /* Run capture session. */
- loop_ = new EventLoop();
- loop_->exec();
stop();
- delete loop_;
- ASSERT_EQ(captureCount_, captureLimit_);
+ EXPECT_LE(captureLimit_, captureCount_);
+ EXPECT_LE(captureCount_, queueCount_);
+ EXPECT_TRUE(!queueLimit_ || queueCount_ <= *queueLimit_);
}
-int CaptureBalanced::queueRequest(Request *request)
+int Capture::queueRequest(libcamera::Request *request)
{
- queueCount_++;
- if (queueCount_ > captureLimit_)
+ if (queueLimit_ && queueCount_ >= *queueLimit_)
return 0;
- return camera_->queueRequest(request);
+ int ret = camera_->queueRequest(request);
+ if (ret < 0)
+ return ret;
+
+ queueCount_ += 1;
+ return 0;
}
-void CaptureBalanced::requestComplete(Request *request)
+void Capture::requestComplete(Request *request)
{
- EXPECT_EQ(request->status(), Request::Status::RequestComplete)
- << "Request didn't complete successfully";
-
captureCount_++;
if (captureCount_ >= captureLimit_) {
loop_->exit(0);
return;
}
+ EXPECT_EQ(request->status(), Request::Status::RequestComplete)
+ << "Request didn't complete successfully";
+
request->reuse(Request::ReuseBuffers);
if (queueRequest(request))
loop_->exit(-EINVAL);
}
-/* CaptureUnbalanced */
-
-CaptureUnbalanced::CaptureUnbalanced(std::shared_ptr<Camera> camera)
- : Capture(camera)
-{
-}
-
-void CaptureUnbalanced::capture(unsigned int numRequests)
+void Capture::start()
{
- start();
+ assert(config_);
+ assert(!config_->empty());
+ assert(!allocator_.allocated());
+ assert(requests_.empty());
Stream *stream = config_->at(0).stream();
- const std::vector<std::unique_ptr<FrameBuffer>> &buffers = allocator_->buffers(stream);
+ int count = allocator_.allocate(stream);
- captureCount_ = 0;
- captureLimit_ = numRequests;
+ ASSERT_GE(count, 0) << "Failed to allocate buffers";
+ EXPECT_EQ(count, config_->at(0).bufferCount) << "Allocated less buffers than expected";
+
+ const std::vector<std::unique_ptr<FrameBuffer>> &buffers = allocator_.buffers(stream);
+
+ /* No point in testing less requests then the camera depth. */
+ if (queueLimit_ && *queueLimit_ < buffers.size()) {
+ GTEST_SKIP() << "Camera needs " << buffers.size()
+ << " requests, can't test only " << *queueLimit_;
+ }
- /* Queue the recommended number of requests. */
for (const std::unique_ptr<FrameBuffer> &buffer : buffers) {
std::unique_ptr<Request> request = camera_->createRequest();
ASSERT_TRUE(request) << "Can't create request";
ASSERT_EQ(request->addBuffer(stream, buffer.get()), 0) << "Can't set buffer for request";
- ASSERT_EQ(camera_->queueRequest(request.get()), 0) << "Failed to queue request";
-
requests_.push_back(std::move(request));
}
- /* Run capture session. */
- loop_ = new EventLoop();
- int status = loop_->exec();
- stop();
- delete loop_;
+ camera_->requestCompleted.connect(this, &Capture::requestComplete);
- ASSERT_EQ(status, 0);
+ ASSERT_EQ(camera_->start(), 0) << "Failed to start camera";
}
-void CaptureUnbalanced::requestComplete(Request *request)
+void Capture::stop()
{
- captureCount_++;
- if (captureCount_ >= captureLimit_) {
- loop_->exit(0);
+ if (!config_ || !allocator_.allocated())
return;
- }
- EXPECT_EQ(request->status(), Request::Status::RequestComplete)
- << "Request didn't complete successfully";
+ camera_->stop();
- request->reuse(Request::ReuseBuffers);
- if (camera_->queueRequest(request))
- loop_->exit(-EINVAL);
+ camera_->requestCompleted.disconnect(this);
+
+ Stream *stream = config_->at(0).stream();
+ requests_.clear();
+ allocator_.free(stream);
}
diff --git a/src/apps/lc-compliance/helpers/capture.h b/src/apps/lc-compliance/helpers/capture.h
index 19b6927c..0e7b848f 100644
--- a/src/apps/lc-compliance/helpers/capture.h
+++ b/src/apps/lc-compliance/helpers/capture.h
@@ -8,6 +8,7 @@
#pragma once
#include <memory>
+#include <optional>
#include <libcamera/libcamera.h>
@@ -16,51 +17,29 @@
class Capture
{
public:
+ Capture(std::shared_ptr<libcamera::Camera> camera);
+ ~Capture();
+
void configure(libcamera::StreamRole role);
+ void run(unsigned int captureLimit, std::optional<unsigned int> queueLimit = {});
-protected:
- Capture(std::shared_ptr<libcamera::Camera> camera);
- virtual ~Capture();
+private:
+ LIBCAMERA_DISABLE_COPY_AND_MOVE(Capture)
void start();
void stop();
- virtual void requestComplete(libcamera::Request *request) = 0;
-
- EventLoop *loop_;
+ int queueRequest(libcamera::Request *request);
+ void requestComplete(libcamera::Request *request);
std::shared_ptr<libcamera::Camera> camera_;
- std::unique_ptr<libcamera::FrameBufferAllocator> allocator_;
+ libcamera::FrameBufferAllocator allocator_;
std::unique_ptr<libcamera::CameraConfiguration> config_;
std::vector<std::unique_ptr<libcamera::Request>> requests_;
-};
-
-class CaptureBalanced : public Capture
-{
-public:
- CaptureBalanced(std::shared_ptr<libcamera::Camera> camera);
-
- void capture(unsigned int numRequests);
-
-private:
- int queueRequest(libcamera::Request *request);
- void requestComplete(libcamera::Request *request) override;
-
- unsigned int queueCount_;
- unsigned int captureCount_;
- unsigned int captureLimit_;
-};
-
-class CaptureUnbalanced : public Capture
-{
-public:
- CaptureUnbalanced(std::shared_ptr<libcamera::Camera> camera);
-
- void capture(unsigned int numRequests);
-
-private:
- void requestComplete(libcamera::Request *request) override;
- unsigned int captureCount_;
- unsigned int captureLimit_;
+ EventLoop *loop_ = nullptr;
+ unsigned int captureLimit_ = 0;
+ std::optional<unsigned int> queueLimit_;
+ unsigned int captureCount_ = 0;
+ unsigned int queueCount_ = 0;
};
diff --git a/src/apps/lc-compliance/main.cpp b/src/apps/lc-compliance/main.cpp
index 3f1d2a61..e9f0ffbb 100644
--- a/src/apps/lc-compliance/main.cpp
+++ b/src/apps/lc-compliance/main.cpp
@@ -45,13 +45,11 @@ class ThrowListener : public testing::EmptyTestEventListener
static void listCameras(CameraManager *cm)
{
for (const std::shared_ptr<Camera> &cam : cm->cameras())
- std::cout << "- " << cam.get()->id() << std::endl;
+ std::cout << "- " << cam->id() << std::endl;
}
static int initCamera(CameraManager *cm, OptionsParser::Options options)
{
- std::shared_ptr<Camera> camera;
-
int ret = cm->start();
if (ret) {
std::cout << "Failed to start camera manager: "
@@ -66,7 +64,7 @@ static int initCamera(CameraManager *cm, OptionsParser::Options options)
}
const std::string &cameraId = options[OptCamera];
- camera = cm->get(cameraId);
+ std::shared_ptr<Camera> camera = cm->get(cameraId);
if (!camera) {
std::cout << "Camera " << cameraId << " not found, available cameras:" << std::endl;
listCameras(cm);
@@ -82,45 +80,27 @@ static int initCamera(CameraManager *cm, OptionsParser::Options options)
static int initGtestParameters(char *arg0, OptionsParser::Options options)
{
- const std::map<std::string, std::string> gtestFlags = { { "list", "--gtest_list_tests" },
- { "filter", "--gtest_filter" } };
-
- int argc = 0;
+ std::vector<const char *> argv;
std::string filterParam;
- /*
- * +2 to have space for both the 0th argument that is needed but not
- * used and the null at the end.
- */
- char **argv = new char *[(gtestFlags.size() + 2)];
- if (!argv)
- return -ENOMEM;
-
- argv[0] = arg0;
- argc++;
+ argv.push_back(arg0);
- if (options.isSet(OptList)) {
- argv[argc] = const_cast<char *>(gtestFlags.at("list").c_str());
- argc++;
- }
+ if (options.isSet(OptList))
+ argv.push_back("--gtest_list_tests");
if (options.isSet(OptFilter)) {
/*
* The filter flag needs to be passed as a single parameter, in
* the format --gtest_filter=filterStr
*/
- filterParam = gtestFlags.at("filter") + "=" +
- static_cast<const std::string &>(options[OptFilter]);
-
- argv[argc] = const_cast<char *>(filterParam.c_str());
- argc++;
+ filterParam = "--gtest_filter=" + options[OptFilter].toString();
+ argv.push_back(filterParam.c_str());
}
- argv[argc] = nullptr;
-
- ::testing::InitGoogleTest(&argc, argv);
+ argv.push_back(nullptr);
- delete[] argv;
+ int argc = argv.size();
+ ::testing::InitGoogleTest(&argc, const_cast<char **>(argv.data()));
return 0;
}
diff --git a/src/apps/lc-compliance/tests/capture_test.cpp b/src/apps/lc-compliance/tests/capture_test.cpp
index ad3a1da2..93bed48f 100644
--- a/src/apps/lc-compliance/tests/capture_test.cpp
+++ b/src/apps/lc-compliance/tests/capture_test.cpp
@@ -14,10 +14,13 @@
#include "environment.h"
+namespace {
+
using namespace libcamera;
-const std::vector<int> NUMREQUESTS = { 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 };
-const std::vector<StreamRole> ROLES = {
+const int NUMREQUESTS[] = { 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 };
+
+const StreamRole ROLES[] = {
StreamRole::Raw,
StreamRole::StillCapture,
StreamRole::VideoRecording,
@@ -84,11 +87,11 @@ TEST_P(SingleStream, Capture)
{
auto [role, numRequests] = GetParam();
- CaptureBalanced capture(camera_);
+ Capture capture(camera_);
capture.configure(role);
- capture.capture(numRequests);
+ capture.run(numRequests, numRequests);
}
/*
@@ -103,12 +106,12 @@ TEST_P(SingleStream, CaptureStartStop)
auto [role, numRequests] = GetParam();
unsigned int numRepeats = 3;
- CaptureBalanced capture(camera_);
+ Capture capture(camera_);
capture.configure(role);
for (unsigned int starts = 0; starts < numRepeats; starts++)
- capture.capture(numRequests);
+ capture.run(numRequests, numRequests);
}
/*
@@ -122,11 +125,11 @@ TEST_P(SingleStream, UnbalancedStop)
{
auto [role, numRequests] = GetParam();
- CaptureUnbalanced capture(camera_);
+ Capture capture(camera_);
capture.configure(role);
- capture.capture(numRequests);
+ capture.run(numRequests);
}
INSTANTIATE_TEST_SUITE_P(CaptureTests,
@@ -134,3 +137,5 @@ INSTANTIATE_TEST_SUITE_P(CaptureTests,
testing::Combine(testing::ValuesIn(ROLES),
testing::ValuesIn(NUMREQUESTS)),
SingleStream::nameParameters);
+
+} /* namespace */
diff --git a/src/apps/qcam/format_converter.cpp b/src/apps/qcam/format_converter.cpp
index 32123493..b025a3c7 100644
--- a/src/apps/qcam/format_converter.cpp
+++ b/src/apps/qcam/format_converter.cpp
@@ -249,7 +249,7 @@ void FormatConverter::convertYUVPacked(const Image *srcImage, unsigned char *dst
dst_stride = width_ * 4;
for (src_y = 0, dst_y = 0; dst_y < height_; src_y++, dst_y++) {
- for (src_x = 0, dst_x = 0; dst_x < width_; ) {
+ for (src_x = 0, dst_x = 0; dst_x < width_;) {
cb = src[src_y * src_stride + src_x * 4 + cb_pos_];
cr = src[src_y * src_stride + src_x * 4 + cr_pos];
diff --git a/src/gstreamer/gstlibcamera-controls.cpp.in b/src/gstreamer/gstlibcamera-controls.cpp.in
index ace36b71..d937b19e 100644
--- a/src/gstreamer/gstlibcamera-controls.cpp.in
+++ b/src/gstreamer/gstlibcamera-controls.cpp.in
@@ -39,7 +39,7 @@ static void value_set_rectangle(GValue *value, const Rectangle &rect)
GValue height = G_VALUE_INIT;
g_value_init(&height, G_TYPE_INT);
- g_value_set_int(&x, size.height);
+ g_value_set_int(&height, size.height);
gst_value_array_append_and_take_value(value, &height);
}
diff --git a/src/ipa/ipu3/algorithms/awb.h b/src/ipa/ipu3/algorithms/awb.h
index 1916990a..dbf69c90 100644
--- a/src/ipa/ipu3/algorithms/awb.h
+++ b/src/ipa/ipu3/algorithms/awb.h
@@ -13,7 +13,7 @@
#include <libcamera/geometry.h>
-#include "libipa/vector.h"
+#include "libcamera/internal/vector.h"
#include "algorithm.h"
diff --git a/src/ipa/libipa/awb.cpp b/src/ipa/libipa/awb.cpp
new file mode 100644
index 00000000..925fac23
--- /dev/null
+++ b/src/ipa/libipa/awb.cpp
@@ -0,0 +1,265 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024 Ideas on Board Oy
+ *
+ * Generic AWB algorithms
+ */
+
+#include "awb.h"
+
+#include <libcamera/base/log.h>
+
+#include <libcamera/control_ids.h>
+
+/**
+ * \file awb.h
+ * \brief Base classes for AWB algorithms
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(Awb)
+
+namespace ipa {
+
+/**
+ * \class AwbResult
+ * \brief The result of an AWB calculation
+ *
+ * This class holds the result of an auto white balance calculation.
+ */
+
+/**
+ * \var AwbResult::gains
+ * \brief The calculated white balance gains
+ */
+
+/**
+ * \var AwbResult::colourTemperature
+ * \brief The calculated colour temperature in Kelvin
+ */
+
+/**
+ * \class AwbStats
+ * \brief An abstraction class wrapping hardware-specific AWB statistics
+ *
+ * IPA modules using an AWB algorithm based on the AwbAlgorithm class need to
+ * implement this class to give the algorithm access to the hardware-specific
+ * statistics data.
+ */
+
+/**
+ * \fn AwbStats::computeColourError()
+ * \brief Compute an error value for when the given gains would be applied
+ * \param[in] gains The gains to apply
+ *
+ * Compute an error value (non-greyness) assuming the given \a gains would be
+ * applied. To keep the actual implementations computationally inexpensive,
+ * the squared colour error shall be returned.
+ *
+ * If the AWB statistics provide multiple zones, the average of the individual
+ * squared errors shall be returned. Averaging/normalizing is necessary so that
+ * the numeric dimensions are the same on all hardware platforms.
+ *
+ * \return The computed error value
+ */
+
+/**
+ * \fn AwbStats::rgbMeans()
+ * \brief Get RGB means of the statistics
+ *
+ * Fetch the RGB means from the statistics. The values of each channel are
+ * dimensionless and only the ratios are used for further calculations. This is
+ * used by the simple grey world model to calculate the gains to apply.
+ *
+ * \return The RGB means
+ */
+
+/**
+ * \class AwbAlgorithm
+ * \brief A base class for auto white balance algorithms
+ *
+ * This class is a base class for auto white balance algorithms. It defines an
+ * interface for the algorithms to implement, and is used by the IPAs to
+ * interact with the concrete implementation.
+ */
+
+/**
+ * \fn AwbAlgorithm::init()
+ * \brief Initialize the algorithm with the given tuning data
+ * \param[in] tuningData The tuning data to use for the algorithm
+ *
+ * \return 0 on success, a negative error code otherwise
+ */
+
+/**
+ * \fn AwbAlgorithm::calculateAwb()
+ * \brief Calculate AWB data from the given statistics
+ * \param[in] stats The statistics to use for the calculation
+ * \param[in] lux The lux value of the scene
+ *
+ * Calculate an AwbResult object from the given statistics and lux value. A \a
+ * lux value of 0 means it is unknown or invalid and the algorithm shall ignore
+ * it.
+ *
+ * \return The AWB result
+ */
+
+/**
+ * \fn AwbAlgorithm::gainsFromColourTemperature()
+ * \brief Compute white balance gains from a colour temperature
+ * \param[in] colourTemperature The colour temperature in Kelvin
+ *
+ * Compute the white balance gains from a \a colourTemperature. This function
+ * does not take any statistics into account. It is used to compute the colour
+ * gains when the user manually specifies a colour temperature.
+ *
+ * \return The colour gains
+ */
+
+/**
+ * \fn AwbAlgorithm::controls()
+ * \brief Get the controls info map for this algorithm
+ *
+ * \return The controls info map
+ */
+
+/**
+ * \fn AwbAlgorithm::handleControls()
+ * \param[in] controls The controls to handle
+ * \brief Handle the controls supplied in a request
+ */
+
+/**
+ * \brief Parse the mode configurations from the tuning data
+ * \param[in] tuningData the YamlObject representing the tuning data
+ * \param[in] def The default value for the AwbMode control
+ *
+ * Utility function to parse the tuning data for an AwbMode entry and read all
+ * provided modes. It adds controls::AwbMode to AwbAlgorithm::controls_ and
+ * populates AwbAlgorithm::modes_. For a list of possible modes see \ref
+ * controls::AwbModeEnum.
+ *
+ * Each mode entry must contain a "lo" and "hi" key to specify the lower and
+ * upper colour temperature of that mode. For example:
+ *
+ * \code{.unparsed}
+ * algorithms:
+ * - Awb:
+ * AwbMode:
+ * AwbAuto:
+ * lo: 2500
+ * hi: 8000
+ * AwbIncandescent:
+ * lo: 2500
+ * hi: 3000
+ * ...
+ * \endcode
+ *
+ * If \a def is supplied but not contained in the the \a tuningData, -EINVAL is
+ * returned.
+ *
+ * \sa controls::AwbModeEnum
+ * \return Zero on success, negative error code otherwise
+ */
+int AwbAlgorithm::parseModeConfigs(const YamlObject &tuningData,
+ const ControlValue &def)
+{
+ std::vector<ControlValue> availableModes;
+
+ const YamlObject &yamlModes = tuningData[controls::AwbMode.name()];
+ if (!yamlModes.isDictionary()) {
+ LOG(Awb, Error)
+ << "AwbModes must be a dictionary.";
+ return -EINVAL;
+ }
+
+ for (const auto &[modeName, modeDict] : yamlModes.asDict()) {
+ if (controls::AwbModeNameValueMap.find(modeName) ==
+ controls::AwbModeNameValueMap.end()) {
+ LOG(Awb, Warning)
+ << "Skipping unknown AWB mode '"
+ << modeName << "'";
+ continue;
+ }
+
+ if (!modeDict.isDictionary()) {
+ LOG(Awb, Error)
+ << "Invalid AWB mode '" << modeName << "'";
+ return -EINVAL;
+ }
+
+ const auto &modeValue = static_cast<controls::AwbModeEnum>(
+ controls::AwbModeNameValueMap.at(modeName));
+
+ ModeConfig &config = modes_[modeValue];
+
+ auto hi = modeDict["hi"].get<double>();
+ if (!hi) {
+ LOG(Awb, Error) << "Failed to read hi param of mode "
+ << modeName;
+ return -EINVAL;
+ }
+ config.ctHi = *hi;
+
+ auto lo = modeDict["lo"].get<double>();
+ if (!lo) {
+ LOG(Awb, Error) << "Failed to read low param of mode "
+ << modeName;
+ return -EINVAL;
+ }
+ config.ctLo = *lo;
+
+ availableModes.push_back(modeValue);
+ }
+
+ if (modes_.empty()) {
+ LOG(Awb, Error) << "No AWB modes configured";
+ return -EINVAL;
+ }
+
+ if (!def.isNone() &&
+ modes_.find(def.get<controls::AwbModeEnum>()) == modes_.end()) {
+ const auto &names = controls::AwbMode.enumerators();
+ LOG(Awb, Error) << names.at(def.get<controls::AwbModeEnum>())
+ << " mode is missing in the configuration.";
+ return -EINVAL;
+ }
+
+ controls_[&controls::AwbMode] = ControlInfo(availableModes, def);
+
+ return 0;
+}
+
+/**
+ * \class AwbAlgorithm::ModeConfig
+ * \brief Holds the configuration of a single AWB mode
+ *
+ * AWB modes limit the regulation of the AWB algorithm to a specific range of
+ * colour temperatures.
+ */
+
+/**
+ * \var AwbAlgorithm::ModeConfig::ctLo
+ * \brief The lowest valid colour temperature of that mode
+ */
+
+/**
+ * \var AwbAlgorithm::ModeConfig::ctHi
+ * \brief The highest valid colour temperature of that mode
+ */
+
+/**
+ * \var AwbAlgorithm::controls_
+ * \brief Controls info map for the controls provided by the algorithm
+ */
+
+/**
+ * \var AwbAlgorithm::modes_
+ * \brief Map of all configured modes
+ * \sa AwbAlgorithm::parseModeConfigs
+ */
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
diff --git a/src/ipa/libipa/awb.h b/src/ipa/libipa/awb.h
new file mode 100644
index 00000000..4bab7a45
--- /dev/null
+++ b/src/ipa/libipa/awb.h
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024 Ideas on Board Oy
+ *
+ * Generic AWB algorithms
+ */
+
+#pragma once
+
+#include <map>
+
+#include <libcamera/control_ids.h>
+#include <libcamera/controls.h>
+
+#include "libcamera/internal/vector.h"
+#include "libcamera/internal/yaml_parser.h"
+
+namespace libcamera {
+
+namespace ipa {
+
+struct AwbResult {
+ RGB<double> gains;
+ double colourTemperature;
+};
+
+struct AwbStats {
+ virtual double computeColourError(const RGB<double> &gains) const = 0;
+ virtual RGB<double> rgbMeans() const = 0;
+
+protected:
+ ~AwbStats() = default;
+};
+
+class AwbAlgorithm
+{
+public:
+ virtual ~AwbAlgorithm() = default;
+
+ virtual int init(const YamlObject &tuningData) = 0;
+ virtual AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) = 0;
+ virtual RGB<double> gainsFromColourTemperature(double colourTemperature) = 0;
+
+ const ControlInfoMap::Map &controls() const
+ {
+ return controls_;
+ }
+
+ virtual void handleControls([[maybe_unused]] const ControlList &controls) {}
+
+protected:
+ int parseModeConfigs(const YamlObject &tuningData,
+ const ControlValue &def = {});
+
+ struct ModeConfig {
+ double ctHi;
+ double ctLo;
+ };
+
+ ControlInfoMap::Map controls_;
+ std::map<controls::AwbModeEnum, AwbAlgorithm::ModeConfig> modes_;
+};
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
diff --git a/src/ipa/libipa/awb_bayes.cpp b/src/ipa/libipa/awb_bayes.cpp
new file mode 100644
index 00000000..d1d0eaf0
--- /dev/null
+++ b/src/ipa/libipa/awb_bayes.cpp
@@ -0,0 +1,505 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ * Copyright (C) 2024 Ideas on Board Oy
+ *
+ * Implementation of a bayesian AWB algorithm
+ */
+
+#include "awb_bayes.h"
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+#include <map>
+#include <ostream>
+#include <vector>
+
+#include <libcamera/base/log.h>
+#include <libcamera/control_ids.h>
+
+#include "colours.h"
+
+/**
+ * \file awb_bayes.h
+ * \brief Implementation of bayesian auto white balance algorithm
+ *
+ * This implementation is based on the initial implementation done by
+ * RaspberryPi.
+ *
+ * \todo Documentation
+ *
+ * \todo Not all the features implemented by RaspberryPi were ported over to
+ * this algorithm because they either rely on hardware features not generally
+ * available or were considered not important enough at the moment.
+ *
+ * The following parts are not implemented:
+ *
+ * - min_pixels: minimum proportion of pixels counted within AWB region for it
+ * to be "useful"
+ * - min_g: minimum G value of those pixels, to be regarded a "useful"
+ * - min_regions: number of AWB regions that must be "useful" in order to do the
+ * AWB calculation
+ * - deltaLimit: clamp on colour error term (so as not to penalize non-grey
+ * excessively)
+ * - bias_proportion: The biasProportion parameter adds a small proportion of
+ * the counted pixels to a region biased to the biasCT colour temperature.
+ * A typical value for biasProportion would be between 0.05 to 0.1.
+ * - bias_ct: CT target for the search bias
+ * - sensitivityR: red sensitivity ratio (set to canonical sensor's R/G divided
+ * by this sensor's R/G)
+ * - sensitivityB: blue sensitivity ratio (set to canonical sensor's B/G divided
+ * by this sensor's B/G)
+ */
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(Awb)
+
+namespace {
+
+template<typename T>
+class LimitsRecorder
+{
+public:
+ LimitsRecorder()
+ : min_(std::numeric_limits<T>::max()),
+ max_(std::numeric_limits<T>::min())
+ {
+ }
+
+ void record(const T &value)
+ {
+ min_ = std::min(min_, value);
+ max_ = std::max(max_, value);
+ }
+
+ const T &min() const { return min_; }
+ const T &max() const { return max_; }
+
+private:
+ T min_;
+ T max_;
+};
+
+#ifndef __DOXYGEN__
+template<typename T>
+std::ostream &operator<<(std::ostream &out, const LimitsRecorder<T> &v)
+{
+ out << "[ " << v.min() << ", " << v.max() << " ]";
+ return out;
+}
+#endif
+
+} /* namespace */
+
+namespace ipa {
+
+/**
+ * \brief Step size control for CT search
+ */
+constexpr double kSearchStep = 0.2;
+
+/**
+ * \copydoc libcamera::ipa::Interpolator::interpolate()
+ */
+template<>
+void Interpolator<Pwl>::interpolate(const Pwl &a, const Pwl &b, Pwl &dest, double lambda)
+{
+ dest = Pwl::combine(a, b,
+ [=](double /*x*/, double y0, double y1) -> double {
+ return y0 * (1.0 - lambda) + y1 * lambda;
+ });
+}
+
+/**
+ * \class AwbBayes
+ * \brief Implementation of a bayesian auto white balance algorithm
+ *
+ * In a bayesian AWB algorithm the auto white balance estimation is improved by
+ * taking the likelihood of a given lightsource based on the estimated lux level
+ * into account. E.g. If it is very bright we can assume that we are outside and
+ * that colour temperatures around 6500 are preferred.
+ *
+ * The second part of this algorithm is the search for the most likely colour
+ * temperature. It is implemented in AwbBayes::coarseSearch() and in
+ * AwbBayes::fineSearch(). The search works very well without prior likelihoods
+ * and therefore the algorithm itself provides very good results even without
+ * prior likelihoods.
+ */
+
+/**
+ * \var AwbBayes::transversePos_
+ * \brief How far to wander off CT curve towards "more purple"
+ */
+
+/**
+ * \var AwbBayes::transverseNeg_
+ * \brief How far to wander off CT curve towards "more green"
+ */
+
+/**
+ * \var AwbBayes::currentMode_
+ * \brief The currently selected mode
+ */
+
+int AwbBayes::init(const YamlObject &tuningData)
+{
+ int ret = colourGainCurve_.readYaml(tuningData["colourGains"], "ct", "gains");
+ if (ret) {
+ LOG(Awb, Error)
+ << "Failed to parse 'colourGains' "
+ << "parameter from tuning file";
+ return ret;
+ }
+
+ ctR_.clear();
+ ctB_.clear();
+ for (const auto &[ct, g] : colourGainCurve_.data()) {
+ ctR_.append(ct, 1.0 / g[0]);
+ ctB_.append(ct, 1.0 / g[1]);
+ }
+
+ /* We will want the inverse functions of these too. */
+ ctRInverse_ = ctR_.inverse().first;
+ ctBInverse_ = ctB_.inverse().first;
+
+ ret = readPriors(tuningData);
+ if (ret) {
+ LOG(Awb, Error) << "Failed to read priors";
+ return ret;
+ }
+
+ ret = parseModeConfigs(tuningData, controls::AwbAuto);
+ if (ret) {
+ LOG(Awb, Error)
+ << "Failed to parse mode parameter from tuning file";
+ return ret;
+ }
+ currentMode_ = &modes_[controls::AwbAuto];
+
+ transversePos_ = tuningData["transversePos"].get<double>(0.01);
+ transverseNeg_ = tuningData["transverseNeg"].get<double>(0.01);
+ if (transversePos_ <= 0 || transverseNeg_ <= 0) {
+ LOG(Awb, Error) << "AwbConfig: transversePos/Neg must be > 0";
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int AwbBayes::readPriors(const YamlObject &tuningData)
+{
+ const auto &priorsList = tuningData["priors"];
+ std::map<uint32_t, Pwl> priors;
+
+ if (!priorsList) {
+ LOG(Awb, Error) << "No priors specified";
+ return -EINVAL;
+ }
+
+ for (const auto &p : priorsList.asList()) {
+ if (!p.contains("lux")) {
+ LOG(Awb, Error) << "Missing lux value";
+ return -EINVAL;
+ }
+
+ uint32_t lux = p["lux"].get<uint32_t>(0);
+ if (priors.count(lux)) {
+ LOG(Awb, Error) << "Duplicate prior for lux value " << lux;
+ return -EINVAL;
+ }
+
+ std::vector<uint32_t> temperatures =
+ p["ct"].getList<uint32_t>().value_or(std::vector<uint32_t>{});
+ std::vector<double> probabilities =
+ p["probability"].getList<double>().value_or(std::vector<double>{});
+
+ if (temperatures.size() != probabilities.size()) {
+ LOG(Awb, Error)
+ << "Ct and probability array sizes are unequal";
+ return -EINVAL;
+ }
+
+ if (temperatures.empty()) {
+ LOG(Awb, Error)
+ << "Ct and probability arrays are empty";
+ return -EINVAL;
+ }
+
+ std::map<int, double> ctToProbability;
+ for (unsigned int i = 0; i < temperatures.size(); i++) {
+ int t = temperatures[i];
+ if (ctToProbability.count(t)) {
+ LOG(Awb, Error) << "Duplicate ct value";
+ return -EINVAL;
+ }
+
+ ctToProbability[t] = probabilities[i];
+ }
+
+ auto &pwl = priors[lux];
+ for (const auto &[ct, prob] : ctToProbability) {
+ if (prob < 1e-6) {
+ LOG(Awb, Error) << "Prior probability must be larger than 1e-6";
+ return -EINVAL;
+ }
+ pwl.append(ct, prob);
+ }
+ }
+
+ if (priors.empty()) {
+ LOG(Awb, Error) << "No priors specified";
+ return -EINVAL;
+ }
+
+ priors_.setData(std::move(priors));
+
+ return 0;
+}
+
+void AwbBayes::handleControls(const ControlList &controls)
+{
+ auto mode = controls.get(controls::AwbMode);
+ if (mode) {
+ auto it = modes_.find(static_cast<controls::AwbModeEnum>(*mode));
+ if (it != modes_.end())
+ currentMode_ = &it->second;
+ else
+ LOG(Awb, Error) << "Unsupported AWB mode " << *mode;
+ }
+}
+
+RGB<double> AwbBayes::gainsFromColourTemperature(double colourTemperature)
+{
+ /*
+ * \todo In the RaspberryPi code, the ct curve was interpolated in
+ * the white point space (1/x) not in gains space. This feels counter
+ * intuitive, as the gains are in linear space. But I can't prove it.
+ */
+ const auto &gains = colourGainCurve_.getInterpolated(colourTemperature);
+ return { { gains[0], 1.0, gains[1] } };
+}
+
+AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)
+{
+ ipa::Pwl prior;
+ if (lux > 0) {
+ prior = priors_.getInterpolated(lux);
+ prior.map([](double x, double y) {
+ LOG(Awb, Debug) << "(" << x << "," << y << ")";
+ });
+ } else {
+ prior.append(0, 1.0);
+ }
+
+ double t = coarseSearch(prior, stats);
+ double r = ctR_.eval(t);
+ double b = ctB_.eval(t);
+ LOG(Awb, Debug)
+ << "After coarse search: r " << r << " b " << b << " (gains r "
+ << 1 / r << " b " << 1 / b << ")";
+
+ /*
+ * Original comment from RaspberryPi:
+ * Not entirely sure how to handle the fine search yet. Mostly the
+ * estimated CT is already good enough, but the fine search allows us to
+ * wander transversely off the CT curve. Under some illuminants, where
+ * there may be more or less green light, this may prove beneficial,
+ * though I probably need more real datasets before deciding exactly how
+ * this should be controlled and tuned.
+ */
+ fineSearch(t, r, b, prior, stats);
+ LOG(Awb, Debug)
+ << "After fine search: r " << r << " b " << b << " (gains r "
+ << 1 / r << " b " << 1 / b << ")";
+
+ return { { { 1.0 / r, 1.0, 1.0 / b } }, t };
+}
+
+double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const
+{
+ std::vector<Pwl::Point> points;
+ size_t bestPoint = 0;
+ double t = currentMode_->ctLo;
+ int spanR = -1;
+ int spanB = -1;
+ LimitsRecorder<double> errorLimits;
+ LimitsRecorder<double> priorLogLikelihoodLimits;
+
+ /* Step down the CT curve evaluating log likelihood. */
+ while (true) {
+ double r = ctR_.eval(t, &spanR);
+ double b = ctB_.eval(t, &spanB);
+ RGB<double> gains({ 1 / r, 1.0, 1 / b });
+ double delta2Sum = stats.computeColourError(gains);
+ double priorLogLikelihood = log(prior.eval(prior.domain().clamp(t)));
+ double finalLogLikelihood = delta2Sum - priorLogLikelihood;
+
+ errorLimits.record(delta2Sum);
+ priorLogLikelihoodLimits.record(priorLogLikelihood);
+
+ points.push_back({ { t, finalLogLikelihood } });
+ if (points.back().y() < points[bestPoint].y())
+ bestPoint = points.size() - 1;
+
+ if (t == currentMode_->ctHi)
+ break;
+
+ /*
+ * Ensure even steps along the r/b curve by scaling them by the
+ * current t.
+ */
+ t = std::min(t + t / 10 * kSearchStep, currentMode_->ctHi);
+ }
+
+ t = points[bestPoint].x();
+ LOG(Awb, Debug) << "Coarse search found CT " << t
+ << " error limits:" << errorLimits
+ << " prior log likelihood limits:" << priorLogLikelihoodLimits;
+
+ /*
+ * We have the best point of the search, but refine it with a quadratic
+ * interpolation around its neighbors.
+ */
+ if (points.size() > 2) {
+ bestPoint = std::clamp(bestPoint, std::size_t{ 1 }, points.size() - 2);
+ t = interpolateQuadratic(points[bestPoint - 1],
+ points[bestPoint],
+ points[bestPoint + 1]);
+ LOG(Awb, Debug)
+ << "After quadratic refinement, coarse search has CT "
+ << t;
+ }
+
+ return t;
+}
+
+void AwbBayes::fineSearch(double &t, double &r, double &b, ipa::Pwl const &prior, const AwbStats &stats) const
+{
+ int spanR = -1;
+ int spanB = -1;
+ double step = t / 10 * kSearchStep * 0.1;
+ int nsteps = 5;
+ ctR_.eval(t, &spanR);
+ ctB_.eval(t, &spanB);
+ double rDiff = ctR_.eval(t + nsteps * step, &spanR) -
+ ctR_.eval(t - nsteps * step, &spanR);
+ double bDiff = ctB_.eval(t + nsteps * step, &spanB) -
+ ctB_.eval(t - nsteps * step, &spanB);
+ Pwl::Point transverse({ bDiff, -rDiff });
+ if (transverse.length2() < 1e-6)
+ return;
+ /*
+ * transverse is a unit vector orthogonal to the b vs. r function
+ * (pointing outwards with r and b increasing)
+ */
+ transverse = transverse / transverse.length();
+ double bestLogLikelihood = 0;
+ double bestT = 0;
+ Pwl::Point bestRB(0);
+ double transverseRange = transverseNeg_ + transversePos_;
+ const int maxNumDeltas = 12;
+ LimitsRecorder<double> errorLimits;
+ LimitsRecorder<double> priorLogLikelihoodLimits;
+
+
+ /* a transverse step approximately every 0.01 r/b units */
+ int numDeltas = floor(transverseRange * 100 + 0.5) + 1;
+ numDeltas = std::clamp(numDeltas, 3, maxNumDeltas);
+
+ /*
+ * Step down CT curve. March a bit further if the transverse range is
+ * large.
+ */
+ nsteps += numDeltas;
+ for (int i = -nsteps; i <= nsteps; i++) {
+ double tTest = t + i * step;
+ double priorLogLikelihood =
+ log(prior.eval(prior.domain().clamp(tTest)));
+ priorLogLikelihoodLimits.record(priorLogLikelihood);
+ Pwl::Point rbStart{ { ctR_.eval(tTest, &spanR),
+ ctB_.eval(tTest, &spanB) } };
+ Pwl::Point samples[maxNumDeltas];
+ int bestPoint = 0;
+
+ /*
+ * Sample numDeltas points transversely *off* the CT curve
+ * in the range [-transverseNeg, transversePos].
+ * The x values of a sample contains the distance and the y
+ * value contains the corresponding log likelihood.
+ */
+ double transverseStep = transverseRange / (numDeltas - 1);
+ for (int j = 0; j < numDeltas; j++) {
+ auto &p = samples[j];
+ p.x() = -transverseNeg_ + transverseStep * j;
+ Pwl::Point rbTest = rbStart + transverse * p.x();
+ RGB<double> gains({ 1 / rbTest[0], 1.0, 1 / rbTest[1] });
+ double delta2Sum = stats.computeColourError(gains);
+ errorLimits.record(delta2Sum);
+ p.y() = delta2Sum - priorLogLikelihood;
+
+ if (p.y() < samples[bestPoint].y())
+ bestPoint = j;
+ }
+
+ /*
+ * We have all samples transversely across the CT curve,
+ * now let's do a quadratic interpolation for the best result.
+ */
+ bestPoint = std::clamp(bestPoint, 1, numDeltas - 2);
+ double bestOffset = interpolateQuadratic(samples[bestPoint - 1],
+ samples[bestPoint],
+ samples[bestPoint + 1]);
+ Pwl::Point rbTest = rbStart + transverse * bestOffset;
+ RGB<double> gains({ 1 / rbTest[0], 1.0, 1 / rbTest[1] });
+ double delta2Sum = stats.computeColourError(gains);
+ errorLimits.record(delta2Sum);
+ double finalLogLikelihood = delta2Sum - priorLogLikelihood;
+
+ if (bestT == 0 || finalLogLikelihood < bestLogLikelihood) {
+ bestLogLikelihood = finalLogLikelihood;
+ bestT = tTest;
+ bestRB = rbTest;
+ }
+ }
+
+ t = bestT;
+ r = bestRB[0];
+ b = bestRB[1];
+ LOG(Awb, Debug)
+ << "Fine search found t " << t << " r " << r << " b " << b
+ << " error limits: " << errorLimits
+ << " prior log likelihood limits: " << priorLogLikelihoodLimits;
+}
+
+/**
+ * \brief Find extremum of function
+ * \param[in] a First point
+ * \param[in] b Second point
+ * \param[in] c Third point
+ *
+ * Given 3 points on a curve, find the extremum of the function in that interval
+ * by fitting a quadratic.
+ *
+ * \return The x value of the extremum clamped to the interval [a.x(), c.x()]
+ */
+double AwbBayes::interpolateQuadratic(Pwl::Point const &a, Pwl::Point const &b,
+ Pwl::Point const &c) const
+{
+ const double eps = 1e-3;
+ Pwl::Point ca = c - a;
+ Pwl::Point ba = b - a;
+ double denominator = 2 * (ba.y() * ca.x() - ca.y() * ba.x());
+ 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));
+ }
+ /* has degenerated to straight line segment */
+ return a.y() < c.y() - eps ? a.x() : (c.y() < a.y() - eps ? c.x() : b.x());
+}
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
diff --git a/src/ipa/libipa/awb_bayes.h b/src/ipa/libipa/awb_bayes.h
new file mode 100644
index 00000000..bb933038
--- /dev/null
+++ b/src/ipa/libipa/awb_bayes.h
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024 Ideas on Board Oy
+ *
+ * Base class for bayes AWB algorithms
+ */
+
+#pragma once
+
+#include <libcamera/controls.h>
+
+#include "libcamera/internal/vector.h"
+#include "libcamera/internal/yaml_parser.h"
+
+#include "awb.h"
+#include "interpolator.h"
+#include "pwl.h"
+
+namespace libcamera {
+
+namespace ipa {
+
+class AwbBayes : public AwbAlgorithm
+{
+public:
+ AwbBayes() = default;
+
+ int init(const YamlObject &tuningData) override;
+ AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override;
+ RGB<double> gainsFromColourTemperature(double temperatureK) override;
+ void handleControls(const ControlList &controls) override;
+
+private:
+ int readPriors(const YamlObject &tuningData);
+
+ void fineSearch(double &t, double &r, double &b, ipa::Pwl const &prior,
+ const AwbStats &stats) const;
+ double coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const;
+ double interpolateQuadratic(ipa::Pwl::Point const &a,
+ ipa::Pwl::Point const &b,
+ ipa::Pwl::Point const &c) const;
+
+ Interpolator<Pwl> priors_;
+ Interpolator<Vector<double, 2>> colourGainCurve_;
+
+ ipa::Pwl ctR_;
+ ipa::Pwl ctB_;
+ ipa::Pwl ctRInverse_;
+ ipa::Pwl ctBInverse_;
+
+ double transversePos_;
+ double transverseNeg_;
+
+ ModeConfig *currentMode_ = nullptr;
+};
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
diff --git a/src/ipa/libipa/awb_grey.cpp b/src/ipa/libipa/awb_grey.cpp
new file mode 100644
index 00000000..d3d2132b
--- /dev/null
+++ b/src/ipa/libipa/awb_grey.cpp
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024 Ideas on Board Oy
+ *
+ * Implementation of grey world AWB algorithm
+ */
+
+#include "awb_grey.h"
+
+#include <algorithm>
+
+#include <libcamera/base/log.h>
+#include <libcamera/control_ids.h>
+
+#include "colours.h"
+
+using namespace libcamera::controls;
+
+/**
+ * \file awb_grey.h
+ * \brief Implementation of a grey world AWB algorithm
+ */
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(Awb)
+namespace ipa {
+
+/**
+ * \class AwbGrey
+ * \brief A Grey world auto white balance algorithm
+ */
+
+/**
+ * \brief Initialize the algorithm with the given tuning data
+ * \param[in] tuningData The tuning data for the algorithm
+ *
+ * Load the colour temperature curve from the tuning data. If there is no tuning
+ * data available, continue with a warning. Manual colour temperature will not
+ * work in that case.
+ *
+ * \return 0 on success, a negative error code otherwise
+ */
+int AwbGrey::init(const YamlObject &tuningData)
+{
+ Interpolator<Vector<double, 2>> gains;
+ int ret = gains.readYaml(tuningData["colourGains"], "ct", "gains");
+ if (ret < 0)
+ LOG(Awb, Warning)
+ << "Failed to parse 'colourGains' "
+ << "parameter from tuning file; "
+ << "manual colour temperature will not work properly";
+ else
+ colourGainCurve_ = gains;
+
+ return 0;
+}
+
+/**
+ * \brief Calculate AWB data from the given statistics
+ * \param[in] stats The statistics to use for the calculation
+ * \param[in] lux The lux value of the scene
+ *
+ * The colour temperature is estimated based on the colours::estimateCCT()
+ * function. The gains are calculated purely based on the RGB means provided by
+ * the \a stats. The colour temperature is not taken into account when
+ * calculating the gains.
+ *
+ * The \a lux parameter is not used in this algorithm.
+ *
+ * \return The AWB result
+ */
+AwbResult AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned int lux)
+{
+ AwbResult result;
+ auto means = stats.rgbMeans();
+ result.colourTemperature = estimateCCT(means);
+
+ /*
+ * 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
+ * divisor to a minimum value of 1.0.
+ */
+ result.gains.r() = means.g() / std::max(means.r(), 1.0);
+ result.gains.g() = 1.0;
+ result.gains.b() = means.g() / std::max(means.b(), 1.0);
+ return result;
+}
+
+/**
+ * \brief Compute white balance gains from a colour temperature
+ * \param[in] colourTemperature The colour temperature in Kelvin
+ *
+ * Compute the white balance gains from a \a colourTemperature. This function
+ * does not take any statistics into account. It simply interpolates the colour
+ * gains configured in the colour temperature curve.
+ *
+ * \return The colour gains if a colour temperature curve is available,
+ * [1, 1, 1] otherwise.
+ */
+RGB<double> AwbGrey::gainsFromColourTemperature(double colourTemperature)
+{
+ if (!colourGainCurve_) {
+ LOG(Awb, Error) << "No gains defined";
+ return RGB<double>({ 1.0, 1.0, 1.0 });
+ }
+
+ auto gains = colourGainCurve_->getInterpolated(colourTemperature);
+ return { { gains[0], 1.0, gains[1] } };
+}
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
diff --git a/src/ipa/libipa/awb_grey.h b/src/ipa/libipa/awb_grey.h
new file mode 100644
index 00000000..7ec7bfa5
--- /dev/null
+++ b/src/ipa/libipa/awb_grey.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024 Ideas on Board Oy
+ *
+ * AWB grey world algorithm
+ */
+
+#pragma once
+
+#include <optional>
+
+#include "libcamera/internal/vector.h"
+
+#include "awb.h"
+#include "interpolator.h"
+
+namespace libcamera {
+
+namespace ipa {
+
+class AwbGrey : public AwbAlgorithm
+{
+public:
+ AwbGrey() = default;
+
+ int init(const YamlObject &tuningData) override;
+ AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override;
+ RGB<double> gainsFromColourTemperature(double colourTemperature) override;
+
+private:
+ std::optional<Interpolator<Vector<double, 2>>> colourGainCurve_;
+};
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
diff --git a/src/ipa/libipa/colours.h b/src/ipa/libipa/colours.h
index fa6a8b57..d39b2ca8 100644
--- a/src/ipa/libipa/colours.h
+++ b/src/ipa/libipa/colours.h
@@ -9,7 +9,7 @@
#include <stdint.h>
-#include "vector.h"
+#include "libcamera/internal/vector.h"
namespace libcamera {
diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp
index f235316d..0c1e99e3 100644
--- a/src/ipa/libipa/exposure_mode_helper.cpp
+++ b/src/ipa/libipa/exposure_mode_helper.cpp
@@ -182,6 +182,7 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const
* the stage limits are initialised.
*/
+ /* Clamp the gain to lastStageGain and regulate exposureTime. */
if (stageExposureTime * lastStageGain >= exposure) {
exposureTime = clampExposureTime(exposure / clampGain(lastStageGain));
gain = clampGain(exposure / exposureTime);
@@ -189,8 +190,9 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const
return { exposureTime, gain, exposure / (exposureTime * gain) };
}
+ /* Clamp the exposureTime to stageExposureTime and regulate gain. */
if (stageExposureTime * stageGain >= exposure) {
- exposureTime = clampExposureTime(exposure / clampGain(stageGain));
+ exposureTime = clampExposureTime(stageExposureTime);
gain = clampGain(exposure / exposureTime);
return { exposureTime, gain, exposure / (exposureTime * gain) };
diff --git a/src/ipa/libipa/interpolator.cpp b/src/ipa/libipa/interpolator.cpp
index 73e8d3b7..f901a86e 100644
--- a/src/ipa/libipa/interpolator.cpp
+++ b/src/ipa/libipa/interpolator.cpp
@@ -126,6 +126,13 @@ namespace ipa {
*/
/**
+ * \fn std::map<unsigned int, T> &Interpolator<T>::data() const
+ * \brief Access the internal map
+ *
+ * \return The internal map
+ */
+
+/**
* \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
@@ -136,8 +143,7 @@ namespace ipa {
*/
/**
- * \fn void Interpolator<T>::interpolate(const T &a, const T &b, T &dest, double
- * lambda)
+ * \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
diff --git a/src/ipa/libipa/interpolator.h b/src/ipa/libipa/interpolator.h
index fffce214..7880db69 100644
--- a/src/ipa/libipa/interpolator.h
+++ b/src/ipa/libipa/interpolator.h
@@ -81,6 +81,11 @@ public:
lastInterpolatedKey_.reset();
}
+ const std::map<unsigned int, T> &data() const
+ {
+ return data_;
+ }
+
const T &getInterpolated(unsigned int key, unsigned int *quantizedKey = nullptr)
{
ASSERT(data_.size() > 0);
diff --git a/src/ipa/libipa/lux.cpp b/src/ipa/libipa/lux.cpp
index 61f8fea8..899e8824 100644
--- a/src/ipa/libipa/lux.cpp
+++ b/src/ipa/libipa/lux.cpp
@@ -44,11 +44,6 @@ namespace ipa {
*/
/**
- * \var Lux::binSize_
- * \brief The maximum count of each bin
- */
-
-/**
* \var Lux::referenceExposureTime_
* \brief The exposure time of the reference image, in microseconds
*/
@@ -65,9 +60,8 @@ namespace ipa {
/**
* \var Lux::referenceY_
- * \brief The measured luminance of the reference image, out of the bin size
+ * \brief The measured luminance of the reference image, normalized to 1
*
- * \sa binSize_
*/
/**
@@ -76,11 +70,9 @@ namespace ipa {
*/
/**
- * \brief Construct the Lux helper module
- * \param[in] binSize The maximum count of each bin
- */
-Lux::Lux(unsigned int binSize)
- : binSize_(binSize)
+ * \brief Construct the Lux helper module
+ */
+Lux::Lux()
{
}
@@ -97,7 +89,7 @@ Lux::Lux(unsigned int binSize)
* referenceExposureTime: 10000
* referenceAnalogueGain: 4.0
* referenceDigitalGain: 1.0
- * referenceY: 12000
+ * referenceY: 0.1831
* referenceLux: 1000
* \endcode
*
@@ -167,7 +159,7 @@ double Lux::estimateLux(utils::Duration exposureTime,
double exposureTimeRatio = referenceExposureTime_ / exposureTime;
double aGainRatio = referenceAnalogueGain_ / aGain;
double dGainRatio = referenceDigitalGain_ / dGain;
- double yRatio = currentY * (binSize_ / yHist.bins()) / referenceY_;
+ double yRatio = (currentY / yHist.bins()) / referenceY_;
double estimatedLux = exposureTimeRatio * aGainRatio * dGainRatio *
yRatio * referenceLux_;
diff --git a/src/ipa/libipa/lux.h b/src/ipa/libipa/lux.h
index 93ca6479..d95bcdaf 100644
--- a/src/ipa/libipa/lux.h
+++ b/src/ipa/libipa/lux.h
@@ -21,7 +21,7 @@ class Histogram;
class Lux
{
public:
- Lux(unsigned int binSize);
+ Lux();
int parseTuningData(const YamlObject &tuningData);
double estimateLux(utils::Duration exposureTime,
@@ -29,7 +29,6 @@ public:
const Histogram &yHist) const;
private:
- unsigned int binSize_;
utils::Duration referenceExposureTime_;
double referenceAnalogueGain_;
double referenceDigitalGain_;
diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build
index f2b2f4be..660be940 100644
--- a/src/ipa/libipa/meson.build
+++ b/src/ipa/libipa/meson.build
@@ -3,6 +3,9 @@
libipa_headers = files([
'agc_mean_luminance.h',
'algorithm.h',
+ 'awb_bayes.h',
+ 'awb_grey.h',
+ 'awb.h',
'camera_sensor_helper.h',
'colours.h',
'exposure_mode_helper.h',
@@ -14,12 +17,14 @@ libipa_headers = files([
'lux.h',
'module.h',
'pwl.h',
- 'vector.h',
])
libipa_sources = files([
'agc_mean_luminance.cpp',
'algorithm.cpp',
+ 'awb_bayes.cpp',
+ 'awb_grey.cpp',
+ 'awb.cpp',
'camera_sensor_helper.cpp',
'colours.cpp',
'exposure_mode_helper.cpp',
@@ -31,7 +36,6 @@ libipa_sources = files([
'lux.cpp',
'module.cpp',
'pwl.cpp',
- 'vector.cpp',
])
libipa_includes = include_directories('..')
diff --git a/src/ipa/libipa/pwl.cpp b/src/ipa/libipa/pwl.cpp
index 88fe2022..3fa005ba 100644
--- a/src/ipa/libipa/pwl.cpp
+++ b/src/ipa/libipa/pwl.cpp
@@ -160,6 +160,11 @@ void Pwl::prepend(double x, double y, const double eps)
*/
/**
+ * \fn Pwl::clear()
+ * \brief Clear the piecewise linear function
+ */
+
+/**
* \fn Pwl::size() const
* \brief Retrieve the number of points in the piecewise linear function
* \return The number of points in the piecewise linear function
diff --git a/src/ipa/libipa/pwl.h b/src/ipa/libipa/pwl.h
index d4ec9f4f..c1496c30 100644
--- a/src/ipa/libipa/pwl.h
+++ b/src/ipa/libipa/pwl.h
@@ -12,7 +12,7 @@
#include <utility>
#include <vector>
-#include "vector.h"
+#include "libcamera/internal/vector.h"
namespace libcamera {
@@ -49,6 +49,7 @@ public:
void append(double x, double y, double eps = 1e-6);
bool empty() const { return points_.empty(); }
+ void clear() { points_.clear(); }
size_t size() const { return points_.size(); }
Interval domain() const;
diff --git a/src/ipa/libipa/vector.h b/src/ipa/libipa/vector.h
deleted file mode 100644
index fe33c9d6..00000000
--- a/src/ipa/libipa/vector.h
+++ /dev/null
@@ -1,370 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>
- *
- * Vector and related operations
- */
-#pragma once
-
-#include <algorithm>
-#include <array>
-#include <cmath>
-#include <functional>
-#include <numeric>
-#include <optional>
-#include <ostream>
-
-#include <libcamera/base/log.h>
-#include <libcamera/base/span.h>
-
-#include "libcamera/internal/matrix.h"
-#include "libcamera/internal/yaml_parser.h"
-
-namespace libcamera {
-
-LOG_DECLARE_CATEGORY(Vector)
-
-namespace ipa {
-
-#ifndef __DOXYGEN__
-template<typename T, unsigned int Rows,
- std::enable_if_t<std::is_arithmetic_v<T>> * = nullptr>
-#else
-template<typename T, unsigned int Rows>
-#endif /* __DOXYGEN__ */
-class Vector
-{
-public:
- constexpr Vector() = default;
-
- constexpr explicit Vector(T scalar)
- {
- data_.fill(scalar);
- }
-
- constexpr Vector(const std::array<T, Rows> &data)
- {
- for (unsigned int i = 0; i < Rows; i++)
- data_[i] = data[i];
- }
-
- const T &operator[](size_t i) const
- {
- ASSERT(i < data_.size());
- return data_[i];
- }
-
- T &operator[](size_t i)
- {
- ASSERT(i < data_.size());
- return data_[i];
- }
-
- constexpr Vector<T, Rows> operator-() const
- {
- Vector<T, Rows> ret;
- for (unsigned int i = 0; i < Rows; i++)
- ret[i] = -data_[i];
- return ret;
- }
-
- constexpr Vector operator+(const Vector &other) const
- {
- return apply(*this, other, std::plus<>{});
- }
-
- constexpr Vector operator+(T scalar) const
- {
- return apply(*this, scalar, std::plus<>{});
- }
-
- constexpr Vector operator-(const Vector &other) const
- {
- return apply(*this, other, std::minus<>{});
- }
-
- constexpr Vector operator-(T scalar) const
- {
- return apply(*this, scalar, std::minus<>{});
- }
-
- constexpr Vector operator*(const Vector &other) const
- {
- return apply(*this, other, std::multiplies<>{});
- }
-
- constexpr Vector operator*(T scalar) const
- {
- return apply(*this, scalar, std::multiplies<>{});
- }
-
- constexpr Vector operator/(const Vector &other) const
- {
- return apply(*this, other, std::divides<>{});
- }
-
- constexpr Vector operator/(T scalar) const
- {
- return apply(*this, scalar, std::divides<>{});
- }
-
- Vector &operator+=(const Vector &other)
- {
- return apply(other, [](T a, T b) { return a + b; });
- }
-
- Vector &operator+=(T scalar)
- {
- return apply(scalar, [](T a, T b) { return a + b; });
- }
-
- Vector &operator-=(const Vector &other)
- {
- return apply(other, [](T a, T b) { return a - b; });
- }
-
- Vector &operator-=(T scalar)
- {
- return apply(scalar, [](T a, T b) { return a - b; });
- }
-
- Vector &operator*=(const Vector &other)
- {
- return apply(other, [](T a, T b) { return a * b; });
- }
-
- Vector &operator*=(T scalar)
- {
- return apply(scalar, [](T a, T b) { return a * b; });
- }
-
- Vector &operator/=(const Vector &other)
- {
- return apply(other, [](T a, T b) { return a / b; });
- }
-
- Vector &operator/=(T scalar)
- {
- return apply(scalar, [](T a, T b) { return a / b; });
- }
-
- constexpr Vector min(const Vector &other) const
- {
- return apply(*this, other, [](T a, T b) { return std::min(a, b); });
- }
-
- constexpr Vector min(T scalar) const
- {
- return apply(*this, scalar, [](T a, T b) { return std::min(a, b); });
- }
-
- constexpr Vector max(const Vector &other) const
- {
- return apply(*this, other, [](T a, T b) { return std::max(a, b); });
- }
-
- constexpr Vector max(T scalar) const
- {
- return apply(*this, scalar, [](T a, T b) -> T { return std::max(a, b); });
- }
-
- constexpr T dot(const Vector<T, Rows> &other) const
- {
- T ret = 0;
- for (unsigned int i = 0; i < Rows; i++)
- ret += data_[i] * other[i];
- return ret;
- }
-
-#ifndef __DOXYGEN__
- template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 1>>
-#endif /* __DOXYGEN__ */
- constexpr const T &x() const { return data_[0]; }
-#ifndef __DOXYGEN__
- template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 2>>
-#endif /* __DOXYGEN__ */
- constexpr const T &y() const { return data_[1]; }
-#ifndef __DOXYGEN__
- template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 3>>
-#endif /* __DOXYGEN__ */
- constexpr const T &z() const { return data_[2]; }
-#ifndef __DOXYGEN__
- template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 1>>
-#endif /* __DOXYGEN__ */
- constexpr T &x() { return data_[0]; }
-#ifndef __DOXYGEN__
- template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 2>>
-#endif /* __DOXYGEN__ */
- constexpr T &y() { return data_[1]; }
-#ifndef __DOXYGEN__
- template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 3>>
-#endif /* __DOXYGEN__ */
- constexpr T &z() { return data_[2]; }
-
-#ifndef __DOXYGEN__
- template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 1>>
-#endif /* __DOXYGEN__ */
- constexpr const T &r() const { return data_[0]; }
-#ifndef __DOXYGEN__
- template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 2>>
-#endif /* __DOXYGEN__ */
- constexpr const T &g() const { return data_[1]; }
-#ifndef __DOXYGEN__
- template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 3>>
-#endif /* __DOXYGEN__ */
- constexpr const T &b() const { return data_[2]; }
-#ifndef __DOXYGEN__
- template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 1>>
-#endif /* __DOXYGEN__ */
- constexpr T &r() { return data_[0]; }
-#ifndef __DOXYGEN__
- template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 2>>
-#endif /* __DOXYGEN__ */
- constexpr T &g() { return data_[1]; }
-#ifndef __DOXYGEN__
- template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 3>>
-#endif /* __DOXYGEN__ */
- constexpr T &b() { return data_[2]; }
-
- constexpr double length2() const
- {
- double ret = 0;
- for (unsigned int i = 0; i < Rows; i++)
- ret += data_[i] * data_[i];
- return ret;
- }
-
- constexpr double length() const
- {
- return std::sqrt(length2());
- }
-
- template<typename R = T>
- constexpr R sum() const
- {
- return std::accumulate(data_.begin(), data_.end(), R{});
- }
-
-private:
- template<class BinaryOp>
- static constexpr Vector apply(const Vector &lhs, const Vector &rhs, BinaryOp op)
- {
- Vector result;
- std::transform(lhs.data_.begin(), lhs.data_.end(),
- rhs.data_.begin(), result.data_.begin(),
- op);
-
- return result;
- }
-
- template<class BinaryOp>
- static constexpr Vector apply(const Vector &lhs, T rhs, BinaryOp op)
- {
- Vector result;
- std::transform(lhs.data_.begin(), lhs.data_.end(),
- result.data_.begin(),
- [&op, rhs](T v) { return op(v, rhs); });
-
- return result;
- }
-
- template<class BinaryOp>
- Vector &apply(const Vector &other, BinaryOp op)
- {
- auto itOther = other.data_.begin();
- std::for_each(data_.begin(), data_.end(),
- [&op, &itOther](T &v) { v = op(v, *itOther++); });
-
- return *this;
- }
-
- template<class BinaryOp>
- Vector &apply(T scalar, BinaryOp op)
- {
- std::for_each(data_.begin(), data_.end(),
- [&op, scalar](T &v) { v = op(v, scalar); });
-
- return *this;
- }
-
- std::array<T, Rows> data_;
-};
-
-template<typename T>
-using RGB = Vector<T, 3>;
-
-template<typename T, unsigned int Rows, unsigned int Cols>
-Vector<T, Rows> operator*(const Matrix<T, Rows, Cols> &m, const Vector<T, Cols> &v)
-{
- Vector<T, Rows> result;
-
- for (unsigned int i = 0; i < Rows; i++) {
- T sum = 0;
- for (unsigned int j = 0; j < Cols; j++)
- sum += m[i][j] * v[j];
- result[i] = sum;
- }
-
- return result;
-}
-
-template<typename T, unsigned int Rows>
-bool operator==(const Vector<T, Rows> &lhs, const Vector<T, Rows> &rhs)
-{
- for (unsigned int i = 0; i < Rows; i++) {
- if (lhs[i] != rhs[i])
- return false;
- }
-
- return true;
-}
-
-template<typename T, unsigned int Rows>
-bool operator!=(const Vector<T, Rows> &lhs, const Vector<T, Rows> &rhs)
-{
- return !(lhs == rhs);
-}
-
-#ifndef __DOXYGEN__
-bool vectorValidateYaml(const YamlObject &obj, unsigned int size);
-#endif /* __DOXYGEN__ */
-
-} /* namespace ipa */
-
-#ifndef __DOXYGEN__
-template<typename T, unsigned int Rows>
-std::ostream &operator<<(std::ostream &out, const ipa::Vector<T, Rows> &v)
-{
- out << "Vector { ";
- for (unsigned int i = 0; i < Rows; i++) {
- out << v[i];
- out << ((i + 1 < Rows) ? ", " : " ");
- }
- out << " }";
-
- return out;
-}
-
-template<typename T, unsigned int Rows>
-struct YamlObject::Getter<ipa::Vector<T, Rows>> {
- std::optional<ipa::Vector<T, Rows>> get(const YamlObject &obj) const
- {
- if (!ipa::vectorValidateYaml(obj, Rows))
- return std::nullopt;
-
- ipa::Vector<T, Rows> vector;
-
- unsigned int i = 0;
- for (const YamlObject &entry : obj.asList()) {
- const auto value = entry.get<T>();
- if (!value)
- return std::nullopt;
- vector[i++] = *value;
- }
-
- return vector;
- }
-};
-#endif /* __DOXYGEN__ */
-
-} /* namespace libcamera */
diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
index 1680669c..5a3ba013 100644
--- a/src/ipa/rkisp1/algorithms/agc.cpp
+++ b/src/ipa/rkisp1/algorithms/agc.cpp
@@ -149,13 +149,13 @@ int Agc::init(IPAContext &context, const YamlObject &tuningData)
return ret;
context.ctrlMap[&controls::ExposureTimeMode] =
- ControlInfo(static_cast<int32_t>(controls::ExposureTimeModeAuto),
- static_cast<int32_t>(controls::ExposureTimeModeManual),
- static_cast<int32_t>(controls::ExposureTimeModeAuto));
+ ControlInfo({ { ControlValue(controls::ExposureTimeModeAuto),
+ ControlValue(controls::ExposureTimeModeManual) } },
+ ControlValue(controls::ExposureTimeModeAuto));
context.ctrlMap[&controls::AnalogueGainMode] =
- ControlInfo(static_cast<int32_t>(controls::AnalogueGainModeAuto),
- static_cast<int32_t>(controls::AnalogueGainModeManual),
- static_cast<int32_t>(controls::AnalogueGainModeAuto));
+ ControlInfo({ { ControlValue(controls::AnalogueGainModeAuto),
+ ControlValue(controls::AnalogueGainModeManual) } },
+ ControlValue(controls::AnalogueGainModeAuto));
/* \todo Move this to the Camera class */
context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true, true);
context.ctrlMap.merge(controls());
@@ -188,12 +188,10 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
context.activeState.agc.meteringMode =
static_cast<controls::AeMeteringModeEnum>(meteringModes_.begin()->first);
- /*
- * \todo This should probably come from FrameDurationLimits instead,
- * except it's computed in the IPA and not here so we'd have to
- * recompute it.
- */
- context.activeState.agc.maxFrameDuration = context.configuration.sensor.maxExposureTime;
+ /* Limit the frame duration to match current initialisation */
+ ControlInfo &frameDurationLimits = context.ctrlMap[&controls::FrameDurationLimits];
+ context.activeState.agc.minFrameDuration = std::chrono::microseconds(frameDurationLimits.min().get<int64_t>());
+ context.activeState.agc.maxFrameDuration = std::chrono::microseconds(frameDurationLimits.max().get<int64_t>());
/*
* Define the measurement window for AGC as a centered rectangle
@@ -310,10 +308,21 @@ void Agc::queueRequest(IPAContext &context,
const auto &frameDurationLimits = controls.get(controls::FrameDurationLimits);
if (frameDurationLimits) {
- utils::Duration maxFrameDuration =
- std::chrono::milliseconds((*frameDurationLimits).back());
- agc.maxFrameDuration = maxFrameDuration;
+ /* Limit the control value to the limits in ControlInfo */
+ ControlInfo &limits = context.ctrlMap[&controls::FrameDurationLimits];
+ int64_t minFrameDuration =
+ std::clamp((*frameDurationLimits).front(),
+ limits.min().get<int64_t>(),
+ limits.max().get<int64_t>());
+ int64_t maxFrameDuration =
+ std::clamp((*frameDurationLimits).back(),
+ limits.min().get<int64_t>(),
+ limits.max().get<int64_t>());
+
+ agc.minFrameDuration = std::chrono::microseconds(minFrameDuration);
+ agc.maxFrameDuration = std::chrono::microseconds(maxFrameDuration);
}
+ frameContext.agc.minFrameDuration = agc.minFrameDuration;
frameContext.agc.maxFrameDuration = agc.maxFrameDuration;
}
@@ -390,6 +399,7 @@ void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext,
* frameContext.sensor.exposure;
metadata.set(controls::AnalogueGain, frameContext.sensor.gain);
metadata.set(controls::ExposureTime, exposureTime.get<std::micro>());
+ metadata.set(controls::FrameDuration, frameContext.agc.frameDuration.get<std::micro>());
metadata.set(controls::ExposureTimeMode,
frameContext.agc.autoExposureEnabled
? controls::ExposureTimeModeAuto
@@ -399,13 +409,6 @@ void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext,
? controls::AnalogueGainModeAuto
: controls::AnalogueGainModeManual);
- /* \todo Use VBlank value calculated from each frame exposure. */
- uint32_t vTotal = context.configuration.sensor.size.height
- + context.configuration.sensor.defVBlank;
- utils::Duration frameDuration = context.configuration.sensor.lineDuration
- * vTotal;
- metadata.set(controls::FrameDuration, frameDuration.get<std::micro>());
-
metadata.set(controls::AeMeteringMode, frameContext.agc.meteringMode);
metadata.set(controls::AeExposureMode, frameContext.agc.exposureMode);
metadata.set(controls::AeConstraintMode, frameContext.agc.constraintMode);
@@ -448,6 +451,27 @@ double Agc::estimateLuminance(double gain) const
}
/**
+ * \brief Process frame duration and compute vblank
+ * \param[in] context The shared IPA context
+ * \param[in] frameContext The current frame context
+ * \param[in] frameDuration The target frame duration
+ *
+ * Compute and populate vblank from the target frame duration.
+ */
+void Agc::processFrameDuration(IPAContext &context,
+ IPAFrameContext &frameContext,
+ utils::Duration frameDuration)
+{
+ IPACameraSensorInfo &sensorInfo = context.sensorInfo;
+ utils::Duration lineDuration = context.configuration.sensor.lineDuration;
+
+ frameContext.agc.vblank = (frameDuration / lineDuration) - sensorInfo.outputSize.height;
+
+ /* Update frame duration accounting for line length quantization. */
+ frameContext.agc.frameDuration = (sensorInfo.outputSize.height + frameContext.agc.vblank) * lineDuration;
+}
+
+/**
* \brief Process RkISP1 statistics, and run AGC operations
* \param[in] context The shared IPA context
* \param[in] frame The frame context sequence number
@@ -463,16 +487,20 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
ControlList &metadata)
{
if (!stats) {
+ processFrameDuration(context, frameContext,
+ frameContext.agc.minFrameDuration);
fillMetadata(context, frameContext, metadata);
return;
}
-
+
if (!(stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP)) {
fillMetadata(context, frameContext, metadata);
LOG(RkISP1Agc, Error) << "AUTOEXP data is missing in statistics";
return;
}
+ const utils::Duration &lineDuration = context.configuration.sensor.lineDuration;
+
/*
* \todo Verify that the exposure and gain applied by the sensor for
* this frame match what has been requested. This isn't a hard
@@ -522,8 +550,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
* The Agc algorithm needs to know the effective exposure value that was
* applied to the sensor when the statistics were collected.
*/
- utils::Duration exposureTime = context.configuration.sensor.lineDuration
- * frameContext.sensor.exposure;
+ utils::Duration exposureTime = lineDuration * frameContext.sensor.exposure;
double analogueGain = frameContext.sensor.gain;
utils::Duration effectiveExposureValue = exposureTime * analogueGain;
@@ -540,10 +567,16 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
IPAActiveState &activeState = context.activeState;
/* Update the estimated exposure and gain. */
- activeState.agc.automatic.exposure = newExposureTime
- / context.configuration.sensor.lineDuration;
+ activeState.agc.automatic.exposure = newExposureTime / lineDuration;
activeState.agc.automatic.gain = aGain;
+ /*
+ * Expand the target frame duration so that we do not run faster than
+ * the minimum frame duration when we have short exposures.
+ */
+ processFrameDuration(context, frameContext,
+ std::max(frameContext.agc.minFrameDuration, newExposureTime));
+
fillMetadata(context, frameContext, metadata);
expMeans_ = {};
}
diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h
index aa86f2c5..62bcde99 100644
--- a/src/ipa/rkisp1/algorithms/agc.h
+++ b/src/ipa/rkisp1/algorithms/agc.h
@@ -50,6 +50,9 @@ private:
void fillMetadata(IPAContext &context, IPAFrameContext &frameContext,
ControlList &metadata);
double estimateLuminance(double gain) const override;
+ void processFrameDuration(IPAContext &context,
+ IPAFrameContext &frameContext,
+ utils::Duration frameDuration);
Span<const uint8_t> expMeans_;
diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp
index cffaa06a..eafe9308 100644
--- a/src/ipa/rkisp1/algorithms/awb.cpp
+++ b/src/ipa/rkisp1/algorithms/awb.cpp
@@ -16,6 +16,8 @@
#include <libcamera/ipa/core_ipa_interface.h>
+#include "libipa/awb_bayes.h"
+#include "libipa/awb_grey.h"
#include "libipa/colours.h"
/**
@@ -40,6 +42,40 @@ constexpr int32_t kDefaultColourTemperature = 5000;
/* Minimum mean value below which AWB can't operate. */
constexpr double kMeanMinThreshold = 2.0;
+class RkISP1AwbStats final : public AwbStats
+{
+public:
+ RkISP1AwbStats(const RGB<double> &rgbMeans)
+ : rgbMeans_(rgbMeans)
+ {
+ rg_ = rgbMeans_.r() / rgbMeans_.g();
+ bg_ = rgbMeans_.b() / rgbMeans_.g();
+ }
+
+ double computeColourError(const RGB<double> &gains) const override
+ {
+ /*
+ * Compute the sum of the squared colour error (non-greyness) as
+ * it appears in the log likelihood equation.
+ */
+ double deltaR = gains.r() * rg_ - 1.0;
+ double deltaB = gains.b() * bg_ - 1.0;
+ double delta2 = deltaR * deltaR + deltaB * deltaB;
+
+ return delta2;
+ }
+
+ RGB<double> rgbMeans() const override
+ {
+ return rgbMeans_;
+ }
+
+private:
+ RGB<double> rgbMeans_;
+ double rg_;
+ double bg_;
+};
+
Awb::Awb()
: rgbMode_(false)
{
@@ -55,15 +91,29 @@ int Awb::init(IPAContext &context, const YamlObject &tuningData)
kMaxColourTemperature,
kDefaultColourTemperature);
- Interpolator<Vector<double, 2>> gainCurve;
- int ret = gainCurve.readYaml(tuningData["colourGains"], "ct", "gains");
- if (ret < 0)
- LOG(RkISP1Awb, Warning)
- << "Failed to parse 'colourGains' "
- << "parameter from tuning file; "
- << "manual colour temperature will not work properly";
- else
- colourGainCurve_ = gainCurve;
+ if (!tuningData.contains("algorithm"))
+ LOG(RkISP1Awb, Info) << "No AWB algorithm specified."
+ << " Default to grey world";
+
+ auto mode = tuningData["algorithm"].get<std::string>("grey");
+ if (mode == "grey") {
+ awbAlgo_ = std::make_unique<AwbGrey>();
+ } else if (mode == "bayes") {
+ awbAlgo_ = std::make_unique<AwbBayes>();
+ } else {
+ LOG(RkISP1Awb, Error) << "Unknown AWB algorithm: " << mode;
+ return -EINVAL;
+ }
+ LOG(RkISP1Awb, Debug) << "Using AWB algorithm: " << mode;
+
+ int ret = awbAlgo_->init(tuningData);
+ if (ret) {
+ LOG(RkISP1Awb, Error) << "Failed to init AWB algorithm";
+ return ret;
+ }
+
+ const auto &src = awbAlgo_->controls();
+ cmap.insert(src.begin(), src.end());
return 0;
}
@@ -75,7 +125,8 @@ int Awb::configure(IPAContext &context,
const IPACameraSensorInfo &configInfo)
{
context.activeState.awb.gains.manual = RGB<double>{ 1.0 };
- context.activeState.awb.gains.automatic = RGB<double>{ 1.0 };
+ context.activeState.awb.gains.automatic =
+ awbAlgo_->gainsFromColourTemperature(kDefaultColourTemperature);
context.activeState.awb.autoEnabled = true;
context.activeState.awb.temperatureK = kDefaultColourTemperature;
@@ -111,6 +162,8 @@ void Awb::queueRequest(IPAContext &context,
<< (*awbEnable ? "Enabling" : "Disabling") << " AWB";
}
+ awbAlgo_->handleControls(controls);
+
frameContext.awb.autoEnabled = awb.autoEnabled;
if (awb.autoEnabled)
@@ -123,15 +176,15 @@ void Awb::queueRequest(IPAContext &context,
awb.gains.manual.r() = (*colourGains)[0];
awb.gains.manual.b() = (*colourGains)[1];
/*
- * \todo: Colour temperature reported in metadata is now
+ * \todo Colour temperature reported in metadata is now
* incorrect, as we can't deduce the temperature from the gains.
* This will be fixed with the bayes AWB algorithm.
*/
update = true;
- } else if (colourTemperature && colourGainCurve_) {
- const auto &gains = colourGainCurve_->getInterpolated(*colourTemperature);
- awb.gains.manual.r() = gains[0];
- awb.gains.manual.b() = gains[1];
+ } else if (colourTemperature) {
+ const auto &gains = awbAlgo_->gainsFromColourTemperature(*colourTemperature);
+ awb.gains.manual.r() = gains.r();
+ awb.gains.manual.b() = gains.b();
awb.temperatureK = *colourTemperature;
update = true;
}
@@ -226,10 +279,7 @@ void Awb::process(IPAContext &context,
const rkisp1_stat_buffer *stats,
ControlList &metadata)
{
- const rkisp1_cif_isp_stat *params = &stats->params;
- const rkisp1_cif_isp_awb_stat *awb = &params->awb;
IPAActiveState &activeState = context.activeState;
- RGB<double> rgbMeans;
metadata.set(controls::AwbEnable, frameContext.awb.autoEnabled);
metadata.set(controls::ColourGains, {
@@ -243,10 +293,57 @@ void Awb::process(IPAContext &context,
return;
}
+ const rkisp1_cif_isp_stat *params = &stats->params;
+ const rkisp1_cif_isp_awb_stat *awb = &params->awb;
+
+ RGB<double> rgbMeans = calculateRgbMeans(frameContext, awb);
+
+ /*
+ * If the means are too small we don't have enough information to
+ * meaningfully calculate gains. Freeze the algorithm in that case.
+ */
+ if (rgbMeans.r() < kMeanMinThreshold && rgbMeans.g() < kMeanMinThreshold &&
+ rgbMeans.b() < kMeanMinThreshold)
+ return;
+
+ RkISP1AwbStats awbStats{ rgbMeans };
+ AwbResult awbResult = awbAlgo_->calculateAwb(awbStats, frameContext.lux.lux);
+
+ activeState.awb.temperatureK = awbResult.colourTemperature;
+
+ /* Metadata shall contain the up to date measurement */
+ metadata.set(controls::ColourTemperature, activeState.awb.temperatureK);
+
+ /*
+ * Clamp the gain values to the hardware, which expresses gains as Q2.8
+ * unsigned integer values. Set the minimum just above zero to avoid
+ * divisions by zero when computing the raw means in subsequent
+ * iterations.
+ */
+ awbResult.gains = awbResult.gains.max(1.0 / 256).min(1023.0 / 256);
+
+ /* Filter the values to avoid oscillations. */
+ double speed = 0.2;
+ awbResult.gains = awbResult.gains * speed +
+ activeState.awb.gains.automatic * (1 - speed);
+
+ activeState.awb.gains.automatic = awbResult.gains;
+
+ LOG(RkISP1Awb, Debug)
+ << std::showpoint
+ << "Means " << rgbMeans << ", gains "
+ << activeState.awb.gains.automatic << ", temp "
+ << activeState.awb.temperatureK << "K";
+}
+
+RGB<double> Awb::calculateRgbMeans(const IPAFrameContext &frameContext, const rkisp1_cif_isp_awb_stat *awb) const
+{
+ Vector<double, 3> rgbMeans;
+
if (rgbMode_) {
rgbMeans = {{
- static_cast<double>(awb->awb_mean[0].mean_y_or_g),
static_cast<double>(awb->awb_mean[0].mean_cr_or_r),
+ static_cast<double>(awb->awb_mean[0].mean_y_or_g),
static_cast<double>(awb->awb_mean[0].mean_cb_or_b)
}};
} else {
@@ -301,46 +398,7 @@ void Awb::process(IPAContext &context,
*/
rgbMeans /= frameContext.awb.gains;
- /*
- * If the means are too small we don't have enough information to
- * meaningfully calculate gains. Freeze the algorithm in that case.
- */
- if (rgbMeans.r() < kMeanMinThreshold && rgbMeans.g() < kMeanMinThreshold &&
- rgbMeans.b() < kMeanMinThreshold)
- return;
-
- activeState.awb.temperatureK = estimateCCT(rgbMeans);
-
- /*
- * 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
- * divisor to a minimum value of 1.0.
- */
- RGB<double> gains({
- rgbMeans.g() / std::max(rgbMeans.r(), 1.0),
- 1.0,
- rgbMeans.g() / std::max(rgbMeans.b(), 1.0)
- });
-
- /*
- * Clamp the gain values to the hardware, which expresses gains as Q2.8
- * unsigned integer values. Set the minimum just above zero to avoid
- * divisions by zero when computing the raw means in subsequent
- * iterations.
- */
- gains = gains.max(1.0 / 256).min(1023.0 / 256);
-
- /* Filter the values to avoid oscillations. */
- double speed = 0.2;
- gains = gains * speed + activeState.awb.gains.automatic * (1 - speed);
-
- activeState.awb.gains.automatic = gains;
-
- LOG(RkISP1Awb, Debug)
- << std::showpoint
- << "Means " << rgbMeans << ", gains "
- << activeState.awb.gains.automatic << ", temp "
- << activeState.awb.temperatureK << "K";
+ return rgbMeans;
}
REGISTER_IPA_ALGORITHM(Awb, "Awb")
diff --git a/src/ipa/rkisp1/algorithms/awb.h b/src/ipa/rkisp1/algorithms/awb.h
index e4248048..7e6c3862 100644
--- a/src/ipa/rkisp1/algorithms/awb.h
+++ b/src/ipa/rkisp1/algorithms/awb.h
@@ -9,8 +9,10 @@
#include <optional>
+#include "libcamera/internal/vector.h"
+
+#include "libipa/awb.h"
#include "libipa/interpolator.h"
-#include "libipa/vector.h"
#include "algorithm.h"
@@ -38,7 +40,11 @@ public:
ControlList &metadata) override;
private:
- std::optional<Interpolator<Vector<double, 2>>> colourGainCurve_;
+ RGB<double> calculateRgbMeans(const IPAFrameContext &frameContext,
+ const rkisp1_cif_isp_awb_stat *awb) const;
+
+ std::unique_ptr<AwbAlgorithm> awbAlgo_;
+
bool rgbMode_;
};
diff --git a/src/ipa/rkisp1/algorithms/ccm.cpp b/src/ipa/rkisp1/algorithms/ccm.cpp
index e2b5cf4d..eb8ca39e 100644
--- a/src/ipa/rkisp1/algorithms/ccm.cpp
+++ b/src/ipa/rkisp1/algorithms/ccm.cpp
@@ -120,12 +120,7 @@ void Ccm::process([[maybe_unused]] IPAContext &context,
[[maybe_unused]] const rkisp1_stat_buffer *stats,
ControlList &metadata)
{
- float m[9];
- for (unsigned int i = 0; i < 3; i++) {
- for (unsigned int j = 0; j < 3; j++)
- m[i * 3 + j] = frameContext.ccm.ccm[i][j];
- }
- metadata.set(controls::ColourCorrectionMatrix, m);
+ metadata.set(controls::ColourCorrectionMatrix, frameContext.ccm.ccm.data());
}
REGISTER_IPA_ALGORITHM(Ccm, "Ccm")
diff --git a/src/ipa/rkisp1/algorithms/lux.cpp b/src/ipa/rkisp1/algorithms/lux.cpp
index b0f74963..a467767e 100644
--- a/src/ipa/rkisp1/algorithms/lux.cpp
+++ b/src/ipa/rkisp1/algorithms/lux.cpp
@@ -33,12 +33,8 @@ namespace ipa::rkisp1::algorithms {
/**
* \brief Construct an rkisp1 Lux algo module
- *
- * The Lux helper is initialized to 65535 as that is the max bin count on the
- * rkisp1.
*/
Lux::Lux()
- : lux_(65535)
{
}
diff --git a/src/ipa/rkisp1/ipa_context.cpp b/src/ipa/rkisp1/ipa_context.cpp
index 261c0472..99611bd5 100644
--- a/src/ipa/rkisp1/ipa_context.cpp
+++ b/src/ipa/rkisp1/ipa_context.cpp
@@ -180,6 +180,9 @@ namespace libcamera::ipa::rkisp1 {
* \var IPAActiveState::agc.meteringMode
* \brief Metering mode as set by the AeMeteringMode control
*
+ * \var IPAActiveState::agc.minFrameDuration
+ * \brief Minimum frame duration as set by the FrameDurationLimits control
+ *
* \var IPAActiveState::agc.maxFrameDuration
* \brief Maximum frame duration as set by the FrameDurationLimits control
*/
@@ -282,7 +285,9 @@ namespace libcamera::ipa::rkisp1 {
* \brief Automatic Gain Control parameters for this frame
*
* The exposure and gain are provided by the AGC algorithm, and are to be
- * applied to the sensor in order to take effect for this frame.
+ * applied to the sensor in order to take effect for this frame. Additionally
+ * the vertical blanking period is determined to maintain a consistent frame
+ * rate matched to the FrameDurationLimits as set by the user.
*
* \var IPAFrameContext::agc.exposure
* \brief Exposure time expressed as a number of lines computed by the algorithm
@@ -292,6 +297,9 @@ namespace libcamera::ipa::rkisp1 {
*
* The gain should be adapted to the sensor specific gain code before applying.
*
+ * \var IPAFrameContext::agc.vblank
+ * \brief Vertical blanking parameter computed by the algorithm
+ *
* \var IPAFrameContext::agc.autoExposureEnabled
* \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control
*
@@ -307,9 +315,15 @@ namespace libcamera::ipa::rkisp1 {
* \var IPAFrameContext::agc.meteringMode
* \brief Metering mode as set by the AeMeteringMode control
*
+ * \var IPAFrameContext::agc.minFrameDuration
+ * \brief Minimum frame duration as set by the FrameDurationLimits control
+ *
* \var IPAFrameContext::agc.maxFrameDuration
* \brief Maximum frame duration as set by the FrameDurationLimits control
*
+ * \var IPAFrameContext::agc.frameDuration
+ * \brief The actual FrameDuration used by the algorithm for the frame
+ *
* \var IPAFrameContext::agc.updateMetering
* \brief Indicate if new ISP AGC metering parameters need to be applied
*
diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
index 5d5b79fa..474f7036 100644
--- a/src/ipa/rkisp1/ipa_context.h
+++ b/src/ipa/rkisp1/ipa_context.h
@@ -22,10 +22,10 @@
#include "libcamera/internal/debug_controls.h"
#include "libcamera/internal/matrix.h"
+#include "libcamera/internal/vector.h"
#include <libipa/camera_sensor_helper.h>
#include <libipa/fc_queue.h>
-#include <libipa/vector.h>
namespace libcamera {
@@ -84,6 +84,7 @@ struct IPAActiveState {
controls::AeConstraintModeEnum constraintMode;
controls::AeExposureModeEnum exposureMode;
controls::AeMeteringModeEnum meteringMode;
+ utils::Duration minFrameDuration;
utils::Duration maxFrameDuration;
} agc;
@@ -125,12 +126,15 @@ struct IPAFrameContext : public FrameContext {
struct {
uint32_t exposure;
double gain;
+ uint32_t vblank;
bool autoExposureEnabled;
bool autoGainEnabled;
controls::AeConstraintModeEnum constraintMode;
controls::AeExposureModeEnum exposureMode;
controls::AeMeteringModeEnum meteringMode;
+ utils::Duration minFrameDuration;
utils::Duration maxFrameDuration;
+ utils::Duration frameDuration;
bool updateMetering;
bool autoExposureModeChange;
bool autoGainModeChange;
diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp
index 2ffdd99b..7547d2f2 100644
--- a/src/ipa/rkisp1/rkisp1.cpp
+++ b/src/ipa/rkisp1/rkisp1.cpp
@@ -435,9 +435,9 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo,
frameDurations[i] = frameSize / (sensorInfo.pixelRate / 1000000U);
}
- ctrlMap[&controls::FrameDurationLimits] = ControlInfo(frameDurations[0],
- frameDurations[1],
- frameDurations[2]);
+ /* \todo Move this (and other agc-related controls) to agc */
+ context_.ctrlMap[&controls::FrameDurationLimits] =
+ ControlInfo(frameDurations[0], frameDurations[1], frameDurations[2]);
ctrlMap.insert(context_.ctrlMap.begin(), context_.ctrlMap.end());
*ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls);
@@ -453,10 +453,12 @@ void IPARkISP1::setControls(unsigned int frame)
IPAFrameContext &frameContext = context_.frameContexts.get(frame);
uint32_t exposure = frameContext.agc.exposure;
uint32_t gain = context_.camHelper->gainCode(frameContext.agc.gain);
+ uint32_t vblank = frameContext.agc.vblank;
ControlList ctrls(sensorControls_);
ctrls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(exposure));
ctrls.set(V4L2_CID_ANALOGUE_GAIN, static_cast<int32_t>(gain));
+ ctrls.set(V4L2_CID_VBLANK, static_cast<int32_t>(vblank));
setSensorControls.emit(frame, ctrls);
}
diff --git a/src/ipa/rpi/common/ipa_base.cpp b/src/ipa/rpi/common/ipa_base.cpp
index bd3c2200..6734c32e 100644
--- a/src/ipa/rpi/common/ipa_base.cpp
+++ b/src/ipa/rpi/common/ipa_base.cpp
@@ -61,12 +61,13 @@ const ControlInfoMap::Map ipaControls{
ControlInfo(static_cast<int32_t>(controls::ExposureTimeModeAuto),
static_cast<int32_t>(controls::ExposureTimeModeManual),
static_cast<int32_t>(controls::ExposureTimeModeAuto)) },
- { &controls::ExposureTime, ControlInfo(0, 66666) },
+ { &controls::ExposureTime,
+ ControlInfo(1, 66666, static_cast<int32_t>(defaultExposureTime.get<std::micro>())) },
{ &controls::AnalogueGainMode,
ControlInfo(static_cast<int32_t>(controls::AnalogueGainModeAuto),
static_cast<int32_t>(controls::AnalogueGainModeManual),
static_cast<int32_t>(controls::AnalogueGainModeAuto)) },
- { &controls::AnalogueGain, ControlInfo(1.0f, 16.0f) },
+ { &controls::AnalogueGain, ControlInfo(1.0f, 16.0f, 1.0f) },
{ &controls::AeMeteringMode, ControlInfo(controls::AeMeteringModeValues) },
{ &controls::AeConstraintMode, ControlInfo(controls::AeConstraintModeValues) },
{ &controls::AeExposureMode, ControlInfo(controls::AeExposureModeValues) },
@@ -80,7 +81,9 @@ const ControlInfoMap::Map ipaControls{
{ &controls::HdrMode, ControlInfo(controls::HdrModeValues) },
{ &controls::Sharpness, ControlInfo(0.0f, 16.0f, 1.0f) },
{ &controls::ScalerCrop, ControlInfo(Rectangle{}, Rectangle(65535, 65535, 65535, 65535), Rectangle{}) },
- { &controls::FrameDurationLimits, ControlInfo(INT64_C(33333), INT64_C(120000)) },
+ { &controls::FrameDurationLimits,
+ ControlInfo(INT64_C(33333), INT64_C(120000),
+ static_cast<int64_t>(defaultMinFrameDuration.get<std::micro>())) },
{ &controls::draft::NoiseReductionMode, ControlInfo(controls::draft::NoiseReductionModeValues) },
{ &controls::rpi::StatsOutputEnable, ControlInfo(false, true, false) },
};
@@ -259,15 +262,18 @@ int32_t IpaBase::configure(const IPACameraSensorInfo &sensorInfo, const ConfigPa
ControlInfoMap::Map ctrlMap = ipaControls;
ctrlMap[&controls::FrameDurationLimits] =
ControlInfo(static_cast<int64_t>(mode_.minFrameDuration.get<std::micro>()),
- static_cast<int64_t>(mode_.maxFrameDuration.get<std::micro>()));
+ static_cast<int64_t>(mode_.maxFrameDuration.get<std::micro>()),
+ static_cast<int64_t>(defaultMinFrameDuration.get<std::micro>()));
ctrlMap[&controls::AnalogueGain] =
ControlInfo(static_cast<float>(mode_.minAnalogueGain),
- static_cast<float>(mode_.maxAnalogueGain));
+ static_cast<float>(mode_.maxAnalogueGain),
+ static_cast<float>(defaultAnalogueGain));
ctrlMap[&controls::ExposureTime] =
ControlInfo(static_cast<int32_t>(mode_.minExposureTime.get<std::micro>()),
- static_cast<int32_t>(mode_.maxExposureTime.get<std::micro>()));
+ static_cast<int32_t>(mode_.maxExposureTime.get<std::micro>()),
+ static_cast<int32_t>(defaultExposureTime.get<std::micro>()));
/* Declare colour processing related controls for non-mono sensors. */
if (!monoSensor_)
diff --git a/src/ipa/rpi/controller/rpi/agc_channel.cpp b/src/ipa/rpi/controller/rpi/agc_channel.cpp
index e79184b7..a5562760 100644
--- a/src/ipa/rpi/controller/rpi/agc_channel.cpp
+++ b/src/ipa/rpi/controller/rpi/agc_channel.cpp
@@ -12,8 +12,9 @@
#include <libcamera/base/log.h>
+#include "libcamera/internal/vector.h"
+
#include "libipa/colours.h"
-#include "libipa/vector.h"
#include "../awb_status.h"
#include "../device_status.h"
@@ -700,7 +701,7 @@ static double computeInitialY(StatisticsPtr &stats, AwbStatus const &awb,
* Note that the weights are applied by the IPA to the statistics directly,
* before they are given to us here.
*/
- ipa::RGB<double> sum{ 0.0 };
+ RGB<double> sum{ 0.0 };
double pixelSum = 0;
for (unsigned int i = 0; i < stats->agcRegions.numRegions(); i++) {
auto &region = stats->agcRegions.get(i);
@@ -716,7 +717,7 @@ static double computeInitialY(StatisticsPtr &stats, AwbStatus const &awb,
/* Factor in the AWB correction if needed. */
if (stats->agcStatsPos == Statistics::AgcStatsPos::PreWb)
- sum *= ipa::RGB<double>{{ awb.gainR, awb.gainR, awb.gainB }};
+ sum *= RGB<double>{ { awb.gainR, awb.gainR, awb.gainB } };
double ySum = ipa::rec601LuminanceFromRGB(sum);
diff --git a/src/libcamera/base/log.cpp b/src/libcamera/base/log.cpp
index 3a656b8f..8bf3e1da 100644
--- a/src/libcamera/base/log.cpp
+++ b/src/libcamera/base/log.cpp
@@ -8,12 +8,15 @@
#include <libcamera/base/log.h>
#include <array>
+#include <charconv>
+#include <fnmatch.h>
#include <fstream>
#include <iostream>
#include <list>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <string_view>
#include <syslog.h>
#include <time.h>
#include <unordered_set>
@@ -38,8 +41,8 @@
* The levels are configurable through the LIBCAMERA_LOG_LEVELS environment
* variable that contains a comma-separated list of 'category:level' pairs.
*
- * The category names are strings and can include a wildcard ('*') character at
- * the end to match multiple categories.
+ * The category names are strings and can include a wildcard ('*') character to
+ * match multiple categories.
*
* The level are either numeric values, or strings containing the log level
* name. The available log levels are DEBUG, INFO, WARN, ERROR and FATAL. Log
@@ -311,15 +314,15 @@ private:
void parseLogFile();
void parseLogLevels();
- static LogSeverity parseLogLevel(const std::string &level);
+ static LogSeverity parseLogLevel(std::string_view level);
friend LogCategory;
- void registerCategory(LogCategory *category);
- LogCategory *findCategory(const char *name) const;
+ LogCategory *findOrCreateCategory(std::string_view name);
static bool destroyed_;
- std::vector<LogCategory *> categories_;
+ Mutex mutex_;
+ std::vector<std::unique_ptr<LogCategory>> categories_ LIBCAMERA_TSA_GUARDED_BY(mutex_);
std::list<std::pair<std::string, LogSeverity>> levels_;
std::shared_ptr<LogOutput> output_;
@@ -436,9 +439,6 @@ void logSetLevel(const char *category, const char *level)
Logger::~Logger()
{
destroyed_ = true;
-
- for (LogCategory *category : categories_)
- delete category;
}
/**
@@ -569,7 +569,9 @@ void Logger::logSetLevel(const char *category, const char *level)
if (severity == LogInvalid)
return;
- for (LogCategory *c : categories_) {
+ MutexLocker locker(mutex_);
+
+ for (const auto &c : categories_) {
if (c->name() == category) {
c->setSeverity(severity);
break;
@@ -640,17 +642,17 @@ void Logger::parseLogLevels()
if (!len)
continue;
- std::string category;
- std::string level;
+ std::string_view category;
+ std::string_view level;
const char *colon = static_cast<const char *>(memchr(pair, ':', len));
if (!colon) {
/* 'x' is a shortcut for '*:x'. */
category = "*";
- level = std::string(pair, len);
+ level = std::string_view(pair, len);
} else {
- category = std::string(pair, colon - pair);
- level = std::string(colon + 1, comma - colon - 1);
+ category = std::string_view(pair, colon - pair);
+ level = std::string_view(colon + 1, comma - colon - 1);
}
/* Both the category and the level must be specified. */
@@ -661,7 +663,7 @@ void Logger::parseLogLevels()
if (severity == LogInvalid)
continue;
- levels_.push_back({ category, severity });
+ levels_.emplace_back(category, severity);
}
}
@@ -675,7 +677,7 @@ void Logger::parseLogLevels()
*
* \return The log severity, or LogInvalid if the string is invalid
*/
-LogSeverity Logger::parseLogLevel(const std::string &level)
+LogSeverity Logger::parseLogLevel(std::string_view level)
{
static const char *const names[] = {
"DEBUG",
@@ -685,15 +687,13 @@ LogSeverity Logger::parseLogLevel(const std::string &level)
"FATAL",
};
- int severity;
+ unsigned int severity = LogInvalid;
if (std::isdigit(level[0])) {
- char *endptr;
- severity = strtoul(level.c_str(), &endptr, 10);
- if (*endptr != '\0' || severity > LogFatal)
+ auto [end, ec] = std::from_chars(level.data(), level.data() + level.size(), severity);
+ if (ec != std::errc() || *end != '\0' || severity > LogFatal)
severity = LogInvalid;
} else {
- severity = LogInvalid;
for (unsigned int i = 0; i < std::size(names); ++i) {
if (names[i] == level) {
severity = i;
@@ -706,52 +706,28 @@ LogSeverity Logger::parseLogLevel(const std::string &level)
}
/**
- * \brief Register a log category with the logger
- * \param[in] category The log category
- *
- * Log categories must have unique names. It is invalid to call this function
- * if a log category with the same name already exists.
+ * \brief Find an existing log category with the given name or create one
+ * \param[in] name Name of the log category
+ * \return The pointer to the log category found or created
*/
-void Logger::registerCategory(LogCategory *category)
+LogCategory *Logger::findOrCreateCategory(std::string_view name)
{
- categories_.push_back(category);
-
- const std::string &name = category->name();
- for (const std::pair<std::string, LogSeverity> &level : levels_) {
- bool match = true;
-
- for (unsigned int i = 0; i < level.first.size(); ++i) {
- if (level.first[i] == '*')
- break;
-
- if (i >= name.size() ||
- name[i] != level.first[i]) {
- match = false;
- break;
- }
- }
+ MutexLocker locker(mutex_);
- if (match) {
- category->setSeverity(level.second);
- break;
- }
+ for (const auto &category : categories_) {
+ if (category->name() == name)
+ return category.get();
}
-}
-/**
- * \brief Find an existing log category with the given name
- * \param[in] name Name of the log category
- * \return The pointer to the found log category or nullptr if not found
- */
-LogCategory *Logger::findCategory(const char *name) const
-{
- if (auto it = std::find_if(categories_.begin(), categories_.end(),
- [name](auto c) { return c->name() == name; });
- it != categories_.end()) {
- return *it;
+ LogCategory *category = categories_.emplace_back(std::unique_ptr<LogCategory>(new LogCategory(name))).get();
+ const char *categoryName = category->name().c_str();
+
+ for (const auto &[pattern, severity] : levels_) {
+ if (fnmatch(pattern.c_str(), categoryName, FNM_NOESCAPE) == 0)
+ category->setSeverity(severity);
}
- return nullptr;
+ return category;
}
/**
@@ -787,25 +763,16 @@ LogCategory *Logger::findCategory(const char *name) const
*
* \return The pointer to the LogCategory
*/
-LogCategory *LogCategory::create(const char *name)
+LogCategory *LogCategory::create(std::string_view name)
{
- static Mutex mutex_;
- MutexLocker locker(mutex_);
- LogCategory *category = Logger::instance()->findCategory(name);
-
- if (!category) {
- category = new LogCategory(name);
- Logger::instance()->registerCategory(category);
- }
-
- return category;
+ return Logger::instance()->findOrCreateCategory(name);
}
/**
* \brief Construct a log category
* \param[in] name The category name
*/
-LogCategory::LogCategory(const char *name)
+LogCategory::LogCategory(std::string_view name)
: name_(name), severity_(LogSeverity::LogInfo)
{
}
@@ -824,15 +791,12 @@ LogCategory::LogCategory(const char *name)
*/
/**
+ * \fn LogCategory::setSeverity(LogSeverity severity)
* \brief Set the severity of the log category
*
* Messages of severity higher than or equal to the severity of the log category
* are printed, other messages are discarded.
*/
-void LogCategory::setSeverity(LogSeverity severity)
-{
- severity_ = severity;
-}
/**
* \brief Retrieve the default log category
@@ -874,39 +838,12 @@ const LogCategory &LogCategory::defaultCategory()
*/
LogMessage::LogMessage(const char *fileName, unsigned int line,
const LogCategory &category, LogSeverity severity,
- const std::string &prefix)
- : category_(category), severity_(severity), prefix_(prefix)
+ std::string prefix)
+ : category_(category), severity_(severity),
+ timestamp_(utils::clock::now()),
+ fileInfo_(static_cast<std::ostringstream &&>(std::ostringstream() << utils::basename(fileName) << ":" << line).str()),
+ prefix_(std::move(prefix))
{
- init(fileName, line);
-}
-
-/**
- * \brief Move-construct a log message
- * \param[in] other The other message
- *
- * The move constructor is meant to support the _log() functions. Thanks to copy
- * elision it will likely never be called, but C++11 only permits copy elision,
- * it doesn't enforce it unlike C++17. To avoid potential link errors depending
- * on the compiler type and version, and optimization level, the move
- * constructor is defined even if it will likely never be called, and ensures
- * that the destructor of the \a other message will not output anything to the
- * log by setting the severity to LogInvalid.
- */
-LogMessage::LogMessage(LogMessage &&other)
- : msgStream_(std::move(other.msgStream_)), category_(other.category_),
- severity_(other.severity_)
-{
- other.severity_ = LogInvalid;
-}
-
-void LogMessage::init(const char *fileName, unsigned int line)
-{
- /* Log the timestamp, severity and file information. */
- timestamp_ = utils::clock::now();
-
- std::ostringstream ossFileInfo;
- ossFileInfo << utils::basename(fileName) << ":" << line;
- fileInfo_ = ossFileInfo.str();
}
LogMessage::~LogMessage()
diff --git a/src/libcamera/base/object.cpp b/src/libcamera/base/object.cpp
index 745d2565..37d133cc 100644
--- a/src/libcamera/base/object.cpp
+++ b/src/libcamera/base/object.cpp
@@ -350,7 +350,7 @@ void Object::connect(SignalBase *signal)
void Object::disconnect(SignalBase *signal)
{
- for (auto iter = signals_.begin(); iter != signals_.end(); ) {
+ for (auto iter = signals_.begin(); iter != signals_.end();) {
if (*iter == signal)
iter = signals_.erase(iter);
else
diff --git a/src/libcamera/base/signal.cpp b/src/libcamera/base/signal.cpp
index b782e050..7876a21d 100644
--- a/src/libcamera/base/signal.cpp
+++ b/src/libcamera/base/signal.cpp
@@ -49,7 +49,7 @@ void SignalBase::disconnect(std::function<bool(SlotList::iterator &)> match)
{
MutexLocker locker(signalsLock);
- for (auto iter = slots_.begin(); iter != slots_.end(); ) {
+ for (auto iter = slots_.begin(); iter != slots_.end();) {
if (match(iter)) {
Object *object = (*iter)->object();
if (object)
diff --git a/src/libcamera/base/thread.cpp b/src/libcamera/base/thread.cpp
index 319bfda9..d8fe0d69 100644
--- a/src/libcamera/base/thread.cpp
+++ b/src/libcamera/base/thread.cpp
@@ -604,10 +604,12 @@ void Thread::removeMessages(Object *receiver)
/**
* \brief Dispatch posted messages for this thread
* \param[in] type The message type
+ * \param[in] receiver The receiver whose messages to dispatch
*
- * This function immediately dispatches all the messages previously posted for
- * this thread with postMessage() that match the message \a type. If the \a type
- * is Message::Type::None, all messages are dispatched.
+ * This function immediately dispatches all the messages of the given \a type
+ * previously posted to this thread for the \a receiver with postMessage(). If
+ * the \a type is Message::Type::None, all messages types are dispatched. If the
+ * \a receiver is null, messages to all receivers are dispatched.
*
* Messages shall only be dispatched from the current thread, typically within
* the thread from the run() function. Calling this function outside of the
@@ -617,7 +619,7 @@ void Thread::removeMessages(Object *receiver)
* same thread from an object's message handler. It guarantees delivery of
* messages in the order they have been posted in all cases.
*/
-void Thread::dispatchMessages(Message::Type type)
+void Thread::dispatchMessages(Message::Type type, Object *receiver)
{
ASSERT(data_ == ThreadData::current());
@@ -634,6 +636,9 @@ void Thread::dispatchMessages(Message::Type type)
if (type != Message::Type::None && msg->type() != type)
continue;
+ if (receiver && receiver != msg->receiver_)
+ continue;
+
/*
* Move the message, setting the entry in the list to null. It
* will cause recursive calls to ignore the entry, and the erase
@@ -641,12 +646,12 @@ void Thread::dispatchMessages(Message::Type type)
*/
std::unique_ptr<Message> message = std::move(msg);
- Object *receiver = message->receiver_;
- ASSERT(data_ == receiver->thread()->data_);
- receiver->pendingMessages_--;
+ Object *messageReceiver = message->receiver_;
+ ASSERT(data_ == messageReceiver->thread()->data_);
+ messageReceiver->pendingMessages_--;
locker.unlock();
- receiver->message(message.get());
+ messageReceiver->message(message.get());
message.reset();
locker.lock();
}
@@ -657,7 +662,7 @@ void Thread::dispatchMessages(Message::Type type)
* the outer calls.
*/
if (!--data_->messages_.recursion_) {
- for (auto iter = messages.begin(); iter != messages.end(); ) {
+ for (auto iter = messages.begin(); iter != messages.end();) {
if (!*iter)
iter = messages.erase(iter);
else
diff --git a/src/libcamera/converter/converter_v4l2_m2m.cpp b/src/libcamera/converter/converter_v4l2_m2m.cpp
index 566f18ce..ee05b798 100644
--- a/src/libcamera/converter/converter_v4l2_m2m.cpp
+++ b/src/libcamera/converter/converter_v4l2_m2m.cpp
@@ -738,7 +738,7 @@ int V4L2M2MConverter::queueBuffers(FrameBuffer *input,
}
/*
- * \todo: This should be extended to include Feature::Flag to denote
+ * \todo This should be extended to include Feature::Flag to denote
* what each converter supports feature-wise.
*/
static std::initializer_list<std::string> compatibles = {
diff --git a/src/libcamera/ipa_proxy.cpp b/src/libcamera/ipa_proxy.cpp
index 25f772a4..9907b961 100644
--- a/src/libcamera/ipa_proxy.cpp
+++ b/src/libcamera/ipa_proxy.cpp
@@ -115,7 +115,7 @@ std::string IPAProxy::configurationFile(const std::string &name,
ipaEnvName = "LIBCAMERA_" + ipaEnvName + "_TUNING_FILE";
char const *configFromEnv = utils::secure_getenv(ipaEnvName.c_str());
- if (configFromEnv && *configFromEnv == '\0')
+ if (configFromEnv && *configFromEnv != '\0')
return { configFromEnv };
struct stat statbuf;
diff --git a/src/libcamera/matrix.cpp b/src/libcamera/matrix.cpp
index 4d95a19b..e7e02722 100644
--- a/src/libcamera/matrix.cpp
+++ b/src/libcamera/matrix.cpp
@@ -53,6 +53,17 @@ LOG_DEFINE_CATEGORY(Matrix)
*/
/**
+ * \fn Matrix::data()
+ * \brief Access the matrix data as a linear array
+ *
+ * Access the contents of the matrix as a one-dimensional linear array of
+ * values in row-major order. The size of the array is equal to the product of
+ * the number of rows and columns of the matrix (Rows x Cols).
+ *
+ * \return A span referencing the matrix data as a linear array
+ */
+
+/**
* \fn Span<const T, Cols> Matrix::operator[](size_t i) const
* \brief Index to a row in the matrix
* \param[in] i Index of row to retrieve
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index 57fde8a8..de22b8e6 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -53,6 +53,7 @@ libcamera_internal_sources = files([
'v4l2_pixelformat.cpp',
'v4l2_subdevice.cpp',
'v4l2_videodevice.cpp',
+ 'vector.cpp',
'yaml_parser.cpp',
])
diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
index 5abd6b20..a05e11fc 100644
--- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp
+++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
@@ -1615,7 +1615,7 @@ bool PipelineHandlerMaliC55::registerSensorCamera(MediaLink *ispLink)
isp_->frameStart.connect(data->delayedCtrls_.get(),
&DelayedControls::applyControls);
- /* \todo: Init properties. */
+ /* \todo Init properties. */
if (!registerMaliCamera(std::move(data), sensor->name()))
return false;
diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
index 1ac8d8ae..52633fe3 100644
--- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp
+++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
@@ -1325,6 +1325,7 @@ int PipelineHandlerRkISP1::createCamera(MediaEntity *sensor)
std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
{ V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },
+ { V4L2_CID_VBLANK, { 1, false } },
};
data->delayedCtrls_ =
diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
index 8ac24e6e..6e039bf3 100644
--- a/src/libcamera/pipeline/simple/simple.cpp
+++ b/src/libcamera/pipeline/simple/simple.cpp
@@ -537,21 +537,7 @@ int SimpleCameraData::init()
<< "Failed to create software ISP, disabling software debayering";
swIsp_.reset();
} else {
- /*
- * The inputBufferReady signal is emitted from the soft ISP thread,
- * and needs to be handled in the pipeline handler thread. Signals
- * implement queued delivery, but this works transparently only if
- * the receiver is bound to the target thread. As the
- * SimpleCameraData class doesn't inherit from the Object class, it
- * is not bound to any thread, and the signal would be delivered
- * synchronously. Instead, connect the signal to a lambda function
- * bound explicitly to the pipe, which is bound to the pipeline
- * handler thread. The function then simply forwards the call to
- * conversionInputDone().
- */
- swIsp_->inputBufferReady.connect(pipe, [this](FrameBuffer *buffer) {
- this->conversionInputDone(buffer);
- });
+ swIsp_->inputBufferReady.connect(this, &SimpleCameraData::conversionInputDone);
swIsp_->outputBufferReady.connect(this, &SimpleCameraData::conversionOutputDone);
swIsp_->ispStatsReady.connect(this, &SimpleCameraData::ispStatsReady);
swIsp_->setSensorControls.connect(this, &SimpleCameraData::setSensorControls);
diff --git a/src/libcamera/pipeline/virtual/config_parser.cpp b/src/libcamera/pipeline/virtual/config_parser.cpp
index 0cbfe39b..1e009946 100644
--- a/src/libcamera/pipeline/virtual/config_parser.cpp
+++ b/src/libcamera/pipeline/virtual/config_parser.cpp
@@ -54,7 +54,7 @@ ConfigParser::parseConfigFile(File &file, PipelineHandler *pipe)
data->config_.id = cameraId;
ControlInfoMap::Map controls;
- /* todo: Check which resolution's frame rate to be reported */
+ /* \todo Check which resolution's frame rate to be reported */
controls[&controls::FrameDurationLimits] =
ControlInfo(1000000 / data->config_.resolutions[0].frameRates[1],
1000000 / data->config_.resolutions[0].frameRates[0]);
diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp
index 469f5655..049ebcba 100644
--- a/src/libcamera/pipeline/virtual/virtual.cpp
+++ b/src/libcamera/pipeline/virtual/virtual.cpp
@@ -232,8 +232,7 @@ PipelineHandlerVirtual::generateConfiguration(Camera *camera,
default:
LOG(Virtual, Error)
<< "Requested stream role not supported: " << role;
- config.reset();
- return config;
+ return {};
}
std::map<PixelFormat, std::vector<SizeRange>> streamFormats;
@@ -287,6 +286,11 @@ int PipelineHandlerVirtual::exportFrameBuffers([[maybe_unused]] Camera *camera,
int PipelineHandlerVirtual::start([[maybe_unused]] Camera *camera,
[[maybe_unused]] const ControlList *controls)
{
+ VirtualCameraData *data = cameraData(camera);
+
+ for (auto &s : data->streamConfigs_)
+ s.seq = 0;
+
return 0;
}
@@ -298,16 +302,27 @@ int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera,
Request *request)
{
VirtualCameraData *data = cameraData(camera);
+ const auto timestamp = currentTimestamp();
for (auto const &[stream, buffer] : request->buffers()) {
bool found = false;
/* map buffer and fill test patterns */
for (auto &streamConfig : data->streamConfigs_) {
if (stream == &streamConfig.stream) {
+ FrameMetadata &fmd = buffer->_d()->metadata();
+
+ fmd.status = FrameMetadata::Status::FrameSuccess;
+ fmd.sequence = streamConfig.seq++;
+ fmd.timestamp = timestamp;
+
+ for (const auto [i, p] : utils::enumerate(buffer->planes()))
+ fmd.planes()[i].bytesused = p.length;
+
found = true;
+
if (streamConfig.frameGenerator->generateFrame(
stream->configuration().size, buffer))
- buffer->_d()->cancel();
+ fmd.status = FrameMetadata::Status::FrameError;
completeBuffer(request, buffer);
break;
@@ -316,7 +331,7 @@ int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera,
ASSERT(found);
}
- request->metadata().set(controls::SensorTimestamp, currentTimestamp());
+ request->metadata().set(controls::SensorTimestamp, timestamp);
completeRequest(request);
return 0;
diff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h
index 92ad7d4a..683cb82b 100644
--- a/src/libcamera/pipeline/virtual/virtual.h
+++ b/src/libcamera/pipeline/virtual/virtual.h
@@ -37,6 +37,7 @@ public:
struct StreamConfig {
Stream stream;
std::unique_ptr<FrameGenerator> frameGenerator;
+ unsigned int seq = 0;
};
/* The config file is parsed to the Configuration struct */
struct Configuration {
diff --git a/src/libcamera/process.cpp b/src/libcamera/process.cpp
index bc9833f4..68fad327 100644
--- a/src/libcamera/process.cpp
+++ b/src/libcamera/process.cpp
@@ -75,7 +75,7 @@ void ProcessManager::sighandler()
return;
}
- for (auto it = processes_.begin(); it != processes_.end(); ) {
+ for (auto it = processes_.begin(); it != processes_.end();) {
Process *process = *it;
int wstatus;
diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp
index 8c56ed30..b206ac13 100644
--- a/src/libcamera/request.cpp
+++ b/src/libcamera/request.cpp
@@ -475,6 +475,15 @@ int Request::addBuffer(const Stream *stream, FrameBuffer *buffer,
return -EINVAL;
}
+ /*
+ * Make sure the fence has been extracted from the buffer
+ * to avoid waiting on a stale fence.
+ */
+ if (buffer->_d()->fence()) {
+ LOG(Request, Error) << "Can't add buffer that still references a fence";
+ return -EEXIST;
+ }
+
auto it = bufferMap_.find(stream);
if (it != bufferMap_.end()) {
LOG(Request, Error) << "FrameBuffer already set for stream";
@@ -485,15 +494,6 @@ int Request::addBuffer(const Stream *stream, FrameBuffer *buffer,
_d()->pending_.insert(buffer);
bufferMap_[stream] = buffer;
- /*
- * Make sure the fence has been extracted from the buffer
- * to avoid waiting on a stale fence.
- */
- if (buffer->_d()->fence()) {
- LOG(Request, Error) << "Can't add buffer that still references a fence";
- return -EEXIST;
- }
-
if (fence && fence->isValid())
buffer->_d()->setFence(std::move(fence));
diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
index 44baf200..a64e6b2c 100644
--- a/src/libcamera/software_isp/software_isp.cpp
+++ b/src/libcamera/software_isp/software_isp.cpp
@@ -13,10 +13,14 @@
#include <sys/types.h>
#include <unistd.h>
+#include <libcamera/base/log.h>
+#include <libcamera/base/thread.h>
+
#include <libcamera/controls.h>
#include <libcamera/formats.h>
#include <libcamera/stream.h>
+#include "libcamera/internal/framebuffer.h"
#include "libcamera/internal/ipa_manager.h"
#include "libcamera/internal/software_isp/debayer_params.h"
@@ -300,8 +304,13 @@ int SoftwareIsp::queueBuffers(uint32_t frame, FrameBuffer *input,
return -EINVAL;
}
- for (auto iter = outputs.begin(); iter != outputs.end(); iter++)
- process(frame, input, iter->second);
+ queuedInputBuffers_.push_back(input);
+
+ for (auto iter = outputs.begin(); iter != outputs.end(); iter++) {
+ FrameBuffer *const buffer = iter->second;
+ queuedOutputBuffers_.push_back(buffer);
+ process(frame, input, buffer);
+ }
return 0;
}
@@ -322,13 +331,32 @@ int SoftwareIsp::start()
/**
* \brief Stops the Software ISP streaming operation
+ *
+ * All pending buffers are returned back as canceled before this function
+ * returns.
*/
void SoftwareIsp::stop()
{
ispWorkerThread_.exit();
ispWorkerThread_.wait();
+ Thread::current()->dispatchMessages(Message::Type::InvokeMessage, this);
+
ipa_->stop();
+
+ for (auto buffer : queuedOutputBuffers_) {
+ FrameMetadata &metadata = buffer->_d()->metadata();
+ metadata.status = FrameMetadata::FrameCancelled;
+ outputBufferReady.emit(buffer);
+ }
+ queuedOutputBuffers_.clear();
+
+ for (auto buffer : queuedInputBuffers_) {
+ FrameMetadata &metadata = buffer->_d()->metadata();
+ metadata.status = FrameMetadata::FrameCancelled;
+ inputBufferReady.emit(buffer);
+ }
+ queuedInputBuffers_.clear();
}
/**
@@ -361,11 +389,15 @@ void SoftwareIsp::statsReady(uint32_t frame, uint32_t bufferId)
void SoftwareIsp::inputReady(FrameBuffer *input)
{
+ ASSERT(queuedInputBuffers_.front() == input);
+ queuedInputBuffers_.pop_front();
inputBufferReady.emit(input);
}
void SoftwareIsp::outputReady(FrameBuffer *output)
{
+ ASSERT(queuedOutputBuffers_.front() == output);
+ queuedOutputBuffers_.pop_front();
outputBufferReady.emit(output);
}
diff --git a/src/ipa/libipa/vector.cpp b/src/libcamera/vector.cpp
index 8019f8cf..85ca2208 100644
--- a/src/ipa/libipa/vector.cpp
+++ b/src/libcamera/vector.cpp
@@ -5,7 +5,7 @@
* Vector and related operations
*/
-#include "vector.h"
+#include "libcamera/internal/vector.h"
#include <libcamera/base/log.h>
@@ -18,8 +18,6 @@ namespace libcamera {
LOG_DEFINE_CATEGORY(Vector)
-namespace ipa {
-
/**
* \class Vector
* \brief Vector class
@@ -346,6 +344,4 @@ bool vectorValidateYaml(const YamlObject &obj, unsigned int size)
}
#endif /* __DOXYGEN__ */
-} /* namespace ipa */
-
} /* namespace libcamera */
diff --git a/src/meson.build b/src/meson.build
index 76198e95..8eb8f05b 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -29,8 +29,18 @@ endif
# libyuv, used by the Android adaptation layer and the virtual pipeline handler.
# Fallback to a subproject if libyuv isn't found, as it's typically not provided
-# by distributions.
-libyuv_dep = dependency('libyuv', required : false)
+# by distributions. Where libyuv is provided by a distribution, it may not
+# always supply a pkg-config implementation, requiring cxx.find_library() to
+# search for it.
+if not get_option('force_fallback_for').contains('libyuv')
+ libyuv_dep = dependency('libyuv', required : false)
+ if not libyuv_dep.found()
+ libyuv_dep = cxx.find_library('yuv', has_headers : 'libyuv.h',
+ required : false)
+ endif
+else
+ libyuv_dep = dependency('', required : false)
+endif
if (pipelines.contains('virtual') or get_option('android').allowed()) and \
not libyuv_dep.found()