[libcamera-devel] [PATCH v4 2/4] ipa: raspberrypi: Switch ipa and cam_helper to use utils::Duration

Naushir Patuck naush at raspberrypi.com
Mon May 24 14:52:58 CEST 2021


Hi Jacopo,

Thank you for the review feedback.

On Mon, 24 May 2021 at 13:19, Jacopo Mondi <jacopo at jmondi.org> wrote:

> Hi Naush,
>
> On Mon, May 24, 2021 at 09:48:20AM +0100, Naushir Patuck wrote:
> > Switch the ipa and cam_helper code to use libcamera::utils::Duration for
> > all time based variables. This improves code readability and avoids
> > possible errors when converting between time bases.
> >
> > Signed-off-by: Naushir Patuck <naush at raspberrypi.com>
> > Reviewed-by: David Plowman <david.plowman at raspberrypi.com>
> > ---
> >  src/ipa/raspberrypi/cam_helper.cpp           | 19 +++---
> >  src/ipa/raspberrypi/cam_helper.hpp           | 12 ++--
> >  src/ipa/raspberrypi/controller/camera_mode.h |  6 +-
> >  src/ipa/raspberrypi/raspberrypi.cpp          | 64 +++++++++++---------
> >  4 files changed, 56 insertions(+), 45 deletions(-)
> >
> > diff --git a/src/ipa/raspberrypi/cam_helper.cpp
> b/src/ipa/raspberrypi/cam_helper.cpp
> > index 09917f3cc079..feb02d1806b2 100644
> > --- a/src/ipa/raspberrypi/cam_helper.cpp
> > +++ b/src/ipa/raspberrypi/cam_helper.cpp
> > @@ -61,20 +61,21 @@ void CamHelper::Process([[maybe_unused]]
> StatisticsPtr &stats,
> >  {
> >  }
> >
> > -uint32_t CamHelper::ExposureLines(double exposure_us) const
> > +uint32_t CamHelper::ExposureLines(const Duration &exposure) const
> >  {
> >       assert(initialized_);
> > -     return exposure_us * 1000.0 / mode_.line_length;
> > +     return exposure / mode_.line_length;
> >  }
> >
> > -double CamHelper::Exposure(uint32_t exposure_lines) const
> > +Duration CamHelper::Exposure(uint32_t exposure_lines) const
> >  {
> >       assert(initialized_);
> > -     return exposure_lines * mode_.line_length / 1000.0;
> > +     return exposure_lines * mode_.line_length;
> >  }
> >
> > -uint32_t CamHelper::GetVBlanking(double &exposure, double
> minFrameDuration,
> > -                              double maxFrameDuration) const
> > +uint32_t CamHelper::GetVBlanking(Duration &exposure,
> > +                              const Duration &minFrameDuration,
> > +                              const Duration &maxFrameDuration) const
> >  {
> >       uint32_t frameLengthMin, frameLengthMax, vblank;
> >       uint32_t exposureLines = ExposureLines(exposure);
> > @@ -85,8 +86,8 @@ uint32_t CamHelper::GetVBlanking(double &exposure,
> double minFrameDuration,
> >        * minFrameDuration and maxFrameDuration are clamped by the caller
> >        * based on the limits for the active sensor mode.
> >        */
> > -     frameLengthMin = 1e3 * minFrameDuration / mode_.line_length;
> > -     frameLengthMax = 1e3 * maxFrameDuration / mode_.line_length;
> > +     frameLengthMin = minFrameDuration / mode_.line_length;
> > +     frameLengthMax = maxFrameDuration / mode_.line_length;
> >
> >       /*
> >        * Limit the exposure to the maximum frame duration requested, and
> > @@ -182,7 +183,7 @@ void CamHelper::parseEmbeddedData(Span<const
> uint8_t> buffer,
> >               return;
> >       }
> >
> > -     deviceStatus.shutter_speed = Exposure(exposureLines);
> > +     deviceStatus.shutter_speed =
> Exposure(exposureLines).get<std::micro>();
> >       deviceStatus.analogue_gain = Gain(gainCode);
> >
> >       LOG(IPARPI, Debug) << "Metadata updated - Exposure : "
> > diff --git a/src/ipa/raspberrypi/cam_helper.hpp
> b/src/ipa/raspberrypi/cam_helper.hpp
> > index a52f3f0b583c..71ee15463a71 100644
> > --- a/src/ipa/raspberrypi/cam_helper.hpp
> > +++ b/src/ipa/raspberrypi/cam_helper.hpp
> > @@ -15,10 +15,13 @@
> >  #include "controller/metadata.hpp"
> >  #include "md_parser.hpp"
> >
> > +#include "libcamera/internal/utils.h"
> >  #include "libcamera/internal/v4l2_videodevice.h"
> >
> >  namespace RPiController {
> >
> > +using libcamera::utils::Duration;
>
> usually, and you do the same below, we spell out the full namespace in
> headers, and use 'using ...' in cpp files.
>
> Up to you.
>

Agreed, to be consistent, I will remove the using... and expand the
namespace
below.


>
> > +
> >  // The CamHelper class provides a number of facilities that anyone
> trying
> >  // to drive a camera will need to know, but which are not provided by
> the
> >  // standard driver framework. Specifically, it provides:
> > @@ -72,10 +75,11 @@ public:
> >       virtual void Prepare(libcamera::Span<const uint8_t> buffer,
> >                            Metadata &metadata);
> >       virtual void Process(StatisticsPtr &stats, Metadata &metadata);
> > -     uint32_t ExposureLines(double exposure_us) const;
> > -     double Exposure(uint32_t exposure_lines) const; // in us
> > -     virtual uint32_t GetVBlanking(double &exposure_us, double
> minFrameDuration,
> > -                                   double maxFrameDuration) const;
> > +     uint32_t ExposureLines(const Duration &exposure) const;
> > +     Duration Exposure(uint32_t exposure_lines) const;
> > +     virtual uint32_t GetVBlanking(Duration &exposure,
> > +                                   const Duration &minFrameDuration,
> > +                                   const Duration &maxFrameDuration)
> const;
> >       virtual uint32_t GainCode(double gain) const = 0;
> >       virtual double Gain(uint32_t gain_code) const = 0;
> >       virtual void GetDelays(int &exposure_delay, int &gain_delay,
> > diff --git a/src/ipa/raspberrypi/controller/camera_mode.h
> b/src/ipa/raspberrypi/controller/camera_mode.h
> > index 256438c931d9..2aa2335dcf90 100644
> > --- a/src/ipa/raspberrypi/controller/camera_mode.h
> > +++ b/src/ipa/raspberrypi/controller/camera_mode.h
> > @@ -8,6 +8,8 @@
> >
> >  #include <libcamera/transform.h>
> >
> > +#include "libcamera/internal/utils.h"
> > +
> >  // Description of a "camera mode", holding enough information for
> control
> >  // algorithms to adapt their behaviour to the different modes of the
> camera,
> >  // including binning, scaling, cropping etc.
> > @@ -33,8 +35,8 @@ struct CameraMode {
> >       double scale_x, scale_y;
> >       // scaling of the noise compared to the native sensor mode
> >       double noise_factor;
> > -     // line time in nanoseconds
> > -     double line_length;
> > +     // line time
> > +     libcamera::utils::Duration line_length;
> >       // any camera transform *not* reflected already in the camera
> tuning
> >       libcamera::Transform transform;
> >       // minimum and maximum fame lengths in units of lines
> > diff --git a/src/ipa/raspberrypi/raspberrypi.cpp
> b/src/ipa/raspberrypi/raspberrypi.cpp
> > index 52d91db282ea..4e02e94084f7 100644
> > --- a/src/ipa/raspberrypi/raspberrypi.cpp
> > +++ b/src/ipa/raspberrypi/raspberrypi.cpp
> > @@ -55,19 +55,22 @@
> >
> >  namespace libcamera {
> >
> > +using namespace std::literals::chrono_literals;
> > +using utils::Duration;
> > +
> >  /* Configure the sensor with these values initially. */
> >  constexpr double DefaultAnalogueGain = 1.0;
> > -constexpr unsigned int DefaultExposureTime = 20000;
> > -constexpr double defaultMinFrameDuration = 1e6 / 30.0;
> > -constexpr double defaultMaxFrameDuration = 1e6 / 0.01;
> > +constexpr Duration DefaultExposureTime = 20.0ms;
> > +constexpr Duration defaultMinFrameDuration = 1.0s / 30.0;
> > +constexpr Duration defaultMaxFrameDuration = 100.0s;
> >
> >  /*
> > - * Determine the minimum allowable inter-frame duration (in us) to run
> the
> > - * controller algorithms. If the pipeline handler provider frames at a
> rate
> > - * higher than this, we rate-limit the controller Prepare() and
> Process() calls
> > - * to lower than or equal to this rate.
> > + * Determine the minimum allowable inter-frame duration to run the
> controller
> > + * algorithms. If the pipeline handler provider frames at a rate higher
> than this,
> > + * we rate-limit the controller Prepare() and Process() calls to lower
> than or
> > + * equal to this rate.
> >   */
> > -constexpr double controllerMinFrameDuration = 1e6 / 60.0;
> > +constexpr Duration controllerMinFrameDuration = 1.0s / 60.0;
> >
> >  LOG_DEFINE_CATEGORY(IPARPI)
> >
> > @@ -111,7 +114,8 @@ private:
> >       void reportMetadata();
> >       void fillDeviceStatus(const ControlList &sensorControls);
> >       void processStats(unsigned int bufferId);
> > -     void applyFrameDurations(double minFrameDuration, double
> maxFrameDuration);
> > +     void applyFrameDurations(const Duration &minFrameDuration,
> > +                              const Duration &maxFrameDuration);
> >       void applyAGC(const struct AgcStatus *agcStatus, ControlList
> &ctrls);
> >       void applyAWB(const struct AwbStatus *awbStatus, ControlList
> &ctrls);
> >       void applyDG(const struct AgcStatus *dgStatus, ControlList &ctrls);
> > @@ -167,9 +171,9 @@ private:
> >       /* Distinguish the first camera start from others. */
> >       bool firstStart_;
> >
> > -     /* Frame duration (1/fps) limits, given in microseconds. */
> > -     double minFrameDuration_;
> > -     double maxFrameDuration_;
> > +     /* Frame duration (1/fps) limits. */
> > +     Duration minFrameDuration_;
> > +     Duration maxFrameDuration_;
> >  };
> >
> >  int IPARPi::init(const IPASettings &settings, ipa::RPi::SensorConfig
> *sensorConfig)
> > @@ -311,10 +315,10 @@ void IPARPi::setMode(const CameraSensorInfo
> &sensorInfo)
> >       mode_.noise_factor = sqrt(mode_.bin_x * mode_.bin_y);
> >
> >       /*
> > -      * Calculate the line length in nanoseconds as the ratio between
> > -      * the line length in pixels and the pixel rate.
> > +      * Calculate the line length as the ratio between the line length
> in
>
> I would keep the 'line length in nanoseconds' as two line lengths in
>
>         mode.line_length = sensorInfo.lineLength
>
> can be easily confused ? Although the mode.line_length type is a
> duration. Would mode.line_duration be a better fit ?
>

I see your point here.  However, as you pointed out mode.line_length is a
Duration type, and
I did not want to specify a time unit in the comment, as Duration hides all
of that for you.
Perhaps I should update the comment like:

s/Calculate the line length as the ratio/Calculate the line length duration
as the ratio/

to be more explicit?  I would prefer not to rename mode.line_length if at
all possible, as that
is (afaik) standard sensor device terminology.


> > +      * pixels and the pixel rate.
> >        */
> > -     mode_.line_length = 1e9 * sensorInfo.lineLength /
> sensorInfo.pixelRate;
> > +     mode_.line_length = sensorInfo.lineLength * (1.0s /
> sensorInfo.pixelRate);
> >
> >       /*
> >        * Set the frame length limits for the mode to ensure exposure and
> > @@ -387,7 +391,7 @@ int IPARPi::configure(const CameraSensorInfo
> &sensorInfo,
> >               /* Supply initial values for gain and exposure. */
> >               ControlList ctrls(sensorCtrls_);
> >               AgcStatus agcStatus;
> > -             agcStatus.shutter_time = DefaultExposureTime;
> > +             agcStatus.shutter_time =
> DefaultExposureTime.get<std::micro>();
> >               agcStatus.analogue_gain = DefaultAnalogueGain;
> >               applyAGC(&agcStatus, ctrls);
> >
> > @@ -862,7 +866,7 @@ void IPARPi::queueRequest(const ControlList
> &controls)
> >
> >               case controls::FRAME_DURATIONS: {
> >                       auto frameDurations = ctrl.second.get<Span<const
> int64_t>>();
> > -                     applyFrameDurations(frameDurations[0],
> frameDurations[1]);
> > +                     applyFrameDurations(frameDurations[0] * 1.0us,
> frameDurations[1] * 1.0us);
>
> It would be lovely to be able to avoid the 1.0us multiplication that
> turns the integers frameDurations[] into an std::chrono:duration.
>

The alternative to this is std::chrono::microseconds(frameDuration[0]).
I have a slight preference using "1.0us" over that though.  If you feel
it is better, I am happy to change.


>
> I wonder if a constructor that takes an integer would make sense. It
> would be rather difficult to make sure the integer passed to the
> constructor is diligently specified in nanoseconds.
>

This was my first thought as well, but it becomes a bit ambiguous.  It
required
the user to know that Duration is a nanosecond base, and frameDuration
is a microsecond base, and we must multiply by 1e3, and that becomes
error-prone.


> Or, can we make controls like frameDurations being Durations ? We can
> use libcamera types in controls definition after all...
>

I think this should certainly be the end goal of this work!  When all code
starts to
use Duration, none of these integer -> Duration conversion (apart from the
start/end
of the chain of course) will be needed.

I was a bit afraid of doing the wholesale change across all libcamera
without some feedback.
We can use the Raspberry Pi source code as a test-bed in the short term :-)


> >                       break;
> >               }
> >
> > @@ -937,9 +941,9 @@ void IPARPi::prepareISP(const ipa::RPi::ISPConfig
> &data)
> >               returnEmbeddedBuffer(data.embeddedBufferId);
> >
> >       /* Allow a 10% margin on the comparison below. */
> > -     constexpr double eps = controllerMinFrameDuration * 1e3 * 0.1;
> > +     Duration delta = (frameTimestamp - lastRunTimestamp_) * 1.0ns;
> >       if (lastRunTimestamp_ && frameCount_ > dropFrameCount_ &&
> > -         frameTimestamp - lastRunTimestamp_ + eps <
> controllerMinFrameDuration * 1e3) {
> > +         delta < controllerMinFrameDuration * 0.9) {
> >               /*
> >                * Ensure we merge the previous frame's metadata with the
> current
> >                * frame. This will not overwrite exposure/gain values for
> the
> > @@ -1012,7 +1016,7 @@ void IPARPi::fillDeviceStatus(const ControlList
> &sensorControls)
> >       int32_t exposureLines =
> sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();
> >       int32_t gainCode =
> sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();
> >
> > -     deviceStatus.shutter_speed = helper_->Exposure(exposureLines);
> > +     deviceStatus.shutter_speed =
> helper_->Exposure(exposureLines).get<std::micro>();
> >       deviceStatus.analogue_gain = helper_->Gain(gainCode);
> >
> >       LOG(IPARPI, Debug) << "Metadata - Exposure : "
> > @@ -1057,10 +1061,11 @@ void IPARPi::applyAWB(const struct AwbStatus
> *awbStatus, ControlList &ctrls)
> >                 static_cast<int32_t>(awbStatus->gain_b * 1000));
> >  }
> >
> > -void IPARPi::applyFrameDurations(double minFrameDuration, double
> maxFrameDuration)
> > +void IPARPi::applyFrameDurations(const Duration &minFrameDuration,
> > +                              const Duration &maxFrameDuration)
> >  {
> > -     const double minSensorFrameDuration = 1e-3 *
> mode_.min_frame_length * mode_.line_length;
> > -     const double maxSensorFrameDuration = 1e-3 *
> mode_.max_frame_length * mode_.line_length;
> > +     const Duration minSensorFrameDuration = mode_.min_frame_length *
> mode_.line_length;
> > +     const Duration maxSensorFrameDuration = mode_.max_frame_length *
> mode_.line_length;
> >
> >       /*
> >        * This will only be applied once AGC recalculations occur.
> > @@ -1076,20 +1081,20 @@ void IPARPi::applyFrameDurations(double
> minFrameDuration, double maxFrameDuratio
> >
> >       /* Return the validated limits via metadata. */
> >       libcameraMetadata_.set(controls::FrameDurations,
> > -                            { static_cast<int64_t>(minFrameDuration_),
> > -                              static_cast<int64_t>(maxFrameDuration_)
> });
> > +                            {
> static_cast<int64_t>(minFrameDuration_.get<std::micro>()),
> > +
> static_cast<int64_t>(maxFrameDuration_.get<std::micro>()) });
> >
> >       /*
> >        * Calculate the maximum exposure time possible for the AGC to use.
> >        * GetVBlanking() will update maxShutter with the largest exposure
> >        * value possible.
> >        */
> > -     double maxShutter = std::numeric_limits<double>::max();
> > +     Duration maxShutter = Duration::max();
> >       helper_->GetVBlanking(maxShutter, minFrameDuration_,
> maxFrameDuration_);
> >
> >       RPiController::AgcAlgorithm *agc =
> dynamic_cast<RPiController::AgcAlgorithm *>(
> >               controller_.GetAlgorithm("agc"));
> > -     agc->SetMaxShutter(maxShutter);
> > +     agc->SetMaxShutter(maxShutter.get<std::micro>());
> >  }
> >
> >  void IPARPi::applyAGC(const struct AgcStatus *agcStatus, ControlList
> &ctrls)
> > @@ -1097,9 +1102,8 @@ void IPARPi::applyAGC(const struct AgcStatus
> *agcStatus, ControlList &ctrls)
> >       int32_t gainCode = helper_->GainCode(agcStatus->analogue_gain);
> >
> >       /* GetVBlanking might clip exposure time to the fps limits. */
> > -     double exposure = agcStatus->shutter_time;
> > -     int32_t vblanking = helper_->GetVBlanking(exposure,
> minFrameDuration_,
> > -                                               maxFrameDuration_);
> > +     Duration exposure = agcStatus->shutter_time * 1.0us;
> > +     int32_t vblanking = helper_->GetVBlanking(exposure,
> minFrameDuration_, maxFrameDuration_);
>
> Overall, very nice introduction, Duration will be really helpful.
>

> One additional concern for sake of discussion. Do we need to specify
> the time unit ? As in DurationNs ?
>

My preference would be not to put a unit to Duration.  The idea is to
ensure the user (as much
as is possible) does not worry about the internal representation, and let
the compiler worry
about it.  I don't think there would be any scenario where having us know
that Duration is
stored as ns would be helpful, since we have a Duration::get<T> to do any
conversions needed.

Regards,
Naush


>
> Thanks
>   j
>
> >       int32_t exposureLines = helper_->ExposureLines(exposure);
> >
> >       LOG(IPARPI, Debug) << "Applying AGC Exposure: " << exposure
> > --
> > 2.25.1
> >
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.libcamera.org/pipermail/libcamera-devel/attachments/20210524/a854dffa/attachment-0001.htm>


More information about the libcamera-devel mailing list