From patchwork Thu Aug 8 15:12:16 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 1750 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 802B96161B for ; Thu, 8 Aug 2019 17:12:26 +0200 (CEST) Received: from localhost.localdomain (cpc89242-aztw30-2-0-cust488.18-1.cable.virginm.net [86.31.129.233]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D1840555; Thu, 8 Aug 2019 17:12:25 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1565277146; bh=CNmTZFvZTq1oxMqRhgSYgm353CpC7sBZ0D0sOKURtfc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Y5hVCuu25RZBEOFSiLg1TJMvBVNFuE4y+fEw1vEjFZC21J4ntu4syDabrVRTfT/M4 UILk+PCo2nl4D2DuDu8LuXYUWJDFshV9vT5uGY2mDVvh20ZAsaMFIe0bATl5l9KYjr 5nFF70HpsbAC6WF8oArrQHQyBwivBfwbRuLdKBuE= From: Kieran Bingham To: LibCamera Devel Date: Thu, 8 Aug 2019 16:12:16 +0100 Message-Id: <20190808151221.24254-2-kieran.bingham@ideasonboard.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190808151221.24254-1-kieran.bingham@ideasonboard.com> References: <20190808151221.24254-1-kieran.bingham@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 1/6] libcamera: v4l2_device: Add setFd() X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 08 Aug 2019 15:12:26 -0000 Provide a means for V4L2 device instances to set the fd_ explicitly. This allows for V4L2Devices to operate when they must use an external open() call. Signed-off-by: Kieran Bingham Reviewed-by: Laurent Pinchart Reviewed-by: Jacopo Mondi --- src/libcamera/include/v4l2_device.h | 1 + src/libcamera/v4l2_device.cpp | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/libcamera/include/v4l2_device.h b/src/libcamera/include/v4l2_device.h index e7e9829cb05f..7c3af5f6ebe0 100644 --- a/src/libcamera/include/v4l2_device.h +++ b/src/libcamera/include/v4l2_device.h @@ -39,6 +39,7 @@ protected: int ioctl(unsigned long request, void *argp); int fd() { return fd_; } + int setFd(int fd); private: void listControls(); diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp index 9a00566a532b..120f68e49d7a 100644 --- a/src/libcamera/v4l2_device.cpp +++ b/src/libcamera/v4l2_device.cpp @@ -308,6 +308,25 @@ int V4L2Device::ioctl(unsigned long request, void *argp) * \return The V4L2 device file descriptor, -1 if the device node is not open */ +/** + * \brief Set the file descriptor of a V4L2 device. + * \param[in] fd The file descriptor handle + * + * Allow a device to set the internal file descriptor rather than opening a + * device node directly. + * + * \return 0 on success or a negative error code otherwise + */ +int V4L2Device::setFd(int fd) +{ + if (fd_ != -1) + return -1; + + fd_ = fd; + + return 0; +} + /* * \brief List and store information about all controls supported by the * V4L2 device From patchwork Thu Aug 8 15:12:17 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 1751 Return-Path: Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id AB4AF615FF for ; Thu, 8 Aug 2019 17:12:26 +0200 (CEST) Received: from localhost.localdomain (cpc89242-aztw30-2-0-cust488.18-1.cable.virginm.net [86.31.129.233]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3FE39737; Thu, 8 Aug 2019 17:12:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1565277146; bh=6OcXaBu06vIo4/eSMUUCV+xbOCseODISKxS5nz5B62g=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=kGMQ4G3FB5h/bwpUJbp2HGlr4Vf2DWBvsodWBlQwilVjsJPHBeWmsmGr00a27dnHX grjI5seyF0jQvi4jePDQx9sgPWILCSE37+y+dy90Qnyg06c3TQjidKi1OJfZzy73OX tsKsap8cf5w+JBBfLLZJ5KDEOcgiOW4tAgE0hmRU= From: Kieran Bingham To: LibCamera Devel Date: Thu, 8 Aug 2019 16:12:17 +0100 Message-Id: <20190808151221.24254-3-kieran.bingham@ideasonboard.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190808151221.24254-1-kieran.bingham@ideasonboard.com> References: <20190808151221.24254-1-kieran.bingham@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 2/6] libcamera: v4l2_videodevice: Fix ordering of debug statement X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 08 Aug 2019 15:12:26 -0000 The "Opened device" statement occurs before the buffertype_ is set. This causes all devices to report that they are [out] devices at open() regardless of their type. As the message operates in the past-tense, move the statement to the end of the function when all work has been completed. Fixes: 04d5be7f76fe ("libcamera: v4l2_device: Inherit from Loggable to print device node name") Signed-off-by: Kieran Bingham Reviewed-by: Laurent Pinchart Reviewed-by: Jacopo Mondi --- src/libcamera/v4l2_videodevice.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp index c43d7cc557a0..81098dd70190 100644 --- a/src/libcamera/v4l2_videodevice.cpp +++ b/src/libcamera/v4l2_videodevice.cpp @@ -314,10 +314,6 @@ int V4L2VideoDevice::open() return ret; } - LOG(V4L2, Debug) - << "Opened device " << caps_.bus_info() << ": " - << caps_.driver() << ": " << caps_.card(); - if (!caps_.hasStreaming()) { LOG(V4L2, Error) << "Device does not support streaming I/O"; return -EINVAL; @@ -352,6 +348,10 @@ int V4L2VideoDevice::open() fdEvent_->activated.connect(this, &V4L2VideoDevice::bufferAvailable); fdEvent_->setEnabled(false); + LOG(V4L2, Debug) + << "Opened device " << caps_.bus_info() << ": " + << caps_.driver() << ": " << caps_.card(); + return 0; } From patchwork Thu Aug 8 15:12:18 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 1752 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 30B46615FF for ; Thu, 8 Aug 2019 17:12:27 +0200 (CEST) Received: from localhost.localdomain (cpc89242-aztw30-2-0-cust488.18-1.cable.virginm.net [86.31.129.233]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 90F76B7D; Thu, 8 Aug 2019 17:12:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1565277146; bh=HE+2iIf2jmH9xq9Ah+w8FOfgMN3VSOatszIlY4aDOxI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=BVMYsW5XvrSEA1zhfcTSzMPKrRdvR7ifqiFilymo2xIrm7IWeF0cHJLKdvNCNMWMQ e+kBm/kMDeJvPYePAUd9Czja86IxgqtjF/VjaaoE29QJwyw+rhYmlqBkAR929ogPwe PX8uX+oJMCExJdcXa43P/NSlUBnHuHXjiNqv7pFg= From: Kieran Bingham To: LibCamera Devel Date: Thu, 8 Aug 2019 16:12:18 +0100 Message-Id: <20190808151221.24254-4-kieran.bingham@ideasonboard.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190808151221.24254-1-kieran.bingham@ideasonboard.com> References: <20190808151221.24254-1-kieran.bingham@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 3/6] libcamera: v4l2_videodevice: Support M2M devices X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 08 Aug 2019 15:12:27 -0000 V4L2 M2M devices represent a V4L2Device with two queues. One output, and one capture on a single device node. Represent this by instantiating a V4L2VideoDevice for each queue type, and preparing each device for its queue. Signed-off-by: Kieran Bingham --- src/libcamera/include/v4l2_videodevice.h | 26 ++- src/libcamera/v4l2_videodevice.cpp | 200 ++++++++++++++++++++--- 2 files changed, 201 insertions(+), 25 deletions(-) diff --git a/src/libcamera/include/v4l2_videodevice.h b/src/libcamera/include/v4l2_videodevice.h index f5c8da93fcb5..24c58d787fde 100644 --- a/src/libcamera/include/v4l2_videodevice.h +++ b/src/libcamera/include/v4l2_videodevice.h @@ -71,6 +71,11 @@ struct V4L2Capability final : v4l2_capability { V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_VIDEO_OUTPUT_MPLANE); } + bool isM2M() const + { + return device_caps() & (V4L2_CAP_VIDEO_M2M | + V4L2_CAP_VIDEO_M2M_MPLANE); + } bool isMeta() const { return device_caps() & (V4L2_CAP_META_CAPTURE | @@ -123,7 +128,7 @@ public: V4L2VideoDevice &operator=(const V4L2VideoDevice &) = delete; - int open(); + int open(int fd = -1, enum v4l2_buf_type type = V4L2_BUF_TYPE_PRIVATE); void close(); const char *driverName() const { return caps_.driver(); } @@ -152,6 +157,9 @@ protected: std::string logPrefix() const; private: + int queryBufferType(); + int queryBufferType(enum v4l2_buf_type type); + int getFormatMeta(V4L2DeviceFormat *format); int setFormatMeta(V4L2DeviceFormat *format); @@ -182,6 +190,22 @@ private: EventNotifier *fdEvent_; }; +class V4L2M2MDevice +{ +public: + V4L2M2MDevice(const std::string &deviceNode); + ~V4L2M2MDevice(); + + V4L2VideoDevice *output() { return output_; } + V4L2VideoDevice *capture() { return capture_; } + unsigned int status() { return status_; } + +private: + V4L2VideoDevice *output_; + V4L2VideoDevice *capture_; + unsigned int status_; +}; + } /* namespace libcamera */ #endif /* __LIBCAMERA_V4L2_VIDEODEVICE_H__ */ diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp index 81098dd70190..4bd33af5f3de 100644 --- a/src/libcamera/v4l2_videodevice.cpp +++ b/src/libcamera/v4l2_videodevice.cpp @@ -89,6 +89,12 @@ LOG_DECLARE_CATEGORY(V4L2) * \return True if the video device can capture or output images */ +/** + * \fn V4L2Capability::isM2M() + * \brief Identify if the device is an M2M device + * \return True if the device can capture and output images using the M2M API + */ + /** * \fn V4L2Capability::isMeta() * \brief Identify if the video device captures or outputs image meta-data @@ -294,17 +300,80 @@ V4L2VideoDevice::~V4L2VideoDevice() close(); } +int V4L2VideoDevice::queryBufferType() +{ + if (caps_.isVideoCapture()) { + fdEvent_ = new EventNotifier(fd(), EventNotifier::Read); + bufferType_ = caps_.isMultiplanar() + ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE + : V4L2_BUF_TYPE_VIDEO_CAPTURE; + } else if (caps_.isVideoOutput()) { + fdEvent_ = new EventNotifier(fd(), EventNotifier::Write); + bufferType_ = caps_.isMultiplanar() + ? V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE + : V4L2_BUF_TYPE_VIDEO_OUTPUT; + } else if (caps_.isMetaCapture()) { + fdEvent_ = new EventNotifier(fd(), EventNotifier::Read); + bufferType_ = V4L2_BUF_TYPE_META_CAPTURE; + } else if (caps_.isMetaOutput()) { + fdEvent_ = new EventNotifier(fd(), EventNotifier::Write); + bufferType_ = V4L2_BUF_TYPE_META_OUTPUT; + } else { + LOG(V4L2, Error) << "Device is not a supported type"; + return -EINVAL; + } + + return 0; +} + +int V4L2VideoDevice::queryBufferType(enum v4l2_buf_type type) +{ + switch (type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + fdEvent_ = new EventNotifier(fd(), EventNotifier::Write); + bufferType_ = caps_.isMultiplanar() + ? V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE + : V4L2_BUF_TYPE_VIDEO_OUTPUT; + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + fdEvent_ = new EventNotifier(fd(), EventNotifier::Read); + bufferType_ = caps_.isMultiplanar() + ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE + : V4L2_BUF_TYPE_VIDEO_CAPTURE; + break; + default: + LOG(V4L2, Error) << "Unsupported buffer type"; + return -EINVAL; + } + + return 0; +} + /** * \brief Open a V4L2 video device and query its capabilities + * + * \param[in] fd The file descriptor to set (optional) + * \param[in] type The device type to operate on (optional) + * + * Opens a device or sets the file descriptor if provided, and then queries the + * capabilities of the device and establishes the buffer types and device events + * accordingly. + * * \return 0 on success or a negative error code otherwise */ -int V4L2VideoDevice::open() +int V4L2VideoDevice::open(int fd, enum v4l2_buf_type type) { int ret; - ret = V4L2Device::open(O_RDWR | O_NONBLOCK); - if (ret < 0) - return ret; + if (fd != -1) { + ret = V4L2Device::setFd(fd); + if (ret < 0) + return ret; + } else { + ret = V4L2Device::open(O_RDWR | O_NONBLOCK); + if (ret < 0) + return ret; + } ret = ioctl(VIDIOC_QUERYCAP, &caps_); if (ret < 0) { @@ -324,26 +393,10 @@ int V4L2VideoDevice::open() * devices (POLLIN), and write notifications for OUTPUT video devices * (POLLOUT). */ - if (caps_.isVideoCapture()) { - fdEvent_ = new EventNotifier(fd(), EventNotifier::Read); - bufferType_ = caps_.isMultiplanar() - ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE - : V4L2_BUF_TYPE_VIDEO_CAPTURE; - } else if (caps_.isVideoOutput()) { - fdEvent_ = new EventNotifier(fd(), EventNotifier::Write); - bufferType_ = caps_.isMultiplanar() - ? V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE - : V4L2_BUF_TYPE_VIDEO_OUTPUT; - } else if (caps_.isMetaCapture()) { - fdEvent_ = new EventNotifier(fd(), EventNotifier::Read); - bufferType_ = V4L2_BUF_TYPE_META_CAPTURE; - } else if (caps_.isMetaOutput()) { - fdEvent_ = new EventNotifier(fd(), EventNotifier::Write); - bufferType_ = V4L2_BUF_TYPE_META_OUTPUT; - } else { - LOG(V4L2, Error) << "Device is not a supported type"; - return -EINVAL; - } + if (type != V4L2_BUF_TYPE_PRIVATE) + queryBufferType(type); + else + queryBufferType(); fdEvent_->activated.connect(this, &V4L2VideoDevice::bufferAvailable); fdEvent_->setEnabled(false); @@ -1143,4 +1196,103 @@ V4L2VideoDevice *V4L2VideoDevice::fromEntityName(const MediaDevice *media, return new V4L2VideoDevice(mediaEntity); } +/** + * \class V4L2M2MDevice + * \brief Memory2Memory device container + * + * The V4L2M2MDevice manages two V4L2Device instances on the same + * deviceNode which operate together using two queues to implement the V4L2 + * Memory to Memory API. + * + * V4L2M2MDevices are opened at the point they are created, and will return + * both a capture and an output device or neither in the event of failure. + * + * The two devices should be configured and started as normal V4L2Device + * instances. Closing either of the devices will require recreating a new + * V4L2M2MDevice. + * + * The two V4L2Device instances share a single device node which is opened at + * the point the V4L2M2MDevice is created. + */ + +/** + * \fn V4L2M2MDevice::output + * \return The output V4L2Device instance + */ + +/** + * \fn V4L2M2MDevice::capture + * \return The capture V4L2Device instance + */ + +/** + * \fn V4L2M2MDevice::status + * \return The construction status of the V4L2M2MDevice + */ + +/** + * \brief Create a new V4L2M2MDevice from the \a deviceNode + * + * The deviceNode will be opened and shared across two V4L2VideoDevice + * instances. One to control the output stream, and one to control the capture + * stream. + * + * After construction, the status() must be checked to validate the instance. + */ +V4L2M2MDevice::V4L2M2MDevice(const std::string &deviceNode) + : status_(0) +{ + int fd[2] = { -1, -1 }; + int ret; + + output_ = new V4L2VideoDevice(deviceNode); + capture_ = new V4L2VideoDevice(deviceNode); + + /* Both V4L2Devices will have the same deviceNode. */ + fd[0] = ::open(deviceNode.c_str(), O_RDWR | O_NONBLOCK); + if (fd[0] < 0) { + ret = -errno; + LOG(V4L2, Error) + << "Failed to open V4L2 M2M device: " << strerror(-ret); + goto err; + } + + fd[1] = dup(fd[0]); + if (fd[1] < 0) { + ret = -errno; + LOG(V4L2, Error) + << "Failed to duplicate M2M device: " << strerror(-ret); + + goto err; + } + + ret = output_->open(fd[0], V4L2_BUF_TYPE_VIDEO_OUTPUT); + if (ret) + goto err; + + ret = capture_->open(fd[1], V4L2_BUF_TYPE_VIDEO_CAPTURE); + if (ret) + goto err; + + return; + +err: + delete capture_; + delete output_; + + capture_ = nullptr; + output_ = nullptr; + + status_ = ret; + + close(fd[1]); + close(fd[0]); +} + +V4L2M2MDevice::~V4L2M2MDevice() +{ + delete capture_; + delete output_; +} + } /* namespace libcamera */ From patchwork Thu Aug 8 15:12:19 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 1753 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 9F709615FF for ; Thu, 8 Aug 2019 17:12:27 +0200 (CEST) Received: from localhost.localdomain (cpc89242-aztw30-2-0-cust488.18-1.cable.virginm.net [86.31.129.233]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3CEDCCC; Thu, 8 Aug 2019 17:12:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1565277147; bh=dKZdRJSfVfYxD4Lj8YGvNVGI4c2LaIsSy7sDoAsAd9Q=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=TOUlaWGf/miJCQ2ypoidYC4/ORvLuTZNrrUNGAXuqCrsIkuo9CPEzcGbWITX5QcHB q5YTH2qPX+jLeydgILzgReoErdEXWYTgJKmvY7EH15+cMA20IlbcxKafpGQW4Ly1PJ ueQlRnAsq7+MOOk2eW94uS6Rw3J6SkwB+kB0c7/w= From: Kieran Bingham To: LibCamera Devel Date: Thu, 8 Aug 2019 16:12:19 +0100 Message-Id: <20190808151221.24254-5-kieran.bingham@ideasonboard.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190808151221.24254-1-kieran.bingham@ideasonboard.com> References: <20190808151221.24254-1-kieran.bingham@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 4/6] test: v4l2_videodevice: Add M2M device test X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 08 Aug 2019 15:12:27 -0000 The V4L2M2MDevice requires two pipelines to be configured. This makes it unsuitable to reuse the existing V4L2DeviceTest test library in it's current form. Implement a full test to run the two M2M pipelines through VIM2M. Signed-off-by: Kieran Bingham --- test/v4l2_videodevice/meson.build | 1 + test/v4l2_videodevice/v4l2_m2mdevice.cpp | 210 +++++++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 test/v4l2_videodevice/v4l2_m2mdevice.cpp diff --git a/test/v4l2_videodevice/meson.build b/test/v4l2_videodevice/meson.build index 76be5e142bb6..ad41898b5f8b 100644 --- a/test/v4l2_videodevice/meson.build +++ b/test/v4l2_videodevice/meson.build @@ -7,6 +7,7 @@ v4l2_videodevice_tests = [ [ 'stream_on_off', 'stream_on_off.cpp' ], [ 'capture_async', 'capture_async.cpp' ], [ 'buffer_sharing', 'buffer_sharing.cpp' ], + [ 'v4l2_m2mdevice', 'v4l2_m2mdevice.cpp' ], ] foreach t : v4l2_videodevice_tests diff --git a/test/v4l2_videodevice/v4l2_m2mdevice.cpp b/test/v4l2_videodevice/v4l2_m2mdevice.cpp new file mode 100644 index 000000000000..7a730f695ab7 --- /dev/null +++ b/test/v4l2_videodevice/v4l2_m2mdevice.cpp @@ -0,0 +1,210 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * libcamera V4L2 API tests + */ + +#include +#include +#include +#include + +#include +#include + +#include "device_enumerator.h" +#include "media_device.h" +#include "v4l2_videodevice.h" + +#include "test.h" + +using namespace std; +using namespace libcamera; + +class V4L2M2MDeviceTest : public Test +{ +public: + V4L2M2MDeviceTest() + : vim2m_(nullptr), outputFrames_(0), captureFrames_(0) + { + } + + void outputBufferComplete(Buffer *buffer) + { + std::cout << "Received output buffer " << buffer->index() + << std::endl; + + outputFrames_++; + + /* Requeue the buffer for further use. */ + vim2m_->output()->queueBuffer(buffer); + } + + void receiveCaptureBuffer(Buffer *buffer) + { + std::cout << "Received capture buffer " << buffer->index() + << std::endl; + + captureFrames_++; + + /* Requeue the buffer for further use. */ + vim2m_->capture()->queueBuffer(buffer); + } + +protected: + int init() + { + enumerator_ = DeviceEnumerator::create(); + if (!enumerator_) { + cerr << "Failed to create device enumerator" << endl; + return TestFail; + } + + if (enumerator_->enumerate()) { + cerr << "Failed to enumerate media devices" << endl; + return TestFail; + } + + DeviceMatch dm("vim2m"); + dm.add("vim2m-source"); + dm.add("vim2m-sink"); + + media_ = enumerator_->search(dm); + if (!media_) { + cerr << "Failed to match device" << endl; + return TestSkip; + } + + MediaEntity *entity = media_->getEntityByName("vim2m-source"); + if (!entity) { + cerr << "Failed to get device entity" << endl; + return TestSkip; + } + + vim2m_ = new V4L2M2MDevice(entity->deviceNode()); + if (vim2m_->status()) + return TestFail; + + V4L2DeviceFormat format = {}; + if (vim2m_->capture()->getFormat(&format)) + return TestFail; + + format.size.width = 640; + format.size.height = 480; + + if (vim2m_->capture()->setFormat(&format)) + return TestFail; + + if (vim2m_->output()->setFormat(&format)) + return TestFail; + + cerr << "Initialised M2M ..." << endl; + + return TestPass; + } + + int run() + { + const unsigned int bufferCount = 8; + + EventDispatcher *dispatcher = CameraManager::instance()->eventDispatcher(); + Timer timeout; + int ret; + + capturePool_.createBuffers(bufferCount); + outputPool_.createBuffers(bufferCount); + + ret = vim2m_->capture()->exportBuffers(&capturePool_); + if (ret) { + cerr << "Failed to export Capture Buffers" << endl; + return TestFail; + } + + ret = vim2m_->output()->exportBuffers(&outputPool_); + if (ret) { + cerr << "Failed to export Output Buffers" << endl; + return TestFail; + } + + vim2m_->capture()->bufferReady.connect(this, &V4L2M2MDeviceTest::receiveCaptureBuffer); + vim2m_->output()->bufferReady.connect(this, &V4L2M2MDeviceTest::outputBufferComplete); + + /* We can't "queueAllBuffers()" on an output device, so we do it manually */ + std::vector> outputBuffers; + for (unsigned int i = 0; i < outputPool_.count(); ++i) { + Buffer *buffer = new Buffer(i); + outputBuffers.emplace_back(buffer); + ret = vim2m_->output()->queueBuffer(buffer); + if (ret) + return {}; + } + + std::vector> captureBuffers; + captureBuffers = vim2m_->capture()->queueAllBuffers(); + if (captureBuffers.empty()) { + cerr << "Failed to queue all Capture Buffers" << endl; + return TestFail; + } + + ret = vim2m_->output()->streamOn(); + if (ret) { + cerr << "Failed to streamOn output" << endl; + return TestFail; + } + + ret = vim2m_->capture()->streamOn(); + if (ret) { + cerr << "Failed to streamOn capture" << endl; + return TestFail; + } + + timeout.start(10000); + while (timeout.isRunning()) { + dispatcher->processEvents(); + if (captureFrames_ > 30) + break; + } + + if (captureFrames_ < 1) { + std::cout << "Failed to capture any frames within timeout." << std::endl; + return TestFail; + } + + if (captureFrames_ < 30) { + std::cout << "Failed to capture 30 frames within timeout." << std::endl; + return TestFail; + } + + std::cout << "Output " << outputFrames_ << " frames" << std::endl; + std::cout << "Captured " << captureFrames_ << " frames" << std::endl; + + ret = vim2m_->capture()->streamOff(); + if (ret) + return TestFail; + + ret = vim2m_->output()->streamOff(); + if (ret) + return TestFail; + + return TestPass; + } + + void cleanup() + { + delete vim2m_; + }; + +private: + std::unique_ptr enumerator_; + std::shared_ptr media_; + V4L2M2MDevice *vim2m_; + + BufferPool capturePool_; + BufferPool outputPool_; + + unsigned int outputFrames_; + unsigned int captureFrames_; +}; + +TEST_REGISTER(V4L2M2MDeviceTest); From patchwork Thu Aug 8 15:12:20 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 1754 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 F17566162B for ; Thu, 8 Aug 2019 17:12:27 +0200 (CEST) Received: from localhost.localdomain (cpc89242-aztw30-2-0-cust488.18-1.cable.virginm.net [86.31.129.233]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 92AB8555; Thu, 8 Aug 2019 17:12:27 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1565277147; bh=PZ0Pcq9Co12WIE6KD6hqDpe25Af9mkFHXHCiqUPzlg8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=OHJvlOKppCOIJEhM6q48p2vUGiRTvColQX+XMhHrlXsNvhepOKzvPRILL7t167N+9 UcdLWDs2yhs9rCW+3cf7cE/njY008ma+1QA5LTqkMWz2hCzrnuA5kGX2OrXrqL5P9h SpFxz2OSWiLBEFjaps6LcWDOXJt2D7vpKikF2leI= From: Kieran Bingham To: LibCamera Devel Date: Thu, 8 Aug 2019 16:12:20 +0100 Message-Id: <20190808151221.24254-6-kieran.bingham@ideasonboard.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190808151221.24254-1-kieran.bingham@ideasonboard.com> References: <20190808151221.24254-1-kieran.bingham@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 5/6] test: v4l2_device: Remove unused function X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 08 Aug 2019 15:12:28 -0000 The 'exists()' call is not used within the code base. Remove it. Signed-off-by: Kieran Bingham Reviewed-by: Laurent Pinchart --- test/v4l2_videodevice/v4l2_videodevice_test.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/test/v4l2_videodevice/v4l2_videodevice_test.cpp b/test/v4l2_videodevice/v4l2_videodevice_test.cpp index b26d06ad4519..b4f4ae672580 100644 --- a/test/v4l2_videodevice/v4l2_videodevice_test.cpp +++ b/test/v4l2_videodevice/v4l2_videodevice_test.cpp @@ -6,7 +6,6 @@ */ #include -#include #include "v4l2_videodevice_test.h" @@ -16,16 +15,6 @@ using namespace std; using namespace libcamera; -bool exists(const std::string &path) -{ - struct stat sb; - - if (stat(path.c_str(), &sb) == 0) - return true; - - return false; -} - int V4L2VideoDeviceTest::init() { enumerator_ = DeviceEnumerator::create(); From patchwork Thu Aug 8 15:12:21 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 1755 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 7006D61620 for ; Thu, 8 Aug 2019 17:12:28 +0200 (CEST) Received: from localhost.localdomain (cpc89242-aztw30-2-0-cust488.18-1.cable.virginm.net [86.31.129.233]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E6A08CC; Thu, 8 Aug 2019 17:12:27 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1565277148; bh=ut3sD1T0kboJx3d8eUc+FZhmLuwtehfrxSpQoG7zK+c=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=pAw2kHV9YtiWLgzMkb6dv/x6wGshzKTTKcC0IU1uRnBvpcqBwmFQHfX5TePb09EVp xBnFRjYdc5N2Pc8PAgVCnY6VcTlQGHPczwnLmlCXk3Qmg6i3z48b1F4Iwh3DoDzKBX mp2Gdm3QzwI1zBTqRxuz3K55UF5X1Gk3lTMsot7o= From: Kieran Bingham To: LibCamera Devel Date: Thu, 8 Aug 2019 16:12:21 +0100 Message-Id: <20190808151221.24254-7-kieran.bingham@ideasonboard.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190808151221.24254-1-kieran.bingham@ideasonboard.com> References: <20190808151221.24254-1-kieran.bingham@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 6/6] [PoC/RFC] libcamera: pipeline: Add RaspberryPi handler X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 08 Aug 2019 15:12:29 -0000 Utilise the CameraSensor class and construct a pipeline for a single sensor on the Unicam, routed through the V4L2 Codec ISP interface. Signed-off-by: Kieran Bingham --- src/libcamera/pipeline/meson.build | 1 + src/libcamera/pipeline/raspberrypi.cpp | 425 +++++++++++++++++++++++++ 2 files changed, 426 insertions(+) create mode 100644 src/libcamera/pipeline/raspberrypi.cpp diff --git a/src/libcamera/pipeline/meson.build b/src/libcamera/pipeline/meson.build index 0d466225a72e..7ed7b26f3033 100644 --- a/src/libcamera/pipeline/meson.build +++ b/src/libcamera/pipeline/meson.build @@ -1,4 +1,5 @@ libcamera_sources += files([ + 'raspberrypi.cpp', 'uvcvideo.cpp', 'vimc.cpp', ]) diff --git a/src/libcamera/pipeline/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi.cpp new file mode 100644 index 000000000000..4c4c3dea0113 --- /dev/null +++ b/src/libcamera/pipeline/raspberrypi.cpp @@ -0,0 +1,425 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * raspberrypi.cpp - Pipeline handler for raspberrypi devices + */ + +#include +#include +#include + +#include "camera_sensor.h" +#include "device_enumerator.h" +#include "log.h" +#include "media_device.h" +#include "pipeline_handler.h" +#include "utils.h" +#include "v4l2_controls.h" +#include "v4l2_videodevice.h" + +namespace libcamera { + +LOG_DEFINE_CATEGORY(RPI) + +class RPiCameraData : public CameraData +{ +public: + RPiCameraData(PipelineHandler *pipe) + : CameraData(pipe), sensor_(nullptr), unicam_(nullptr), + isp_(nullptr) + { + } + + ~RPiCameraData() + { + bayerBuffers_.destroyBuffers(); + delete unicam_; + delete isp_; + } + + void sensorReady(Buffer *buffer); + void ispOutputReady(Buffer *buffer); + void ispCaptureReady(Buffer *buffer); + + CameraSensor *sensor_; + V4L2VideoDevice *unicam_; + V4L2M2MDevice *isp_; + Stream stream_; + + BufferPool bayerBuffers_; + std::vector> rawBuffers_; +}; + +class RPiCameraConfiguration : public CameraConfiguration +{ +public: + RPiCameraConfiguration(); + + Status validate() override; +}; + +class PipelineHandlerRPi : public PipelineHandler +{ +public: + PipelineHandlerRPi(CameraManager *manager); + ~PipelineHandlerRPi(); + + CameraConfiguration * + generateConfiguration(Camera *camera, + const StreamRoles &roles) override; + int configure(Camera *camera, + CameraConfiguration *config) override; + + int allocateBuffers(Camera *camera, + const std::set &streams) override; + int freeBuffers(Camera *camera, + const std::set &streams) override; + + int start(Camera *camera) override; + void stop(Camera *camera) override; + + int queueRequest(Camera *camera, Request *request) override; + + bool match(DeviceEnumerator *enumerator) override; + +private: + RPiCameraData *cameraData(const Camera *camera) + { + return static_cast( + PipelineHandler::cameraData(camera)); + } + + std::shared_ptr unicam_; + std::shared_ptr codec_; +}; + +RPiCameraConfiguration::RPiCameraConfiguration() + : CameraConfiguration() +{ +} + +CameraConfiguration::Status RPiCameraConfiguration::validate() +{ + Status status = Valid; + + if (config_.empty()) + return Invalid; + + /* Todo: Experiment with increased stream support through the ISP. */ + if (config_.size() > 1) { + config_.resize(1); + status = Adjusted; + } + + StreamConfiguration &cfg = config_[0]; + + /* Todo: restrict to hardware capabilities. */ + + cfg.bufferCount = 4; + + return status; +} + +PipelineHandlerRPi::PipelineHandlerRPi(CameraManager *manager) + : PipelineHandler(manager), unicam_(nullptr), codec_(nullptr) +{ +} + +PipelineHandlerRPi::~PipelineHandlerRPi() +{ + if (unicam_) + unicam_->release(); + + if (codec_) + codec_->release(); +} + +CameraConfiguration * +PipelineHandlerRPi::generateConfiguration(Camera *camera, + const StreamRoles &roles) +{ + CameraConfiguration *config = new RPiCameraConfiguration(); + + if (roles.empty()) + return config; + + StreamConfiguration cfg{}; + cfg.pixelFormat = V4L2_PIX_FMT_YUYV; + cfg.size = { 1920, 1080 }; // data->sensor_->resolution(); + cfg.bufferCount = 4; + + LOG(RPI, Debug) << "Config default as " << cfg.toString(); + + config->addConfiguration(cfg); + + config->validate(); + + return config; +} + +int PipelineHandlerRPi::configure(Camera *camera, + CameraConfiguration *config) +{ + RPiCameraData *data = cameraData(camera); + StreamConfiguration &cfg = config->at(0); + int ret; + + Size sensorSize = { 1920, 1080 }; + Size outputSize = { 1920, 1088 }; + + V4L2DeviceFormat format = {}; + format.size = sensorSize; + format.fourcc = V4L2_PIX_FMT_SRGGB10P; + + LOG(RPI, Debug) << "Setting format to " << format.toString(); + + ret = data->unicam_->setFormat(&format); + if (ret) + return ret; + + if (format.size != sensorSize || + format.fourcc != V4L2_PIX_FMT_SRGGB10P) { + LOG(RPI, Error) + << "Failed to set format on Video device: " + << format.toString(); + return -EINVAL; + } + + format.size = outputSize; + + ret = data->isp_->output()->setFormat(&format); + + if (format.size != outputSize || + format.fourcc != V4L2_PIX_FMT_SRGGB10P) { + LOG(RPI, Error) << "Failed to set format on ISP output device: " + << format.toString(); + return -EINVAL; + } + + /* Configure the ISP based to generate the requested size and format. */ + format.size = cfg.size; + format.fourcc = cfg.pixelFormat; + + ret = data->isp_->capture()->setFormat(&format); + + if (format.size != cfg.size || + format.fourcc != cfg.pixelFormat) { + LOG(RPI, Error) + << "Failed to set format on ISP capture device: " + << format.toString(); + return -EINVAL; + } + + cfg.setStream(&data->stream_); + + return 0; +} + +int PipelineHandlerRPi::allocateBuffers(Camera *camera, + const std::set &streams) +{ + RPiCameraData *data = cameraData(camera); + Stream *stream = *streams.begin(); + const StreamConfiguration &cfg = stream->configuration(); + int ret; + + LOG(RPI, Debug) << "Requesting " << cfg.bufferCount << " buffers"; + + /* + * Buffers are allocated on the camera, and the capture pad of the ISP + * unicam -> isp.output -> isp.capture -> Application + */ + + /* Create a new intermediate buffer pool */ + data->bayerBuffers_.createBuffers(cfg.bufferCount); + + /* Tie the unicam video buffers to the intermediate pool */ + ret = data->unicam_->exportBuffers(&data->bayerBuffers_); + if (ret) + return ret; + + ret = data->isp_->output()->importBuffers(&data->bayerBuffers_); + if (ret) + return ret; + + /* Tie the stream buffers to the capture device of the ISP */ + if (stream->memoryType() == InternalMemory) + ret = data->isp_->capture()->exportBuffers(&stream->bufferPool()); + else + ret = data->isp_->capture()->importBuffers(&stream->bufferPool()); + + return ret; +} + +int PipelineHandlerRPi::freeBuffers(Camera *camera, + const std::set &streams) +{ + RPiCameraData *data = cameraData(camera); + int ret; + + ret = data->unicam_->releaseBuffers(); + ret = data->isp_->output()->releaseBuffers(); + ret = data->isp_->capture()->releaseBuffers(); + + data->bayerBuffers_.destroyBuffers(); + + return ret; +} + +int PipelineHandlerRPi::start(Camera *camera) +{ + RPiCameraData *data = cameraData(camera); + int ret; + + data->rawBuffers_ = data->unicam_->queueAllBuffers(); + if (data->rawBuffers_.empty()) { + LOG(RPI, Debug) << "Failed to queue unicam buffers"; + return -EINVAL; + } + + LOG(RPI, Warning) << "Using hard-coded exposure/gain defaults"; + + V4L2ControlList controls; + controls.add(V4L2_CID_EXPOSURE, 1700); + controls.add(V4L2_CID_ANALOGUE_GAIN, 180); + ret = data->unicam_->setControls(&controls); + if (ret) { + LOG(RPI, Error) << "Failed to set controls"; + return ret; + } + + ret = data->isp_->output()->streamOn(); + if (ret) + return ret; + + ret = data->isp_->capture()->streamOn(); + if (ret) + return ret; + + ret = data->unicam_->streamOn(); + if (ret) + return ret; + + return ret; +} + +void PipelineHandlerRPi::stop(Camera *camera) +{ + RPiCameraData *data = cameraData(camera); + + data->isp_->capture()->streamOff(); + data->isp_->output()->streamOff(); + data->unicam_->streamOff(); + + data->rawBuffers_.clear(); +} + +int PipelineHandlerRPi::queueRequest(Camera *camera, Request *request) +{ + RPiCameraData *data = cameraData(camera); + Buffer *buffer = request->findBuffer(&data->stream_); + if (!buffer) { + LOG(RPI, Error) + << "Attempt to queue request with invalid stream"; + + return -ENOENT; + } + + int ret = data->isp_->capture()->queueBuffer(buffer); + if (ret < 0) + return ret; + + PipelineHandler::queueRequest(camera, request); + + return 0; +} + +bool PipelineHandlerRPi::match(DeviceEnumerator *enumerator) +{ + DeviceMatch unicam("unicam"); + DeviceMatch codec("bcm2835-codec"); + + /* We explicitly need the ISP device from the MMAL codec driver. */ + codec.add("bcm2835-codec-isp"); + + unicam_ = enumerator->search(unicam); + if (!unicam_) + return false; + + codec_ = enumerator->search(codec); + if (!codec_) + return false; + + unicam_->acquire(); + codec_->acquire(); + + std::unique_ptr data = utils::make_unique(this); + + /* Locate and open the unicam video node. */ + data->unicam_ = new V4L2VideoDevice(unicam_->getEntityByName("unicam")); + if (data->unicam_->open()) + return false; + + /* Locate the ISP M2M node */ + MediaEntity *isp = codec_->getEntityByName("bcm2835-codec-isp"); + if (!isp) + return false; + + data->isp_ = new V4L2M2MDevice(isp->deviceNode()); + if (data->isp_->status()) { + LOG(RPI, Error) << "Could not create the ISP device"; + return false; + } + + data->unicam_->bufferReady.connect(data.get(), &RPiCameraData::sensorReady); + data->isp_->output()->bufferReady.connect(data.get(), &RPiCameraData::ispOutputReady); + data->isp_->capture()->bufferReady.connect(data.get(), &RPiCameraData::ispCaptureReady); + + /* Identify the sensor */ + for (MediaEntity *entity : unicam_->entities()) { + if (entity->function() == MEDIA_ENT_F_CAM_SENSOR) { + data->sensor_ = new CameraSensor(entity); + break; + } + } + + if (!data->sensor_) + return false; + + int ret = data->sensor_->init(); + if (ret) + return false; + + /* Create and register the camera. */ + std::set streams{ &data->stream_ }; + std::shared_ptr camera = + Camera::create(this, data->sensor_->entity()->name(), streams); + registerCamera(std::move(camera), std::move(data)); + + return true; +} + +void RPiCameraData::sensorReady(Buffer *buffer) +{ + /* Deliver the frame from the sensor to the ISP */ + isp_->output()->queueBuffer(buffer); +} + +void RPiCameraData::ispOutputReady(Buffer *buffer) +{ + /* Return a completed buffer from the ISP back to the sensor */ + unicam_->queueBuffer(buffer); +} + +void RPiCameraData::ispCaptureReady(Buffer *buffer) +{ + Request *request = buffer->request(); + + pipe_->completeBuffer(camera_, request, buffer); + pipe_->completeRequest(camera_, request); +} + +REGISTER_PIPELINE_HANDLER(PipelineHandlerRPi); + +} /* namespace libcamera */