[libcamera-devel,v2,3/5] ipa: rpi: agc: Implementation of multi-channel AGC
diff mbox series

Message ID 20230823120915.18621-4-david.plowman@raspberrypi.com
State Accepted
Headers show
Series
  • Multi-channel AGC
Related show

Commit Message

David Plowman Aug. 23, 2023, 12:09 p.m. UTC
The switchMode, prepare and process methods are updated to implement
multi-channel AGC correctly:

* switchMode now invokes switchMode on all the channels (whether
  active or not).

* prepare must find what channel the current frame is, and run on
  behalf of that channel.

* process updates the most recent DeviceStatus and statistics for the
  channel of the frame that has just arrived, but generates updated
  values working through the active channels in round-robin fashion.

One minor detail in process is that we don't want to change the
DeviceStatus metadata of the current frame, so we now pass this to the
AgcChannel's process method, rather than letting it find the
DeviceStatus in the metadata.

Signed-off-by: David Plowman <david.plowman@raspberrypi.com>
Reviewed-by: Naushir Patuck <naush@raspberrypi.com>
---
 src/ipa/rpi/controller/agc_status.h        |   1 +
 src/ipa/rpi/controller/rpi/agc.cpp         | 108 +++++++++++++++++++--
 src/ipa/rpi/controller/rpi/agc.h           |   1 +
 src/ipa/rpi/controller/rpi/agc_channel.cpp |  13 +--
 src/ipa/rpi/controller/rpi/agc_channel.h   |   4 +-
 5 files changed, 109 insertions(+), 18 deletions(-)

Comments

Jacopo Mondi Aug. 30, 2023, 7:56 a.m. UTC | #1
Hi David

