From patchwork Wed Mar 20 10:16:48 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 19781 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id D3BD2C3274 for ; Wed, 20 Mar 2024 10:17:09 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A201462D2F; Wed, 20 Mar 2024 11:17:07 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="YDXbfm78"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id A093462CA4 for ; Wed, 20 Mar 2024 11:17:04 +0100 (CET) Received: from localhost.localdomain (unknown [IPv6:2001:b07:5d2e:52c9:cc1e:e404:491f:e6ea]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 320E03F1; Wed, 20 Mar 2024 11:16:37 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1710929797; bh=Dr3Lm/VDL/TUI5SdwOAryBoZL/In+cKNvFijSofBUaM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=YDXbfm787eMEPwbVnkW08EUwTRGrYrxsbxgXLAlVb+mUxwn9o0dSRaYr89AcF6lTJ IGHJ7yt0zn8+xhOYZgkLpHrocH7rFXu10QYj8ayDp6ptvs8u8Ww4gvbLX4voWBEFvF i6wN56/eBRIsPssW0z1AHsM6XQgHwhfTDbJJd0GI= From: Jacopo Mondi To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH 1/4] libcamera: Add 'required' property to controls Date: Wed, 20 Mar 2024 11:16:48 +0100 Message-ID: <20240320101652.12873-2-jacopo.mondi@ideasonboard.com> X-Mailer: git-send-email 2.44.0 In-Reply-To: <20240320101652.12873-1-jacopo.mondi@ideasonboard.com> References: <20240320101652.12873-1-jacopo.mondi@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add to the ControlId class a 'required' boolean flag that determine if the control (or property) is mandatory to be supported by a Camera in order to comply with the libcamera API specification. Add support for a 'required' field to the controls and properties yaml file definition and control generation scripts. Also plumb support for the flag in the control serializer component to allow pass the information across IPC borders. Signed-off-by: Jacopo Mondi Reviewed-by: Umang Jain --- Documentation/guides/pipeline-handler.rst | 13 ++++++++---- include/libcamera/controls.h | 10 ++++++---- include/libcamera/ipa/ipa_controls.h | 3 ++- src/libcamera/control_serializer.cpp | 4 +++- src/libcamera/controls.cpp | 24 +++++++++++++++++------ src/libcamera/ipa_controls.cpp | 2 ++ src/libcamera/v4l2_device.cpp | 2 +- utils/gen-controls.py | 8 +++++++- 8 files changed, 48 insertions(+), 18 deletions(-) diff --git a/Documentation/guides/pipeline-handler.rst b/Documentation/guides/pipeline-handler.rst index 728e9676b4a6..b0d77d795211 100644 --- a/Documentation/guides/pipeline-handler.rst +++ b/Documentation/guides/pipeline-handler.rst @@ -610,10 +610,15 @@ information can be found in the `ControlInfoMap`_ class documentation. .. _ControlInfoMap: https://libcamera.org/api-html/classlibcamera_1_1ControlInfoMap.html Pipeline handlers register controls to expose the tunable device and IPA -parameters to applications. Our example pipeline handler only exposes trivial -controls of the video device, by registering a ``ControlId`` instance with -associated values for each supported V4L2 control but demonstrates the mapping -of V4L2 Controls to libcamera ControlIDs. +parameters to applications and register properties to expose the Camera +immutable characteristics. Controls and properties which have the ``required`` +field specified in the YAML definition are mandatory to be supported by a Camera +in order for it to comply with the libcamera API specification. + +Our example pipeline handler only exposes trivial controls of the video device, +by registering a ``ControlId`` instance with associated values for each +supported V4L2 control but demonstrates the mapping of V4L2 Controls to +libcamera ControlIDs. Complete the initialization of the ``VividCameraData`` class by adding the following code to the ``VividCameraData::init()`` function to initialise the diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h index 82b955995380..0382a99de7fa 100644 --- a/include/libcamera/controls.h +++ b/include/libcamera/controls.h @@ -213,14 +213,15 @@ private: class ControlId { public: - ControlId(unsigned int id, const std::string &name, ControlType type) - : id_(id), name_(name), type_(type) + ControlId(unsigned int id, const std::string &name, ControlType type, bool required) + : id_(id), name_(name), type_(type), required_(required) { } unsigned int id() const { return id_; } const std::string &name() const { return name_; } ControlType type() const { return type_; } + bool required() const { return required_; } private: LIBCAMERA_DISABLE_COPY_AND_MOVE(ControlId) @@ -228,6 +229,7 @@ private: unsigned int id_; std::string name_; ControlType type_; + bool required_; }; static inline bool operator==(unsigned int lhs, const ControlId &rhs) @@ -256,8 +258,8 @@ class Control : public ControlId public: using type = T; - Control(unsigned int id, const char *name) - : ControlId(id, name, details::control_type>::value) + Control(unsigned int id, const char *name, bool required) + : ControlId(id, name, details::control_type>::value, required) { } diff --git a/include/libcamera/ipa/ipa_controls.h b/include/libcamera/ipa/ipa_controls.h index e5da1946ce1d..5268b0a8f659 100644 --- a/include/libcamera/ipa/ipa_controls.h +++ b/include/libcamera/ipa/ipa_controls.h @@ -46,7 +46,8 @@ struct ipa_control_info_entry { uint32_t id; uint32_t type; uint32_t offset; - uint32_t padding[1]; + uint8_t required; + uint8_t padding[3]; }; #ifdef __cplusplus diff --git a/src/libcamera/control_serializer.cpp b/src/libcamera/control_serializer.cpp index 0cf719bde798..b9ed1eea6552 100644 --- a/src/libcamera/control_serializer.cpp +++ b/src/libcamera/control_serializer.cpp @@ -281,6 +281,7 @@ int ControlSerializer::serialize(const ControlInfoMap &infoMap, entry.id = id->id(); entry.type = id->type(); entry.offset = values.offset(); + entry.required = id->required(); entries.write(&entry); store(info, values); @@ -498,7 +499,8 @@ ControlInfoMap ControlSerializer::deserialize(ByteStreamBuffer & * debugging purpose. */ controlIds_.emplace_back(std::make_unique(entry->id, - "", type)); + "", type, + entry->required)); (*localIdMap)[entry->id] = controlIds_.back().get(); } diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp index 16d3547c8c07..2799d89869d7 100644 --- a/src/libcamera/controls.cpp +++ b/src/libcamera/controls.cpp @@ -378,18 +378,19 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen * \class ControlId * \brief Control static metadata * - * The ControlId class stores a control ID, name and data type. It provides - * unique identification of a control, but without support for compile-time - * type deduction that the derived template Control class supports. See the - * Control class for more information. + * The ControlId class stores a control ID, name, data type and a boolean + * 'required' flag. It provides unique identification of a control, but without + * support for compile-time type deduction that the derived template Control + * class supports. See the Control class for more information. */ /** - * \fn ControlId::ControlId(unsigned int id, const std::string &name, ControlType type) + * \fn ControlId::ControlId(unsigned int id, const std::string &name, ControlType type, bool required) * \brief Construct a ControlId instance * \param[in] id The control numerical ID * \param[in] name The control name * \param[in] type The control data type + * \param[in] required Boolean flag that determine if the control is required */ /** @@ -410,6 +411,16 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen * \return The control data type */ +/** + * \fn bool ControlId::required() const + * \brief Determine if the control is required or not + * + * A control is 'required' if it is mandatory for a Camera to support it to + * comply with the libcamera API specification. + * + * \return True if the control is required, false otherwise + */ + /** * \fn bool operator==(unsigned int lhs, const ControlId &rhs) * \brief Compare a ControlId with a control numerical ID @@ -456,10 +467,11 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen */ /** - * \fn Control::Control(unsigned int id, const char *name) + * \fn Control::Control(unsigned int id, const char *name, bool required) * \brief Construct a Control instance * \param[in] id The control numerical ID * \param[in] name The control name + * \param[in] required Boolean flag that determine if a control is required * * The control data type is automatically deduced from the template type T. */ diff --git a/src/libcamera/ipa_controls.cpp b/src/libcamera/ipa_controls.cpp index 870a443b0f38..2376cbd9df60 100644 --- a/src/libcamera/ipa_controls.cpp +++ b/src/libcamera/ipa_controls.cpp @@ -220,6 +220,8 @@ static_assert(sizeof(ipa_control_value_entry) == 16, * \var ipa_control_info_entry::offset * The offset in bytes from the beginning of the data section to the control * info data (shall be a multiple of 8 bytes) + * \var ipa_control_info_entry::required + * Boolean flag that determines if the controls is required or not * \var ipa_control_info_entry::padding * Padding bytes (shall be set to 0) */ diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp index 24d208ef77dc..b055d14c2e51 100644 --- a/src/libcamera/v4l2_device.cpp +++ b/src/libcamera/v4l2_device.cpp @@ -522,7 +522,7 @@ std::unique_ptr V4L2Device::v4l2ControlId(const v4l2_query_ext_ctrl & const std::string name(static_cast(ctrl.name), len); const ControlType type = v4l2CtrlType(ctrl.type); - return std::make_unique(ctrl.id, name, type); + return std::make_unique(ctrl.id, name, type, false); } /** diff --git a/utils/gen-controls.py b/utils/gen-controls.py index 6cd5e362c66f..1e997708b10d 100755 --- a/utils/gen-controls.py +++ b/utils/gen-controls.py @@ -112,6 +112,11 @@ class Control(object): else: return f"Span" + @property + def required(self): + """Is the control required""" + return self.__data.get('required') + def snake_case(s): return ''.join([c.isupper() and ('_' + c) or c for c in s]).strip('_') @@ -133,7 +138,7 @@ ${description}''') * \\var ${name} ${description} */''') - def_template = string.Template('extern const Control<${type}> ${name}(${id_name}, "${name}");') + def_template = string.Template('extern const Control<${type}> ${name}(${id_name}, "${name}", ${required});') enum_values_doc = string.Template('''/** * \\var ${name}Values * \\brief List of all $name supported values @@ -158,6 +163,7 @@ ${description} 'type': ctrl.type, 'description': format_description(ctrl.description), 'id_name': id_name, + 'required': "true" if ctrl.required else "false" } target_doc = ctrls_doc[vendor] From patchwork Wed Mar 20 10:16:49 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 19782 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id BAEE1C3272 for ; Wed, 20 Mar 2024 10:17:11 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id AF41062D32; Wed, 20 Mar 2024 11:17:08 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="I61e5ZOX"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id D2FA562CA8 for ; Wed, 20 Mar 2024 11:17:04 +0100 (CET) Received: from localhost.localdomain (unknown [IPv6:2001:b07:5d2e:52c9:cc1e:e404:491f:e6ea]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7E181C58; Wed, 20 Mar 2024 11:16:37 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1710929797; bh=CfzT+mr+GuWMnVHAecA6FVm0MCys20U3JH+JVaKeW/w=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=I61e5ZOX/Dw8xM0SbMWN+17vCPRQP/Bi9SQezpl+HGLhyjVXb9FvJEHtY0JJz7O36 Kp1AsRmKh9JgbSUZNKvZ5sOnYK6wTfahcxRtgsARNxY03QxgSVQYZyQTOGjNDeTInV nkaFAFtDVWN6csPJIKlqi3DmJbcW/iRJPQoBa+iU= From: Jacopo Mondi To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH 2/4] apps: lc-compliance: Test for mandatory controls Date: Wed, 20 Mar 2024 11:16:49 +0100 Message-ID: <20240320101652.12873-3-jacopo.mondi@ideasonboard.com> X-Mailer: git-send-email 2.44.0 In-Reply-To: <20240320101652.12873-1-jacopo.mondi@ideasonboard.com> References: <20240320101652.12873-1-jacopo.mondi@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Test for mandatory controls and properties to be supported by a Camera. Signed-off-by: Jacopo Mondi --- src/apps/lc-compliance/meson.build | 1 + .../lc-compliance/tests/controls_test.cpp | 98 +++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 src/apps/lc-compliance/tests/controls_test.cpp diff --git a/src/apps/lc-compliance/meson.build b/src/apps/lc-compliance/meson.build index b1f605f3392a..09f068628d53 100644 --- a/src/apps/lc-compliance/meson.build +++ b/src/apps/lc-compliance/meson.build @@ -16,6 +16,7 @@ lc_compliance_sources = files([ 'helpers/capture.cpp', 'main.cpp', 'tests/capture_test.cpp', + 'tests/controls_test.cpp' ]) lc_compliance_includes = ([ diff --git a/src/apps/lc-compliance/tests/controls_test.cpp b/src/apps/lc-compliance/tests/controls_test.cpp new file mode 100644 index 000000000000..ff594fbe672a --- /dev/null +++ b/src/apps/lc-compliance/tests/controls_test.cpp @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2023, Ideas On Board Oy + * + * controls_test.cpp - Test controls and properties + */ + +#include +#include + +#include +#include +#include + +#include + +#include "environment.h" + +using namespace libcamera; + +std::array controlMaps = { + &controls::controls, + &properties::properties, +}; + +class ControlTest : public testing::TestWithParam +{ +public: + static std::string nameParameters(const testing::TestParamInfo &info); + +protected: + void SetUp() override; + void TearDown() override; + + std::shared_ptr camera_; +}; + +void ControlTest::SetUp() +{ + Environment *env = Environment::get(); + + camera_ = env->cm()->get(env->cameraId()); + + ASSERT_EQ(camera_->acquire(), 0); +} + +void ControlTest::TearDown() +{ + if (!camera_) + return; + + camera_->release(); + camera_.reset(); +} + +std::string ControlTest::nameParameters(const testing::TestParamInfo &info) +{ + const ControlIdMap *idMap = info.param; + if (idMap == &controls::controls) + return "controls"; + else if (idMap == &properties::properties) + return "properties"; + + return "vendor"; +} + +/* Test that mandatory controls and properties are supported by a camera. */ +TEST_P(ControlTest, RequiredControls) +{ + auto controlMap = GetParam(); + + for (const auto &[id, ctrl] : *controlMap) { + if (!ctrl->required()) + continue; + + if (controlMap == &controls::controls) { + const auto it = camera_->controls().find(ctrl); + + if (it == camera_->controls().end()) + FAIL() << "Mandatory control \"" << ctrl->name() + << "\" not supported" << std::endl; + } else if (controlMap == &properties::properties) { + bool found = camera_->properties().contains(id); + + if (!found) + FAIL() << "Mandatory property \"" << ctrl->name() + << "\" not supported" << std::endl; + } + } +} + +/* + * Use a Value-Parametrized Test case so that vendors can easily test vendor + * control lists by expanding 'controlMaps'. + */ +INSTANTIATE_TEST_SUITE_P(ControlsTest, ControlTest, + testing::ValuesIn(controlMaps), + ControlTest::nameParameters); From patchwork Wed Mar 20 10:16:50 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 19783 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id ACE8CC32C3 for ; Wed, 20 Mar 2024 10:17:12 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5547A62D3C; Wed, 20 Mar 2024 11:17:09 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="fby09bR7"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3B21C61C5B for ; Wed, 20 Mar 2024 11:17:05 +0100 (CET) Received: from localhost.localdomain (unknown [IPv6:2001:b07:5d2e:52c9:cc1e:e404:491f:e6ea]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C886EB1; Wed, 20 Mar 2024 11:16:37 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1710929798; bh=reHW3rV8XT6OttbOYl32USLYdnzSHXomL4YtHDSBGwY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=fby09bR7TcBANPh3tqkbcSMzxCH6VPHfNbY6tVviEBQv//uf0lWFOk+0QQb0GRgL+ hg1e7lnJAQom+YvkYtOIWhL9IcZhYnsiWMG4698jVo3qPF2RSzDZ9CC0dj6cpvwI/r juAkouq6ZAIwqmB+1m1olJQftGOqGrZYtW+H4a9M= From: Jacopo Mondi To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH 3/4] apps: lc-compliance: Support multiple streams in helpers Date: Wed, 20 Mar 2024 11:16:50 +0100 Message-ID: <20240320101652.12873-4-jacopo.mondi@ideasonboard.com> X-Mailer: git-send-email 2.44.0 In-Reply-To: <20240320101652.12873-1-jacopo.mondi@ideasonboard.com> References: <20240320101652.12873-1-jacopo.mondi@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Prepare to add a test suite for capture operations with multiple streams. Modify the Capture helper class to support multiple roles and streams in the configure() and capture() operations. Multi-stream support will be added in next patches. Signed-off-by: Jacopo Mondi Reviewed-by: Stefan Klug --- src/apps/lc-compliance/helpers/capture.cpp | 85 ++++++++++++++----- src/apps/lc-compliance/helpers/capture.h | 2 +- src/apps/lc-compliance/tests/capture_test.cpp | 26 +++--- 3 files changed, 76 insertions(+), 37 deletions(-) diff --git a/src/apps/lc-compliance/helpers/capture.cpp b/src/apps/lc-compliance/helpers/capture.cpp index 5aab973f0392..2fcaf7285eec 100644 --- a/src/apps/lc-compliance/helpers/capture.cpp +++ b/src/apps/lc-compliance/helpers/capture.cpp @@ -7,6 +7,8 @@ #include "capture.h" +#include + #include using namespace libcamera; @@ -22,15 +24,27 @@ Capture::~Capture() stop(); } -void Capture::configure(StreamRole role) +void Capture::configure(Span roles) { - config_ = camera_->generateConfiguration({ role }); + config_ = camera_->generateConfiguration(roles); if (!config_) { - std::cout << "Role not supported by camera" << std::endl; + std::cout << "Roles not supported by camera" << std::endl; GTEST_SKIP(); } + /* + * Set the buffers count to the largest value across all streams. + * \todo: Should all streams from a Camera have the same buffer count ? + */ + auto largest = + std::max_element(config_->begin(), config_->end(), + [](StreamConfiguration &l, StreamConfiguration &r) + { return l.bufferCount < r.bufferCount; }); + + for (auto &stream : *config_) + stream.bufferCount = largest->bufferCount; + if (config_->validate() != CameraConfiguration::Valid) { config_.reset(); FAIL() << "Configuration not valid"; @@ -44,11 +58,17 @@ void Capture::configure(StreamRole role) void Capture::start() { - Stream *stream = config_->at(0).stream(); - int count = allocator_->allocate(stream); + unsigned int i = 0; + for (auto const &config : *config_) { + Stream *stream = config.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"; + ASSERT_GE(count, 0) << "Failed to allocate buffers for stream " << i; + EXPECT_EQ(count, config.bufferCount) + << "Allocated less buffers than expected for stream " << i; + + ++i; + } camera_->requestCompleted.connect(this, &Capture::requestComplete); @@ -64,9 +84,12 @@ void Capture::stop() camera_->requestCompleted.disconnect(this); - Stream *stream = config_->at(0).stream(); requests_.clear(); - allocator_->free(stream); + + for (auto const &config : *config_) { + Stream *stream = config.stream(); + allocator_->free(stream); + } } /* CaptureBalanced */ @@ -80,14 +103,12 @@ void CaptureBalanced::capture(unsigned int numRequests) { start(); - Stream *stream = config_->at(0).stream(); - const std::vector> &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; + const unsigned int bufferCount = config_->at(0).bufferCount; + if (bufferCount > numRequests) { + std::cout << "Camera needs " << bufferCount + << " requests, can't test only " << numRequests + << std::endl; GTEST_SKIP(); } @@ -96,11 +117,21 @@ void CaptureBalanced::capture(unsigned int numRequests) captureLimit_ = numRequests; /* Queue the recommended number of requests. */ - for (const std::unique_ptr &buffer : buffers) { + for (unsigned int bufferIdx = 0; bufferIdx < bufferCount; ++bufferIdx) { std::unique_ptr request = camera_->createRequest(); ASSERT_TRUE(request) << "Can't create request"; - ASSERT_EQ(request->addBuffer(stream, buffer.get()), 0) << "Can't set buffer for request"; + /* Add buffers for each stream. */ + unsigned int i = 0; + for (const auto &config : *config_) { + Stream *stream = config.stream(); + const auto &buffers = allocator_->buffers(stream); + + ASSERT_EQ(request->addBuffer(stream, buffers[bufferIdx].get()), 0) + << "Can't add buffers for stream " << i; + + ++i; + } ASSERT_EQ(queueRequest(request.get()), 0) << "Failed to queue request"; @@ -152,18 +183,26 @@ void CaptureUnbalanced::capture(unsigned int numRequests) { start(); - Stream *stream = config_->at(0).stream(); - const std::vector> &buffers = allocator_->buffers(stream); - captureCount_ = 0; captureLimit_ = numRequests; /* Queue the recommended number of requests. */ - for (const std::unique_ptr &buffer : buffers) { + const unsigned int bufferCount = config_->at(0).bufferCount; + for (unsigned int bufferIdx = 0; bufferIdx < bufferCount; ++bufferIdx) { std::unique_ptr request = camera_->createRequest(); ASSERT_TRUE(request) << "Can't create request"; - ASSERT_EQ(request->addBuffer(stream, buffer.get()), 0) << "Can't set buffer for request"; + /* Add buffers for each stream. */ + unsigned int i = 0; + for (const auto &config : *config_) { + Stream *stream = config.stream(); + const auto &buffers = allocator_->buffers(stream); + + ASSERT_EQ(request->addBuffer(stream, buffers[bufferIdx].get()), 0) + << "Can't add buffers for stream " << i; + + ++i; + } ASSERT_EQ(camera_->queueRequest(request.get()), 0) << "Failed to queue request"; diff --git a/src/apps/lc-compliance/helpers/capture.h b/src/apps/lc-compliance/helpers/capture.h index 0574ab1c7ac7..3e2b2889244d 100644 --- a/src/apps/lc-compliance/helpers/capture.h +++ b/src/apps/lc-compliance/helpers/capture.h @@ -16,7 +16,7 @@ class Capture { public: - void configure(libcamera::StreamRole role); + void configure(libcamera::Span roles); protected: Capture(std::shared_ptr camera); diff --git a/src/apps/lc-compliance/tests/capture_test.cpp b/src/apps/lc-compliance/tests/capture_test.cpp index 284d36307619..3d3cc97791d9 100644 --- a/src/apps/lc-compliance/tests/capture_test.cpp +++ b/src/apps/lc-compliance/tests/capture_test.cpp @@ -17,14 +17,14 @@ using namespace libcamera; const std::vector NUMREQUESTS = { 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 }; -const std::vector ROLES = { - StreamRole::Raw, - StreamRole::StillCapture, - StreamRole::VideoRecording, - StreamRole::Viewfinder +const std::vector> ROLES = { + { StreamRole::Raw }, + { StreamRole::StillCapture }, + { StreamRole::VideoRecording }, + { StreamRole::Viewfinder }, }; -class SingleStream : public testing::TestWithParam> +class SingleStream : public testing::TestWithParam, int>> { public: static std::string nameParameters(const testing::TestParamInfo &info); @@ -67,7 +67,7 @@ std::string SingleStream::nameParameters(const testing::TestParamInfo(info.param)]; + std::string roleName = rolesMap[std::get<0>(info.param)[0]]; std::string numRequestsName = std::to_string(std::get<1>(info.param)); return roleName + "_" + numRequestsName; @@ -82,11 +82,11 @@ std::string SingleStream::nameParameters(const testing::TestParamInfo{ role }); capture.capture(numRequests); } @@ -100,12 +100,12 @@ TEST_P(SingleStream, Capture) */ TEST_P(SingleStream, CaptureStartStop) { - auto [role, numRequests] = GetParam(); + const auto [role, numRequests] = GetParam(); unsigned int numRepeats = 3; CaptureBalanced capture(camera_); - capture.configure(role); + capture.configure(Span{ role }); for (unsigned int starts = 0; starts < numRepeats; starts++) capture.capture(numRequests); @@ -120,11 +120,11 @@ TEST_P(SingleStream, CaptureStartStop) */ TEST_P(SingleStream, UnbalancedStop) { - auto [role, numRequests] = GetParam(); + const auto [role, numRequests] = GetParam(); CaptureUnbalanced capture(camera_); - capture.configure(role); + capture.configure(Span{ role }); capture.capture(numRequests); } From patchwork Wed Mar 20 10:16:51 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 19784 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 1CABEC32C4 for ; Wed, 20 Mar 2024 10:17:13 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C6F3F62D37; Wed, 20 Mar 2024 11:17:10 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="MNmCxSDM"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8773E62CA8 for ; Wed, 20 Mar 2024 11:17:05 +0100 (CET) Received: from localhost.localdomain (unknown [IPv6:2001:b07:5d2e:52c9:cc1e:e404:491f:e6ea]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 2A8AC3F1; Wed, 20 Mar 2024 11:16:38 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1710929798; bh=A2obIogv+J4rJzyAy8SkAi5Tjj86Yqz0mk0F770Wilg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=MNmCxSDMg0HtvCTV1qlyvdhKiVNU9zoE2/RT8kMcuKmSW97KmNah/4QaPbUHeRHq/ 7cUWPdwxYnTITXgfQvG1vbfUsc/+a3hpiq/G+NHcEBk7CFzXAuVnmUNw5dvY3dqE2x 74LYjBWP2cuKuzzbLu/3pFMePATxSxllh4nIUCPc= From: Jacopo Mondi To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Stefan Klug Subject: [PATCH 4/4] apps: lc-compliance: Add multi-stream tests Date: Wed, 20 Mar 2024 11:16:51 +0100 Message-ID: <20240320101652.12873-5-jacopo.mondi@ideasonboard.com> X-Mailer: git-send-email 2.44.0 In-Reply-To: <20240320101652.12873-1-jacopo.mondi@ideasonboard.com> References: <20240320101652.12873-1-jacopo.mondi@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a test suite for testing capture operations with multiple streams. Signed-off-by: Jacopo Mondi Reviewed-by: Stefan Klug --- src/apps/lc-compliance/tests/capture_test.cpp | 92 +++++++++++++++++-- 1 file changed, 85 insertions(+), 7 deletions(-) diff --git a/src/apps/lc-compliance/tests/capture_test.cpp b/src/apps/lc-compliance/tests/capture_test.cpp index 3d3cc97791d9..3f4a2f71d10b 100644 --- a/src/apps/lc-compliance/tests/capture_test.cpp +++ b/src/apps/lc-compliance/tests/capture_test.cpp @@ -16,6 +16,8 @@ using namespace libcamera; +namespace { + const std::vector NUMREQUESTS = { 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 }; const std::vector> ROLES = { { StreamRole::Raw }, @@ -24,6 +26,22 @@ const std::vector> ROLES = { { StreamRole::Viewfinder }, }; +const std::vector> MULTIROLES = { + { StreamRole::Raw, StreamRole::StillCapture }, + { StreamRole::Raw, StreamRole::VideoRecording }, + { StreamRole::StillCapture, StreamRole::VideoRecording }, + { StreamRole::VideoRecording, StreamRole::VideoRecording }, +}; + +std::map rolesMap = { + { StreamRole::Raw, "Raw" }, + { StreamRole::StillCapture, "StillCapture" }, + { StreamRole::VideoRecording, "VideoRecording" }, + { StreamRole::Viewfinder, "Viewfinder" } +}; + +} /* namespace */ + class SingleStream : public testing::TestWithParam, int>> { public: @@ -60,13 +78,6 @@ void SingleStream::TearDown() std::string SingleStream::nameParameters(const testing::TestParamInfo &info) { - std::map rolesMap = { - { StreamRole::Raw, "Raw" }, - { StreamRole::StillCapture, "StillCapture" }, - { StreamRole::VideoRecording, "VideoRecording" }, - { StreamRole::Viewfinder, "Viewfinder" } - }; - std::string roleName = rolesMap[std::get<0>(info.param)[0]]; std::string numRequestsName = std::to_string(std::get<1>(info.param)); @@ -134,3 +145,70 @@ INSTANTIATE_TEST_SUITE_P(CaptureTests, testing::Combine(testing::ValuesIn(ROLES), testing::ValuesIn(NUMREQUESTS)), SingleStream::nameParameters); + +class MultiStream : public testing::TestWithParam, int>> +{ +public: + static std::string nameParameters(const testing::TestParamInfo &info); + +protected: + void SetUp() override; + void TearDown() override; + + std::shared_ptr camera_; +}; + +/* + * We use gtest's SetUp() and TearDown() instead of constructor and destructor + * in order to be able to assert on them. + */ +void MultiStream::SetUp() +{ + Environment *env = Environment::get(); + + camera_ = env->cm()->get(env->cameraId()); + + ASSERT_EQ(camera_->acquire(), 0); +} + +void MultiStream::TearDown() +{ + if (!camera_) + return; + + camera_->release(); + camera_.reset(); +} + +std::string MultiStream::nameParameters(const testing::TestParamInfo &info) +{ + std::string roleName = rolesMap[std::get<0>(info.param)[0]] + "_" + + rolesMap[std::get<0>(info.param)[1]]; + std::string numRequestsName = std::to_string(std::get<1>(info.param)); + + return roleName + "_" + numRequestsName; +} + +/* + * Test multi-stream capture cycles + * + * Makes sure the camera completes the exact number of requests queued. Example + * failure is a camera that completes less requests than the number of requests + * queued. + */ +TEST_P(MultiStream, Capture) +{ + const auto [roles, numRequests] = GetParam(); + + CaptureBalanced capture(camera_); + + capture.configure(Span{ roles }); + + capture.capture(numRequests); +} + +INSTANTIATE_TEST_SUITE_P(MultiCaptureTests, + MultiStream, + testing::Combine(testing::ValuesIn(MULTIROLES), + testing::ValuesIn(NUMREQUESTS)), + MultiStream::nameParameters);