[v3] virtual: Implement framerate handling
diff mbox series

Message ID 20261013102534.45147-1-robert.mader@collabora.com
State New
Headers show
Series
  • [v3] virtual: Implement framerate handling
Related show

Commit Message

Robert Mader Oct. 13, 2026, 10:25 a.m. UTC
Throttle requests to the minimal frame duration in order to honor the
configured framerates. Client-requested values are ignored for now.

Signed-off-by: Robert Mader <robert.mader@collabora.com>

---

Changes in v3:
 - Removed a leftover debug change

Changes in v2:
 - Remove debug log in order to avoid CI issues.
 - Simplify code in queueRequestDevice(), hopefully making it easier to
   follow.

Changes in v1 (from RFC version):
 - Use std::this_thread::sleep_for() instead usleep() (Barnabás)
 - Sleep only once per request, after generating all frames
 - Remove control handling in start() (defer that for later)
 - Reset state in stop() instead of start()
---
 src/libcamera/pipeline/virtual/virtual.cpp | 42 ++++++++++++++++++----
 src/libcamera/pipeline/virtual/virtual.h   |  1 +
 2 files changed, 37 insertions(+), 6 deletions(-)

Patch
diff mbox series

diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp
index e25dd85a7391..408923234979 100644
--- a/src/libcamera/pipeline/virtual/virtual.cpp
+++ b/src/libcamera/pipeline/virtual/virtual.cpp
@@ -130,6 +130,9 @@  VirtualCameraData::VirtualCameraData(PipelineHandler *pipe,
 
 void VirtualCameraData::processRequest(Request *request)
 {
+	int64_t sensorTimestamp = *request->metadata().get(controls::SensorTimestamp);
+	std::vector<FrameBuffer *> buffers;
+
 	for (const auto &[stream, buffer] : request->buffers()) {
 		bool found = false;
 		/* map buffer and fill test patterns */
@@ -139,7 +142,7 @@  void VirtualCameraData::processRequest(Request *request)
 
 				fmd.status = FrameMetadata::Status::FrameSuccess;
 				fmd.sequence = streamConfig.seq++;
-				fmd.timestamp = currentTimestamp();
+				fmd.timestamp = sensorTimestamp;
 
 				std::span<const FrameBuffer::Plane> planes = buffer->planes();
 				for (const auto [i, p] : utils::enumerate(planes))
@@ -151,12 +154,21 @@  void VirtualCameraData::processRequest(Request *request)
 					    stream->configuration().size, buffer))
 					fmd.status = FrameMetadata::Status::FrameError;
 
-				bufferCompleted.emit(buffer);
+				buffers.push_back(buffer);
 				break;
 			}
 		}
 		ASSERT(found);
 	}
+
+	const uint64_t now = currentTimestamp();
+	if (now < static_cast<uint64_t>(sensorTimestamp)) {
+		std::chrono::nanoseconds duration(sensorTimestamp - now);
+		std::this_thread::sleep_for(duration);
+	}
+
+	for (auto buffer : buffers)
+		bufferCompleted.emit(buffer);
 }
 
 VirtualCameraConfiguration::VirtualCameraConfiguration(VirtualCameraData *data)
@@ -349,18 +361,36 @@  void PipelineHandlerVirtual::stopDevice(Camera *camera)
 	/* Process pending `bufferCompleted` signals. */
 	thread()->dispatchMessages(Message::Type::InvokeMessage, this);
 	data->bufferCompleted.disconnect(this);
+	data->lastTimestamp = 0;
 
 	while (!data->queuedRequests_.empty())
 		cancelRequest(data->queuedRequests_.front());
 }
 
-int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera,
-					       Request *request)
+int PipelineHandlerVirtual::queueRequestDevice(Camera *camera, Request *request)
 {
 	VirtualCameraData *data = cameraData(camera);
-	const auto timestamp = currentTimestamp();
+	const uint64_t now = currentTimestamp();
+	uint64_t minFrameDurationNsec;
+	uint64_t targetTimestamp;
+
+	auto frameDurationLimits = camera->controls().find(&controls::FrameDurationLimits);
+	if (frameDurationLimits != camera->controls().end()) {
+		minFrameDurationNsec =
+			frameDurationLimits->second.min().get<int64_t>() * 1000;
+	} else {
+		/* Default to 30 FPS */
+		minFrameDurationNsec = 33333 * 1000;
+	}
+
+	targetTimestamp = data->lastTimestamp + minFrameDurationNsec;
+	if (targetTimestamp < now)
+		targetTimestamp = now;
+	data->lastTimestamp = targetTimestamp;
+
+	request->_d()->metadata().set(controls::SensorTimestamp,
+				      targetTimestamp);
 
-	request->_d()->metadata().set(controls::SensorTimestamp, timestamp);
 	data->invokeMethod(&VirtualCameraData::processRequest,
 			   ConnectionTypeQueued, request);
 
diff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h
index 215e56fa3b9d..84b53dfb66f3 100644
--- a/src/libcamera/pipeline/virtual/virtual.h
+++ b/src/libcamera/pipeline/virtual/virtual.h
@@ -66,6 +66,7 @@  public:
 
 	std::vector<StreamConfig> streamConfigs_;
 	Signal<FrameBuffer *> bufferCompleted;
+	uint64_t lastTimestamp = 0;
 };
 
 } /* namespace libcamera */