From patchwork Thu Jun 23 14:47:30 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16339 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 93E75BD808 for ; Thu, 23 Jun 2022 14:48:02 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2F40965636; Thu, 23 Jun 2022 16:48:02 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1655995682; bh=OK+Gtkm8vVSc70bwKYeTSCzxUdJjJrN89szkRE52YsM=; 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=VFptnshIQC8aiPtiw3ndPLpq3tE0SGjiC4rCPkLjAJkikKIA8v841Ku90s1UM6MYG 0f9R3lk47YLikHfnNQNgKoFsm1QSEyJnXg+KpG+bfGQWznSZv8ur1K+3kOkHjOnahM pGrEFLyIcr3kY0AM6T9PnivDzxcjBwaFbdgpv3Wc6Ki+OvpuKJCgp4bAdPW1RjXHpO N7dcRKdnGiEVLtG8E6mJBsxBXf8XZYoB+dZa5GOj8Ge+qxgX0i4FqTnO1xqe943Ftt EWl7Lr4nxXWyEKbTKRQNyeX3u+WyAvIXTzLRiYMYya+J2JdpFQwJFPtoBxEscH2dqH fA65EBRy+esTw== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2EDB56048F for ; Thu, 23 Jun 2022 16:47:58 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="o5s79w2j"; dkim-atps=neutral Received: from deskari.lan (91-158-154-79.elisa-laajakaista.fi [91.158.154.79]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 577B06BB; Thu, 23 Jun 2022 16:47:57 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1655995677; bh=OK+Gtkm8vVSc70bwKYeTSCzxUdJjJrN89szkRE52YsM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=o5s79w2jQwgFDqol4AARzU6kbbTYN/lrcdMFTgXteMfHmL9/ltDKlYr0LeR9WFMtN ked7jMDXtsjj+xlKrcMTGrwtiltH1IwG08bZRa7sxI2B4aDomu/fBH9bULEdhxv4jG ILtEBaSBZhofhVJWOElZo9/x2Sz6rat9Siw9eNqI= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Thu, 23 Jun 2022 17:47:30 +0300 Message-Id: <20220623144736.78537-2-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220623144736.78537-1-tomi.valkeinen@ideasonboard.com> References: <20220623144736.78537-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [RFC v1 1/7] py: cam.py: Fix multi camera capture without -C 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: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" -C flag is supposed to affect only the camera that was previously defined in the arguments. That's not the case, and, e.g.: cam.py -c2 -C -c3 causes camera 3 to start capturing, but it stops after the initial Requests have been completed. Fix the issue by filtering out camera contexts that do not have -C defined. Signed-off-by: Tomi Valkeinen Reviewed-by: Kieran Bingham Reviewed-by: Laurent Pinchart --- src/py/cam/cam.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py index 2ae89fa8..733e9ae5 100755 --- a/src/py/cam/cam.py +++ b/src/py/cam/cam.py @@ -434,6 +434,8 @@ def main(): if args.info: ctx.do_cmd_info() + contexts = [ctx for ctx in contexts if ctx.opt_capture > 0] + if args.capture: state = CaptureState(cm, contexts) From patchwork Thu Jun 23 14:47:31 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16340 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 04595BD808 for ; Thu, 23 Jun 2022 14:48:04 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2F8BA6563F; Thu, 23 Jun 2022 16:48:03 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1655995683; bh=EmB0VW8svYaSSYkOiGp3wWK3DCinwXd6t/TOpLxDyEk=; 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=fXnO4KqrO3fCP4oJz/kf5ppvNxfRPWiv+Ppo6G7/luM/JLkDjo38tlnzOmVxJmPJ7 Vl9GqgVbnDJssQ6Lap124okpyUKBJjDG5yCkGrt7q9G1zYNsgejdH8GaMu4I2VEH0D UtEhKSo0KZ6hBaL48EO0cGAWwOTaRese6T82BtyNosGk6AFoMy0pjDZ/X7AVfT3XVJ 4ssyir5lIC0Tu6EalTpDtpGWjABIByK5aVXNuYZaXl/R4s3GDU8xZeUKMndhVnSzsp kYxqeWb467/Dm9Mhiewmqt+g1OgILqzGRiYhebCD58WBpR0SF/HtZqEfdKP9VrI5yw yKTzF8pBs4LAQ== 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 5CB3265635 for ; Thu, 23 Jun 2022 16:47:58 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="XhKTZ4SN"; dkim-atps=neutral Received: from deskari.lan (91-158-154-79.elisa-laajakaista.fi [91.158.154.79]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D21FD80A; Thu, 23 Jun 2022 16:47:57 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1655995678; bh=EmB0VW8svYaSSYkOiGp3wWK3DCinwXd6t/TOpLxDyEk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=XhKTZ4SNMb0QFM2w1T0InsDtJnswbASpBP2Qf4KAKBRQpjWSWUEz5eYFEe1pllvq7 y/r5NEgJH+OmAp0sZvW8GZU0TxtST/57S1ACdMTqD2wVXoKwHyDIp9zN6Bo6xARHHR X55q/GZs5rgO2kozvx9BF43WITqMgdMps+M22kjY= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Thu, 23 Jun 2022 17:47:31 +0300 Message-Id: <20220623144736.78537-3-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220623144736.78537-1-tomi.valkeinen@ideasonboard.com> References: <20220623144736.78537-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [RFC v1 2/7] py: Add Python logging category 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: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add Python logging category, and use it in handleRequestCompleted(). Signed-off-by: Tomi Valkeinen Reviewed-by: Kieran Bingham Reviewed-by: Laurent Pinchart --- src/py/libcamera/py_main.cpp | 8 ++++++-- src/py/libcamera/py_main.h | 12 ++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 src/py/libcamera/py_main.h diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp index 505cc3dc..17b17f60 100644 --- a/src/py/libcamera/py_main.cpp +++ b/src/py/libcamera/py_main.cpp @@ -5,6 +5,8 @@ * Python bindings */ +#include "py_main.h" + #include #include #include @@ -23,6 +25,8 @@ namespace py = pybind11; using namespace libcamera; +LOG_DEFINE_CATEGORY(Python) + template static py::object valueOrTuple(const ControlValue &cv) { @@ -120,10 +124,10 @@ static void handleRequestCompleted(Request *req) size_t s = write(gEventfd, &v, 8); /* * We should never fail, and have no simple means to manage the error, - * so let's use LOG(Fatal). + * so let's use LOG(Python, Fatal). */ if (s != 8) - LOG(Fatal) << "Unable to write to eventfd"; + LOG(Python, Fatal) << "Unable to write to eventfd"; } void init_py_enums(py::module &m); diff --git a/src/py/libcamera/py_main.h b/src/py/libcamera/py_main.h new file mode 100644 index 00000000..1b543a55 --- /dev/null +++ b/src/py/libcamera/py_main.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2022, Tomi Valkeinen + */ + +#pragma once + +#include + +using namespace libcamera; + +LOG_DECLARE_CATEGORY(Python) From patchwork Thu Jun 23 14:47:32 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16341 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 47A0BBD808 for ; Thu, 23 Jun 2022 14:48:05 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id F16726564D; Thu, 23 Jun 2022 16:48:04 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1655995685; bh=Kes4IEJUdm5RCMrrRequIPqPOCTHyYS5zdzrNvorcGM=; 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=mZPDhAkxFssrfDtjJLrLnOJo0W7rEhmuAo/QdN61p0Bx5Su0/009w03j5Vkb9Op5o qnok+4DvePDH719M27Izmk43ur60iGoEsfdGxc4RbwE9QguJz67a7TbGj4+zLL9Jv1 o9KJ5eYIF/D7jRTdyrgT8/93IH0HX0IUFV1qrpvGJIDW4sHMvrzAZQeYGQQBrcJq7b yJR0yUMiwjyTFDEhASeZUHPHRtg+bcvsf6aqadhifHdpFi/dX9cf09gRYc3TUzFovm xh9YZT5hIPI5FULboM5dDkb2B90wFlFndA9dh6s14rQmsiluPXUCPktwm6HkD89/Fc DpxL1OHwBL7Sg== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E1D5565638 for ; Thu, 23 Jun 2022 16:47:58 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ijfQTBm3"; dkim-atps=neutral Received: from deskari.lan (91-158-154-79.elisa-laajakaista.fi [91.158.154.79]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 57A6BDD; Thu, 23 Jun 2022 16:47:58 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1655995678; bh=Kes4IEJUdm5RCMrrRequIPqPOCTHyYS5zdzrNvorcGM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ijfQTBm3ZkMi9q02goNQtIKOERp8jPJy3cs2Qyy6cB1tWAGViafDJ3fqc+1lnCmai LGlLwFmWoWbjrI1ht8OQI/VDI4UlBELtGo4GrfUYPry5JPfmr63BQfA9II2354vk4c t+kLf+bLH99p6mFkdjBPywuZBnfTMR6s98GmdzUk= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Thu, 23 Jun 2022 17:47:32 +0300 Message-Id: <20220623144736.78537-4-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220623144736.78537-1-tomi.valkeinen@ideasonboard.com> References: <20220623144736.78537-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [RFC v1 3/7] py: Move ControlValue helpers to py_helpers.cpp 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: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Clean up the py_main.cpp a bit by moving the ControlValue helpers to a separate file. Signed-off-by: Tomi Valkeinen Reviewed-by: Kieran Bingham Reviewed-by: Laurent Pinchart --- src/py/libcamera/meson.build | 1 + src/py/libcamera/py_helpers.cpp | 98 +++++++++++++++++++++++++++++++++ src/py/libcamera/py_helpers.h | 13 +++++ src/py/libcamera/py_main.cpp | 83 +--------------------------- 4 files changed, 114 insertions(+), 81 deletions(-) create mode 100644 src/py/libcamera/py_helpers.cpp create mode 100644 src/py/libcamera/py_helpers.h diff --git a/src/py/libcamera/meson.build b/src/py/libcamera/meson.build index eb884538..04578bac 100644 --- a/src/py/libcamera/meson.build +++ b/src/py/libcamera/meson.build @@ -15,6 +15,7 @@ pybind11_dep = pybind11_proj.get_variable('pybind11_dep') pycamera_sources = files([ 'py_enums.cpp', 'py_geometry.cpp', + 'py_helpers.cpp', 'py_main.cpp', ]) diff --git a/src/py/libcamera/py_helpers.cpp b/src/py/libcamera/py_helpers.cpp new file mode 100644 index 00000000..d0a8b5c4 --- /dev/null +++ b/src/py/libcamera/py_helpers.cpp @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2022, Tomi Valkeinen + */ + +#include "py_helpers.h" + +#include + +#include +#include +#include +#include + +namespace py = pybind11; + +using namespace libcamera; + +template +static py::object valueOrTuple(const ControlValue &cv) +{ + if (cv.isArray()) { + const T *v = reinterpret_cast(cv.data().data()); + auto t = py::tuple(cv.numElements()); + + for (size_t i = 0; i < cv.numElements(); ++i) + t[i] = v[i]; + + return std::move(t); + } + + return py::cast(cv.get()); +} + +py::object controlValueToPy(const ControlValue &cv) +{ + switch (cv.type()) { + case ControlTypeBool: + return valueOrTuple(cv); + case ControlTypeByte: + return valueOrTuple(cv); + case ControlTypeInteger32: + return valueOrTuple(cv); + case ControlTypeInteger64: + return valueOrTuple(cv); + case ControlTypeFloat: + return valueOrTuple(cv); + case ControlTypeString: + return py::cast(cv.get()); + case ControlTypeRectangle: { + const Rectangle *v = reinterpret_cast(cv.data().data()); + return py::cast(v); + } + case ControlTypeSize: { + const Size *v = reinterpret_cast(cv.data().data()); + return py::cast(v); + } + case ControlTypeNone: + default: + throw std::runtime_error("Unsupported ControlValue type"); + } +} + +template +static ControlValue controlValueMaybeArray(const py::object &ob) +{ + if (py::isinstance(ob) || py::isinstance(ob)) { + std::vector vec = ob.cast>(); + return ControlValue(Span(vec)); + } + + return ControlValue(ob.cast()); +} + +ControlValue pyToControlValue(const py::object &ob, ControlType type) +{ + switch (type) { + case ControlTypeBool: + return ControlValue(ob.cast()); + case ControlTypeByte: + return controlValueMaybeArray(ob); + case ControlTypeInteger32: + return controlValueMaybeArray(ob); + case ControlTypeInteger64: + return controlValueMaybeArray(ob); + case ControlTypeFloat: + return controlValueMaybeArray(ob); + case ControlTypeString: + return ControlValue(ob.cast()); + case ControlTypeRectangle: + return ControlValue(ob.cast()); + case ControlTypeSize: + return ControlValue(ob.cast()); + case ControlTypeNone: + default: + throw std::runtime_error("Control type not implemented"); + } +} diff --git a/src/py/libcamera/py_helpers.h b/src/py/libcamera/py_helpers.h new file mode 100644 index 00000000..cd31e2cc --- /dev/null +++ b/src/py/libcamera/py_helpers.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2022, Tomi Valkeinen + */ + +#pragma once + +#include + +#include + +pybind11::object controlValueToPy(const libcamera::ControlValue &cv); +libcamera::ControlValue pyToControlValue(const pybind11::object &ob, libcamera::ControlType type); diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp index 17b17f60..5a423ece 100644 --- a/src/py/libcamera/py_main.cpp +++ b/src/py/libcamera/py_main.cpp @@ -21,93 +21,14 @@ #include #include +#include "py_helpers.h" + namespace py = pybind11; using namespace libcamera; LOG_DEFINE_CATEGORY(Python) -template -static py::object valueOrTuple(const ControlValue &cv) -{ - if (cv.isArray()) { - const T *v = reinterpret_cast(cv.data().data()); - auto t = py::tuple(cv.numElements()); - - for (size_t i = 0; i < cv.numElements(); ++i) - t[i] = v[i]; - - return std::move(t); - } - - return py::cast(cv.get()); -} - -static py::object controlValueToPy(const ControlValue &cv) -{ - switch (cv.type()) { - case ControlTypeBool: - return valueOrTuple(cv); - case ControlTypeByte: - return valueOrTuple(cv); - case ControlTypeInteger32: - return valueOrTuple(cv); - case ControlTypeInteger64: - return valueOrTuple(cv); - case ControlTypeFloat: - return valueOrTuple(cv); - case ControlTypeString: - return py::cast(cv.get()); - case ControlTypeRectangle: { - const Rectangle *v = reinterpret_cast(cv.data().data()); - return py::cast(v); - } - case ControlTypeSize: { - const Size *v = reinterpret_cast(cv.data().data()); - return py::cast(v); - } - case ControlTypeNone: - default: - throw std::runtime_error("Unsupported ControlValue type"); - } -} - -template -static ControlValue controlValueMaybeArray(const py::object &ob) -{ - if (py::isinstance(ob) || py::isinstance(ob)) { - std::vector vec = ob.cast>(); - return ControlValue(Span(vec)); - } - - return ControlValue(ob.cast()); -} - -static ControlValue pyToControlValue(const py::object &ob, ControlType type) -{ - switch (type) { - case ControlTypeBool: - return ControlValue(ob.cast()); - case ControlTypeByte: - return controlValueMaybeArray(ob); - case ControlTypeInteger32: - return controlValueMaybeArray(ob); - case ControlTypeInteger64: - return controlValueMaybeArray(ob); - case ControlTypeFloat: - return controlValueMaybeArray(ob); - case ControlTypeString: - return ControlValue(ob.cast()); - case ControlTypeRectangle: - return ControlValue(ob.cast()); - case ControlTypeSize: - return ControlValue(ob.cast()); - case ControlTypeNone: - default: - throw std::runtime_error("Control type not implemented"); - } -} - static std::weak_ptr gCameraManager; static int gEventfd; static std::mutex gReqlistMutex; From patchwork Thu Jun 23 14:47:33 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16342 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 D6472BD808 for ; Thu, 23 Jun 2022 14:48:05 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7E9F865646; Thu, 23 Jun 2022 16:48:05 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1655995685; bh=udNMIcTVClz269HEeNat9aXGvxRVuCxQIK1mJk1zZVo=; 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=wl+RhzTtzm1Tidhgx+HLBYE4crPwE5+FWEHgm5NBmAL0x+oZY++D9m5jteGvPKz1L MOAnGk/jiCGunUxMwgnbpUHL8PnhGQ8cBPgtL35aS3r3vEokVsgeM1eukbpI833aP5 UDNh3nFdUjnVcytv7kKhxAT0qrbW1D/2PG+XvAGxPC06l/CjDnLpfCQcNcTQ0Lb3q+ KwuMv8ILRAYYTLKL0n6jDsU6D2l6deFOIIhvVIIPAaNxLo46llE8n5LwbuhYtjzS0D iix5Uwm9VKqYplHQRUXR5qxN9kk5weTl1JKIsvX7QSVofqGYyzppoV2xnFetl+z6Mm Kh7vZqNbKgb6Q== 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 7C3CC6048F for ; Thu, 23 Jun 2022 16:47:59 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="hlxPQvgz"; dkim-atps=neutral Received: from deskari.lan (91-158-154-79.elisa-laajakaista.fi [91.158.154.79]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D57EC6BB; Thu, 23 Jun 2022 16:47:58 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1655995679; bh=udNMIcTVClz269HEeNat9aXGvxRVuCxQIK1mJk1zZVo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=hlxPQvgzuC9qBYqWn+yFkHeHee4kS0SisJ31eKl/p9otN+CTMYgiCejKaZYua+TeF Zw1dDWL/Ia7R82ee3jfkuMVHeJk5wmuGMloZVtg8+YsBakRVAPJjJccgghXlPvhK5G HjduWM6CYvOxFYXpQtjemgknRe/xt4fAORvzH4Lo= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Thu, 23 Jun 2022 17:47:33 +0300 Message-Id: <20220623144736.78537-5-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220623144736.78537-1-tomi.valkeinen@ideasonboard.com> References: <20220623144736.78537-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [RFC v1 4/7] py: Create PyCameraManager 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: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Wrap the CameraManager with a PyCameraManager class and move the related code inside the new class. This helps understanding the life times of the used-to-be global variables, gets rid of static handleRequestCompleted function, and allows us to simplify the binding code as the more complex pieces are inside the class. There should be no user visible functional changes. Note that attaching to requestCompleted signal is a bit funny, as apparently the only way to use a lambda with a signal is to provide an object instance pointer as the first parameter, even if there's really no instance. Signed-off-by: Tomi Valkeinen --- src/py/libcamera/meson.build | 1 + src/py/libcamera/py_camera_manager.cpp | 115 ++++++++++++++++++++++++ src/py/libcamera/py_camera_manager.h | 39 ++++++++ src/py/libcamera/py_main.cpp | 120 ++++++------------------- 4 files changed, 181 insertions(+), 94 deletions(-) create mode 100644 src/py/libcamera/py_camera_manager.cpp create mode 100644 src/py/libcamera/py_camera_manager.h diff --git a/src/py/libcamera/meson.build b/src/py/libcamera/meson.build index 04578bac..ad2a6858 100644 --- a/src/py/libcamera/meson.build +++ b/src/py/libcamera/meson.build @@ -13,6 +13,7 @@ pybind11_proj = subproject('pybind11') pybind11_dep = pybind11_proj.get_variable('pybind11_dep') pycamera_sources = files([ + 'py_camera_manager.cpp', 'py_enums.cpp', 'py_geometry.cpp', 'py_helpers.cpp', diff --git a/src/py/libcamera/py_camera_manager.cpp b/src/py/libcamera/py_camera_manager.cpp new file mode 100644 index 00000000..c9e5a99c --- /dev/null +++ b/src/py/libcamera/py_camera_manager.cpp @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2022, Tomi Valkeinen + */ + +#include "py_camera_manager.h" + +#include +#include + +#include "py_main.h" + +namespace py = pybind11; + +using namespace libcamera; + +PyCameraManager::PyCameraManager() +{ + int fd = eventfd(0, 0); + if (fd == -1) + throw std::system_error(errno, std::generic_category(), + "Failed to create eventfd"); + + eventFd_ = fd; + + int ret = start(); + if (ret) { + close(fd); + eventFd_ = -1; + throw std::system_error(-ret, std::generic_category(), + "Failed to start CameraManager"); + } +} + +PyCameraManager::~PyCameraManager() +{ + if (eventFd_ != -1) { + close(eventFd_); + eventFd_ = -1; + } +} + +py::list PyCameraManager::getCameras() +{ + /* Create a list of Cameras, where each camera has a keep-alive to CameraManager */ + py::list l; + + for (auto &c : cameras()) { + py::object py_cm = py::cast(this); + py::object py_cam = py::cast(c); + py::detail::keep_alive_impl(py_cam, py_cm); + l.append(py_cam); + } + + return l; +} + +std::vector PyCameraManager::getReadyRequests() +{ + readFd(); + + std::vector v; + getRequests(v); + + std::vector ret; + + for (Request *req : v) { + py::object o = py::cast(req); + /* Decrease the ref increased in Camera.queue_request() */ + o.dec_ref(); + ret.push_back(o); + } + + return ret; +} + +/* Note: Called from another thread */ +void PyCameraManager::handleRequestCompleted(Request *req) +{ + pushRequest(req); + writeFd(); +} + +void PyCameraManager::writeFd() +{ + uint64_t v = 1; + + size_t s = write(eventFd_, &v, 8); + /* + * We should never fail, and have no simple means to manage the error, + * so let's use LOG(Python, Fatal). + */ + if (s != 8) + LOG(Python, Fatal) << "Unable to write to eventfd"; +} + +void PyCameraManager::readFd() +{ + uint8_t buf[8]; + + if (read(eventFd_, buf, 8) != 8) + throw std::system_error(errno, std::generic_category()); +} + +void PyCameraManager::pushRequest(Request *req) +{ + std::lock_guard guard(reqlist_mutex_); + reqList_.push_back(req); +} + +void PyCameraManager::getRequests(std::vector &v) +{ + std::lock_guard guard(reqlist_mutex_); + swap(v, reqList_); +} diff --git a/src/py/libcamera/py_camera_manager.h b/src/py/libcamera/py_camera_manager.h new file mode 100644 index 00000000..b0b971ad --- /dev/null +++ b/src/py/libcamera/py_camera_manager.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2022, Tomi Valkeinen + */ + +#pragma once + +#include + +#include + +#include + +using namespace libcamera; + +class PyCameraManager : public CameraManager +{ +public: + PyCameraManager(); + virtual ~PyCameraManager(); + + pybind11::list getCameras(); + + int eventFd() const { return eventFd_; } + + std::vector getReadyRequests(); + + void handleRequestCompleted(Request *req); + +private: + int eventFd_ = -1; + std::mutex reqlist_mutex_; + std::vector reqList_; + + void writeFd(); + void readFd(); + void pushRequest(Request *req); + void getRequests(std::vector &v); +}; diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp index 5a423ece..23018288 100644 --- a/src/py/libcamera/py_main.cpp +++ b/src/py/libcamera/py_main.cpp @@ -7,10 +7,7 @@ #include "py_main.h" -#include #include -#include -#include #include @@ -21,6 +18,7 @@ #include #include +#include "py_camera_manager.h" #include "py_helpers.h" namespace py = pybind11; @@ -29,27 +27,11 @@ using namespace libcamera; LOG_DEFINE_CATEGORY(Python) -static std::weak_ptr gCameraManager; -static int gEventfd; -static std::mutex gReqlistMutex; -static std::vector gReqList; - -static void handleRequestCompleted(Request *req) -{ - { - std::lock_guard guard(gReqlistMutex); - gReqList.push_back(req); - } - - uint64_t v = 1; - size_t s = write(gEventfd, &v, 8); - /* - * We should never fail, and have no simple means to manage the error, - * so let's use LOG(Python, Fatal). - */ - if (s != 8) - LOG(Python, Fatal) << "Unable to write to eventfd"; -} +/* + * XXX Note: global C++ destructors can be ran on this before the py module is + * destructed. + */ +static std::weak_ptr gCameraManager; void init_py_enums(py::module &m); void init_py_controls_generated(py::module &m); @@ -72,7 +54,7 @@ PYBIND11_MODULE(_libcamera, m) * https://pybind11.readthedocs.io/en/latest/advanced/misc.html#avoiding-c-types-in-docstrings */ - auto pyCameraManager = py::class_(m, "CameraManager"); + auto pyCameraManager = py::class_(m, "CameraManager"); auto pyCamera = py::class_(m, "Camera"); auto pyCameraConfiguration = py::class_(m, "CameraConfiguration"); auto pyCameraConfigurationStatus = py::enum_(pyCameraConfiguration, "Status"); @@ -106,78 +88,22 @@ PYBIND11_MODULE(_libcamera, m) /* Classes */ pyCameraManager .def_static("singleton", []() { - std::shared_ptr cm = gCameraManager.lock(); - if (cm) - return cm; - - int fd = eventfd(0, 0); - if (fd == -1) - throw std::system_error(errno, std::generic_category(), - "Failed to create eventfd"); - - cm = std::shared_ptr(new CameraManager, [](auto p) { - close(gEventfd); - gEventfd = -1; - delete p; - }); - - gEventfd = fd; - gCameraManager = cm; - - int ret = cm->start(); - if (ret) - throw std::system_error(-ret, std::generic_category(), - "Failed to start CameraManager"); - - return cm; - }) - - .def_property_readonly("version", &CameraManager::version) - - .def_property_readonly("event_fd", [](CameraManager &) { - return gEventfd; - }) + std::shared_ptr cm = gCameraManager.lock(); - .def("get_ready_requests", [](CameraManager &) { - uint8_t buf[8]; - - if (read(gEventfd, buf, 8) != 8) - throw std::system_error(errno, std::generic_category()); - - std::vector v; - - { - std::lock_guard guard(gReqlistMutex); - swap(v, gReqList); + if (!cm) { + cm = std::make_shared(); + gCameraManager = cm; } - std::vector ret; - - for (Request *req : v) { - py::object o = py::cast(req); - /* Decrease the ref increased in Camera.queue_request() */ - o.dec_ref(); - ret.push_back(o); - } - - return ret; + return cm; }) - .def("get", py::overload_cast(&CameraManager::get), py::keep_alive<0, 1>()) + .def_property_readonly("version", &PyCameraManager::version) + .def("get", py::overload_cast(&PyCameraManager::get), py::keep_alive<0, 1>()) + .def_property_readonly("cameras", &PyCameraManager::getCameras) - /* Create a list of Cameras, where each camera has a keep-alive to CameraManager */ - .def_property_readonly("cameras", [](CameraManager &self) { - py::list l; - - for (auto &c : self.cameras()) { - py::object py_cm = py::cast(self); - py::object py_cam = py::cast(c); - py::detail::keep_alive_impl(py_cam, py_cm); - l.append(py_cam); - } - - return l; - }); + .def_property_readonly("event_fd", &PyCameraManager::eventFd) + .def("get_ready_requests", &PyCameraManager::getReadyRequests); pyCamera .def_property_readonly("id", &Camera::id) @@ -187,7 +113,13 @@ PYBIND11_MODULE(_libcamera, m) const std::unordered_map &controls) { /* \todo What happens if someone calls start() multiple times? */ - self.requestCompleted.connect(handleRequestCompleted); + auto cm = gCameraManager.lock(); + assert(cm); + + self.requestCompleted.connect(&self, [cm=std::move(cm)](Request *req) { + + cm->handleRequestCompleted(req); + }); ControlList controlList(self.controls()); @@ -198,7 +130,7 @@ PYBIND11_MODULE(_libcamera, m) int ret = self.start(&controlList); if (ret) { - self.requestCompleted.disconnect(handleRequestCompleted); + self.requestCompleted.disconnect(); return ret; } @@ -210,7 +142,7 @@ PYBIND11_MODULE(_libcamera, m) if (ret) return ret; - self.requestCompleted.disconnect(handleRequestCompleted); + self.requestCompleted.disconnect(); return 0; }) From patchwork Thu Jun 23 14:47:34 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16343 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 CC360BD808 for ; Thu, 23 Jun 2022 14:48:06 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 277F165647; Thu, 23 Jun 2022 16:48:06 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1655995686; bh=ypyZr0h0B0esoBDRGJv43rUnwKdehaP63OxAJslBc+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=syCXpx4nCTb3CpWpur5eimpbM1ouvl8Wf7ZGoMW66KD88prcwYC+Quh7z7qAhtWtF YzY5azWSrzuBcO9ZQq471jS2s40JVw5qXIBt/zv1F1+MqUF1fiIm+0n3chHlz8eeWC eGb6UMsJ8Rpa3xK9gOeg0avcnY/uu8lRfcyhD/kaKiHBeFQSEULDIzk3op4GIRSZly x0fV/czK/PsWUdRHSD8paAnjN5hw9T3vBbBbTrOgHlQ77L0hfrMJev0jp3SoPnK3+r ZMKny3lO1EgcoHSTfiP6UY1v4JgjID76ynDn3np0F0sstDugUjJhILlIu59tZLrFj7 +WWKilP5c6ECQ== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id F1A6B65636 for ; Thu, 23 Jun 2022 16:47:59 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="BpJnU9oR"; dkim-atps=neutral Received: from deskari.lan (91-158-154-79.elisa-laajakaista.fi [91.158.154.79]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7069CDD; Thu, 23 Jun 2022 16:47:59 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1655995679; bh=ypyZr0h0B0esoBDRGJv43rUnwKdehaP63OxAJslBc+Q=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=BpJnU9oRv4qBXgVVXu9gwToebJ2SukHbpw1jufxqaOvqM09Onmns2hwv4GwWAqaGA UpgW2jmKMXZ86IyvHvMnVdLjj1G4kIx41SlwvfQr3J+34q7bopOjxUsHyPTHgsaDPP O6FSOQeUUbzzslLQgtowoV29IxdNSK3nDyRMwUXI= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Thu, 23 Jun 2022 17:47:34 +0300 Message-Id: <20220623144736.78537-6-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220623144736.78537-1-tomi.valkeinen@ideasonboard.com> References: <20220623144736.78537-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [RFC v1 5/7] py: Add 'nonblocking' argument to get_ready_requests() 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: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add 'nonblocking' argument to get_ready_requests() which allows the user to ensure get_ready_requests() never blocks. This can be used e.g. after calling camera.stop(), to process or discard any ready or cancelled Requests. In fact, it probably should always be used after stopping the cameras, unless you have made sure that there are no unprocessed Requests. If you start the camera again, and you have left Requests unprocessed, you will get those "old" Requests when you expect to get the new Requests. It may be good to call this even if your script exits after stopping the cameras, as unprocessed Requests will keep the Cameras and related objects alive, and thus they won't be freed. As your script is exiting it's strictly speaking not an issue, but it does make tracking other "real" memory leaks more difficult. Perhaps the camera.start() should go and discard any old Requests related to that camera. For the exit issue I don't see any automatic solution. Signed-off-by: Tomi Valkeinen --- src/py/libcamera/py_camera_manager.cpp | 21 +++++++++++++++++++-- src/py/libcamera/py_camera_manager.h | 4 +++- src/py/libcamera/py_main.cpp | 3 ++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/py/libcamera/py_camera_manager.cpp b/src/py/libcamera/py_camera_manager.cpp index c9e5a99c..ba45f713 100644 --- a/src/py/libcamera/py_camera_manager.cpp +++ b/src/py/libcamera/py_camera_manager.cpp @@ -5,6 +5,7 @@ #include "py_camera_manager.h" +#include #include #include @@ -55,9 +56,10 @@ py::list PyCameraManager::getCameras() return l; } -std::vector PyCameraManager::getReadyRequests() +std::vector PyCameraManager::getReadyRequests(bool nonBlocking) { - readFd(); + if (!nonBlocking || hasEvents()) + readFd(); std::vector v; getRequests(v); @@ -113,3 +115,18 @@ void PyCameraManager::getRequests(std::vector &v) std::lock_guard guard(reqlist_mutex_); swap(v, reqList_); } + +bool PyCameraManager::hasEvents() +{ + struct pollfd pfd = { + .fd = eventFd_, + .events = POLLIN, + .revents = 0, + }; + + int ret = poll(&pfd, 1, 0); + if (ret == -1) + throw std::system_error(errno, std::generic_category()); + + return pfd.revents & POLLIN; +} diff --git a/src/py/libcamera/py_camera_manager.h b/src/py/libcamera/py_camera_manager.h index b0b971ad..2396d236 100644 --- a/src/py/libcamera/py_camera_manager.h +++ b/src/py/libcamera/py_camera_manager.h @@ -23,7 +23,7 @@ public: int eventFd() const { return eventFd_; } - std::vector getReadyRequests(); + std::vector getReadyRequests(bool nonBlocking = false); void handleRequestCompleted(Request *req); @@ -36,4 +36,6 @@ private: void readFd(); void pushRequest(Request *req); void getRequests(std::vector &v); + + bool hasEvents(); }; diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp index 23018288..ee4ecb9b 100644 --- a/src/py/libcamera/py_main.cpp +++ b/src/py/libcamera/py_main.cpp @@ -103,7 +103,8 @@ PYBIND11_MODULE(_libcamera, m) .def_property_readonly("cameras", &PyCameraManager::getCameras) .def_property_readonly("event_fd", &PyCameraManager::eventFd) - .def("get_ready_requests", &PyCameraManager::getReadyRequests); + .def("get_ready_requests", &PyCameraManager::getReadyRequests, + py::arg("nonblocking") = false); pyCamera .def_property_readonly("id", &Camera::id) From patchwork Thu Jun 23 14:47:35 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16344 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 F1582BD808 for ; Thu, 23 Jun 2022 14:48:07 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 63F2265645; Thu, 23 Jun 2022 16:48:07 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1655995687; bh=M6fjLGQzQLE2U11ZyzyAjSmclu1VWp3cZ+RqxZSq8Hw=; 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=bnuu7ipLJU0B6NRWCe5YPLe5CKglRXvpFrR/ypA6vA7PFedD89JDJ1RmZd3SJw+zs j40wvRkBEP0pEPbBckLaVazIMw+xafnuVuJfClOJy2/yOrr5wRNrJ29kubWTVPwwfg rLdSLzRtfwWzidBFRef31QRj0faVGDb/7s9OOUrgNteW81PvAnr/Fs6w8889g2wDe4 RqRreUmac6tqNGLM1oATtUbp4+Qaj1J3WTh4hXdpmtl5lly25UjODhk2CPefhzn84H p5lSjSHzFqa3QEwlK5ut+KPLrvPwohRYH2Qrtht6NaOsCmi9fZp0PGJqI/9REb/qSH ZcN7zdYV9fAVg== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 95EB465635 for ; Thu, 23 Jun 2022 16:48:00 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="vUVhrQhh"; dkim-atps=neutral Received: from deskari.lan (91-158-154-79.elisa-laajakaista.fi [91.158.154.79]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id EA8586BB; Thu, 23 Jun 2022 16:47:59 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1655995680; bh=M6fjLGQzQLE2U11ZyzyAjSmclu1VWp3cZ+RqxZSq8Hw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=vUVhrQhhjpNu93j9Fc776/SFwXQF7qxk4PROcGCR4ynLhl/ViAzsG3dwhkOAF1iiS KG6meXzFrC/JHNMldUXF2r1eE7ynTlN1oLB88aXYtZxA57/FuOobhjZ1SMpvvnfG+H Zd8Xjd8vPq4Cr8B6lC6eWfoWZt8jwq7bOMGW/FrU= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Thu, 23 Jun 2022 17:47:35 +0300 Message-Id: <20220623144736.78537-7-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220623144736.78537-1-tomi.valkeinen@ideasonboard.com> References: <20220623144736.78537-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [RFC v1 6/7] py: New event handling 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: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" At the moment the Python bindings only handle the requestCompleted events. But we have others, bufferCompleted and disconnec from the Camera, and cameraAdded and cameraRemoved from the CameraManager. This makes all those events available to the users. The get_ready_requests() method is now deprecated, but available to keep backward compatibility. The new event handling works as follows: The user sets callbacks to the CameraManager or Cameras (e.g. Camera.buffer_completed). When the event_fd informs of an event, the user must call CameraManager.dispatch_events() which gets the queued events and calls the relevant callbacks for each queued event. Additionally there is CameraManager.discard_events() if the user does not want to process the events but wants to clear the event queue (e.g. after stopping the cameras or when exiting the app). I'm not very happy with this patch. It works fine, but there's a lot of repetition of almost-the-same code. Perhaps some template magics might reduce the repetition, but I also fear that it can easily make the code more difficult to read. TODO: Documentation. Signed-off-by: Tomi Valkeinen --- src/py/libcamera/py_camera_manager.cpp | 291 +++++++++++++++++++++++-- src/py/libcamera/py_camera_manager.h | 41 +++- src/py/libcamera/py_main.cpp | 87 +++++++- 3 files changed, 397 insertions(+), 22 deletions(-) diff --git a/src/py/libcamera/py_camera_manager.cpp b/src/py/libcamera/py_camera_manager.cpp index ba45f713..bbadb9ee 100644 --- a/src/py/libcamera/py_camera_manager.cpp +++ b/src/py/libcamera/py_camera_manager.cpp @@ -15,6 +15,37 @@ namespace py = pybind11; using namespace libcamera; +struct CameraEvent { + enum class EventType { + Undefined = 0, + CameraAdded, + CameraRemoved, + Disconnect, + RequestCompleted, + BufferCompleted, + }; + + CameraEvent(EventType type) + : type(type) + { + } + + EventType type; + + std::shared_ptr cam; + + union { + struct { + Request *req; + FrameBuffer *fb; + } buf_completed; + + struct { + Request *req; + } req_completed; + }; +}; + PyCameraManager::PyCameraManager() { int fd = eventfd(0, 0); @@ -56,33 +87,261 @@ py::list PyCameraManager::getCameras() return l; } +/* DEPRECATED */ std::vector PyCameraManager::getReadyRequests(bool nonBlocking) { if (!nonBlocking || hasEvents()) readFd(); - std::vector v; - getRequests(v); + std::vector v; + getEvents(v); std::vector ret; - for (Request *req : v) { - py::object o = py::cast(req); - /* Decrease the ref increased in Camera.queue_request() */ - o.dec_ref(); - ret.push_back(o); + for (const auto &ev : v) { + switch (ev.type) { + case CameraEvent::EventType::RequestCompleted: { + Request *req = ev.req_completed.req; + py::object o = py::cast(req); + /* Decrease the ref increased in Camera.queue_request() */ + o.dec_ref(); + ret.push_back(o); + } + default: + /* ignore */ + break; + } } return ret; } /* Note: Called from another thread */ -void PyCameraManager::handleRequestCompleted(Request *req) +void PyCameraManager::handleBufferCompleted(std::shared_ptr cam, Request *req, FrameBuffer *fb) +{ + CameraEvent ev(CameraEvent::EventType::BufferCompleted); + ev.cam = cam; + ev.buf_completed.req = req; + ev.buf_completed.fb = fb; + + pushEvent(ev); + writeFd(); +} + +/* Note: Called from another thread */ +void PyCameraManager::handleRequestCompleted(std::shared_ptr cam, Request *req) +{ + CameraEvent ev(CameraEvent::EventType::RequestCompleted); + ev.cam = cam; + ev.req_completed.req = req; + + pushEvent(ev); + writeFd(); +} + +/* Note: Called from another thread */ +void PyCameraManager::handleDisconnected(std::shared_ptr cam) { - pushRequest(req); + CameraEvent ev(CameraEvent::EventType::Disconnect); + ev.cam = cam; + + pushEvent(ev); writeFd(); } +/* Note: Called from another thread */ +void PyCameraManager::handleCameraAdded(std::shared_ptr cam) +{ + CameraEvent ev(CameraEvent::EventType::CameraAdded); + ev.cam = cam; + + pushEvent(ev); + writeFd(); +} + +/* Note: Called from another thread */ +void PyCameraManager::handleCameraRemoved(std::shared_ptr cam) +{ + CameraEvent ev(CameraEvent::EventType::CameraRemoved); + ev.cam = cam; + + pushEvent(ev); + writeFd(); +} + +void PyCameraManager::dispatchEvents(bool nonBlocking) +{ + if (!nonBlocking || hasEvents()) + readFd(); + + std::vector v; + getEvents(v); + + LOG(Python, Debug) << "Dispatch " << v.size() << " events"; + + for (const auto &ev : v) { + switch (ev.type) { + case CameraEvent::EventType::CameraAdded: { + std::shared_ptr cam = ev.cam; + + if (cameraAddedHandler_) + cameraAddedHandler_(cam); + + break; + } + case CameraEvent::EventType::CameraRemoved: { + std::shared_ptr cam = ev.cam; + + if (cameraRemovedHandler_) + cameraRemovedHandler_(cam); + + break; + } + case CameraEvent::EventType::BufferCompleted: { + std::shared_ptr cam = ev.cam; + + auto cb = getBufferCompleted(cam.get()); + + if (cb) + cb(cam, ev.buf_completed.req, ev.buf_completed.fb); + + break; + } + case CameraEvent::EventType::RequestCompleted: { + std::shared_ptr cam = ev.cam; + + auto cb = getRequestCompleted(cam.get()); + + if (cb) + cb(cam, ev.req_completed.req); + + /* Decrease the ref increased in Camera.queue_request() */ + py::object o = py::cast(ev.req_completed.req); + o.dec_ref(); + + break; + } + case CameraEvent::EventType::Disconnect: { + std::shared_ptr cam = ev.cam; + + auto cb = getDisconnected(cam.get()); + + if (cb) + cb(cam); + + break; + } + default: + assert(false); + } + } +} + +void PyCameraManager::discardEvents() +{ + if (hasEvents()) + readFd(); + + std::vector v; + getEvents(v); + + LOG(Python, Debug) << "Discard " << v.size() << " events"; + + for (const auto &ev : v) { + if (ev.type != CameraEvent::EventType::RequestCompleted) + continue; + + std::shared_ptr cam = ev.cam; + + /* Decrease the ref increased in Camera.queue_request() */ + py::object o = py::cast(ev.req_completed.req); + o.dec_ref(); + } +} + +std::function)> PyCameraManager::getCameraAdded() const +{ + return cameraAddedHandler_; +} + +void PyCameraManager::setCameraAdded(std::function)> fun) +{ + if (cameraAddedHandler_) + cameraAdded.disconnect(); + + cameraAddedHandler_ = fun; + + if (fun) + cameraAdded.connect(this, &PyCameraManager::handleCameraAdded); +} + +std::function)> PyCameraManager::getCameraRemoved() const +{ + return cameraRemovedHandler_; +} + +void PyCameraManager::setCameraRemoved(std::function)> fun) +{ + if (cameraRemovedHandler_) + cameraRemoved.disconnect(); + + cameraRemovedHandler_ = fun; + + if (fun) + cameraRemoved.connect(this, &PyCameraManager::handleCameraRemoved); +} + +std::function, Request *)> PyCameraManager::getRequestCompleted(Camera *cam) +{ + if (auto it = cameraRequestCompletedHandlers_.find(cam); + it != cameraRequestCompletedHandlers_.end()) + return it->second; + + return nullptr; +} + +void PyCameraManager::setRequestCompleted(Camera *cam, std::function, Request *)> fun) +{ + if (fun) + cameraRequestCompletedHandlers_[cam] = fun; + else + cameraRequestCompletedHandlers_.erase(cam); +} + +std::function, Request *, FrameBuffer *)> PyCameraManager::getBufferCompleted(Camera *cam) +{ + if (auto it = cameraBufferCompletedHandlers_.find(cam); + it != cameraBufferCompletedHandlers_.end()) + return it->second; + + return nullptr; +} + +void PyCameraManager::setBufferCompleted(Camera *cam, std::function, Request *, FrameBuffer *)> fun) +{ + if (fun) + cameraBufferCompletedHandlers_[cam] = fun; + else + cameraBufferCompletedHandlers_.erase(cam); +} + +std::function)> PyCameraManager::getDisconnected(Camera *cam) +{ + if (auto it = cameraDisconnectHandlers_.find(cam); + it != cameraDisconnectHandlers_.end()) + return it->second; + + return nullptr; +} + +void PyCameraManager::setDisconnected(Camera *cam, std::function)> fun) +{ + if (fun) + cameraDisconnectHandlers_[cam] = fun; + else + cameraDisconnectHandlers_.erase(cam); +} + void PyCameraManager::writeFd() { uint64_t v = 1; @@ -104,16 +363,18 @@ void PyCameraManager::readFd() throw std::system_error(errno, std::generic_category()); } -void PyCameraManager::pushRequest(Request *req) +void PyCameraManager::pushEvent(const CameraEvent &ev) { - std::lock_guard guard(reqlist_mutex_); - reqList_.push_back(req); + std::lock_guard guard(reqlistMutex_); + cameraEvents_.push_back(ev); + + LOG(Python, Debug) << "Queued events: " << cameraEvents_.size(); } -void PyCameraManager::getRequests(std::vector &v) +void PyCameraManager::getEvents(std::vector &v) { - std::lock_guard guard(reqlist_mutex_); - swap(v, reqList_); + std::lock_guard guard(reqlistMutex_); + swap(v, cameraEvents_); } bool PyCameraManager::hasEvents() diff --git a/src/py/libcamera/py_camera_manager.h b/src/py/libcamera/py_camera_manager.h index 2396d236..fd28291b 100644 --- a/src/py/libcamera/py_camera_manager.h +++ b/src/py/libcamera/py_camera_manager.h @@ -13,6 +13,8 @@ using namespace libcamera; +struct CameraEvent; + class PyCameraManager : public CameraManager { public: @@ -27,15 +29,46 @@ public: void handleRequestCompleted(Request *req); + void handleBufferCompleted(std::shared_ptr cam, Request *req, FrameBuffer *fb); + void handleRequestCompleted(std::shared_ptr cam, Request *req); + void handleDisconnected(std::shared_ptr cam); + void handleCameraAdded(std::shared_ptr cam); + void handleCameraRemoved(std::shared_ptr cam); + + void dispatchEvents(bool nonBlocking = false); + void discardEvents(); + + std::function)> getCameraAdded() const; + void setCameraAdded(std::function)> fun); + + std::function)> getCameraRemoved() const; + void setCameraRemoved(std::function)> fun); + + std::function, Request *)> getRequestCompleted(Camera *cam); + void setRequestCompleted(Camera *cam, std::function, Request *)> fun); + + std::function, Request *, FrameBuffer *)> getBufferCompleted(Camera *cam); + void setBufferCompleted(Camera *cam, std::function, Request *, FrameBuffer *)> fun); + + std::function)> getDisconnected(Camera *cam); + void setDisconnected(Camera *cam, std::function)> fun); + private: int eventFd_ = -1; - std::mutex reqlist_mutex_; + std::mutex reqlistMutex_; std::vector reqList_; + std::vector cameraEvents_; + + std::function)> cameraAddedHandler_; + std::function)> cameraRemovedHandler_; + + std::map, Request *, FrameBuffer *)>> cameraBufferCompletedHandlers_; + std::map, Request *)>> cameraRequestCompletedHandlers_; + std::map)>> cameraDisconnectHandlers_; void writeFd(); void readFd(); - void pushRequest(Request *req); - void getRequests(std::vector &v); - + void pushEvent(const CameraEvent &ev); + void getEvents(std::vector &v); bool hasEvents(); }; diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp index ee4ecb9b..b6fd9a9d 100644 --- a/src/py/libcamera/py_main.cpp +++ b/src/py/libcamera/py_main.cpp @@ -103,8 +103,20 @@ PYBIND11_MODULE(_libcamera, m) .def_property_readonly("cameras", &PyCameraManager::getCameras) .def_property_readonly("event_fd", &PyCameraManager::eventFd) + /* DEPRECATED */ .def("get_ready_requests", &PyCameraManager::getReadyRequests, - py::arg("nonblocking") = false); + py::arg("nonblocking") = false) + .def("dispatch_events", &PyCameraManager::dispatchEvents, + py::arg("nonblocking") = false) + .def("discard_events", &PyCameraManager::discardEvents) + + .def_property("camera_added", + &PyCameraManager::getCameraAdded, + &PyCameraManager::setCameraAdded) + + .def_property("camera_removed", + &PyCameraManager::getCameraRemoved, + &PyCameraManager::setCameraRemoved); pyCamera .def_property_readonly("id", &Camera::id) @@ -117,9 +129,13 @@ PYBIND11_MODULE(_libcamera, m) auto cm = gCameraManager.lock(); assert(cm); - self.requestCompleted.connect(&self, [cm=std::move(cm)](Request *req) { + /* + * Note: We always subscribe requestComplete, as the bindings + * use requestComplete event to decrement the Request refcount- + */ - cm->handleRequestCompleted(req); + self.requestCompleted.connect(&self, [cm, camera=self.shared_from_this()](Request *req) { + cm->handleRequestCompleted(camera, req); }); ControlList controlList(self.controls()); @@ -148,6 +164,71 @@ PYBIND11_MODULE(_libcamera, m) return 0; }) + .def_property("request_completed", + [](Camera &self) { + auto cm = gCameraManager.lock(); + assert(cm); + + return cm->getRequestCompleted(&self); + }, + [](Camera &self, std::function, Request *)> f) { + auto cm = gCameraManager.lock(); + assert(cm); + + cm->setRequestCompleted(&self, f); + + /* + * Note: We do not subscribe requestComplete here, as we + * do that in the start() method. + */ + }) + + .def_property("buffer_completed", + [](Camera &self) -> std::function, Request *, FrameBuffer *)> { + auto cm = gCameraManager.lock(); + assert(cm); + + return cm->getBufferCompleted(&self); + }, + [](Camera &self, std::function, Request *, FrameBuffer *)> f) { + auto cm = gCameraManager.lock(); + assert(cm); + + cm->setBufferCompleted(&self, f); + + self.bufferCompleted.disconnect(); + + if (!f) + return; + + self.bufferCompleted.connect(&self, [cm, camera=self.shared_from_this()](Request *req, FrameBuffer *fb) { + cm->handleBufferCompleted(camera, req, fb); + }); + }) + + .def_property("disconnected", + [](Camera &self) -> std::function)> { + auto cm = gCameraManager.lock(); + assert(cm); + + return cm->getDisconnected(&self); + }, + [](Camera &self, std::function)> f) { + auto cm = gCameraManager.lock(); + assert(cm); + + cm->setDisconnected(&self, f); + + self.disconnected.disconnect(); + + if (!f) + return; + + self.disconnected.connect(&self, [cm, camera=self.shared_from_this()]() { + cm->handleDisconnected(camera); + }); + }) + .def("__str__", [](Camera &self) { return ""; }) From patchwork Thu Jun 23 14:47:36 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16345 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 B8126BD808 for ; Thu, 23 Jun 2022 14:48:08 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 71F3665637; Thu, 23 Jun 2022 16:48:08 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1655995688; bh=kIO3WgRrLWjCEOq4w3LCDR5Kbdy/9I5vKkQ+ah1/vwA=; 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=iikUDItzd5xTmucTFvJYdpqnppmBvwFEUReHMReEB/msjS8n0qWpMuM1MAf4d99Bb cqYeSMEnBASQ0jU3J/lxfsqG9LtIVbnbOwJc0zvhPNPEJNlyuRxsj2xvrvnFq+C0r+ EpCRU6T7ubRPjT+q+WMuLbSDslnt3UQoaD+wLvmKrfYb95wvaytKwKgxJ09B97dWTi c3+ry6BSRmgJGdM6Bpl3T+HfAqNNRgdwd5mUx5eDwtMFJczsr6NmG2b0eHf4unbTmG GLQeVPm9O0SoRgNhQoGLNq+yuC4hXVgZKbwqHsCamerc5V5QDgh+qnIu39tjYL4JlY uw23j2swCYnFA== 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 21E886563D for ; Thu, 23 Jun 2022 16:48:01 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="h9Ms2c4+"; dkim-atps=neutral Received: from deskari.lan (91-158-154-79.elisa-laajakaista.fi [91.158.154.79]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8AC2DDD; Thu, 23 Jun 2022 16:48:00 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1655995680; bh=kIO3WgRrLWjCEOq4w3LCDR5Kbdy/9I5vKkQ+ah1/vwA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=h9Ms2c4+E88kZ+1Dy1VIsq14Q+GUhfhg5+959GFcWxa3Wtu49nDlMDB8lgne0utUY EGfDqysGq2tAWleR5U2qNxOq78uSFdZ8nFtiAGbDrnusKH5W58ru8xGFwva28ris1d cm4qhWXw3nAaAg7xyrCk5MI0ynwU/UaeseL/siRU= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Thu, 23 Jun 2022 17:47:36 +0300 Message-Id: <20220623144736.78537-8-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220623144736.78537-1-tomi.valkeinen@ideasonboard.com> References: <20220623144736.78537-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [RFC v1 7/7] py: cam.py: Use new events support 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: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- src/py/cam/cam.py | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py index 733e9ae5..538f0c1d 100755 --- a/src/py/cam/cam.py +++ b/src/py/cam/cam.py @@ -243,11 +243,7 @@ class CaptureState: # Called from renderer when there is a libcamera event def event_handler(self): try: - reqs = self.cm.get_ready_requests() - - for req in reqs: - ctx = next(ctx for ctx in self.contexts if ctx.idx == req.cookie) - self.__request_handler(ctx, req) + self.cm.dispatch_events() running = any(ctx.reqs_completed < ctx.opt_capture for ctx in self.contexts) return running @@ -255,7 +251,9 @@ class CaptureState: traceback.print_exc() return False - def __request_handler(self, ctx, req): + def __request_handler(self, cam, req): + ctx = next(ctx for ctx in self.contexts if ctx.camera == cam) + if req.status != libcam.Request.Status.Complete: raise Exception('{}: Request failed: {}'.format(ctx.id, req.status)) @@ -310,6 +308,9 @@ class CaptureState: ctx.camera.queue_request(req) ctx.reqs_queued += 1 + def __disconnect_handler(self, cam): + print('Camera', cam, 'disconnected') + def __capture_init(self): for ctx in self.contexts: ctx.acquire() @@ -323,6 +324,15 @@ class CaptureState: for ctx in self.contexts: ctx.create_requests() + for ctx in self.contexts: + # These cause circular dependencies: + # The callback in the camera points to CaptureState. + # The CaptureState points to the camera. + # This can be solved by setting the callbacks to None after stopping + # or using lambdas here and weakref.WeakMethod. + ctx.camera.request_completed = self.__request_handler + ctx.camera.disconnected = self.__disconnect_handler + def __capture_start(self): for ctx in self.contexts: ctx.start() @@ -334,6 +344,10 @@ class CaptureState: for ctx in self.contexts: ctx.stop() + for ctx in self.contexts: + ctx.camera.request_completed = None + ctx.camera.disconnected = None + for ctx in self.contexts: ctx.release() @@ -402,6 +416,9 @@ def main(): cm = libcam.CameraManager.singleton() + cm.camera_added = lambda c: print("Camera added:", c) + cm.camera_removed = lambda c: print("Camera removed:", c) + if args.list: do_cmd_list(cm) @@ -459,6 +476,8 @@ def main(): state.do_cmd_capture() + cm.discard_events() + return 0