On Wed, Aug 23, 2023 at 01:09:13PM +0100, David Plowman via libcamera-devel wrote:
> The switchMode, prepare and process methods are updated to implement
> multi-channel AGC correctly:
>
> * switchMode now invokes switchMode on all the channels (whether
>   active or not).
>
> * prepare must find what channel the current frame is, and run on
>   behalf of that channel.
>
> * process updates the most recent DeviceStatus and statistics for the
>   channel of the frame that has just arrived, but generates updated
>   values working through the active channels in round-robin fashion.
>
> One minor detail in process is that we don't want to change the
> DeviceStatus metadata of the current frame, so we now pass this to the
> AgcChannel's process method, rather than letting it find the
> DeviceStatus in the metadata.
>
> Signed-off-by: David Plowman <david.plowman@raspberrypi.com>
> Reviewed-by: Naushir Patuck <naush@raspberrypi.com>
> ---
>  src/ipa/rpi/controller/agc_status.h        |   1 +
>  src/ipa/rpi/controller/rpi/agc.cpp         | 108 +++++++++++++++++++--
>  src/ipa/rpi/controller/rpi/agc.h           |   1 +
>  src/ipa/rpi/controller/rpi/agc_channel.cpp |  13 +--
>  src/ipa/rpi/controller/rpi/agc_channel.h   |   4 +-
>  5 files changed, 109 insertions(+), 18 deletions(-)
>
> diff --git a/src/ipa/rpi/controller/agc_status.h b/src/ipa/rpi/controller/agc_status.h
> index 597eddd7..e5c4ee22 100644
> --- a/src/ipa/rpi/controller/agc_status.h
> +++ b/src/ipa/rpi/controller/agc_status.h
> @@ -36,6 +36,7 @@ struct AgcStatus {
>  	int floatingRegionEnable;
>  	libcamera::utils::Duration fixedShutter;
>  	double fixedAnalogueGain;
> +	unsigned int channel;
>  };
>
>  struct AgcPrepareStatus {
> diff --git a/src/ipa/rpi/controller/rpi/agc.cpp b/src/ipa/rpi/controller/rpi/agc.cpp
> index c9c9c297..7e627bba 100644
> --- a/src/ipa/rpi/controller/rpi/agc.cpp
> +++ b/src/ipa/rpi/controller/rpi/agc.cpp
> @@ -22,7 +22,8 @@ LOG_DEFINE_CATEGORY(RPiAgc)
>
>  Agc::Agc(Controller *controller)
>  	: AgcAlgorithm(controller),
> -	  activeChannels_({ 0 })
> +	  activeChannels_({ 0 }),
> +	  index_(0)
>  {
>  }
>
> @@ -203,20 +204,113 @@ void Agc::setActiveChannels(const std::vector<unsigned int> &activeChannels)
>  void Agc::switchMode(CameraMode const &cameraMode,
>  		     Metadata *metadata)
>  {
> -	LOG(RPiAgc, Debug) << "switchMode for channel 0";
> -	channelData_[0].channel.switchMode(cameraMode, metadata);
> +	/*
> +	 * We run switchMode on every channel, and then we're going to start over
> +	 * with the first active channel again which means that this channel's
> +	 * status needs to be the one we leave in the metadata.
> +	 */
> +	AgcStatus status;
> +
> +	for (unsigned int channelIndex = 0; channelIndex < channelData_.size(); channelIndex++) {
> +		LOG(RPiAgc, Debug) << "switchMode for channel " << channelIndex;
> +		channelData_[channelIndex].channel.switchMode(cameraMode, metadata);
> +		if (channelIndex == activeChannels_[0])
> +			metadata->get("agc.status", status);
> +	}
> +
> +	status.channel = activeChannels_[0];
> +	metadata->set("agc.status", status);
> +	index_ = 0;
> +}
> +
> +static void getChannelIndex(Metadata *metadata, const char *message, unsigned int &channelIndex)
> +{
> +	std::unique_lock<RPiController::Metadata> lock(*metadata);
> +	AgcStatus *status = metadata->getLocked<AgcStatus>("agc.delayed_status");
> +	if (status)
> +		channelIndex = status->channel;

I'm missing in the code base where delayed_status.channel gets set :/

> +	else
> +		/* This does happen at startup, otherwise it would be a Warning or Error. */
> +		LOG(RPiAgc, Debug) << message;

If this is a known condition, do you need to have the message
polluting the logs ?

> +}
> +
> +static void setChannelIndex(Metadata *metadata, const char *message, unsigned int channelIndex)
> +{
> +	std::unique_lock<RPiController::Metadata> lock(*metadata);
> +	AgcStatus *status = metadata->getLocked<AgcStatus>("agc.status");
> +	if (status)
> +		status->channel = channelIndex;
> +	else
> +		/* This does happen at startup, otherwise it would be a Warning or Error. */
> +		LOG(RPiAgc, Debug) << message;

ditto

>  }
>
>  void Agc::prepare(Metadata *imageMetadata)
>  {
> -	LOG(RPiAgc, Debug) << "prepare for channel 0";
> -	channelData_[0].channel.prepare(imageMetadata);
> +	/*
> +	 * The DeviceStatus in the metadata should be correct for the image we
> +	 * are processing. THe delayed status should tell us what channel this frame
> +	 * was from, so we will use that channel's prepare method.
> +	 *
> +	 * \todo To be honest, there's not much that's stateful in the prepare methods
> +	 * so we should perhaps re-evaluate whether prepare even needs to be done
> +	 * "per channel".

on which channel would you do 'prepare' if not the last active one ?

> +	 */
> +	unsigned int channelIndex = activeChannels_[0];
> +	getChannelIndex(imageMetadata, "prepare: no delayed status", channelIndex);
> +
> +	LOG(RPiAgc, Debug) << "prepare for channel " << channelIndex;
> +	channelData_[channelIndex].channel.prepare(imageMetadata);
>  }
>
>  void Agc::process(StatisticsPtr &stats, Metadata *imageMetadata)
>  {
> -	LOG(RPiAgc, Debug) << "process for channel 0";
> -	channelData_[0].channel.process(stats, imageMetadata);
> +	/*
> +	 * We want to generate values for the next channel in round robin fashion
> +	 * (i.e. the channel at location index_ in the activeChannel list), even though
> +	 * the statistics we have will be for a different channel (which we find
> +	 * again from the delayed status).
> +	 */
> +
> +	/* Generate updated AGC values for this channel: */
> +	unsigned int channelIndex = activeChannels_[index_];
> +	AgcChannelData &channelData = channelData_[channelIndex];
> +	/* Stats are from this channel: */
> +	unsigned int statsIndex = 0;

why not use 'activeChannels_[index_]' again here ?

> +	getChannelIndex(imageMetadata, "process: no delayed status for stats", statsIndex);
> +	LOG(RPiAgc, Debug) << "process for channel " << channelIndex;
> +
> +	/*
> +	 * We keep a cache of the most recent DeviceStatus and stats for each channel,
> +	 * so that we can invoke the next channel's process method with the most up to date
> +	 * values.
> +	 */
> +	LOG(RPiAgc, Debug) << "Save DeviceStatus and stats for channel " << statsIndex;
> +	DeviceStatus deviceStatus;
> +	if (imageMetadata->get<DeviceStatus>("device.status", deviceStatus) == 0)
> +		channelData_[statsIndex].deviceStatus = deviceStatus;
> +	else
> +		/* Every frame should have a DeviceStatus. */
> +		LOG(RPiAgc, Error) << "process: no device status found";
> +	channelData_[statsIndex].statistics = stats;

Again I'm missing why we need to use a different channelIndex and
statsIndex..
> +
> +	/*
> +	 * Finally fetch the most recent DeviceStatus and stats for this channel, if both
> +	 * exist, and call process(). We must make the agc.status metadata record correctly
> +	 * which channel this is.
> +	 */
> +	if (channelData.statistics && channelData.deviceStatus) {
> +		deviceStatus = *channelData.deviceStatus;
> +		stats = channelData.statistics;

Do you need to copy ? Could channelData.statistics get overwritten
during the process() call chain ?

> +	} else
> +		/* Can also happen when new channels start. */
> +		LOG(RPiAgc, Debug) << "process: channel " << channelIndex << " not seen yet";
> +
> +	channelData.channel.process(stats, &deviceStatus, imageMetadata);
> +	setChannelIndex(imageMetadata, "process: no AGC status found", channelIndex);
> +
> +	/* And onto the next channel for the next call. */
> +	index_ = (index_ + 1) % activeChannels_.size();
>  }
>
>  /* Register algorithm with the system. */
> diff --git a/src/ipa/rpi/controller/rpi/agc.h b/src/ipa/rpi/controller/rpi/agc.h
> index a9158910..2eed2bab 100644
> --- a/src/ipa/rpi/controller/rpi/agc.h
> +++ b/src/ipa/rpi/controller/rpi/agc.h
> @@ -53,6 +53,7 @@ private:
>  	int checkChannel(unsigned int channel) const;
>  	std::vector<AgcChannelData> channelData_;
>  	std::vector<unsigned int> activeChannels_;
> +	unsigned int index_; /* index into the activeChannels_ */
>  };
>
>  } /* namespace RPiController */
> diff --git a/src/ipa/rpi/controller/rpi/agc_channel.cpp b/src/ipa/rpi/controller/rpi/agc_channel.cpp
> index d6e30ef2..ddec611f 100644
> --- a/src/ipa/rpi/controller/rpi/agc_channel.cpp
> +++ b/src/ipa/rpi/controller/rpi/agc_channel.cpp
> @@ -447,7 +447,7 @@ void AgcChannel::prepare(Metadata *imageMetadata)
>  	}
>  }
>
> -void AgcChannel::process(StatisticsPtr &stats, Metadata *imageMetadata)
> +void AgcChannel::process(StatisticsPtr &stats, const DeviceStatus *deviceStatus, Metadata *imageMetadata)
>  {
>  	frameCount_++;
>  	/*
> @@ -458,7 +458,7 @@ void AgcChannel::process(StatisticsPtr &stats, Metadata *imageMetadata)
>  	/* Fetch the AWB status immediately, so that we can assume it's there. */
>  	fetchAwbStatus(imageMetadata);
>  	/* Get the current exposure values for the frame that's just arrived. */
> -	fetchCurrentExposure(imageMetadata);
> +	fetchCurrentExposure(deviceStatus);
>  	/* Compute the total gain we require relative to the current exposure. */
>  	double gain, targetY;
>  	computeGain(stats, imageMetadata, gain, targetY);
> @@ -570,18 +570,13 @@ void AgcChannel::housekeepConfig()
>  			   << meteringModeName_;
>  }
>
> -void AgcChannel::fetchCurrentExposure(Metadata *imageMetadata)
> +void AgcChannel::fetchCurrentExposure(const DeviceStatus *deviceStatus)
>  {
> -	std::unique_lock<Metadata> lock(*imageMetadata);
> -	DeviceStatus *deviceStatus =
> -		imageMetadata->getLocked<DeviceStatus>("device.status");
>  	if (!deviceStatus)
>  		LOG(RPiAgc, Fatal) << "No device metadata";
>  	current_.shutter = deviceStatus->shutterSpeed;
>  	current_.analogueGain = deviceStatus->analogueGain;
> -	AgcStatus *agcStatus =
> -		imageMetadata->getLocked<AgcStatus>("agc.status");
> -	current_.totalExposure = agcStatus ? agcStatus->totalExposureValue : 0s;
> +	current_.totalExposure = 0s; /* this value is unused */

Uh, why is this not used anymore ? :)

