diff options
Diffstat (limited to 'src')
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 = ¶ms->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 = ¶ms->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 ®ion = 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() |