From patchwork Tue Aug 13 09:40:15 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 1800 Return-Path: Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3819060E2E for ; Tue, 13 Aug 2019 11:40:25 +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 7F7D5327; Tue, 13 Aug 2019 11:40:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1565689224; bh=xI2XcWja3BLJ6a0uba0Qub2XZnAnpLCWWkJyhpEgTXM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=l23AjlQbYzPz+56z+lqA/dbwag8evyov10QUHJ5YheOmenB7BWG+gLk2pnX+etQjF gRBmDFt26D88OoppIM0c4Qh+j8NCTVtSXWuFqdXjaH8QGWmvNJvgrjzynDTVJpnUkx g/SzTzcH6tt8wGZ995GesK60hhiZbGQ32F/KoZ2I= From: Kieran Bingham To: LibCamera Devel Date: Tue, 13 Aug 2019 10:40:15 +0100 Message-Id: <20190813094020.10277-2-kieran.bingham@ideasonboard.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190813094020.10277-1-kieran.bingham@ideasonboard.com> References: <20190813094020.10277-1-kieran.bingham@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 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: Tue, 13 Aug 2019 09:40:25 -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 | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/libcamera/include/v4l2_device.h b/src/libcamera/include/v4l2_device.h index e7e9829cb05f..75a52c33d821 100644 --- a/src/libcamera/include/v4l2_device.h +++ b/src/libcamera/include/v4l2_device.h @@ -35,6 +35,7 @@ protected: ~V4L2Device(); int open(unsigned int flags); + int setFd(int fd); int ioctl(unsigned long request, void *argp); diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp index 9a00566a532b..f89546610ac6 100644 --- a/src/libcamera/v4l2_device.cpp +++ b/src/libcamera/v4l2_device.cpp @@ -88,6 +88,32 @@ int V4L2Device::open(unsigned int flags) return 0; } +/** + * \brief Set the file descriptor of a V4L2 device + * \param[in] fd The file descriptor handle + * + * This method allows a device to provide an already opened file descriptor + * referring to the V4L2 device node, instead of opening it with open(). This + * can be used for V4L2 M2M devices where a single video device node is used for + * both the output and capture devices, or when receiving an open file + * descriptor in a context that doesn't have permission to open the device node + * itself. + * + * This method and the open() method are mutually exclusive, only one of the two + * shall be used for a V4L2Device instance. + * + * \return 0 on success or a negative error code otherwise + */ +int V4L2Device::setFd(int fd) +{ + if (isOpen()) + return -EBUSY; + + fd_ = fd; + + return 0; +} + /** * \brief Close the device node * From patchwork Tue Aug 13 09:40: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: 1801 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 6FCEB60E2E for ; Tue, 13 Aug 2019 11:40:25 +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 E3BE09C2; Tue, 13 Aug 2019 11:40:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1565689225; bh=IfHHB8dfTYxK3Lv0mDTbPdiEtnFRE5efgajG5Or+d+M=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=IyOKGW6mnyw7g2Ji3u9QxZYC3NHxZWp1LFen5frfm3IN5wj8LDbmnYHuhSa4WqXsC HN95jGuVJe23KOAUh4hYzkpIcePvUn2Ut73fYihRhc9r/coQJjqEATV/oyZSb2sv1X lblMPtEwiwYSrIxTKPThptXx4zFTlIwTzywY72Vw= From: Kieran Bingham To: LibCamera Devel Date: Tue, 13 Aug 2019 10:40:16 +0100 Message-Id: <20190813094020.10277-3-kieran.bingham@ideasonboard.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190813094020.10277-1-kieran.bingham@ideasonboard.com> References: <20190813094020.10277-1-kieran.bingham@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 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: Tue, 13 Aug 2019 09:40:25 -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 Tue Aug 13 09:40: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: 1802 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 BFC3C61624 for ; Tue, 13 Aug 2019 11:40:25 +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 4E3DAFA0; Tue, 13 Aug 2019 11:40:25 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1565689225; bh=f/HUeRJSLJmSZu+rCLW2nAb7pY/qBSCbAOGt8l90Apk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=vpU9opdQlpvjQ4dShsh1WU8iphDTItPDioUwCNAdFHXVKwsz6COQatEGq9b+4pu6w oIhYHu6lYg8AKgOv/vxZR9HakYiadWbTimsbs7CRIKTMK2U2w+r+7l3NgE1l4AC0xX mu3B3z6RsHu2gW2JkM4ZL9BtvbmOn4VdeJfhpTgQ= From: Kieran Bingham To: LibCamera Devel Date: Tue, 13 Aug 2019 10:40:17 +0100 Message-Id: <20190813094020.10277-4-kieran.bingham@ideasonboard.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190813094020.10277-1-kieran.bingham@ideasonboard.com> References: <20190813094020.10277-1-kieran.bingham@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 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: Tue, 13 Aug 2019 09:40:25 -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 Reviewed-by: Laurent Pinchart Reviewed-by: Jacopo Mondi --- src/libcamera/include/v4l2_videodevice.h | 25 +++ src/libcamera/v4l2_videodevice.cpp | 194 ++++++++++++++++++++++- 2 files changed, 218 insertions(+), 1 deletion(-) diff --git a/src/libcamera/include/v4l2_videodevice.h b/src/libcamera/include/v4l2_videodevice.h index f5c8da93fcb5..0fc2dcd81d2b 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 | @@ -124,6 +129,7 @@ public: V4L2VideoDevice &operator=(const V4L2VideoDevice &) = delete; int open(); + int open(int handle, enum v4l2_buf_type type); void close(); const char *driverName() const { return caps_.driver(); } @@ -182,6 +188,25 @@ private: EventNotifier *fdEvent_; }; +class V4L2M2MDevice +{ +public: + V4L2M2MDevice(const std::string &deviceNode); + ~V4L2M2MDevice(); + + int open(); + void close(); + + V4L2VideoDevice *output() { return output_; } + V4L2VideoDevice *capture() { return capture_; } + +private: + std::string deviceNode_; + + V4L2VideoDevice *output_; + V4L2VideoDevice *capture_; +}; + } /* namespace libcamera */ #endif /* __LIBCAMERA_V4L2_VIDEODEVICE_H__ */ diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp index 81098dd70190..5e41f44eeb50 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 @@ -295,7 +301,8 @@ V4L2VideoDevice::~V4L2VideoDevice() } /** - * \brief Open a V4L2 video device and query its capabilities + * \brief Open the V4L2 video device node and query its capabilities + * * \return 0 on success or a negative error code otherwise */ int V4L2VideoDevice::open() @@ -355,6 +362,92 @@ int V4L2VideoDevice::open() return 0; } +/** + * \brief Open a V4L2 video device from an opened file handle and query its + * capabilities + * \param[in] handle The file descriptor to set + * \param[in] type The device type to operate on + * + * This methods opens a video device from the existing file descriptor \a + * handle. Like open(), this method queries the capabilities of the device, but + * handles it according to the given device \a type instead of determining its + * type from the capabilities. This can be used to force a given device type for + * M2M devices. + * + * The file descriptor \a handle is duplicated, and the caller is responsible + * for closing the \a handle when it has no further use for it. The close() + * method will close the duplicated file descriptor, leaving \a handle + * untouched. + * + * \return 0 on success or a negative error code otherwise + */ +int V4L2VideoDevice::open(int handle, enum v4l2_buf_type type) +{ + int ret; + int newFd; + + newFd = dup(handle); + if (newFd < 0) { + ret = -errno; + LOG(V4L2, Error) << "Failed to duplicate file handle: " + << strerror(-ret); + return ret; + } + + ret = V4L2Device::setFd(newFd); + if (ret < 0) { + LOG(V4L2, Error) << "Failed to set file handle: " + << strerror(-ret); + ::close(newFd); + return ret; + } + + ret = ioctl(VIDIOC_QUERYCAP, &caps_); + if (ret < 0) { + LOG(V4L2, Error) + << "Failed to query device capabilities: " + << strerror(-ret); + return ret; + } + + if (!caps_.hasStreaming()) { + LOG(V4L2, Error) << "Device does not support streaming I/O"; + return -EINVAL; + } + + /* + * Set buffer type and wait for read notifications on CAPTURE video + * devices (POLLIN), and write notifications for OUTPUT video devices + * (POLLOUT). + */ + 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; + } + + fdEvent_->activated.connect(this, &V4L2VideoDevice::bufferAvailable); + fdEvent_->setEnabled(false); + + LOG(V4L2, Debug) + << "Opened device " << caps_.bus_info() << ": " + << caps_.driver() << ": " << caps_.card(); + + return 0; +} + /** * \brief Close the video device, releasing any resources acquired by open() */ @@ -1143,4 +1236,103 @@ V4L2VideoDevice *V4L2VideoDevice::fromEntityName(const MediaDevice *media, return new V4L2VideoDevice(mediaEntity); } +/** + * \class V4L2M2MDevice + * \brief Memory2Memory video device + * + * The V4L2M2MDevice manages two V4L2VideoDevice instances on the same + * deviceNode which operate together using two queues to implement the V4L2 + * Memory to Memory API. + * + * The two devices should be opened by calling open() on the V4L2M2MDevice, and + * can be closed by calling close on the V4L2M2MDevice. + * + * Calling V4L2VideoDevice::open() and V4L2VideoDevice::close() on the capture + * or output V4L2VideoDevice is not permitted. + */ + +/** + * \fn V4L2M2MDevice::output + * \brief Retrieve the output V4L2VideoDevice instance + * \return The output V4L2Device instance + */ + +/** + * \fn V4L2M2MDevice::capture + * \brief Retrieve the capture V4L2VideoDevice instance + * \return The capture V4L2Device instance + */ + +/** + * \brief Create a new V4L2M2MDevice from the \a deviceNode + */ +V4L2M2MDevice::V4L2M2MDevice(const std::string &deviceNode) + : deviceNode_(deviceNode) +{ + output_ = new V4L2VideoDevice(deviceNode); + capture_ = new V4L2VideoDevice(deviceNode); +} + +V4L2M2MDevice::~V4L2M2MDevice() +{ + delete capture_; + delete output_; +} + +/** + * \brief Open a V4L2 Memory to Memory device + * + * Open the device node and prepare the two V4L2VideoDevice instances to handle + * their respective buffer queues. + * + * \return 0 on success or a negative error code otherwise + */ +int V4L2M2MDevice::open() +{ + int fd; + int ret; + + /* + * The output and capture V4L2VideoDevice instances use the same file + * handle for the same device node. The local file handle can be closed + * as the V4L2VideoDevice::open() retains a handle by duplicating the + * fd passed in. + */ + fd = ::open(deviceNode_.c_str(), O_RDWR | O_NONBLOCK); + if (fd < 0) { + ret = -errno; + LOG(V4L2, Error) + << "Failed to open V4L2 M2M device: " << strerror(-ret); + return ret; + } + + ret = output_->open(fd, V4L2_BUF_TYPE_VIDEO_OUTPUT); + if (ret) + goto err; + + ret = capture_->open(fd, V4L2_BUF_TYPE_VIDEO_CAPTURE); + if (ret) + goto err; + + ::close(fd); + + return 0; + +err: + close(); + ::close(fd); + + return ret; +} + +/** + * \brief Close the memory-to-memory device, releasing any resources acquired by + * open() + */ +void V4L2M2MDevice::close() +{ + capture_->close(); + output_->close(); +} + } /* namespace libcamera */ From patchwork Tue Aug 13 09:40: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: 1803 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 3010E61624 for ; Tue, 13 Aug 2019 11:40: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 ACBDB30F; Tue, 13 Aug 2019 11:40:25 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1565689225; bh=+h3NGbgpj3N9PBiKaHYzFyXp8tC94Xb5e0DlfDTfRl0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=eq3mhVu6DrHq8D2fWXVjmk4kARzqKAPabIx0iPIEC/bzGbnSY4euPZBj1pUCLYfdP COW/UMqIWvX6UQOKEVWwzP6yEkEHkU5EUYP6fzowoFpOB/0jLTAX4lH6yfPDCy1wbc YHSWZ4uSWiqDVge1cTgQX4n3TUtfI/dXS5DmbpNk= From: Kieran Bingham To: LibCamera Devel Date: Tue, 13 Aug 2019 10:40:18 +0100 Message-Id: <20190813094020.10277-5-kieran.bingham@ideasonboard.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190813094020.10277-1-kieran.bingham@ideasonboard.com> References: <20190813094020.10277-1-kieran.bingham@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 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: Tue, 13 Aug 2019 09:40:26 -0000 The V4L2M2MDevice requires two video devices to be configured. This makes it unsuitable to reuse the existing V4L2DeviceTest test library in its current form. Implement a full test to run the two M2M pipelines through VIM2M. Signed-off-by: Kieran Bingham Reviewed-by: Laurent Pinchart Reviewed-by: Jacopo Mondi --- test/v4l2_videodevice/meson.build | 1 + test/v4l2_videodevice/v4l2_m2mdevice.cpp | 212 +++++++++++++++++++++++ 2 files changed, 213 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..d132b1db2432 --- /dev/null +++ b/test/v4l2_videodevice/v4l2_m2mdevice.cpp @@ -0,0 +1,212 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * libcamera V4L2 M2M video device tests + */ + +#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) + { + cout << "Received output buffer " << buffer->index() << endl; + + outputFrames_++; + + /* Requeue the buffer for further use. */ + vim2m_->output()->queueBuffer(buffer); + } + + void receiveCaptureBuffer(Buffer *buffer) + { + cout << "Received capture buffer " << buffer->index() << 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 << "No vim2m device found" << endl; + return TestSkip; + } + + return TestPass; + } + + int run() + { + constexpr unsigned int bufferCount = 4; + + EventDispatcher *dispatcher = CameraManager::instance()->eventDispatcher(); + int ret; + + MediaEntity *entity = media_->getEntityByName("vim2m-source"); + vim2m_ = new V4L2M2MDevice(entity->deviceNode()); + if (vim2m_->open()) { + cerr << "Failed to open VIM2M device" << endl; + return TestFail; + } + + V4L2VideoDevice *capture = vim2m_->capture(); + V4L2VideoDevice *output = vim2m_->output(); + + V4L2DeviceFormat format = {}; + if (capture->getFormat(&format)) { + cerr << "Failed to get capture format" << endl; + return TestFail; + } + + format.size.width = 640; + format.size.height = 480; + + if (capture->setFormat(&format)) { + cerr << "Failed to set capture format" << endl; + return TestFail; + } + + if (output->setFormat(&format)) { + cerr << "Failed to set output format" << endl; + return TestFail; + } + + capturePool_.createBuffers(bufferCount); + outputPool_.createBuffers(bufferCount); + + ret = capture->exportBuffers(&capturePool_); + if (ret) { + cerr << "Failed to export Capture Buffers" << endl; + return TestFail; + } + + ret = output->exportBuffers(&outputPool_); + if (ret) { + cerr << "Failed to export Output Buffers" << endl; + return TestFail; + } + + capture->bufferReady.connect(this, &V4L2M2MDeviceTest::receiveCaptureBuffer); + output->bufferReady.connect(this, &V4L2M2MDeviceTest::outputBufferComplete); + + std::vector> captureBuffers; + captureBuffers = capture->queueAllBuffers(); + if (captureBuffers.empty()) { + cerr << "Failed to queue all Capture Buffers" << endl; + return TestFail; + } + + /* 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 = output->queueBuffer(buffer); + if (ret) { + cerr << "Failed to queue output buffer" << i << endl; + return TestFail; + } + } + + ret = capture->streamOn(); + if (ret) { + cerr << "Failed to streamOn capture" << endl; + return TestFail; + } + + ret = output->streamOn(); + if (ret) { + cerr << "Failed to streamOn output" << endl; + return TestFail; + } + + Timer timeout; + timeout.start(5000); + while (timeout.isRunning()) { + dispatcher->processEvents(); + if (captureFrames_ > 30) + break; + } + + cerr << "Output " << outputFrames_ << " frames" << std::endl; + cerr << "Captured " << captureFrames_ << " frames" << std::endl; + + if (captureFrames_ < 30) { + cerr << "Failed to capture 30 frames within timeout." << std::endl; + return TestFail; + } + + ret = capture->streamOff(); + if (ret) { + cerr << "Failed to StreamOff the capture device." << std::endl; + return TestFail; + } + + ret = output->streamOff(); + if (ret) { + cerr << "Failed to StreamOff the output device." << std::endl; + 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 Tue Aug 13 09:40: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: 1804 Return-Path: Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 78E4861624 for ; Tue, 13 Aug 2019 11:40: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 1578A327; Tue, 13 Aug 2019 11:40:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1565689226; bh=KuWEHIP7kI5s1B4Y6wAB4Z/UIf5ZF8he91quKAYIGKA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=tPQwXRKDyMszwiyGPWapyOa5MJFuguE2ieCoSoZ+fthW6WIx/znlkjgOgtbE6guUo 1OMY+NE395anlFi4lfNNKOAnxjBK/FEdvM2ac2M69jmxg4aZG7yD8fJm2RUx2Ebn1s 384PknzoQwy9zb5/EpxjcSEQ4LrC3cwkHGYvsY6g= From: Kieran Bingham To: LibCamera Devel Date: Tue, 13 Aug 2019 10:40:19 +0100 Message-Id: <20190813094020.10277-6-kieran.bingham@ideasonboard.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190813094020.10277-1-kieran.bingham@ideasonboard.com> References: <20190813094020.10277-1-kieran.bingham@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 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: Tue, 13 Aug 2019 09:40:26 -0000 The 'exists()' call is not used within the code base. Remove it. Signed-off-by: Kieran Bingham Reviewed-by: Laurent Pinchart Reviewed-by: Jacopo Mondi --- 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 a0d269fef7f4..096f9649bfc9 100644 --- a/test/v4l2_videodevice/v4l2_videodevice_test.cpp +++ b/test/v4l2_videodevice/v4l2_videodevice_test.cpp @@ -6,7 +6,6 @@ */ #include -#include #include @@ -18,16 +17,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 Tue Aug 13 09:40: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: 1805 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 0A69E6162D for ; Tue, 13 Aug 2019 11:40: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 6AF4130F; Tue, 13 Aug 2019 11:40:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1565689226; bh=Ix8dnqJYhKVuHRIDeXvP0HFhRVUfJ4/thXTwmeu5bHI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=GcW4aQlgrDv3oqCgnPve+FyGaXRA7eFUjnfNofIYk/CMANJRW7pZijG+KPcIlH10B q9gGkfVXlVRgHKgcF1sT9AvCvdxNd46P/DVm2pCSYNpIeZzPW5wz9iQ8E93OdrxJU0 U30n09muaFo+TEseagxBxwI7ZU7N1/yqI9WSa42M= From: Kieran Bingham To: LibCamera Devel Date: Tue, 13 Aug 2019 10:40:20 +0100 Message-Id: <20190813094020.10277-7-kieran.bingham@ideasonboard.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190813094020.10277-1-kieran.bingham@ideasonboard.com> References: <20190813094020.10277-1-kieran.bingham@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 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: Tue, 13 Aug 2019 09:40:27 -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 | 453 +++++++++++++++++++++++++ 2 files changed, 454 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..d11b78df2e03 --- /dev/null +++ b/src/libcamera/pipeline/raspberrypi.cpp @@ -0,0 +1,453 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * raspberrypi.cpp - Pipeline handler for Raspberry Pi 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 sensor_; + 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(); + RPiCameraData *data = cameraData(camera); + + if (roles.empty()) + return config; + + StreamConfiguration cfg{}; + cfg.pixelFormat = V4L2_PIX_FMT_YUYV; + cfg.size = { 1920, 1080 }; + + LOG(RPI, Debug) << "Sensor Resolution is: " << data->sensor_->resolution().toString(); + + cfg.bufferCount = 4; + + 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 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; + + /* + * 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(); + if (ret) + return ret; + + ret = data->isp_->output()->releaseBuffers(); + if (ret) + return ret; + + ret = data->isp_->capture()->releaseBuffers(); + if (ret) + return ret; + + 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) + goto output_streamoff; + + ret = data->unicam_->streamOn(); + if (ret) + goto capture_streamoff; + + return 0; + +capture_streamoff: + data->isp_->capture()->streamOff(); +output_streamoff: + data->isp_->output()->streamOff(); + + 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); + Stream *stream = &data->stream_; + + Buffer *buffer = request->findBuffer(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"); + + /* The video node is also named unicam. */ + unicam.add("unicam"); + + /* We explicitly need the ISP device from the MMAL codec driver. */ + codec.add("bcm2835-codec-isp-source"); + + 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-source"); + if (!isp) { + LOG(RPI, Error) << "Could not identify the ISP"; + return false; + } + + data->isp_ = new V4L2M2MDevice(isp->deviceNode()); + if (data->isp_->open()) { + LOG(RPI, Error) << "Could not open 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; + + if (data->sensor_->init()) + 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) +{ + /* \todo Handle buffer failures when state is set to BufferError. */ + if (buffer->status() == Buffer::BufferCancelled) + return; + + /* Deliver the frame from the sensor to the ISP. */ + isp_->output()->queueBuffer(buffer); +} + +void RPiCameraData::ispOutputReady(Buffer *buffer) +{ + /* \todo Handle buffer failures when state is set to BufferError. */ + if (buffer->status() == Buffer::BufferCancelled) + return; + + /* 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 */