[RFC PATCH 3/3] libcamera: v4l2: Add wallclock metadata to video devices
David Plowman
david.plowman at raspberrypi.com
Tue Nov 26 13:17:06 CET 2024
FrameMetadata is extended to include wallclock timestamps, both
"raw" (unsmoothed) and de-jittered versions.
The "raw" wallclock timestamps are recorded when frames start, and at
frame end we generate a de-jittered version of this wallclock, handing
both out to pipeline handlers.
Signed-off-by: David Plowman <david.plowman at raspberrypi.com>
---
include/libcamera/framebuffer.h | 2 ++
include/libcamera/internal/v4l2_device.h | 4 +++
include/libcamera/internal/v4l2_videodevice.h | 3 ++
src/libcamera/v4l2_device.cpp | 22 ++++++++++++++
src/libcamera/v4l2_videodevice.cpp | 29 +++++++++++++++++++
5 files changed, 60 insertions(+)
diff --git a/include/libcamera/framebuffer.h b/include/libcamera/framebuffer.h
index ff839243..a181d97a 100644
--- a/include/libcamera/framebuffer.h
+++ b/include/libcamera/framebuffer.h
@@ -35,6 +35,8 @@ struct FrameMetadata {
Status status;
unsigned int sequence;
uint64_t timestamp;
+ uint64_t wallClockRaw;
+ uint64_t wallClock;
Span<Plane> planes() { return planes_; }
Span<const Plane> planes() const { return planes_; }
diff --git a/include/libcamera/internal/v4l2_device.h b/include/libcamera/internal/v4l2_device.h
index f5aa5024..90dbc4a8 100644
--- a/include/libcamera/internal/v4l2_device.h
+++ b/include/libcamera/internal/v4l2_device.h
@@ -10,6 +10,7 @@
#include <map>
#include <memory>
#include <optional>
+#include <queue>
#include <vector>
#include <linux/videodev2.h>
@@ -67,6 +68,9 @@ protected:
template<typename T>
static int fromColorSpace(const std::optional<ColorSpace> &colorSpace, T &v4l2Format);
+ std::queue<std::pair<uint64_t, uint64_t>> wallClockQueue_;
+ bool frameStartEnabled() const { return frameStartEnabled_; }
+
private:
static ControlType v4l2CtrlType(uint32_t ctrlType);
static std::unique_ptr<ControlId> v4l2ControlId(const v4l2_query_ext_ctrl &ctrl);
diff --git a/include/libcamera/internal/v4l2_videodevice.h b/include/libcamera/internal/v4l2_videodevice.h
index f021c2a0..d6127928 100644
--- a/include/libcamera/internal/v4l2_videodevice.h
+++ b/include/libcamera/internal/v4l2_videodevice.h
@@ -31,6 +31,7 @@
#include <libcamera/geometry.h>
#include <libcamera/pixel_format.h>
+#include "libcamera/internal/clock_recovery.h"
#include "libcamera/internal/formats.h"
#include "libcamera/internal/v4l2_device.h"
#include "libcamera/internal/v4l2_pixelformat.h"
@@ -290,6 +291,8 @@ private:
Timer watchdog_;
utils::Duration watchdogDuration_;
+
+ ClockRecovery wallClockRecovery_;
};
class V4L2M2MDevice
diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
index 7d21cf15..285527b4 100644
--- a/src/libcamera/v4l2_device.cpp
+++ b/src/libcamera/v4l2_device.cpp
@@ -474,6 +474,11 @@ int V4L2Device::ioctl(unsigned long request, void *argp)
* \return The V4L2 device file descriptor, -1 if the device node is not open
*/
+/**
+ * \fn V4L2Device::frameStartEnabled()
+ * \return True if frame start notifications are enabled, otherwise false
+ */
+
/**
* \brief Retrieve the libcamera control type associated with the V4L2 control
* \param[in] ctrlType The V4L2 control type
@@ -761,6 +766,23 @@ void V4L2Device::eventAvailable()
return;
}
+ /*
+ * Record this frame (by its sequence number) and its corresponding wallclock value.
+ * Use a queue as these two events may not interleave perfectly.
+ */
+ auto now = std::chrono::system_clock::now();
+ uint64_t wallClock = std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch()).count();
+
+ wallClockQueue_.emplace(event.u.frame_sync.frame_sequence, wallClock);
+
+ /*
+ * Also avoid growing the queue indefiniteily. It seems highly unlikely that you could
+ * get more than a few "frame starts" being processed without a "frame end", so the value
+ * 5, while arbitrary, appears to be more than enough in practice.
+ */
+ while (wallClockQueue_.size() > 5)
+ wallClockQueue_.pop();
+
frameStart.emit(event.u.frame_sync.frame_sequence);
}
diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp
index 14eba056..5c5dfc79 100644
--- a/src/libcamera/v4l2_videodevice.cpp
+++ b/src/libcamera/v4l2_videodevice.cpp
@@ -1865,6 +1865,33 @@ FrameBuffer *V4L2VideoDevice::dequeueBuffer()
metadata.timestamp = buf.timestamp.tv_sec * 1000000000ULL
+ buf.timestamp.tv_usec * 1000ULL;
+ if (frameStartEnabled()) {
+ /*
+ * Find the wallclock that should have been recorded for this frame, discarding any
+ * stale frames on the way.
+ */
+ while (!wallClockQueue_.empty() && wallClockQueue_.front().first < buf.sequence)
+ wallClockQueue_.pop();
+
+ if (!wallClockQueue_.empty() && wallClockQueue_.front().first == buf.sequence) {
+ metadata.wallClockRaw = wallClockQueue_.front().second;
+ wallClockQueue_.pop();
+ } else {
+ /*
+ * At higher framerates it can happen that this gets handled before the frame
+ * start event, meaning there's no wallclock time in the queue. So the best we
+ * can do is sample the wallclock now. (The frame start will subsequently add
+ * another wallclock timestamp, but this will get safely discarded.)
+ */
+ auto now = std::chrono::system_clock::now();
+ metadata.wallClockRaw = std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch()).count();
+ }
+
+ /* Now recover a de-jittered wallclock value. Everything must be in microseconds. */
+ wallClockRecovery_.addSample(metadata.timestamp / 1000, metadata.wallClockRaw);
+ metadata.wallClock = wallClockRecovery_.getOutput(metadata.timestamp / 1000);
+ }
+
if (V4L2_TYPE_IS_OUTPUT(buf.type))
return buffer;
@@ -1958,6 +1985,8 @@ int V4L2VideoDevice::streamOn()
firstFrame_.reset();
+ wallClockQueue_ = {};
+
ret = ioctl(VIDIOC_STREAMON, &bufferType_);
if (ret < 0) {
LOG(V4L2, Error)
--
2.39.5
More information about the libcamera-devel
mailing list