>  	current_.totalExposureNoDG = current_.shutter * current_.analogueGain;
>  }
>
> diff --git a/src/ipa/rpi/controller/rpi/agc_channel.h b/src/ipa/rpi/controller/rpi/agc_channel.h
> index dc4356f3..0e3d44b0 100644
> --- a/src/ipa/rpi/controller/rpi/agc_channel.h
> +++ b/src/ipa/rpi/controller/rpi/agc_channel.h
> @@ -83,13 +83,13 @@ public:
>  	void disableAuto();
>  	void switchMode(CameraMode const &cameraMode, Metadata *metadata);
>  	void prepare(Metadata *imageMetadata);
> -	void process(StatisticsPtr &stats, Metadata *imageMetadata);
> +	void process(StatisticsPtr &stats, const DeviceStatus *deviceStatus, Metadata *imageMetadata);
>
>  private:
>  	bool updateLockStatus(DeviceStatus const &deviceStatus);
>  	AgcConfig config_;
>  	void housekeepConfig();
> -	void fetchCurrentExposure(Metadata *imageMetadata);
> +	void fetchCurrentExposure(const DeviceStatus *deviceStatus);
>  	void fetchAwbStatus(Metadata *imageMetadata);
>  	void computeGain(StatisticsPtr &statistics, Metadata *imageMetadata,
>  			 double &gain, double &targetY);
> --
> 2.30.2
>
David Plowman Sept. 11, 2023, 1:50 p.m. UTC | #2
Hi Jacopo

Thanks for the review!

