From patchwork Thu Oct 29 13:25:35 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ray Strode X-Patchwork-Id: 28691 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 9E1E4C324C for ; Thu, 29 Oct 2026 13:25:51 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id ACBE56909B; Thu, 29 Oct 2026 14:25:50 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="hjt9Qz4W"; dkim-atps=neutral Received: from mail-ua1-x936.google.com (mail-ua1-x936.google.com [IPv6:2607:f8b0:4864:20::936]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0A19D68E07 for ; Thu, 29 Oct 2026 14:25:49 +0100 (CET) Received: by mail-ua1-x936.google.com with SMTP id a1e0cc1a2514c-9906870a9baso1121029241.0 for ; Thu, 29 Oct 2026 06:25:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793280348; x=1793885148; 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=IT9uDOPub1KjnVH/sviAaKH/NTIE86rfQJHHor+6R9w=; b=hjt9Qz4WjOSZCsUqMu+oh4HmGjwpwgSLflqW5k7JQ6g+7QvwbdimigP+bDw9qJtmyK U515VC01sjsKTR03fK3YsaL11IpazeIBX+qyDaSP/Aq9ia2Ay6nvHtK6wukfFXvjirIt u4tQsP3Xdm6/hQxa1P4hwNkvpG/FsmaTMWz+Ny6aOs4XAyRV/dqkpeBQm1YYHhnoAJv+ cXfUis3k8f5k1O43Z5EXD0lV40bZ9x9n2fo7oLDs2RJKrFarhPAxGZxiaxwMIqzRGFpw /N6AoXf+Ab1HqFn5jlfB3tI8qJUKUZshBS/7YWkJ4J/hqhRo+MjDvttK2aliMfflfl3d W1aw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793280348; x=1793885148; 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=IT9uDOPub1KjnVH/sviAaKH/NTIE86rfQJHHor+6R9w=; b=eEWhGyyoAkaTB4ikxHzZS+wYQN1eIW9Y9BILAzhQABv0UlqC53zSviuOkMh/GmBWeg WjjQr+uQJIPXbpPM27gc1ScIqKxsSxywGwBHscx7TAmKCeyYsR94p1WQ0QBinKH6O9z/ j8L0IE6zl9Yp1O9lGAPSwK6loswdwgoKdnYgzWSv5ogdoMpZZi9SXN8Mh1pc6P9CeOt1 bJ8tJeSZohcNYntRuv6hXxsm5gt8gXE0jzOAAbZakPc++cQ/sl+McCnZWlKJfNxLT9rb SM7CEJWz4NjDwTLwS4c0ErylEypROCs3TFNXexAleFSkqyw6aY2uldiyIoohg6eLPVTj XtmA== X-Gm-Message-State: AFq9FYKHUSqQzC/ytZ5QdwymKAuwfQj4r7sg2svx+QdZQIsKdNKS+o7L PV0QsBBuKedlH2ZNCWO//WkKrYckPLi3RFXd3GgwD05R60EYhs3nMma1JduWH+htTXY= X-Gm-Gg: AYBFou1TmsD5N/4hu0O6XJAVODbowIU5kgpQFR/LRcqQ45xAEeMF4JOo+BUMbGH0G/n Z2k2YFcP8CBaGqYMUh8M2HZSpDdqWm3J4hGNCWstXkSDvcBK2etGrTVvqnQlBHW3snNRBjHVjzh eIWNSHnH9K5DwIoot5LdeevOzmvWdo3DI8n3GmE3qxKek+lppzrM65GEws39klm+Z6URhs6Kqox gDwQfK/C+1m3si5Qa0634CnMzuo4xIONSEML3fa+PDwaRCut8vNlypsKxcTgiuj/9RKCXAQVLuP i9HFcboa5VL94ZUDCqn85vHD2gUX4avu469PtPpG9KMB0OdrpfdeT4eM/NyxDTsTtpR3y2I5Cau O4UiaZRGOrOyA6chwEjR+wAEPuqGGkRhPUODEchNOWJvtxwazTYe1eN/giZhm8PcUASTAmcmHjz YwNXv7pZOsA5REBw5ZwdgvJGIJZESwovrqI9rKPaTPr4nUkKEqemBI1XjIz8ij3DdQcZ/DhpCil ncc4U3+JsfrkwMI5wvFQ+WNhzd+cfZn1bex5nqWhKG6eeReiIS0AfuEwl84PsrJkOuV4phikpG+ tNAV0jsK6VZ4NrxMgX7Snvf1FhbE+MRRlrrnQfPYTbKGsVgZB+JSn7UhKA== X-Received: by 2002:a05:6102:4b13:b0:7af:fef2:8ecf with SMTP id ada2fe7eead31-7d03a434b91mr2972612137.8.1793280347453; Thu, 29 Oct 2026 06:25:47 -0700 (PDT) Received: from localhost ([144.121.52.163]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-991259f3b23sm9764368241.9.2026.10.29.06.25.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Oct 2026 06:25:46 -0700 (PDT) From: Ray Strode To: libcamera-devel@lists.libcamera.org Cc: kieran.bingham@ideasonboard.com, bod.linux@nxsw.ie, laurent.pinchart@ideasonboard.com Subject: [PATCH v4 1/1] libcamera: v4l2_subdevice: Return optional selection rectangles Date: Thu, 29 Oct 2026 09:25:35 -0400 Message-ID: <20261029132535.369555-1-halfline@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: References: 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. Return 0 and an empty optional for EINVAL or ENOTTY from G_SELECTION, and propagate other ioctl errors. Clear the output on entry so a missing rectangle or failed query cannot leave a previous result behind. Reject invalid pads and nonzero streams on devices without streams support before issuing the ioctl. Initialize the cached capabilities so this check is also safe before open(). EINVAL from the driver still means only that no rectangle was reported for the current configuration. Update callers to 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(-) base-commit: 6e19ca0fa7aa75aa965511072f43b5e50dec1904 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) {