| Message ID | 20261020132409.37650-1-robert.mader@collabora.com |
|---|---|
| State | Superseded |
| Headers | show |
| Series |
|
| Related | show |
Hi 2026. 10. 20. 15:24 keltezéssel, Robert Mader írta: > Throttle requests to the minimal frame duration in order to honor the > configured frame rates. > > Signed-off-by: Robert Mader <robert.mader@collabora.com> > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > > --- > > Changes in v7: > - Address feedback from Laurent > - Replace lastTimestamp_ with targetTimestamp_ and simplify calculation > > Changes in v6: > - Use std::clamp (Kieran) and minor code reshuffle in start() > - Use "frame rate" consistently > - Added R-B from Kieran (assuming the changes are ok) > > Changes in v5: > - Also clamp frame duration limit to highest one allowed by the config > so clients can't set a unsupported frame rate. > - Added comments for that calculations using frame rate logic, as that is > what the config files use. > - Successfully tested the patch against the following Pipewire MR: > https://gitlab.freedesktop.org/pipewire/pipewire/-/merge_requests/3059 > > 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 | 58 ++++++++++++++++++---- > src/libcamera/pipeline/virtual/virtual.h | 5 +- > 2 files changed, 53 insertions(+), 10 deletions(-) > > diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp > index f35af97b103d..13a96e6a9412 100644 > --- a/src/libcamera/pipeline/virtual/virtual.cpp > +++ b/src/libcamera/pipeline/virtual/virtual.cpp > @@ -135,8 +135,14 @@ VirtualCameraData::VirtualCameraData(PipelineHandler *pipe, > moveToThread(this); > } > > -void VirtualCameraData::processRequest(Request *request) > +void VirtualCameraData::processRequest(Request *request, > + uint64_t targetTimestamp) > { > + const auto targetTime = std::chrono::steady_clock::time_point{ > + std::chrono::nanoseconds(targetTimestamp) > + }; > + std::this_thread::sleep_until(targetTime); > + > for (const auto &[stream, buffer] : request->buffers()) { > bool found = false; > /* map buffer and fill test patterns */ > @@ -146,7 +152,7 @@ void VirtualCameraData::processRequest(Request *request) > > fmd.status = FrameMetadata::Status::FrameSuccess; > fmd.sequence = streamConfig.seq++; > - fmd.timestamp = currentTimestamp(); > + fmd.timestamp = targetTimestamp; > > std::span<const FrameBuffer::Plane> planes = buffer->planes(); > for (const auto [i, p] : utils::enumerate(planes)) > @@ -348,11 +354,34 @@ 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()); > + > + /* By default target the highest frame rate allowed by the config. */ > + data->minFrameDuration_ = > + cameraFrameDurationLimits->second.min().get<int64_t>(); > + > + if (controls) { > + if (auto clientFrameDurationLimits = > + controls->get(controls::FrameDurationLimits)) { > + /* > + * Target the highest frame rate allowed by the client, > + * clamped to the values allowed by the config. > + */ > + data->minFrameDuration_ = std::clamp( > + clientFrameDurationLimits->front(), > + data->minFrameDuration_, > + cameraFrameDurationLimits->second.max().get<int64_t>()); > + } > + } > + > + data->targetTimestamp_ = 0; > + > for (auto &s : data->streamConfigs_) > s.seq = 0; > > @@ -379,15 +408,26 @@ void PipelineHandlerVirtual::stopDevice(Camera *camera) > cancelRequest(data->queuedRequests_.front()); > } > > -int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera, > - Request *request) > +int PipelineHandlerVirtual::queueRequestDevice(Camera *camera, Request *request) > { Is the lack of handling in `queueRequest()` intentional? Unless there are some very compelling reasons, I'd like to see that implemented. E.g. when `FrameDurationLimits` is initialized in `ConfigParser::parseConfigFile()`, you could set a new member like std::array<int64_t, 2> frameDurationLimits_; (possibly making `ConfigParser` and `friend` of `VirtualCameraData`) and then use that to clamp everywhere. Or am I missing something? > VirtualCameraData *data = cameraData(camera); > - const auto timestamp = currentTimestamp(); > > - request->_d()->metadata().set(controls::SensorTimestamp, timestamp); > + /* > + * TODO: Skip frames if we can't keep up with the maximum frame > + * duration. > + */ > + data->targetTimestamp_ = > + std::max(currentTimestamp(), > + data->targetTimestamp_ + data->minFrameDuration_ * 1000); > + > + request->_d()->metadata().set(controls::SensorTimestamp, > + data->targetTimestamp_); > + request->_d()->metadata().set(controls::FrameDuration, > + data->minFrameDuration_); > + > data->invokeMethod(&VirtualCameraData::processRequest, > - ConnectionTypeQueued, request); > + ConnectionTypeQueued, request, > + data->targetTimestamp_); > > return 0; > } > diff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h > index 215e56fa3b9d..40e9095c0c4f 100644 > --- a/src/libcamera/pipeline/virtual/virtual.h > +++ b/src/libcamera/pipeline/virtual/virtual.h > @@ -60,12 +60,15 @@ public: > > ~VirtualCameraData() = default; > > - void processRequest(Request *request); > + void processRequest(Request *request, uint64_t targetTimestamp); > > Configuration config_; > > std::vector<StreamConfig> streamConfigs_; > Signal<FrameBuffer *> bufferCompleted; > + > + int64_t minFrameDuration_; > + uint64_t targetTimestamp_; > }; > > } /* namespace libcamera */
2026. 10. 22. 18:46 keltezéssel, Barnabás Pőcze írta: > Hi > > 2026. 10. 20. 15:24 keltezéssel, Robert Mader írta: >> Throttle requests to the minimal frame duration in order to honor the >> configured frame rates. >> >> Signed-off-by: Robert Mader <robert.mader@collabora.com> >> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> >> >> --- >> >> Changes in v7: >> - Address feedback from Laurent >> - Replace lastTimestamp_ with targetTimestamp_ and simplify calculation >> >> Changes in v6: >> - Use std::clamp (Kieran) and minor code reshuffle in start() >> - Use "frame rate" consistently >> - Added R-B from Kieran (assuming the changes are ok) >> >> Changes in v5: >> - Also clamp frame duration limit to highest one allowed by the config >> so clients can't set a unsupported frame rate. >> - Added comments for that calculations using frame rate logic, as that is >> what the config files use. >> - Successfully tested the patch against the following Pipewire MR: >> https://gitlab.freedesktop.org/pipewire/pipewire/-/merge_requests/3059 >> >> 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 | 58 ++++++++++++++++++---- >> src/libcamera/pipeline/virtual/virtual.h | 5 +- >> 2 files changed, 53 insertions(+), 10 deletions(-) >> >> diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp >> index f35af97b103d..13a96e6a9412 100644 >> --- a/src/libcamera/pipeline/virtual/virtual.cpp >> +++ b/src/libcamera/pipeline/virtual/virtual.cpp >> @@ -135,8 +135,14 @@ VirtualCameraData::VirtualCameraData(PipelineHandler *pipe, >> moveToThread(this); >> } >> -void VirtualCameraData::processRequest(Request *request) >> +void VirtualCameraData::processRequest(Request *request, >> + uint64_t targetTimestamp) >> { >> + const auto targetTime = std::chrono::steady_clock::time_point{ >> + std::chrono::nanoseconds(targetTimestamp) >> + }; >> + std::this_thread::sleep_until(targetTime); >> + >> for (const auto &[stream, buffer] : request->buffers()) { >> bool found = false; >> /* map buffer and fill test patterns */ >> @@ -146,7 +152,7 @@ void VirtualCameraData::processRequest(Request *request) >> fmd.status = FrameMetadata::Status::FrameSuccess; >> fmd.sequence = streamConfig.seq++; >> - fmd.timestamp = currentTimestamp(); >> + fmd.timestamp = targetTimestamp; >> std::span<const FrameBuffer::Plane> planes = buffer->planes(); >> for (const auto [i, p] : utils::enumerate(planes)) >> @@ -348,11 +354,34 @@ 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()); >> + >> + /* By default target the highest frame rate allowed by the config. */ >> + data->minFrameDuration_ = >> + cameraFrameDurationLimits->second.min().get<int64_t>(); >> + >> + if (controls) { >> + if (auto clientFrameDurationLimits = >> + controls->get(controls::FrameDurationLimits)) { >> + /* >> + * Target the highest frame rate allowed by the client, >> + * clamped to the values allowed by the config. >> + */ >> + data->minFrameDuration_ = std::clamp( >> + clientFrameDurationLimits->front(), >> + data->minFrameDuration_, >> + cameraFrameDurationLimits->second.max().get<int64_t>()); >> + } >> + } >> + >> + data->targetTimestamp_ = 0; >> + >> for (auto &s : data->streamConfigs_) >> s.seq = 0; >> @@ -379,15 +408,26 @@ void PipelineHandlerVirtual::stopDevice(Camera *camera) >> cancelRequest(data->queuedRequests_.front()); >> } >> -int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera, >> - Request *request) >> +int PipelineHandlerVirtual::queueRequestDevice(Camera *camera, Request *request) >> { > > Is the lack of handling in `queueRequest()` intentional? Unless there are some > very compelling reasons, I'd like to see that implemented. E.g. when `FrameDurationLimits` > is initialized in `ConfigParser::parseConfigFile()`, you could set a new member like > > std::array<int64_t, 2> frameDurationLimits_; > > (possibly making `ConfigParser` and `friend` of `VirtualCameraData`) > > and then use that to clamp everywhere. Or am I missing something? I have made an attempt, seems to work. Capture script: ``` properties: - loop: 500 frames: - 0: FrameDurationLimits: [ 111111, 111111 ] - 50: FrameDurationLimits: [ 99999, 99999 ] - 100: FrameDurationLimits: [ 88888, 88888 ] - 150: FrameDurationLimits: [ 77777, 77777 ] - 200: FrameDurationLimits: [ 66666, 66666 ] - 250: FrameDurationLimits: [ 55555, 55555 ] - 300: FrameDurationLimits: [ 44444, 44444 ] - 350: FrameDurationLimits: [ 33333, 33333 ] - 400: FrameDurationLimits: [ 22222, 22222 ] - 450: FrameDurationLimits: [ 11111, 11111 ] ``` Diff compared to v7: ```diff diff --git a/src/libcamera/pipeline/virtual/config_parser.cpp b/src/libcamera/pipeline/virtual/config_parser.cpp index 5169fd39bc..ec89158c7c 100644 --- a/src/libcamera/pipeline/virtual/config_parser.cpp +++ b/src/libcamera/pipeline/virtual/config_parser.cpp @@ -52,12 +52,17 @@ ConfigParser::parseConfigFile(File &file, PipelineHandler *pipe) continue; } - data->config_.id = cameraId; ControlInfoMap::Map controls; + + data->config_.id = cameraId; + /* \todo Check which resolution's frame rate to be reported */ + data->frameDurationLimits_ = { + 1000000 / data->config_.resolutions[0].frameRates[1], + 1000000 / data->config_.resolutions[0].frameRates[0], + }; controls[&controls::FrameDurationLimits] = - ControlInfo(1000000 / data->config_.resolutions[0].frameRates[1], - 1000000 / data->config_.resolutions[0].frameRates[0]); + ControlInfo(data->frameDurationLimits_[0], data->frameDurationLimits_[1]); std::vector<ControlValue> supportedFaceDetectModes{ static_cast<int32_t>(controls::draft::FaceDetectModeOff), diff --git a/src/libcamera/pipeline/virtual/data/virtual.yaml b/src/libcamera/pipeline/virtual/data/virtual.yaml index 20471bb94b..959e83d790 100644 --- a/src/libcamera/pipeline/virtual/data/virtual.yaml +++ b/src/libcamera/pipeline/virtual/data/virtual.yaml @@ -6,8 +6,8 @@ - width: 1920 height: 1080 frame_rates: - - 30 - - 60 + - 1 + - 120 - width: 1680 height: 1050 frame_rates: diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp index 13a96e6a94..8298be6e91 100644 --- a/src/libcamera/pipeline/virtual/virtual.cpp +++ b/src/libcamera/pipeline/virtual/virtual.cpp @@ -172,6 +172,22 @@ void VirtualCameraData::processRequest(Request *request, } } +void VirtualCameraData::updateFrameDuration(const ControlList &ctrls) +{ + auto fdl = ctrls.get(controls::FrameDurationLimits); + if (!fdl) + return; + + /* + * Target the highest frame rate allowed by the client, + * clamped to the values allowed by the config. + */ + minFrameDuration_ = std::clamp( + fdl->front(), + frameDurationLimits_[0], + frameDurationLimits_[1]); +} + VirtualCameraConfiguration::VirtualCameraConfiguration(VirtualCameraData *data) : CameraConfiguration(), data_(data) { @@ -358,27 +374,12 @@ int PipelineHandlerVirtual::start(Camera *camera, const ControlList *controls) { VirtualCameraData *data = cameraData(camera); - auto cameraFrameDurationLimits = - camera->controls().find(&controls::FrameDurationLimits); - ASSERT(cameraFrameDurationLimits != camera->controls().end()); - /* By default target the highest frame rate allowed by the config. */ - data->minFrameDuration_ = - cameraFrameDurationLimits->second.min().get<int64_t>(); + data->minFrameDuration_ = data->frameDurationLimits_[0]; + ASSERT(data->minFrameDuration_ > 0); - if (controls) { - if (auto clientFrameDurationLimits = - controls->get(controls::FrameDurationLimits)) { - /* - * Target the highest frame rate allowed by the client, - * clamped to the values allowed by the config. - */ - data->minFrameDuration_ = std::clamp( - clientFrameDurationLimits->front(), - data->minFrameDuration_, - cameraFrameDurationLimits->second.max().get<int64_t>()); - } - } + if (controls) + data->updateFrameDuration(*controls); data->targetTimestamp_ = 0; @@ -412,6 +413,8 @@ int PipelineHandlerVirtual::queueRequestDevice(Camera *camera, Request *request) { VirtualCameraData *data = cameraData(camera); + data->updateFrameDuration(request->controls()); + /* * TODO: Skip frames if we can't keep up with the maximum frame * duration. diff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h index 40e9095c0c..48ceda395b 100644 --- a/src/libcamera/pipeline/virtual/virtual.h +++ b/src/libcamera/pipeline/virtual/virtual.h @@ -61,12 +61,14 @@ public: ~VirtualCameraData() = default; void processRequest(Request *request, uint64_t targetTimestamp); + void updateFrameDuration(const ControlList &ctrls); Configuration config_; std::vector<StreamConfig> streamConfigs_; Signal<FrameBuffer *> bufferCompleted; + std::array<int64_t, 2> frameDurationLimits_ = {}; int64_t minFrameDuration_; uint64_t targetTimestamp_; }; ``` > > > > >> VirtualCameraData *data = cameraData(camera); >> - const auto timestamp = currentTimestamp(); >> - request->_d()->metadata().set(controls::SensorTimestamp, timestamp); >> + /* >> + * TODO: Skip frames if we can't keep up with the maximum frame >> + * duration. >> + */ >> + data->targetTimestamp_ = >> + std::max(currentTimestamp(), >> + data->targetTimestamp_ + data->minFrameDuration_ * 1000); >> + >> + request->_d()->metadata().set(controls::SensorTimestamp, >> + data->targetTimestamp_); >> + request->_d()->metadata().set(controls::FrameDuration, >> + data->minFrameDuration_); >> + >> data->invokeMethod(&VirtualCameraData::processRequest, >> - ConnectionTypeQueued, request); >> + ConnectionTypeQueued, request, >> + data->targetTimestamp_); >> return 0; >> } >> diff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h >> index 215e56fa3b9d..40e9095c0c4f 100644 >> --- a/src/libcamera/pipeline/virtual/virtual.h >> +++ b/src/libcamera/pipeline/virtual/virtual.h >> @@ -60,12 +60,15 @@ public: >> ~VirtualCameraData() = default; >> - void processRequest(Request *request); >> + void processRequest(Request *request, uint64_t targetTimestamp); >> Configuration config_; >> std::vector<StreamConfig> streamConfigs_; >> Signal<FrameBuffer *> bufferCompleted; >> + >> + int64_t minFrameDuration_; >> + uint64_t targetTimestamp_; >> }; >> } /* namespace libcamera */ >
Hi On 22.10.26 19:20, Barnabás Pőcze wrote: > 2026. 10. 22. 18:46 keltezéssel, Barnabás Pőcze írta: >> Hi >> >> 2026. 10. 20. 15:24 keltezéssel, Robert Mader írta: >>> Throttle requests to the minimal frame duration in order to honor the >>> configured frame rates. >>> >>> Signed-off-by: Robert Mader <robert.mader@collabora.com> >>> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> >>> >>> --- >>> >>> Changes in v7: >>> - Address feedback from Laurent >>> - Replace lastTimestamp_ with targetTimestamp_ and simplify >>> calculation >>> >>> Changes in v6: >>> - Use std::clamp (Kieran) and minor code reshuffle in start() >>> - Use "frame rate" consistently >>> - Added R-B from Kieran (assuming the changes are ok) >>> >>> Changes in v5: >>> - Also clamp frame duration limit to highest one allowed by the >>> config >>> so clients can't set a unsupported frame rate. >>> - Added comments for that calculations using frame rate logic, as >>> that is >>> what the config files use. >>> - Successfully tested the patch against the following Pipewire MR: >>> https://gitlab.freedesktop.org/pipewire/pipewire/-/merge_requests/3059 >>> >>> 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 | 58 >>> ++++++++++++++++++---- >>> src/libcamera/pipeline/virtual/virtual.h | 5 +- >>> 2 files changed, 53 insertions(+), 10 deletions(-) >>> >>> diff --git a/src/libcamera/pipeline/virtual/virtual.cpp >>> b/src/libcamera/pipeline/virtual/virtual.cpp >>> index f35af97b103d..13a96e6a9412 100644 >>> --- a/src/libcamera/pipeline/virtual/virtual.cpp >>> +++ b/src/libcamera/pipeline/virtual/virtual.cpp >>> @@ -135,8 +135,14 @@ >>> VirtualCameraData::VirtualCameraData(PipelineHandler *pipe, >>> moveToThread(this); >>> } >>> -void VirtualCameraData::processRequest(Request *request) >>> +void VirtualCameraData::processRequest(Request *request, >>> + uint64_t targetTimestamp) >>> { >>> + const auto targetTime = std::chrono::steady_clock::time_point{ >>> + std::chrono::nanoseconds(targetTimestamp) >>> + }; >>> + std::this_thread::sleep_until(targetTime); >>> + >>> for (const auto &[stream, buffer] : request->buffers()) { >>> bool found = false; >>> /* map buffer and fill test patterns */ >>> @@ -146,7 +152,7 @@ void VirtualCameraData::processRequest(Request >>> *request) >>> fmd.status = FrameMetadata::Status::FrameSuccess; >>> fmd.sequence = streamConfig.seq++; >>> - fmd.timestamp = currentTimestamp(); >>> + fmd.timestamp = targetTimestamp; >>> std::span<const FrameBuffer::Plane> planes = >>> buffer->planes(); >>> for (const auto [i, p] : utils::enumerate(planes)) >>> @@ -348,11 +354,34 @@ 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()); >>> + >>> + /* By default target the highest frame rate allowed by the >>> config. */ >>> + data->minFrameDuration_ = >>> + cameraFrameDurationLimits->second.min().get<int64_t>(); >>> + >>> + if (controls) { >>> + if (auto clientFrameDurationLimits = >>> + controls->get(controls::FrameDurationLimits)) { >>> + /* >>> + * Target the highest frame rate allowed by the client, >>> + * clamped to the values allowed by the config. >>> + */ >>> + data->minFrameDuration_ = std::clamp( >>> + clientFrameDurationLimits->front(), >>> + data->minFrameDuration_, >>> + cameraFrameDurationLimits->second.max().get<int64_t>()); >>> + } >>> + } >>> + >>> + data->targetTimestamp_ = 0; >>> + >>> for (auto &s : data->streamConfigs_) >>> s.seq = 0; >>> @@ -379,15 +408,26 @@ void PipelineHandlerVirtual::stopDevice(Camera >>> *camera) >>> cancelRequest(data->queuedRequests_.front()); >>> } >>> -int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] >>> Camera *camera, >>> - Request *request) >>> +int PipelineHandlerVirtual::queueRequestDevice(Camera *camera, >>> Request *request) >>> { >> >> Is the lack of handling in `queueRequest()` intentional? Unless there >> are some >> very compelling reasons, I'd like to see that implemented. E.g. when >> `FrameDurationLimits` >> is initialized in `ConfigParser::parseConfigFile()`, you could set a >> new member like >> >> std::array<int64_t, 2> frameDurationLimits_; >> >> (possibly making `ConfigParser` and `friend` of `VirtualCameraData`) >> >> and then use that to clamp everywhere. Or am I missing something? > > I have made an attempt, seems to work. Capture script: Look great - do you mind just taking over the patch and sending your version as v8? > > ``` > properties: > - loop: 500 > frames: > - 0: > FrameDurationLimits: [ 111111, 111111 ] > - 50: > FrameDurationLimits: [ 99999, 99999 ] > - 100: > FrameDurationLimits: [ 88888, 88888 ] > - 150: > FrameDurationLimits: [ 77777, 77777 ] > - 200: > FrameDurationLimits: [ 66666, 66666 ] > - 250: > FrameDurationLimits: [ 55555, 55555 ] > - 300: > FrameDurationLimits: [ 44444, 44444 ] > - 350: > FrameDurationLimits: [ 33333, 33333 ] > - 400: > FrameDurationLimits: [ 22222, 22222 ] > - 450: > FrameDurationLimits: [ 11111, 11111 ] > ``` > > Diff compared to v7: > > > ```diff > diff --git a/src/libcamera/pipeline/virtual/config_parser.cpp > b/src/libcamera/pipeline/virtual/config_parser.cpp > index 5169fd39bc..ec89158c7c 100644 > --- a/src/libcamera/pipeline/virtual/config_parser.cpp > +++ b/src/libcamera/pipeline/virtual/config_parser.cpp > @@ -52,12 +52,17 @@ ConfigParser::parseConfigFile(File &file, > PipelineHandler *pipe) > continue; > } > > - data->config_.id = cameraId; > ControlInfoMap::Map controls; > + > + data->config_.id = cameraId; > + > /* \todo Check which resolution's frame rate to be reported */ > + data->frameDurationLimits_ = { > + 1000000 / data->config_.resolutions[0].frameRates[1], > + 1000000 / data->config_.resolutions[0].frameRates[0], > + }; > controls[&controls::FrameDurationLimits] = > - ControlInfo(1000000 / > data->config_.resolutions[0].frameRates[1], > - 1000000 / > data->config_.resolutions[0].frameRates[0]); > + ControlInfo(data->frameDurationLimits_[0], > data->frameDurationLimits_[1]); > > std::vector<ControlValue> supportedFaceDetectModes{ > static_cast<int32_t>(controls::draft::FaceDetectModeOff), > diff --git a/src/libcamera/pipeline/virtual/data/virtual.yaml > b/src/libcamera/pipeline/virtual/data/virtual.yaml > index 20471bb94b..959e83d790 100644 > --- a/src/libcamera/pipeline/virtual/data/virtual.yaml > +++ b/src/libcamera/pipeline/virtual/data/virtual.yaml > @@ -6,8 +6,8 @@ > - width: 1920 > height: 1080 > frame_rates: > - - 30 > - - 60 > + - 1 > + - 120 > - width: 1680 > height: 1050 > frame_rates: > diff --git a/src/libcamera/pipeline/virtual/virtual.cpp > b/src/libcamera/pipeline/virtual/virtual.cpp > index 13a96e6a94..8298be6e91 100644 > --- a/src/libcamera/pipeline/virtual/virtual.cpp > +++ b/src/libcamera/pipeline/virtual/virtual.cpp > @@ -172,6 +172,22 @@ void VirtualCameraData::processRequest(Request > *request, > } > } > > +void VirtualCameraData::updateFrameDuration(const ControlList &ctrls) > +{ > + auto fdl = ctrls.get(controls::FrameDurationLimits); > + if (!fdl) > + return; > + > + /* > + * Target the highest frame rate allowed by the client, > + * clamped to the values allowed by the config. > + */ > + minFrameDuration_ = std::clamp( > + fdl->front(), > + frameDurationLimits_[0], > + frameDurationLimits_[1]); > +} > + > VirtualCameraConfiguration::VirtualCameraConfiguration(VirtualCameraData > *data) > : CameraConfiguration(), data_(data) > { > @@ -358,27 +374,12 @@ int PipelineHandlerVirtual::start(Camera > *camera, const ControlList *controls) > { > VirtualCameraData *data = cameraData(camera); > > - auto cameraFrameDurationLimits = > - camera->controls().find(&controls::FrameDurationLimits); > - ASSERT(cameraFrameDurationLimits != camera->controls().end()); > - > /* By default target the highest frame rate allowed by the > config. */ > - data->minFrameDuration_ = > - cameraFrameDurationLimits->second.min().get<int64_t>(); > + data->minFrameDuration_ = data->frameDurationLimits_[0]; > + ASSERT(data->minFrameDuration_ > 0); > > - if (controls) { > - if (auto clientFrameDurationLimits = > - controls->get(controls::FrameDurationLimits)) { > - /* > - * Target the highest frame rate allowed by the client, > - * clamped to the values allowed by the config. > - */ > - data->minFrameDuration_ = std::clamp( > - clientFrameDurationLimits->front(), > - data->minFrameDuration_, > - cameraFrameDurationLimits->second.max().get<int64_t>()); > - } > - } > + if (controls) > + data->updateFrameDuration(*controls); > > data->targetTimestamp_ = 0; > > @@ -412,6 +413,8 @@ int > PipelineHandlerVirtual::queueRequestDevice(Camera *camera, Request > *request) > { > VirtualCameraData *data = cameraData(camera); > > + data->updateFrameDuration(request->controls()); > + > /* > * TODO: Skip frames if we can't keep up with the maximum frame > * duration. > diff --git a/src/libcamera/pipeline/virtual/virtual.h > b/src/libcamera/pipeline/virtual/virtual.h > index 40e9095c0c..48ceda395b 100644 > --- a/src/libcamera/pipeline/virtual/virtual.h > +++ b/src/libcamera/pipeline/virtual/virtual.h > @@ -61,12 +61,14 @@ public: > ~VirtualCameraData() = default; > > void processRequest(Request *request, uint64_t targetTimestamp); > + void updateFrameDuration(const ControlList &ctrls); > > Configuration config_; > > std::vector<StreamConfig> streamConfigs_; > Signal<FrameBuffer *> bufferCompleted; > > + std::array<int64_t, 2> frameDurationLimits_ = {}; > int64_t minFrameDuration_; > uint64_t targetTimestamp_; > }; > ``` > > >> >> >> >> >>> VirtualCameraData *data = cameraData(camera); >>> - const auto timestamp = currentTimestamp(); >>> - request->_d()->metadata().set(controls::SensorTimestamp, timestamp); >>> + /* >>> + * TODO: Skip frames if we can't keep up with the maximum frame >>> + * duration. >>> + */ >>> + data->targetTimestamp_ = >>> + std::max(currentTimestamp(), >>> + data->targetTimestamp_ + data->minFrameDuration_ * 1000); >>> + >>> + request->_d()->metadata().set(controls::SensorTimestamp, >>> + data->targetTimestamp_); >>> + request->_d()->metadata().set(controls::FrameDuration, >>> + data->minFrameDuration_); >>> + >>> data->invokeMethod(&VirtualCameraData::processRequest, >>> - ConnectionTypeQueued, request); >>> + ConnectionTypeQueued, request, >>> + data->targetTimestamp_); >>> return 0; >>> } >>> diff --git a/src/libcamera/pipeline/virtual/virtual.h >>> b/src/libcamera/pipeline/virtual/virtual.h >>> index 215e56fa3b9d..40e9095c0c4f 100644 >>> --- a/src/libcamera/pipeline/virtual/virtual.h >>> +++ b/src/libcamera/pipeline/virtual/virtual.h >>> @@ -60,12 +60,15 @@ public: >>> ~VirtualCameraData() = default; >>> - void processRequest(Request *request); >>> + void processRequest(Request *request, uint64_t targetTimestamp); >>> Configuration config_; >>> std::vector<StreamConfig> streamConfigs_; >>> Signal<FrameBuffer *> bufferCompleted; >>> + >>> + int64_t minFrameDuration_; >>> + uint64_t targetTimestamp_; >>> }; >>> } /* namespace libcamera */ >> >
diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp index f35af97b103d..13a96e6a9412 100644 --- a/src/libcamera/pipeline/virtual/virtual.cpp +++ b/src/libcamera/pipeline/virtual/virtual.cpp @@ -135,8 +135,14 @@ VirtualCameraData::VirtualCameraData(PipelineHandler *pipe, moveToThread(this); } -void VirtualCameraData::processRequest(Request *request) +void VirtualCameraData::processRequest(Request *request, + uint64_t targetTimestamp) { + const auto targetTime = std::chrono::steady_clock::time_point{ + std::chrono::nanoseconds(targetTimestamp) + }; + std::this_thread::sleep_until(targetTime); + for (const auto &[stream, buffer] : request->buffers()) { bool found = false; /* map buffer and fill test patterns */ @@ -146,7 +152,7 @@ void VirtualCameraData::processRequest(Request *request) fmd.status = FrameMetadata::Status::FrameSuccess; fmd.sequence = streamConfig.seq++; - fmd.timestamp = currentTimestamp(); + fmd.timestamp = targetTimestamp; std::span<const FrameBuffer::Plane> planes = buffer->planes(); for (const auto [i, p] : utils::enumerate(planes)) @@ -348,11 +354,34 @@ 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()); + + /* By default target the highest frame rate allowed by the config. */ + data->minFrameDuration_ = + cameraFrameDurationLimits->second.min().get<int64_t>(); + + if (controls) { + if (auto clientFrameDurationLimits = + controls->get(controls::FrameDurationLimits)) { + /* + * Target the highest frame rate allowed by the client, + * clamped to the values allowed by the config. + */ + data->minFrameDuration_ = std::clamp( + clientFrameDurationLimits->front(), + data->minFrameDuration_, + cameraFrameDurationLimits->second.max().get<int64_t>()); + } + } + + data->targetTimestamp_ = 0; + for (auto &s : data->streamConfigs_) s.seq = 0; @@ -379,15 +408,26 @@ void PipelineHandlerVirtual::stopDevice(Camera *camera) 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); + /* + * TODO: Skip frames if we can't keep up with the maximum frame + * duration. + */ + data->targetTimestamp_ = + std::max(currentTimestamp(), + data->targetTimestamp_ + data->minFrameDuration_ * 1000); + + request->_d()->metadata().set(controls::SensorTimestamp, + data->targetTimestamp_); + request->_d()->metadata().set(controls::FrameDuration, + data->minFrameDuration_); + data->invokeMethod(&VirtualCameraData::processRequest, - ConnectionTypeQueued, request); + ConnectionTypeQueued, request, + data->targetTimestamp_); return 0; } diff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h index 215e56fa3b9d..40e9095c0c4f 100644 --- a/src/libcamera/pipeline/virtual/virtual.h +++ b/src/libcamera/pipeline/virtual/virtual.h @@ -60,12 +60,15 @@ public: ~VirtualCameraData() = default; - void processRequest(Request *request); + void processRequest(Request *request, uint64_t targetTimestamp); Configuration config_; std::vector<StreamConfig> streamConfigs_; Signal<FrameBuffer *> bufferCompleted; + + int64_t minFrameDuration_; + uint64_t targetTimestamp_; }; } /* namespace libcamera */