From patchwork Sat Apr 4 00:44:28 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 3378 Return-Path: Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 19BE3605D1 for ; Sat, 4 Apr 2020 02:44:52 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="grsRgeuO"; dkim-atps=neutral Received: from pendragon.bb.dnainternet.fi (81-175-216-236.bb.dnainternet.fi [81.175.216.236]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7C9D755B; Sat, 4 Apr 2020 02:44:51 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1585961091; bh=N8Wd1RXvXaepZeynUnAt+46D7n8LgtQ8RidmVeAPp+c=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=grsRgeuOJksiBpGcZPg7DwjxlgXK5c5fjd7u9JhLG8a/dBsP/FmnsUCgTp8oKOv29 mNlWzzG5tAnOh2ZWVScXIe8guFykSWyhHb2nFCyftmkqpq6VIEh8Ryjyv7H7i8aqtm fIb2If9fcFMKhSWkm4J5pbYi3E1mzg54k0PuAElc= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Cc: Martijn Braam , Benjamin GAIGNARD , Andrey Konovalov Date: Sat, 4 Apr 2020 03:44:28 +0300 Message-Id: <20200404004438.17992-2-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.24.1 In-Reply-To: <20200404004438.17992-1-laurent.pinchart@ideasonboard.com> References: <20200404004438.17992-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 01/11] [DNI] include: linux: Extend VIDIOC_ENUM_FMT to support MC-centric devices 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-List-Received-Date: Sat, 04 Apr 2020 00:44:52 -0000 This is an experimental change that hasn't been accepted in mainline yet. The commit corresponds to the userspace API extensions from v6 1/5 and 2/5, as posted to the linux-media mailing list ([1]). [1] https://lore.kernel.org/linux-media/20200319004701.30416-1-laurent.pinchart@ideasonboard.com/ Signed-off-by: Laurent Pinchart --- include/linux/videodev2.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/linux/videodev2.h b/include/linux/videodev2.h index ab40b3272ed2..66f34fc05e3e 100644 --- a/include/linux/videodev2.h +++ b/include/linux/videodev2.h @@ -476,6 +476,8 @@ struct v4l2_capability { #define V4L2_CAP_TOUCH 0x10000000 /* Is a touch device */ +#define V4L2_CAP_IO_MC 0x20000000 /* Is input/output controlled by the media controller */ + #define V4L2_CAP_DEVICE_CAPS 0x80000000 /* sets device capabilities field */ /* @@ -769,7 +771,8 @@ struct v4l2_fmtdesc { __u32 flags; __u8 description[32]; /* Description string */ __u32 pixelformat; /* Format fourcc */ - __u32 reserved[4]; + __u32 mbus_code; /* Media bus code */ + __u32 reserved[3]; }; #define V4L2_FMT_FLAG_COMPRESSED 0x0001 From patchwork Sat Apr 4 00:44:29 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 3379 Return-Path: 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 97F16605D1 for ; Sat, 4 Apr 2020 02:44:52 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="DWb1CjQ8"; dkim-atps=neutral Received: from pendragon.bb.dnainternet.fi (81-175-216-236.bb.dnainternet.fi [81.175.216.236]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 0C5E972E; Sat, 4 Apr 2020 02:44:51 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1585961092; bh=GJot6HF1SG9eiADRdo2WwxIhwXYKPWyxLEI7qzHNHyk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=DWb1CjQ8qGHe7a3jrBPdRhp9DrlJq6zJMwStqoWbs1iG05eyd/704TOkJCPFfFPt6 cODMq0+l8evj+q/cWMVXXrHN62/sHrsyjQH7ku5T+pFYmOTYjYfblfrSVudsm2MElc MAbOGDLAC1ZTRNbrQiZh/IYbMbMVwFaiYHLmusTE= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Cc: Martijn Braam , Benjamin GAIGNARD , Andrey Konovalov Date: Sat, 4 Apr 2020 03:44:29 +0300 Message-Id: <20200404004438.17992-3-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.24.1 In-Reply-To: <20200404004438.17992-1-laurent.pinchart@ideasonboard.com> References: <20200404004438.17992-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 02/11] libcamera: utils: Add string join function 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-List-Received-Date: Sat, 04 Apr 2020 00:44:52 -0000 Add a utils::join() function to join elements of a container into a string, with a separator and an optional conversion function if the elements are not implicitly convertible to std::string. Signed-off-by: Laurent Pinchart Reviewed-by: Niklas Söderlund Reviewed-by: Jacopo Mondi --- Changes since v3: - Add a test for the conversion function --- src/libcamera/include/utils.h | 44 +++++++++++++++++++++++++++++++++++ src/libcamera/utils.cpp | 16 +++++++++++++ test/utils.cpp | 20 +++++++++++++++- 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/src/libcamera/include/utils.h b/src/libcamera/include/utils.h index cfa620f24581..242eeded9d50 100644 --- a/src/libcamera/include/utils.h +++ b/src/libcamera/include/utils.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -109,6 +110,49 @@ inline _hex hex(uint64_t value, unsigned int width) size_t strlcpy(char *dst, const char *src, size_t size); +#ifndef __DOXYGEN__ +template +std::string join(const Container &items, const std::string &sep, UnaryOp op) +{ + std::ostringstream ss; + bool first = true; + + for (typename Container::const_iterator it = std::begin(items); + it != std::end(items); ++it) { + if (!first) + ss << sep; + else + first = false; + + ss << op(*it); + } + + return ss.str(); +} + +template +std::string join(const Container &items, const std::string &sep) +{ + std::ostringstream ss; + bool first = true; + + for (typename Container::const_iterator it = std::begin(items); + it != std::end(items); ++it) { + if (!first) + ss << sep; + else + first = false; + + ss << *it; + } + + return ss.str(); +} +#else +template +std::string join(const Container &items, const std::string &sep, UnaryOp op = nullptr); +#endif + namespace details { class StringSplitter diff --git a/src/libcamera/utils.cpp b/src/libcamera/utils.cpp index 58ee7cc19c8c..97f9b632e45b 100644 --- a/src/libcamera/utils.cpp +++ b/src/libcamera/utils.cpp @@ -297,6 +297,22 @@ details::StringSplitter::iterator details::StringSplitter::end() const return iterator(this, std::string::npos); } +/** + * \fn template \ + * std::string utils::join(const Container &items, const std::string &sep, UnaryOp op) + * \brief Join elements of a container in a string with a separator + * \param[in] items The container + * \param[in] sep The separator to add between elements + * \param[in] op A function that converts individual elements to strings + * + * This function joins all elements in the \a items container into a string and + * returns it. The \a sep separator is added between elements. If the container + * elements are not implicitly convertible to std::string, the \a op function + * shall be provided to perform conversion of elements to std::string. + * + * \return A string that concatenates all elements in the container + */ + /** * \fn split(const std::string &str, const std::string &delim) * \brief Split a string based on a delimiter diff --git a/test/utils.cpp b/test/utils.cpp index 58816f153066..55ce9365a53e 100644 --- a/test/utils.cpp +++ b/test/utils.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include "test.h" #include "utils.h" @@ -99,7 +101,7 @@ protected: return TestFail; } - /* utils::split() test. */ + /* utils::join() and utils::split() test. */ std::vector elements = { "/bin", "/usr/bin", @@ -111,6 +113,11 @@ protected: for (const auto &element : elements) path += (path.empty() ? "" : ":") + element; + if (path != utils::join(elements, ":")) { + cerr << "utils::join() test failed" << endl; + return TestFail; + } + std::vector dirs; for (const auto &dir : utils::split(path, ":")) @@ -121,6 +128,17 @@ protected: return TestFail; } + /* utils::join() with conversion function test. */ + std::vector sizes = { { 0, 0 }, { 100, 100 } }; + s = utils::join(sizes, "/", [](const Size &size) { + return size.toString(); + }); + + if (s != "0x0/100x100") { + cerr << "utils::join() with conversion test failed" << endl; + return TestFail; + } + /* utils::dirname() tests. */ if (TestPass != testDirname()) return TestFail; From patchwork Sat Apr 4 00:44:30 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 3380 Return-Path: Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 29D64629B9 for ; Sat, 4 Apr 2020 02:44:53 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="pZr+jKT3"; dkim-atps=neutral Received: from pendragon.bb.dnainternet.fi (81-175-216-236.bb.dnainternet.fi [81.175.216.236]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 939E011C5; Sat, 4 Apr 2020 02:44:52 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1585961092; bh=c10gNrHAm+EO2q6JnAi1ds4eikzpT59BYjnc+NZNhs8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=pZr+jKT3PoYkRsusCJg3iD7rVlOwRQvOqd+7Q6rDD+ueVaRbB/EEFrIQs6qLPVVel KXeAOkLBRIpgUsoq97POPLTmQ5mkntvogfyJps2E8HGGEa+p9W5AE4tTM/DjW+xagh 3eMTRsuM5YhiexvMRxN2X24Ag6U7bho6k1Bq/mso= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Cc: Martijn Braam , Benjamin GAIGNARD , Andrey Konovalov Date: Sat, 4 Apr 2020 03:44:30 +0300 Message-Id: <20200404004438.17992-4-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.24.1 In-Reply-To: <20200404004438.17992-1-laurent.pinchart@ideasonboard.com> References: <20200404004438.17992-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 03/11] libcamera: v4l2_subdevice: Extend [gs]etFormat() to specify format type 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-List-Received-Date: Sat, 04 Apr 2020 00:44:54 -0000 The V4L2 subdevice API exposes ACTIVE and TRY formats, but the V4L2Subdevice getFormat() and setFormat() operations only apply on ACTIVE formats. Extend them to support TRY formats as well. Signed-off-by: Laurent Pinchart Reviewed-by: Niklas Söderlund --- Changes since v2: - Reference ActiveFormat and TryFormat correctly from documentation --- src/libcamera/include/v4l2_subdevice.h | 11 +++++++++-- src/libcamera/v4l2_subdevice.cpp | 25 +++++++++++++++++++++---- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/libcamera/include/v4l2_subdevice.h b/src/libcamera/include/v4l2_subdevice.h index 9c077674f997..4a04eadfb1f9 100644 --- a/src/libcamera/include/v4l2_subdevice.h +++ b/src/libcamera/include/v4l2_subdevice.h @@ -32,6 +32,11 @@ struct V4L2SubdeviceFormat { class V4L2Subdevice : public V4L2Device { public: + enum Whence { + ActiveFormat, + TryFormat, + }; + explicit V4L2Subdevice(const MediaEntity *entity); V4L2Subdevice(const V4L2Subdevice &) = delete; V4L2Subdevice &operator=(const V4L2Subdevice &) = delete; @@ -46,8 +51,10 @@ public: ImageFormats formats(unsigned int pad); - int getFormat(unsigned int pad, V4L2SubdeviceFormat *format); - int setFormat(unsigned int pad, V4L2SubdeviceFormat *format); + int getFormat(unsigned int pad, V4L2SubdeviceFormat *format, + Whence whence = ActiveFormat); + int setFormat(unsigned int pad, V4L2SubdeviceFormat *format, + Whence whence = ActiveFormat); static V4L2Subdevice *fromEntityName(const MediaDevice *media, const std::string &entity); diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp index 8b9da81e8ab3..f554d2385fff 100644 --- a/src/libcamera/v4l2_subdevice.cpp +++ b/src/libcamera/v4l2_subdevice.cpp @@ -95,6 +95,15 @@ const std::string V4L2SubdeviceFormat::toString() const * any device left open will be closed, and any resources released. */ +/** + * \enum V4L2Subdevice::Whence + * \brief Specify the type of format for getFormat() and setFormat() operations + * \var V4L2Subdevice::ActiveFormat + * \brief The format operation applies to ACTIVE formats + * \var V4L2Subdevice::TryFormat + * \brief The format operation applies to TRY formats + */ + /** * \brief Create a V4L2 subdevice from a MediaEntity using its device node * path @@ -184,12 +193,16 @@ ImageFormats V4L2Subdevice::formats(unsigned int pad) * \brief Retrieve the image format set on one of the V4L2 subdevice pads * \param[in] pad The 0-indexed pad number the format is to be retrieved from * \param[out] format The image bus format + * \param[in] whence The format to get, \ref V4L2Subdevice::ActiveFormat + * "ActiveFormat" or \ref V4L2Subdevice::TryFormat "TryFormat" * \return 0 on success or a negative error code otherwise */ -int V4L2Subdevice::getFormat(unsigned int pad, V4L2SubdeviceFormat *format) +int V4L2Subdevice::getFormat(unsigned int pad, V4L2SubdeviceFormat *format, + Whence whence) { struct v4l2_subdev_format subdevFmt = {}; - subdevFmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + subdevFmt.which = whence == ActiveFormat ? V4L2_SUBDEV_FORMAT_ACTIVE + : V4L2_SUBDEV_FORMAT_TRY; subdevFmt.pad = pad; int ret = ioctl(VIDIOC_SUBDEV_G_FMT, &subdevFmt); @@ -211,6 +224,8 @@ int V4L2Subdevice::getFormat(unsigned int pad, V4L2SubdeviceFormat *format) * \brief Set an image format on one of the V4L2 subdevice pads * \param[in] pad The 0-indexed pad number the format is to be applied to * \param[inout] format The image bus format to apply to the subdevice's pad + * \param[in] whence The format to set, \ref V4L2Subdevice::ActiveFormat + * "ActiveFormat" or \ref V4L2Subdevice::TryFormat "TryFormat" * * Apply the requested image format to the desired media pad and return the * actually applied format parameters, as \ref V4L2Subdevice::getFormat would @@ -218,10 +233,12 @@ int V4L2Subdevice::getFormat(unsigned int pad, V4L2SubdeviceFormat *format) * * \return 0 on success or a negative error code otherwise */ -int V4L2Subdevice::setFormat(unsigned int pad, V4L2SubdeviceFormat *format) +int V4L2Subdevice::setFormat(unsigned int pad, V4L2SubdeviceFormat *format, + Whence whence) { struct v4l2_subdev_format subdevFmt = {}; - subdevFmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + subdevFmt.which = whence == ActiveFormat ? V4L2_SUBDEV_FORMAT_ACTIVE + : V4L2_SUBDEV_FORMAT_TRY; subdevFmt.pad = pad; subdevFmt.format.width = format->size.width; subdevFmt.format.height = format->size.height; From patchwork Sat Apr 4 00:44:31 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 3381 Return-Path: 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 BC95262DA4 for ; Sat, 4 Apr 2020 02:44:53 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="EI9pPPzh"; dkim-atps=neutral Received: from pendragon.bb.dnainternet.fi (81-175-216-236.bb.dnainternet.fi [81.175.216.236]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 215DE1265; Sat, 4 Apr 2020 02:44:53 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1585961093; bh=n6kBxFpep6vevtnx3jm8XuuWOteokWZ6wvu3GXl/GzI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=EI9pPPzh1t1lwGXEQXg2kK4urMyK4DS7oN/QNwCOrQ1Tv8lbvawpM+V1CFdn/sJfk hRNcZn9yQd7Ar8RSYe+5dly84gxF+2iQZ9i7IAwzgpKVmRqvNJE3txYk82NQ8nNFxj eSMLKbFNouzYScTcYMOJLfttXCRzYnCTYb3z07Fw= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Cc: Martijn Braam , Benjamin GAIGNARD , Andrey Konovalov Date: Sat, 4 Apr 2020 03:44:31 +0300 Message-Id: <20200404004438.17992-5-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.24.1 In-Reply-To: <20200404004438.17992-1-laurent.pinchart@ideasonboard.com> References: <20200404004438.17992-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 04/11] libcamera: v4l2_subdevice: Don't use doxygen \ref needlessly 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-List-Received-Date: Sat, 04 Apr 2020 00:44:54 -0000 The doxygen \ref command is used to generate a reference to a function, while adding parentheses after the function name is enough. Simplify the documentation. Signed-off-by: Laurent Pinchart Reviewed-by: Niklas Söderlund --- src/libcamera/v4l2_subdevice.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp index f554d2385fff..c28e591ba51a 100644 --- a/src/libcamera/v4l2_subdevice.cpp +++ b/src/libcamera/v4l2_subdevice.cpp @@ -228,8 +228,7 @@ int V4L2Subdevice::getFormat(unsigned int pad, V4L2SubdeviceFormat *format, * "ActiveFormat" or \ref V4L2Subdevice::TryFormat "TryFormat" * * Apply the requested image format to the desired media pad and return the - * actually applied format parameters, as \ref V4L2Subdevice::getFormat would - * do. + * actually applied format parameters, as getFormat() would do. * * \return 0 on success or a negative error code otherwise */ From patchwork Sat Apr 4 00:44:32 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 3382 Return-Path: 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 3C19462DAF for ; Sat, 4 Apr 2020 02:44:54 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="AK1ZazhQ"; dkim-atps=neutral Received: from pendragon.bb.dnainternet.fi (81-175-216-236.bb.dnainternet.fi [81.175.216.236]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A89FF130D; Sat, 4 Apr 2020 02:44:53 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1585961094; bh=9w5UTR+aZxJ4jswklEa3wwomaex0lboSeIl20AObEAc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=AK1ZazhQDSb/xy4plfEjue8ipgL5gsPA6BNcKLE8MNKMyyPkPLjji7BQOHLjpOfNu ISBs6mC7UvczKXCRNZDaXUyTgTzTtRDjzFpSG/GEAIJB7+uTatR5IsitBz+jMiplAm PV+qrzbOVjl3ZDvl3c94kSccPbWfU6gd0qAd6RVU= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Cc: Martijn Braam , Benjamin GAIGNARD , Andrey Konovalov Date: Sat, 4 Apr 2020 03:44:32 +0300 Message-Id: <20200404004438.17992-6-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.24.1 In-Reply-To: <20200404004438.17992-1-laurent.pinchart@ideasonboard.com> References: <20200404004438.17992-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 05/11] libcamera: v4l2_videodevice: Support filtering formats by media bus code 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-List-Received-Date: Sat, 04 Apr 2020 00:44:55 -0000 Add support for the recent V4L2 extension to VIDIOC_ENUM_FMT that allows filtering pixel formats by media bus codes. Signed-off-by: Laurent Pinchart Reviewed-by: Niklas Söderlund --- src/libcamera/include/v4l2_videodevice.h | 4 ++-- src/libcamera/v4l2_videodevice.cpp | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/libcamera/include/v4l2_videodevice.h b/src/libcamera/include/v4l2_videodevice.h index 7d7c4a9e6ebd..7b011d3b4e87 100644 --- a/src/libcamera/include/v4l2_videodevice.h +++ b/src/libcamera/include/v4l2_videodevice.h @@ -207,7 +207,7 @@ public: int getFormat(V4L2DeviceFormat *format); int setFormat(V4L2DeviceFormat *format); - std::map> formats(); + std::map> formats(uint32_t code = 0); int setCrop(Rectangle *rect); int setCompose(Rectangle *rect); @@ -246,7 +246,7 @@ private: int getFormatSingleplane(V4L2DeviceFormat *format); int setFormatSingleplane(V4L2DeviceFormat *format); - std::vector enumPixelformats(); + std::vector enumPixelformats(uint32_t code); std::vector enumSizes(V4L2PixelFormat pixelFormat); int setSelection(unsigned int target, Rectangle *rect); diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp index eb33a68e50d6..f7b98272d247 100644 --- a/src/libcamera/v4l2_videodevice.cpp +++ b/src/libcamera/v4l2_videodevice.cpp @@ -973,16 +973,19 @@ int V4L2VideoDevice::setFormatSingleplane(V4L2DeviceFormat *format) /** * \brief Enumerate all pixel formats and frame sizes + * \param[in] code Restrict formats to this media bus code. * * Enumerate all pixel formats and frame sizes supported by the video device. + * If the \a code argument is not zero, only formats compatible with that media + * bus code will be enumerated. * * \return A list of the supported video device formats */ -std::map> V4L2VideoDevice::formats() +std::map> V4L2VideoDevice::formats(uint32_t code) { std::map> formats; - for (V4L2PixelFormat pixelFormat : enumPixelformats()) { + for (V4L2PixelFormat pixelFormat : enumPixelformats(code)) { std::vector sizes = enumSizes(pixelFormat); if (sizes.empty()) return {}; @@ -1000,15 +1003,22 @@ std::map> V4L2VideoDevice::formats() return formats; } -std::vector V4L2VideoDevice::enumPixelformats() +std::vector V4L2VideoDevice::enumPixelformats(uint32_t code) { std::vector formats; int ret; + if (code && !(caps_.device_caps() & V4L2_CAP_IO_MC)) { + LOG(V4L2, Error) + << "Media bus code filtering not supported by the device"; + return {}; + } + for (unsigned int index = 0; ; index++) { struct v4l2_fmtdesc pixelformatEnum = {}; pixelformatEnum.index = index; pixelformatEnum.type = bufferType_; + pixelformatEnum.mbus_code = code; ret = ioctl(VIDIOC_ENUM_FMT, &pixelformatEnum); if (ret) From patchwork Sat Apr 4 00:44:33 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 3383 Return-Path: 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 CCBF4629B9 for ; Sat, 4 Apr 2020 02:44:54 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="UWAsM77x"; dkim-atps=neutral Received: from pendragon.bb.dnainternet.fi (81-175-216-236.bb.dnainternet.fi [81.175.216.236]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 36D6113FE; Sat, 4 Apr 2020 02:44:54 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1585961094; bh=EnlEjUTU/RU991YO/TMd74+L5ujET8zMYBBjLys4/yM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=UWAsM77xeAdWlFiwNAIkITMUX2Hf3RBKXoCkzCpD5UnBdkgdqTmOmDHPDqSgYyhBE UJK8x+gotZIvwOp66bTId9BxYN+OI8Q4p/nvqKEChm2WdL+lS89VBkLkWprWQnTUFO 5DVCvjPDT2huquEClF53bUTifszWqmMKGv+TYTuM= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Cc: Martijn Braam , Benjamin GAIGNARD , Andrey Konovalov Date: Sat, 4 Apr 2020 03:44:33 +0300 Message-Id: <20200404004438.17992-7-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.24.1 In-Reply-To: <20200404004438.17992-1-laurent.pinchart@ideasonboard.com> References: <20200404004438.17992-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 06/11] libcamera: v4l2_videodevice: Expose the device capabilities 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-List-Received-Date: Sat, 04 Apr 2020 00:44:55 -0000 Add a caps() function that exposes the V4L2 capabilities for the device. This is useful for generic code that can't hardcode any a priori knowledge of the device, such as in a simple pipeline handler. Signed-off-by: Laurent Pinchart Reviewed-by: Niklas Söderlund Reviewed-by: Jacopo Mondi --- src/libcamera/include/v4l2_videodevice.h | 2 ++ src/libcamera/v4l2_videodevice.cpp | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/libcamera/include/v4l2_videodevice.h b/src/libcamera/include/v4l2_videodevice.h index 7b011d3b4e87..84d8d433b0ad 100644 --- a/src/libcamera/include/v4l2_videodevice.h +++ b/src/libcamera/include/v4l2_videodevice.h @@ -205,6 +205,8 @@ public: const char *deviceName() const { return caps_.card(); } const char *busName() const { return caps_.bus_info(); } + const V4L2Capability &caps() const { return caps_; } + int getFormat(V4L2DeviceFormat *format); int setFormat(V4L2DeviceFormat *format); std::map> formats(uint32_t code = 0); diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp index f7b98272d247..42b6c1ed1ef8 100644 --- a/src/libcamera/v4l2_videodevice.cpp +++ b/src/libcamera/v4l2_videodevice.cpp @@ -759,6 +759,12 @@ void V4L2VideoDevice::close() * \return The string containing the device location */ +/** + * \fn V4L2VideoDevice::caps() + * \brief Retrieve the device V4L2 capabilities + * \return The device V4L2 capabilities + */ + std::string V4L2VideoDevice::logPrefix() const { return deviceNode() + (V4L2_TYPE_IS_OUTPUT(bufferType_) ? "[out]" : "[cap]"); From patchwork Sat Apr 4 00:44:34 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 3384 Return-Path: Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5BCBF629B9 for ; Sat, 4 Apr 2020 02:44:55 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Kg6tYjyE"; dkim-atps=neutral Received: from pendragon.bb.dnainternet.fi (81-175-216-236.bb.dnainternet.fi [81.175.216.236]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id BE97414DA; Sat, 4 Apr 2020 02:44:54 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1585961095; bh=SLE8NyRGi69ia6Vcrm2HnZBLNbgV81C/Kfg5Chlui80=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Kg6tYjyE/3aisHLNCP96hHHTAnBey8WGIu9tTinBN8oaDp9eo7ltvGwOxItYzyiqL 12ocyqWMRY/niBaC23aIorai0RI78B5u9GLLgM9srYV3MN0JovqcITGYoGdaZGuZSY ydq87hgUmDdsG0Mh0m3eyB+MTCKwrmkWUKkCiJDU= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Cc: Martijn Braam , Benjamin GAIGNARD , Andrey Konovalov Date: Sat, 4 Apr 2020 03:44:34 +0300 Message-Id: <20200404004438.17992-8-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.24.1 In-Reply-To: <20200404004438.17992-1-laurent.pinchart@ideasonboard.com> References: <20200404004438.17992-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 07/11] libcamera: v4l2_videodevice: Downgrade 4CC conversion errors to warnings 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-List-Received-Date: Sat, 04 Apr 2020 00:44:55 -0000 Failing to convert between V4L2 and DRM 4CCs indicates something is likely wrong, but doesn't necessarily prevent the camera from being usable. It may for instance only limit the number of supported formats. Downgrade the related log messages from LogError to LogWarning. Signed-off-by: Laurent Pinchart Reviewed-by: Kieran Bingham Reviewed-by: Niklas Söderlund --- src/libcamera/v4l2_videodevice.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp index 42b6c1ed1ef8..5208e5a47148 100644 --- a/src/libcamera/v4l2_videodevice.cpp +++ b/src/libcamera/v4l2_videodevice.cpp @@ -1711,7 +1711,7 @@ PixelFormat V4L2VideoDevice::toPixelFormat(V4L2PixelFormat v4l2Fourcc) * class. Until we fix the logger, work around it. */ libcamera::_log(__FILE__, __LINE__, _LOG_CATEGORY(V4L2)(), - LogError).stream() + LogWarning).stream() << "Unsupported V4L2 pixel format " << v4l2Fourcc.toString(); return PixelFormat(); @@ -1799,7 +1799,8 @@ V4L2PixelFormat V4L2VideoDevice::toV4L2PixelFormat(const PixelFormat &pixelForma * \todo We can't use LOG() in a static method of a Loggable * class. Until we fix the logger, work around it. */ - libcamera::_log(__FILE__, __LINE__, _LOG_CATEGORY(V4L2)(), LogError).stream() + libcamera::_log(__FILE__, __LINE__, _LOG_CATEGORY(V4L2)(), + LogWarning).stream() << "Unsupported V4L2 pixel format " << pixelFormat.toString(); return {}; } From patchwork Sat Apr 4 00:44:35 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 3385 Return-Path: Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E7A61629B9 for ; Sat, 4 Apr 2020 02:44:55 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="IHUoYTgg"; dkim-atps=neutral Received: from pendragon.bb.dnainternet.fi (81-175-216-236.bb.dnainternet.fi [81.175.216.236]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 4ACA313C7; Sat, 4 Apr 2020 02:44:55 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1585961095; bh=eojVJlZ6geJjqQh0s1X8+sYEFg1dITg5+xxKxPU+w/M=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=IHUoYTggU3jvHHK42b8cs4EPOTOvVlKdsulQ2eVYjp913nRwPRSAQYy3kKQepszjd p6Nf0cSwX7RuZjHOJow9etqgw7vRX8mm6J3mESRvbXY/AY+cOPyx0qkyZwDBnKAv2D 2Z/QaUV95+2VPlP/AAaZVRxjygfB6IyJnWo8dlH4= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Cc: Martijn Braam , Benjamin GAIGNARD , Andrey Konovalov Date: Sat, 4 Apr 2020 03:44:35 +0300 Message-Id: <20200404004438.17992-9-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.24.1 In-Reply-To: <20200404004438.17992-1-laurent.pinchart@ideasonboard.com> References: <20200404004438.17992-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 08/11] libcamera: v4l2_videodevice: Map V4L2_PIX_FMT_GREY to DRM FourCC 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-List-Received-Date: Sat, 04 Apr 2020 00:44:56 -0000 DRM has a format for 8-bit greyscale data, DRM_FORMAT_R8. Despite the 'R' name, which comes from GL/Vulkan to mean single-channel data, the format maps to greyscale for display. We can thus map it to V4L2_PIX_FMT_GREY. Signed-off-by: Laurent Pinchart Reviewed-by: Niklas Söderlund --- Changes since v2: - Rebase on top of PixelFormat class --- src/libcamera/v4l2_videodevice.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp index 5208e5a47148..437e771d7fe4 100644 --- a/src/libcamera/v4l2_videodevice.cpp +++ b/src/libcamera/v4l2_videodevice.cpp @@ -1699,12 +1699,15 @@ PixelFormat V4L2VideoDevice::toPixelFormat(V4L2PixelFormat v4l2Fourcc) case V4L2_PIX_FMT_NV21M: return PixelFormat(DRM_FORMAT_NV21); + /* Greyscale formats. */ + case V4L2_PIX_FMT_GREY: + return PixelFormat(DRM_FORMAT_R8); + /* Compressed formats. */ case V4L2_PIX_FMT_MJPEG: return PixelFormat(DRM_FORMAT_MJPEG); /* V4L2 formats not yet supported by DRM. */ - case V4L2_PIX_FMT_GREY: default: /* * \todo We can't use LOG() in a static method of a Loggable @@ -1790,6 +1793,10 @@ V4L2PixelFormat V4L2VideoDevice::toV4L2PixelFormat(const PixelFormat &pixelForma case DRM_FORMAT_NV21: return V4L2PixelFormat(V4L2_PIX_FMT_NV21); + /* Greyscale formats. */ + case DRM_FORMAT_R8: + return V4L2PixelFormat(V4L2_PIX_FMT_GREY); + /* Compressed formats. */ case DRM_FORMAT_MJPEG: return V4L2PixelFormat(V4L2_PIX_FMT_MJPEG); From patchwork Sat Apr 4 00:44:36 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 3386 Return-Path: Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 85FC862DF2 for ; Sat, 4 Apr 2020 02:44:56 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="QppwGjpR"; dkim-atps=neutral Received: from pendragon.bb.dnainternet.fi (81-175-216-236.bb.dnainternet.fi [81.175.216.236]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E5A30321; Sat, 4 Apr 2020 02:44:55 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1585961096; bh=82qV4qvKWo9Cyc7ALWvQcxzWX8f87fC+vOoi6j+WmZU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=QppwGjpRP/1q/v1Tf3qel2+48NzEBQGum+t9DW7T6nJMEi9+WYjthY1IU/dQvhLJq ouTBqeNxjafTDdyVGAeQz4eK17C6M8suCzJu2dGioZzH0NOdCQgPK+jPu8KHDplFtl Z2+u3NCjo1HwryQ3VxVUM5cQx6jIdzZNXwGqHR6M= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Cc: Martijn Braam , Benjamin GAIGNARD , Andrey Konovalov Date: Sat, 4 Apr 2020 03:44:36 +0300 Message-Id: <20200404004438.17992-10-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.24.1 In-Reply-To: <20200404004438.17992-1-laurent.pinchart@ideasonboard.com> References: <20200404004438.17992-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 09/11] libcamera: pipeline: Add a simple pipeline handler 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-List-Received-Date: Sat, 04 Apr 2020 00:44:56 -0000 From: Martijn Braam This new pipeline handler aims at supporting any simple device without requiring any device-specific code. Simple devices are currently defined as a graph made of one or multiple camera sensors and a single video node, with each sensor connected to the video node through a linear pipeline. The simple pipeline handler will automatically parse the media graph, enumerate sensors, build supported stream configurations, and configure the pipeline, without any device-specific knowledge. It doesn't support configuration of any processing in the pipeline at the moment, but may be extended to support simple processing such as format conversion or scaling in the future. The only device-specific information in the pipeline handler is the list of supported drivers, required for device matching. We may be able to remove this in the future by matching with the simple pipeline handler as a last resort option, after all other pipeline handlers have been tried. Signed-off-by: Martijn Braam Signed-off-by: Laurent Pinchart Reviewed-by: Kieran Bingham Reviewed-by: Niklas Söderlund Reviewed-by: Andrey Konovalov --- Changes since v3: - Fix typos - Improve comments - Add local variable to increase readability - Move list of supported drivers to the top of the file Changes since v2: - Log an error when setupFormats() fail - Propagate getFormat() and setFormat() errors to the caller of setupFormats() - Reorder variable declarations in validate() - Add \todo comment related to the selection of the default format - Use log Error instead of Info if pipeline isn't valid - Rebase on top of V4L2PixelFormat Changes since v1: - Rebase on top of buffer API rework - Expose stream formats - Rework camera data config --- meson_options.txt | 2 +- src/libcamera/pipeline/simple/meson.build | 3 + src/libcamera/pipeline/simple/simple.cpp | 717 ++++++++++++++++++++++ 3 files changed, 721 insertions(+), 1 deletion(-) create mode 100644 src/libcamera/pipeline/simple/meson.build create mode 100644 src/libcamera/pipeline/simple/simple.cpp diff --git a/meson_options.txt b/meson_options.txt index 6464df837cc3..166429f8583e 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -14,7 +14,7 @@ option('gstreamer', option('pipelines', type : 'array', - choices : ['ipu3', 'rkisp1', 'uvcvideo', 'vimc'], + choices : ['ipu3', 'rkisp1', 'simple', 'uvcvideo', 'vimc'], description : 'Select which pipeline handlers to include') option('test', diff --git a/src/libcamera/pipeline/simple/meson.build b/src/libcamera/pipeline/simple/meson.build new file mode 100644 index 000000000000..4945a3e173cf --- /dev/null +++ b/src/libcamera/pipeline/simple/meson.build @@ -0,0 +1,3 @@ +libcamera_sources += files([ + 'simple.cpp', +]) diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp new file mode 100644 index 000000000000..e4f33f6ff531 --- /dev/null +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -0,0 +1,717 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Laurent Pinchart + * Copyright (C) 2019, Martijn Braam + * + * simple.cpp - Pipeline handler for simple pipelines + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "camera_sensor.h" +#include "device_enumerator.h" +#include "log.h" +#include "media_device.h" +#include "pipeline_handler.h" +#include "v4l2_subdevice.h" +#include "v4l2_videodevice.h" + +namespace libcamera { + +LOG_DEFINE_CATEGORY(SimplePipeline) + +class SimplePipelineHandler; + +namespace { + +static const char * const drivers[] = { + "imx7-csi", + "sun6i-csi", +}; + +} /* namespace */ + +class SimpleCameraData : public CameraData +{ +public: + SimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor, + MediaEntity *video); + + bool isValid() const { return sensor_ != nullptr; } + std::set streams() { return { &stream_ }; } + + int init(); + int setupLinks(); + int setupFormats(V4L2SubdeviceFormat *format, + V4L2Subdevice::Whence whence); + + struct Entity { + MediaEntity *entity; + MediaLink *link; + }; + + struct Configuration { + uint32_t code; + PixelFormat pixelFormat; + Size size; + }; + + Stream stream_; + std::unique_ptr sensor_; + std::list entities_; + + std::vector configs_; + std::map formats_; +}; + +class SimpleCameraConfiguration : public CameraConfiguration +{ +public: + SimpleCameraConfiguration(Camera *camera, SimpleCameraData *data); + + Status validate() override; + + const V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; } + +private: + /* + * The SimpleCameraData instance is guaranteed to be valid as long as + * the corresponding Camera instance is valid. In order to borrow a + * reference to the camera data, store a new reference to the camera. + */ + std::shared_ptr camera_; + const SimpleCameraData *data_; + + V4L2SubdeviceFormat sensorFormat_; +}; + +class SimplePipelineHandler : public PipelineHandler +{ +public: + SimplePipelineHandler(CameraManager *manager); + ~SimplePipelineHandler(); + + CameraConfiguration *generateConfiguration(Camera *camera, + const StreamRoles &roles) override; + int configure(Camera *camera, CameraConfiguration *config) override; + + int exportFrameBuffers(Camera *camera, Stream *stream, + std::vector> *buffers) override; + + int start(Camera *camera) override; + void stop(Camera *camera) override; + + bool match(DeviceEnumerator *enumerator) override; + + V4L2VideoDevice *video() { return video_; } + V4L2Subdevice *subdev(const MediaEntity *entity); + +protected: + int queueRequestDevice(Camera *camera, Request *request) override; + +private: + SimpleCameraData *cameraData(const Camera *camera) + { + return static_cast( + PipelineHandler::cameraData(camera)); + } + + int initLinks(); + + int createCamera(MediaEntity *sensor); + + void bufferReady(FrameBuffer *buffer); + + MediaDevice *media_; + V4L2VideoDevice *video_; + std::map subdevs_; + + Camera *activeCamera_; +}; + +/* ----------------------------------------------------------------------------- + * Camera Data + */ + +SimpleCameraData::SimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor, + MediaEntity *video) + : CameraData(pipe) +{ + int ret; + + /* + * Walk the pipeline towards the video node and store all entities + * along the way. + */ + MediaEntity *source = sensor; + + while (source) { + /* If we have reached the video node, we're done. */ + if (source == video) + break; + + /* Use the first output pad that has links. */ + MediaPad *sourcePad = nullptr; + for (MediaPad *pad : source->pads()) { + if ((pad->flags() & MEDIA_PAD_FL_SOURCE) && + !pad->links().empty()) { + sourcePad = pad; + break; + } + } + + if (!sourcePad) + return; + + /* Use the first link that isn't immutable and disabled. */ + MediaLink *sourceLink = nullptr; + for (MediaLink *link : sourcePad->links()) { + if ((link->flags() & MEDIA_LNK_FL_ENABLED) || + !(link->flags() & MEDIA_LNK_FL_IMMUTABLE)) { + sourceLink = link; + break; + } + } + + if (!sourceLink) + return; + + entities_.push_back({ source, sourceLink }); + + source = sourceLink->sink()->entity(); + + /* Avoid infinite loops. */ + auto iter = std::find_if(entities_.begin(), entities_.end(), + [&](const Entity &entity) { + return entity.entity == source; + }); + if (iter != entities_.end()) { + LOG(SimplePipeline, Info) << "Loop detected in pipeline"; + return; + } + } + + /* We have a valid pipeline, create the camera sensor. */ + sensor_ = std::make_unique(sensor); + ret = sensor_->init(); + if (ret) { + sensor_.reset(); + return; + } +} + +int SimpleCameraData::init() +{ + SimplePipelineHandler *pipe = static_cast(pipe_); + V4L2VideoDevice *video = pipe->video(); + int ret; + + /* + * Enumerate the possible pipeline configurations. For each media bus + * format supported by the sensor, propagate the formats through the + * pipeline, and enumerate the corresponding possible V4L2 pixel + * formats on the video node. + */ + for (unsigned int code : sensor_->mbusCodes()) { + V4L2SubdeviceFormat format{ code, sensor_->resolution() }; + + /* + * Setup links first as some subdev drivers take active links + * into account to propagate TRY formats. Such is life :-( + */ + ret = setupLinks(); + if (ret < 0) + return ret; + + ret = setupFormats(&format, V4L2Subdevice::TryFormat); + if (ret < 0) { + LOG(SimplePipeline, Error) + << "Failed to setup pipeline for media bus code " + << utils::hex(code, 4); + return ret; + } + + std::map> videoFormats = + video->formats(format.mbus_code); + + LOG(SimplePipeline, Debug) + << "Adding configuration for " << format.size.toString() + << " in pixel formats [ " + << utils::join(videoFormats, ", ", + [](const auto &f) { + return f.first.toString(); + }) + << " ]"; + + /* + * Store the configuration in the formats_ map, mapping the + * PixelFormat to the corresponding configuration. Any + * previously stored value is overwritten, as the pipeline + * handler currently doesn't care about how a particular + * PixelFormat is achieved. + */ + for (const auto &videoFormat : videoFormats) { + PixelFormat pixelFormat = video->toPixelFormat(videoFormat.first); + if (!pixelFormat) + continue; + + Configuration config; + config.code = code; + config.pixelFormat = pixelFormat; + config.size = format.size; + + formats_[pixelFormat] = config; + } + } + + if (formats_.empty()) { + LOG(SimplePipeline, Error) << "No valid configuration found"; + return -EINVAL; + } + + return 0; +} + +int SimpleCameraData::setupLinks() +{ + int ret; + + /* + * Configure all links along the pipeline. Some entities may not allow + * multiple sink links to be enabled together, even on different sink + * pads. We must thus start by disabling all sink links (but the one we + * want to enable) before enabling the pipeline link. + */ + for (SimpleCameraData::Entity &e : entities_) { + MediaEntity *remote = e.link->sink()->entity(); + for (MediaPad *pad : remote->pads()) { + for (MediaLink *link : pad->links()) { + if (link == e.link) + continue; + + if ((link->flags() & MEDIA_LNK_FL_ENABLED) && + !(link->flags() & MEDIA_LNK_FL_IMMUTABLE)) { + ret = link->setEnabled(false); + if (ret < 0) + return ret; + } + } + } + + if (!(e.link->flags() & MEDIA_LNK_FL_ENABLED)) { + ret = e.link->setEnabled(true); + if (ret < 0) + return ret; + } + } + + return 0; +} + +int SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format, + V4L2Subdevice::Whence whence) +{ + SimplePipelineHandler *pipe = static_cast(pipe_); + int ret; + + /* + * Configure the format on the sensor output and propagate it through + * the pipeline. + */ + ret = sensor_->setFormat(format); + if (ret < 0) + return ret; + + for (const Entity &e : entities_) { + MediaLink *link = e.link; + MediaPad *source = link->source(); + MediaPad *sink = link->sink(); + + if (source->entity() != sensor_->entity()) { + V4L2Subdevice *subdev = pipe->subdev(source->entity()); + ret = subdev->getFormat(source->index(), format, whence); + if (ret < 0) + return ret; + } + + if (sink->entity()->function() != MEDIA_ENT_F_IO_V4L) { + V4L2Subdevice *subdev = pipe->subdev(sink->entity()); + ret = subdev->setFormat(sink->index(), format, whence); + if (ret < 0) + return ret; + } + + LOG(SimplePipeline, Debug) + << "Link '" << source->entity()->name() + << "':" << source->index() + << " -> '" << sink->entity()->name() + << "':" << sink->index() + << " configured with format " << format->toString(); + } + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Camera Configuration + */ + +SimpleCameraConfiguration::SimpleCameraConfiguration(Camera *camera, + SimpleCameraData *data) + : CameraConfiguration(), camera_(camera->shared_from_this()), + data_(data) +{ +} + +CameraConfiguration::Status SimpleCameraConfiguration::validate() +{ + Status status = Valid; + + if (config_.empty()) + return Invalid; + + /* Cap the number of entries to the available streams. */ + if (config_.size() > 1) { + config_.resize(1); + status = Adjusted; + } + + StreamConfiguration &cfg = config_[0]; + + /* Adjust the pixel format. */ + auto it = data_->formats_.find(cfg.pixelFormat); + if (it == data_->formats_.end()) + it = data_->formats_.begin(); + + PixelFormat pixelFormat = it->first; + if (cfg.pixelFormat != pixelFormat) { + LOG(SimplePipeline, Debug) << "Adjusting pixel format"; + cfg.pixelFormat = pixelFormat; + status = Adjusted; + } + + const SimpleCameraData::Configuration &pipeConfig = it->second; + if (cfg.size != pipeConfig.size) { + LOG(SimplePipeline, Debug) + << "Adjusting size from " << cfg.size.toString() + << " to " << pipeConfig.size.toString(); + cfg.size = pipeConfig.size; + status = Adjusted; + } + + cfg.bufferCount = 3; + + return status; +} + +/* ----------------------------------------------------------------------------- + * Pipeline Handler + */ + +SimplePipelineHandler::SimplePipelineHandler(CameraManager *manager) + : PipelineHandler(manager), video_(nullptr) +{ +} + +SimplePipelineHandler::~SimplePipelineHandler() +{ + delete video_; +} + +CameraConfiguration *SimplePipelineHandler::generateConfiguration(Camera *camera, + const StreamRoles &roles) +{ + SimpleCameraData *data = cameraData(camera); + CameraConfiguration *config = + new SimpleCameraConfiguration(camera, data); + + if (roles.empty()) + return config; + + /* Create the formats map. */ + std::map> formats; + std::transform(data->formats_.begin(), data->formats_.end(), + std::inserter(formats, formats.end()), + [](const auto &format) -> decltype(formats)::value_type { + const PixelFormat &pixelFormat = format.first; + const Size &size = format.second.size; + return { pixelFormat, { size } }; + }); + + /* + * Create the stream configuration. Take the first entry in the formats + * map as the default, for lack of a better option. + * + * \todo Implement a better way to pick the default format + */ + StreamConfiguration cfg{ StreamFormats{ formats } }; + cfg.pixelFormat = formats.begin()->first; + cfg.size = formats.begin()->second[0].max; + + config->addConfiguration(cfg); + + config->validate(); + + return config; +} + +int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c) +{ + SimpleCameraConfiguration *config = + static_cast(c); + SimpleCameraData *data = cameraData(camera); + StreamConfiguration &cfg = config->at(0); + int ret; + + /* + * Configure links on the pipeline and propagate formats from the + * sensor to the video node. + */ + ret = data->setupLinks(); + if (ret < 0) + return ret; + + const SimpleCameraData::Configuration &pipeConfig = + data->formats_[cfg.pixelFormat]; + + V4L2SubdeviceFormat format{ pipeConfig.code, data->sensor_->resolution() }; + + ret = data->setupFormats(&format, V4L2Subdevice::ActiveFormat); + if (ret < 0) + return ret; + + /* Configure the video node. */ + V4L2PixelFormat videoFormat = video_->toV4L2PixelFormat(cfg.pixelFormat); + + V4L2DeviceFormat outputFormat = {}; + outputFormat.fourcc = videoFormat; + outputFormat.size = cfg.size; + + ret = video_->setFormat(&outputFormat); + if (ret) + return ret; + + if (outputFormat.size != cfg.size || outputFormat.fourcc != videoFormat) { + LOG(SimplePipeline, Error) + << "Unable to configure capture in " << cfg.toString(); + return -EINVAL; + } + + cfg.setStream(&data->stream_); + + return 0; +} + +int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream, + std::vector> *buffers) +{ + unsigned int count = stream->configuration().bufferCount; + + return video_->exportBuffers(count, buffers); +} + +int SimplePipelineHandler::start(Camera *camera) +{ + SimpleCameraData *data = cameraData(camera); + unsigned int count = data->stream_.configuration().bufferCount; + + int ret = video_->importBuffers(count); + if (ret < 0) + return ret; + + ret = video_->streamOn(); + if (ret < 0) { + video_->releaseBuffers(); + return ret; + } + + activeCamera_ = camera; + + return 0; +} + +void SimplePipelineHandler::stop(Camera *camera) +{ + video_->streamOff(); + video_->releaseBuffers(); + activeCamera_ = nullptr; +} + +int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request) +{ + SimpleCameraData *data = cameraData(camera); + Stream *stream = &data->stream_; + + FrameBuffer *buffer = request->findBuffer(stream); + if (!buffer) { + LOG(SimplePipeline, Error) + << "Attempt to queue request with invalid stream"; + return -ENOENT; + } + + return video_->queueBuffer(buffer); +} + +/* ----------------------------------------------------------------------------- + * Match and Setup + */ + +bool SimplePipelineHandler::match(DeviceEnumerator *enumerator) +{ + for (const char *driver : drivers) { + DeviceMatch dm(driver); + media_ = acquireMediaDevice(enumerator, dm); + if (media_) + break; + } + + if (!media_) + return false; + + /* + * Locate sensors and video nodes. We only support pipelines with at + * least one sensor and exactly one video capture node. + */ + std::vector sensors; + std::vector videos; + + for (MediaEntity *entity : media_->entities()) { + switch (entity->function()) { + case MEDIA_ENT_F_CAM_SENSOR: + sensors.push_back(entity); + break; + + case MEDIA_ENT_F_IO_V4L: + if (entity->pads().size() == 1 && + (entity->pads()[0]->flags() & MEDIA_PAD_FL_SINK)) + videos.push_back(entity); + break; + + default: + break; + } + } + + if (sensors.empty()) { + LOG(SimplePipeline, Error) << "No sensor found"; + return false; + } + + if (videos.size() != 1) { + LOG(SimplePipeline, Error) + << "Pipeline with " << videos.size() + << " video capture nodes is not supported"; + return false; + } + + /* Locate and open the capture video node. */ + video_ = new V4L2VideoDevice(videos[0]); + if (video_->open() < 0) + return false; + + if (video_->caps().isMultiplanar()) { + LOG(SimplePipeline, Error) + << "V4L2 multiplanar devices are not supported"; + return false; + } + + video_->bufferReady.connect(this, &SimplePipelineHandler::bufferReady); + + /* + * Create one camera data instance for each sensor and gather all + * entities in all pipelines. + */ + std::vector> pipelines; + std::set entities; + + pipelines.reserve(sensors.size()); + + for (MediaEntity *sensor : sensors) { + std::unique_ptr data = + std::make_unique(this, sensor, + videos[0]); + if (!data->isValid()) { + LOG(SimplePipeline, Error) + << "No valid pipeline for sensor '" + << sensor->name() << "', skipping"; + continue; + } + + for (SimpleCameraData::Entity &entity : data->entities_) + entities.insert(entity.entity); + + pipelines.push_back(std::move(data)); + } + + if (entities.empty()) + return false; + + /* Create and open V4L2Subdev instances for all the entities. */ + for (MediaEntity *entity : entities) { + auto elem = subdevs_.emplace(std::piecewise_construct, + std::forward_as_tuple(entity), + std::forward_as_tuple(entity)); + V4L2Subdevice *subdev = &elem.first->second; + int ret = subdev->open(); + if (ret < 0) { + LOG(SimplePipeline, Error) + << "Failed to open " << subdev->deviceNode() + << ": " << strerror(-ret); + return false; + } + } + + /* Initialize each pipeline and register a corresponding camera. */ + for (std::unique_ptr &data : pipelines) { + int ret = data->init(); + if (ret < 0) + continue; + + std::shared_ptr camera = + Camera::create(this, data->sensor_->entity()->name(), + data->streams()); + registerCamera(std::move(camera), std::move(data)); + } + + return true; +} + +V4L2Subdevice *SimplePipelineHandler::subdev(const MediaEntity *entity) +{ + auto iter = subdevs_.find(entity); + if (iter == subdevs_.end()) + return nullptr; + + return &iter->second; +} + +/* ----------------------------------------------------------------------------- + * Buffer Handling + */ + +void SimplePipelineHandler::bufferReady(FrameBuffer *buffer) +{ + ASSERT(activeCamera_); + Request *request = buffer->request(); + completeBuffer(activeCamera_, request, buffer); + completeRequest(activeCamera_, request); +} + +REGISTER_PIPELINE_HANDLER(SimplePipelineHandler); + +} /* namespace libcamera */ From patchwork Sat Apr 4 00:44:37 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 3387 Return-Path: Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1A59362E16 for ; Sat, 4 Apr 2020 02:44:57 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="VZkdFevE"; dkim-atps=neutral Received: from pendragon.bb.dnainternet.fi (81-175-216-236.bb.dnainternet.fi [81.175.216.236]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 84A2F1817; Sat, 4 Apr 2020 02:44:56 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1585961096; bh=ChQIQHpuMlIpTbnJTvgEWT9L4l2Ls6D85Z0m1e51cuw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=VZkdFevETqXFQXQWGO4b/ZLh+TjBovM3TaUx6oe4iXKue0J4CbC/+DxbywsHS2Pdr p60vp4EmSzqLa3ZKmXFNOLxmbJPY7Vc+WdpT/mWq4DDz/nAdRFlsvArDGVVWTDuzny KbTezC3uHA443YyDviKHGflVC67SLEPoZbdOQhDA= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Cc: Martijn Braam , Benjamin GAIGNARD , Andrey Konovalov Date: Sat, 4 Apr 2020 03:44:37 +0300 Message-Id: <20200404004438.17992-11-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.24.1 In-Reply-To: <20200404004438.17992-1-laurent.pinchart@ideasonboard.com> References: <20200404004438.17992-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 10/11] libcamera: pipeline: simple: Add simple format converter 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-List-Received-Date: Sat, 04 Apr 2020 00:44:57 -0000 The simple format converter supports V4L2 M2M devices that convert pixel formats. Signed-off-by: Laurent Pinchart Reviewed-by: Kieran Bingham Reviewed-by: Niklas Söderlund --- Changes since v3: - Add a comment to explain format enumeration Changes since v2: - Rebase on top of V4L2PixelFormat --- src/libcamera/pipeline/simple/converter.cpp | 213 ++++++++++++++++++++ src/libcamera/pipeline/simple/converter.h | 60 ++++++ src/libcamera/pipeline/simple/meson.build | 1 + 3 files changed, 274 insertions(+) create mode 100644 src/libcamera/pipeline/simple/converter.cpp create mode 100644 src/libcamera/pipeline/simple/converter.h diff --git a/src/libcamera/pipeline/simple/converter.cpp b/src/libcamera/pipeline/simple/converter.cpp new file mode 100644 index 000000000000..50e554147c5f --- /dev/null +++ b/src/libcamera/pipeline/simple/converter.cpp @@ -0,0 +1,213 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Laurent Pinchart + * + * converter.cpp - Format converter for simple pipeline handler + */ + +#include "converter.h" + +#include + +#include +#include +#include + +#include "log.h" +#include "media_device.h" +#include "v4l2_videodevice.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(SimplePipeline); + +SimpleConverter::SimpleConverter(MediaDevice *media) + : m2m_(nullptr) +{ + /* + * Locate the video node. There's no need to validate the pipeline + * further, the caller guarantees that this is a V4L2 mem2mem device. + */ + const std::vector &entities = media->entities(); + auto it = std::find_if(entities.begin(), entities.end(), + [](MediaEntity *entity) { + return entity->function() == MEDIA_ENT_F_IO_V4L; + }); + if (it == entities.end()) + return; + + m2m_ = new V4L2M2MDevice((*it)->deviceNode()); + + m2m_->output()->bufferReady.connect(this, &SimpleConverter::outputBufferReady); + m2m_->capture()->bufferReady.connect(this, &SimpleConverter::captureBufferReady); +} + +SimpleConverter::~SimpleConverter() +{ + delete m2m_; +} + +int SimpleConverter::open() +{ + if (!m2m_) + return -ENODEV; + + return m2m_->open(); +} + +void SimpleConverter::close() +{ + if (m2m_) + m2m_->close(); +} + +std::vector SimpleConverter::formats(PixelFormat input) +{ + if (!m2m_) + return {}; + + /* + * Set the format on the input side (V4L2 output) of the converter to + * enumerate the conversion capabilities on its output (V4L2 capture). + */ + V4L2DeviceFormat format; + format.fourcc = m2m_->output()->toV4L2PixelFormat(input); + format.size = { 1, 1 }; + + int ret = m2m_->output()->setFormat(&format); + if (ret < 0) { + LOG(SimplePipeline, Error) + << "Failed to set format: " << strerror(-ret); + return {}; + } + + std::vector pixelFormats; + + for (const auto &format : m2m_->capture()->formats()) { + PixelFormat pixelFormat = m2m_->capture()->toPixelFormat(format.first); + if (pixelFormat) + pixelFormats.push_back(pixelFormat); + } + + return pixelFormats; +} + +int SimpleConverter::configure(PixelFormat inputFormat, + PixelFormat outputFormat, const Size &size) +{ + V4L2DeviceFormat format; + int ret; + + V4L2PixelFormat videoFormat = m2m_->output()->toV4L2PixelFormat(inputFormat); + format.fourcc = videoFormat; + format.size = size; + + ret = m2m_->output()->setFormat(&format); + if (ret < 0) { + LOG(SimplePipeline, Error) + << "Failed to set input format: " << strerror(-ret); + return ret; + } + + if (format.fourcc != videoFormat || format.size != size) { + LOG(SimplePipeline, Error) + << "Input format not supported"; + return -EINVAL; + } + + videoFormat = m2m_->capture()->toV4L2PixelFormat(outputFormat); + format.fourcc = videoFormat; + + ret = m2m_->capture()->setFormat(&format); + if (ret < 0) { + LOG(SimplePipeline, Error) + << "Failed to set output format: " << strerror(-ret); + return ret; + } + + if (format.fourcc != videoFormat || format.size != size) { + LOG(SimplePipeline, Error) + << "Output format not supported"; + return -EINVAL; + } + + return 0; +} + +int SimpleConverter::exportBuffers(unsigned int count, + std::vector> *buffers) +{ + return m2m_->capture()->exportBuffers(count, buffers); +} + +int SimpleConverter::start(unsigned int count) +{ + int ret = m2m_->output()->importBuffers(count); + if (ret < 0) + return ret; + + ret = m2m_->capture()->importBuffers(count); + if (ret < 0) { + stop(); + return ret; + } + + ret = m2m_->output()->streamOn(); + if (ret < 0) { + stop(); + return ret; + } + + ret = m2m_->capture()->streamOn(); + if (ret < 0) { + stop(); + return ret; + } + + return 0; +} + +void SimpleConverter::stop() +{ + m2m_->capture()->streamOff(); + m2m_->output()->streamOff(); + m2m_->capture()->releaseBuffers(); + m2m_->output()->releaseBuffers(); +} + +int SimpleConverter::queueBuffers(FrameBuffer *input, FrameBuffer *output) +{ + int ret = m2m_->output()->queueBuffer(input); + if (ret < 0) + return ret; + + ret = m2m_->capture()->queueBuffer(output); + if (ret < 0) + return ret; + + return 0; +} + +void SimpleConverter::captureBufferReady(FrameBuffer *buffer) +{ + if (!outputDoneQueue_.empty()) { + FrameBuffer *other = outputDoneQueue_.front(); + outputDoneQueue_.pop(); + bufferReady.emit(other, buffer); + } else { + captureDoneQueue_.push(buffer); + } +} + +void SimpleConverter::outputBufferReady(FrameBuffer *buffer) +{ + if (!captureDoneQueue_.empty()) { + FrameBuffer *other = captureDoneQueue_.front(); + captureDoneQueue_.pop(); + bufferReady.emit(buffer, other); + } else { + outputDoneQueue_.push(buffer); + } +} + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/simple/converter.h b/src/libcamera/pipeline/simple/converter.h new file mode 100644 index 000000000000..a33071fa8578 --- /dev/null +++ b/src/libcamera/pipeline/simple/converter.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Laurent Pinchart + * + * converter.h - Format converter for simple pipeline handler + */ + +#ifndef __LIBCAMERA_PIPELINE_SIMPLE_CONVERTER_H__ +#define __LIBCAMERA_PIPELINE_SIMPLE_CONVERTER_H__ + +#include +#include +#include + +#include +#include + +namespace libcamera { + +class FrameBuffer; +class MediaDevice; +struct Size; +class V4L2M2MDevice; + +class SimpleConverter +{ +public: + SimpleConverter(MediaDevice *media); + ~SimpleConverter(); + + int open(); + void close(); + + std::vector formats(PixelFormat input); + + int configure(PixelFormat inputFormat, PixelFormat outputFormat, + const Size &size); + int exportBuffers(unsigned int count, + std::vector> *buffers); + + int start(unsigned int count); + void stop(); + + int queueBuffers(FrameBuffer *input, FrameBuffer *output); + + Signal bufferReady; + +private: + void captureBufferReady(FrameBuffer *buffer); + void outputBufferReady(FrameBuffer *buffer); + + V4L2M2MDevice *m2m_; + + std::queue captureDoneQueue_; + std::queue outputDoneQueue_; +}; + +} /* namespace libcamera */ + +#endif /* __LIBCAMERA_PIPELINE_SIMPLE_CONVERTER_H__ */ diff --git a/src/libcamera/pipeline/simple/meson.build b/src/libcamera/pipeline/simple/meson.build index 4945a3e173cf..8372f24e3788 100644 --- a/src/libcamera/pipeline/simple/meson.build +++ b/src/libcamera/pipeline/simple/meson.build @@ -1,3 +1,4 @@ libcamera_sources += files([ + 'converter.cpp', 'simple.cpp', ]) From patchwork Sat Apr 4 00:44:38 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 3388 Return-Path: Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id A928962DD9 for ; Sat, 4 Apr 2020 02:44:57 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="KYgfmCXI"; dkim-atps=neutral Received: from pendragon.bb.dnainternet.fi (81-175-216-236.bb.dnainternet.fi [81.175.216.236]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 14A9355B; Sat, 4 Apr 2020 02:44:57 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1585961097; bh=nLXEyMuDusyDeNafX+6TY854JqAIGEbPhDPCXj5zQXs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=KYgfmCXIMXryHPtxkQIbniUYu4tQfyyWGkTrR+IpiZbh6n08I626zFBs2fwgXE9TT Fmr35bc3/f17bmB3vBBLqrCcDklEnjGEs7mqwFFRBo/p9qhnS/4XuZKENhPbIU2zsf tJUh2hw5dO1ViwYNATLooVO76MZa/z6iz53WfIg0= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Cc: Martijn Braam , Benjamin GAIGNARD , Andrey Konovalov Date: Sat, 4 Apr 2020 03:44:38 +0300 Message-Id: <20200404004438.17992-12-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.24.1 In-Reply-To: <20200404004438.17992-1-laurent.pinchart@ideasonboard.com> References: <20200404004438.17992-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 11/11] libcamera: pipeline: simple: Integrate converter 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-List-Received-Date: Sat, 04 Apr 2020 00:44:58 -0000 Add support for an optional format converter, supported by the SimpleConverter class. If a converter is available for the pipeline, it will be used to expose additional pixel formats. Signed-off-by: Laurent Pinchart Reviewed-by: Niklas Söderlund --- Changes since v2: - Rebase on top of V4L2PixelFormat - Don't crash if the device has no converter --- src/libcamera/pipeline/simple/simple.cpp | 196 ++++++++++++++++++++--- 1 file changed, 178 insertions(+), 18 deletions(-) diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp index e4f33f6ff531..8e7dd091b4ab 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -31,17 +32,24 @@ #include "v4l2_subdevice.h" #include "v4l2_videodevice.h" +#include "converter.h" + namespace libcamera { LOG_DEFINE_CATEGORY(SimplePipeline) class SimplePipelineHandler; +struct SimplePipelineInfo { + const char *driver; + const char *converter; +}; + namespace { -static const char * const drivers[] = { - "imx7-csi", - "sun6i-csi", +static const SimplePipelineInfo supportedDevices[] = { + { "imx7-csi", "pxp" }, + { "sun6i-csi", nullptr }, }; } /* namespace */ @@ -88,6 +96,8 @@ public: const V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; } + bool needConversion() const { return needConversion_; } + private: /* * The SimpleCameraData instance is guaranteed to be valid as long as @@ -98,6 +108,7 @@ private: const SimpleCameraData *data_; V4L2SubdeviceFormat sensorFormat_; + bool needConversion_; }; class SimplePipelineHandler : public PipelineHandler @@ -120,6 +131,7 @@ public: V4L2VideoDevice *video() { return video_; } V4L2Subdevice *subdev(const MediaEntity *entity); + SimpleConverter *converter() { return converter_; } protected: int queueRequestDevice(Camera *camera, Request *request) override; @@ -136,11 +148,17 @@ private: int createCamera(MediaEntity *sensor); void bufferReady(FrameBuffer *buffer); + void converterDone(FrameBuffer *input, FrameBuffer *output); MediaDevice *media_; V4L2VideoDevice *video_; std::map subdevs_; + SimpleConverter *converter_; + bool useConverter_; + std::vector> converterBuffers_; + std::queue converterQueue_; + Camera *activeCamera_; }; @@ -219,6 +237,7 @@ int SimpleCameraData::init() { SimplePipelineHandler *pipe = static_cast(pipe_); V4L2VideoDevice *video = pipe->video(); + SimpleConverter *converter = pipe->converter(); int ret; /* @@ -275,7 +294,13 @@ int SimpleCameraData::init() config.pixelFormat = pixelFormat; config.size = format.size; - formats_[pixelFormat] = config; + if (!converter) { + formats_[pixelFormat] = config; + continue; + } + + for (PixelFormat format : converter->formats(pixelFormat)) + formats_[format] = config; } } @@ -414,6 +439,8 @@ CameraConfiguration::Status SimpleCameraConfiguration::validate() status = Adjusted; } + needConversion_ = cfg.pixelFormat != pipeConfig.pixelFormat; + cfg.bufferCount = 3; return status; @@ -424,13 +451,14 @@ CameraConfiguration::Status SimpleCameraConfiguration::validate() */ SimplePipelineHandler::SimplePipelineHandler(CameraManager *manager) - : PipelineHandler(manager), video_(nullptr) + : PipelineHandler(manager), video_(nullptr), converter_(nullptr) { } SimplePipelineHandler::~SimplePipelineHandler() { delete video_; + delete converter_; } CameraConfiguration *SimplePipelineHandler::generateConfiguration(Camera *camera, @@ -496,22 +524,37 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c) return ret; /* Configure the video node. */ - V4L2PixelFormat videoFormat = video_->toV4L2PixelFormat(cfg.pixelFormat); + V4L2PixelFormat videoFormat = video_->toV4L2PixelFormat(pipeConfig.pixelFormat); - V4L2DeviceFormat outputFormat = {}; - outputFormat.fourcc = videoFormat; - outputFormat.size = cfg.size; + V4L2DeviceFormat captureFormat = {}; + captureFormat.fourcc = videoFormat; + captureFormat.size = cfg.size; - ret = video_->setFormat(&outputFormat); + ret = video_->setFormat(&captureFormat); if (ret) return ret; - if (outputFormat.size != cfg.size || outputFormat.fourcc != videoFormat) { + if (captureFormat.fourcc != videoFormat || captureFormat.size != cfg.size) { LOG(SimplePipeline, Error) << "Unable to configure capture in " << cfg.toString(); return -EINVAL; } + /* Configure the converter if required. */ + useConverter_ = config->needConversion(); + + if (useConverter_) { + int ret = converter_->configure(pipeConfig.pixelFormat, + cfg.pixelFormat, cfg.size); + if (ret < 0) { + LOG(SimplePipeline, Error) + << "Unable to configure converter"; + return ret; + } + + LOG(SimplePipeline, Debug) << "Using format converter"; + } + cfg.setStream(&data->stream_); return 0; @@ -522,24 +565,47 @@ int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream, { unsigned int count = stream->configuration().bufferCount; - return video_->exportBuffers(count, buffers); + /* + * Export buffers on the converter or capture video node, depending on + * whether the converter is used or not. + */ + if (useConverter_) + return converter_->exportBuffers(count, buffers); + else + return video_->exportBuffers(count, buffers); } int SimplePipelineHandler::start(Camera *camera) { SimpleCameraData *data = cameraData(camera); unsigned int count = data->stream_.configuration().bufferCount; + int ret; - int ret = video_->importBuffers(count); + if (useConverter_) + ret = video_->allocateBuffers(count, &converterBuffers_); + else + ret = video_->importBuffers(count); if (ret < 0) return ret; ret = video_->streamOn(); if (ret < 0) { - video_->releaseBuffers(); + stop(camera); return ret; } + if (useConverter_) { + ret = converter_->start(count); + if (ret < 0) { + stop(camera); + return ret; + } + + /* Queue all internal buffers for capture. */ + for (std::unique_ptr &buffer : converterBuffers_) + video_->queueBuffer(buffer.get()); + } + activeCamera_ = camera; return 0; @@ -547,8 +613,13 @@ int SimplePipelineHandler::start(Camera *camera) void SimplePipelineHandler::stop(Camera *camera) { + if (useConverter_) + converter_->stop(); + video_->streamOff(); video_->releaseBuffers(); + + converterBuffers_.clear(); activeCamera_ = nullptr; } @@ -564,6 +635,15 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request) return -ENOENT; } + /* + * If conversion is needed, push the buffer to the converter queue, it + * will be handed to the converter in the capture completion handler. + */ + if (useConverter_) { + converterQueue_.push(buffer); + return 0; + } + return video_->queueBuffer(buffer); } @@ -573,11 +653,20 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request) bool SimplePipelineHandler::match(DeviceEnumerator *enumerator) { - for (const char *driver : drivers) { - DeviceMatch dm(driver); + MediaDevice *converter = nullptr; + + for (const SimplePipelineInfo &info : supportedDevices) { + DeviceMatch dm(info.driver); media_ = acquireMediaDevice(enumerator, dm); - if (media_) + if (!media_) + continue; + + if (!info.converter) break; + + DeviceMatch converterMatch(info.converter); + converter = acquireMediaDevice(enumerator, converterMatch); + break; } if (!media_) @@ -632,6 +721,19 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator) video_->bufferReady.connect(this, &SimplePipelineHandler::bufferReady); + /* Open the converter, if any. */ + if (converter) { + converter_ = new SimpleConverter(converter); + if (converter_->open() < 0) { + LOG(SimplePipeline, Warning) + << "Failed to open converter, disabling format conversion"; + delete converter_; + converter_ = nullptr; + } + + converter_->bufferReady.connect(this, &SimplePipelineHandler::converterDone); + } + /* * Create one camera data instance for each sensor and gather all * entities in all pipelines. @@ -706,12 +808,70 @@ V4L2Subdevice *SimplePipelineHandler::subdev(const MediaEntity *entity) void SimplePipelineHandler::bufferReady(FrameBuffer *buffer) { - ASSERT(activeCamera_); + /* + * If an error occurred during capture, or if the buffer was cancelled, + * complete the request, even if the converter is in use as there's no + * point converting an erroneous buffer. + */ + if (buffer->metadata().status != FrameMetadata::FrameSuccess) { + if (useConverter_) { + /* Requeue the buffer for capture. */ + video_->queueBuffer(buffer); + + /* + * Get the next user-facing buffer to complete the + * request. + */ + if (converterQueue_.empty()) + return; + + buffer = converterQueue_.front(); + converterQueue_.pop(); + } + + Request *request = buffer->request(); + completeBuffer(activeCamera_, request, buffer); + completeRequest(activeCamera_, request); + return; + } + + /* + * Queue the captured and the request buffer to the converter if format + * conversion is needed. If there's no queued request, just requeue the + * captured buffer for capture. + */ + if (useConverter_) { + if (converterQueue_.empty()) { + video_->queueBuffer(buffer); + return; + } + + FrameBuffer *output = converterQueue_.front(); + converterQueue_.pop(); + + converter_->queueBuffers(buffer, output); + return; + } + + /* Otherwise simply complete the request. */ Request *request = buffer->request(); completeBuffer(activeCamera_, request, buffer); completeRequest(activeCamera_, request); } +void SimplePipelineHandler::converterDone(FrameBuffer *input, + FrameBuffer *output) +{ + /* Complete the request. */ + ASSERT(activeCamera_); + Request *request = output->request(); + completeBuffer(activeCamera_, request, output); + completeRequest(activeCamera_, request); + + /* Queue the input buffer back for capture. */ + video_->queueBuffer(input); +} + REGISTER_PIPELINE_HANDLER(SimplePipelineHandler); } /* namespace libcamera */ From patchwork Sat Apr 4 01:16:24 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 3389 Return-Path: Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8A08960409 for ; Sat, 4 Apr 2020 03:16:38 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="p4yPhB5y"; dkim-atps=neutral Received: from pendragon.bb.dnainternet.fi (81-175-216-236.bb.dnainternet.fi [81.175.216.236]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E9550321; Sat, 4 Apr 2020 03:16:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1585962998; bh=Gh6eGiCVZoiIvC6IvOZ9ipOxTaFpb00WKQf2eMwsTPI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=p4yPhB5yu6Y8c1+zmmfLMisDKp8hD2m81dMQyDo7Dow27JHOps1Hkq4K8tOFVC85G Fe/+OBDpbDV41NCDywCx/1ivSrWfHHhj3Bl55AbVBkPxvm+OYXV50jII+tmDv4mIfL x85W5NfGKkM6wajhJdi4kYGOhOq77rvNGolQAXKw= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Date: Sat, 4 Apr 2020 04:16:24 +0300 Message-Id: <20200404011624.19728-1-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.24.1 In-Reply-To: <20200404004438.17992-1-laurent.pinchart@ideasonboard.com> References: <20200404004438.17992-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v4 12/11] libcamera: pipeline: simple: Support multiple capture video nodes 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-List-Received-Date: Sat, 04 Apr 2020 01:16:38 -0000 The simple pipeline handler rejects devices that have multiple capture video nodes. There's no real reason to do so, a more dynamic approach is possible as the pipeline handler already locates the video device by walking the media graph. Rework the match sequence by skipping any check on the video nodes, and create the V4L2VideoDevice for the media entity at the end of the pipeline when initializing the camera data. The V4L2VideoDevice instances are managed by the pipeline handler itself, to avoid creating separate instances in the camera data if multiple sensors are routed to the same video device. Signed-off-by: Laurent Pinchart Reviewed-by: Andrey Konovalov Reviewed-by: Niklas Söderlund --- src/libcamera/pipeline/simple/simple.cpp | 136 +++++++++++++---------- 1 file changed, 75 insertions(+), 61 deletions(-) Andrey, does this fix your issue with the qcom-camss ? diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp index 8e7dd091b4ab..b5f9177dd383 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -57,8 +57,7 @@ static const SimplePipelineInfo supportedDevices[] = { class SimpleCameraData : public CameraData { public: - SimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor, - MediaEntity *video); + SimpleCameraData(SimplePipelineHandler *pipe, MediaEntity *sensor); bool isValid() const { return sensor_ != nullptr; } std::set streams() { return { &stream_ }; } @@ -82,6 +81,7 @@ public: Stream stream_; std::unique_ptr sensor_; std::list entities_; + V4L2VideoDevice *video_; std::vector configs_; std::map formats_; @@ -129,7 +129,7 @@ public: bool match(DeviceEnumerator *enumerator) override; - V4L2VideoDevice *video() { return video_; } + V4L2VideoDevice *video(const MediaEntity *entity); V4L2Subdevice *subdev(const MediaEntity *entity); SimpleConverter *converter() { return converter_; } @@ -151,7 +151,7 @@ private: void converterDone(FrameBuffer *input, FrameBuffer *output); MediaDevice *media_; - V4L2VideoDevice *video_; + std::map> videos_; std::map subdevs_; SimpleConverter *converter_; @@ -166,8 +166,8 @@ private: * Camera Data */ -SimpleCameraData::SimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor, - MediaEntity *video) +SimpleCameraData::SimpleCameraData(SimplePipelineHandler *pipe, + MediaEntity *sensor) : CameraData(pipe) { int ret; @@ -179,8 +179,8 @@ SimpleCameraData::SimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor, MediaEntity *source = sensor; while (source) { - /* If we have reached the video node, we're done. */ - if (source == video) + /* If we have reached a video node, we're done. */ + if (source->function() == MEDIA_ENT_F_IO_V4L) break; /* Use the first output pad that has links. */ @@ -224,7 +224,14 @@ SimpleCameraData::SimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor, } } - /* We have a valid pipeline, create the camera sensor. */ + /* + * We have a valid pipeline, get the video device and create the camera + * sensor. + */ + video_ = pipe->video(source); + if (!video_) + return; + sensor_ = std::make_unique(sensor); ret = sensor_->init(); if (ret) { @@ -236,7 +243,6 @@ SimpleCameraData::SimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor, int SimpleCameraData::init() { SimplePipelineHandler *pipe = static_cast(pipe_); - V4L2VideoDevice *video = pipe->video(); SimpleConverter *converter = pipe->converter(); int ret; @@ -266,7 +272,7 @@ int SimpleCameraData::init() } std::map> videoFormats = - video->formats(format.mbus_code); + video_->formats(format.mbus_code); LOG(SimplePipeline, Debug) << "Adding configuration for " << format.size.toString() @@ -285,7 +291,7 @@ int SimpleCameraData::init() * PixelFormat is achieved. */ for (const auto &videoFormat : videoFormats) { - PixelFormat pixelFormat = video->toPixelFormat(videoFormat.first); + PixelFormat pixelFormat = video_->toPixelFormat(videoFormat.first); if (!pixelFormat) continue; @@ -451,13 +457,12 @@ CameraConfiguration::Status SimpleCameraConfiguration::validate() */ SimplePipelineHandler::SimplePipelineHandler(CameraManager *manager) - : PipelineHandler(manager), video_(nullptr), converter_(nullptr) + : PipelineHandler(manager), converter_(nullptr) { } SimplePipelineHandler::~SimplePipelineHandler() { - delete video_; delete converter_; } @@ -503,6 +508,7 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c) SimpleCameraConfiguration *config = static_cast(c); SimpleCameraData *data = cameraData(camera); + V4L2VideoDevice *video = data->video_; StreamConfiguration &cfg = config->at(0); int ret; @@ -524,13 +530,13 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c) return ret; /* Configure the video node. */ - V4L2PixelFormat videoFormat = video_->toV4L2PixelFormat(pipeConfig.pixelFormat); + V4L2PixelFormat videoFormat = video->toV4L2PixelFormat(pipeConfig.pixelFormat); V4L2DeviceFormat captureFormat = {}; captureFormat.fourcc = videoFormat; captureFormat.size = cfg.size; - ret = video_->setFormat(&captureFormat); + ret = video->setFormat(&captureFormat); if (ret) return ret; @@ -563,6 +569,7 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c) int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream, std::vector> *buffers) { + SimpleCameraData *data = cameraData(camera); unsigned int count = stream->configuration().bufferCount; /* @@ -572,23 +579,24 @@ int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream, if (useConverter_) return converter_->exportBuffers(count, buffers); else - return video_->exportBuffers(count, buffers); + return data->video_->exportBuffers(count, buffers); } int SimplePipelineHandler::start(Camera *camera) { SimpleCameraData *data = cameraData(camera); + V4L2VideoDevice *video = data->video_; unsigned int count = data->stream_.configuration().bufferCount; int ret; if (useConverter_) - ret = video_->allocateBuffers(count, &converterBuffers_); + ret = video->allocateBuffers(count, &converterBuffers_); else - ret = video_->importBuffers(count); + ret = video->importBuffers(count); if (ret < 0) return ret; - ret = video_->streamOn(); + ret = video->streamOn(); if (ret < 0) { stop(camera); return ret; @@ -603,7 +611,7 @@ int SimplePipelineHandler::start(Camera *camera) /* Queue all internal buffers for capture. */ for (std::unique_ptr &buffer : converterBuffers_) - video_->queueBuffer(buffer.get()); + video->queueBuffer(buffer.get()); } activeCamera_ = camera; @@ -613,11 +621,14 @@ int SimplePipelineHandler::start(Camera *camera) void SimplePipelineHandler::stop(Camera *camera) { + SimpleCameraData *data = cameraData(camera); + V4L2VideoDevice *video = data->video_; + if (useConverter_) converter_->stop(); - video_->streamOff(); - video_->releaseBuffers(); + video->streamOff(); + video->releaseBuffers(); converterBuffers_.clear(); activeCamera_ = nullptr; @@ -644,7 +655,7 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request) return 0; } - return video_->queueBuffer(buffer); + return data->video_->queueBuffer(buffer); } /* ----------------------------------------------------------------------------- @@ -672,12 +683,8 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator) if (!media_) return false; - /* - * Locate sensors and video nodes. We only support pipelines with at - * least one sensor and exactly one video capture node. - */ + /* Locate the sensors. */ std::vector sensors; - std::vector videos; for (MediaEntity *entity : media_->entities()) { switch (entity->function()) { @@ -685,12 +692,6 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator) sensors.push_back(entity); break; - case MEDIA_ENT_F_IO_V4L: - if (entity->pads().size() == 1 && - (entity->pads()[0]->flags() & MEDIA_PAD_FL_SINK)) - videos.push_back(entity); - break; - default: break; } @@ -701,26 +702,6 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator) return false; } - if (videos.size() != 1) { - LOG(SimplePipeline, Error) - << "Pipeline with " << videos.size() - << " video capture nodes is not supported"; - return false; - } - - /* Locate and open the capture video node. */ - video_ = new V4L2VideoDevice(videos[0]); - if (video_->open() < 0) - return false; - - if (video_->caps().isMultiplanar()) { - LOG(SimplePipeline, Error) - << "V4L2 multiplanar devices are not supported"; - return false; - } - - video_->bufferReady.connect(this, &SimplePipelineHandler::bufferReady); - /* Open the converter, if any. */ if (converter) { converter_ = new SimpleConverter(converter); @@ -745,8 +726,7 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator) for (MediaEntity *sensor : sensors) { std::unique_ptr data = - std::make_unique(this, sensor, - videos[0]); + std::make_unique(this, sensor); if (!data->isValid()) { LOG(SimplePipeline, Error) << "No valid pipeline for sensor '" @@ -793,6 +773,35 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator) return true; } +V4L2VideoDevice *SimplePipelineHandler::video(const MediaEntity *entity) +{ + /* + * Return the V4L2VideoDevice corresponding to the media entity, either + * as a previously constructed device if available from the cache, or + * by constructing a new one. + */ + + auto iter = videos_.find(entity); + if (iter != videos_.end()) + return iter->second.get(); + + std::unique_ptr video = + std::make_unique(entity); + if (video->open() < 0) + return nullptr; + + if (video->caps().isMultiplanar()) { + LOG(SimplePipeline, Error) + << "V4L2 multiplanar devices are not supported"; + return nullptr; + } + + video->bufferReady.connect(this, &SimplePipelineHandler::bufferReady); + + auto element = videos_.emplace(entity, std::move(video)); + return element.first->second.get(); +} + V4L2Subdevice *SimplePipelineHandler::subdev(const MediaEntity *entity) { auto iter = subdevs_.find(entity); @@ -808,6 +817,9 @@ V4L2Subdevice *SimplePipelineHandler::subdev(const MediaEntity *entity) void SimplePipelineHandler::bufferReady(FrameBuffer *buffer) { + ASSERT(activeCamera_); + SimpleCameraData *data = cameraData(activeCamera_); + /* * If an error occurred during capture, or if the buffer was cancelled, * complete the request, even if the converter is in use as there's no @@ -816,7 +828,7 @@ void SimplePipelineHandler::bufferReady(FrameBuffer *buffer) if (buffer->metadata().status != FrameMetadata::FrameSuccess) { if (useConverter_) { /* Requeue the buffer for capture. */ - video_->queueBuffer(buffer); + data->video_->queueBuffer(buffer); /* * Get the next user-facing buffer to complete the @@ -842,7 +854,7 @@ void SimplePipelineHandler::bufferReady(FrameBuffer *buffer) */ if (useConverter_) { if (converterQueue_.empty()) { - video_->queueBuffer(buffer); + data->video_->queueBuffer(buffer); return; } @@ -862,14 +874,16 @@ void SimplePipelineHandler::bufferReady(FrameBuffer *buffer) void SimplePipelineHandler::converterDone(FrameBuffer *input, FrameBuffer *output) { - /* Complete the request. */ ASSERT(activeCamera_); + SimpleCameraData *data = cameraData(activeCamera_); + + /* Complete the request. */ Request *request = output->request(); completeBuffer(activeCamera_, request, output); completeRequest(activeCamera_, request); /* Queue the input buffer back for capture. */ - video_->queueBuffer(input); + data->video_->queueBuffer(input); } REGISTER_PIPELINE_HANDLER(SimplePipelineHandler);