On Wed, 30 Aug 2023 at 08:57, Jacopo Mondi
<jacopo.mondi@ideasonboard.com> wrote:
>
> Hi David
>
> On Wed, Aug 23, 2023 at 01:09:13PM +0100, David Plowman via libcamera-devel wrote:
> > The switchMode, prepare and process methods are updated to implement
> > multi-channel AGC correctly:
> >
> > * switchMode now invokes switchMode on all the channels (whether
> >   active or not).
> >
> > * prepare must find what channel the current frame is, and run on
> >   behalf of that channel.
> >
> > * process updates the most recent DeviceStatus and statistics for the
> >   channel of the frame that has just arrived, but generates updated
> >   values working through the active channels in round-robin fashion.
> >
> > One minor detail in process is that we don't want to change the
> > DeviceStatus metadata of the current frame, so we now pass this to the
> > AgcChannel's process method, rather than letting it find the
> > DeviceStatus in the metadata.
> >
> > Signed-off-by: David Plowman <david.plowman@raspberrypi.com>
> > Reviewed-by: Naushir Patuck <naush@raspberrypi.com>
> > ---
> >  src/ipa/rpi/controller/agc_status.h        |   1 +
> >  src/ipa/rpi/controller/rpi/agc.cpp         | 108 +++++++++++++++++++--
> >  src/ipa/rpi/controller/rpi/agc.h           |   1 +
> >  src/ipa/rpi/controller/rpi/agc_channel.cpp |  13 +--
> >  src/ipa/rpi/controller/rpi/agc_channel.h   |   4 +-
> >  5 files changed, 109 insertions(+), 18 deletions(-)
> >
> > diff --git a/src/ipa/rpi/controller/agc_status.h b/src/ipa/rpi/controller/agc_status.h
> > index 597eddd7..e5c4ee22 100644
> > --- a/src/ipa/rpi/controller/agc_status.h
> > +++ b/src/ipa/rpi/controller/agc_status.h
> > @@ -36,6 +36,7 @@ struct AgcStatus {
> >       int floatingRegionEnable;
> >       libcamera::utils::Duration fixedShutter;
> >       double fixedAnalogueGain;
> > +     unsigned int channel;
> >  };
> >
> >  struct AgcPrepareStatus {
> > diff --git a/src/ipa/rpi/controller/rpi/agc.cpp b/src/ipa/rpi/controller/rpi/agc.cpp
> > index c9c9c297..7e627bba 100644
> > --- a/src/ipa/rpi/controller/rpi/agc.cpp
> > +++ b/src/ipa/rpi/controller/rpi/agc.cpp
> > @@ -22,7 +22,8 @@ LOG_DEFINE_CATEGORY(RPiAgc)
> >
> >  Agc::Agc(Controller *controller)
> >       : AgcAlgorithm(controller),
> > -       activeChannels_({ 0 })
> > +       activeChannels_({ 0 }),
> > +       index_(0)
> >  {
> >  }
> >
> > @@ -203,20 +204,113 @@ void Agc::setActiveChannels(const std::vector<unsigned int> &activeChannels)
> >  void Agc::switchMode(CameraMode const &cameraMode,
> >                    Metadata *metadata)
> >  {
> > -     LOG(RPiAgc, Debug) << "switchMode for channel 0";
> > -     channelData_[0].channel.switchMode(cameraMode, metadata);
> > +     /*
> > +      * We run switchMode on every channel, and then we're going to start over
> > +      * with the first active channel again which means that this channel's
> > +      * status needs to be the one we leave in the metadata.
> > +      */
> > +     AgcStatus status;
> > +
> > +     for (unsigned int channelIndex = 0; channelIndex < channelData_.size(); channelIndex++) {
> > +             LOG(RPiAgc, Debug) << "switchMode for channel " << channelIndex;
> > +             channelData_[channelIndex].channel.switchMode(cameraMode, metadata);
> > +             if (channelIndex == activeChannels_[0])
> > +                     metadata->get("agc.status", status);
> > +     }
> > +
> > +     status.channel = activeChannels_[0];
> > +     metadata->set("agc.status", status);
> > +     index_ = 0;
> > +}
> > +
> > +static void getChannelIndex(Metadata *metadata, const char *message, unsigned int &channelIndex)
> > +{
> > +     std::unique_lock<RPiController::Metadata> lock(*metadata);
> > +     AgcStatus *status = metadata->getLocked<AgcStatus>("agc.delayed_status");
> > +     if (status)
> > +             channelIndex = status->channel;
>
> I'm missing in the code base where delayed_status.channel gets set :/

Indeed, there's some background you need to know. The channel gets set
in the process method in this file, when it calls setChannelIndex near
the end. Later, the "agc.status" metadata gets copied as the
"agc.delayed_status" when the frame, for which "agc.status" calculated
the desired exposure a while back, actually arrives from the sensor.
It's "here's what you asked for <n> frames ago, and now this frame is
what you got!".

>
> > +     else
> > +             /* This does happen at startup, otherwise it would be a Warning or Error. */
> > +             LOG(RPiAgc, Debug) << message;
>
> If this is a known condition, do you need to have the message
> polluting the logs ?

Not sure. It's not really meant to happen, so a warning would have
been nice, on the other hand it _does_ happen while things are
starting up so a warning might be scary. Another option might be to
have an easy way to detect the "startup" case, then we could output a
warning conditionally. On the other hand, there has been some
discussion about signalling those "startup" frames differently, so
maybe the whole thing will change... so I'm not too sure what is best
at the moment!

>
> > +}
> > +
> > +static void setChannelIndex(Metadata *metadata, const char *message, unsigned int channelIndex)
> > +{
> > +     std::unique_lock<RPiController::Metadata> lock(*metadata);
> > +     AgcStatus *status = metadata->getLocked<AgcStatus>("agc.status");
> > +     if (status)
> > +             status->channel = channelIndex;
> > +     else
> > +             /* This does happen at startup, otherwise it would be a Warning or Error. */
> > +             LOG(RPiAgc, Debug) << message;
>
> ditto
>
> >  }
> >
> >  void Agc::prepare(Metadata *imageMetadata)
> >  {
> > -     LOG(RPiAgc, Debug) << "prepare for channel 0";
> > -     channelData_[0].channel.prepare(imageMetadata);
> > +     /*
> > +      * The DeviceStatus in the metadata should be correct for the image we
> > +      * are processing. THe delayed status should tell us what channel this frame
> > +      * was from, so we will use that channel's prepare method.
> > +      *
> > +      * \todo To be honest, there's not much that's stateful in the prepare methods
> > +      * so we should perhaps re-evaluate whether prepare even needs to be done
> > +      * "per channel".
>
> on which channel would you do 'prepare' if not the last active one ?

Just thinking out loud, really. It kind of feels to me like we don't
really need to touch anything in the AgcChannel object itself, so it
could probably be a static class method. Though that's not true of
some of the "lock counts", but I don't really like how those work
either. Maybe I should remove such speculative thoughts until they're
a bit clearer?

>
> > +      */
> > +     unsigned int channelIndex = activeChannels_[0];
> > +     getChannelIndex(imageMetadata, "prepare: no delayed status", channelIndex);
> > +
> > +     LOG(RPiAgc, Debug) << "prepare for channel " << channelIndex;
> > +     channelData_[channelIndex].channel.prepare(imageMetadata);
> >  }
> >
> >  void Agc::process(StatisticsPtr &stats, Metadata *imageMetadata)
> >  {
> > -     LOG(RPiAgc, Debug) << "process for channel 0";
> > -     channelData_[0].channel.process(stats, imageMetadata);
> > +     /*
> > +      * We want to generate values for the next channel in round robin fashion
> > +      * (i.e. the channel at location index_ in the activeChannel list), even though
> > +      * the statistics we have will be for a different channel (which we find
> > +      * again from the delayed status).
> > +      */
> > +
> > +     /* Generate updated AGC values for this channel: */
> > +     unsigned int channelIndex = activeChannels_[index_];
> > +     AgcChannelData &channelData = channelData_[channelIndex];
> > +     /* Stats are from this channel: */
> > +     unsigned int statsIndex = 0;
>
> why not use 'activeChannels_[index_]' again here ?

activeChannels_[index_] is the channel for which we want to generate
new exposure/gain values. As you can see, index_ just goes up by 1
every time (modulo active_channels_.size()).

The statsIndex needs to be the channel number of this frame that has
just arrived, so it will be found in the agc.status that was written
several frames ago, and which is now available as agc.delayed_status.

Possibly "statsIndex" isn't the best name. Perhaps statsChannelIndex
would be better? It's not easy to know what to name things when there
are indices and channel numbers, which I then manage to call channel
indices. Perhaps I should be really strict about not using "channel
index" when I'm referring to a channel number, and indices only when
I'm indexing into the activeChannels_ list? Except that channel
numbers are also indices into the list of channels. So I'm a bit
confused as well!

>
> > +     getChannelIndex(imageMetadata, "process: no delayed status for stats", statsIndex);
> > +     LOG(RPiAgc, Debug) << "process for channel " << channelIndex;
> > +
> > +     /*
> > +      * We keep a cache of the most recent DeviceStatus and stats for each channel,
> > +      * so that we can invoke the next channel's process method with the most up to date
> > +      * values.
> > +      */
> > +     LOG(RPiAgc, Debug) << "Save DeviceStatus and stats for channel " << statsIndex;
> > +     DeviceStatus deviceStatus;
> > +     if (imageMetadata->get<DeviceStatus>("device.status", deviceStatus) == 0)
> > +             channelData_[statsIndex].deviceStatus = deviceStatus;
> > +     else
> > +             /* Every frame should have a DeviceStatus. */
> > +             LOG(RPiAgc, Error) << "process: no device status found";
> > +     channelData_[statsIndex].statistics = stats;
>
> Again I'm missing why we need to use a different channelIndex and
> statsIndex..

I hope I explained that above, but in the interests of trying harder...

When we're running with two channels (0 and 1), we always alternate
the exposure/gain values that we request. These get put into the
agc.status where the IPA will find them.

The important point is that we don't generate values for channel 0
only when a previous channel 0 frame arrives. That's because exposure
updates in the camera _can_ get lost if we're unlucky... in which case
channel 0 might disappear completely! So it's always channel 0,
channel 1, channel 0, channel 1...

But of course, this means that we might find ourselves generating
values for channel 0 when the frame and statistics that have just
arrived are from channel 1. We have to record the channel 1
statistics, but use the most recent channel 0 statistics instead.

> > +
> > +     /*
> > +      * Finally fetch the most recent DeviceStatus and stats for this channel, if both
> > +      * exist, and call process(). We must make the agc.status metadata record correctly
> > +      * which channel this is.
> > +      */
> > +     if (channelData.statistics && channelData.deviceStatus) {
> > +             deviceStatus = *channelData.deviceStatus;
> > +             stats = channelData.statistics;
>
> Do you need to copy ? Could channelData.statistics get overwritten
> during the process() call chain ?

We actually copy out all the statistics as soon as we get them (see
IpaVc4::platformProcessStats in src/ipa/rpi/vc4/vc4.ccp), so I think
that should be safe. After that we use the copied version through a
shared pointer so the memory should get cleared up once no one is
holding onto it any more.

>
> > +     } else
> > +             /* Can also happen when new channels start. */
> > +             LOG(RPiAgc, Debug) << "process: channel " << channelIndex << " not seen yet";
> > +
> > +     channelData.channel.process(stats, &deviceStatus, imageMetadata);
> > +     setChannelIndex(imageMetadata, "process: no AGC status found", channelIndex);
> > +
> > +     /* And onto the next channel for the next call. */
> > +     index_ = (index_ + 1) % activeChannels_.size();
> >  }
> >
> >  /* Register algorithm with the system. */
> > diff --git a/src/ipa/rpi/controller/rpi/agc.h b/src/ipa/rpi/controller/rpi/agc.h
> > index a9158910..2eed2bab 100644
> > --- a/src/ipa/rpi/controller/rpi/agc.h
> > +++ b/src/ipa/rpi/controller/rpi/agc.h
> > @@ -53,6 +53,7 @@ private:
> >       int checkChannel(unsigned int channel) const;
> >       std::vector<AgcChannelData> channelData_;
> >       std::vector<unsigned int> activeChannels_;
> > +     unsigned int index_; /* index into the activeChannels_ */
> >  };
> >
> >  } /* namespace RPiController */
> > diff --git a/src/ipa/rpi/controller/rpi/agc_channel.cpp b/src/ipa/rpi/controller/rpi/agc_channel.cpp
> > index d6e30ef2..ddec611f 100644
> > --- a/src/ipa/rpi/controller/rpi/agc_channel.cpp
> > +++ b/src/ipa/rpi/controller/rpi/agc_channel.cpp
> > @@ -447,7 +447,7 @@ void AgcChannel::prepare(Metadata *imageMetadata)
> >       }
> >  }
> >
> > -void AgcChannel::process(StatisticsPtr &stats, Metadata *imageMetadata)
> > +void AgcChannel::process(StatisticsPtr &stats, const DeviceStatus *deviceStatus, Metadata *imageMetadata)
> >  {
> >       frameCount_++;
> >       /*
> > @@ -458,7 +458,7 @@ void AgcChannel::process(StatisticsPtr &stats, Metadata *imageMetadata)
> >       /* Fetch the AWB status immediately, so that we can assume it's there. */
> >       fetchAwbStatus(imageMetadata);
> >       /* Get the current exposure values for the frame that's just arrived. */
> > -     fetchCurrentExposure(imageMetadata);
> > +     fetchCurrentExposure(deviceStatus);
> >       /* Compute the total gain we require relative to the current exposure. */
> >       double gain, targetY;
> >       computeGain(stats, imageMetadata, gain, targetY);
> > @@ -570,18 +570,13 @@ void AgcChannel::housekeepConfig()
> >                          << meteringModeName_;
> >  }
> >
> > -void AgcChannel::fetchCurrentExposure(Metadata *imageMetadata)
> > +void AgcChannel::fetchCurrentExposure(const DeviceStatus *deviceStatus)
> >  {
> > -     std::unique_lock<Metadata> lock(*imageMetadata);
> > -     DeviceStatus *deviceStatus =
> > -             imageMetadata->getLocked<DeviceStatus>("device.status");
> >       if (!deviceStatus)
> >               LOG(RPiAgc, Fatal) << "No device metadata";
> >       current_.shutter = deviceStatus->shutterSpeed;
> >       current_.analogueGain = deviceStatus->analogueGain;
> > -     AgcStatus *agcStatus =
> > -             imageMetadata->getLocked<AgcStatus>("agc.status");
> > -     current_.totalExposure = agcStatus ? agcStatus->totalExposureValue : 0s;
> > +     current_.totalExposure = 0s; /* this value is unused */
>
> Uh, why is this not used anymore ? :)

