/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2021, Ideas on Board Oy * * kms_sink.cpp - KMS Sink */ #include "kms_sink.h" #include #include #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 specific connector is requested, * pick the first connected connector or, if no connector is connected, * the first connector with unknown status. */ for (const DRM::Connector &conn : dev_.connectors()) { if (!connectorName.empty()) { if (conn.name() != connectorName) continue; connector_ = &conn; break; } if (conn.status() == DRM::Connector::Connected) { connector_ = &conn; break; } if (!connector_ && conn.status() == DRM::Connector::Unknown) 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::array strides = {}; /* \todo Should libcamera report per-plane strides ? */ unsigned int uvStrideMultiplier; switch (format_) { case libcamera::formats::NV24: case libcamera::formats::NV42: uvStrideMultiplier = 4; break; case libcamera::formats::YUV420: case libcamera::formats::YVU420: case libcamera::formats::YUV422: uvStrideMultiplier = 1; break; default: uvStrideMultiplier = 2; break; } strides[0] = stride_; for (unsigned int i = 1; i < buffer->planes().size(); ++i) strides[i] = stride_ * uvStrideMultiplier / 2; std::unique_ptr drmBuffer = dev_.createFrameBuffer(*buffer, format_, size_, strides); 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) { if (!connector_) return -EINVAL; crtc_ = nullptr; plane_ = nullptr; mode_ = nullptr; const libcamera::StreamConfiguration &cfg = config.at(0); 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; } int ret = configurePipeline(cfg.pixelFormat); if (ret < 0) return ret; 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; break; } if (plane->supportsFormat(xFormat)) { crtc_ = crtc; plane_ = plane; format_ = xFormat; break; } } } } if (!crtc_) { std::cerr << "Unable to find display pipeline for format " << format.toString() << std::endl; return -EPIPE; } std::cout << "Using KMS plane " << plane_->id() << ", CRTC " << crtc_->id() << ", connector " << connector_->name() << " (" << connector_->id() << ")" << std::endl; return 0; } 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; } 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::processRequest(libcamera::Request *camRequest) { /* * Perform a very crude rate adaptation by simply dropping the request * if the display queue is full. */ if (pending_) return true; libcamera::FrameBuffer *buffer = camRequest->buffers().begin()->second; auto iter = buffers_.find(buffer); if (iter == buffers_.end()) return true; DRM::FrameBu/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2019, Google Inc. * * timer-thread.cpp - Threaded timer test */ #include <chrono> #include <iostream> #include <libcamera/base/event_dispatcher.h> #include <libcamera/base/thread.h> #include <libcamera/base/timer.h> #include "test.h" using namespace libcamera; using namespace std; using namespace std::chrono_literals; class TimeoutHandler : public Object { public: TimeoutHandler() : timer_(this), timeout_(false) { timer_.timeout.connect(this, &TimeoutHandler::timeoutHandler); timer_.start(100ms); } void restart() { timeout_ = false; timer_.start(100ms); } bool timeout() const { return timeout_; } private: void timeoutHandler() { timeout_ = true; } Timer timer_; bool timeout_; }; class TimerThreadTest : public Test { protected: int init() { thread_.start(); timeout_.moveToThread(&thread_); return TestPass; } int run() { /* * Test that the timer expires and emits the timeout signal in * the thread it belongs to. */ this_thread::sleep_for(chrono::milliseconds(200)); if (!timeout_.timeout()) { cout << "Timer expiration test failed" << endl; return TestFail; } /* * Test that starting the timer from another thread fails. We * need to interrupt the event dispatcher to make sure we don't * succeed simply because the event dispatcher hasn't noticed * the timer restart. */ timeout_.restart(); thread_.eventDispatcher()->interrupt(); this_thread::sleep_for(chrono::milliseconds(200)); if (timeout_.timeout()) { cout << "Timer restart test failed" << endl; return TestFail; } return TestPass; } void cleanup() { /* Must stop thread before destroying timeout. */ thread_.exit(0); thread_.wait(); } private: TimeoutHandler timeout_; Thread thread_; }; TEST_REGISTER(TimerThreadTest)