<div dir="auto"><div>Hi Niklas,<div dir="auto"><br></div><br><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Wed, 25 Nov 2020, 7:58 pm Niklas Söderlund, <<a href="mailto:niklas.soderlund@ragnatech.se">niklas.soderlund@ragnatech.se</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Hi Naushir,<br>
<br>
Thanks for your feedback.<br>
<br>
On 2020-11-25 17:54:53 +0000, Naushir Patuck wrote:<br>
> Hi Niklas,<br>
> <br>
> On Tue, 10 Nov 2020 at 00:27, Niklas Söderlund <<br>
> <a href="mailto:niklas.soderlund@ragnatech.se" target="_blank" rel="noreferrer">niklas.soderlund@ragnatech.se</a>> wrote:<br>
> <br>
> > Some sensor controls take effect with a delay as the sensor needs time<br>
> > to adjust, for example exposure. Add a optional helper DelayedControls<br>
> > to help pipelines deal with such controls.<br>
> ><br>
> > The idea is to provide a queue of controls towards the V4L2 device and<br>
> > apply individual controls with the specified delay with the aim to get<br>
> > predictable and retrievable control values for any given frame. To do<br>
> > this the queue of controls needs to be at least as deep as the control<br>
> > with the largest delay.<br>
> ><br>
> > The DelayedControls needs to be informed of every start of exposure.<br>
> > This can be emulated but the helper is designed to be used with this<br>
> > event being provide by the kernel thru V4L2 events.<br>
> ><br>
> > This helper is based on StaggeredCtrl from the Raspberry Pi pipeline<br>
> > handler but expands on its API. This helpers aims to replace the<br>
> > Raspberry Pi implementations and mimics it behavior perfectly.<br>
> ><br>
> > Signed-off-by: Niklas Söderlund <<a href="mailto:niklas.soderlund@ragnatech.se" target="_blank" rel="noreferrer">niklas.soderlund@ragnatech.se</a>><br>
> > ---<br>
> > * Changes since v1<br>
> > - Correct copyright to reflect work is derived from Raspberry Pi<br>
> >   pipeline handler. This was always the intention and was wrong in v1.<br>
> > - Rewrite large parts of the documentation.<br>
> > - Join two loops to one in DelayedControls::DelayedControls()<br>
> > ---<br>
> >  include/libcamera/internal/delayed_controls.h |  87 ++++++<br>
> >  src/libcamera/delayed_controls.cpp            | 260 ++++++++++++++++++<br>
> >  src/libcamera/meson.build                     |   1 +<br>
> >  3 files changed, 348 insertions(+)<br>
> >  create mode 100644 include/libcamera/internal/delayed_controls.h<br>
> >  create mode 100644 src/libcamera/delayed_controls.cpp<br>
> ><br>
> > diff --git a/include/libcamera/internal/delayed_controls.h<br>
> > b/include/libcamera/internal/delayed_controls.h<br>
> > new file mode 100644<br>
> > index 0000000000000000..52bfba876e00e081<br>
> > --- /dev/null<br>
> > +++ b/include/libcamera/internal/delayed_controls.h<br>
> > @@ -0,0 +1,87 @@<br>
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */<br>
> > +/*<br>
> > + * Copyright (C) 2020, Raspberry Pi (Trading) Ltd.<br>
> > + *<br>
> > + * delayed_controls.h - Helper to deal with controls that are applied<br>
> > with a delay<br>
> > + */<br>
> > +#ifndef __LIBCAMERA_INTERNAL_DELAYED_CONTROLS_H__<br>
> > +#define __LIBCAMERA_INTERNAL_DELAYED_CONTROLS_H__<br>
> > +<br>
> > +#include <mutex><br>
> > +#include <stdint.h><br>
> > +#include <unordered_map><br>
> > +<br>
> > +#include <libcamera/controls.h><br>
> > +<br>
> > +namespace libcamera {<br>
> > +<br>
> > +class V4L2Device;<br>
> > +<br>
> > +class DelayedControls<br>
> > +{<br>
> > +public:<br>
> > +       DelayedControls(V4L2Device *device,<br>
> > +                       const std::unordered_map<uint32_t, unsigned int><br>
> > &delays);<br>
> > +<br>
> > +       void reset(ControlList *controls = nullptr);<br>
> > +<br>
> > +       bool push(const ControlList &controls);<br>
> > +       ControlList get(uint32_t sequence);<br>
> > +<br>
> > +       void frameStart(uint32_t sequence);<br>
> > +<br>
> > +private:<br>
> > +       class ControlInfo<br>
> > +       {<br>
> > +       public:<br>
> > +               ControlInfo()<br>
> > +                       : updated(false)<br>
> > +               {<br>
> > +               }<br>
> > +<br>
> > +               ControlInfo(const ControlValue &v)<br>
> > +                       : value(v), updated(true)<br>
> > +               {<br>
> > +               }<br>
> > +<br>
> > +               ControlValue value;<br>
> > +               bool updated;<br>
> > +       };<br>
> > +<br>
> > +       static constexpr int listSize = 16;<br>
> > +       class ControlArray : public std::array<ControlInfo, listSize><br>
> > +       {<br>
> > +       public:<br>
> > +               ControlInfo &operator[](unsigned int index)<br>
> > +               {<br>
> > +                       return std::array<ControlInfo,<br>
> > listSize>::operator[](index % listSize);<br>
> > +               }<br>
> > +<br>
> > +               const ControlInfo &operator[](unsigned int index) const<br>
> > +               {<br>
> > +                       return std::array<ControlInfo,<br>
> > listSize>::operator[](index % listSize);<br>
> > +               }<br>
> > +       };<br>
> > +<br>
> > +       using ControlsDelays = std::unordered_map<const ControlId *,<br>
> > unsigned int>;<br>
> > +       using ControlsValues = std::unordered_map<const ControlId *,<br>
> > ControlArray>;<br>
> > +<br>
> > +       bool queue(const ControlList &controls);<br>
> > +<br>
> > +       std::mutex lock_;<br>
> > +<br>
> > +       V4L2Device *device_;<br>
> > +       ControlsDelays delays_;<br>
> > +       unsigned int maxDelay_;<br>
> > +<br>
> > +       bool running_;<br>
> > +       uint32_t fistSequence_;<br>
> > +<br>
> > +       uint32_t queueCount_;<br>
> > +       uint32_t writeCount_;<br>
> > +       ControlsValues ctrls_;<br>
> > +};<br>
> > +<br>
> > +} /* namespace libcamera */<br>
> > +<br>
> > +#endif /* __LIBCAMERA_INTERNAL_DELAYED_CONTROLS_H__ */<br>
> > diff --git a/src/libcamera/delayed_controls.cpp<br>
> > b/src/libcamera/delayed_controls.cpp<br>
> > new file mode 100644<br>
> > index 0000000000000000..f1c5f890957ec040<br>
> > --- /dev/null<br>
> > +++ b/src/libcamera/delayed_controls.cpp<br>
> > @@ -0,0 +1,260 @@<br>
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */<br>
> > +/*<br>
> > + * Copyright (C) 2020, Raspberry Pi (Trading) Ltd.<br>
> > + *<br>
> > + * delayed_controls.h - Helper to deal with controls that are applied<br>
> > with a delay<br>
> > + */<br>
> > +<br>
> > +#include "libcamera/internal/delayed_controls.h"<br>
> > +#include "libcamera/internal/v4l2_device.h"<br>
> > +<br>
> > +#include <libcamera/controls.h><br>
> > +<br>
> > +#include "libcamera/internal/log.h"<br>
> > +<br>
> > +/**<br>
> > + * \file delayed_controls.h<br>
> > + * \brief Helper to deal with controls that are applied with a delay<br>
> > + */<br>
> > +<br>
> > +namespace libcamera {<br>
> > +<br>
> > +LOG_DEFINE_CATEGORY(DelayedControls)<br>
> > +<br>
> > +/**<br>
> > + * \class DelayedControls<br>
> > + * \brief Helper to deal with controls that take effect with a delay<br>
> > + *<br>
> > + * Some sensor controls take effect with a delay as the sensor needs time<br>
> > to<br>
> > + * adjust, for example exposure and focus. This is an optional helper<br>
> > class to<br>
> > + * deal with such controls and the intended users are pipeline handlers.<br>
> > + *<br>
> > + * The idea is to extend the concept of the buffer depth of a pipeline the<br>
> > + * application need to maintain to also cover controls. Just as with<br>
> > buffer<br>
> > + * depth if the application keeps the number of requests queued above the<br>
> > + * control depth the controls are guaranteed to take effect for the<br>
> > correct<br>
> > + * request. The control depth is determined by the control with the grates<br>
> > + * delay.<br>
> > + */<br>
> > +<br>
> > +/**<br>
> > + * \brief Construct a DelayedControls<br>
> > + * \param[in] device The V4L2 device the controls have to be applied to<br>
> > + * \param[in] delays Map of the numerical V4L2 control ids to their<br>
> > associated<br>
> > + * delays (in frames)<br>
> > + *<br>
> > + * Only controls specified in \a delays are handled. If it's desired to<br>
> > mix<br>
> > + * delayed controls and controls that take effect immediately the<br>
> > immediate<br>
> > + * controls must be listed in the \a delays map with a delay value of 0.<br>
> > + */<br>
> > +DelayedControls::DelayedControls(V4L2Device *device,<br>
> > +                                const std::unordered_map<uint32_t,<br>
> > unsigned int> &delays)<br>
> > +       : device_(device), maxDelay_(0)<br>
> > +{<br>
> > +       const ControlInfoMap &controls = device_->controls();<br>
> > +<br>
> > +       /*<br>
> > +        * Create a map of control ids to delays for controls exposed by<br>
> > the<br>
> > +        * device.<br>
> > +        */<br>
> > +       for (auto const &delay : delays) {<br>
> > +               auto itControl = controls.find(delay.first);<br>
> > +               if (itControl == controls.end()) {<br>
> > +                       LOG(DelayedControls, Error)<br>
> > +                               << "Delay request for control id "<br>
> > +                               << utils::hex(delay.first)<br>
> > +                               << " but control is not exposed by device "<br>
> > +                               << device_->deviceNode();<br>
> > +                       continue;<br>
> > +               }<br>
> > +<br>
> > +               const ControlId *id = itControl->first;<br>
> > +<br>
> > +               delays_[id] = delay.second;<br>
> > +<br>
> > +               LOG(DelayedControls, Debug)<br>
> > +                       << "Set a delay of " << delays_[id]<br>
> > +                       << " for " << id->name();<br>
> > +<br>
> > +               maxDelay_ = std::max(maxDelay_, delays_[id]);<br>
> > +       }<br>
> > +<br>
> > +       reset();<br>
> > +}<br>
> > +<br>
> > +/**<br>
> > + * \brief Reset state machine and controls<br>
> > + * \param[in] controls Optional list of controls to apply to the device<br>
> > + *<br>
> > + * Resets the state machine to a starting position based on control values<br>
> > + * retrieved from the device. Controls may optionally be set before they<br>
> > are<br>
> > + * read back using \a controls.<br>
> > + */<br>
> > +void DelayedControls::reset(ControlList *controls)<br>
> > +{<br>
> > +       std::lock_guard<std::mutex> lock(lock_);<br>
> > +<br>
> > +       running_ = false;<br>
> > +       fistSequence_ = 0;<br>
> > +       queueCount_ = 0;<br>
> > +       writeCount_ = 0;<br>
> > +<br>
> > +       /* Set the controls on the device if requested. */<br>
> > +       if (controls)<br>
> > +               device_->setControls(controls);<br>
> > +<br>
> > +       /* Retrieve current control values reported by the device. */<br>
> > +       std::vector<uint32_t> ids;<br>
> > +       for (auto const &delay : delays_)<br>
> > +               ids.push_back(delay.first->id());<br>
> > +<br>
> > +       ControlList devCtrls = device_->getControls(ids);<br>
> > +<br>
> > +       /* Seed the control queue with the controls reported by the<br>
> > device. */<br>
> > +       ctrls_.clear();<br>
> > +       for (const auto &ctrl : devCtrls) {<br>
> > +               const ControlId *id =<br>
> > devCtrls.infoMap()->idmap().at(ctrl.first);<br>
> > +               ctrls_[id][queueCount_] = ControlInfo(ctrl.second);<br>
> > +       }<br>
> > +<br>
> > +       queueCount_++;<br>
> > +}<br>
> > +<br>
> > +/**<br>
> > + * \brief Push a set of controls on the queue<br>
> > + * \param[in] controls List of controls to add to the device queue<br>
> > + *<br>
> > + * Push a set of controls to the control queue. This increases the<br>
> > control queue<br>
> > + * depth by one.<br>
> > + *<br>
> > + * \returns true if \a controls are accepted, or false otherwise<br>
> > + */<br>
> > +bool DelayedControls::push(const ControlList &controls)<br>
> > +{<br>
> > +       std::lock_guard<std::mutex> lock(lock_);<br>
> > +<br>
> > +       return queue(controls);<br>
> > +}<br>
> > +<br>
> > +bool DelayedControls::queue(const ControlList &controls)<br>
> > +{<br>
> > +       /* Copy state from previous frame. */<br>
> > +       for (auto &ctrl : ctrls_) {<br>
> > +               ControlInfo &info = ctrl.second[queueCount_];<br>
> > +               info.value = ctrls_[ctrl.first][queueCount_ - 1].value;<br>
> > +               info.updated = false;<br>
> > +       }<br>
> > +<br>
> > +       /* Update with new controls. */<br>
> > +       for (const auto &control : controls) {<br>
> > +               const ControlId *id =<br>
> > device_->controls().idmap().at(control.first);<br>
> > +<br>
> > +               if (delays_.find(id) == delays_.end())<br>
> > +                       return false;<br>
> > +<br>
> > +               ControlInfo &info = ctrls_[id][queueCount_];<br>
> > +<br>
> > +               info.value = control.second;<br>
> > +               info.updated = true;<br>
> > +<br>
> > +               LOG(DelayedControls, Debug)<br>
> > +                       << "Queuing " << id->name()<br>
> > +                       << " to " << info.value.toString()<br>
> > +                       << " at index " << queueCount_;<br>
> > +       }<br>
> > +<br>
> > +       queueCount_++;<br>
> > +<br>
> > +       return true;<br>
> > +}<br>
> > +<br>
> > +/**<br>
> > + * \brief Read back controls in effect at a sequence number<br>
> > + * \param[in] sequence The sequence number to get controls for<br>
> > + *<br>
> > + * Read back what controls where in effect at a specific sequence number.<br>
> > The<br>
> > + * history is a ring buffer of 16 entries where new and old values<br>
> > coexist. It's<br>
> > + * the callers responsibility to not read to old sequence numbers that<br>
> > have been<br>
> > + * pushed out of the history.<br>
> > + *<br>
> > + * Historic values are evicted by pushing new values onto the queue using<br>
> > + * push(). The max history from the current sequence number that yields<br>
> > valid<br>
> > + * values are thus 16 minus number of controls pushed.<br>
> > + *<br>
> > + * \return The controls at \a sequence number<br>
> > + */<br>
> > +ControlList DelayedControls::get(uint32_t sequence)<br>
> > +{<br>
> > +       std::lock_guard<std::mutex> lock(lock_);<br>
> > +<br>
> > +       uint32_t adjustedSeq = sequence - fistSequence_ + 1;<br>
> > +       unsigned int index = std::max<int>(0, adjustedSeq - maxDelay_);<br>
> > +<br>
> > +       ControlList out(device_->controls());<br>
> > +       for (const auto &ctrl : ctrls_) {<br>
> > +               const ControlId *id = ctrl.first;<br>
> > +               const ControlInfo &info = ctrl.second[index];<br>
> > +<br>
> > +               out.set(id->id(), info.value);<br>
> > +<br>
> > +               LOG(DelayedControls, Debug)<br>
> > +                       << "Reading " << id->name()<br>
> > +                       << " to " << info.value.toString()<br>
> > +                       << " at index " << index;<br>
> > +       }<br>
> > +<br>
> > +       return out;<br>
> > +}<br>
> > +<br>
> > +/**<br>
> > + * \brief Inform DelayedControls of the start of a new frame<br>
> > + * \param[in] sequence Sequence number of the frame that started<br>
> > + *<br>
> > + * Inform the state machine that a new frame has started and of its<br>
> > sequence<br>
> > + * number. Any user of these helpers is responsible to inform the helper<br>
> > about<br>
> > + * the start of any frame.This can be connected with ease to the start of<br>
> > a<br>
> > + * exposure (SOE) V4L2 event.<br>
> > + */<br>
> > +void DelayedControls::frameStart(uint32_t sequence)<br>
> > +{<br>
> > +       LOG(DelayedControls, Debug) << "frame " << sequence << " started";<br>
> > +<br>
> > +       std::lock_guard<std::mutex> lock(lock_);<br>
> > +<br>
> > +       if (!running_) {<br>
> > +               fistSequence_ = sequence;<br>
> > +               running_ = true;<br>
> > +       }<br>
> > +<br>
> > +       /*<br>
> > +        * Create control list peaking ahead in the value queue to ensure<br>
> > +        * values are set in time to satisfy the sensor delay.<br>
> > +        */<br>
> > +       ControlList out(device_->controls());<br>
> > +       for (const auto &ctrl : ctrls_) {<br>
> > +               const ControlId *id = ctrl.first;<br>
> > +               unsigned int delayDiff = maxDelay_ - delays_[id];<br>
> > +               unsigned int index = std::max<int>(0, writeCount_ -<br>
> > delayDiff);<br>
> > +               const ControlInfo &info = ctrl.second[index];<br>
> > +<br>
> > +               if (info.updated) {<br>
> > +                       out.set(id->id(), info.value);<br>
> > +                       LOG(DelayedControls, Debug)<br>
> > +                               << "Setting " << id->name()<br>
> > +                               << " to " << info.value.toString()<br>
> > +                               << " at index " << index;<br>
> > +               }<br>
> > +       }<br>
> > +<br>
> > +       writeCount_++;<br>
> > +<br>
> > +       while (writeCount_ >= queueCount_) {<br>
> > +               LOG(DelayedControls, Debug)<br>
> > +                       << "Queue is empty, auto queue no-op.";<br>
> > +               queue({});<br>
> > +       }<br>
> > +<br>
> > +       device_->setControls(&out);<br>
> > +}<br>
> ><br>
> <br>
> There is one thing I've been meaning to do in the StaggeredWriter for<br>
> framerate / exposure control that will be superseded with this work. I<br>
> wonder if you could add that functionality here?<br>
> The issue is that some controls may need to be set before others.  For<br>
> example, VBLANK must be set separately before EXPOSURE.  If this does not<br>
> happen, the EXPOSURE value may be artificially constrained by the old<br>
> VBLANK limits, and the device driver may erroneously reject the control.<br>
> <br>
> To work around this, I was imagining that we have an "immediate update"<br>
> flag associated with a control.  If that flag is set,<br>
> DelayedControls::frameStart() will set that control straight away, i.e.<br>
> without passing a list of controls to the device.  All other controls<br>
> without that flag set will work as above, i.e. get added to the<br>
> ControlList::out map and set at the end of the function.  With this<br>
> implementation, we can guarantee VBLANK is set separately and before<br>
> EXPOSURE, hence the new EXPOSURE control will be correctly validated by the<br>
> limits of the new VBLANK.<br>
> <br>
> Hope that makes sense?<br>
<br>
I think the idea make sens but I think it should be added as part of a <br>
series that makes use of if so it can be evaluated in a working context.  <br>
For now I would like to focus on getting this series merged as it's been <br>
floating around the ML for far too long IMHO and then we can build new <br>
features on top.<br></blockquote></div></div><div dir="auto"><br></div><div dir="auto">Sure that makes sense. I might include it as part of my fps control changes.  Otherwise, apart from adding the flushing mechanism to match the reset behaviour of staggered write, I have no other objections to this work.</div><div dir="auto"><br></div><div dir="auto">Regards,</div><div dir="auto">Naush</div><div dir="auto"><br></div><div dir="auto"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
> <br>
> Regards,<br>
> Naush<br>
> <br>
> <br>
> <br>
> > +<br>
> > +} /* namespace libcamera */<br>
> > diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build<br>
> > index 387d5d88ecae11ad..5a4bf0d7ba4fd231 100644<br>
> > --- a/src/libcamera/meson.build<br>
> > +++ b/src/libcamera/meson.build<br>
> > @@ -12,6 +12,7 @@ libcamera_sources = files([<br>
> >      'controls.cpp',<br>
> >      'control_serializer.cpp',<br>
> >      'control_validator.cpp',<br>
> > +    'delayed_controls.cpp',<br>
> >      'device_enumerator.cpp',<br>
> >      'device_enumerator_sysfs.cpp',<br>
> >      'event_dispatcher.cpp',<br>
> > --<br>
> > 2.29.2<br>
> ><br>
> ><br>
<br>
-- <br>
Regards,<br>
Niklas Söderlund<br>
</blockquote></div></div></div>