I think the reason is that it was never being used, but it wasn't
quite so obvious until I started to look at this and worry what
channel "agc.status" might be referring to.

The current exposure values are only required to interpret the
statistics, so that we can work out how much to change the exposure by
in order to cause a particular change in the statistics. And of course
the statistics depend only on totalExposureNoDG (the requested
exposure excluding any digital gain). So the "totalExposure",
including digital gain, simply isn't required. I've tried to make that
more obvious by simply setting that number to zero.

Thanks again for looking at this patch, I'm sorry that it can be a bit
tricky to understand in places.

David

>
> >       current_.totalExposureNoDG = current_.shutter * current_.analogueGain;
> >  }
> >
> > diff --git a/src/ipa/rpi/controller/rpi/agc_channel.h b/src/ipa/rpi/controller/rpi/agc_channel.h
> > index dc4356f3..0e3d44b0 100644
> > --- a/src/ipa/rpi/controller/rpi/agc_channel.h
> > +++ b/src/ipa/rpi/controller/rpi/agc_channel.h
> > @@ -83,13 +83,13 @@ public:
> >       void disableAuto();
> >       void switchMode(CameraMode const &cameraMode, Metadata *metadata);
> >       void prepare(Metadata *imageMetadata);
> > -     void process(StatisticsPtr &stats, Metadata *imageMetadata);
> > +     void process(StatisticsPtr &stats, const DeviceStatus *deviceStatus, Metadata *imageMetadata);
> >
> >  private:
> >       bool updateLockStatus(DeviceStatus const &deviceStatus);
> >       AgcConfig config_;
> >       void housekeepConfig();
> > -     void fetchCurrentExposure(Metadata *imageMetadata);
> > +     void fetchCurrentExposure(const DeviceStatus *deviceStatus);
> >       void fetchAwbStatus(Metadata *imageMetadata);
> >       void computeGain(StatisticsPtr &statistics, Metadata *imageMetadata,
> >                        double &gain, double &targetY);
> > --
> > 2.30.2
> >

