From patchwork Fri Jan 27 15:43:20 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Naushir Patuck X-Patchwork-Id: 18213 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 C3A7BC3298 for ; Fri, 27 Jan 2023 15:43:52 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 818BF625EF; Fri, 27 Jan 2023 16:43:52 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1674834232; bh=NxhZeUKE1nvXap0YMG8vugqatVNdph4Ii/SKNyji8+Q=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=Eyd2/tcRRF1BrfTIbr6EP5aL8r2FQYumwuRuvKdxg9uAxoW5dGA9hbT/rir/uPPoW vRVunDoQ8cRdY6n03rqeA1a8wenQQJZBITjfIAcdSZWl8UaQ2A2fCQCDzkRn8gIi+w 933b+leEplToYNw7FlzYcw2koWRO7lzzKppln8+rDk3qvN00OEIqENjGA+KfDekovi yElIbbUUm9IA+dbgiziSWC7LFs30bMFJlxuFcwIfdg1F/Bdia5x0J5bFkPyxXTiRli sMOx55kQEszmIBTY7ZjXnzs9rh9YYXPBSQl0w7/VXhm8toZR9mx9/JBp02akhnO6hw soZNQ64AWteHw== Received: from mail-wm1-x335.google.com (mail-wm1-x335.google.com [IPv6:2a00:1450:4864:20::335]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 70049625FD for ; Fri, 27 Jan 2023 16:43:44 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=raspberrypi.com header.i=@raspberrypi.com header.b="Lxlzb4kP"; dkim-atps=neutral Received: by mail-wm1-x335.google.com with SMTP id c4-20020a1c3504000000b003d9e2f72093so5693712wma.1 for ; Fri, 27 Jan 2023 07:43:44 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=raspberrypi.com; s=google; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=nytONP2YDrHV/JpeWzsmettXZPjzIzkspdiWeo6WEy0=; b=Lxlzb4kP4hg392zv0MbsrOvfF73Do60pvWhQ0ZtDmgYiiUfdqSlgUp8v8p3NPGO4er QVgiYL+HzVr1USnbe2XN59jubjp1tO6vht12hdzSSrqQhwR5aAOJBkgwOvYnDTsAYe++ VNaqMBxiYr89T+eJKQ+mN8/AbwzqnfMF3QHh6w4wSsdiKhvJEx07KJ9eJSKL3Zis7Vtd DdQgbDcETSDQbGBumvEc2lZICYNgb/QT3ltUENq8Z20Pas+jWwBxI7noLqNoN0ceZ2ZQ VmlYrEh9NHMYu/zR3UbB5XLeRL+TytfgkA4IHmoSYx0wm+Fa13sSwIGp3r8dqZAJh736 o7rA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=nytONP2YDrHV/JpeWzsmettXZPjzIzkspdiWeo6WEy0=; b=eITmeCF7H2KsoAURQWac7ct9DjZzmx3H26KLMTEDK/J35Z6k7TTagsakAeTGaaX1Zo mjgJvQym07yxjxNNBY348BhiNadXCkA1N9uAEfAutOq2P9JgpUYk5eUFvNtHFYIR43Re ijQo9rlMvR0VGvCRbIjziBdkqF7L09kOaKw2wnvl9SEFq2VaygM9mEkra60Ws+v4pbid tFamZNIJh+SnF3GV4+OJ5pxcjRg84snx1kCRnHpv9udF4NaJ/JR3wEzvqPyA4ZYuA8xQ vlFJ2el6pPvnfHluKGfJ1lZ2jO/vCte2sOE1ENde6Tx3b4+LNA6JActs6fYsGOYf9NIG KCDw== X-Gm-Message-State: AFqh2kq3+G7GImDPxHzjvaMCou7XfWwo05l//LeVgo2tZs7t2ZASK46J QVxZPx9qu7BxKCA0EnsHFSORYtjju1NGWqYI5z0= X-Google-Smtp-Source: AMrXdXvK1BXqHUIAd/qAFNd4DZSnnS+U/tJqlt2Wm38buk5GohF4oqLui8SyNuj+f/amExjov/AnmQ== X-Received: by 2002:a05:600c:3ba5:b0:3da:ff66:e3cc with SMTP id n37-20020a05600c3ba500b003daff66e3ccmr38723861wms.21.1674834223589; Fri, 27 Jan 2023 07:43:43 -0800 (PST) Received: from localhost.localdomain ([93.93.133.154]) by smtp.gmail.com with ESMTPSA id h18-20020a05600c30d200b003c21ba7d7d6sm4549566wmn.44.2023.01.27.07.43.43 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 27 Jan 2023 07:43:43 -0800 (PST) To: libcamera-devel@lists.libcamera.org Date: Fri, 27 Jan 2023 15:43:20 +0000 Message-Id: <20230127154322.29019-14-naush@raspberrypi.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20230127154322.29019-1-naush@raspberrypi.com> References: <20230127154322.29019-1-naush@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v6 13/15] libcamera: apps: lcc: Add multi-stream capture test framework 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: , X-Patchwork-Original-From: Naushir Patuck via libcamera-devel From: Naushir Patuck Reply-To: Naushir Patuck Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a framework for testing multi-stream captures with lcc. To start with, a single test class, MultiStream, has been implemented. This class is based off the SimpleCaptureBalanced behavior, and tests that the pipeline handler completes the exact number of requests queued to it. Add a test to capture_test.cpp using the MultiStream class to test the pipeline handler running with two simultaneous streams. The test framework permutate across a list of named stream pairs. Signed-off-by: Naushir Patuck --- src/apps/lc-compliance/capture_test.cpp | 77 ++++++++++ src/apps/lc-compliance/meson.build | 1 + src/apps/lc-compliance/multi_capture.cpp | 187 +++++++++++++++++++++++ src/apps/lc-compliance/multi_capture.h | 61 ++++++++ 4 files changed, 326 insertions(+) create mode 100644 src/apps/lc-compliance/multi_capture.cpp create mode 100644 src/apps/lc-compliance/multi_capture.h diff --git a/src/apps/lc-compliance/capture_test.cpp b/src/apps/lc-compliance/capture_test.cpp index 37138dfb3d2e..8d534161e985 100644 --- a/src/apps/lc-compliance/capture_test.cpp +++ b/src/apps/lc-compliance/capture_test.cpp @@ -11,6 +11,7 @@ #include #include "environment.h" +#include "multi_capture.h" #include "simple_capture.h" using namespace libcamera; @@ -32,6 +33,13 @@ 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 }, +}; + } /* namespace */ class SingleStream : public testing::TestWithParam> @@ -137,3 +145,72 @@ 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).first] + "_" + + rolesMap[std::get<0>(info.param).second]; + 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) +{ + constexpr unsigned int NumStreams = 2; + + auto [roles, numRequests] = GetParam(); + + MultiCapture capture(camera_); + + capture.configure({ roles.first, roles.second }); + + capture.capture(numRequests, NumStreams); +} + +INSTANTIATE_TEST_SUITE_P(MultiCaptureTests, + MultiStream, + testing::Combine(testing::ValuesIn(MULTIROLES), + testing::ValuesIn(NUMREQUESTS)), + MultiStream::nameParameters); diff --git a/src/apps/lc-compliance/meson.build b/src/apps/lc-compliance/meson.build index 51d9075ac30b..0357cda2f301 100644 --- a/src/apps/lc-compliance/meson.build +++ b/src/apps/lc-compliance/meson.build @@ -13,6 +13,7 @@ lc_compliance_enabled = true lc_compliance_sources = files([ 'environment.cpp', 'main.cpp', + 'multi_capture.cpp', 'simple_capture.cpp', 'capture_test.cpp', ]) diff --git a/src/apps/lc-compliance/multi_capture.cpp b/src/apps/lc-compliance/multi_capture.cpp new file mode 100644 index 000000000000..23becf964fd7 --- /dev/null +++ b/src/apps/lc-compliance/multi_capture.cpp @@ -0,0 +1,187 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2023, Raspberry Pi Ltd + * + * multi_capture.cpp - Multi-stream capture helper + */ +#include "multi_capture.h" + +#include + +#include + +using namespace libcamera; + +MultiCaptureBase::MultiCaptureBase(std::shared_ptr camera) + : loop_(nullptr), camera_(camera), + allocator_(std::make_unique(camera)) +{ +} + +MultiCaptureBase::~MultiCaptureBase() +{ + stop(); +} + +void MultiCaptureBase::configure(const libcamera::StreamRoles &roles) +{ + config_ = camera_->generateConfiguration(roles); + + if (!config_) { + std::cout << "Roles not supported by camera" << std::endl; + GTEST_SKIP(); + } + + /* Set the buffer counts to the largest value across all streams */ + auto largest = + std::max_element(config_->begin(), config_->end(), + [](libcamera::StreamConfiguration &l, libcamera::StreamConfiguration &r) + { return l.bufferCount < r.bufferCount; }); + + for (auto &stream : *config_) + stream.bufferCount = largest->bufferCount; + + updateConfig(); + + if (config_->validate() != CameraConfiguration::Valid) { + config_.reset(); + FAIL() << "Configuration not valid"; + } + + if (camera_->configure(config_.get())) { + config_.reset(); + FAIL() << "Failed to configure camera"; + } +} + +void MultiCaptureBase::start() +{ + 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 for stream " + << i; + EXPECT_EQ(count, config.bufferCount) + << "Alocated less buffers than expected for stream " + << i; + i++; + } + + camera_->requestCompleted.connect(this, &MultiCaptureBase::requestComplete); + + ASSERT_EQ(camera_->start(), 0) << "Failed to start camera"; +} + +void MultiCaptureBase::stop() +{ + if (!config_ || !allocator_->allocated()) + return; + + camera_->stop(); + + camera_->requestCompleted.disconnect(this); + + requests_.clear(); + + for (auto const &config : *config_) { + Stream *stream = config.stream(); + allocator_->free(stream); + } +} + +std::vector +MultiCaptureBase::prepareBuffers(unsigned int numRequests, unsigned int numStreams) +{ + std::vector buffers; + + for (unsigned int i = 0; i < numStreams; i++) { + Stream *stream = config_->at(i).stream(); + + buffers.emplace_back(&allocator_->buffers(stream)); + + /* No point in testing less requests then the camera depth. */ + if (buffers.back()->size() > numRequests) { + std::cout << "Stream " << i + << " needs " << std::to_string(buffers.back()->size()) + << " requests, can't test only " + << std::to_string(numRequests) << std::endl; + return {}; + } + } + + return buffers; +} + +/* MultiCapture */ + +MultiCapture::MultiCapture(std::shared_ptr camera) + : MultiCaptureBase(camera) +{ +} + +void MultiCapture::capture(unsigned int numRequests, unsigned int numStreams) +{ + start(); + + queueCount_ = 0; + captureCount_ = 0; + captureLimit_ = numRequests; + + std::vector + buffers = prepareBuffers(numRequests, numStreams); + + if (!buffers.size()) + GTEST_SKIP(); + + /* Queue the recommended number of requests. */ + const unsigned int inFlightRequests = config_->at(0).bufferCount; + for (unsigned int i = 0; i < inFlightRequests; i++) { + std::unique_ptr request = camera_->createRequest(); + ASSERT_TRUE(request) << "Can't create request"; + + for (unsigned int j = 0; j < numStreams; j++) { + const FrameBufferList *bufferList = buffers[j]; + Stream *stream = config_->at(j).stream(); + + ASSERT_EQ(request->addBuffer(stream, (*bufferList)[i].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_); +} + +int MultiCapture::queueRequest(Request *request) +{ + queueCount_++; + if (queueCount_ > captureLimit_) + return 0; + + return camera_->queueRequest(request); +} + +void MultiCapture::requestComplete(Request *request) +{ + captureCount_++; + if (captureCount_ >= captureLimit_) { + loop_->exit(0); + return; + } + + request->reuse(Request::ReuseBuffers); + if (queueRequest(request)) + loop_->exit(-EINVAL); +} diff --git a/src/apps/lc-compliance/multi_capture.h b/src/apps/lc-compliance/multi_capture.h new file mode 100644 index 000000000000..9037099988e5 --- /dev/null +++ b/src/apps/lc-compliance/multi_capture.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2023, Raspberry Pi Ltd + * + * multi_capture.h - Multi-stream capture helper + */ + +#pragma once + +#include +#include + +#include + +#include "../common/event_loop.h" + +using FrameBufferList = std::vector>; + +class MultiCaptureBase +{ +public: + void configure(const libcamera::StreamRoles &roles); + +protected: + MultiCaptureBase(std::shared_ptr camera); + virtual ~MultiCaptureBase(); + + void start(); + void stop(); + + std::vector + prepareBuffers(unsigned int numRequests, unsigned int numStreams); + + virtual void requestComplete(libcamera::Request *request) = 0; + virtual void updateConfig() = 0; + + EventLoop *loop_; + + std::shared_ptr camera_; + std::unique_ptr allocator_; + std::unique_ptr config_; + std::vector> requests_; +}; + +class MultiCapture : public MultiCaptureBase +{ +public: + MultiCapture(std::shared_ptr camera); + + void capture(unsigned int numRequests, unsigned int numStreams); + +protected: + int queueRequest(libcamera::Request *request); + void requestComplete(libcamera::Request *request) override; + + void updateConfig() override {} + + unsigned int queueCount_; + unsigned int captureCount_; + unsigned int captureLimit_; +};