From patchwork Fri Sep 18 15:20:16 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 9668 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 D9B7FBF01C for ; Fri, 18 Sep 2020 15:21:00 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D16BC62FC4; Fri, 18 Sep 2020 17:20:59 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="TGWZRegZ"; dkim-atps=neutral Received: from mail-lj1-x22c.google.com (mail-lj1-x22c.google.com [IPv6:2a00:1450:4864:20::22c]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id CB4F162FAB for ; Fri, 18 Sep 2020 17:20:58 +0200 (CEST) Received: by mail-lj1-x22c.google.com with SMTP id u21so5405249ljl.6 for ; Fri, 18 Sep 2020 08:20:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=sender:from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=y6ntxVWpoyf5iI1ZJtT/kEJ6lazIu5I/vZpPPi61OUs=; b=TGWZRegZE8ZlxiZF97siEM+mF7HSqDO3grBy/PvEvtfJREGHL+Mtdl4a1n7cLqMCcE Okk08QWIyJqIxB/9XkoryqmX+f9sLPvXMJ/y/0wyVmizZh0kEBG2JnhNEoyelfYhNdBt Ce3GxcHvJgIMTkKDA2XyX+2Z1wCjJ5eVxKEiA2FUqMxjBZqYgmyao7wqZ8Bi7VGfzTtI hKUbCBJN62FLAVb/j9Xqc1gvR6a0FO9yvg97nISQCMAaxScyW/NQONRIOAEy49AZB6aO 2sXM+LBUwNHBpXh0cvdWOiLoWQPy6zS4LaavZCF+neDzpM8UmoMBpZJqIe17mtnUvQWk Njqg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:from:to:cc:subject:date:message-id :in-reply-to:references:mime-version:content-transfer-encoding; bh=y6ntxVWpoyf5iI1ZJtT/kEJ6lazIu5I/vZpPPi61OUs=; b=JcnpSKzGRIv28/wQSaGYApG1horsyT7RAshTOEnUO0sPZwJzHTxx9mpvm8Zuxkd4bL jpawiy06jFN6u8Q8/jvocNj0dfOonoJbAFeO/+GSKtpmRZjKCeAUJbfCNSETaMwZlLS0 qVA0b4yKEg35Dj6r1ZI5gJZHikbDS4oTy1WG22sVnGUUJhUsAG7KMgAQM3F4KWgi259U 1HOb4tB1lksBK0bFeol3THQoV10Ldt9fTu6Gm1BtJJkO0BtcGro6WLhxTkLaOri6Q2Zd ivf737EozfOrP/Ef4hTMr/yqbHGOntdxHasmJJ/zmZfHqN4XV/EKspBER7fTTiSY7h/X nvRQ== X-Gm-Message-State: AOAM531dYRAFG9YOEju6c+LOcaA+38xo5vAEtGRuFf4hluVazYn3i90i zSrYCIAO5lJn5f3sOwos+fLHlXWqoZQd3w== X-Google-Smtp-Source: ABdhPJxk+puIvoSzBJtTnVZqRVww/MkdZ11fmanmhx5mNmKvsyzZD/gatnMPl0xk7EX8I5F10+0vIg== X-Received: by 2002:a2e:8e71:: with SMTP id t17mr11339261ljk.413.1600442457931; Fri, 18 Sep 2020 08:20:57 -0700 (PDT) Received: from deskari.ti.com (91-152-83-50.elisa-laajakaista.fi. [91.152.83.50]) by smtp.gmail.com with ESMTPSA id i63sm666472lji.66.2020.09.18.08.20.56 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 18 Sep 2020 08:20:57 -0700 (PDT) From: Tomi Valkeinen To: libcamera-devel@lists.libcamera.org Date: Fri, 18 Sep 2020 18:20:16 +0300 Message-Id: <20200918152019.784315-2-tomi.valkeinen@iki.fi> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20200918152019.784315-1-tomi.valkeinen@iki.fi> References: <20200918152019.784315-1-tomi.valkeinen@iki.fi> MIME-Version: 1.0 Subject: [libcamera-devel] [RFC 1/4] Add pybind11 submodule 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: , Cc: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Signed-off-by: Tomi Valkeinen --- .gitmodules | 3 +++ ext/pybind11 | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 ext/pybind11 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c6d1083 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "ext/pybind11"] + path = ext/pybind11 + url = https://github.com/pybind/pybind11.git diff --git a/ext/pybind11 b/ext/pybind11 new file mode 160000 index 0000000..3b1dbeb --- /dev/null +++ b/ext/pybind11 @@ -0,0 +1 @@ +Subproject commit 3b1dbebabc801c9cf6f0953a4c20b904d444f879 From patchwork Fri Sep 18 15:20:17 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 9669 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 58EE8BF01C for ; Fri, 18 Sep 2020 15:21:02 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2486B62FCF; Fri, 18 Sep 2020 17:21:02 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="h3wXPs/q"; dkim-atps=neutral Received: from mail-lf1-x130.google.com (mail-lf1-x130.google.com [IPv6:2a00:1450:4864:20::130]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 55AC362F4F for ; Fri, 18 Sep 2020 17:20:59 +0200 (CEST) Received: by mail-lf1-x130.google.com with SMTP id b12so6515392lfp.9 for ; Fri, 18 Sep 2020 08:20:59 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=sender:from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=KsNV7hPubnwpAA0/Hbjp4VcYJ38B/XQfNuqba0A+rgc=; b=h3wXPs/qOo2L/FP6P90EnrZL7ClyoRz7vMNtTTpxTeFHtHfkU8uJ+gh2TQyQ6/yZhB A7+rlKd48Pu+OnWNFOfvdGadSenXP7R+iHVGQVGzszsz1LhwA5ZAUcWQD5F/97oxGuIm 5clgfHZ2rkry6B6ocEu56XYo0RLsJ3j+xAx2Je0fYJcs3+VBRgWAt5KcGeWl7lQDbYax rEeIfbPL0DOd6q7jo1aQe0eJQ772AWFpfDXwZPi19DkNJIwhwK8i4DXkepL+VPK0MEQu LWOKzLIvcNeQYZJaU/rWEbAj/jw3ERpFv/NUJVSAzwcMkcQfzue2vrf2RUC+ACZxLkaI TAWw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:from:to:cc:subject:date:message-id :in-reply-to:references:mime-version:content-transfer-encoding; bh=KsNV7hPubnwpAA0/Hbjp4VcYJ38B/XQfNuqba0A+rgc=; b=Q2kBK2Nm/1Qf6587h5V6+eeN9fqMnmWBnPo70RYygMWh9lJatvmDza9WNFxoD9LEaN RkEwBbt7bAsgDwhAae/Q0JaqkGCwS69/Hz3kyVe0InOeoDs0RkUbwnfjsePfAlq30XfY 8LNwaDh/VduOCt7NbzJ3q68XJBa755SQJQ6eWQmyBgcQViUE+9/jupS4XBxdNAXbD8iT dolB1V++2pveMWUnrc+NMQ1dBJZP3jCHmMp5ONhcleOahMIhZ2gtfwLvLK65hP/75XuC E/xZq0PTvFV1rta401s4fpVafvgp/+nmrsojq80s42+NW0TY1lujvCRMUqgmG7Wlwvmf UCQQ== X-Gm-Message-State: AOAM531hSD/U2u2Hvg1yZ7Lg8jlCCZljAbNaDmRcZLFcJJfnAxVxaoe9 0ETE3zcPiA/V3a/8NS+mpYDE3aBbuOWYTA== X-Google-Smtp-Source: ABdhPJzTbFcNZdaZUR9T2/GY3pcCIqK3qU6Iaj2uSgr5ombjd9lWqHtBBYf+nKadO/XPi9uAag7itQ== X-Received: by 2002:a19:b97:: with SMTP id 145mr12082108lfl.269.1600442458620; Fri, 18 Sep 2020 08:20:58 -0700 (PDT) Received: from deskari.ti.com (91-152-83-50.elisa-laajakaista.fi. [91.152.83.50]) by smtp.gmail.com with ESMTPSA id i63sm666472lji.66.2020.09.18.08.20.58 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 18 Sep 2020 08:20:58 -0700 (PDT) From: Tomi Valkeinen To: libcamera-devel@lists.libcamera.org Date: Fri, 18 Sep 2020 18:20:17 +0300 Message-Id: <20200918152019.784315-3-tomi.valkeinen@iki.fi> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20200918152019.784315-1-tomi.valkeinen@iki.fi> References: <20200918152019.784315-1-tomi.valkeinen@iki.fi> MIME-Version: 1.0 Subject: [libcamera-devel] [RFC 2/4] Add BoundMethodFunction 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: , Cc: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Note: no way to disconnect, except disconnect all. Signed-off-by: Tomi Valkeinen --- include/libcamera/bound_method.h | 25 +++++++++++++++++++++++++ include/libcamera/signal.h | 6 ++++++ 2 files changed, 31 insertions(+) diff --git a/include/libcamera/bound_method.h b/include/libcamera/bound_method.h index d1e4448..d720199 100644 --- a/include/libcamera/bound_method.h +++ b/include/libcamera/bound_method.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace libcamera { @@ -228,6 +229,30 @@ private: R (*func_)(Args...); }; +template +class BoundMethodFunction : public BoundMethodArgs +{ +public: + BoundMethodFunction(std::function func) + : BoundMethodArgs(nullptr, nullptr, ConnectionTypeAuto), + func_(func) + { + } + + R activate(Args... args, [[maybe_unused]] bool deleteMethod = false) override + { + return func_(args...); + } + + R invoke(Args...) override + { + return R(); + } + +private: + std::function func_; +}; + } /* namespace libcamera */ #endif /* __LIBCAMERA_BOUND_METHOD_H__ */ diff --git a/include/libcamera/signal.h b/include/libcamera/signal.h index accb797..939169f 100644 --- a/include/libcamera/signal.h +++ b/include/libcamera/signal.h @@ -68,6 +68,12 @@ public: SignalBase::connect(new BoundMethodStatic(func)); } + template + void connect(std::function func) + { + SignalBase::connect(new BoundMethodFunction(func)); + } + void disconnect() { SignalBase::disconnect([]([[maybe_unused]] SlotList::iterator &iter) { From patchwork Fri Sep 18 15:20:18 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 9670 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 D4DB3BF01C for ; Fri, 18 Sep 2020 15:21:02 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7D20262FD2; Fri, 18 Sep 2020 17:21:02 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="jO9PqtG0"; dkim-atps=neutral Received: from mail-lf1-x12f.google.com (mail-lf1-x12f.google.com [IPv6:2a00:1450:4864:20::12f]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0FD8562FCA for ; Fri, 18 Sep 2020 17:21:00 +0200 (CEST) Received: by mail-lf1-x12f.google.com with SMTP id y17so6538404lfa.8 for ; Fri, 18 Sep 2020 08:21:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=sender:from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=+KkrZj+Dl4+mETvjskRQsD4wDGRHpd+x6BOuY6WTd6Q=; b=jO9PqtG04aDr5MMCQJEDuG7nSPtehBmATbUpbmcXwNI9nklGnzOxEqGgF0vbu5B9bJ YEkRIN4FE51ez41nq+AsxqiOD8j8O3vA+LAxtSstOOzNxuOBJu4PrSxiR3lfWPX/eIng o3GTAkd5km9yJSwnCgbhAbY2Tkae5kJpFDreoYUWT5qAnwTIxZIcUAMEHVGTAEFLwbPH sX2dF5fKei8NcZ8ozIwVPGO2jpVPiH+cvJ5UWDtM2Nats9MlPQwOn9FpUziNrnB7tZf+ 03xcxUtoO9xRJKA6Kr/duqB1BeDZYD6zJd86Ic8dJqjmFquaQXf1As4dSlVRIFH9xscp dsTg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:from:to:cc:subject:date:message-id :in-reply-to:references:mime-version:content-transfer-encoding; bh=+KkrZj+Dl4+mETvjskRQsD4wDGRHpd+x6BOuY6WTd6Q=; b=OZNFhj5MQyGpvJIRE3iXpCBpky8CjA6GnSYN1d50x+i/uYcr2WLjwDzapbuxI3s6nJ lTQcj/10GwUdoYWm4CSjjk3Tn2bx99gOA7w4dPw1BmrgbM9LexvWllAQHV66RKOSBR5U fC3MM5k0VZHZNIKmSTynUgPbifPy5JuwS9oO+eMjf2WVrmoyd0f2C81Bey4GOkh55OYg zhKphlibLp4Z6UyR/j8u2TzXuimaCCF5YNqgr308PhQ2daLl0Ju5ETC73wtd+gPwBSJg zNW//bB8rKeqPwCuHd9eh52blhpsFXPNajJ4Pjv0Q8YcdD+UpKueyxRRWqpk6ybhf9Yi wXgg== X-Gm-Message-State: AOAM532hHFFHC8LbWP4uDLZdYt28zsXYYCtKHAQ7j8Zx1Q6+ia+OCi2c QMEkzyupv5RQZoK/GLGjFv/gHdbCwjThGQ== X-Google-Smtp-Source: ABdhPJwvzJqZuqodZZkddQ10cU6QHb8Gkgg+yDDfROqwHgDUKIIdnEDrazxz1ZdN0nnBOvpDzEf9qQ== X-Received: by 2002:a19:7912:: with SMTP id u18mr11964535lfc.298.1600442459393; Fri, 18 Sep 2020 08:20:59 -0700 (PDT) Received: from deskari.ti.com (91-152-83-50.elisa-laajakaista.fi. [91.152.83.50]) by smtp.gmail.com with ESMTPSA id i63sm666472lji.66.2020.09.18.08.20.58 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 18 Sep 2020 08:20:58 -0700 (PDT) From: Tomi Valkeinen To: libcamera-devel@lists.libcamera.org Date: Fri, 18 Sep 2020 18:20:18 +0300 Message-Id: <20200918152019.784315-4-tomi.valkeinen@iki.fi> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20200918152019.784315-1-tomi.valkeinen@iki.fi> References: <20200918152019.784315-1-tomi.valkeinen@iki.fi> MIME-Version: 1.0 Subject: [libcamera-devel] [RFC 3/4] hack: Camera public destructor 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: , Cc: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Not clear why we need this with pybind11... Signed-off-by: Tomi Valkeinen --- include/libcamera/camera.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h index 272c12c..d50b7ca 100644 --- a/include/libcamera/camera.h +++ b/include/libcamera/camera.h @@ -99,10 +99,10 @@ public: int start(); int stop(); + ~Camera(); private: Camera(PipelineHandler *pipe, const std::string &id, const std::set &streams); - ~Camera(); class Private; std::unique_ptr p_; From patchwork Fri Sep 18 15:20:19 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 9671 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 3A8DEC3B5D for ; Fri, 18 Sep 2020 15:21:03 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E5DF362FCD; Fri, 18 Sep 2020 17:21:02 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="qV8sDXVM"; dkim-atps=neutral Received: from mail-lf1-x136.google.com (mail-lf1-x136.google.com [IPv6:2a00:1450:4864:20::136]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7411B62FAB for ; Fri, 18 Sep 2020 17:21:01 +0200 (CEST) Received: by mail-lf1-x136.google.com with SMTP id d15so6511496lfq.11 for ; Fri, 18 Sep 2020 08:21:01 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=sender:from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=1E1P1JfouIvoqAcSQcz8mxEV25Kx5h+CZg32vriOSpY=; b=qV8sDXVMUf3mfv7Oqjk/UJNowN9u628qXbnEHmoNHTI8z2s1oPp2Nxkw3UhHoWWa4e wXqhqwRVwt+Foo7355MwM7iE6LciWAlxDcHYa8VTPBLBZ8Ll7n2O1ZD8jW6Mw3Ju65KJ oS2SMxwuM44c5t859GpxwSMNP1CC+3+IXI+GuLvH82rn1y6NUEfG6ZomDGEXT9+Q4I7S /GKwcTXcqchV8tdrMmhs5DvjMWWXQ1Kjbi8eEtp7FxSA1yVeRAlCTDD9aVH7c2rMzvCf NKiRIJslvuQPi6ej40T3ICqGjmazhHqcFpYeoNcqPFPTMH5E8j2aGRmV64qm4f4caXXd XgGw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:from:to:cc:subject:date:message-id :in-reply-to:references:mime-version:content-transfer-encoding; bh=1E1P1JfouIvoqAcSQcz8mxEV25Kx5h+CZg32vriOSpY=; b=LW2OJz7JKSA969AvcFquHa2OIrKU8cWsvEqdr2PWR6ElIV4y0nbW6rgdsLL3cak5ji 0riDSyZOmAA62GhL0R21QeKywiDpmQst1mQjpV2iqWaiSKXpeBB09ccuWL6i/4CkZyZg 7T8iUpD9oG2+8qj5Meuh93vapj0++LJ1gauCb260pkonbgdjfOzIH3tAdk6lIV6jOWyp TVIEKHrtvBtgUGT6NCuGHB6UkCBKAqA0B3kXnIpZYxEs7XfRukk2Kenuj4W8YEv+jrGE 8IkUEtIO3eyUtu76TU8reIEJys/YW/BnemKj0O/OGi5vhzf813g5+TxvQn+D8fOoB7J/ TKlw== X-Gm-Message-State: AOAM531HYelRHwS6i1H9nDvYQyQ2MYxCahnIN+mwxLePy8OfK+ADh7pN +16EmvD66asmBu2VgXzZY7Io8Lvg07Ytmg== X-Google-Smtp-Source: ABdhPJyp2/BRuDvRBCqWi9hQEZ2tWCTBSdQ+pb1a5MZWLE8eswextC1isIre11DB8avLEKGjBaFbvg== X-Received: by 2002:a05:6512:1051:: with SMTP id c17mr12286412lfb.20.1600442460389; Fri, 18 Sep 2020 08:21:00 -0700 (PDT) Received: from deskari.ti.com (91-152-83-50.elisa-laajakaista.fi. [91.152.83.50]) by smtp.gmail.com with ESMTPSA id i63sm666472lji.66.2020.09.18.08.20.59 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 18 Sep 2020 08:20:59 -0700 (PDT) From: Tomi Valkeinen To: libcamera-devel@lists.libcamera.org Date: Fri, 18 Sep 2020 18:20:19 +0300 Message-Id: <20200918152019.784315-5-tomi.valkeinen@iki.fi> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20200918152019.784315-1-tomi.valkeinen@iki.fi> References: <20200918152019.784315-1-tomi.valkeinen@iki.fi> MIME-Version: 1.0 Subject: [libcamera-devel] [RFC 4/4] libcamera python bindings 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: , Cc: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Main issues: - Memory management in general. Who owns the object, how to pass ownership, etc. - Specifically, Request is currently broken. We can't, afaik, pass ownership around. So currently Python never frees a Request, and if the Request is not given to Camera::queueRequest, it will leak. - The forced threading causes some headache. Need to take care to use gil_scoped_release when C++ context can invoke callbacks, and gil_scoped_acquire at the invoke wrapper. - Callbacks. Difficult to attach context to the callbacks. I solved it with BoundMethodFunction and using lambda captures - Need public Camera destructor. It is not clear to me why C++ allows it to be private, but pybind11 doesn't. Signed-off-by: Tomi Valkeinen --- meson.build | 1 + meson_options.txt | 2 + py/meson.build | 1 + py/pycamera/__init__.py | 29 ++++++ py/pycamera/meson.build | 35 +++++++ py/pycamera/pymain.cpp | 169 +++++++++++++++++++++++++++++++ py/test/run-valgrind.sh | 3 + py/test/run.sh | 3 + py/test/test.py | 177 +++++++++++++++++++++++++++++++++ py/test/valgrind-pycamera.supp | 17 ++++ 10 files changed, 437 insertions(+) create mode 100644 py/meson.build create mode 100644 py/pycamera/__init__.py create mode 100644 py/pycamera/meson.build create mode 100644 py/pycamera/pymain.cpp create mode 100755 py/test/run-valgrind.sh create mode 100755 py/test/run.sh create mode 100755 py/test/test.py create mode 100644 py/test/valgrind-pycamera.supp diff --git a/meson.build b/meson.build index c58d458..3d1c797 100644 --- a/meson.build +++ b/meson.build @@ -104,6 +104,7 @@ libcamera_includes = include_directories('include') subdir('include') subdir('src') subdir('utils') +subdir('py') # The documentation and test components are optional and can be disabled # through configuration values. They are enabled by default. diff --git a/meson_options.txt b/meson_options.txt index d2e07ef..45b88b6 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -32,3 +32,5 @@ option('v4l2', type : 'boolean', value : false, description : 'Compile the V4L2 compatibility layer') + +option('pycamera', type : 'feature', value : 'auto') diff --git a/py/meson.build b/py/meson.build new file mode 100644 index 0000000..42ffa22 --- /dev/null +++ b/py/meson.build @@ -0,0 +1 @@ +subdir('pycamera') diff --git a/py/pycamera/__init__.py b/py/pycamera/__init__.py new file mode 100644 index 0000000..c37571b --- /dev/null +++ b/py/pycamera/__init__.py @@ -0,0 +1,29 @@ +from .pycamera import * +from enum import Enum +import os +import struct +import mmap + +# Add a wrapper which returns an array of Cameras, which have keep-alive to the CameraManager +def __CameraManager__cameras(self): + cameras = [] + for i in range(self.num_cameras): + cameras.append(self.at(i)) + return cameras + + +CameraManager.cameras = property(__CameraManager__cameras) + +# Add a wrapper which returns an array of buffers, which have keep-alive to the FB allocator +def __FrameBufferAllocator__buffers(self, stream): + buffers = [] + for i in range(self.num_buffers(stream)): + buffers.append(self.at(stream, i)) + return buffers + +FrameBufferAllocator.buffers = __FrameBufferAllocator__buffers + +def __FrameBuffer__mmap(self, plane): + return mmap.mmap(self.fd(plane), self.length(plane), mmap.MAP_SHARED, mmap.PROT_READ) + +FrameBuffer.mmap = __FrameBuffer__mmap diff --git a/py/pycamera/meson.build b/py/pycamera/meson.build new file mode 100644 index 0000000..50bdfb8 --- /dev/null +++ b/py/pycamera/meson.build @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: CC0-1.0 + +py3_dep = dependency('python3', required : get_option('pycamera')) + +if py3_dep.found() == false + subdir_done() +endif + +pycamera_sources = files([ + 'pymain.cpp', +]) + +pycamera_deps = [ + libcamera_dep, + py3_dep, +] + +includes = [ + '../../ext/pybind11/include', +] + +destdir = get_option('libdir') + '/python' + py3_dep.version() + '/site-packages/pycamera' + +pycamera = shared_module('pycamera', + pycamera_sources, + install : true, + install_dir : destdir, + name_prefix : '', + include_directories : includes, + dependencies : pycamera_deps) + +# Copy __init__.py to build dir so that we can run without installing +configure_file(input: '__init__.py', output: '__init__.py', copy: true) + +install_data(['__init__.py'], install_dir: destdir) diff --git a/py/pycamera/pymain.cpp b/py/pycamera/pymain.cpp new file mode 100644 index 0000000..569423a --- /dev/null +++ b/py/pycamera/pymain.cpp @@ -0,0 +1,169 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace py = pybind11; + +using namespace std; +using namespace libcamera; + +PYBIND11_MODULE(pycamera, m) { + m.def("sleep", [](double s) { + py::gil_scoped_release release; + this_thread::sleep_for(std::chrono::duration(s)); + }); + + py::class_(m, "CameraManager") + // Call cm->start implicitly, as we can't use stop() either + .def(py::init([]() { + auto cm = make_unique(); + cm->start(); + return cm; + })) + + //.def("start", &CameraManager::start) + + // stop() cannot be called, as CameraManager expects all Camera instances to be released before calling stop + // and we can't have such requirement in python, especially as we have a keep-alive from Camera to CameraManager. + // So we rely on GC and the keep-alives. + //.def("stop", &CameraManager::stop) + + .def_property_readonly("num_cameras", [](CameraManager& cm) { return cm.cameras().size(); }) + .def("at", [](CameraManager& cm, unsigned int idx) { return cm.cameras()[idx]; }, py::keep_alive<0, 1>()) + ; + + py::class_>(m, "Camera") + .def_property_readonly("id", &Camera::id) + .def("acquire", &Camera::acquire) + .def("release", &Camera::release) + .def("start", &Camera::start) + .def("stop", [](shared_ptr& self) { + // Camera::stop can cause callbacks to be invoked, so we must release GIL + py::gil_scoped_release release; + self->stop(); + }) + .def("generateConfiguration", &Camera::generateConfiguration) + .def("configure", &Camera::configure) + + // XXX created requests MUST be queued to be freed, python will not free them + .def("createRequest", &Camera::createRequest, py::arg("cookie") = 0, py::return_value_policy::reference_internal) + .def("queueRequest", &Camera::queueRequest) + + .def_property("requestCompleted", + nullptr, + [](shared_ptr& self, function f) { + if (f) { + self->requestCompleted.connect(function([f = move(f)](Request* req) { + // Called from libcamera's internal thread, so need to get GIL + py::gil_scoped_acquire acquire; + f(req); + })); + } else { + // XXX Disconnects all, as we have no means to disconnect the specific std::function + self->requestCompleted.disconnect(); + } + } + ) + + .def_property("bufferCompleted", + nullptr, + [](shared_ptr& self, function f) { + if (f) { + self->bufferCompleted.connect(function([f = move(f)](Request* req, FrameBuffer* fb) { + // Called from libcamera's internal thread, so need to get GIL + py::gil_scoped_acquire acquire; + f(req, fb); + })); + } else { + // XXX Disconnects all, as we have no means to disconnect the specific std::function + self->bufferCompleted.disconnect(); + } + } + ) + + ; + + py::class_(m, "CameraConfiguration") + .def("at", (StreamConfiguration& (CameraConfiguration::*)(unsigned int))&CameraConfiguration::at, + py::return_value_policy::reference_internal) + .def("validate", &CameraConfiguration::validate) + .def_property_readonly("size", &CameraConfiguration::size) + .def_property_readonly("empty", &CameraConfiguration::empty) + ; + + py::class_(m, "StreamConfiguration") + .def("toString", &StreamConfiguration::toString) + .def_property_readonly("stream", &StreamConfiguration::stream, + py::return_value_policy::reference_internal) + .def_property("width", + [](StreamConfiguration& c) { return c.size.width; }, + [](StreamConfiguration& c, unsigned int w) { c.size.width = w; } + ) + .def_property("height", + [](StreamConfiguration& c) { return c.size.height; }, + [](StreamConfiguration& c, unsigned int h) { c.size.height = h; } + ) + .def_property("fmt", + [](StreamConfiguration& c) { return c.pixelFormat.toString(); }, + [](StreamConfiguration& c, string fmt) { c.pixelFormat = PixelFormat::fromString(fmt); } + ) + ; + + py::enum_(m, "StreamRole") + .value("StillCapture", StreamRole::StillCapture) + .value("StillCaptureRaw", StreamRole::StillCaptureRaw) + .value("VideoRecording", StreamRole::VideoRecording) + .value("Viewfinder", StreamRole::Viewfinder) + ; + + py::class_(m, "FrameBufferAllocator") + .def(py::init>(), py::keep_alive<1, 2>()) + .def("allocate", &FrameBufferAllocator::allocate) + .def("free", &FrameBufferAllocator::free) + .def("num_buffers", [](FrameBufferAllocator& fa, Stream* stream) { return fa.buffers(stream).size(); }) + .def("at", [](FrameBufferAllocator& fa, Stream* stream, unsigned int idx) { return fa.buffers(stream).at(idx).get(); }, + py::return_value_policy::reference_internal) + ; + + py::class_>(m, "FrameBuffer") + .def_property_readonly("metadata", &FrameBuffer::metadata, py::return_value_policy::reference_internal) + .def("length", [](FrameBuffer& fb, uint32_t idx) { + const FrameBuffer::Plane &plane = fb.planes()[idx]; + return plane.length; + }) + .def("fd", [](FrameBuffer& fb, uint32_t idx) { + const FrameBuffer::Plane &plane = fb.planes()[idx]; + return plane.fd.fd(); + }) + ; + + py::class_>(m, "Stream") + ; + + py::class_>(m, "Request") + .def("addBuffer", &Request::addBuffer) + .def_property_readonly("status", &Request::status) + .def_property_readonly("buffers", &Request::buffers) + ; + + + py::enum_(m, "RequestStatus") + .value("Pending", Request::RequestPending) + .value("Complete", Request::RequestComplete) + .value("Cancelled", Request::RequestCancelled) + ; + + py::class_(m, "FrameMetadata") + .def_property_readonly("sequence", [](FrameMetadata& data) { return data.sequence; }) + .def("bytesused", [](FrameMetadata& data, uint32_t idx) { return data.planes[idx].bytesused; }) + ; +} diff --git a/py/test/run-valgrind.sh b/py/test/run-valgrind.sh new file mode 100755 index 0000000..623188e --- /dev/null +++ b/py/test/run-valgrind.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +PYTHONMALLOC=malloc PYTHONPATH=../../build/debug/py valgrind --suppressions=valgrind-pycamera.supp --leak-check=full --show-leak-kinds=definite --gen-suppressions=yes python3 test.py $* diff --git a/py/test/run.sh b/py/test/run.sh new file mode 100755 index 0000000..035d3ea --- /dev/null +++ b/py/test/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +PYTHONPATH=../../build/debug/py python3 test.py $* diff --git a/py/test/test.py b/py/test/test.py new file mode 100755 index 0000000..0f874d3 --- /dev/null +++ b/py/test/test.py @@ -0,0 +1,177 @@ +#!/usr/bin/python3 + +import pycamera as pycam +import time +import binascii +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument("-n", "--num-frames", type=int, default=10) +parser.add_argument("-c", "--print-crc", action="store_true") +parser.add_argument("-s", "--save-frames", action="store_true") +parser.add_argument("-m", "--max-cameras", type=int) +args = parser.parse_args() + +cm = pycam.CameraManager() + +cameras = cm.cameras + +if len(cameras) == 0: + print("No cameras") + exit(0) + +print("Cameras:") +for c in cameras: + print(" {}".format(c.id)) + +contexts = [] + +for i in range(len(cameras)): + contexts.append({ "camera": cameras[i], "id": i }) + if args.max_cameras and args.max_cameras - 1 == i: + break + +for ctx in contexts: + ctx["camera"].acquire() + +def configure_camera(ctx): + camera = ctx["camera"] + + # Configure + + config = camera.generateConfiguration([pycam.StreamRole.Viewfinder]) + stream_config = config.at(0) + + #stream_config.width = 160; + #stream_config.height = 120; + #stream_config.fmt = "YUYV" + + print("Cam {}: stream config {}".format(ctx["id"], stream_config.toString())) + + camera.configure(config); + + # Allocate buffers + + stream = stream_config.stream + + allocator = pycam.FrameBufferAllocator(camera); + ret = allocator.allocate(stream) + if ret < 0: + print("Can't allocate buffers") + exit(-1) + + allocated = allocator.num_buffers(stream) + print("Cam {}: Allocated {} buffers for stream".format(ctx["id"], allocated)) + + # Create Requests + + requests = [] + buffers = allocator.buffers(stream) + + for buffer in buffers: + request = camera.createRequest() + if request == None: + print("Can't create request") + exit(-1) + + ret = request.addBuffer(stream, buffer) + if ret < 0: + print("Can't set buffer for request") + exit(-1) + + requests.append(request) + + ctx["allocator"] = allocator + ctx["requests"] = requests + + +def buffer_complete_cb(ctx, req, fb): + print("Cam {}: Buf {} Complete: {}".format(ctx["id"], ctx["bufs_completed"], req.status)) + + with fb.mmap(0) as b: + if args.print_crc: + crc = binascii.crc32(b) + print("Cam {}: CRC {:#x}".format(ctx["id"], crc)) + + if args.save_frames: + id = ctx["id"] + num = ctx["bufs_completed"] + filename = "frame-{}-{}.data".format(id, num) + with open(filename, "wb") as f: + f.write(b) + print("Cam {}: Saved {}".format(ctx["id"], filename)) + + ctx["bufs_completed"] += 1 + +def req_complete_cb(ctx, req): + camera = ctx["camera"] + + print("Cam {}: Req {} Complete: {}".format(ctx["id"], ctx["reqs_completed"], req.status)) + + bufs = req.buffers + for stream, fb in bufs.items(): + meta = fb.metadata + print("Cam {}: Buf seq {}, bytes {}".format(ctx["id"], meta.sequence, meta.bytesused(0))) + + ctx["reqs_completed"] += 1 + + if ctx["reqs_queued"] < args.num_frames: + request = camera.createRequest() + if request == None: + print("Can't create request") + exit(-1) + + for stream, fb in bufs.items(): + ret = request.addBuffer(stream, fb) + if ret < 0: + print("Can't set buffer for request") + exit(-1) + + camera.queueRequest(request) + ctx["reqs_queued"] += 1 + + +def setup_callbacks(ctx): + camera = ctx["camera"] + + ctx["reqs_queued"] = 0 + ctx["reqs_completed"] = 0 + ctx["bufs_completed"] = 0 + + camera.requestCompleted = lambda req, ctx = ctx: req_complete_cb(ctx, req) + camera.bufferCompleted = lambda req, fb, ctx = ctx: buffer_complete_cb(ctx, req, fb) + +def queue_requests(ctx): + camera = ctx["camera"] + requests = ctx["requests"] + + camera.start() + + for request in requests: + camera.queueRequest(request) + ctx["reqs_queued"] += 1 + + + +for ctx in contexts: + configure_camera(ctx) + setup_callbacks(ctx) + +for ctx in contexts: + queue_requests(ctx) + + +print("Processing...") + +# Need to release GIL here, so that callbacks can be called +while any(ctx["reqs_completed"] < args.num_frames for ctx in contexts): + pycam.sleep(0.1) + +print("Exiting...") + +for ctx in contexts: + camera = ctx["camera"] + camera.stop() + camera.release() + +print("Done") diff --git a/py/test/valgrind-pycamera.supp b/py/test/valgrind-pycamera.supp new file mode 100644 index 0000000..98c693f --- /dev/null +++ b/py/test/valgrind-pycamera.supp @@ -0,0 +1,17 @@ +{ + + Memcheck:Leak + match-leak-kinds: definite + fun:_Znwm + fun:_ZN8pybind116moduleC1EPKcS2_ + fun:PyInit_pycamera + fun:_PyImport_LoadDynamicModuleWithSpec + obj:/usr/bin/python3.8 + obj:/usr/bin/python3.8 + fun:PyVectorcall_Call + fun:_PyEval_EvalFrameDefault + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyEval_EvalFrameDefault + fun:_PyFunction_Vectorcall +}