Patch
diff mbox series

diff --git a/src/ipa/rpi/controller/agc_status.h b/src/ipa/rpi/controller/agc_status.h
index 597eddd7..e5c4ee22 100644
--- a/src/ipa/rpi/controller/agc_status.h
+++ b/src/ipa/rpi/controller/agc_status.h
@@ -36,6 +36,7 @@  struct AgcStatus {
 	int floatingRegionEnable;
 	libcamera::utils::Duration fixedShutter;
 	double fixedAnalogueGain;
+	unsigned int channel;
 };
 
 struct AgcPrepareStatus {
diff --git a/src/ipa/rpi/controller/rpi/agc.cpp b/src/ipa/rpi/controller/rpi/agc.cpp
index c9c9c297..7e627bba 100644
--- a/src/ipa/rpi/controller/rpi/agc.cpp
+++ b/src/ipa/rpi/controller/rpi/agc.cpp
@@ -22,7 +22,8 @@  LOG_DEFINE_CATEGORY(RPiAgc)
 
 Agc::Agc(Controller *controller)
 	: AgcAlgorithm(controller),
-	  activeChannels_({ 0 })
+	  activeChannels_({ 0 }),
+	  index_(0)
 {
 }
 
@@ -203,20 +204,113 @@  void Agc::setActiveChannels(const std::vector<unsigned int> &activeChannels)
 void Agc::switchMode(CameraMode const &cameraMode,
 		     Metadata *metadata)
 {
-	LOG(RPiAgc, Debug) << "switchMode for channel 0";
-	channelData_[0].channel.switchMode(cameraMode, metadata);
+	/*
+	 * We run switchMode on every channel, and then we're going to start over
+	 * with the first active channel again which means that this channel's
+	 * status needs to be the one we leave in the metadata.
+	 */
+	AgcStatus status;
+
+	for (unsigned int channelIndex = 0; channelIndex < channelData_.size(); channelIndex++) {
+		LOG(RPiAgc, Debug) << "switchMode for channel " << channelIndex;
+		channelData_[channelIndex].channel.switchMode(cameraMode, metadata);
+		if (channelIndex == activeChannels_[0])
+			metadata->get("agc.status", status);
+	}
+
+	status.channel = activeChannels_[0];
+	metadata->set("agc.status", status);
+	index_ = 0;
+}
+
+static void getChannelIndex(Metadata *metadata, const char *message, unsigned int &channelIndex)
+{
+	std::unique_lock<RPiController::Metadata> lock(*metadata);
+	AgcStatus *status = metadata->getLocked<AgcStatus>("agc.delayed_status");
+	if (status)
+		channelIndex = status->channel;
+	else
+		/* This does happen at startup, otherwise it would be a Warning or Error. */
+		LOG(RPiAgc, Debug) << message;
+}
+
+static void setChannelIndex(Metadata *metadata, const char *message, unsigned int channelIndex)
+{
+	std::unique_lock<RPiController::Metadata> lock(*metadata);
+	AgcStatus *status = metadata->getLocked<AgcStatus>("agc.status");
+	if (status)
+		status->channel = channelIndex;
+	else
+		/* This does happen at startup, otherwise it would be a Warning or Error. */
+		LOG(RPiAgc, Debug) << message;
 }
 
 void Agc::prepare(Metadata *imageMetadata)
 {
-	LOG(RPiAgc, Debug) << "prepare for channel 0";
-	channelData_[0].channel.prepare(imageMetadata);
+	/*
+	 * The DeviceStatus in the metadata should be correct for the image we
+	 * are processing. THe delayed status should tell us what channel this frame
+	 * was from, so we will use that channel's prepare method.
+	 *
+	 * \todo To be honest, there's not much that's stateful in the prepare methods
+	 * so we should perhaps re-evaluate whether prepare even needs to be done
+	 * "per channel".
+	 */
+	unsigned int channelIndex = activeChannels_[0];
+	getChannelIndex(imageMetadata, "prepare: no delayed status", channelIndex);
+
+	LOG(RPiAgc, Debug) << "prepare for channel " << channelIndex;
+	channelData_[channelIndex].channel.prepare(imageMetadata);
 }
 
 void Agc::process(StatisticsPtr &stats, Metadata *imageMetadata)
 {
-	LOG(RPiAgc, Debug) << "process for channel 0";
-	channelData_[0].channel.process(stats, imageMetadata);
+	/*
+	 * We want to generate values for the next channel in round robin fashion
+	 * (i.e. the channel at location index_ in the activeChannel list), even though
+	 * the statistics we have will be for a different channel (which we find
+	 * again from the delayed status).
+	 */
+
+	/* Generate updated AGC values for this channel: */
+	unsigned int channelIndex = activeChannels_[index_];
+	AgcChannelData &channelData = channelData_[channelIndex];
+	/* Stats are from this channel: */
+	unsigned int statsIndex = 0;
+	getChannelIndex(imageMetadata, "process: no delayed status for stats", statsIndex);
+	LOG(RPiAgc, Debug) << "process for channel " << channelIndex;
+
+	/*
+	 * We keep a cache of the most recent DeviceStatus and stats for each channel,
+	 * so that we can invoke the next channel's process method with the most up to date
+	 * values.
+	 */
+	LOG(RPiAgc, Debug) << "Save DeviceStatus and stats for channel " << statsIndex;
+	DeviceStatus deviceStatus;
+	if (imageMetadata->get<DeviceStatus>("device.status", deviceStatus) == 0)
+		channelData_[statsIndex].deviceStatus = deviceStatus;
+	else
+		/* Every frame should have a DeviceStatus. */
+		LOG(RPiAgc, Error) << "process: no device status found";
+	channelData_[statsIndex].statistics = stats;
+
+	/*
+	 * Finally fetch the most recent DeviceStatus and stats for this channel, if both
+	 * exist, and call process(). We must make the agc.status metadata record correctly
+	 * which channel this is.
+	 */
+	if (channelData.statistics && channelData.deviceStatus) {
+		deviceStatus = *channelData.deviceStatus;
+		stats = channelData.statistics;
+	} else
+		/* Can also happen when new channels start. */
+		LOG(RPiAgc, Debug) << "process: channel " << channelIndex << " not seen yet";
+
+	channelData.channel.process(stats, &deviceStatus, imageMetadata);
+	setChannelIndex(imageMetadata, "process: no AGC status found", channelIndex);
+
+	/* And onto the next channel for the next call. */
+	index_ = (index_ + 1) % activeChannels_.size();
 }
 
 /* Register algorithm with the system. */
diff --git a/src/ipa/rpi/controller/rpi/agc.h b/src/ipa/rpi/controller/rpi/agc.h
index a9158910..2eed2bab 100644
--- a/src/ipa/rpi/controller/rpi/agc.h
+++ b/src/ipa/rpi/controller/rpi/agc.h
@@ -53,6 +53,7 @@  private:
 	int checkChannel(unsigned int channel) const;
 	std::vector<AgcChannelData> channelData_;
 	std::vector<unsigned int> activeChannels_;
+	unsigned int index_; /* index into the activeChannels_ */
 };
 
 } /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/rpi/agc_channel.cpp b/src/ipa/rpi/controller/rpi/agc_channel.cpp
