From patchwork Wed Dec 10 13:37:03 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 25455 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 7CBB4C326B for ; Wed, 10 Dec 2025 13:37:13 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7876A61463; Wed, 10 Dec 2025 14:37:12 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="YL7R62NM"; 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 7FA7E6143E for ; Wed, 10 Dec 2025 14:37:10 +0100 (CET) Received: from pb-laptop.local (185.221.143.114.nat.pool.zt.hu [185.221.143.114]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3AF7210C4 for ; Wed, 10 Dec 2025 14:37:07 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1765373827; bh=cubgMvdtQ+KzRQ8LxYpxreucREysBuJ9Xb5azFQkSMI=; h=From:To:Subject:Date:In-Reply-To:References:From; b=YL7R62NMTtwW7lNcPU5dfuqh0QBJdPEfzRGXvkG2qsYaMkWxrYgA48fP+5yIluzcX v6uzWaMMWRka1zV7Spi/2iJC0pdkti9F06/8VB5kDqxw2c4XIrBZWJ+l1dueQ8Qado ePlBelMQ39gsRoOL3mjoPtJXPKKz5gGPvc8qFBww= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v1 1/2] libcamera: pipeline: uvcvideo: Report `FrameDuration` Date: Wed, 10 Dec 2025 14:37:03 +0100 Message-ID: <20251210133704.2711629-2-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20251210133704.2711629-1-barnabas.pocze@ideasonboard.com> References: <20251210133704.2711629-1-barnabas.pocze@ideasonboard.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" If available, query the time-per-frame parameter from the device after starting, and report it in the metadata. The reported frame duration remains constant during a particular streaming session, and will most likely not accurately reflect the actual frame duration for any given frame, depending on the manual exposure time, or if `V4L2_CID_EXPOSURE_AUTO_PRIORITY` or similar is in effect. But at least it shows the "intended" frame duration. Signed-off-by: Barnabás Pőcze --- include/libcamera/internal/v4l2_videodevice.h | 2 + src/libcamera/pipeline/uvcvideo/uvcvideo.cpp | 13 +++++ src/libcamera/v4l2_videodevice.cpp | 55 +++++++++++++++++++ 3 files changed, 70 insertions(+) diff --git a/include/libcamera/internal/v4l2_videodevice.h b/include/libcamera/internal/v4l2_videodevice.h index 57db0036db..82d98184ed 100644 --- a/include/libcamera/internal/v4l2_videodevice.h +++ b/include/libcamera/internal/v4l2_videodevice.h @@ -208,6 +208,8 @@ public: int setFormat(V4L2DeviceFormat *format); Formats formats(uint32_t code = 0); + int getFrameInterval(std::chrono::microseconds *interval); + int getSelection(unsigned int target, Rectangle *rect); int setSelection(unsigned int target, Rectangle *rect); diff --git a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp index cb8cc82dff..3f98e8ece0 100644 --- a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp +++ b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp @@ -60,6 +60,7 @@ public: std::optional autoExposureMode_; std::optional manualExposureMode_; + std::optional timePerFrame_; private: bool generateId(); @@ -295,6 +296,8 @@ int PipelineHandlerUVC::start(Camera *camera, const ControlList *controls) UVCCameraData *data = cameraData(camera); unsigned int count = data->stream_.configuration().bufferCount; + data->timePerFrame_.reset(); + int ret = data->video_->importBuffers(count); if (ret < 0) return ret; @@ -309,6 +312,13 @@ int PipelineHandlerUVC::start(Camera *camera, const ControlList *controls) if (ret < 0) goto err_release_buffers; + if (!data->timePerFrame_) { + std::chrono::microseconds interval; + ret = data->video_->getFrameInterval(&interval); + if (ret == 0) + data->timePerFrame_ = interval; + } + return 0; err_release_buffers: @@ -898,6 +908,9 @@ void UVCCameraData::imageBufferReady(FrameBuffer *buffer) request->metadata().set(controls::SensorTimestamp, buffer->metadata().timestamp); + if (timePerFrame_) + request->metadata().set(controls::FrameDuration, timePerFrame_->count()); + pipe()->completeBuffer(request, buffer); pipe()->completeRequest(request); } diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp index 25b61d049a..3836dabef3 100644 --- a/src/libcamera/v4l2_videodevice.cpp +++ b/src/libcamera/v4l2_videodevice.cpp @@ -1147,6 +1147,61 @@ V4L2VideoDevice::Formats V4L2VideoDevice::formats(uint32_t code) return formats; } +namespace { + +std::chrono::microseconds +v4l2FractionToMs(const v4l2_fract &f) +{ + auto seconds = std::chrono::duration(f.numerator) / f.denominator; + return std::chrono::duration_cast(seconds); +} + +} + +/** + * \brief Retrieve the frame interval set on the V4L2 video device + * \param[out] interval The frame interval applied on the device + * + * Retrieve the current time-per-frame parameter from the device. + * + * \return 0 on success or a negative error code otherwise + */ +int V4L2VideoDevice::getFrameInterval(std::chrono::microseconds *interval) +{ + const v4l2_fract *frameInterval = nullptr; + v4l2_streamparm sparm = {}; + uint32_t caps = 0; + + sparm.type = bufferType_; + + int ret = ioctl(VIDIOC_G_PARM, &sparm); + if (ret) + return ret; + + switch (sparm.type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + frameInterval = &sparm.parm.capture.timeperframe; + caps = sparm.parm.capture.capability; + break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + frameInterval = &sparm.parm.output.timeperframe; + caps = sparm.parm.output.capability; + break; + } + + if (!frameInterval) + return -EINVAL; + + if (!(caps & V4L2_CAP_TIMEPERFRAME)) + return -ENOTSUP; + + *interval = v4l2FractionToMs(*frameInterval); + + return 0; +} + std::vector V4L2VideoDevice::enumPixelformats(uint32_t code) { std::vector formats;