[v1] virtual: Implement framerate handling
diff mbox series

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

Commit Message

Robert Mader Oct. 9, 2026, 7:32 p.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 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 | 35 ++++++++++++++++++----
 src/libcamera/pipeline/virtual/virtual.h   |  1 +
 2 files changed, 31 insertions(+), 5 deletions(-)

Comments

Robert Mader Oct. 9, 2026, 7:54 p.m. UTC | #1
There's a CI build failure on Debian 12, see below

On 09.10.26 21:32, Robert Mader wrote:
> 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 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 | 35 ++++++++++++++++++----
>   src/libcamera/pipeline/virtual/virtual.h   |  1 +
>   2 files changed, 31 insertions(+), 5 deletions(-)
>
> diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp
> index e25dd85a7391..8d63a39caca2 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,22 @@ void VirtualCameraData::processRequest(Request *request)
>   					    stream->configuration().size, buffer))
>   					fmd.status = FrameMetadata::Status::FrameError;
>   
> -				bufferCompleted.emit(buffer);
> +				buffers.push_back(buffer);
>   				break;
>   			}
>   		}
>   		ASSERT(found);
>   	}
> +
> +	uint64_t now = currentTimestamp();
> +	if (now < static_cast<uint64_t>(sensorTimestamp)) {
> +		std::chrono::nanoseconds sleep(sensorTimestamp - now);
> +		LOG(Virtual, Debug) << "Sleeping for ns: " << sleep;
|
../src/libcamera/pipeline/virtual/virtual.cpp:167:46: error: invalid 
operands to binary expression ('basic_ostream<char, 
std::char_traits<char>>' and 'std::chrono::nanoseconds' (aka 
'duration<long long, ratio<1LL, 1000000000LL>>'))
LOG(Virtual, Debug) << "Sleeping for ns: " << sleep;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^ ~~~~~
/usr/lib/llvm-14/bin/../include/c++/v1/cstddef:143:3: note: candidate 
function template not viable: no known conversion from 
'basic_ostream<char, std::char_traits<char>>' to 'std::byte' for 1st 
argument
operator<< (byte __lhs, _Integer __shift) noexcept
|
> +		std::this_thread::sleep_for(sleep);
> +	}
> +
> +	for (auto buffer : buffers)
> +		bufferCompleted.emit(buffer);
>   }
>   
>   VirtualCameraConfiguration::VirtualCameraConfiguration(VirtualCameraData *data)
> @@ -349,18 +362,30 @@ 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();
> +	uint64_t timestamp = currentTimestamp();
> +	uint64_t minFrameDurationNsec =
> +		camera->controls().find(&controls::FrameDurationLimits)->second.min().get<int64_t>() * 1000;
> +
> +	if (data->lastTimestamp) {
> +		if (((timestamp - data->lastTimestamp) < minFrameDurationNsec) ||
> +		    (timestamp < data->lastTimestamp))
> +			timestamp = data->lastTimestamp + minFrameDurationNsec;
> +	} else {
> +		timestamp += minFrameDurationNsec;
> +	}
>   
>   	request->_d()->metadata().set(controls::SensorTimestamp, timestamp);
> +	data->lastTimestamp = 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 */

Patch
diff mbox series

diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp
index e25dd85a7391..8d63a39caca2 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,22 @@  void VirtualCameraData::processRequest(Request *request)
 					    stream->configuration().size, buffer))
 					fmd.status = FrameMetadata::Status::FrameError;
 
-				bufferCompleted.emit(buffer);
+				buffers.push_back(buffer);
 				break;
 			}
 		}
 		ASSERT(found);
 	}
+
+	uint64_t now = currentTimestamp();
+	if (now < static_cast<uint64_t>(sensorTimestamp)) {
+		std::chrono::nanoseconds sleep(sensorTimestamp - now);
+		LOG(Virtual, Debug) << "Sleeping for ns: " << sleep;
+		std::this_thread::sleep_for(sleep);
+	}
+
+	for (auto buffer : buffers)
+		bufferCompleted.emit(buffer);
 }
 
 VirtualCameraConfiguration::VirtualCameraConfiguration(VirtualCameraData *data)
@@ -349,18 +362,30 @@  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();
+	uint64_t timestamp = currentTimestamp();
+	uint64_t minFrameDurationNsec =
+		camera->controls().find(&controls::FrameDurationLimits)->second.min().get<int64_t>() * 1000;
+
+	if (data->lastTimestamp) {
+		if (((timestamp - data->lastTimestamp) < minFrameDurationNsec) ||
+		    (timestamp < data->lastTimestamp))
+			timestamp = data->lastTimestamp + minFrameDurationNsec;
+	} else {
+		timestamp += minFrameDurationNsec;
+	}
 
 	request->_d()->metadata().set(controls::SensorTimestamp, timestamp);
+	data->lastTimestamp = 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 */