| Message ID | 20261013204921.115407-1-robert.mader@collabora.com |
|---|---|
| State | Superseded |
| Headers | show |
| Series |
|
| Related | show |
Hi, On 13.10.26 22:49, Robert Mader wrote: > Throttle requests to the minimal frame duration in order to honor the > configured framerates. > > Signed-off-by: Robert Mader <robert.mader@collabora.com> > > --- > > Changes in v4: > - Calculate minFrameDuration_ once in start() using FDLs from camera > and, if present, from client provided controls. The later is not yet > tested. I just successfully tested it with the following Pipewire implementation: https://gitlab.freedesktop.org/pipewire/pipewire/-/merge_requests/3059 Various clients like Firefox and Snapshot request 30 FPS by default if no negotiation is possible. This patch, together with the PW MR above, make the virtual pipeline honor that, even if the config allows e.g. 60 FPS. What's missing though is checking against the maximum frame duration values - i.e. clients can now request values lower than what the config file allows. Will fix that in v5 > - Assert that the camera has FDLs set by the config parser. > - Set FrameDuration in request metadata. > > 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 | 53 ++++++++++++++++++---- > src/libcamera/pipeline/virtual/virtual.h | 3 ++ > 2 files changed, 48 insertions(+), 8 deletions(-) > > diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp > index e25dd85a7391..442a793f82f0 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) > @@ -323,11 +335,24 @@ int PipelineHandlerVirtual::exportFrameBuffers([[maybe_unused]] Camera *camera, > return dmaBufAllocator_.exportBuffers(config.bufferCount, planeSizes, buffers); > } > > -int PipelineHandlerVirtual::start([[maybe_unused]] Camera *camera, > - [[maybe_unused]] const ControlList *controls) > +int PipelineHandlerVirtual::start(Camera *camera, const ControlList *controls) > { > VirtualCameraData *data = cameraData(camera); > > + auto cameraFrameDurationLimits = > + camera->controls().find(&controls::FrameDurationLimits); > + ASSERT(cameraFrameDurationLimits != camera->controls().end()); > + data->minFrameDuration_ = > + cameraFrameDurationLimits->second.min().get<int64_t>(); > + > + if (controls) { > + if (auto clientFrameDurationLimits = > + controls->get(controls::FrameDurationLimits)) { > + data->minFrameDuration_ = std::max(data->minFrameDuration_, > + clientFrameDurationLimits->front()); > + } > + } > + > for (auto &s : data->streamConfigs_) > s.seq = 0; > > @@ -349,18 +374,30 @@ void PipelineHandlerVirtual::stopDevice(Camera *camera) > /* Process pending `bufferCompleted` signals. */ > thread()->dispatchMessages(Message::Type::InvokeMessage, this); > data->bufferCompleted.disconnect(this); > + data->minFrameDuration_ = 0; > + 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(); > > - request->_d()->metadata().set(controls::SensorTimestamp, timestamp); > + const uint64_t now = currentTimestamp(); > + uint64_t targetTimestamp = > + data->lastTimestamp_ + data->minFrameDuration_ * 1000; > + > + if (targetTimestamp < now) > + targetTimestamp = now; > + data->lastTimestamp_ = targetTimestamp; > + > + request->_d()->metadata().set(controls::SensorTimestamp, > + targetTimestamp); > + request->_d()->metadata().set(controls::FrameDuration, > + data->minFrameDuration_); > + > data->invokeMethod(&VirtualCameraData::processRequest, > ConnectionTypeQueued, request); > > diff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h > index 215e56fa3b9d..477f5b15a88f 100644 > --- a/src/libcamera/pipeline/virtual/virtual.h > +++ b/src/libcamera/pipeline/virtual/virtual.h > @@ -66,6 +66,9 @@ public: > > std::vector<StreamConfig> streamConfigs_; > Signal<FrameBuffer *> bufferCompleted; > + > + int64_t minFrameDuration_ = 0; > + uint64_t lastTimestamp_ = 0; > }; > > } /* namespace libcamera */
Quoting Robert Mader (2026-10-13 21:49:21) > Throttle requests to the minimal frame duration in order to honor the > configured framerates. > > Signed-off-by: Robert Mader <robert.mader@collabora.com> > > --- > > Changes in v4: > - Calculate minFrameDuration_ once in start() using FDLs from camera > and, if present, from client provided controls. The later is not yet > tested. > - Assert that the camera has FDLs set by the config parser. > - Set FrameDuration in request metadata. > > 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 | 53 ++++++++++++++++++---- > src/libcamera/pipeline/virtual/virtual.h | 3 ++ > 2 files changed, 48 insertions(+), 8 deletions(-) > > diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp > index e25dd85a7391..442a793f82f0 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) > @@ -323,11 +335,24 @@ int PipelineHandlerVirtual::exportFrameBuffers([[maybe_unused]] Camera *camera, > return dmaBufAllocator_.exportBuffers(config.bufferCount, planeSizes, buffers); > } > > -int PipelineHandlerVirtual::start([[maybe_unused]] Camera *camera, > - [[maybe_unused]] const ControlList *controls) > +int PipelineHandlerVirtual::start(Camera *camera, const ControlList *controls) > { > VirtualCameraData *data = cameraData(camera); > > + auto cameraFrameDurationLimits = > + camera->controls().find(&controls::FrameDurationLimits); > + ASSERT(cameraFrameDurationLimits != camera->controls().end()); As far as I can tell this is somewhat guaranteed by the time we get here, but no objection to it to make sure it stays that way. > + data->minFrameDuration_ = > + cameraFrameDurationLimits->second.min().get<int64_t>(); > + > + if (controls) { > + if (auto clientFrameDurationLimits = > + controls->get(controls::FrameDurationLimits)) { > + data->minFrameDuration_ = std::max(data->minFrameDuration_, > + clientFrameDurationLimits->front()); This means data->minFrameDuration_ can only grow which might not be 100% correct. We're into corner cases here but imagine this psuedo code: v = cm.Camera("Virtual"); v.start({FrameDurationLimits(20000, 30000)}); # data->minFrameDuration_ = 20ms v.run(); v.stop(); ... v.start({FrameDurationLimits(10000, 15000)}); # FrameDuration was clamped to 20ms as data->minFrameDuration_ # is still 20ms,, but the user asked to test 10ms. v.run(); v.stop(); So I think the clamp/std::max here should be against the values registered in the ControlInfoMap. kbingham@Monstersaurus:~/iob/libcamera/libcamera$ ./build/gcc/src/apps/cam/cam -c"Virtual1" --list-controls Using camera Virtual1 as cam0 Control: [inout] draft::FaceDetectMode: - FaceDetectModeOff (0) [default] Control: [inout] libcamera::FrameDurationLimits: [16666..16666] Size: 2 edit: I think I've just seen you've already noticed and that's for the next v :-) Otherwise this is looking good to me. > + } > + } > + > for (auto &s : data->streamConfigs_) > s.seq = 0; > > @@ -349,18 +374,30 @@ void PipelineHandlerVirtual::stopDevice(Camera *camera) > /* Process pending `bufferCompleted` signals. */ > thread()->dispatchMessages(Message::Type::InvokeMessage, this); > data->bufferCompleted.disconnect(this); > + data->minFrameDuration_ = 0; > + 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(); > > - request->_d()->metadata().set(controls::SensorTimestamp, timestamp); > + const uint64_t now = currentTimestamp(); > + uint64_t targetTimestamp = > + data->lastTimestamp_ + data->minFrameDuration_ * 1000; > + > + if (targetTimestamp < now) > + targetTimestamp = now; > + data->lastTimestamp_ = targetTimestamp; > + > + request->_d()->metadata().set(controls::SensorTimestamp, > + targetTimestamp); > + request->_d()->metadata().set(controls::FrameDuration, > + data->minFrameDuration_); > + > data->invokeMethod(&VirtualCameraData::processRequest, > ConnectionTypeQueued, request); > > diff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h > index 215e56fa3b9d..477f5b15a88f 100644 > --- a/src/libcamera/pipeline/virtual/virtual.h > +++ b/src/libcamera/pipeline/virtual/virtual.h > @@ -66,6 +66,9 @@ public: > > std::vector<StreamConfig> streamConfigs_; > Signal<FrameBuffer *> bufferCompleted; > + > + int64_t minFrameDuration_ = 0; > + uint64_t lastTimestamp_ = 0; > }; > > } /* namespace libcamera */ > -- > 2.56.0 >
Hi, On 14.10.26 10:25, Kieran Bingham wrote: > Quoting Robert Mader (2026-10-13 21:49:21) >> Throttle requests to the minimal frame duration in order to honor the >> configured framerates. >> >> Signed-off-by: Robert Mader <robert.mader@collabora.com> >> >> --- >> >> Changes in v4: >> - Calculate minFrameDuration_ once in start() using FDLs from camera >> and, if present, from client provided controls. The later is not yet >> tested. >> - Assert that the camera has FDLs set by the config parser. >> - Set FrameDuration in request metadata. >> >> 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 | 53 ++++++++++++++++++---- >> src/libcamera/pipeline/virtual/virtual.h | 3 ++ >> 2 files changed, 48 insertions(+), 8 deletions(-) >> >> diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp >> index e25dd85a7391..442a793f82f0 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) >> @@ -323,11 +335,24 @@ int PipelineHandlerVirtual::exportFrameBuffers([[maybe_unused]] Camera *camera, >> return dmaBufAllocator_.exportBuffers(config.bufferCount, planeSizes, buffers); >> } >> >> -int PipelineHandlerVirtual::start([[maybe_unused]] Camera *camera, >> - [[maybe_unused]] const ControlList *controls) >> +int PipelineHandlerVirtual::start(Camera *camera, const ControlList *controls) >> { >> VirtualCameraData *data = cameraData(camera); >> >> + auto cameraFrameDurationLimits = >> + camera->controls().find(&controls::FrameDurationLimits); >> + ASSERT(cameraFrameDurationLimits != camera->controls().end()); > As far as I can tell this is somewhat guaranteed by the time we get > here, but no objection to it to make sure it stays that way. > > >> + data->minFrameDuration_ = >> + cameraFrameDurationLimits->second.min().get<int64_t>(); >> + >> + if (controls) { >> + if (auto clientFrameDurationLimits = >> + controls->get(controls::FrameDurationLimits)) { >> + data->minFrameDuration_ = std::max(data->minFrameDuration_, >> + clientFrameDurationLimits->front()); > This means data->minFrameDuration_ can only grow which might not be 100% > correct. We're into corner cases here but imagine this psuedo code: > > v = cm.Camera("Virtual"); > v.start({FrameDurationLimits(20000, 30000)}); > # data->minFrameDuration_ = 20ms > v.run(); > v.stop(); > > ... > > v.start({FrameDurationLimits(10000, 15000)}); > > # FrameDuration was clamped to 20ms as data->minFrameDuration_ > # is still 20ms,, but the user asked to test 10ms. > > v.run(); > v.stop(); > > So I think the clamp/std::max here should be against the values > registered in the ControlInfoMap. > > kbingham@Monstersaurus:~/iob/libcamera/libcamera$ ./build/gcc/src/apps/cam/cam -c"Virtual1" --list-controls > Using camera Virtual1 as cam0 > Control: [inout] draft::FaceDetectMode: > - FaceDetectModeOff (0) [default] > Control: [inout] libcamera::FrameDurationLimits: [16666..16666] > Size: 2 > > > edit: I think I've just seen you've already noticed and that's for the > next v :-) Yep, just send out v5 with that change. > > Otherwise this is looking good to me. 🤞 > > > > > >> + } >> + } >> + >> for (auto &s : data->streamConfigs_) >> s.seq = 0; >> >> @@ -349,18 +374,30 @@ void PipelineHandlerVirtual::stopDevice(Camera *camera) >> /* Process pending `bufferCompleted` signals. */ >> thread()->dispatchMessages(Message::Type::InvokeMessage, this); >> data->bufferCompleted.disconnect(this); >> + data->minFrameDuration_ = 0; >> + 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(); >> >> - request->_d()->metadata().set(controls::SensorTimestamp, timestamp); >> + const uint64_t now = currentTimestamp(); >> + uint64_t targetTimestamp = >> + data->lastTimestamp_ + data->minFrameDuration_ * 1000; >> + >> + if (targetTimestamp < now) >> + targetTimestamp = now; >> + data->lastTimestamp_ = targetTimestamp; >> + >> + request->_d()->metadata().set(controls::SensorTimestamp, >> + targetTimestamp); >> + request->_d()->metadata().set(controls::FrameDuration, >> + data->minFrameDuration_); >> + >> data->invokeMethod(&VirtualCameraData::processRequest, >> ConnectionTypeQueued, request); >> >> diff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h >> index 215e56fa3b9d..477f5b15a88f 100644 >> --- a/src/libcamera/pipeline/virtual/virtual.h >> +++ b/src/libcamera/pipeline/virtual/virtual.h >> @@ -66,6 +66,9 @@ public: >> >> std::vector<StreamConfig> streamConfigs_; >> Signal<FrameBuffer *> bufferCompleted; >> + >> + int64_t minFrameDuration_ = 0; >> + uint64_t lastTimestamp_ = 0; >> }; >> >> } /* namespace libcamera */ >> -- >> 2.56.0 >>
diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp index e25dd85a7391..442a793f82f0 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) @@ -323,11 +335,24 @@ int PipelineHandlerVirtual::exportFrameBuffers([[maybe_unused]] Camera *camera, return dmaBufAllocator_.exportBuffers(config.bufferCount, planeSizes, buffers); } -int PipelineHandlerVirtual::start([[maybe_unused]] Camera *camera, - [[maybe_unused]] const ControlList *controls) +int PipelineHandlerVirtual::start(Camera *camera, const ControlList *controls) { VirtualCameraData *data = cameraData(camera); + auto cameraFrameDurationLimits = + camera->controls().find(&controls::FrameDurationLimits); + ASSERT(cameraFrameDurationLimits != camera->controls().end()); + data->minFrameDuration_ = + cameraFrameDurationLimits->second.min().get<int64_t>(); + + if (controls) { + if (auto clientFrameDurationLimits = + controls->get(controls::FrameDurationLimits)) { + data->minFrameDuration_ = std::max(data->minFrameDuration_, + clientFrameDurationLimits->front()); + } + } + for (auto &s : data->streamConfigs_) s.seq = 0; @@ -349,18 +374,30 @@ void PipelineHandlerVirtual::stopDevice(Camera *camera) /* Process pending `bufferCompleted` signals. */ thread()->dispatchMessages(Message::Type::InvokeMessage, this); data->bufferCompleted.disconnect(this); + data->minFrameDuration_ = 0; + 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(); - request->_d()->metadata().set(controls::SensorTimestamp, timestamp); + const uint64_t now = currentTimestamp(); + uint64_t targetTimestamp = + data->lastTimestamp_ + data->minFrameDuration_ * 1000; + + if (targetTimestamp < now) + targetTimestamp = now; + data->lastTimestamp_ = targetTimestamp; + + request->_d()->metadata().set(controls::SensorTimestamp, + targetTimestamp); + request->_d()->metadata().set(controls::FrameDuration, + data->minFrameDuration_); + data->invokeMethod(&VirtualCameraData::processRequest, ConnectionTypeQueued, request); diff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h index 215e56fa3b9d..477f5b15a88f 100644 --- a/src/libcamera/pipeline/virtual/virtual.h +++ b/src/libcamera/pipeline/virtual/virtual.h @@ -66,6 +66,9 @@ public: std::vector<StreamConfig> streamConfigs_; Signal<FrameBuffer *> bufferCompleted; + + int64_t minFrameDuration_ = 0; + uint64_t lastTimestamp_ = 0; }; } /* namespace libcamera */
Throttle requests to the minimal frame duration in order to honor the configured framerates. Signed-off-by: Robert Mader <robert.mader@collabora.com> --- Changes in v4: - Calculate minFrameDuration_ once in start() using FDLs from camera and, if present, from client provided controls. The later is not yet tested. - Assert that the camera has FDLs set by the config parser. - Set FrameDuration in request metadata. 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 | 53 ++++++++++++++++++---- src/libcamera/pipeline/virtual/virtual.h | 3 ++ 2 files changed, 48 insertions(+), 8 deletions(-)