From patchwork Fri Feb 3 09:44:22 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Naushir Patuck X-Patchwork-Id: 18251 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 A59F2C3243 for ; Fri, 3 Feb 2023 09:44:35 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 53ACB625F3; Fri, 3 Feb 2023 10:44:35 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1675417475; 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=LvYvOKyHWAuqiBTtUdgVlq2mFtigLix+hV56Ac+xjEzJ8SYHIExkl/9iT2mOBET+w /c8LTQB+Q4kY4nKa6GUdZWK5zqFHUiyfZZy57lRK1y08mN8hJzSgjGzA5KmvFRWXCB 5RxLeFvKJU/qSBoVqHNb2CSQMiVinLUs/XixcjBnVlE5UROwce9v/CxkOXwN7Yz73V 8CIHoabNJaCPyQzI9fikji0qfvegTy5QWrL1hopDCEkjqIkTjEOnr+6qkQtoDtJAQV Y2q6t2yNTAWuyNVGfsMeRboWvdVa0PUUlTTN5nwsvZ7xG/q2EhS4zLi5TOV1a3+7YE N5LOWKbtF77WQ== Received: from mail-wr1-x436.google.com (mail-wr1-x436.google.com [IPv6:2a00:1450:4864:20::436]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 691B2625E4 for ; Fri, 3 Feb 2023 10:44:31 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=raspberrypi.com header.i=@raspberrypi.com header.b="ojeDGagq"; dkim-atps=neutral Received: by mail-wr1-x436.google.com with SMTP id o18so4115408wrj.3 for ; Fri, 03 Feb 2023 01:44:31 -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=ojeDGagqcTXV80rDOVET1k4HOtW57O1a1iWnyk8a9ngMEj8ptd5gNie5QVHb632QyG cD9XCQ57h2zSKYtEyLeU7QFu8ikREJFODnZreZaz4Hv8GwEKifhaow9CwzYp9f+GZVzN Hy08i66M+0A7kmqKEUOgBGQScSuaHEufRMIar0UF3bFQyCHR4KTJcGUSRV4GaAWHcOKU BzSvbwF/FY5iY/d7Y5Q8DU0wkwsrAdS98FcJUCV6PtEXyPF2D/jsEYJSCCCqgUpa5Nlf LbGgG49bBfhf57WwvV5PMX+EY6fDxXNIx49uCrO5w2Lg6i/wNBP/dTGG0gt5b3llVQEZ 1ioQ== 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=XDZQh/l77g2/Zj+CS/5mDSmMy3aJtBGGDWKwd0Z0C8v9daNMQkxd+Rg4uUEt9En1Nh z6J7NRLkicJR1LHa79DsnHt/C0/UCx3BjzRKMzNmvvV9KgR7iyUr084g21hHb8I66sAU xhpVvsfP3HlrSBCJFDBF6vOwqghwAfL6VKgAscr/Bmu2vR01UFtq1bgKKM2dqz5Rplmn e3yjORXJVpz1nrQEXpqjsXShze5gtxPyJNtlQ5m0LC1PC/8ABwKopCPyTB5/fkOe/BBF CBEOLkBAXuTqHmDVOzHcDSbi77sqzlIFhT7VR8sJVX5GfR7SleadF3sR5GIciFSoYINe x3Qw== X-Gm-Message-State: AO0yUKXDdikqNMx5wWfHHDDa5N/CuPZJZyEJkiLeRM2RRsdZAb3YZlp5 39FYxzuRS4F6Ox5294/i3F78Hlbnny8XpDPJS+S/MA== X-Google-Smtp-Source: AK7set+f7Fd9anaaFY3vn+5nDamJRU2/Ug1DUFtORdvwxS6qEkRdQNpb43xlphrDRlV4msEj6GpPxQ== X-Received: by 2002:adf:f5ca:0:b0:2bf:d686:c873 with SMTP id k10-20020adff5ca000000b002bfd686c873mr2906188wrp.28.1675417470768; Fri, 03 Feb 2023 01:44:30 -0800 (PST) Received: from localhost.localdomain ([93.93.133.154]) by smtp.gmail.com with ESMTPSA id f17-20020a5d50d1000000b002bfe266d710sm1562503wrt.90.2023.02.03.01.44.30 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 03 Feb 2023 01:44:30 -0800 (PST) To: libcamera-devel@lists.libcamera.org Date: Fri, 3 Feb 2023 09:44:22 +0000 Message-Id: <20230203094424.25243-7-naush@raspberrypi.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20230203094424.25243-1-naush@raspberrypi.com> References: <20230203094424.25243-1-naush@raspberrypi.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v1 6/8] 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_; +};