index d6e30ef2..ddec611f 100644
--- a/src/ipa/rpi/controller/rpi/agc_channel.cpp
+++ b/src/ipa/rpi/controller/rpi/agc_channel.cpp
@@ -447,7 +447,7 @@  void AgcChannel::prepare(Metadata *imageMetadata)
 	}
 }
 
-void AgcChannel::process(StatisticsPtr &stats, Metadata *imageMetadata)
+void AgcChannel::process(StatisticsPtr &stats, const DeviceStatus *deviceStatus, Metadata *imageMetadata)
 {
 	frameCount_++;
 	/*
@@ -458,7 +458,7 @@  void AgcChannel::process(StatisticsPtr &stats, Metadata *imageMetadata)
 	/* Fetch the AWB status immediately, so that we can assume it's there. */
 	fetchAwbStatus(imageMetadata);
 	/* Get the current exposure values for the frame that's just arrived. */
-	fetchCurrentExposure(imageMetadata);
+	fetchCurrentExposure(deviceStatus);
 	/* Compute the total gain we require relative to the current exposure. */
 	double gain, targetY;
 	computeGain(stats, imageMetadata, gain, targetY);
@@ -570,18 +570,13 @@  void AgcChannel::housekeepConfig()
 			   << meteringModeName_;
 }
 
