From patchwork Fri Jul 30 01:02:59 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 13145 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 29CD5C3230 for ; Fri, 30 Jul 2021 01:03:22 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 222C8687CD; Fri, 30 Jul 2021 03:03:21 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="fe8F+sNb"; dkim-atps=neutral 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 EA7A5687BD for ; Fri, 30 Jul 2021 03:03:18 +0200 (CEST) Received: from pendragon.lan (62-78-145-57.bb.dnainternet.fi [62.78.145.57]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 92AF1101E for ; Fri, 30 Jul 2021 03:03:18 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1627606998; bh=3kw4e4aKYS0TdKoIEjU2QVbi/0iC9BQA98HJwgjfhtI=; h=From:To:Subject:Date:In-Reply-To:References:From; b=fe8F+sNbNx0VdSggrnwulrkUgHoPR1pzeCgwsMdXqUHMJyyyOIF85jWFEBn8e7p9e KxHDFBAH0ENsbQ4fy65lPCPY3lgSl+CRmhEhdd/4U30EK/RH8v+cwjevdQk4wzwYWL 7qnJofLPKWUuyjwJZDruZcOZHVPTwlPGO3NfgoCE= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Date: Fri, 30 Jul 2021 04:02:59 +0300 Message-Id: <20210730010306.19956-2-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210730010306.19956-1-laurent.pinchart@ideasonboard.com> References: <20210730010306.19956-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 1/8] cam: event_loop: Add support for file descriptor events 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: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Extend the EventLoop class to support watching file descriptors for read and write events. Signed-off-by: Laurent Pinchart Reviewed-by: Niklas Söderlund Reviewed-by: Paul Elder Reviewed-by: Kieran Bingham Reviewed-by: Umang Jain --- src/cam/event_loop.cpp | 44 ++++++++++++++++++++++++++++++++++++++++++ src/cam/event_loop.h | 20 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/cam/event_loop.cpp b/src/cam/event_loop.cpp index 6a4c47f287d0..e25784c083cf 100644 --- a/src/cam/event_loop.cpp +++ b/src/cam/event_loop.cpp @@ -10,6 +10,7 @@ #include #include #include +#include EventLoop *EventLoop::instance_ = nullptr; @@ -26,6 +27,7 @@ EventLoop::~EventLoop() { instance_ = nullptr; + events_.clear(); event_base_free(base_); libevent_global_shutdown(); } @@ -58,6 +60,30 @@ void EventLoop::callLater(const std::function &func) event_base_once(base_, -1, EV_TIMEOUT, dispatchCallback, this, nullptr); } +void EventLoop::addEvent(int fd, EventType type, + const std::function &callback) +{ + std::unique_ptr event = std::make_unique(callback); + short events = (type & Read ? EV_READ : 0) + | (type & Write ? EV_WRITE : 0) + | EV_PERSIST; + + event->event_ = event_new(base_, fd, events, &EventLoop::Event::dispatch, + event.get()); + if (!event->event_) { + std::cerr << "Failed to create event for fd " << fd << std::endl; + return; + } + + int ret = event_add(event->event_, nullptr); + if (ret < 0) { + std::cerr << "Failed to add event for fd " << fd << std::endl; + return; + } + + events_.push_back(std::move(event)); +} + void EventLoop::dispatchCallback([[maybe_unused]] evutil_socket_t fd, [[maybe_unused]] short flags, void *param) { @@ -80,3 +106,21 @@ void EventLoop::dispatchCall() call(); } + +EventLoop::Event::Event(const std::function &callback) + : callback_(callback), event_(nullptr) +{ +} + +EventLoop::Event::~Event() +{ + event_del(event_); + event_free(event_); +} + +void EventLoop::Event::dispatch([[maybe_unused]] int fd, + [[maybe_unused]] short events, void *arg) +{ + Event *event = static_cast(arg); + event->callback_(); +} diff --git a/src/cam/event_loop.h b/src/cam/event_loop.h index ba3ba3a4a675..57bb6fb34aa7 100644 --- a/src/cam/event_loop.h +++ b/src/cam/event_loop.h @@ -8,6 +8,7 @@ #define __CAM_EVENT_LOOP_H__ #include +#include #include #include @@ -18,6 +19,11 @@ struct event_base; class EventLoop { public: + enum EventType { + Read = 1, + Write = 2, + }; + EventLoop(); ~EventLoop(); @@ -28,13 +34,27 @@ public: void callLater(const std::function &func); + void addEvent(int fd, EventType type, + const std::function &handler); + private: + struct Event { + Event(const std::function &callback); + ~Event(); + + static void dispatch(int fd, short events, void *arg); + + std::function callback_; + struct event *event_; + }; + static EventLoop *instance_; struct event_base *base_; int exitCode_; std::list> calls_; + std::list> events_; std::mutex lock_; static void dispatchCallback(evutil_socket_t fd, short flags, From patchwork Fri Jul 30 01:03:00 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 13147 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id CA036C3230 for ; Fri, 30 Jul 2021 01:03:24 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 051BA687C7; Fri, 30 Jul 2021 03:03:23 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Ool5aPj/"; dkim-atps=neutral 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 44BFC687BE for ; Fri, 30 Jul 2021 03:03:19 +0200 (CEST) Received: from pendragon.lan (62-78-145-57.bb.dnainternet.fi [62.78.145.57]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E1C8B9FB for ; Fri, 30 Jul 2021 03:03:18 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1627606999; bh=2Amx5m8JczLn2JMxvAEx9dCy/nMaGljXfZrAfhmhgYQ=; h=From:To:Subject:Date:In-Reply-To:References:From; b=Ool5aPj/hfBc2o7stB2FtJnm+sP5jcCZsl11PoyzYZaplewlc+9bCXwRPWyAPLlDH Bu4zNtXtnV3ZXFSiGAH6hgnlrGpyU1rjxqvxVn+w7oQDQgPYX3E8tPVxqd+zgHHsNy 82ylOndeDVf1zjmd0cVjWheJTOrqnnJ0GyejCBh8= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Date: Fri, 30 Jul 2021 04:03:00 +0300 Message-Id: <20210730010306.19956-3-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210730010306.19956-1-laurent.pinchart@ideasonboard.com> References: <20210730010306.19956-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 2/8] cam: Add FrameSink base class 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: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" The FrameSink class serves as a base to implement components that consume frames. This allows handling frame sinks in a generic way, independent of their nature. The BufferWrite class will be ported to FrameSink in a second step. Signed-off-by: Laurent Pinchart Reviewed-by: Niklas Söderlund Reviewed-by: Paul Elder Reviewed-by: Kieran Bingham Reviewed-by: Umang Jain --- src/cam/frame_sink.cpp | 31 +++++++++++++++++++++++++++++++ src/cam/frame_sink.h | 34 ++++++++++++++++++++++++++++++++++ src/cam/meson.build | 1 + 3 files changed, 66 insertions(+) create mode 100644 src/cam/frame_sink.cpp create mode 100644 src/cam/frame_sink.h diff --git a/src/cam/frame_sink.cpp b/src/cam/frame_sink.cpp new file mode 100644 index 000000000000..6e15c1007f12 --- /dev/null +++ b/src/cam/frame_sink.cpp @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Ideas on Board Oy + * + * frame_sink.cpp - Base Frame Sink Class + */ + +#include "frame_sink.h" + +FrameSink::~FrameSink() +{ +} + +int FrameSink::configure([[maybe_unused]] const libcamera::CameraConfiguration &config) +{ + return 0; +} + +void FrameSink::mapBuffer([[maybe_unused]] libcamera::FrameBuffer *buffer) +{ +} + +int FrameSink::start() +{ + return 0; +} + +int FrameSink::stop() +{ + return 0; +} diff --git a/src/cam/frame_sink.h b/src/cam/frame_sink.h new file mode 100644 index 000000000000..116e5267bebe --- /dev/null +++ b/src/cam/frame_sink.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Ideas on Board Oy + * + * frame_sink.h - Base Frame Sink Class + */ +#ifndef __CAM_FRAME_SINK_H__ +#define __CAM_FRAME_SINK_H__ + +#include + +namespace libcamera { +class CameraConfiguration; +class FrameBuffer; +class Request; +} /* namespace libcamera */ + +class FrameSink +{ +public: + virtual ~FrameSink(); + + virtual int configure(const libcamera::CameraConfiguration &config); + + virtual void mapBuffer(libcamera::FrameBuffer *buffer); + + virtual int start(); + virtual int stop(); + + virtual bool consumeRequest(libcamera::Request *request) = 0; + libcamera::Signal requestReleased; +}; + +#endif /* __CAM_FRAME_SINK_H__ */ diff --git a/src/cam/meson.build b/src/cam/meson.build index 1e90ee521f74..649cc990d867 100644 --- a/src/cam/meson.build +++ b/src/cam/meson.build @@ -13,6 +13,7 @@ cam_sources = files([ 'buffer_writer.cpp', 'camera_session.cpp', 'event_loop.cpp', + 'frame_sink.cpp', 'main.cpp', 'options.cpp', 'stream_options.cpp', From patchwork Fri Jul 30 01:03:01 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 13148 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 2BE2BC3231 for ; Fri, 30 Jul 2021 01:03:25 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E19D0687C8; Fri, 30 Jul 2021 03:03:23 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Gj9MGOSz"; dkim-atps=neutral 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 9345F687BF for ; Fri, 30 Jul 2021 03:03:19 +0200 (CEST) Received: from pendragon.lan (62-78-145-57.bb.dnainternet.fi [62.78.145.57]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3BCA8101E for ; Fri, 30 Jul 2021 03:03:19 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1627606999; bh=yB7zZUjUyj0n8p1NblBiZjn+PRFy4w8j2SNWYy5fsoY=; h=From:To:Subject:Date:In-Reply-To:References:From; b=Gj9MGOSz/Hu2p6DaJh5gZpdV6CCJBgTZXGlI7PNzNGPypAfyP2SHw4QqMeNgg448c EbeK2lxSjJy0TSoQ9Zhge0PBbbgtfg0ilziUSy36h5cM1JK8j5+4a/mcosP0tUH9IO /EdLpWiAJJjKbGnwSIh10rTtc9t6R+sCrpcg8R1c= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Date: Fri, 30 Jul 2021 04:03:01 +0300 Message-Id: <20210730010306.19956-4-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210730010306.19956-1-laurent.pinchart@ideasonboard.com> References: <20210730010306.19956-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 3/8] cam: Turn BufferWriter into a FrameSink 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: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Make the BufferWriter class inherit from FrameSink, and use the FrameSink API to manage it. This makes the code more generic, and will allow usage of other sinks. Signed-off-by: Laurent Pinchart Reviewed-by: Niklas Söderlund Reviewed-by: Paul Elder Reviewed-by: Kieran Bingham Reviewed-by: Umang Jain --- Changes since v1: - Print message if the file can't be opened --- src/cam/buffer_writer.cpp | 33 +++++++++++++++++--- src/cam/buffer_writer.h | 14 ++++++--- src/cam/camera_session.cpp | 64 ++++++++++++++++++++++++++++++++------ src/cam/camera_session.h | 6 ++-- 4 files changed, 96 insertions(+), 21 deletions(-) diff --git a/src/cam/buffer_writer.cpp b/src/cam/buffer_writer.cpp index a7648a92fc92..2cf8644e843d 100644 --- a/src/cam/buffer_writer.cpp +++ b/src/cam/buffer_writer.cpp @@ -13,6 +13,8 @@ #include #include +#include + #include "buffer_writer.h" using namespace libcamera; @@ -32,6 +34,21 @@ BufferWriter::~BufferWriter() mappedBuffers_.clear(); } +int BufferWriter::configure(const libcamera::CameraConfiguration &config) +{ + int ret = FrameSink::configure(config); + if (ret < 0) + return ret; + + streamNames_.clear(); + for (unsigned int index = 0; index < config.size(); ++index) { + const StreamConfiguration &cfg = config.at(index); + streamNames_[cfg.stream()] = "stream" + std::to_string(index); + } + + return 0; +} + void BufferWriter::mapBuffer(FrameBuffer *buffer) { for (const FrameBuffer::Plane &plane : buffer->planes()) { @@ -43,8 +60,11 @@ void BufferWriter::mapBuffer(FrameBuffer *buffer) } } -int BufferWriter::write(FrameBuffer *buffer, const std::string &streamName) +bool BufferWriter::consumeRequest(Request *request) { + const Stream *stream = request->buffers().begin()->first; + FrameBuffer *buffer = request->buffers().begin()->second; + std::string filename; size_t pos; int fd, ret = 0; @@ -58,7 +78,7 @@ int BufferWriter::write(FrameBuffer *buffer, const std::string &streamName) pos = filename.find_first_of('#'); if (pos != std::string::npos) { std::stringstream ss; - ss << streamName << "-" << std::setw(6) + ss << streamNames_[stream] << "-" << std::setw(6) << std::setfill('0') << buffer->metadata().sequence; filename.replace(pos, 1, ss.str()); } @@ -66,8 +86,11 @@ int BufferWriter::write(FrameBuffer *buffer, const std::string &streamName) fd = open(filename.c_str(), O_CREAT | O_WRONLY | (pos == std::string::npos ? O_APPEND : O_TRUNC), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); - if (fd == -1) - return -errno; + if (fd == -1) { + std::cerr << "failed to open file " << filename << ": " + << strerror(-ret) << std::endl; + return true; + } for (unsigned int i = 0; i < buffer->planes().size(); ++i) { const FrameBuffer::Plane &plane = buffer->planes()[i]; @@ -97,5 +120,5 @@ int BufferWriter::write(FrameBuffer *buffer, const std::string &streamName) close(fd); - return ret; + return true; } diff --git a/src/cam/buffer_writer.h b/src/cam/buffer_writer.h index 7626de42d369..955bc2713f4c 100644 --- a/src/cam/buffer_writer.h +++ b/src/cam/buffer_writer.h @@ -10,20 +10,24 @@ #include #include -#include +#include -class BufferWriter +#include "frame_sink.h" + +class BufferWriter : public FrameSink { public: BufferWriter(const std::string &pattern = ""); ~BufferWriter(); - void mapBuffer(libcamera::FrameBuffer *buffer); + int configure(const libcamera::CameraConfiguration &config) override; - int write(libcamera::FrameBuffer *buffer, - const std::string &streamName); + void mapBuffer(libcamera::FrameBuffer *buffer) override; + + bool consumeRequest(libcamera::Request *request) override; private: + std::map streamNames_; std::string pattern_; std::map> mappedBuffers_; }; diff --git a/src/cam/camera_session.cpp b/src/cam/camera_session.cpp index 0d49fc1ade83..465c8e24190e 100644 --- a/src/cam/camera_session.cpp +++ b/src/cam/camera_session.cpp @@ -13,6 +13,7 @@ #include #include +#include "buffer_writer.h" #include "camera_session.h" #include "event_loop.h" #include "main.h" @@ -162,9 +163,20 @@ int CameraSession::start() if (options_.isSet(OptFile)) { if (!options_[OptFile].toString().empty()) - writer_ = std::make_unique(options_[OptFile]); + sink_ = std::make_unique(options_[OptFile]); else - writer_ = std::make_unique(); + sink_ = std::make_unique(); + } + + if (sink_) { + ret = sink_->configure(*config_); + if (ret < 0) { + std::cout << "Failed to configure frame sink" + << std::endl; + return ret; + } + + sink_->requestReleased.connect(this, &CameraSession::sinkRelease); } allocator_ = std::make_unique(camera_); @@ -178,7 +190,13 @@ void CameraSession::stop() if (ret) std::cout << "Failed to stop capture" << std::endl; - writer_.reset(); + if (sink_) { + ret = sink_->stop(); + if (ret) + std::cout << "Failed to stop frame sink" << std::endl; + } + + sink_.reset(); requests_.clear(); @@ -227,16 +245,26 @@ int CameraSession::startCapture() return ret; } - if (writer_) - writer_->mapBuffer(buffer.get()); + if (sink_) + sink_->mapBuffer(buffer.get()); } requests_.push_back(std::move(request)); } + if (sink_) { + ret = sink_->start(); + if (ret) { + std::cout << "Failed to start frame sink" << std::endl; + return ret; + } + } + ret = camera_->start(); if (ret) { std::cout << "Failed to start capture" << std::endl; + if (sink_) + sink_->stop(); return ret; } @@ -245,6 +273,8 @@ int CameraSession::startCapture() if (ret < 0) { std::cerr << "Can't queue request" << std::endl; camera_->stop(); + if (sink_) + sink_->stop(); return ret; } } @@ -296,6 +326,8 @@ void CameraSession::processRequest(Request *request) fps = last_ != 0 && fps ? 1000000000.0 / fps : 0.0; last_ = ts; + bool requeue = true; + std::stringstream info; info << ts / 1000000000 << "." << std::setw(6) << std::setfill('0') << ts / 1000 % 1000000 @@ -304,11 +336,10 @@ void CameraSession::processRequest(Request *request) for (auto it = buffers.begin(); it != buffers.end(); ++it) { const Stream *stream = it->first; FrameBuffer *buffer = it->second; - const std::string &name = streamName_[stream]; const FrameMetadata &metadata = buffer->metadata(); - info << " " << name + info << " " << streamName_[stream] << " seq: " << std::setw(6) << std::setfill('0') << metadata.sequence << " bytesused: "; @@ -318,9 +349,11 @@ void CameraSession::processRequest(Request *request) if (++nplane < metadata.planes.size()) info << "/"; } + } - if (writer_) - writer_->write(buffer, name); + if (sink_) { + if (!sink_->consumeRequest(request)) + requeue = false; } std::cout << info.str() << std::endl; @@ -340,6 +373,19 @@ void CameraSession::processRequest(Request *request) return; } + /* + * If the frame sink holds on the request, we'll requeue it later in the + * complete handler. + */ + if (!requeue) + return; + + request->reuse(Request::ReuseBuffers); + camera_->queueRequest(request); +} + +void CameraSession::sinkRelease(Request *request) +{ request->reuse(Request::ReuseBuffers); queueRequest(request); } diff --git a/src/cam/camera_session.h b/src/cam/camera_session.h index b0f50e7f998e..2ccc71977a99 100644 --- a/src/cam/camera_session.h +++ b/src/cam/camera_session.h @@ -21,9 +21,10 @@ #include #include -#include "buffer_writer.h" #include "options.h" +class FrameSink; + class CameraSession { public: @@ -53,13 +54,14 @@ private: int queueRequest(libcamera::Request *request); void requestComplete(libcamera::Request *request); void processRequest(libcamera::Request *request); + void sinkRelease(libcamera::Request *request); const OptionsParser::Options &options_; std::shared_ptr camera_; std::unique_ptr config_; std::map streamName_; - std::unique_ptr writer_; + std::unique_ptr sink_; unsigned int cameraIndex_; uint64_t last_; From patchwork Fri Jul 30 01:03:02 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 13150 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 0F09FC3232 for ; Fri, 30 Jul 2021 01:03:27 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A70F5687C6; Fri, 30 Jul 2021 03:03:26 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="l+FJKWBX"; dkim-atps=neutral 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 EBBC7687BE for ; Fri, 30 Jul 2021 03:03:19 +0200 (CEST) Received: from pendragon.lan (62-78-145-57.bb.dnainternet.fi [62.78.145.57]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8BE419FB for ; Fri, 30 Jul 2021 03:03:19 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1627606999; bh=4TzzbLhuRmMH/t9rrPRbYyzh6hWs+j/wiGBfngZFHf4=; h=From:To:Subject:Date:In-Reply-To:References:From; b=l+FJKWBXjjTJmDiPKGC1H8RTxRAeCsEY0juyAvO8ZvnY34JqdtZXQnI9ooeXjsDXk xKgbR60WZ3siN/2e2s7zl0gOQCvprCbQGGYuk8FSdlF/6KHOOAaIb34iNulaLU3KuK iUXNaK8oEsZw2skpX2c02cFEqYCBtrj+vrw1LLc0= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Date: Fri, 30 Jul 2021 04:03:02 +0300 Message-Id: <20210730010306.19956-5-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210730010306.19956-1-laurent.pinchart@ideasonboard.com> References: <20210730010306.19956-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 4/8] cam: Rename BufferWriter to FileSink 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: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Rename the BufferWriter class to FileSink to establish a common naming scheme for all sinks. Signed-off-by: Laurent Pinchart Reviewed-by: Niklas Söderlund Reviewed-by: Paul Elder Reviewed-by: Kieran Bingham Reviewed-by: Umang Jain --- src/cam/camera_session.cpp | 6 +++--- src/cam/{buffer_writer.cpp => file_sink.cpp} | 14 +++++++------- src/cam/{buffer_writer.h => file_sink.h} | 14 +++++++------- src/cam/meson.build | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) rename src/cam/{buffer_writer.cpp => file_sink.cpp} (89%) rename src/cam/{buffer_writer.h => file_sink.h} (69%) diff --git a/src/cam/camera_session.cpp b/src/cam/camera_session.cpp index 465c8e24190e..4f1f9ec8eb10 100644 --- a/src/cam/camera_session.cpp +++ b/src/cam/camera_session.cpp @@ -13,9 +13,9 @@ #include #include -#include "buffer_writer.h" #include "camera_session.h" #include "event_loop.h" +#include "file_sink.h" #include "main.h" #include "stream_options.h" @@ -163,9 +163,9 @@ int CameraSession::start() if (options_.isSet(OptFile)) { if (!options_[OptFile].toString().empty()) - sink_ = std::make_unique(options_[OptFile]); + sink_ = std::make_unique(options_[OptFile]); else - sink_ = std::make_unique(); + sink_ = std::make_unique(); } if (sink_) { diff --git a/src/cam/buffer_writer.cpp b/src/cam/file_sink.cpp similarity index 89% rename from src/cam/buffer_writer.cpp rename to src/cam/file_sink.cpp index 2cf8644e843d..0ff8107c811b 100644 --- a/src/cam/buffer_writer.cpp +++ b/src/cam/file_sink.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * buffer_writer.cpp - Buffer writer + * file_sink.cpp - File Sink */ #include @@ -15,16 +15,16 @@ #include -#include "buffer_writer.h" +#include "file_sink.h" using namespace libcamera; -BufferWriter::BufferWriter(const std::string &pattern) +FileSink::FileSink(const std::string &pattern) : pattern_(pattern) { } -BufferWriter::~BufferWriter() +FileSink::~FileSink() { for (auto &iter : mappedBuffers_) { void *memory = iter.second.first; @@ -34,7 +34,7 @@ BufferWriter::~BufferWriter() mappedBuffers_.clear(); } -int BufferWriter::configure(const libcamera::CameraConfiguration &config) +int FileSink::configure(const libcamera::CameraConfiguration &config) { int ret = FrameSink::configure(config); if (ret < 0) @@ -49,7 +49,7 @@ int BufferWriter::configure(const libcamera::CameraConfiguration &config) return 0; } -void BufferWriter::mapBuffer(FrameBuffer *buffer) +void FileSink::mapBuffer(FrameBuffer *buffer) { for (const FrameBuffer::Plane &plane : buffer->planes()) { void *memory = mmap(NULL, plane.length, PROT_READ, MAP_SHARED, @@ -60,7 +60,7 @@ void BufferWriter::mapBuffer(FrameBuffer *buffer) } } -bool BufferWriter::consumeRequest(Request *request) +bool FileSink::consumeRequest(Request *request) { const Stream *stream = request->buffers().begin()->first; FrameBuffer *buffer = request->buffers().begin()->second; diff --git a/src/cam/buffer_writer.h b/src/cam/file_sink.h similarity index 69% rename from src/cam/buffer_writer.h rename to src/cam/file_sink.h index 955bc2713f4c..545b1771189a 100644 --- a/src/cam/buffer_writer.h +++ b/src/cam/file_sink.h @@ -2,10 +2,10 @@ /* * Copyright (C) 2019, Google Inc. * - * buffer_writer.h - Buffer writer + * file_sink.h - File Sink */ -#ifndef __CAM_BUFFER_WRITER_H__ -#define __CAM_BUFFER_WRITER_H__ +#ifndef __CAM_FILE_SINK_H__ +#define __CAM_FILE_SINK_H__ #include #include @@ -14,11 +14,11 @@ #include "frame_sink.h" -class BufferWriter : public FrameSink +class FileSink : public FrameSink { public: - BufferWriter(const std::string &pattern = ""); - ~BufferWriter(); + FileSink(const std::string &pattern = ""); + ~FileSink(); int configure(const libcamera::CameraConfiguration &config) override; @@ -32,4 +32,4 @@ private: std::map> mappedBuffers_; }; -#endif /* __CAM_BUFFER_WRITER_H__ */ +#endif /* __CAM_FILE_SINK_H__ */ diff --git a/src/cam/meson.build b/src/cam/meson.build index 649cc990d867..e692ea351987 100644 --- a/src/cam/meson.build +++ b/src/cam/meson.build @@ -10,9 +10,9 @@ endif cam_enabled = true cam_sources = files([ - 'buffer_writer.cpp', 'camera_session.cpp', 'event_loop.cpp', + 'file_sink.cpp', 'frame_sink.cpp', 'main.cpp', 'options.cpp', From patchwork Fri Jul 30 01:03:03 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 13149 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 897F7C3233 for ; Fri, 30 Jul 2021 01:03:27 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 210C068810; Fri, 30 Jul 2021 03:03:27 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="vwEb+1/4"; dkim-atps=neutral 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 4C295687BE for ; Fri, 30 Jul 2021 03:03:20 +0200 (CEST) Received: from pendragon.lan (62-78-145-57.bb.dnainternet.fi [62.78.145.57]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DC75C101E for ; Fri, 30 Jul 2021 03:03:19 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1627607000; bh=ILEMf8QJSzjnk8IdnvOF16d72j5T/nfRdWIg3cwOAl8=; h=From:To:Subject:Date:In-Reply-To:References:From; b=vwEb+1/4fSf+dZNoCst/xmuFeJDeff9QHNSCGDoRiworjvk701VFxU/ogAdbu40MB hpJSR7CGE7Y1uX7JTYBOgXNIbDLNx7aL6CdRhC02no1rXfbK5SmF7m/9pOZlDu+G1w WpgaisBNl20vINtadpbbqoYNt/HSAp7eD24Bejg4= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Date: Fri, 30 Jul 2021 04:03:03 +0300 Message-Id: <20210730010306.19956-6-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210730010306.19956-1-laurent.pinchart@ideasonboard.com> References: <20210730010306.19956-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 5/8] cam: Add DRM helper classes 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: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" To prepare for viewfinder operation through the DRM/KMS API, add a set of helper classes that encapsulate the libdrm functions. Signed-off-by: Laurent Pinchart Reviewed-by: Paul Elder --- Changes since v1: - Use drm.h and drm_mode.h from libdrm - Drop writeback connector --- src/cam/drm.cpp | 663 ++++++++++++++++++++++++++++++++++++++++++++ src/cam/drm.h | 331 ++++++++++++++++++++++ src/cam/meson.build | 13 + 3 files changed, 1007 insertions(+) create mode 100644 src/cam/drm.cpp create mode 100644 src/cam/drm.h diff --git a/src/cam/drm.cpp b/src/cam/drm.cpp new file mode 100644 index 000000000000..56a6cbd2442e --- /dev/null +++ b/src/cam/drm.cpp @@ -0,0 +1,663 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Ideas on Board Oy + * + * drm.cpp - DRM/KMS Helpers + */ + +#include "drm.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "event_loop.h" + +namespace DRM { + +Object::Object(Device *dev, uint32_t id, Type type) + : id_(id), dev_(dev), type_(type) +{ + /* Retrieve properties from the objects that support them. */ + if (type != TypeConnector && type != TypeCrtc && + type != TypeEncoder && type != TypePlane) + return; + + /* + * We can't distinguish between failures due to the object having no + * property and failures due to other conditions. Assume we use the API + * correctly and consider the object has no property. + */ + drmModeObjectProperties *properties = drmModeObjectGetProperties(dev->fd(), id, type); + if (!properties) + return; + + properties_.reserve(properties->count_props); + for (uint32_t i = 0; i < properties->count_props; ++i) + properties_.emplace_back(properties->props[i], + properties->prop_values[i]); + + drmModeFreeObjectProperties(properties); +} + +Object::~Object() +{ +} + +const Property *Object::property(const std::string &name) const +{ + for (const PropertyValue &pv : properties_) { + const Property *property = static_cast(dev_->object(pv.id())); + if (property && property->name() == name) + return property; + } + + return nullptr; +} + +const PropertyValue *Object::propertyValue(const std::string &name) const +{ + for (const PropertyValue &pv : properties_) { + const Property *property = static_cast(dev_->object(pv.id())); + if (property && property->name() == name) + return &pv; + } + + return nullptr; +} + +Property::Property(Device *dev, drmModePropertyRes *property) + : Object(dev, property->prop_id, TypeProperty), + name_(property->name), flags_(property->flags), + values_(property->values, property->values + property->count_values), + blobs_(property->blob_ids, property->blob_ids + property->count_blobs) +{ + if (drm_property_type_is(property, DRM_MODE_PROP_RANGE)) + type_ = TypeRange; + else if (drm_property_type_is(property, DRM_MODE_PROP_ENUM)) + type_ = TypeEnum; + else if (drm_property_type_is(property, DRM_MODE_PROP_BLOB)) + type_ = TypeBlob; + else if (drm_property_type_is(property, DRM_MODE_PROP_BITMASK)) + type_ = TypeBitmask; + else if (drm_property_type_is(property, DRM_MODE_PROP_OBJECT)) + type_ = TypeObject; + else if (drm_property_type_is(property, DRM_MODE_PROP_SIGNED_RANGE)) + type_ = TypeSignedRange; + else + type_ = TypeUnknown; + + for (int i = 0; i < property->count_enums; ++i) + enums_[property->enums[i].value] = property->enums[i].name; +} + +Blob::Blob(Device *dev, const libcamera::Span &data) + : Object(dev, 0, Object::TypeBlob) +{ + drmModeCreatePropertyBlob(dev->fd(), data.data(), data.size(), &id_); +} + +Blob::~Blob() +{ + if (isValid()) + drmModeDestroyPropertyBlob(device()->fd(), id()); +} + +Mode::Mode(const drmModeModeInfo &mode) + : drmModeModeInfo(mode) +{ +} + +std::unique_ptr Mode::toBlob(Device *dev) const +{ + libcamera::Span data{ reinterpret_cast(this), + sizeof(*this) }; + return std::make_unique(dev, data); +} + +Crtc::Crtc(Device *dev, const drmModeCrtc *crtc, unsigned int index) + : Object(dev, crtc->crtc_id, Object::TypeCrtc), index_(index) +{ +} + +Encoder::Encoder(Device *dev, const drmModeEncoder *encoder) + : Object(dev, encoder->encoder_id, Object::TypeEncoder), + type_(encoder->encoder_type) +{ + const std::list &crtcs = dev->crtcs(); + possibleCrtcs_.reserve(crtcs.size()); + + for (const Crtc &crtc : crtcs) { + if (encoder->possible_crtcs & (1 << crtc.index())) + possibleCrtcs_.push_back(&crtc); + } + + possibleCrtcs_.shrink_to_fit(); +} + +namespace { + +const std::map connectorTypeNames{ + { DRM_MODE_CONNECTOR_Unknown, "Unknown" }, + { DRM_MODE_CONNECTOR_VGA, "VGA" }, + { DRM_MODE_CONNECTOR_DVII, "DVI-I" }, + { DRM_MODE_CONNECTOR_DVID, "DVI-D" }, + { DRM_MODE_CONNECTOR_DVIA, "DVI-A" }, + { DRM_MODE_CONNECTOR_Composite, "Composite" }, + { DRM_MODE_CONNECTOR_SVIDEO, "S-Video" }, + { DRM_MODE_CONNECTOR_LVDS, "LVDS" }, + { DRM_MODE_CONNECTOR_Component, "Component" }, + { DRM_MODE_CONNECTOR_9PinDIN, "9-Pin-DIN" }, + { DRM_MODE_CONNECTOR_DisplayPort, "DP" }, + { DRM_MODE_CONNECTOR_HDMIA, "HDMI-A" }, + { DRM_MODE_CONNECTOR_HDMIB, "HDMI-B" }, + { DRM_MODE_CONNECTOR_TV, "TV" }, + { DRM_MODE_CONNECTOR_eDP, "eDP" }, + { DRM_MODE_CONNECTOR_VIRTUAL, "Virtual" }, + { DRM_MODE_CONNECTOR_DSI, "DSI" }, + { DRM_MODE_CONNECTOR_DPI, "DPI" }, +}; + +} /* namespace */ + +Connector::Connector(Device *dev, const drmModeConnector *connector) + : Object(dev, connector->connector_id, Object::TypeConnector), + type_(connector->connector_type) +{ + auto typeName = connectorTypeNames.find(connector->connector_type); + if (typeName == connectorTypeNames.end()) { + std::cerr + << "Invalid connector type " + << connector->connector_type << std::endl; + typeName = connectorTypeNames.find(DRM_MODE_CONNECTOR_Unknown); + } + + name_ = std::string(typeName->second) + "-" + + std::to_string(connector->connector_type_id); + + switch (connector->connection) { + case DRM_MODE_CONNECTED: + status_ = Status::Connected; + break; + + case DRM_MODE_DISCONNECTED: + status_ = Status::Disconnected; + break; + + case DRM_MODE_UNKNOWNCONNECTION: + default: + status_ = Status::Unknown; + break; + } + + const std::list &encoders = dev->encoders(); + + encoders_.reserve(connector->count_encoders); + + for (int i = 0; i < connector->count_encoders; ++i) { + uint32_t encoderId = connector->encoders[i]; + auto encoder = std::find_if(encoders.begin(), encoders.end(), + [=](const Encoder &e) { + return e.id() == encoderId; + }); + if (encoder == encoders.end()) { + std::cerr + << "Encoder " << encoderId << " not found" + << std::endl; + continue; + } + + encoders_.push_back(&*encoder); + } + + encoders_.shrink_to_fit(); + + modes_ = { connector->modes, connector->modes + connector->count_modes }; +} + +Plane::Plane(Device *dev, const drmModePlane *plane) + : Object(dev, plane->plane_id, Object::TypePlane), + possibleCrtcsMask_(plane->possible_crtcs) +{ + formats_ = { plane->formats, plane->formats + plane->count_formats }; + + const std::list &crtcs = dev->crtcs(); + possibleCrtcs_.reserve(crtcs.size()); + + for (const Crtc &crtc : crtcs) { + if (plane->possible_crtcs & (1 << crtc.index())) + possibleCrtcs_.push_back(&crtc); + } + + possibleCrtcs_.shrink_to_fit(); +} + +bool Plane::supportsFormat(const libcamera::PixelFormat &format) const +{ + return std::find(formats_.begin(), formats_.end(), format.fourcc()) + != formats_.end(); +} + +int Plane::setup() +{ + const PropertyValue *pv = propertyValue("type"); + if (!pv) + return -EINVAL; + + switch (pv->value()) { + case DRM_PLANE_TYPE_OVERLAY: + type_ = TypeOverlay; + break; + + case DRM_PLANE_TYPE_PRIMARY: + type_ = TypePrimary; + break; + + case DRM_PLANE_TYPE_CURSOR: + type_ = TypeCursor; + break; + + default: + return -EINVAL; + } + + return 0; +} + +FrameBuffer::FrameBuffer(Device *dev) + : Object(dev, 0, Object::TypeFb) +{ +} + +FrameBuffer::~FrameBuffer() +{ + for (FrameBuffer::Plane &plane : planes_) { + struct drm_gem_close gem_close = { + .handle = plane.handle, + .pad = 0, + }; + int ret; + + do { + ret = ioctl(device()->fd(), DRM_IOCTL_GEM_CLOSE, &gem_close); + } while (ret == -1 && (errno == EINTR || errno == EAGAIN)); + + if (ret == -1) { + ret = -errno; + std::cerr + << "Failed to close GEM object: " + << strerror(-ret) << std::endl; + } + } + + drmModeRmFB(device()->fd(), id()); +} + +AtomicRequest::AtomicRequest(Device *dev) + : dev_(dev), valid_(true) +{ + request_ = drmModeAtomicAlloc(); + if (!request_) + valid_ = false; +} + +AtomicRequest::~AtomicRequest() +{ + if (request_) + drmModeAtomicFree(request_); +} + +int AtomicRequest::addProperty(const Object *object, const std::string &property, + uint64_t value) +{ + if (!valid_) + return -EINVAL; + + const Property *prop = object->property(property); + if (!prop) { + valid_ = false; + return -EINVAL; + } + + return addProperty(object->id(), prop->id(), value); +} + +int AtomicRequest::addProperty(const Object *object, const std::string &property, + std::unique_ptr blob) +{ + if (!valid_) + return -EINVAL; + + const Property *prop = object->property(property); + if (!prop) { + valid_ = false; + return -EINVAL; + } + + int ret = addProperty(object->id(), prop->id(), blob->id()); + if (ret < 0) + return ret; + + blobs_.emplace_back(std::move(blob)); + + return 0; +} + +int AtomicRequest::addProperty(uint32_t object, uint32_t property, uint64_t value) +{ + int ret = drmModeAtomicAddProperty(request_, object, property, value); + if (ret < 0) { + valid_ = false; + return ret; + } + + return 0; +} + +int AtomicRequest::commit(unsigned int flags) +{ + if (!valid_) + return -EINVAL; + + uint32_t drmFlags = 0; + if (flags & FlagAllowModeset) + drmFlags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + if (flags & FlagAsync) + drmFlags |= DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK; + + return drmModeAtomicCommit(dev_->fd(), request_, drmFlags, this); +} + +Device::Device() + : fd_(-1) +{ +} + +Device::~Device() +{ + if (fd_ != -1) + drmClose(fd_); +} + +int Device::init() +{ + constexpr size_t NODE_NAME_MAX = sizeof("/dev/dri/card255"); + char name[NODE_NAME_MAX]; + int ret; + + /* + * Open the first DRM/KMS device. The libdrm drmOpen*() functions + * require either a module name or a bus ID, which we don't have, so + * bypass them. The automatic module loading and device node creation + * from drmOpen() is of no practical use as any modern system will + * handle that through udev or an equivalent component. + */ + snprintf(name, sizeof(name), "/dev/dri/card%u", 0); + fd_ = open(name, O_RDWR | O_CLOEXEC); + if (fd_ < 0) { + ret = -errno; + std::cerr + << "Failed to open DRM/KMS device " << name << ": " + << strerror(-ret) << std::endl; + return ret; + } + + /* + * Enable the atomic APIs. This also enables automatically the + * universal planes API. + */ + ret = drmSetClientCap(fd_, DRM_CLIENT_CAP_ATOMIC, 1); + if (ret < 0) { + ret = -errno; + std::cerr + << "Failed to enable atomic capability: " + << strerror(-ret) << std::endl; + return ret; + } + + /* List all the resources. */ + ret = getResources(); + if (ret < 0) + return ret; + + EventLoop::instance()->addEvent(fd_, EventLoop::Read, + std::bind(&Device::drmEvent, this)); + + return 0; +} + +int Device::getResources() +{ + int ret; + + std::unique_ptr resources{ + drmModeGetResources(fd_), + &drmModeFreeResources + }; + if (!resources) { + ret = -errno; + std::cerr + << "Failed to get DRM/KMS resources: " + << strerror(-ret) << std::endl; + return ret; + } + + for (int i = 0; i < resources->count_crtcs; ++i) { + drmModeCrtc *crtc = drmModeGetCrtc(fd_, resources->crtcs[i]); + if (!crtc) { + ret = -errno; + std::cerr + << "Failed to get CRTC: " << strerror(-ret) + << std::endl; + return ret; + } + + crtcs_.emplace_back(this, crtc, i); + drmModeFreeCrtc(crtc); + + Crtc &obj = crtcs_.back(); + objects_[obj.id()] = &obj; + } + + for (int i = 0; i < resources->count_encoders; ++i) { + drmModeEncoder *encoder = + drmModeGetEncoder(fd_, resources->encoders[i]); + if (!encoder) { + ret = -errno; + std::cerr + << "Failed to get encoder: " << strerror(-ret) + << std::endl; + return ret; + } + + encoders_.emplace_back(this, encoder); + drmModeFreeEncoder(encoder); + + Encoder &obj = encoders_.back(); + objects_[obj.id()] = &obj; + } + + for (int i = 0; i < resources->count_connectors; ++i) { + drmModeConnector *connector = + drmModeGetConnector(fd_, resources->connectors[i]); + if (!connector) { + ret = -errno; + std::cerr + << "Failed to get connector: " << strerror(-ret) + << std::endl; + return ret; + } + + connectors_.emplace_back(this, connector); + drmModeFreeConnector(connector); + + Connector &obj = connectors_.back(); + objects_[obj.id()] = &obj; + } + + std::unique_ptr planes{ + drmModeGetPlaneResources(fd_), + &drmModeFreePlaneResources + }; + if (!planes) { + ret = -errno; + std::cerr + << "Failed to get DRM/KMS planes: " + << strerror(-ret) << std::endl; + return ret; + } + + for (uint32_t i = 0; i < planes->count_planes; ++i) { + drmModePlane *plane = + drmModeGetPlane(fd_, planes->planes[i]); + if (!plane) { + ret = -errno; + std::cerr + << "Failed to get plane: " << strerror(-ret) + << std::endl; + return ret; + } + + planes_.emplace_back(this, plane); + drmModeFreePlane(plane); + + Plane &obj = planes_.back(); + objects_[obj.id()] = &obj; + } + + /* Set the possible planes for each CRTC. */ + for (Crtc &crtc : crtcs_) { + for (const Plane &plane : planes_) { + if (plane.possibleCrtcsMask_ & (1 << crtc.index())) + crtc.planes_.push_back(&plane); + } + } + + /* Collect all property IDs and create Property instances. */ + std::set properties; + for (const auto &object : objects_) { + for (const PropertyValue &value : object.second->properties()) + properties.insert(value.id()); + } + + for (uint32_t id : properties) { + drmModePropertyRes *property = drmModeGetProperty(fd_, id); + if (!property) { + ret = -errno; + std::cerr + << "Failed to get property: " << strerror(-ret) + << std::endl; + continue; + } + + properties_.emplace_back(this, property); + drmModeFreeProperty(property); + + Property &obj = properties_.back(); + objects_[obj.id()] = &obj; + } + + /* Finally, perform all delayed setup of mode objects. */ + for (auto &object : objects_) { + ret = object.second->setup(); + if (ret < 0) { + std::cerr + << "Failed to setup object " << object.second->id() + << ": " << strerror(-ret) << std::endl; + return ret; + } + } + + return 0; +} + +const Object *Device::object(uint32_t id) +{ + const auto iter = objects_.find(id); + if (iter == objects_.end()) + return nullptr; + + return iter->second; +} + +std::unique_ptr Device::createFrameBuffer( + const libcamera::FrameBuffer &buffer, + const libcamera::PixelFormat &format, + const libcamera::Size &size, unsigned int stride) +{ + std::unique_ptr fb{ new FrameBuffer(this) }; + + uint32_t handles[4] = {}; + uint32_t pitches[4] = {}; + uint32_t offsets[4] = {}; + int ret; + + const std::vector &planes = buffer.planes(); + fb->planes_.reserve(planes.size()); + + unsigned int i = 0; + for (const libcamera::FrameBuffer::Plane &plane : planes) { + uint32_t handle; + + ret = drmPrimeFDToHandle(fd_, plane.fd.fd(), &handle); + if (ret < 0) { + ret = -errno; + std::cerr + << "Unable to import framebuffer dmabuf: " + << strerror(-ret) << std::endl; + return nullptr; + } + + fb->planes_.push_back({ handle }); + + handles[i] = handle; + pitches[i] = stride; + offsets[i] = 0; /* TODO */ + ++i; + } + + ret = drmModeAddFB2(fd_, size.width, size.height, format.fourcc(), handles, + pitches, offsets, &fb->id_, 0); + if (ret < 0) { + ret = -errno; + std::cerr + << "Failed to add framebuffer: " + << strerror(-ret) << std::endl; + return nullptr; + } + + return fb; +} + +void Device::drmEvent() +{ + drmEventContext ctx{}; + ctx.version = DRM_EVENT_CONTEXT_VERSION; + ctx.page_flip_handler = &Device::pageFlipComplete; + + drmHandleEvent(fd_, &ctx); +} + +void Device::pageFlipComplete([[maybe_unused]] int fd, + [[maybe_unused]] unsigned int sequence, + [[maybe_unused]] unsigned int tv_sec, + [[maybe_unused]] unsigned int tv_usec, + void *user_data) +{ + AtomicRequest *request = static_cast(user_data); + request->device()->requestComplete.emit(request); +} + +} /* namespace DRM */ diff --git a/src/cam/drm.h b/src/cam/drm.h new file mode 100644 index 000000000000..e1395bc9c7e6 --- /dev/null +++ b/src/cam/drm.h @@ -0,0 +1,331 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Ideas on Board Oy + * + * drm.h - DRM/KMS Helpers + */ +#ifndef __CAM_DRM_H__ +#define __CAM_DRM_H__ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace libcamera { +class FrameBuffer; +class PixelFormat; +class Size; +} /* namespace libcamera */ + +namespace DRM { + +class Device; +class Plane; +class Property; +class PropertyValue; + +class Object +{ +public: + enum Type { + TypeCrtc = DRM_MODE_OBJECT_CRTC, + TypeConnector = DRM_MODE_OBJECT_CONNECTOR, + TypeEncoder = DRM_MODE_OBJECT_ENCODER, + TypeMode = DRM_MODE_OBJECT_MODE, + TypeProperty = DRM_MODE_OBJECT_PROPERTY, + TypeFb = DRM_MODE_OBJECT_FB, + TypeBlob = DRM_MODE_OBJECT_BLOB, + TypePlane = DRM_MODE_OBJECT_PLANE, + TypeAny = DRM_MODE_OBJECT_ANY, + }; + + Object(Device *dev, uint32_t id, Type type); + virtual ~Object(); + + Device *device() const { return dev_; } + uint32_t id() const { return id_; } + Type type() const { return type_; } + + const Property *property(const std::string &name) const; + const PropertyValue *propertyValue(const std::string &name) const; + const std::vector &properties() const { return properties_; } + +protected: + virtual int setup() + { + return 0; + } + + uint32_t id_; + +private: + friend Device; + + Device *dev_; + Type type_; + std::vector properties_; +}; + +class Property : public Object +{ +public: + enum Type { + TypeUnknown = 0, + TypeRange, + TypeEnum, + TypeBlob, + TypeBitmask, + TypeObject, + TypeSignedRange, + }; + + Property(Device *dev, drmModePropertyRes *property); + + Type type() const { return type_; } + const std::string &name() const { return name_; } + + bool isImmutable() const { return flags_ & DRM_MODE_PROP_IMMUTABLE; } + + const std::vector values() const { return values_; } + const std::map &enums() const { return enums_; } + const std::vector blobs() const { return blobs_; } + +private: + Type type_; + std::string name_; + uint32_t flags_; + std::vector values_; + std::map enums_; + std::vector blobs_; +}; + +class PropertyValue +{ +public: + PropertyValue(uint32_t id, uint64_t value) + : id_(id), value_(value) + { + } + + uint32_t id() const { return id_; } + uint32_t value() const { return value_; } + +private: + uint32_t id_; + uint64_t value_; +}; + +class Blob : public Object +{ +public: + Blob(Device *dev, const libcamera::Span &data); + ~Blob(); + + bool isValid() const { return id() != 0; } +}; + +class Mode : public drmModeModeInfo +{ +public: + Mode(const drmModeModeInfo &mode); + + std::unique_ptr toBlob(Device *dev) const; +}; + +class Crtc : public Object +{ +public: + Crtc(Device *dev, const drmModeCrtc *crtc, unsigned int index); + + unsigned int index() const { return index_; } + const std::vector &planes() const { return planes_; } + +private: + friend Device; + + unsigned int index_; + std::vector planes_; +}; + +class Encoder : public Object +{ +public: + Encoder(Device *dev, const drmModeEncoder *encoder); + + uint32_t type() const { return type_; } + + const std::vector &possibleCrtcs() const { return possibleCrtcs_; } + +private: + uint32_t type_; + std::vector possibleCrtcs_; +}; + +class Connector : public Object +{ +public: + enum Status { + Connected, + Disconnected, + Unknown, + }; + + Connector(Device *dev, const drmModeConnector *connector); + + uint32_t type() const { return type_; } + const std::string &name() const { return name_; } + + Status status() const { return status_; } + + const std::vector &encoders() const { return encoders_; } + const std::vector &modes() const { return modes_; } + +private: + uint32_t type_; + std::string name_; + Status status_; + std::vector encoders_; + std::vector modes_; +}; + +class Plane : public Object +{ +public: + enum Type { + TypeOverlay, + TypePrimary, + TypeCursor, + }; + + Plane(Device *dev, const drmModePlane *plane); + + Type type() const { return type_; } + const std::vector &formats() const { return formats_; } + const std::vector &possibleCrtcs() const { return possibleCrtcs_; } + + bool supportsFormat(const libcamera::PixelFormat &format) const; + +protected: + int setup() override; + +private: + friend class Device; + + Type type_; + std::vector formats_; + std::vector possibleCrtcs_; + uint32_t possibleCrtcsMask_; +}; + +class FrameBuffer : public Object +{ +public: + struct Plane { + uint32_t handle; + }; + + ~FrameBuffer(); + +private: + friend class Device; + + FrameBuffer(Device *dev); + + std::vector planes_; +}; + +class AtomicRequest +{ +public: + enum Flags { + FlagAllowModeset = (1 << 0), + FlagAsync = (1 << 1), + }; + + AtomicRequest(Device *dev); + ~AtomicRequest(); + + Device *device() const { return dev_; } + bool isValid() const { return valid_; } + + int addProperty(const Object *object, const std::string &property, + uint64_t value); + int addProperty(const Object *object, const std::string &property, + std::unique_ptr blob); + int commit(unsigned int flags = 0); + +private: + AtomicRequest(const AtomicRequest &) = delete; + AtomicRequest(const AtomicRequest &&) = delete; + AtomicRequest &operator=(const AtomicRequest &) = delete; + AtomicRequest &operator=(const AtomicRequest &&) = delete; + + int addProperty(uint32_t object, uint32_t property, uint64_t value); + + Device *dev_; + bool valid_; + drmModeAtomicReq *request_; + std::list> blobs_; +}; + +class Device +{ +public: + Device(); + ~Device(); + + int init(); + + int fd() const { return fd_; } + + const std::list &crtcs() const { return crtcs_; } + const std::list &encoders() const { return encoders_; } + const std::list &connectors() const { return connectors_; } + const std::list &planes() const { return planes_; } + const std::list &properties() const { return properties_; } + + const Object *object(uint32_t id); + + std::unique_ptr createFrameBuffer( + const libcamera::FrameBuffer &buffer, + const libcamera::PixelFormat &format, + const libcamera::Size &size, unsigned int stride); + + libcamera::Signal requestComplete; + +private: + Device(const Device &) = delete; + Device(const Device &&) = delete; + Device &operator=(const Device &) = delete; + Device &operator=(const Device &&) = delete; + + int getResources(); + + void drmEvent(); + static void pageFlipComplete(int fd, unsigned int sequence, + unsigned int tv_sec, unsigned int tv_usec, + void *user_data); + + int fd_; + + std::list crtcs_; + std::list encoders_; + std::list connectors_; + std::list planes_; + std::list properties_; + + std::map objects_; +}; + +} /* namespace DRM */ + +#endif /* __CAM_DRM_H__ */ diff --git a/src/cam/meson.build b/src/cam/meson.build index e692ea351987..b47add55b0cb 100644 --- a/src/cam/meson.build +++ b/src/cam/meson.build @@ -19,10 +19,23 @@ cam_sources = files([ 'stream_options.cpp', ]) +cam_cpp_args = [] + +libdrm = dependency('libdrm', required : false) + +if libdrm.found() +cam_cpp_args += [ '-DHAVE_KMS' ] +cam_sources += files([ + 'drm.cpp', +]) +endif + cam = executable('cam', cam_sources, dependencies : [ libatomic, libcamera_public, + libdrm, libevent, ], + cpp_args : cam_cpp_args, install : true) From patchwork Fri Jul 30 01:03:04 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 13152 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 2E2FEC3234 for ; Fri, 30 Jul 2021 01:03:28 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8F4C968811; Fri, 30 Jul 2021 03:03:27 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="DpxieMhg"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id ADEC0687BE for ; Fri, 30 Jul 2021 03:03:20 +0200 (CEST) Received: from pendragon.lan (62-78-145-57.bb.dnainternet.fi [62.78.145.57]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 43BDB9FB for ; Fri, 30 Jul 2021 03:03:20 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1627607000; bh=lKsht/tckm4R7RvSQf5+sJ8RSPNPRLnckLFWl3g+fFU=; h=From:To:Subject:Date:In-Reply-To:References:From; b=DpxieMhgpQqUNtJW8V9/D9YR7pFbXoVADXxrMr5Bbyfofl4HmD1FXMUU2h9tteZYs CZazCFWHemXk8ZJbj1BdtiOWAxGZlD0LDknYs8kju6homK6aTnaEbGvZG7OpUJyDrO M7g07duggnromqOdnPoYG8M7EJ2/fifqODnzOeKU= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Date: Fri, 30 Jul 2021 04:03:04 +0300 Message-Id: <20210730010306.19956-7-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210730010306.19956-1-laurent.pinchart@ideasonboard.com> References: <20210730010306.19956-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 6/8] cam: Add KMS sink class 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: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a KMSSink class to display framebuffers through the DRM/KMS API. Signed-off-by: Laurent Pinchart --- Changes since v1: - Use formats:: namespace --- src/cam/kms_sink.cpp | 298 +++++++++++++++++++++++++++++++++++++++++++ src/cam/kms_sink.h | 76 +++++++++++ src/cam/meson.build | 1 + 3 files changed, 375 insertions(+) create mode 100644 src/cam/kms_sink.cpp create mode 100644 src/cam/kms_sink.h diff --git a/src/cam/kms_sink.cpp b/src/cam/kms_sink.cpp new file mode 100644 index 000000000000..b8f86dcb6f4e --- /dev/null +++ b/src/cam/kms_sink.cpp @@ -0,0 +1,298 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Ideas on Board Oy + * + * kms_sink.cpp - KMS Sink + */ + +#include "kms_sink.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "drm.h" + +KMSSink::KMSSink(const std::string &connectorName) + : connector_(nullptr), crtc_(nullptr), plane_(nullptr), mode_(nullptr) +{ + int ret = dev_.init(); + if (ret < 0) + return; + + /* + * Find the requested connector. If no connector is requested, pick the + * first connected connector. + */ + for (const DRM::Connector &conn : dev_.connectors()) { + if (conn.name() == connectorName) { + connector_ = &conn; + break; + } + + if (conn.status() != DRM::Connector::Disconnected) { + if (!connector_ || + (connector_->status() == DRM::Connector::Unknown && + conn.status() == DRM::Connector::Connected)) + connector_ = &conn; + } + } + + if (!connector_) { + if (!connectorName.empty()) + std::cerr + << "Connector " << connectorName << " not found" + << std::endl; + else + std::cerr << "No connected connector found" << std::endl; + return; + } + + dev_.requestComplete.connect(this, &KMSSink::requestComplete); +} + +void KMSSink::mapBuffer(libcamera::FrameBuffer *buffer) +{ + std::unique_ptr drmBuffer = + dev_.createFrameBuffer(*buffer, format_, size_, stride_); + if (!drmBuffer) + return; + + buffers_.emplace(std::piecewise_construct, + std::forward_as_tuple(buffer), + std::forward_as_tuple(std::move(drmBuffer))); +} + +int KMSSink::configure(const libcamera::CameraConfiguration &config) +{ + crtc_ = nullptr; + plane_ = nullptr; + mode_ = nullptr; + + const libcamera::StreamConfiguration &cfg = config.at(0); + int ret = configurePipeline(cfg.pixelFormat); + if (ret < 0) + return ret; + + const std::vector &modes = connector_->modes(); + const auto iter = std::find_if(modes.begin(), modes.end(), + [&](const DRM::Mode &mode) { + return mode.hdisplay == cfg.size.width && + mode.vdisplay == cfg.size.height; + }); + if (iter == modes.end()) { + std::cerr + << "No mode matching " << cfg.size.toString() + << std::endl; + return -EINVAL; + } + + mode_ = &*iter; + size_ = cfg.size; + stride_ = cfg.stride; + + return 0; +} + +int KMSSink::configurePipeline(const libcamera::PixelFormat &format) +{ + /* + * If the requested format has an alpha channel, also consider the X + * variant. + */ + libcamera::PixelFormat xFormat; + + switch (format) { + case libcamera::formats::ABGR8888: + xFormat = libcamera::formats::XBGR8888; + break; + case libcamera::formats::ARGB8888: + xFormat = libcamera::formats::XRGB8888; + break; + case libcamera::formats::BGRA8888: + xFormat = libcamera::formats::BGRX8888; + break; + case libcamera::formats::RGBA8888: + xFormat = libcamera::formats::RGBX8888; + break; + } + + /* + * Find a CRTC and plane suitable for the request format and the + * connector at the end of the pipeline. Restrict the search to primary + * planes for now. + */ + for (const DRM::Encoder *encoder : connector_->encoders()) { + for (const DRM::Crtc *crtc : encoder->possibleCrtcs()) { + for (const DRM::Plane *plane : crtc->planes()) { + if (plane->type() != DRM::Plane::TypePrimary) + continue; + + if (plane->supportsFormat(format)) { + crtc_ = crtc; + plane_ = plane; + format_ = format; + return 0; + } + + if (plane->supportsFormat(xFormat)) { + crtc_ = crtc; + plane_ = plane; + format_ = xFormat; + return 0; + } + } + } + } + + std::cerr + << "Unable to find display pipeline for format " + << format.toString() << std::endl; + return -EPIPE; +} + +int KMSSink::start() +{ + std::unique_ptr request; + + int ret = FrameSink::start(); + if (ret < 0) + return ret; + + /* Disable all CRTCs and planes to start from a known valid state. */ + request = std::make_unique(&dev_); + + for (const DRM::Crtc &crtc : dev_.crtcs()) + request->addProperty(&crtc, "ACTIVE", 0); + + for (const DRM::Plane &plane : dev_.planes()) { + request->addProperty(&plane, "CRTC_ID", 0); + request->addProperty(&plane, "FB_ID", 0); + } + + ret = request->commit(DRM::AtomicRequest::FlagAllowModeset); + if (ret < 0) { + std::cerr + << "Failed to disable CRTCs and planes: " + << strerror(-ret) << std::endl; + return ret; + } + + /* Enable the display pipeline with no plane to start with. */ + request = std::make_unique(&dev_); + + request->addProperty(connector_, "CRTC_ID", crtc_->id()); + request->addProperty(crtc_, "ACTIVE", 1); + request->addProperty(crtc_, "MODE_ID", mode_->toBlob(&dev_)); + + ret = request->commit(DRM::AtomicRequest::FlagAllowModeset); + if (ret < 0) { + std::cerr + << "Failed to enable display pipeline: " + << strerror(-ret) << std::endl; + return ret; + } + + planeInitialized_ = false; + + return 0; +} + +int KMSSink::stop() +{ + /* Display pipeline. */ + DRM::AtomicRequest request(&dev_); + + request.addProperty(connector_, "CRTC_ID", 0); + request.addProperty(crtc_, "ACTIVE", 0); + request.addProperty(crtc_, "MODE_ID", 0); + request.addProperty(plane_, "CRTC_ID", 0); + request.addProperty(plane_, "FB_ID", 0); + + int ret = request.commit(DRM::AtomicRequest::FlagAllowModeset); + if (ret < 0) { + std::cerr + << "Failed to stop display pipeline: " + << strerror(-ret) << std::endl; + return ret; + } + + /* Free all buffers. */ + pending_.reset(); + queued_.reset(); + active_.reset(); + buffers_.clear(); + + return FrameSink::stop(); +} + +bool KMSSink::consumeRequest(libcamera::Request *camRequest) +{ + if (pending_) + return true; + + libcamera::FrameBuffer *buffer = camRequest->buffers().begin()->second; + auto iter = buffers_.find(buffer); + if (iter == buffers_.end()) + return true; + + DRM::FrameBuffer *drmBuffer = iter->second.get(); + + DRM::AtomicRequest *drmRequest = new DRM::AtomicRequest(&dev_); + drmRequest->addProperty(plane_, "FB_ID", drmBuffer->id()); + + if (!planeInitialized_) { + drmRequest->addProperty(plane_, "CRTC_ID", crtc_->id()); + drmRequest->addProperty(plane_, "SRC_X", 0 << 16); + drmRequest->addProperty(plane_, "SRC_Y", 0 << 16); + drmRequest->addProperty(plane_, "SRC_W", mode_->hdisplay << 16); + drmRequest->addProperty(plane_, "SRC_H", mode_->vdisplay << 16); + drmRequest->addProperty(plane_, "CRTC_X", 0); + drmRequest->addProperty(plane_, "CRTC_Y", 0); + drmRequest->addProperty(plane_, "CRTC_W", mode_->hdisplay); + drmRequest->addProperty(plane_, "CRTC_H", mode_->vdisplay); + planeInitialized_ = true; + } + + pending_ = std::make_unique(drmRequest, camRequest); + + std::lock_guard lock(lock_); + + if (!queued_) { + int ret = drmRequest->commit(DRM::AtomicRequest::FlagAsync); + if (ret < 0) + std::cerr + << "Failed to commit atomic request: " + << strerror(-ret) << std::endl; + queued_ = std::move(pending_); + } + + return false; +} + +void KMSSink::requestComplete(DRM::AtomicRequest *request) +{ + std::lock_guard lock(lock_); + + assert(queued_ && queued_->drmRequest_.get() == request); + + /* Complete the active request, if any. */ + if (active_) + requestReleased.emit(active_->camRequest_); + + /* The queued request becomes active. */ + active_ = std::move(queued_); + + /* Queue the pending request, if any. */ + if (pending_) { + pending_->drmRequest_->commit(DRM::AtomicRequest::FlagAsync); + queued_ = std::move(pending_); + } +} diff --git a/src/cam/kms_sink.h b/src/cam/kms_sink.h new file mode 100644 index 000000000000..7b6ffcede28c --- /dev/null +++ b/src/cam/kms_sink.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Ideas on Board Oy + * + * kms_sink.h - KMS Sink + */ +#ifndef __CAM_KMS_SINK_H__ +#define __CAM_KMS_SINK_H__ + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "drm.h" +#include "frame_sink.h" + +class KMSSink : public FrameSink +{ +public: + KMSSink(const std::string &connectorName); + + bool isValid() const { return connector_ != nullptr; } + + void mapBuffer(libcamera::FrameBuffer *buffer) override; + + int configure(const libcamera::CameraConfiguration &config) override; + int start() override; + int stop() override; + + bool consumeRequest(libcamera::Request *request) override; + +private: + class Request + { + public: + Request(DRM::AtomicRequest *drmRequest, libcamera::Request *camRequest) + : drmRequest_(drmRequest), camRequest_(camRequest) + { + } + + std::unique_ptr drmRequest_; + libcamera::Request *camRequest_; + }; + + int configurePipeline(const libcamera::PixelFormat &format); + void requestComplete(DRM::AtomicRequest *request); + + DRM::Device dev_; + + const DRM::Connector *connector_; + const DRM::Crtc *crtc_; + const DRM::Plane *plane_; + const DRM::Mode *mode_; + + libcamera::PixelFormat format_; + libcamera::Size size_; + unsigned int stride_; + + bool planeInitialized_; + + std::map> buffers_; + + std::mutex lock_; + std::unique_ptr pending_; + std::unique_ptr queued_; + std::unique_ptr active_; +}; + +#endif /* __CAM_KMS_SINK_H__ */ diff --git a/src/cam/meson.build b/src/cam/meson.build index b47add55b0cb..ea36aaa5c514 100644 --- a/src/cam/meson.build +++ b/src/cam/meson.build @@ -27,6 +27,7 @@ if libdrm.found() cam_cpp_args += [ '-DHAVE_KMS' ] cam_sources += files([ 'drm.cpp', + 'kms_sink.cpp' ]) endif From patchwork Fri Jul 30 01:03:05 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 13151 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 5D76DC3235 for ; Fri, 30 Jul 2021 01:03:28 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0F57E687FA; Fri, 30 Jul 2021 03:03:28 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="GDuVQes+"; dkim-atps=neutral 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 01909687BE for ; Fri, 30 Jul 2021 03:03:21 +0200 (CEST) Received: from pendragon.lan (62-78-145-57.bb.dnainternet.fi [62.78.145.57]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 92B1E101E for ; Fri, 30 Jul 2021 03:03:20 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1627607000; bh=WaBgTgKNnFsIAIOquzNhwQvmGzwrlHUaHB6eaghRYZQ=; h=From:To:Subject:Date:In-Reply-To:References:From; b=GDuVQes+hgvBe7BAQuuqF8gymeAJponEi2L96NpKoDwtLELRC6uTWyhUTlkZOoFK5 J8DQCNc+Nk2z81fec3wCPv678MQ5mjEHK5/sV8iE+t8Oh1XVjRTFj2jg5TMqUV7tAR SVGWBqZhtAQqOjOkZiAqo4kPLwA5kMRELBbqOVdw= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Date: Fri, 30 Jul 2021 04:03:05 +0300 Message-Id: <20210730010306.19956-8-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210730010306.19956-1-laurent.pinchart@ideasonboard.com> References: <20210730010306.19956-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 7/8] cam: kms_sink: Enable display on first frame 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: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Not all display controllers support enabling the display without any active plane. Delay display enabling to the first frame. Signed-off-by: Laurent Pinchart Reviewed-by: Paul Elder --- src/cam/kms_sink.cpp | 31 +++++++++++-------------------- src/cam/kms_sink.h | 2 -- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/src/cam/kms_sink.cpp b/src/cam/kms_sink.cpp index b8f86dcb6f4e..2c767cb40885 100644 --- a/src/cam/kms_sink.cpp +++ b/src/cam/kms_sink.cpp @@ -185,23 +185,6 @@ int KMSSink::start() return ret; } - /* Enable the display pipeline with no plane to start with. */ - request = std::make_unique(&dev_); - - request->addProperty(connector_, "CRTC_ID", crtc_->id()); - request->addProperty(crtc_, "ACTIVE", 1); - request->addProperty(crtc_, "MODE_ID", mode_->toBlob(&dev_)); - - ret = request->commit(DRM::AtomicRequest::FlagAllowModeset); - if (ret < 0) { - std::cerr - << "Failed to enable display pipeline: " - << strerror(-ret) << std::endl; - return ret; - } - - planeInitialized_ = false; - return 0; } @@ -245,10 +228,17 @@ bool KMSSink::consumeRequest(libcamera::Request *camRequest) DRM::FrameBuffer *drmBuffer = iter->second.get(); + unsigned int flags = DRM::AtomicRequest::FlagAsync; DRM::AtomicRequest *drmRequest = new DRM::AtomicRequest(&dev_); drmRequest->addProperty(plane_, "FB_ID", drmBuffer->id()); - if (!planeInitialized_) { + if (!active_ && !queued_) { + /* Enable the display pipeline on the first frame. */ + drmRequest->addProperty(connector_, "CRTC_ID", crtc_->id()); + + drmRequest->addProperty(crtc_, "ACTIVE", 1); + drmRequest->addProperty(crtc_, "MODE_ID", mode_->toBlob(&dev_)); + drmRequest->addProperty(plane_, "CRTC_ID", crtc_->id()); drmRequest->addProperty(plane_, "SRC_X", 0 << 16); drmRequest->addProperty(plane_, "SRC_Y", 0 << 16); @@ -258,7 +248,8 @@ bool KMSSink::consumeRequest(libcamera::Request *camRequest) drmRequest->addProperty(plane_, "CRTC_Y", 0); drmRequest->addProperty(plane_, "CRTC_W", mode_->hdisplay); drmRequest->addProperty(plane_, "CRTC_H", mode_->vdisplay); - planeInitialized_ = true; + + flags |= DRM::AtomicRequest::FlagAllowModeset; } pending_ = std::make_unique(drmRequest, camRequest); @@ -266,7 +257,7 @@ bool KMSSink::consumeRequest(libcamera::Request *camRequest) std::lock_guard lock(lock_); if (!queued_) { - int ret = drmRequest->commit(DRM::AtomicRequest::FlagAsync); + int ret = drmRequest->commit(flags); if (ret < 0) std::cerr << "Failed to commit atomic request: " diff --git a/src/cam/kms_sink.h b/src/cam/kms_sink.h index 7b6ffcede28c..8536ef2e532d 100644 --- a/src/cam/kms_sink.h +++ b/src/cam/kms_sink.h @@ -63,8 +63,6 @@ private: libcamera::Size size_; unsigned int stride_; - bool planeInitialized_; - std::map> buffers_; std::mutex lock_; From patchwork Fri Jul 30 01:03:06 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 13153 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 9A26BC3236 for ; Fri, 30 Jul 2021 01:03:30 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B7320687C8; Fri, 30 Jul 2021 03:03:28 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="cu5Q9THw"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4AAF1687CE for ; Fri, 30 Jul 2021 03:03:21 +0200 (CEST) Received: from pendragon.lan (62-78-145-57.bb.dnainternet.fi [62.78.145.57]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id EB9899FB for ; Fri, 30 Jul 2021 03:03:20 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1627607001; bh=+JLWAgLxR47H93PYKTUGzbplxLYFwMrlODz5cI9G0Js=; h=From:To:Subject:Date:In-Reply-To:References:From; b=cu5Q9THwSaF/7IrTJw/YZDV4VNZvsas5nfdgOpybmrkvApQDDrlTfy+A1UZ38HmWb xA1BV+PlzpR8bgCWsU6U5ilHBuZIgYbjmLf1kXvoicY1Q0zo2tN+CJbS94S4hWcCI2 d3CxwQodv4n5Vn8EpVNDD+4bysSqRzCOrOL8B5s8= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Date: Fri, 30 Jul 2021 04:03:06 +0300 Message-Id: <20210730010306.19956-9-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210730010306.19956-1-laurent.pinchart@ideasonboard.com> References: <20210730010306.19956-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 8/8] cam: Add support for viewfinder through DRM/KMS 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: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Use the KMSSink class to display the viewfinder stream, if any, through DRM/KMS. The output connector is selected through the new -D/--display argument. Signed-off-by: Laurent Pinchart Reviewed-by: Paul Elder --- Changes since v1: - Validate display option in CamApp::init() --- src/cam/camera_session.cpp | 28 ++++++++++++++++++++++++++++ src/cam/main.cpp | 6 ++++++ src/cam/main.h | 1 + 3 files changed, 35 insertions(+) diff --git a/src/cam/camera_session.cpp b/src/cam/camera_session.cpp index 4f1f9ec8eb10..8d8c1b8ba469 100644 --- a/src/cam/camera_session.cpp +++ b/src/cam/camera_session.cpp @@ -16,6 +16,9 @@ #include "camera_session.h" #include "event_loop.h" #include "file_sink.h" +#ifdef HAVE_KMS +#include "kms_sink.h" +#endif #include "main.h" #include "stream_options.h" @@ -66,6 +69,26 @@ CameraSession::CameraSession(CameraManager *cm, bool strictFormats = options_.isSet(OptStrictFormats); + if (options_.isSet(OptDisplay)) { + if (options_.isSet(OptFile)) { + std::cerr << "--display and --file options are mutually exclusive" + << std::endl; + return; + } + + if (roles.size() != 1) { + std::cerr << "Display doesn't support multiple streams" + << std::endl; + return; + } + + if (roles[0] != StreamRole::Viewfinder) { + std::cerr << "Display requires a viewfinder stream" + << std::endl; + return; + } + } + switch (config->validate()) { case CameraConfiguration::Valid: break; @@ -161,6 +184,11 @@ int CameraSession::start() camera_->requestCompleted.connect(this, &CameraSession::requestComplete); +#ifdef HAVE_KMS + if (options_.isSet(OptDisplay)) + sink_ = std::make_unique(options_[OptDisplay].toString()); +#endif + if (options_.isSet(OptFile)) { if (!options_[OptFile].toString().empty()) sink_ = std::make_unique(options_[OptFile]); diff --git a/src/cam/main.cpp b/src/cam/main.cpp index 34cbc3229563..c7f664b903e0 100644 --- a/src/cam/main.cpp +++ b/src/cam/main.cpp @@ -132,6 +132,12 @@ int CamApp::parseOptions(int argc, char *argv[]) "Capture until interrupted by user or until frames captured", "capture", ArgumentOptional, "count", false, OptCamera); +#ifdef HAVE_KMS + parser.addOption(OptDisplay, OptionString, + "Display viewfinder through DRM/KMS on specified connector", + "display", ArgumentOptional, "connector", false, + OptCamera); +#endif parser.addOption(OptFile, OptionString, "Write captured frames to disk\n" "If the file name ends with a '/', it sets the directory in which\n" diff --git a/src/cam/main.h b/src/cam/main.h index d22451f59817..1c2fab763698 100644 --- a/src/cam/main.h +++ b/src/cam/main.h @@ -10,6 +10,7 @@ enum { OptCamera = 'c', OptCapture = 'C', + OptDisplay = 'D', OptFile = 'F', OptHelp = 'h', OptInfo = 'I',