From patchwork Thu Oct 29 15:20:50 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ray Strode X-Patchwork-Id: 28706 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 31042C324C for ; Thu, 29 Oct 2026 15:21:38 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E6AD5690BE; Thu, 29 Oct 2026 16:21:37 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="wNGo3ZnA"; dkim-atps=neutral Received: from mail-ua1-x92b.google.com (mail-ua1-x92b.google.com [IPv6:2607:f8b0:4864:20::92b]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5BA36690A0 for ; Thu, 29 Oct 2026 16:21:35 +0100 (CET) Received: by mail-ua1-x92b.google.com with SMTP id a1e0cc1a2514c-990835cec4bso1375537241.1 for ; Thu, 29 Oct 2026 08:21:35 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793287294; x=1793892094; darn=lists.libcamera.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=qkB8CTmZtsW7GOSMvr1zjF6q2XWsZmXzbAJ8b54gKkg=; b=wNGo3ZnAgZBe8mCTdz48tEJ+4Q5GxdUX9IisbMPTFaNGCzfbWarg76Z4m9hayCigNX 0yTU3tpo9P39IDdkehf28JqUWIr10nAK5SN/rqPLY0n2u7QKAce7avjeGlUQyKHL81mp 1VAn3us1DUMUbDZunvIQeuwdUDkfosrKJb10/xOq5nTO1fl750xcM2mlxQkLeq8NOhO2 89crd0FxS+FrYrvrSAq2Gjk2IeboMe5ax2DhVZJCpzkRWflMDHv/THDqQEKBIeOCLild p0BjQWYJCgTppWsFJubZJ6Hnl8PgQ66WWqRu/t3bRbqsN+Yg95L4GpcRbVF6kpFBQ/rJ cT+g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793287294; x=1793892094; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=qkB8CTmZtsW7GOSMvr1zjF6q2XWsZmXzbAJ8b54gKkg=; b=CjZeG/gGhOhW+gbZcITUK9VsRl/p9pz+RpkoYwamAOzlBmLU6f5BKP9Ax2SRAWii+t qcGe2RVi5RMEItxMMCaWehAzqGUaaBv+vL6YeAYiAjNOsF+pNPmpwnRPPf3UY7mJRT8K sEhpGc8SoZ5E2yFVZgMme5owPJJLyYkdtc/su941Ml+PG5JQMPjf+AYHE9jtX08cVtup TndzHDTerbcoueMmGAsNAx0a5/VCk2ze6c1BewoUp+YgT7IomUubOaPkct5f6apJ3F7l vQbwf0qkV4CV2lRf71JXXfjoQK4UjkO9H1S/0V+e1vF9mP9lxDT1z1hBbj2sDpa2+aSk XBIA== X-Gm-Message-State: AFq9FYLD9w9XparOUx10nzZw3+Y0y4Bmd34A/0aTEYlxDfIOb2KANp+O agAV4DV/Kh+zRhlAXiznJ2dCQFTGCfWvncw2gSYUP+LyfiU0OMKV1CbxOUh1a7fugdI= X-Gm-Gg: AYBFou2koDGOQ9u+P1fTrrLs0W6EoVyJiTwvC227N76Xmh5fxhUOOCDu8+smgiSZ71n pL96qZhP9xf2nCGYUAZAprqNwUw/bcxPm6ngVBmjYXRL7A7tz1I1DZmw+ReSkmV6hEyCEMQwb9w KOUDtnmDeaCXYrv3X4zTD+zu4/azbIdL0hQIYsLjUgxwme6SHz0V6H0KzzYXZXaziuFAON8Us1T rNhertg8FM20bUCusR2jMg9qylwGUfLlU9EK0JHUssizlbICGE5sM9raC4xixQ5oAX4HWyMp8ae 2DLyYObYu+Xvc7KPZKtxOliVk/qpYR2+kAX3cCdnwBcgQUYgT+2FgRzLZPljGeTiK1zXrAecAKf vwND/StRBOXW7u2YQQD8RPCXZyZnSj0kTq0WuGmdQqCE1ZuKWHyEQrjPyt+G4tL6fDpXXRe1g24 52Mw5M5f0YPGVP8IP5qqSwboFRdcMGT+gnGzDLYo4M2WHfCPylbpyAcHG9Wrl4R/US7KUEwQx18 nFmFAOrOPJ6lcVtLzJ0NDI28F7YqyCJS135m+gmZ8ePGqwwnVLUUtLvW+flINGUWmU+6g0s/L1y Al6mSGum0IN9K1d5AC/PuPpYCZ96QUGZ+aI1ek6KX9SP+QftRCXcF6LK X-Received: by 2002:a05:6102:5106:b0:7cc:115:ddf4 with SMTP id ada2fe7eead31-7d03a4e928cmr3022712137.25.1793287293731; Thu, 29 Oct 2026 08:21:33 -0700 (PDT) Received: from localhost ([144.121.52.163]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-991f74e5e22sm206563241.4.2026.10.29.08.21.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Oct 2026 08:21:28 -0700 (PDT) From: Ray Strode To: libcamera-devel@lists.libcamera.org Cc: kieran.bingham@ideasonboard.com, bod.linux@nxsw.ie, laurent.pinchart@ideasonboard.com, stefan.klug@ideasonboard.com, martincarvalho@gmail.com Subject: [PATCH v5 03/11] libcamera: v4l2_subdevice: Return optional selection rectangles Date: Thu, 29 Oct 2026 11:20:50 -0400 Message-ID: <20261029152058.896209-4-halfline@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029152058.896209-1-halfline@gmail.com> References: <20261029152058.896209-1-halfline@gmail.com> MIME-Version: 1.0 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" From: Ray Strode This commit distinguishes an unavailable selection rectangle from an ioctl failure by returning the rectangle through std::optional. The query returns 0 and an empty optional for EINVAL or ENOTTY from G_SELECTION and propagates other ioctl errors. The output is cleared on entry so a missing rectangle or failed query cannot leave a previous result behind. Invalid pads and nonzero streams on devices without streams support are rejected before the ioctl. The cached capabilities are initialized so the check is also safe before open(). EINVAL from the driver still means only that no rectangle was reported for the current configuration. Callers handle the empty result explicitly. Raw sensors still require their selection rectangles. Legacy sensors retain defaults for unavailable selections and propagate query errors during driver validation and sensorInfo(), so failed reads cannot produce default geometry. Signed-off-by: Ray Strode --- include/libcamera/internal/v4l2_subdevice.h | 5 ++- src/libcamera/sensor/camera_sensor_legacy.cpp | 42 ++++++++++++++----- src/libcamera/sensor/camera_sensor_raw.cpp | 31 ++++++++------ src/libcamera/v4l2_subdevice.cpp | 40 +++++++++++++----- 4 files changed, 82 insertions(+), 36 deletions(-) diff --git a/include/libcamera/internal/v4l2_subdevice.h b/include/libcamera/internal/v4l2_subdevice.h index c37a82af..f0818d6d 100644 --- a/include/libcamera/internal/v4l2_subdevice.h +++ b/include/libcamera/internal/v4l2_subdevice.h @@ -96,62 +96,63 @@ public: unsigned int pad; unsigned int stream; }; struct Route { Route() : flags(0) { } Route(const Stream &snk, const Stream &src, uint32_t f) : sink(snk), source(src), flags(f) { } Stream sink; Stream source; uint32_t flags; }; using Routing = std::vector; explicit V4L2Subdevice(const MediaEntity *entity); ~V4L2Subdevice(); int open(); const MediaEntity *entity() const { return entity_; } int getSelection(const Stream &stream, unsigned int target, - Rectangle *rect); - int getSelection(unsigned int pad, unsigned int target, Rectangle *rect) + std::optional *rect); + int getSelection(unsigned int pad, unsigned int target, + std::optional *rect) { return getSelection({ pad, 0 }, target, rect); } int setSelection(const Stream &stream, unsigned int target, Rectangle *rect); int setSelection(unsigned int pad, unsigned int target, Rectangle *rect) { return setSelection({ pad, 0 }, target, rect); } Formats formats(const Stream &stream); Formats formats(unsigned int pad) { return formats({ pad, 0 }); } int getFormat(const Stream &stream, V4L2SubdeviceFormat *format, Whence whence = ActiveFormat); int getFormat(unsigned int pad, V4L2SubdeviceFormat *format, Whence whence = ActiveFormat) { return getFormat({ pad, 0 }, format, whence); } int setFormat(const Stream &stream, V4L2SubdeviceFormat *format, Whence whence = ActiveFormat); int setFormat(unsigned int pad, V4L2SubdeviceFormat *format, Whence whence = ActiveFormat) { return setFormat({ pad, 0 }, format, whence); } diff --git a/src/libcamera/sensor/camera_sensor_legacy.cpp b/src/libcamera/sensor/camera_sensor_legacy.cpp index 792e4b09..570372a3 100644 --- a/src/libcamera/sensor/camera_sensor_legacy.cpp +++ b/src/libcamera/sensor/camera_sensor_legacy.cpp @@ -1,44 +1,45 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2019, Google Inc. * * A V4L2-backed camera sensor */ #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libcamera/internal/bayer_format.h" #include "libcamera/internal/camera_lens.h" #include "libcamera/internal/camera_sensor.h" #include "libcamera/internal/camera_sensor_properties.h" #include "libcamera/internal/formats.h" #include "libcamera/internal/media_device.h" #include "libcamera/internal/sysfs.h" #include "libcamera/internal/v4l2_subdevice.h" namespace libcamera { class BayerFormat; @@ -363,103 +364,117 @@ int CameraSensorLegacy::validateSensorDriver() } /* Verify if sensor supports horizontal/vertical flips. */ auto queryFlip = [&](uint32_t id, const char *name) -> std::pair { const struct v4l2_query_ext_ctrl *flipInfo = subdev_->controlInfo(id); if (!flipInfo || flipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY) { LOG(CameraSensor, Debug) << "Camera sensor does not support " << name << " flip"; return { false, false }; } return { true, flipInfo->flags & V4L2_CTRL_FLAG_MODIFY_LAYOUT }; }; auto hflip = queryFlip(V4L2_CID_HFLIP, "horizontal"); auto vflip = queryFlip(V4L2_CID_VFLIP, "vertical"); if (hflip.first && vflip.first && (hflip.second != vflip.second)) { LOG(CameraSensor, Error) << "Flips with differing alter bayer order properties" << " are unsupported. Disabling flipping."; } else { supportHFlips_ = hflip.first; supportVFlips_ = vflip.first; flipsAlterBayerOrder_ = hflip.second || vflip.second; } /* * Make sure the required selection targets are supported. * - * Failures in reading any of the targets are not deemed to be fatal, + * Unavailable selection targets are not deemed to be fatal, * but some properties and features, like constructing a * IPACameraSensorInfo for the IPA module, won't be supported. * * \todo Make support for selection targets mandatory as soon as all * test platforms have been updated. */ - int ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_NATIVE_SIZE, &pixelArrayArea_); - if (ret) { + std::optional rect; + int ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_NATIVE_SIZE, &rect); + if (ret) + return ret; + if (!rect) { /* * Default the pixel array size to the largest size supported * by the sensor. The sizes_ vector is sorted in ascending * order, the largest size is thus the last element. */ pixelArrayArea_ = Rectangle(sizes_.back()); LOG(CameraSensor, Warning) << "The PixelArrayArea property has been defaulted to " << pixelArrayArea_; err = -EINVAL; + } else { + pixelArrayArea_ = *rect; } - ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP_BOUNDS, &readableArea_); - if (ret) { + ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP_BOUNDS, &rect); + if (ret) + return ret; + if (!rect) { readableArea_ = pixelArrayArea_; LOG(CameraSensor, Warning) << "The PixelArrayReadableArea property has been defaulted to " << readableArea_; err = -EINVAL; + } else { + readableArea_ = *rect; } - ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP_DEFAULT, &activeArea_); - if (ret) { + ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP_DEFAULT, &rect); + if (ret) + return ret; + if (!rect) { activeArea_ = readableArea_; LOG(CameraSensor, Warning) << "The PixelArrayActiveAreas property has been defaulted to " << activeArea_; err = -EINVAL; + } else { + activeArea_ = *rect; } - Rectangle rect; ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP, &rect); - if (ret) { + if (ret) + return ret; + if (!rect) { LOG(CameraSensor, Warning) << "Failed to retrieve the sensor crop rectangle"; err = -EINVAL; } if (err) { LOG(CameraSensor, Warning) << "The sensor kernel driver needs to be fixed"; LOG(CameraSensor, Warning) << "See Documentation/sensor_driver_requirements.rst in the libcamera sources for more information"; } if (!bayerFormat_) return 0; /* * For raw sensors, make sure the sensor driver supports the controls * required by the CameraSensor class. */ static constexpr uint32_t mandatoryControls[] = { V4L2_CID_ANALOGUE_GAIN, V4L2_CID_EXPOSURE, V4L2_CID_HBLANK, V4L2_CID_PIXEL_RATE, V4L2_CID_VBLANK, }; err = 0; for (uint32_t ctrl : mandatoryControls) { if (!controls.count(ctrl)) { @@ -871,65 +886,70 @@ int CameraSensorLegacy::applyConfiguration(const SensorConfiguration &config, /* \todo Handle AnalogCrop. Most sensors do not support set_selection */ /* \todo Handle scaling in the digital domain. */ return 0; } V4L2Subdevice::Stream CameraSensorLegacy::imageStream() const { return { pad_, 0 }; } int CameraSensorLegacy::sensorInfo(IPACameraSensorInfo *info) const { if (!bayerFormat_) return -EINVAL; info->model = model(); info->pixelArrayArea = pixelArrayArea_; /* * The active area is a static property, while the crop rectangle needs * to be re-read as it depends on the sensor configuration. */ info->activeArea = activeArea_; /* * \todo Support for retrieving the crop rectangle is scheduled to * become mandatory. For the time being use the default value if it has * been initialized at sensor driver validation time. */ - int ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP, &info->analogCrop); - if (ret) { + std::optional crop; + int ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP, &crop); + if (ret) + return ret; + if (!crop) { info->analogCrop = activeArea_; LOG(CameraSensor, Warning) << "The analogue crop rectangle has been defaulted to the active area size"; + } else { + info->analogCrop = *crop; } /* The bit depth and image size depend on the currently applied format. */ V4L2SubdeviceFormat format{}; ret = subdev_->getFormat(pad_, &format); if (ret) return ret; info->bitsPerPixel = MediaBusFormatInfo::info(format.code).bitsPerPixel; info->outputSize = format.size; std::optional cfa = properties_.get(properties::draft::ColorFilterArrangement); info->cfaPattern = cfa ? *cfa : properties::draft::ColorFilterArrangementRGB; /* * Retrieve the pixel rate, line length and minimum/maximum frame * duration through V4L2 controls. Support for the V4L2_CID_PIXEL_RATE, * V4L2_CID_HBLANK and V4L2_CID_VBLANK controls is mandatory. */ static constexpr uint32_t cids[] = { V4L2_CID_PIXEL_RATE, V4L2_CID_HBLANK, V4L2_CID_VBLANK, }; ControlList ctrls = subdev_->getControls(cids); if (ctrls.empty()) { LOG(CameraSensor, Error) << "Failed to retrieve camera info controls"; return -EINVAL; } diff --git a/src/libcamera/sensor/camera_sensor_raw.cpp b/src/libcamera/sensor/camera_sensor_raw.cpp index 55b386ce..641801a1 100644 --- a/src/libcamera/sensor/camera_sensor_raw.cpp +++ b/src/libcamera/sensor/camera_sensor_raw.cpp @@ -397,92 +397,93 @@ std::optional CameraSensorRaw::init() /* Drop non-raw formats (in case we have a hybrid sensor). */ const MediaBusFormatInfo &info = MediaBusFormatInfo::info(code); if (info.colourEncoding != PixelFormatInfo::ColourEncodingRAW) continue; mbusCodes_.push_back(code); std::transform(ranges.begin(), ranges.end(), std::back_inserter(sizes_), [](const SizeRange &range) { return range.max; }); } if (mbusCodes_.empty()) { LOG(CameraSensor, Debug) << "No raw image formats found"; return { 0 }; } std::sort(mbusCodes_.begin(), mbusCodes_.end()); std::sort(sizes_.begin(), sizes_.end()); /* * Remove duplicate sizes. There are no duplicate media bus codes as * they are the keys in the formats map. */ auto last = std::unique(sizes_.begin(), sizes_.end()); sizes_.erase(last, sizes_.end()); /* * 3. Query selection rectangles. Retrieve properties, and verify that * all the expected selection rectangles are supported. */ - Rectangle rect; + std::optional rect; ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP_BOUNDS, &rect); - if (ret) { + if (ret || !rect) { LOG(CameraSensor, Error) << "No pixel array crop bounds"; - return { ret }; + return { ret ? ret : -EINVAL }; } /* * \todo Implement querying the physical sensor size based on pad 1, * stream 0. See * https://lore.kernel.org/linux-media/20260409201501.975242-23-sakari.ailus@linux.intel.com/ */ LOG(CameraSensor, Warning) << "PixelArrayArea might be incorrect"; pixelArrayArea_ = Rectangle(0, 0, - rect.x + rect.width, - rect.y + rect.height); + rect->x + rect->width, + rect->y + rect->height); - readableArea_ = rect; + readableArea_ = *rect; ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP_DEFAULT, - &activeArea_); - if (ret) { + &rect); + if (ret || !rect) { LOG(CameraSensor, Error) << "No pixel array crop default"; - return { ret }; + return { ret ? ret : -EINVAL }; } + activeArea_ = *rect; ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP, &rect); - if (ret) { + if (ret || !rect) { LOG(CameraSensor, Error) << "No pixel array crop rectangle"; - return { ret }; + return { ret ? ret : -EINVAL }; } /* * 4. Verify that all required controls are present. */ const ControlIdMap &controls = subdev_->controls().idmap(); static constexpr uint32_t mandatoryControls[] = { V4L2_CID_ANALOGUE_GAIN, V4L2_CID_CAMERA_ORIENTATION, V4L2_CID_EXPOSURE, V4L2_CID_HBLANK, V4L2_CID_PIXEL_RATE, V4L2_CID_VBLANK, }; ret = 0; for (uint32_t ctrl : mandatoryControls) { if (!controls.count(ctrl)) { LOG(CameraSensor, Error) << "Mandatory V4L2 control " << utils::hex(ctrl) << " not available"; ret = -EINVAL; } } if (ret) { LOG(CameraSensor, Error) @@ -987,64 +988,70 @@ int CameraSensorRaw::setEmbeddedDataEnabled(bool enable) * source stream, as there would be something seriously wrong * otherwise. */ bool enabled = false; for (const V4L2Subdevice::Route &route : routing) { if (route.source != streams_.edata->source) continue; enabled = route.flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE; break; } if (enabled != enable) return enabled ? -EISCONN : -ENOSTR; return 0; } int CameraSensorRaw::sensorInfo(IPACameraSensorInfo *info) const { info->model = model(); info->pixelArrayArea = pixelArrayArea_; /* * The active area is a static property, while the crop rectangle needs * to be re-read as it depends on the sensor configuration. */ info->activeArea = activeArea_; + std::optional crop; int ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP, - &info->analogCrop); + &crop); if (ret) return ret; + if (!crop) { + LOG(CameraSensor, Error) << "No pixel array crop rectangle"; + return -EINVAL; + } + info->analogCrop = *crop; /* The bit depth and image size depend on the currently applied format. */ V4L2SubdeviceFormat format{}; ret = subdev_->getFormat(streams_.image.source, &format); if (ret) return ret; info->bitsPerPixel = MediaBusFormatInfo::info(format.code).bitsPerPixel; info->outputSize = format.size; std::optional cfa = properties_.get(properties::draft::ColorFilterArrangement); info->cfaPattern = cfa ? *cfa : properties::draft::ColorFilterArrangementRGB; /* * Retrieve the pixel rate, line length and minimum/maximum frame * duration through V4L2 controls. Support for the V4L2_CID_PIXEL_RATE, * V4L2_CID_HBLANK and V4L2_CID_VBLANK controls is mandatory. */ static constexpr uint32_t cids[] = { V4L2_CID_PIXEL_RATE, V4L2_CID_HBLANK, V4L2_CID_VBLANK, }; ControlList ctrls = subdev_->getControls(cids); if (ctrls.empty()) { LOG(CameraSensor, Error) << "Failed to retrieve camera info controls"; return -EINVAL; } diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp index a69de154..0ab9d9ff 100644 --- a/src/libcamera/v4l2_subdevice.cpp +++ b/src/libcamera/v4l2_subdevice.cpp @@ -1082,163 +1082,181 @@ std::ostream &operator<<(std::ostream &out, const V4L2Subdevice::Route &route) /** * \typedef V4L2Subdevice::Routing * \brief V4L2 subdevice routing table * * This class stores a subdevice routing table as a vector of routes. */ /** * \brief Insert a text representation of a V4L2Subdevice::Routing into an * output stream * \param[in] out The output stream * \param[in] routing The V4L2Subdevice::Routing * \return The output stream \a out */ std::ostream &operator<<(std::ostream &out, const V4L2Subdevice::Routing &routing) { for (const auto &[i, route] : utils::enumerate(routing)) { out << "[" << i << "] " << route; if (i != routing.size() - 1) out << ", "; } return out; } /** * \brief Create a V4L2 subdevice from a MediaEntity using its device node * path */ V4L2Subdevice::V4L2Subdevice(const MediaEntity *entity) - : V4L2Device(entity->deviceNode()), entity_(entity) + : V4L2Device(entity->deviceNode()), entity_(entity), caps_{} { } V4L2Subdevice::~V4L2Subdevice() { close(); } /** * \brief Open a V4L2 subdevice * \return 0 on success or a negative error code otherwise */ int V4L2Subdevice::open() { int ret = V4L2Device::open(O_RDWR); if (ret) return ret; /* * Try to query the subdev capabilities. The VIDIOC_SUBDEV_QUERYCAP API * was introduced in kernel v5.8, ENOTTY errors must be ignored to * support older kernels. */ caps_ = {}; ret = ioctl(VIDIOC_SUBDEV_QUERYCAP, &caps_); if (ret < 0 && errno != ENOTTY) { ret = -errno; LOG(V4L2, Error) << "Unable to query capabilities: " << strerror(-ret); return ret; } /* If the subdev supports streams, enable the streams API. */ if (caps_.hasStreams()) { struct v4l2_subdev_client_capability clientCaps{}; clientCaps.capabilities = V4L2_SUBDEV_CLIENT_CAP_STREAMS; ret = ioctl(VIDIOC_SUBDEV_S_CLIENT_CAP, &clientCaps); if (ret < 0) { ret = -errno; LOG(V4L2, Error) << "Unable to set client capabilities: " << strerror(-ret); return ret; } } return 0; } /** * \fn V4L2Subdevice::entity() * \brief Retrieve the media entity associated with the subdevice * \return The subdevice's associated media entity. */ /** * \brief Get selection rectangle \a rect for \a target * \param[in] stream The stream the rectangle is retrieved from * \param[in] target The selection target defined by the V4L2_SEL_TGT_* flags - * \param[out] rect The retrieved selection rectangle + * \param[out] rect The retrieved selection rectangle, if available + * + * If the ioctl returns EINVAL or ENOTTY, no rectangle is reported and \a rect + * is empty. EINVAL can indicate an unsupported target or unavailable stream + * state; an empty result does not distinguish these cases. + * + * Invalid pad indices and nonzero streams on devices without streams support + * are rejected with -EINVAL before issuing the ioctl. All other ioctl errors + * are returned unchanged. The output \a rect is cleared on entry. * * \todo Define a V4L2SelectionTarget enum for the selection target * - * \return 0 on success or a negative error code otherwise + * \return 0 if the rectangle was retrieved or is unavailable, or a negative + * error code otherwise */ int V4L2Subdevice::getSelection(const Stream &stream, unsigned int target, - Rectangle *rect) + std::optional *rect) { + rect->reset(); + + if (stream.pad >= entity_->pads().size() || + (stream.stream && !caps_.hasStreams())) { + LOG(V4L2, Error) << "Invalid selection stream " << stream; + return -EINVAL; + } + struct v4l2_subdev_selection sel = {}; sel.which = V4L2_SUBDEV_FORMAT_ACTIVE; sel.pad = stream.pad; sel.stream = stream.stream; sel.target = target; sel.flags = 0; int ret = ioctl(VIDIOC_SUBDEV_G_SELECTION, &sel); if (ret < 0) { + if (ret == -EINVAL || ret == -ENOTTY) + return 0; + LOG(V4L2, Error) << "Unable to get rectangle " << target << " on pad " << stream << ": " << strerror(-ret); return ret; } - rect->x = sel.r.left; - rect->y = sel.r.top; - rect->width = sel.r.width; - rect->height = sel.r.height; + *rect = Rectangle(sel.r.left, sel.r.top, sel.r.width, sel.r.height); return 0; } /** * \fn V4L2Subdevice::getSelection(unsigned int pad, unsigned int target, - * Rectangle *rect) + * std::optional *rect) * \brief Get selection rectangle \a rect for \a target * \param[in] pad The 0-indexed pad number the rectangle is retrieved from * \param[in] target The selection target defined by the V4L2_SEL_TGT_* flags - * \param[out] rect The retrieved selection rectangle + * \param[out] rect The retrieved selection rectangle, if available * - * \return 0 on success or a negative error code otherwise + * \return 0 if the rectangle was retrieved or is unavailable, or a negative + * error code otherwise */ /** * \brief Set selection rectangle \a rect for \a target * \param[in] stream The stream the rectangle is to be applied to * \param[in] target The selection target defined by the V4L2_SEL_TGT_* flags * \param[inout] rect The selection rectangle to be applied * * \todo Define a V4L2SelectionTarget enum for the selection target * * \return 0 on success or a negative error code otherwise */ int V4L2Subdevice::setSelection(const Stream &stream, unsigned int target, Rectangle *rect) { struct v4l2_subdev_selection sel = {}; sel.which = V4L2_SUBDEV_FORMAT_ACTIVE; sel.pad = stream.pad; sel.stream = stream.stream; sel.target = target; sel.flags = 0; sel.r.left = rect->x; sel.r.top = rect->y; sel.r.width = rect->width; sel.r.height = rect->height; int ret = ioctl(VIDIOC_SUBDEV_S_SELECTION, &sel); if (ret < 0) {