-void AgcChannel::fetchCurrentExposure(Metadata *imageMetadata)
+void AgcChannel::fetchCurrentExposure(const DeviceStatus *deviceStatus)
 {
-	std::unique_lock<Metadata> lock(*imageMetadata);
-	DeviceStatus *deviceStatus =
-		imageMetadata->getLocked<DeviceStatus>("device.status");
 	if (!deviceStatus)
 		LOG(RPiAgc, Fatal) << "No device metadata";
 	current_.shutter = deviceStatus->shutterSpeed;
 	current_.analogueGain = deviceStatus->analogueGain;
-	AgcStatus *agcStatus =
-		imageMetadata->getLocked<AgcStatus>("agc.status");
-	current_.totalExposure = agcStatus ? agcStatus->totalExposureValue : 0s;
+	current_.totalExposure = 0s; /* this value is unused */
 	current_.totalExposureNoDG = current_.shutter * current_.analogueGain;
 }
 
diff --git a/src/ipa/rpi/controller/rpi/agc_channel.h b/src/ipa/rpi/controller/rpi/agc_channel.h
index dc4356f3..0e3d44b0 100644
--- a/src/ipa/rpi/controller/rpi/agc_channel.h
+++ b/src/ipa/rpi/controller/rpi/agc_channel.h
@@ -83,13 +83,13 @@  public:
 	void disableAuto();
 	void switchMode(CameraMode const &cameraMode, Metadata *metadata);
 	void prepare(Metadata *imageMetadata);
-	void process(StatisticsPtr &stats, Metadata *imageMetadata);
+	void process(StatisticsPtr &stats, const DeviceStatus *deviceStatus, Metadata *imageMetadata);
 
 private:
 	bool updateLockStatus(DeviceStatus const &deviceStatus);
 	AgcConfig config_;
 	void housekeepConfig();
-	void fetchCurrentExposure(Metadata *imageMetadata);
+	void fetchCurrentExposure(const DeviceStatus *deviceStatus);
 	void fetchAwbStatus(Metadata *imageMetadata);
 	void computeGain(StatisticsPtr &statistics, Metadata *imageMetadata,
 			 double &gain, double &targetY);