summaryrefslogtreecommitdiff
path: root/src/cam/kms_sink.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/cam/kms_sink.cpp')
-rw-r--r--src/cam/kms_sink.cpp538
1 files changed, 0 insertions, 538 deletions
diff --git a/src/cam/kms_sink.cpp b/src/cam/kms_sink.cpp
deleted file mode 100644
index 754b061e..00000000
--- a/src/cam/kms_sink.cpp
+++ /dev/null
@@ -1,538 +0,0 @@
-/* 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 <array>
-#include <algorithm>
-#include <assert.h>
-#include <iostream>
-#include <limits.h>
-#include <memory>
-#include <stdint.h>
-#include <string.h>
-
-#include <libcamera/camera.h>
-#include <libcamera/formats.h>
-#include <libcamera/framebuffer.h>
-#include <libcamera/stream.h>
-
-#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<uint32_t, 4> 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<DRM::FrameBuffer> 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);
-
- /* Find the best mode for the stream size. */
- const std::vector<DRM::Mode> &modes = connector_->modes();
-
- unsigned int cfgArea = cfg.size.width * cfg.size.height;
- unsigned int bestDistance = UINT_MAX;
-
- for (const DRM::Mode &mode : modes) {
- unsigned int modeArea = mode.hdisplay * mode.vdisplay;
- unsigned int distance = modeArea > cfgArea ? modeArea - cfgArea
- : cfgArea - modeArea;
-
- if (distance < bestDistance) {
- mode_ = &mode;
- bestDistance = distance;
-
- /*
- * If the sizes match exactly, there will be no better
- * match.
- */
- if (distance == 0)
- break;
- }
- }
-
- if (!mode_) {
- std::cerr << "No modes\n";
- return -EINVAL;
- }
-
- int ret = configurePipeline(cfg.pixelFormat);
- if (ret < 0)
- return ret;
-
- size_ = cfg.size;
- stride_ = cfg.stride;
-
- /* Configure color space. */
- colorEncoding_ = std::nullopt;
- colorRange_ = std::nullopt;
-
- if (cfg.colorSpace->ycbcrEncoding == libcamera::ColorSpace::YcbcrEncoding::None)
- return 0;
-
- /*
- * The encoding and range enums are defined in the kernel but not
- * exposed in public headers.
- */
- enum drm_color_encoding {
- DRM_COLOR_YCBCR_BT601,
- DRM_COLOR_YCBCR_BT709,
- DRM_COLOR_YCBCR_BT2020,
- };
-
- enum drm_color_range {
- DRM_COLOR_YCBCR_LIMITED_RANGE,
- DRM_COLOR_YCBCR_FULL_RANGE,
- };
-
- const DRM::Property *colorEncoding = plane_->property("COLOR_ENCODING");
- const DRM::Property *colorRange = plane_->property("COLOR_RANGE");
-
- if (colorEncoding) {
- drm_color_encoding encoding;
-
- switch (cfg.colorSpace->ycbcrEncoding) {
- case libcamera::ColorSpace::YcbcrEncoding::Rec601:
- default:
- encoding = DRM_COLOR_YCBCR_BT601;
- break;
- case libcamera::ColorSpace::YcbcrEncoding::Rec709:
- encoding = DRM_COLOR_YCBCR_BT709;
- break;
- case libcamera::ColorSpace::YcbcrEncoding::Rec2020:
- encoding = DRM_COLOR_YCBCR_BT2020;
- break;
- }
-
- for (const auto &[id, name] : colorEncoding->enums()) {
- if (id == encoding) {
- colorEncoding_ = encoding;
- break;
- }
- }
- }
-
- if (colorRange) {
- drm_color_range range;
-
- switch (cfg.colorSpace->range) {
- case libcamera::ColorSpace::Range::Limited:
- default:
- range = DRM_COLOR_YCBCR_LIMITED_RANGE;
- break;
- case libcamera::ColorSpace::Range::Full:
- range = DRM_COLOR_YCBCR_FULL_RANGE;
- break;
- }
-
- for (const auto &[id, name] : colorRange->enums()) {
- if (id == range) {
- colorRange_ = range;
- break;
- }
- }
- }
-
- if (!colorEncoding_ || !colorRange_)
- std::cerr << "Color space " << cfg.colorSpace->toString()
- << " not supported by the display device."
- << " Colors may be wrong." << std::endl;
-
- return 0;
-}
-
-int KMSSink::selectPipeline(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;
- }
- }
- }
- }
-
- return -EPIPE;
-}
-
-int KMSSink::configurePipeline(const libcamera::PixelFormat &format)
-{
- const int ret = selectPipeline(format);
- if (ret) {
- std::cerr
- << "Unable to find display pipeline for format "
- << format << std::endl;
-
- return ret;
- }
-
- std::cout
- << "Using KMS plane " << plane_->id() << ", CRTC " << crtc_->id()
- << ", connector " << connector_->name()
- << " (" << connector_->id() << "), mode " << mode_->hdisplay
- << "x" << mode_->vdisplay << "@" << mode_->vrefresh << std::endl;
-
- return 0;
-}
-
-int KMSSink::start()
-{
- std::unique_ptr<DRM::AtomicRequest> 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<DRM::AtomicRequest>(&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::testModeSet(DRM::FrameBuffer *drmBuffer,
- const libcamera::Rectangle &src,
- const libcamera::Rectangle &dst)
-{
- DRM::AtomicRequest drmRequest{ &dev_ };
-
- 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_, "FB_ID", drmBuffer->id());
- drmRequest.addProperty(plane_, "SRC_X", src.x << 16);
- drmRequest.addProperty(plane_, "SRC_Y", src.y << 16);
- drmRequest.addProperty(plane_, "SRC_W", src.width << 16);
- drmRequest.addProperty(plane_, "SRC_H", src.height << 16);
- drmRequest.addProperty(plane_, "CRTC_X", dst.x);
- drmRequest.addProperty(plane_, "CRTC_Y", dst.y);
- drmRequest.addProperty(plane_, "CRTC_W", dst.width);
- drmRequest.addProperty(plane_, "CRTC_H", dst.height);
-
- return !drmRequest.commit(DRM::AtomicRequest::FlagAllowModeset |
- DRM::AtomicRequest::FlagTestOnly);
-}
-
-bool KMSSink::setupComposition(DRM::FrameBuffer *drmBuffer)
-{
- /*
- * Test composition options, from most to least desirable, to select the
- * best one.
- */
- const libcamera::Rectangle framebuffer{ size_ };
- const libcamera::Rectangle display{ 0, 0, mode_->hdisplay, mode_->vdisplay };
-
- /* 1. Scale the frame buffer to full screen, preserving aspect ratio. */
- libcamera::Rectangle src = framebuffer;
- libcamera::Rectangle dst = display.size().boundedToAspectRatio(framebuffer.size())
- .centeredTo(display.center());
-
- if (testModeSet(drmBuffer, src, dst)) {
- std::cout << "KMS: full-screen scaled output, square pixels"
- << std::endl;
- src_ = src;
- dst_ = dst;
- return true;
- }
-
- /*
- * 2. Scale the frame buffer to full screen, without preserving aspect
- * ratio.
- */
- src = framebuffer;
- dst = display;
-
- if (testModeSet(drmBuffer, src, dst)) {
- std::cout << "KMS: full-screen scaled output, non-square pixels"
- << std::endl;
- src_ = src;
- dst_ = dst;
- return true;
- }
-
- /* 3. Center the frame buffer on the display. */
- src = display.size().centeredTo(framebuffer.center()).boundedTo(framebuffer);
- dst = framebuffer.size().centeredTo(display.center()).boundedTo(display);
-
- if (testModeSet(drmBuffer, src, dst)) {
- std::cout << "KMS: centered output" << std::endl;
- src_ = src;
- dst_ = dst;
- return true;
- }
-
- /* 4. Align the frame buffer on the top-left of the display. */
- src = framebuffer.boundedTo(display);
- dst = display.boundedTo(framebuffer);
-
- if (testModeSet(drmBuffer, src, dst)) {
- std::cout << "KMS: top-left aligned output" << std::endl;
- src_ = src;
- dst_ = dst;
- return true;
- }
-
- return false;
-}
-
-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::FrameBuffer *drmBuffer = iter->second.get();
-
- unsigned int flags = DRM::AtomicRequest::FlagAsync;
- std::unique_ptr<DRM::AtomicRequest> drmRequest =
- std::make_unique<DRM::AtomicRequest>(&dev_);
- drmRequest->addProperty(plane_, "FB_ID", drmBuffer->id());
-
- if (!active_ && !queued_) {
- /* Enable the display pipeline on the first frame. */
- if (!setupComposition(drmBuffer)) {
- std::cerr << "Failed to setup composition" << std::endl;
- return true;
- }
-
- 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", src_.x << 16);
- drmRequest->addProperty(plane_, "SRC_Y", src_.y << 16);
- drmRequest->addProperty(plane_, "SRC_W", src_.width << 16);
- drmRequest->addProperty(plane_, "SRC_H", src_.height << 16);
- drmRequest->addProperty(plane_, "CRTC_X", dst_.x);
- drmRequest->addProperty(plane_, "CRTC_Y", dst_.y);
- drmRequest->addProperty(plane_, "CRTC_W", dst_.width);
- drmRequest->addProperty(plane_, "CRTC_H", dst_.height);
-
- if (colorEncoding_)
- drmRequest->addProperty(plane_, "COLOR_ENCODING", *colorEncoding_);
- if (colorRange_)
- drmRequest->addProperty(plane_, "COLOR_RANGE", *colorRange_);
-
- flags |= DRM::AtomicRequest::FlagAllowModeset;
- }
-
- pending_ = std::make_unique<Request>(std::move(drmRequest), camRequest);
-
- std::lock_guard<std::mutex> lock(lock_);
-
- if (!queued_) {
- int ret = pending_->drmRequest_->commit(flags);
- if (ret < 0) {
- std::cerr
- << "Failed to commit atomic request: "
- << strerror(-ret) << std::endl;
- /* \todo Implement error handling */
- }
-
- queued_ = std::move(pending_);
- }
-
- return false;
-}
-
-void KMSSink::requestComplete(DRM::AtomicRequest *request)
-{
- std::lock_guard<std::mutex> lock(lock_);
-
- assert(queued_ && queued_->drmRequest_.get() == request);
-
- /* Complete the active request, if any. */
- if (active_)
- requestProcessed.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_);
- }
-}