[RFC,v1,18/23] libcamera: pipeline: Fill `MetadataListPlan` of cameras
diff mbox series

Message ID 20250606164156.1442682-19-barnabas.pocze@ideasonboard.com
State New
Headers show
Series
  • libcamera: Add `MetadataList`
Related show

Commit Message

Barnabás Pőcze June 6, 2025, 4:41 p.m. UTC
Fill the newly introduced `MetadataListPlan` member of the camera's private
data during initializations, similarly to the camera's `ControlInfoMap`.

Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
---
 .../internal/software_isp/software_isp.h      |  3 +-
 include/libcamera/ipa/ipu3.mojom              |  3 +-
 include/libcamera/ipa/mali-c55.mojom          |  3 +-
 include/libcamera/ipa/raspberrypi.mojom       |  1 +
 include/libcamera/ipa/rkisp1.mojom            |  2 +-
 include/libcamera/ipa/soft.mojom              |  3 +-
 src/ipa/ipu3/algorithms/agc.cpp               |  4 +++
 src/ipa/ipu3/algorithms/awb.cpp               | 12 +++++++
 src/ipa/ipu3/algorithms/awb.h                 |  1 +
 src/ipa/ipu3/ipa_context.cpp                  |  3 ++
 src/ipa/ipu3/ipa_context.h                    |  3 ++
 src/ipa/ipu3/ipu3.cpp                         |  8 +++--
 src/ipa/mali-c55/algorithms/agc.cpp           |  5 +++
 src/ipa/mali-c55/algorithms/awb.cpp           |  7 ++++
 src/ipa/mali-c55/algorithms/awb.h             |  1 +
 src/ipa/mali-c55/algorithms/blc.cpp           |  2 ++
 src/ipa/mali-c55/ipa_context.h                |  3 ++
 src/ipa/mali-c55/mali-c55.cpp                 |  6 ++--
 src/ipa/rkisp1/algorithms/agc.cpp             | 10 ++++++
 src/ipa/rkisp1/algorithms/awb.cpp             |  4 +++
 src/ipa/rkisp1/algorithms/blc.cpp             |  2 ++
 src/ipa/rkisp1/ipa_context.h                  |  1 +
 src/ipa/rkisp1/rkisp1.cpp                     |  8 +++--
 src/ipa/rpi/common/ipa_base.cpp               | 34 +++++++++++++++++++
 src/ipa/rpi/pisp/pisp.cpp                     |  5 +--
 src/ipa/rpi/vc4/vc4.cpp                       |  4 ++-
 src/ipa/simple/algorithms/agc.cpp             |  8 +++++
 src/ipa/simple/algorithms/agc.h               |  1 +
 src/ipa/simple/algorithms/awb.cpp             |  8 +++++
 src/ipa/simple/algorithms/awb.h               |  1 +
 src/ipa/simple/algorithms/blc.cpp             |  3 ++
 src/ipa/simple/algorithms/ccm.cpp             |  3 ++
 src/ipa/simple/algorithms/lut.cpp             |  3 ++
 src/ipa/simple/ipa_context.h                  |  2 ++
 src/ipa/simple/soft_simple.cpp                |  8 +++--
 src/libcamera/pipeline/imx8-isi/imx8-isi.cpp  |  2 ++
 src/libcamera/pipeline/ipu3/ipu3.cpp          |  7 +++-
 src/libcamera/pipeline/mali-c55/mali-c55.cpp  |  2 +-
 src/libcamera/pipeline/rkisp1/rkisp1.cpp      |  4 ++-
 .../pipeline/rpi/common/pipeline_base.cpp     |  7 ++++
 src/libcamera/pipeline/simple/simple.cpp      |  2 +-
 src/libcamera/pipeline/uvcvideo/uvcvideo.cpp  |  2 ++
 src/libcamera/pipeline/vimc/vimc.cpp          |  2 ++
 .../pipeline/virtual/config_parser.cpp        |  3 ++
 src/libcamera/software_isp/software_isp.cpp   |  6 ++--
 45 files changed, 190 insertions(+), 22 deletions(-)

Comments

Jacopo Mondi June 19, 2025, 1:47 p.m. UTC | #1
Hi Barnabás

On Fri, Jun 06, 2025 at 06:41:51PM +0200, Barnabás Pőcze wrote:
> Fill the newly introduced `MetadataListPlan` member of the camera's private
> data during initializations, similarly to the camera's `ControlInfoMap`.
>
> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
> ---
>  .../internal/software_isp/software_isp.h      |  3 +-
>  include/libcamera/ipa/ipu3.mojom              |  3 +-
>  include/libcamera/ipa/mali-c55.mojom          |  3 +-
>  include/libcamera/ipa/raspberrypi.mojom       |  1 +
>  include/libcamera/ipa/rkisp1.mojom            |  2 +-
>  include/libcamera/ipa/soft.mojom              |  3 +-
>  src/ipa/ipu3/algorithms/agc.cpp               |  4 +++
>  src/ipa/ipu3/algorithms/awb.cpp               | 12 +++++++
>  src/ipa/ipu3/algorithms/awb.h                 |  1 +
>  src/ipa/ipu3/ipa_context.cpp                  |  3 ++
>  src/ipa/ipu3/ipa_context.h                    |  3 ++
>  src/ipa/ipu3/ipu3.cpp                         |  8 +++--
>  src/ipa/mali-c55/algorithms/agc.cpp           |  5 +++
>  src/ipa/mali-c55/algorithms/awb.cpp           |  7 ++++
>  src/ipa/mali-c55/algorithms/awb.h             |  1 +
>  src/ipa/mali-c55/algorithms/blc.cpp           |  2 ++
>  src/ipa/mali-c55/ipa_context.h                |  3 ++
>  src/ipa/mali-c55/mali-c55.cpp                 |  6 ++--
>  src/ipa/rkisp1/algorithms/agc.cpp             | 10 ++++++
>  src/ipa/rkisp1/algorithms/awb.cpp             |  4 +++
>  src/ipa/rkisp1/algorithms/blc.cpp             |  2 ++
>  src/ipa/rkisp1/ipa_context.h                  |  1 +
>  src/ipa/rkisp1/rkisp1.cpp                     |  8 +++--
>  src/ipa/rpi/common/ipa_base.cpp               | 34 +++++++++++++++++++
>  src/ipa/rpi/pisp/pisp.cpp                     |  5 +--
>  src/ipa/rpi/vc4/vc4.cpp                       |  4 ++-
>  src/ipa/simple/algorithms/agc.cpp             |  8 +++++
>  src/ipa/simple/algorithms/agc.h               |  1 +
>  src/ipa/simple/algorithms/awb.cpp             |  8 +++++
>  src/ipa/simple/algorithms/awb.h               |  1 +
>  src/ipa/simple/algorithms/blc.cpp             |  3 ++
>  src/ipa/simple/algorithms/ccm.cpp             |  3 ++
>  src/ipa/simple/algorithms/lut.cpp             |  3 ++
>  src/ipa/simple/ipa_context.h                  |  2 ++
>  src/ipa/simple/soft_simple.cpp                |  8 +++--
>  src/libcamera/pipeline/imx8-isi/imx8-isi.cpp  |  2 ++
>  src/libcamera/pipeline/ipu3/ipu3.cpp          |  7 +++-
>  src/libcamera/pipeline/mali-c55/mali-c55.cpp  |  2 +-
>  src/libcamera/pipeline/rkisp1/rkisp1.cpp      |  4 ++-
>  .../pipeline/rpi/common/pipeline_base.cpp     |  7 ++++
>  src/libcamera/pipeline/simple/simple.cpp      |  2 +-
>  src/libcamera/pipeline/uvcvideo/uvcvideo.cpp  |  2 ++
>  src/libcamera/pipeline/vimc/vimc.cpp          |  2 ++
>  .../pipeline/virtual/config_parser.cpp        |  3 ++
>  src/libcamera/software_isp/software_isp.cpp   |  6 ++--
>  45 files changed, 190 insertions(+), 22 deletions(-)
>
> diff --git a/include/libcamera/internal/software_isp/software_isp.h b/include/libcamera/internal/software_isp/software_isp.h
> index 786246592..bd85f6b4b 100644
> --- a/include/libcamera/internal/software_isp/software_isp.h
> +++ b/include/libcamera/internal/software_isp/software_isp.h
> @@ -41,6 +41,7 @@ class DebayerCpu;
>  class FrameBuffer;
>  class PixelFormat;
>  class Stream;
> +class MetadataListPlan;
>  struct StreamConfiguration;
>
>  LOG_DECLARE_CATEGORY(SoftwareIsp)
> @@ -49,7 +50,7 @@ class SoftwareIsp : public Object
>  {
>  public:
>  	SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,
> -		    ControlInfoMap *ipaControls);
> +		    ControlInfoMap *ipaControls, MetadataListPlan *metadataPlan);
>  	~SoftwareIsp();
>
>  	int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; }
> diff --git a/include/libcamera/ipa/ipu3.mojom b/include/libcamera/ipa/ipu3.mojom
> index d9a50b01d..f49b77797 100644
> --- a/include/libcamera/ipa/ipu3.mojom
> +++ b/include/libcamera/ipa/ipu3.mojom
> @@ -20,7 +20,8 @@ interface IPAIPU3Interface {
>  	init(libcamera.IPASettings settings,
>  	     libcamera.IPACameraSensorInfo sensorInfo,
>  	     libcamera.ControlInfoMap sensorControls)
> -		=> (int32 ret, libcamera.ControlInfoMap ipaControls);
> +		=> (int32 ret, libcamera.ControlInfoMap ipaControls,
> +		    libcamera.MetadataListPlan metadata);
>  	start() => (int32 ret);
>  	stop();
>
> diff --git a/include/libcamera/ipa/mali-c55.mojom b/include/libcamera/ipa/mali-c55.mojom
> index 5d7eb4eef..7229c53e1 100644
> --- a/include/libcamera/ipa/mali-c55.mojom
> +++ b/include/libcamera/ipa/mali-c55.mojom
> @@ -11,7 +11,8 @@ struct IPAConfigInfo {
>
>  interface IPAMaliC55Interface {
>  	init(libcamera.IPASettings settings, IPAConfigInfo configInfo)
> -		=> (int32 ret, libcamera.ControlInfoMap ipaControls);
> +		=> (int32 ret, libcamera.ControlInfoMap ipaControls,
> +		    libcamera.MetadataListPlan metadataPlan);
>  	start() => (int32 ret);
>  	stop();
>
> diff --git a/include/libcamera/ipa/raspberrypi.mojom b/include/libcamera/ipa/raspberrypi.mojom
> index e30c70bde..69160e133 100644
> --- a/include/libcamera/ipa/raspberrypi.mojom
> +++ b/include/libcamera/ipa/raspberrypi.mojom
> @@ -26,6 +26,7 @@ struct InitParams {
>  struct InitResult {
>  	SensorConfig sensorConfig;
>  	libcamera.ControlInfoMap controlInfo;
> +	libcamera.MetadataListPlan metadataPlan;
>  };
>
>  struct BufferIds {
> diff --git a/include/libcamera/ipa/rkisp1.mojom b/include/libcamera/ipa/rkisp1.mojom
> index 043ad27ea..440a59977 100644
> --- a/include/libcamera/ipa/rkisp1.mojom
> +++ b/include/libcamera/ipa/rkisp1.mojom
> @@ -19,7 +19,7 @@ interface IPARkISP1Interface {
>  	     uint32 hwRevision,
>  	     libcamera.IPACameraSensorInfo sensorInfo,
>  	     libcamera.ControlInfoMap sensorControls)
> -		=> (int32 ret, libcamera.ControlInfoMap ipaControls);
> +		=> (int32 ret, libcamera.ControlInfoMap ipaControls, libcamera.MetadataListPlan metadataPlan);

I would break the line like you've done for other mojom files

>  	start() => (int32 ret);
>  	stop();
>
> diff --git a/include/libcamera/ipa/soft.mojom b/include/libcamera/ipa/soft.mojom
> index 77328c5fd..360ed668b 100644
> --- a/include/libcamera/ipa/soft.mojom
> +++ b/include/libcamera/ipa/soft.mojom
> @@ -18,7 +18,8 @@ interface IPASoftInterface {
>  	     libcamera.SharedFD fdParams,
>  	     libcamera.IPACameraSensorInfo sensorInfo,
>  	     libcamera.ControlInfoMap sensorControls)
> -		=> (int32 ret, libcamera.ControlInfoMap ipaControls, bool ccmEnabled);
> +		=> (int32 ret, libcamera.ControlInfoMap ipaControls, bool ccmEnabled,
> +		    libcamera.MetadataListPlan metadataPlan);
>  	start() => (int32 ret);
>  	stop();
>  	configure(IPAConfigInfo configInfo)
> diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp
> index 39d0aebb0..e3b2acbc6 100644
> --- a/src/ipa/ipu3/algorithms/agc.cpp
> +++ b/src/ipa/ipu3/algorithms/agc.cpp
> @@ -82,6 +82,10 @@ int Agc::init(IPAContext &context, const YamlObject &tuningData)
>
>  	context.ctrlMap.merge(controls());
>
> +	context.metadataPlan.add(controls::AnalogueGain);
> +	context.metadataPlan.add(controls::ExposureTime);
> +	context.metadataPlan.add(controls::FrameDuration);
> +
>  	return 0;
>  }
>
> diff --git a/src/ipa/ipu3/algorithms/awb.cpp b/src/ipa/ipu3/algorithms/awb.cpp
> index 55de05d9e..28e29e29c 100644
> --- a/src/ipa/ipu3/algorithms/awb.cpp
> +++ b/src/ipa/ipu3/algorithms/awb.cpp
> @@ -197,6 +197,18 @@ Awb::Awb()
>
>  Awb::~Awb() = default;
>
> +/**
> + * \copydoc libcamera::ipa::Algorithm::init
> + */
> +int Awb::init(IPAContext &context, [[maybe_unused]] const YamlObject &tuningData)
> +{
> +	context.metadataPlan.add(controls::AwbEnable);
> +	context.metadataPlan.add(controls::ColourGains);
> +	context.metadataPlan.add(controls::ColourTemperature);
> +
> +	return 0;
> +}
> +
>  /**
>   * \copydoc libcamera::ipa::Algorithm::configure
>   */
> diff --git a/src/ipa/ipu3/algorithms/awb.h b/src/ipa/ipu3/algorithms/awb.h
> index dbf69c907..1d47bffa3 100644
> --- a/src/ipa/ipu3/algorithms/awb.h
> +++ b/src/ipa/ipu3/algorithms/awb.h
> @@ -40,6 +40,7 @@ public:
>  	Awb();
>  	~Awb();
>
> +	int init(IPAContext &context, const YamlObject &tuningData) override;
>  	int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
>  	void prepare(IPAContext &context, const uint32_t frame,
>  		     IPAFrameContext &frameContext,
> diff --git a/src/ipa/ipu3/ipa_context.cpp b/src/ipa/ipu3/ipa_context.cpp
> index 3b22f7917..f0b8b5dbe 100644
> --- a/src/ipa/ipu3/ipa_context.cpp
> +++ b/src/ipa/ipu3/ipa_context.cpp
> @@ -54,6 +54,9 @@ namespace libcamera::ipa::ipu3 {
>   *
>   * \var IPAContext::ctrlMap
>   * \brief A ControlInfoMap::Map of controls populated by the algorithms
> + *
> + * \var IPAContext::metadataPlan
> + * \brief A MetadataListPlan populated by the algorithms
>   */
>
>  /**
> diff --git a/src/ipa/ipu3/ipa_context.h b/src/ipa/ipu3/ipa_context.h
> index 97fcf06cd..f4f45ef4d 100644
> --- a/src/ipa/ipu3/ipa_context.h
> +++ b/src/ipa/ipu3/ipa_context.h
> @@ -14,6 +14,7 @@
>
>  #include <libcamera/controls.h>
>  #include <libcamera/geometry.h>
> +#include <libcamera/metadata_list_plan.h>
>
>  #include <libipa/fc_queue.h>
>
> @@ -95,6 +96,8 @@ struct IPAContext {
>  	FCQueue<IPAFrameContext> frameContexts;
>
>  	ControlInfoMap::Map ctrlMap;
> +
> +	MetadataListPlan metadataPlan; // TODO: only needed during init(), how could be removed?

Do you think we could remove it by passing metadataPlan to the init()
function of each algorithm ?

>  };
>
>  } /* namespace ipa::ipu3 */
> diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp
> index 1cae08bf2..8d8811f71 100644
> --- a/src/ipa/ipu3/ipu3.cpp
> +++ b/src/ipa/ipu3/ipu3.cpp
> @@ -143,7 +143,8 @@ public:
>  	int init(const IPASettings &settings,
>  		 const IPACameraSensorInfo &sensorInfo,
>  		 const ControlInfoMap &sensorControls,
> -		 ControlInfoMap *ipaControls) override;
> +		 ControlInfoMap *ipaControls,
> +		 MetadataListPlan *metadataPlan) override;
>
>  	int start() override;
>  	void stop() override;
> @@ -299,7 +300,8 @@ void IPAIPU3::updateControls(const IPACameraSensorInfo &sensorInfo,
>  int IPAIPU3::init(const IPASettings &settings,
>  		  const IPACameraSensorInfo &sensorInfo,
>  		  const ControlInfoMap &sensorControls,
> -		  ControlInfoMap *ipaControls)
> +		  ControlInfoMap *ipaControls,
> +		  MetadataListPlan *metadataPlan)
>  {
>  	camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel);
>  	if (camHelper_ == nullptr) {
> @@ -348,6 +350,8 @@ int IPAIPU3::init(const IPASettings &settings,
>  	/* Initialize controls. */
>  	updateControls(sensorInfo, sensorControls, ipaControls);
>
> +	*metadataPlan = std::move(context_.metadataPlan);
> +
>  	return 0;
>  }
>
> diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp
> index 70667db34..bfca8f5a5 100644
> --- a/src/ipa/mali-c55/algorithms/agc.cpp
> +++ b/src/ipa/mali-c55/algorithms/agc.cpp
> @@ -145,6 +145,11 @@ int Agc::init(IPAContext &context, const YamlObject &tuningData)
>  	);
>  	context.ctrlMap.merge(controls());
>
> +	context.metadataPlan.add(controls::ExposureTime);
> +	context.metadataPlan.add(controls::AnalogueGain);
> +	context.metadataPlan.add(controls::DigitalGain);
> +	context.metadataPlan.add(controls::ColourTemperature);
> +
>  	return 0;
>  }
>
> diff --git a/src/ipa/mali-c55/algorithms/awb.cpp b/src/ipa/mali-c55/algorithms/awb.cpp
> index 050b191b7..b5acd4390 100644
> --- a/src/ipa/mali-c55/algorithms/awb.cpp
> +++ b/src/ipa/mali-c55/algorithms/awb.cpp
> @@ -29,6 +29,13 @@ Awb::Awb()
>  {
>  }
>
> +int Awb::init(IPAContext &context, [[maybe_unused]] const YamlObject &tuningData)
> +{
> +	context.metadataPlan.add(controls::ColourGains);
> +
> +	return 0;
> +}
> +
>  int Awb::configure([[maybe_unused]] IPAContext &context,
>  		   [[maybe_unused]] const IPACameraSensorInfo &configInfo)
>  {
> diff --git a/src/ipa/mali-c55/algorithms/awb.h b/src/ipa/mali-c55/algorithms/awb.h
> index 800c2e834..db59e8d79 100644
> --- a/src/ipa/mali-c55/algorithms/awb.h
> +++ b/src/ipa/mali-c55/algorithms/awb.h
> @@ -18,6 +18,7 @@ public:
>  	Awb();
>  	~Awb() = default;
>
> +	int init(IPAContext &context, const YamlObject &tuningData) override;
>  	int configure(IPAContext &context,
>  		      const IPACameraSensorInfo &configInfo) override;
>  	void prepare(IPAContext &context, const uint32_t frame,
> diff --git a/src/ipa/mali-c55/algorithms/blc.cpp b/src/ipa/mali-c55/algorithms/blc.cpp
> index 2a54c86a9..0aaaae7dd 100644
> --- a/src/ipa/mali-c55/algorithms/blc.cpp
> +++ b/src/ipa/mali-c55/algorithms/blc.cpp
> @@ -51,6 +51,8 @@ int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context,
>
>  	tuningParameters_ = true;
>
> +	context.metadataPlan.add(controls::SensorBlackLevels);
> +
>  	LOG(MaliC55Blc, Debug)
>  		<< "Black levels: 00 " << offset00 << ", 01 " << offset01
>  		<< ", 10 " << offset10 << ", 11 " << offset11;
> diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h
> index 5e3e2fbde..d76fdcb2b 100644
> --- a/src/ipa/mali-c55/ipa_context.h
> +++ b/src/ipa/mali-c55/ipa_context.h
> @@ -9,6 +9,7 @@
>
>  #include <libcamera/base/utils.h>
>  #include <libcamera/controls.h>
> +#include <libcamera/metadata_list_plan.h>
>
>  #include "libcamera/internal/bayer_format.h"
>
> @@ -83,6 +84,8 @@ struct IPAContext {
>  	FCQueue<IPAFrameContext> frameContexts;
>
>  	ControlInfoMap::Map ctrlMap;
> +
> +	MetadataListPlan metadataPlan; // TODO: only needed during init(), how could be removed?
>  };
>
>  } /* namespace ipa::mali_c55 */
> diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp
> index c6941a950..7006ae55c 100644
> --- a/src/ipa/mali-c55/mali-c55.cpp
> +++ b/src/ipa/mali-c55/mali-c55.cpp
> @@ -46,7 +46,7 @@ public:
>  	IPAMaliC55();
>
>  	int init(const IPASettings &settings, const IPAConfigInfo &ipaConfig,
> -		 ControlInfoMap *ipaControls) override;
> +		 ControlInfoMap *ipaControls, MetadataListPlan *metadataPlan) override;
>  	int start() override;
>  	void stop() override;
>  	int configure(const IPAConfigInfo &ipaConfig, uint8_t bayerOrder,
> @@ -96,7 +96,7 @@ std::string IPAMaliC55::logPrefix() const
>  }
>
>  int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig,
> -		     ControlInfoMap *ipaControls)
> +		     ControlInfoMap *ipaControls, MetadataListPlan *metadataPlan)
>  {
>  	camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel);
>  	if (!camHelper_) {
> @@ -131,6 +131,8 @@ int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig
>
>  	updateControls(ipaConfig.sensorInfo, ipaConfig.sensorControls, ipaControls);
>
> +	*metadataPlan = std::move(context_.metadataPlan);
> +
>  	return 0;
>  }
>
> diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
> index 137a07500..22db11b24 100644
> --- a/src/ipa/rkisp1/algorithms/agc.cpp
> +++ b/src/ipa/rkisp1/algorithms/agc.cpp
> @@ -159,6 +159,16 @@ int Agc::init(IPAContext &context, const YamlObject &tuningData)
>  	context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true, true);
>  	context.ctrlMap.merge(controls());
>
> +	context.metadataPlan.add(controls::AnalogueGain);
> +	context.metadataPlan.add(controls::ExposureTime);
> +	context.metadataPlan.add(controls::FrameDuration);
> +	context.metadataPlan.add(controls::FrameDuration);

Double entry

> +	context.metadataPlan.add(controls::ExposureTimeMode);
> +	context.metadataPlan.add(controls::AnalogueGainMode);
> +	context.metadataPlan.add(controls::AeMeteringMode);
> +	context.metadataPlan.add(controls::AeExposureMode);
> +	context.metadataPlan.add(controls::AeConstraintMode);
> +
>  	return 0;
>  }
>
> diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp
> index 399fb51be..aaaa3cced 100644
> --- a/src/ipa/rkisp1/algorithms/awb.cpp
> +++ b/src/ipa/rkisp1/algorithms/awb.cpp
> @@ -117,6 +117,10 @@ int Awb::init(IPAContext &context, const YamlObject &tuningData)
>  	const auto &src = awbAlgo_->controls();
>  	cmap.insert(src.begin(), src.end());
>
> +	context.metadataPlan.add(controls::AwbEnable);
> +	context.metadataPlan.add(controls::ColourGains);
> +	context.metadataPlan.add(controls::ColourTemperature);
> +
>  	return 0;
>  }
>
> diff --git a/src/ipa/rkisp1/algorithms/blc.cpp b/src/ipa/rkisp1/algorithms/blc.cpp
> index 98cb7145e..3288f24e3 100644
> --- a/src/ipa/rkisp1/algorithms/blc.cpp
> +++ b/src/ipa/rkisp1/algorithms/blc.cpp
> @@ -103,6 +103,8 @@ int BlackLevelCorrection::init(IPAContext &context, const YamlObject &tuningData
>  		<< ", green (blue) " << blackLevelGreenB_
>  		<< ", blue " << blackLevelBlue_;
>
> +	context.metadataPlan.add(controls::SensorBlackLevels);
> +
>  	return 0;
>  }

I also see the following metadata populated by algorithms in the
RkISP1 IPA

src/ipa/rkisp1/algorithms/ccm.cpp:      metadata.set(controls::ColourCorrectionMatrix, frameContext.ccm.ccm.data());
src/ipa/rkisp1/algorithms/goc.cpp:      metadata.set(controls::Gamma, frameContext.goc.gamma);
src/ipa/rkisp1/algorithms/lux.cpp:      metadata.set(controls::Lux, lux);

>
> diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
> index f0d504215..cd4b98921 100644
> --- a/src/ipa/rkisp1/ipa_context.h
> +++ b/src/ipa/rkisp1/ipa_context.h
> @@ -200,6 +200,7 @@ struct IPAContext {
>  	FCQueue<IPAFrameContext> frameContexts;
>
>  	ControlInfoMap::Map ctrlMap;
> +	MetadataListPlan metadataPlan; // TODO: only needed during init(), how could be removed?
>
>  	DebugMetadata debugMetadata;
>
> diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp
> index 1ed7d7d92..88c454ea1 100644
> --- a/src/ipa/rkisp1/rkisp1.cpp
> +++ b/src/ipa/rkisp1/rkisp1.cpp
> @@ -54,7 +54,8 @@ public:
>  	int init(const IPASettings &settings, unsigned int hwRevision,
>  		 const IPACameraSensorInfo &sensorInfo,
>  		 const ControlInfoMap &sensorControls,
> -		 ControlInfoMap *ipaControls) override;
> +		 ControlInfoMap *ipaControls,
> +		 MetadataListPlan *metadataPlan) override;
>  	int start() override;
>  	void stop() override;
>
> @@ -135,7 +136,8 @@ std::string IPARkISP1::logPrefix() const
>  int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision,
>  		    const IPACameraSensorInfo &sensorInfo,
>  		    const ControlInfoMap &sensorControls,
> -		    ControlInfoMap *ipaControls)
> +		    ControlInfoMap *ipaControls,
> +		    MetadataListPlan *metadataPlan)
>  {
>  	/* \todo Add support for other revisions */
>  	switch (hwRevision) {
> @@ -204,6 +206,8 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision,
>  	/* Initialize controls. */
>  	updateControls(sensorInfo, sensorControls, ipaControls);
>
> +	*metadataPlan = std::move(context_.metadataPlan);
> +
>  	return 0;
>  }
>
> diff --git a/src/ipa/rpi/common/ipa_base.cpp b/src/ipa/rpi/common/ipa_base.cpp
> index e0f8b7e78..032d6c43b 100644
> --- a/src/ipa/rpi/common/ipa_base.cpp
> +++ b/src/ipa/rpi/common/ipa_base.cpp
> @@ -180,6 +180,40 @@ int32_t IpaBase::init(const IPASettings &settings, const InitParams &params, Ini
>
>  	result->controlInfo = ControlInfoMap(std::move(ctrlMap), controls::controls);
>
> +	// TODO: only set those that can be reported by configured algorithms?


mmm, we don't have the equivalent of Algorithm::init() for rpi, right ?

> +	// TODO: move this somewhere else?

Where would you propse ?

> +	result->metadataPlan.add(controls::AnalogueGainMode);
> +	result->metadataPlan.add(controls::ExposureTimeMode);
> +	result->metadataPlan.add(controls::ExposureTime);
> +	result->metadataPlan.add(controls::AnalogueGain);
> +	result->metadataPlan.add(controls::AeMeteringMode);
> +	result->metadataPlan.add(controls::AeConstraintMode);
> +	result->metadataPlan.add(controls::AeExposureMode);
> +	result->metadataPlan.add(controls::ExposureValue);
> +	result->metadataPlan.add(controls::AwbEnable);
> +	result->metadataPlan.add(controls::AwbMode);
> +	result->metadataPlan.add(controls::ColourGains);
> +	result->metadataPlan.add(controls::Brightness);
> +	result->metadataPlan.add(controls::Contrast);
> +	result->metadataPlan.add(controls::Saturation);
> +	result->metadataPlan.add(controls::Sharpness);
> +	result->metadataPlan.add(controls::draft::NoiseReductionMode);
> +	result->metadataPlan.add(controls::FrameDuration);
> +	result->metadataPlan.add(controls::SensorTemperature);
> +	result->metadataPlan.add(controls::LensPosition);
> +	result->metadataPlan.add(controls::DigitalGain);
> +	result->metadataPlan.add(controls::AeState);
> +	result->metadataPlan.add(controls::Lux);
> +	result->metadataPlan.add(controls::ColourTemperature);
> +	result->metadataPlan.add(controls::SensorBlackLevels);
> +	result->metadataPlan.add(controls::FocusFoM);
> +	result->metadataPlan.add(controls::ColourCorrectionMatrix);
> +	result->metadataPlan.add(controls::AfState);
> +	result->metadataPlan.add(controls::AfPauseState);
> +	result->metadataPlan.add(controls::HdrMode);
> +	result->metadataPlan.add(controls::HdrChannel);
> +	result->metadataPlan.add(controls::FrameDurationLimits);
> +
>  	return platformInit(params, result);
>  }
>
> diff --git a/src/ipa/rpi/pisp/pisp.cpp b/src/ipa/rpi/pisp/pisp.cpp
> index bb50a9e05..a640bec32 100644
> --- a/src/ipa/rpi/pisp/pisp.cpp
> +++ b/src/ipa/rpi/pisp/pisp.cpp
> @@ -267,8 +267,7 @@ private:
>  	HdrStatus lastStitchHdrStatus_;
>  };
>
> -int32_t IpaPiSP::platformInit(const InitParams &params,
> -			      [[maybe_unused]] InitResult *result)
> +int32_t IpaPiSP::platformInit(const InitParams &params, InitResult *result)
>  {
>  	const std::string &target = controller_.getTarget();
>  	if (target != "pisp") {
> @@ -301,6 +300,8 @@ int32_t IpaPiSP::platformInit(const InitParams &params,
>
>  	setDefaultConfig();
>
> +	result->metadataPlan.add(controls::rpi::PispStatsOutput, sizeof(pisp_statistics));
> +
>  	return 0;
>  }
>
> diff --git a/src/ipa/rpi/vc4/vc4.cpp b/src/ipa/rpi/vc4/vc4.cpp
> index ba43e4741..d02e06589 100644
> --- a/src/ipa/rpi/vc4/vc4.cpp
> +++ b/src/ipa/rpi/vc4/vc4.cpp
> @@ -83,7 +83,7 @@ private:
>  	void *lsTable_;
>  };
>
> -int32_t IpaVc4::platformInit([[maybe_unused]] const InitParams &params, [[maybe_unused]] InitResult *result)
> +int32_t IpaVc4::platformInit([[maybe_unused]] const InitParams &params, InitResult *result)
>  {
>  	const std::string &target = controller_.getTarget();
>
> @@ -94,6 +94,8 @@ int32_t IpaVc4::platformInit([[maybe_unused]] const InitParams &params, [[maybe_
>  		return -EINVAL;
>  	}
>
> +	result->metadataPlan.add(controls::rpi::Bcm2835StatsOutput, sizeof(bcm2835_isp_stats));
> +
>  	return 0;
>  }
>
> diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp
> index c46bb0ebe..47a499be6 100644
> --- a/src/ipa/simple/algorithms/agc.cpp
> +++ b/src/ipa/simple/algorithms/agc.cpp
> @@ -41,6 +41,14 @@ Agc::Agc()
>  {
>  }
>
> +int Agc::init(IPAContext &context, [[maybe_unused]] const YamlObject &tuningData)
> +{
> +	context.metadataPlan.add(controls::ExposureTime);
> +	context.metadataPlan.add(controls::AnalogueGain);
> +
> +	return 0;
> +}
> +
>  void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV)
>  {
>  	/*
> diff --git a/src/ipa/simple/algorithms/agc.h b/src/ipa/simple/algorithms/agc.h
> index 112d9f5a1..00e70ea70 100644
> --- a/src/ipa/simple/algorithms/agc.h
> +++ b/src/ipa/simple/algorithms/agc.h
> @@ -19,6 +19,7 @@ public:
>  	Agc();
>  	~Agc() = default;
>
> +	int init(IPAContext &context, const YamlObject &tuningData) override;
>  	void process(IPAContext &context, const uint32_t frame,
>  		     IPAFrameContext &frameContext,
>  		     const SwIspStats *stats,
> diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp
> index cf567e894..cce4585f9 100644
> --- a/src/ipa/simple/algorithms/awb.cpp
> +++ b/src/ipa/simple/algorithms/awb.cpp
> @@ -25,6 +25,14 @@ LOG_DEFINE_CATEGORY(IPASoftAwb)
>
>  namespace ipa::soft::algorithms {
>
> +int Awb::init(IPAContext &context, [[maybe_unused]] const YamlObject &tuningData)
> +{
> +	context.metadataPlan.add(controls::ColourGains);
> +	context.metadataPlan.add(controls::ColourTemperature);
> +
> +	return 0;
> +}
> +
>  int Awb::configure(IPAContext &context,
>  		   [[maybe_unused]] const IPAConfigInfo &configInfo)
>  {
> diff --git a/src/ipa/simple/algorithms/awb.h b/src/ipa/simple/algorithms/awb.h
> index ad993f39c..b8ae63dcb 100644
> --- a/src/ipa/simple/algorithms/awb.h
> +++ b/src/ipa/simple/algorithms/awb.h
> @@ -19,6 +19,7 @@ public:
>  	Awb() = default;
>  	~Awb() = default;
>
> +	int init(IPAContext &context, const YamlObject &tuningData) override;
>  	int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
>  	void prepare(IPAContext &context,
>  		     const uint32_t frame,
> diff --git a/src/ipa/simple/algorithms/blc.cpp b/src/ipa/simple/algorithms/blc.cpp
> index 8c1e9ed08..4b9284e54 100644
> --- a/src/ipa/simple/algorithms/blc.cpp
> +++ b/src/ipa/simple/algorithms/blc.cpp
> @@ -34,6 +34,9 @@ int BlackLevel::init([[maybe_unused]] IPAContext &context,
>  		 */
>  		definedLevel_ = blackLevel.value() >> 8;
>  	}
> +
> +	context.metadataPlan.add(controls::SensorBlackLevels);
> +
>  	return 0;
>  }
>
> diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp
> index 0a98406c1..3cab8eaa4 100644
> --- a/src/ipa/simple/algorithms/ccm.cpp
> +++ b/src/ipa/simple/algorithms/ccm.cpp
> @@ -39,6 +39,9 @@ int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData
>  	context.ccmEnabled = true;
>  	context.ctrlMap[&controls::Saturation] = ControlInfo(0.0f, 2.0f, 1.0f);
>
> +	context.metadataPlan.add(controls::ColourCorrectionMatrix);
> +	context.metadataPlan.add(controls::Saturation);
> +
>  	return 0;
>  }
>
> diff --git a/src/ipa/simple/algorithms/lut.cpp b/src/ipa/simple/algorithms/lut.cpp
> index d1d5f7271..0f99be544 100644
> --- a/src/ipa/simple/algorithms/lut.cpp
> +++ b/src/ipa/simple/algorithms/lut.cpp
> @@ -28,6 +28,9 @@ int Lut::init(IPAContext &context,
>  	      [[maybe_unused]] const YamlObject &tuningData)
>  {
>  	context.ctrlMap[&controls::Contrast] = ControlInfo(0.0f, 2.0f, 1.0f);
> +
> +	context.metadataPlan.add(controls::Contrast);
> +
>  	return 0;
>  }
>
> diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h
> index a471b80ae..f4ecf43db 100644
> --- a/src/ipa/simple/ipa_context.h
> +++ b/src/ipa/simple/ipa_context.h
> @@ -12,6 +12,7 @@
>  #include <stdint.h>
>
>  #include <libcamera/controls.h>
> +#include <libcamera/metadata_list_plan.h>
>
>  #include "libcamera/internal/matrix.h"
>  #include "libcamera/internal/vector.h"
> @@ -97,6 +98,7 @@ struct IPAContext {
>  	IPAActiveState activeState;
>  	FCQueue<IPAFrameContext> frameContexts;
>  	ControlInfoMap::Map ctrlMap;
> +	MetadataListPlan metadataPlan; // TODO: only needed during init(), how could be removed?
>  	bool ccmEnabled = false;
>  };
>
> diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp
> index c94c4cd55..db721199d 100644
> --- a/src/ipa/simple/soft_simple.cpp
> +++ b/src/ipa/simple/soft_simple.cpp
> @@ -56,7 +56,8 @@ public:
>  		 const IPACameraSensorInfo &sensorInfo,
>  		 const ControlInfoMap &sensorControls,
>  		 ControlInfoMap *ipaControls,
> -		 bool *ccmEnabled) override;
> +		 bool *ccmEnabled,
> +		 MetadataListPlan *metadataPlan) override;
>  	int configure(const IPAConfigInfo &configInfo) override;
>
>  	int start() override;
> @@ -96,7 +97,8 @@ int IPASoftSimple::init(const IPASettings &settings,
>  			const IPACameraSensorInfo &sensorInfo,
>  			const ControlInfoMap &sensorControls,
>  			ControlInfoMap *ipaControls,
> -			bool *ccmEnabled)
> +			bool *ccmEnabled,
> +			MetadataListPlan *metadataPlan)
>  {
>  	camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel);
>  	if (!camHelper_) {
> @@ -190,6 +192,8 @@ int IPASoftSimple::init(const IPASettings &settings,
>  		return -EINVAL;
>  	}
>
> +	*metadataPlan = std::move(context_.metadataPlan);
> +
>  	return 0;
>  }
>
> diff --git a/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp b/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp
> index ecda426a6..1c6da2006 100644
> --- a/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp
> +++ b/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp
> @@ -166,6 +166,8 @@ int ISICameraData::init()
>
>  	properties_ = sensor_->properties();
>
> +	metadataPlan_.add(controls::SensorTimestamp);
> +
>  	return 0;
>  }
>
> diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
> index e31e3879d..b4a4283f4 100644
> --- a/src/libcamera/pipeline/ipu3/ipu3.cpp
> +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
> @@ -1078,6 +1078,11 @@ int PipelineHandlerIPU3::registerCameras()
>  		if (ret)
>  			continue;
>
> +		data->metadataPlan_.add(controls::draft::PipelineDepth);
> +		data->metadataPlan_.add(controls::draft::TestPatternMode);
> +		data->metadataPlan_.add(controls::ScalerCrop);
> +		data->metadataPlan_.add(controls::SensorTimestamp);
> +
>  		const CameraSensorProperties::SensorDelays &delays = cio2->sensor()->sensorDelays();
>  		std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
>  			{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
> @@ -1187,7 +1192,7 @@ int IPU3CameraData::loadIPA()
>  		ipa_->configurationFile(sensor->model() + ".yaml", "uncalibrated.yaml");
>
>  	ret = ipa_->init(IPASettings{ ipaTuningFile, sensor->model() },
> -			 sensorInfo, sensor->controls(), &ipaControls_);
> +			 sensorInfo, sensor->controls(), &ipaControls_, &metadataPlan_);
>  	if (ret) {
>  		LOG(IPU3, Error) << "Failed to initialise the IPU3 IPA";
>  		return ret;
> diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
> index 4acc091bd..19980f6d2 100644
> --- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp
> +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
> @@ -402,7 +402,7 @@ int MaliC55CameraData::loadIPA()
>
>  	ControlInfoMap ipaControls;
>  	ret = ipa_->init({ ipaTuningFile, sensor_->model() }, ipaConfig,
> -			 &ipaControls);
> +			 &ipaControls, &metadataPlan_);
>  	if (ret) {
>  		LOG(MaliC55, Error) << "Failed to initialise the Mali-C55 IPA";
>  		return ret;
> diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
> index 675f0a749..34be87087 100644
> --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp
> +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
> @@ -394,7 +394,7 @@ int RkISP1CameraData::loadIPA(unsigned int hwRevision)
>  	}
>
>  	ret = ipa_->init({ ipaTuningFile, sensor_->model() }, hwRevision,
> -			 sensorInfo, sensor_->controls(), &ipaControls_);
> +			 sensorInfo, sensor_->controls(), &ipaControls_, &metadataPlan_);
>  	if (ret < 0) {
>  		LOG(RkISP1, Error) << "IPA initialization failure";
>  		return ret;
> @@ -1333,6 +1333,8 @@ int PipelineHandlerRkISP1::createCamera(MediaEntity *sensor)
>
>  	updateControls(data.get());
>
> +	data->metadataPlan_.add(controls::SensorTimestamp);
> +
>  	std::set<Stream *> streams{
>  		&data->mainPathStream_,
>  		&data->selfPathStream_,
> diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
> index 1f13e5230..98507a152 100644
> --- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
> +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
> @@ -589,6 +589,9 @@ int PipelineHandlerBase::configure(Camera *camera, CameraConfiguration *config)
>
>  	data->controlInfo_ = ControlInfoMap(std::move(ctrlMap), result.controlInfo.idmap());
>
> +	/* Update `rpi::ScalerCrops` size for the corrent configuration. */
> +	data->metadataPlan_.add(controls::rpi::ScalerCrops, config->size());
> +
>  	/* Setup the Video Mux/Bridge entities. */
>  	for (auto &[device, link] : data->bridgeDevices_) {
>  		/*
> @@ -832,6 +835,10 @@ int PipelineHandlerBase::registerCamera(std::unique_ptr<RPi::CameraData> &camera
>  	/* Initialize the camera properties. */
>  	data->properties_ = data->sensor_->properties();
>
> +	data->metadataPlan_ = std::move(result.metadataPlan);
> +	data->metadataPlan_.add(controls::SensorTimestamp);
> +	data->metadataPlan_.add(controls::ScalerCrop);
> +
>  	/*
>  	 * The V4L2_CID_NOTIFY_GAINS control, if present, is used to inform the
>  	 * sensor of the colour gains. It is defined to be a linear gain where
> diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
> index efb07051b..05387ca7c 100644
> --- a/src/libcamera/pipeline/simple/simple.cpp
> +++ b/src/libcamera/pipeline/simple/simple.cpp
> @@ -592,7 +592,7 @@ int SimpleCameraData::init()
>  	 * Instantiate Soft ISP if this is enabled for the given driver and no converter is used.
>  	 */
>  	if (!converter_ && pipe->swIspEnabled()) {
> -		swIsp_ = std::make_unique<SoftwareIsp>(pipe, sensor_.get(), &controlInfo_);
> +		swIsp_ = std::make_unique<SoftwareIsp>(pipe, sensor_.get(), &controlInfo_, &metadataPlan_);
>  		if (!swIsp_->isValid()) {
>  			LOG(SimplePipeline, Warning)
>  				<< "Failed to create software ISP, disabling software debayering";
> diff --git a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
> index 58aa0eb4c..e0036e3b5 100644
> --- a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
> +++ b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
> @@ -603,6 +603,8 @@ int UVCCameraData::init(MediaDevice *media)
>
>  	controlInfo_ = ControlInfoMap(std::move(ctrls), controls::controls);
>
> +	metadataPlan_.add(controls::SensorTimestamp);
> +
>  	/*
>  	 * Close to allow camera to go into runtime-suspend, video_ will be
>  	 * re-opened from acquireDevice() and validate().
> diff --git a/src/libcamera/pipeline/vimc/vimc.cpp b/src/libcamera/pipeline/vimc/vimc.cpp
> index 07273bd2b..f8a29da41 100644
> --- a/src/libcamera/pipeline/vimc/vimc.cpp
> +++ b/src/libcamera/pipeline/vimc/vimc.cpp
> @@ -590,6 +590,8 @@ int VimcCameraData::init()
>
>  	controlInfo_ = ControlInfoMap(std::move(ctrls), controls::controls);
>
> +	metadataPlan_.add(controls::SensorTimestamp);
> +
>  	/* Initialize the camera properties. */
>  	properties_ = sensor_->properties();
>
> diff --git a/src/libcamera/pipeline/virtual/config_parser.cpp b/src/libcamera/pipeline/virtual/config_parser.cpp
> index 1d3d9ba87..f10aea7bf 100644
> --- a/src/libcamera/pipeline/virtual/config_parser.cpp
> +++ b/src/libcamera/pipeline/virtual/config_parser.cpp
> @@ -65,6 +65,9 @@ ConfigParser::parseConfigFile(File &file, PipelineHandler *pipe)
>  		controls[&controls::draft::FaceDetectMode] = ControlInfo(supportedFaceDetectModes);
>
>  		data->controlInfo_ = ControlInfoMap(std::move(controls), controls::controls);
> +
> +		data->metadataPlan_.add(controls::SensorTimestamp);
> +
>  		configurations.push_back(std::move(data));
>  	}
>
> diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
> index 28e2a360e..6da69daf9 100644
> --- a/src/libcamera/software_isp/software_isp.cpp
> +++ b/src/libcamera/software_isp/software_isp.cpp
> @@ -71,10 +71,11 @@ LOG_DEFINE_CATEGORY(SoftwareIsp)
>   * \param[in] pipe The pipeline handler in use
>   * \param[in] sensor Pointer to the CameraSensor instance owned by the pipeline
>   * \param[out] ipaControls The IPA controls to update
> + * \param[out] metadataPlan The metadata plan to update
>   * handler
>   */
>  SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,
> -			 ControlInfoMap *ipaControls)
> +			 ControlInfoMap *ipaControls, MetadataListPlan *metadataPlan)
>  	: dmaHeap_(DmaBufAllocator::DmaBufAllocatorFlag::CmaHeap |
>  		   DmaBufAllocator::DmaBufAllocatorFlag::SystemHeap |
>  		   DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf)
> @@ -146,7 +147,8 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,
>  			 sensorInfo,
>  			 sensor->controls(),
>  			 ipaControls,
> -			 &ccmEnabled_);
> +			 &ccmEnabled_,
> +			 metadataPlan);
>  	if (ret) {
>  		LOG(SoftwareIsp, Error) << "IPA init failed";
>  		debayer_.reset();

With the above fixed
Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>

Thanks
  j

> --
> 2.49.0
>
Barnabás Pőcze June 19, 2025, 2:35 p.m. UTC | #2
Hi

2025. 06. 19. 15:47 keltezéssel, Jacopo Mondi írta:
> Hi Barnabás
> 
> On Fri, Jun 06, 2025 at 06:41:51PM +0200, Barnabás Pőcze wrote:
>> Fill the newly introduced `MetadataListPlan` member of the camera's private
>> data during initializations, similarly to the camera's `ControlInfoMap`.
>>
>> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
>> ---
> [...]
>> diff --git a/include/libcamera/ipa/raspberrypi.mojom b/include/libcamera/ipa/raspberrypi.mojom
>> index e30c70bde..69160e133 100644
>> --- a/include/libcamera/ipa/raspberrypi.mojom
>> +++ b/include/libcamera/ipa/raspberrypi.mojom
>> @@ -26,6 +26,7 @@ struct InitParams {
>>   struct InitResult {
>>   	SensorConfig sensorConfig;
>>   	libcamera.ControlInfoMap controlInfo;
>> +	libcamera.MetadataListPlan metadataPlan;
>>   };
>>
>>   struct BufferIds {
>> diff --git a/include/libcamera/ipa/rkisp1.mojom b/include/libcamera/ipa/rkisp1.mojom
>> index 043ad27ea..440a59977 100644
>> --- a/include/libcamera/ipa/rkisp1.mojom
>> +++ b/include/libcamera/ipa/rkisp1.mojom
>> @@ -19,7 +19,7 @@ interface IPARkISP1Interface {
>>   	     uint32 hwRevision,
>>   	     libcamera.IPACameraSensorInfo sensorInfo,
>>   	     libcamera.ControlInfoMap sensorControls)
>> -		=> (int32 ret, libcamera.ControlInfoMap ipaControls);
>> +		=> (int32 ret, libcamera.ControlInfoMap ipaControls, libcamera.MetadataListPlan metadataPlan);
> 
> I would break the line like you've done for other mojom files

Done.


> 
>>   	start() => (int32 ret);
>>   	stop();
>>
> [...]
>> diff --git a/src/ipa/ipu3/ipa_context.h b/src/ipa/ipu3/ipa_context.h
>> index 97fcf06cd..f4f45ef4d 100644
>> --- a/src/ipa/ipu3/ipa_context.h
>> +++ b/src/ipa/ipu3/ipa_context.h
>> @@ -14,6 +14,7 @@
>>
>>   #include <libcamera/controls.h>
>>   #include <libcamera/geometry.h>
>> +#include <libcamera/metadata_list_plan.h>
>>
>>   #include <libipa/fc_queue.h>
>>
>> @@ -95,6 +96,8 @@ struct IPAContext {
>>   	FCQueue<IPAFrameContext> frameContexts;
>>
>>   	ControlInfoMap::Map ctrlMap;
>> +
>> +	MetadataListPlan metadataPlan; // TODO: only needed during init(), how could be removed?
> 
> Do you think we could remove it by passing metadataPlan to the init()
> function of each algorithm ?

The arguments are controlled by the generic `Algorithm` template,
and I did not want to make modifications there.


> 
>>   };
>>
>>   } /* namespace ipa::ipu3 */
> [...]
>> diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
>> index 137a07500..22db11b24 100644
>> --- a/src/ipa/rkisp1/algorithms/agc.cpp
>> +++ b/src/ipa/rkisp1/algorithms/agc.cpp
>> @@ -159,6 +159,16 @@ int Agc::init(IPAContext &context, const YamlObject &tuningData)
>>   	context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true, true);
>>   	context.ctrlMap.merge(controls());
>>
>> +	context.metadataPlan.add(controls::AnalogueGain);
>> +	context.metadataPlan.add(controls::ExposureTime);
>> +	context.metadataPlan.add(controls::FrameDuration);
>> +	context.metadataPlan.add(controls::FrameDuration);
> 
> Double entry

Done.


> 
>> +	context.metadataPlan.add(controls::ExposureTimeMode);
>> +	context.metadataPlan.add(controls::AnalogueGainMode);
>> +	context.metadataPlan.add(controls::AeMeteringMode);
>> +	context.metadataPlan.add(controls::AeExposureMode);
>> +	context.metadataPlan.add(controls::AeConstraintMode);
>> +
>>   	return 0;
>>   }
>>
>> diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp
>> index 399fb51be..aaaa3cced 100644
>> --- a/src/ipa/rkisp1/algorithms/awb.cpp
>> +++ b/src/ipa/rkisp1/algorithms/awb.cpp
>> @@ -117,6 +117,10 @@ int Awb::init(IPAContext &context, const YamlObject &tuningData)
>>   	const auto &src = awbAlgo_->controls();
>>   	cmap.insert(src.begin(), src.end());
>>
>> +	context.metadataPlan.add(controls::AwbEnable);
>> +	context.metadataPlan.add(controls::ColourGains);
>> +	context.metadataPlan.add(controls::ColourTemperature);
>> +
>>   	return 0;
>>   }
>>
>> diff --git a/src/ipa/rkisp1/algorithms/blc.cpp b/src/ipa/rkisp1/algorithms/blc.cpp
>> index 98cb7145e..3288f24e3 100644
>> --- a/src/ipa/rkisp1/algorithms/blc.cpp
>> +++ b/src/ipa/rkisp1/algorithms/blc.cpp
>> @@ -103,6 +103,8 @@ int BlackLevelCorrection::init(IPAContext &context, const YamlObject &tuningData
>>   		<< ", green (blue) " << blackLevelGreenB_
>>   		<< ", blue " << blackLevelBlue_;
>>
>> +	context.metadataPlan.add(controls::SensorBlackLevels);
>> +
>>   	return 0;
>>   }
> 
> I also see the following metadata populated by algorithms in the
> RkISP1 IPA
> 
> src/ipa/rkisp1/algorithms/ccm.cpp:      metadata.set(controls::ColourCorrectionMatrix, frameContext.ccm.ccm.data());
> src/ipa/rkisp1/algorithms/goc.cpp:      metadata.set(controls::Gamma, frameContext.goc.gamma);
> src/ipa/rkisp1/algorithms/lux.cpp:      metadata.set(controls::Lux, lux);

Oops... you're right.


> 
>>
>> diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
>> index f0d504215..cd4b98921 100644
>> --- a/src/ipa/rkisp1/ipa_context.h
>> +++ b/src/ipa/rkisp1/ipa_context.h
>> @@ -200,6 +200,7 @@ struct IPAContext {
>>   	FCQueue<IPAFrameContext> frameContexts;
>>
>>   	ControlInfoMap::Map ctrlMap;
>> +	MetadataListPlan metadataPlan; // TODO: only needed during init(), how could be removed?
>>
>>   	DebugMetadata debugMetadata;
>>
>> diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp
>> index 1ed7d7d92..88c454ea1 100644
>> --- a/src/ipa/rkisp1/rkisp1.cpp
>> +++ b/src/ipa/rkisp1/rkisp1.cpp
>> @@ -54,7 +54,8 @@ public:
>>   	int init(const IPASettings &settings, unsigned int hwRevision,
>>   		 const IPACameraSensorInfo &sensorInfo,
>>   		 const ControlInfoMap &sensorControls,
>> -		 ControlInfoMap *ipaControls) override;
>> +		 ControlInfoMap *ipaControls,
>> +		 MetadataListPlan *metadataPlan) override;
>>   	int start() override;
>>   	void stop() override;
>>
>> @@ -135,7 +136,8 @@ std::string IPARkISP1::logPrefix() const
>>   int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision,
>>   		    const IPACameraSensorInfo &sensorInfo,
>>   		    const ControlInfoMap &sensorControls,
>> -		    ControlInfoMap *ipaControls)
>> +		    ControlInfoMap *ipaControls,
>> +		    MetadataListPlan *metadataPlan)
>>   {
>>   	/* \todo Add support for other revisions */
>>   	switch (hwRevision) {
>> @@ -204,6 +206,8 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision,
>>   	/* Initialize controls. */
>>   	updateControls(sensorInfo, sensorControls, ipaControls);
>>
>> +	*metadataPlan = std::move(context_.metadataPlan);
>> +
>>   	return 0;
>>   }
>>
>> diff --git a/src/ipa/rpi/common/ipa_base.cpp b/src/ipa/rpi/common/ipa_base.cpp
>> index e0f8b7e78..032d6c43b 100644
>> --- a/src/ipa/rpi/common/ipa_base.cpp
>> +++ b/src/ipa/rpi/common/ipa_base.cpp
>> @@ -180,6 +180,40 @@ int32_t IpaBase::init(const IPASettings &settings, const InitParams &params, Ini
>>
>>   	result->controlInfo = ControlInfoMap(std::move(ctrlMap), controls::controls);
>>
>> +	// TODO: only set those that can be reported by configured algorithms?
> 
> 
> mmm, we don't have the equivalent of Algorithm::init() for rpi, right ?
> 
>> +	// TODO: move this somewhere else?
> 
> Where would you propse ?

Not sure. The rpi `Algorithm` class is a bit different, it does have `initialize()`
but it takes no arguments. So that could be modified, but metadata handling is also
done through the custom `Metadata` type, and the conversion is also carried out
outside the algorithms. Now everything libcamera metadata related is in `ipa_base.cpp`.
Another thing that could be done is calling `controller_.getAlgorithm(...)` to check
if the algorithm is active, and enabling the metadata in that case.


Regards,
Barnabás Pőcze


> [...]

Patch
diff mbox series

diff --git a/include/libcamera/internal/software_isp/software_isp.h b/include/libcamera/internal/software_isp/software_isp.h
index 786246592..bd85f6b4b 100644
--- a/include/libcamera/internal/software_isp/software_isp.h
+++ b/include/libcamera/internal/software_isp/software_isp.h
@@ -41,6 +41,7 @@  class DebayerCpu;
 class FrameBuffer;
 class PixelFormat;
 class Stream;
+class MetadataListPlan;
 struct StreamConfiguration;
 
 LOG_DECLARE_CATEGORY(SoftwareIsp)
@@ -49,7 +50,7 @@  class SoftwareIsp : public Object
 {
 public:
 	SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,
-		    ControlInfoMap *ipaControls);
+		    ControlInfoMap *ipaControls, MetadataListPlan *metadataPlan);
 	~SoftwareIsp();
 
 	int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; }
diff --git a/include/libcamera/ipa/ipu3.mojom b/include/libcamera/ipa/ipu3.mojom
index d9a50b01d..f49b77797 100644
--- a/include/libcamera/ipa/ipu3.mojom
+++ b/include/libcamera/ipa/ipu3.mojom
@@ -20,7 +20,8 @@  interface IPAIPU3Interface {
 	init(libcamera.IPASettings settings,
 	     libcamera.IPACameraSensorInfo sensorInfo,
 	     libcamera.ControlInfoMap sensorControls)
-		=> (int32 ret, libcamera.ControlInfoMap ipaControls);
+		=> (int32 ret, libcamera.ControlInfoMap ipaControls,
+		    libcamera.MetadataListPlan metadata);
 	start() => (int32 ret);
 	stop();
 
diff --git a/include/libcamera/ipa/mali-c55.mojom b/include/libcamera/ipa/mali-c55.mojom
index 5d7eb4eef..7229c53e1 100644
--- a/include/libcamera/ipa/mali-c55.mojom
+++ b/include/libcamera/ipa/mali-c55.mojom
@@ -11,7 +11,8 @@  struct IPAConfigInfo {
 
 interface IPAMaliC55Interface {
 	init(libcamera.IPASettings settings, IPAConfigInfo configInfo)
-		=> (int32 ret, libcamera.ControlInfoMap ipaControls);
+		=> (int32 ret, libcamera.ControlInfoMap ipaControls,
+		    libcamera.MetadataListPlan metadataPlan);
 	start() => (int32 ret);
 	stop();
 
diff --git a/include/libcamera/ipa/raspberrypi.mojom b/include/libcamera/ipa/raspberrypi.mojom
index e30c70bde..69160e133 100644
--- a/include/libcamera/ipa/raspberrypi.mojom
+++ b/include/libcamera/ipa/raspberrypi.mojom
@@ -26,6 +26,7 @@  struct InitParams {
 struct InitResult {
 	SensorConfig sensorConfig;
 	libcamera.ControlInfoMap controlInfo;
+	libcamera.MetadataListPlan metadataPlan;
 };
 
 struct BufferIds {
diff --git a/include/libcamera/ipa/rkisp1.mojom b/include/libcamera/ipa/rkisp1.mojom
index 043ad27ea..440a59977 100644
--- a/include/libcamera/ipa/rkisp1.mojom
+++ b/include/libcamera/ipa/rkisp1.mojom
@@ -19,7 +19,7 @@  interface IPARkISP1Interface {
 	     uint32 hwRevision,
 	     libcamera.IPACameraSensorInfo sensorInfo,
 	     libcamera.ControlInfoMap sensorControls)
-		=> (int32 ret, libcamera.ControlInfoMap ipaControls);
+		=> (int32 ret, libcamera.ControlInfoMap ipaControls, libcamera.MetadataListPlan metadataPlan);
 	start() => (int32 ret);
 	stop();
 
diff --git a/include/libcamera/ipa/soft.mojom b/include/libcamera/ipa/soft.mojom
index 77328c5fd..360ed668b 100644
--- a/include/libcamera/ipa/soft.mojom
+++ b/include/libcamera/ipa/soft.mojom
@@ -18,7 +18,8 @@  interface IPASoftInterface {
 	     libcamera.SharedFD fdParams,
 	     libcamera.IPACameraSensorInfo sensorInfo,
 	     libcamera.ControlInfoMap sensorControls)
-		=> (int32 ret, libcamera.ControlInfoMap ipaControls, bool ccmEnabled);
+		=> (int32 ret, libcamera.ControlInfoMap ipaControls, bool ccmEnabled,
+		    libcamera.MetadataListPlan metadataPlan);
 	start() => (int32 ret);
 	stop();
 	configure(IPAConfigInfo configInfo)
diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp
index 39d0aebb0..e3b2acbc6 100644
--- a/src/ipa/ipu3/algorithms/agc.cpp
+++ b/src/ipa/ipu3/algorithms/agc.cpp
@@ -82,6 +82,10 @@  int Agc::init(IPAContext &context, const YamlObject &tuningData)
 
 	context.ctrlMap.merge(controls());
 
+	context.metadataPlan.add(controls::AnalogueGain);
+	context.metadataPlan.add(controls::ExposureTime);
+	context.metadataPlan.add(controls::FrameDuration);
+
 	return 0;
 }
 
diff --git a/src/ipa/ipu3/algorithms/awb.cpp b/src/ipa/ipu3/algorithms/awb.cpp
index 55de05d9e..28e29e29c 100644
--- a/src/ipa/ipu3/algorithms/awb.cpp
+++ b/src/ipa/ipu3/algorithms/awb.cpp
@@ -197,6 +197,18 @@  Awb::Awb()
 
 Awb::~Awb() = default;
 
+/**
+ * \copydoc libcamera::ipa::Algorithm::init
+ */
+int Awb::init(IPAContext &context, [[maybe_unused]] const YamlObject &tuningData)
+{
+	context.metadataPlan.add(controls::AwbEnable);
+	context.metadataPlan.add(controls::ColourGains);
+	context.metadataPlan.add(controls::ColourTemperature);
+
+	return 0;
+}
+
 /**
  * \copydoc libcamera::ipa::Algorithm::configure
  */
diff --git a/src/ipa/ipu3/algorithms/awb.h b/src/ipa/ipu3/algorithms/awb.h
index dbf69c907..1d47bffa3 100644
--- a/src/ipa/ipu3/algorithms/awb.h
+++ b/src/ipa/ipu3/algorithms/awb.h
@@ -40,6 +40,7 @@  public:
 	Awb();
 	~Awb();
 
+	int init(IPAContext &context, const YamlObject &tuningData) override;
 	int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
 	void prepare(IPAContext &context, const uint32_t frame,
 		     IPAFrameContext &frameContext,
diff --git a/src/ipa/ipu3/ipa_context.cpp b/src/ipa/ipu3/ipa_context.cpp
index 3b22f7917..f0b8b5dbe 100644
--- a/src/ipa/ipu3/ipa_context.cpp
+++ b/src/ipa/ipu3/ipa_context.cpp
@@ -54,6 +54,9 @@  namespace libcamera::ipa::ipu3 {
  *
  * \var IPAContext::ctrlMap
  * \brief A ControlInfoMap::Map of controls populated by the algorithms
+ *
+ * \var IPAContext::metadataPlan
+ * \brief A MetadataListPlan populated by the algorithms
  */
 
 /**
diff --git a/src/ipa/ipu3/ipa_context.h b/src/ipa/ipu3/ipa_context.h
index 97fcf06cd..f4f45ef4d 100644
--- a/src/ipa/ipu3/ipa_context.h
+++ b/src/ipa/ipu3/ipa_context.h
@@ -14,6 +14,7 @@ 
 
 #include <libcamera/controls.h>
 #include <libcamera/geometry.h>
+#include <libcamera/metadata_list_plan.h>
 
 #include <libipa/fc_queue.h>
 
@@ -95,6 +96,8 @@  struct IPAContext {
 	FCQueue<IPAFrameContext> frameContexts;
 
 	ControlInfoMap::Map ctrlMap;
+
+	MetadataListPlan metadataPlan; // TODO: only needed during init(), how could be removed?
 };
 
 } /* namespace ipa::ipu3 */
diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp
index 1cae08bf2..8d8811f71 100644
--- a/src/ipa/ipu3/ipu3.cpp
+++ b/src/ipa/ipu3/ipu3.cpp
@@ -143,7 +143,8 @@  public:
 	int init(const IPASettings &settings,
 		 const IPACameraSensorInfo &sensorInfo,
 		 const ControlInfoMap &sensorControls,
-		 ControlInfoMap *ipaControls) override;
+		 ControlInfoMap *ipaControls,
+		 MetadataListPlan *metadataPlan) override;
 
 	int start() override;
 	void stop() override;
@@ -299,7 +300,8 @@  void IPAIPU3::updateControls(const IPACameraSensorInfo &sensorInfo,
 int IPAIPU3::init(const IPASettings &settings,
 		  const IPACameraSensorInfo &sensorInfo,
 		  const ControlInfoMap &sensorControls,
-		  ControlInfoMap *ipaControls)
+		  ControlInfoMap *ipaControls,
+		  MetadataListPlan *metadataPlan)
 {
 	camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel);
 	if (camHelper_ == nullptr) {
@@ -348,6 +350,8 @@  int IPAIPU3::init(const IPASettings &settings,
 	/* Initialize controls. */
 	updateControls(sensorInfo, sensorControls, ipaControls);
 
+	*metadataPlan = std::move(context_.metadataPlan);
+
 	return 0;
 }
 
diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp
index 70667db34..bfca8f5a5 100644
--- a/src/ipa/mali-c55/algorithms/agc.cpp
+++ b/src/ipa/mali-c55/algorithms/agc.cpp
@@ -145,6 +145,11 @@  int Agc::init(IPAContext &context, const YamlObject &tuningData)
 	);
 	context.ctrlMap.merge(controls());
 
+	context.metadataPlan.add(controls::ExposureTime);
+	context.metadataPlan.add(controls::AnalogueGain);
+	context.metadataPlan.add(controls::DigitalGain);
+	context.metadataPlan.add(controls::ColourTemperature);
+
 	return 0;
 }
 
diff --git a/src/ipa/mali-c55/algorithms/awb.cpp b/src/ipa/mali-c55/algorithms/awb.cpp
index 050b191b7..b5acd4390 100644
--- a/src/ipa/mali-c55/algorithms/awb.cpp
+++ b/src/ipa/mali-c55/algorithms/awb.cpp
@@ -29,6 +29,13 @@  Awb::Awb()
 {
 }
 
+int Awb::init(IPAContext &context, [[maybe_unused]] const YamlObject &tuningData)
+{
+	context.metadataPlan.add(controls::ColourGains);
+
+	return 0;
+}
+
 int Awb::configure([[maybe_unused]] IPAContext &context,
 		   [[maybe_unused]] const IPACameraSensorInfo &configInfo)
 {
diff --git a/src/ipa/mali-c55/algorithms/awb.h b/src/ipa/mali-c55/algorithms/awb.h
index 800c2e834..db59e8d79 100644
--- a/src/ipa/mali-c55/algorithms/awb.h
+++ b/src/ipa/mali-c55/algorithms/awb.h
@@ -18,6 +18,7 @@  public:
 	Awb();
 	~Awb() = default;
 
+	int init(IPAContext &context, const YamlObject &tuningData) override;
 	int configure(IPAContext &context,
 		      const IPACameraSensorInfo &configInfo) override;
 	void prepare(IPAContext &context, const uint32_t frame,
diff --git a/src/ipa/mali-c55/algorithms/blc.cpp b/src/ipa/mali-c55/algorithms/blc.cpp
index 2a54c86a9..0aaaae7dd 100644
--- a/src/ipa/mali-c55/algorithms/blc.cpp
+++ b/src/ipa/mali-c55/algorithms/blc.cpp
@@ -51,6 +51,8 @@  int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context,
 
 	tuningParameters_ = true;
 
+	context.metadataPlan.add(controls::SensorBlackLevels);
+
 	LOG(MaliC55Blc, Debug)
 		<< "Black levels: 00 " << offset00 << ", 01 " << offset01
 		<< ", 10 " << offset10 << ", 11 " << offset11;
diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h
index 5e3e2fbde..d76fdcb2b 100644
--- a/src/ipa/mali-c55/ipa_context.h
+++ b/src/ipa/mali-c55/ipa_context.h
@@ -9,6 +9,7 @@ 
 
 #include <libcamera/base/utils.h>
 #include <libcamera/controls.h>
+#include <libcamera/metadata_list_plan.h>
 
 #include "libcamera/internal/bayer_format.h"
 
@@ -83,6 +84,8 @@  struct IPAContext {
 	FCQueue<IPAFrameContext> frameContexts;
 
 	ControlInfoMap::Map ctrlMap;
+
+	MetadataListPlan metadataPlan; // TODO: only needed during init(), how could be removed?
 };
 
 } /* namespace ipa::mali_c55 */
diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp
index c6941a950..7006ae55c 100644
--- a/src/ipa/mali-c55/mali-c55.cpp
+++ b/src/ipa/mali-c55/mali-c55.cpp
@@ -46,7 +46,7 @@  public:
 	IPAMaliC55();
 
 	int init(const IPASettings &settings, const IPAConfigInfo &ipaConfig,
-		 ControlInfoMap *ipaControls) override;
+		 ControlInfoMap *ipaControls, MetadataListPlan *metadataPlan) override;
 	int start() override;
 	void stop() override;
 	int configure(const IPAConfigInfo &ipaConfig, uint8_t bayerOrder,
@@ -96,7 +96,7 @@  std::string IPAMaliC55::logPrefix() const
 }
 
 int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig,
-		     ControlInfoMap *ipaControls)
+		     ControlInfoMap *ipaControls, MetadataListPlan *metadataPlan)
 {
 	camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel);
 	if (!camHelper_) {
@@ -131,6 +131,8 @@  int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig
 
 	updateControls(ipaConfig.sensorInfo, ipaConfig.sensorControls, ipaControls);
 
+	*metadataPlan = std::move(context_.metadataPlan);
+
 	return 0;
 }
 
diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
index 137a07500..22db11b24 100644
--- a/src/ipa/rkisp1/algorithms/agc.cpp
+++ b/src/ipa/rkisp1/algorithms/agc.cpp
@@ -159,6 +159,16 @@  int Agc::init(IPAContext &context, const YamlObject &tuningData)
 	context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true, true);
 	context.ctrlMap.merge(controls());
 
+	context.metadataPlan.add(controls::AnalogueGain);
+	context.metadataPlan.add(controls::ExposureTime);
+	context.metadataPlan.add(controls::FrameDuration);
+	context.metadataPlan.add(controls::FrameDuration);
+	context.metadataPlan.add(controls::ExposureTimeMode);
+	context.metadataPlan.add(controls::AnalogueGainMode);
+	context.metadataPlan.add(controls::AeMeteringMode);
+	context.metadataPlan.add(controls::AeExposureMode);
+	context.metadataPlan.add(controls::AeConstraintMode);
+
 	return 0;
 }
 
diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp
index 399fb51be..aaaa3cced 100644
--- a/src/ipa/rkisp1/algorithms/awb.cpp
+++ b/src/ipa/rkisp1/algorithms/awb.cpp
@@ -117,6 +117,10 @@  int Awb::init(IPAContext &context, const YamlObject &tuningData)
 	const auto &src = awbAlgo_->controls();
 	cmap.insert(src.begin(), src.end());
 
+	context.metadataPlan.add(controls::AwbEnable);
+	context.metadataPlan.add(controls::ColourGains);
+	context.metadataPlan.add(controls::ColourTemperature);
+
 	return 0;
 }
 
diff --git a/src/ipa/rkisp1/algorithms/blc.cpp b/src/ipa/rkisp1/algorithms/blc.cpp
index 98cb7145e..3288f24e3 100644
--- a/src/ipa/rkisp1/algorithms/blc.cpp
+++ b/src/ipa/rkisp1/algorithms/blc.cpp
@@ -103,6 +103,8 @@  int BlackLevelCorrection::init(IPAContext &context, const YamlObject &tuningData
 		<< ", green (blue) " << blackLevelGreenB_
 		<< ", blue " << blackLevelBlue_;
 
+	context.metadataPlan.add(controls::SensorBlackLevels);
+
 	return 0;
 }
 
diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
index f0d504215..cd4b98921 100644
--- a/src/ipa/rkisp1/ipa_context.h
+++ b/src/ipa/rkisp1/ipa_context.h
@@ -200,6 +200,7 @@  struct IPAContext {
 	FCQueue<IPAFrameContext> frameContexts;
 
 	ControlInfoMap::Map ctrlMap;
+	MetadataListPlan metadataPlan; // TODO: only needed during init(), how could be removed?
 
 	DebugMetadata debugMetadata;
 
diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp
index 1ed7d7d92..88c454ea1 100644
--- a/src/ipa/rkisp1/rkisp1.cpp
+++ b/src/ipa/rkisp1/rkisp1.cpp
@@ -54,7 +54,8 @@  public:
 	int init(const IPASettings &settings, unsigned int hwRevision,
 		 const IPACameraSensorInfo &sensorInfo,
 		 const ControlInfoMap &sensorControls,
-		 ControlInfoMap *ipaControls) override;
+		 ControlInfoMap *ipaControls,
+		 MetadataListPlan *metadataPlan) override;
 	int start() override;
 	void stop() override;
 
@@ -135,7 +136,8 @@  std::string IPARkISP1::logPrefix() const
 int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision,
 		    const IPACameraSensorInfo &sensorInfo,
 		    const ControlInfoMap &sensorControls,
-		    ControlInfoMap *ipaControls)
+		    ControlInfoMap *ipaControls,
+		    MetadataListPlan *metadataPlan)
 {
 	/* \todo Add support for other revisions */
 	switch (hwRevision) {
@@ -204,6 +206,8 @@  int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision,
 	/* Initialize controls. */
 	updateControls(sensorInfo, sensorControls, ipaControls);
 
+	*metadataPlan = std::move(context_.metadataPlan);
+
 	return 0;
 }
 
diff --git a/src/ipa/rpi/common/ipa_base.cpp b/src/ipa/rpi/common/ipa_base.cpp
index e0f8b7e78..032d6c43b 100644
--- a/src/ipa/rpi/common/ipa_base.cpp
+++ b/src/ipa/rpi/common/ipa_base.cpp
@@ -180,6 +180,40 @@  int32_t IpaBase::init(const IPASettings &settings, const InitParams &params, Ini
 
 	result->controlInfo = ControlInfoMap(std::move(ctrlMap), controls::controls);
 
+	// TODO: only set those that can be reported by configured algorithms?
+	// TODO: move this somewhere else?
+	result->metadataPlan.add(controls::AnalogueGainMode);
+	result->metadataPlan.add(controls::ExposureTimeMode);
+	result->metadataPlan.add(controls::ExposureTime);
+	result->metadataPlan.add(controls::AnalogueGain);
+	result->metadataPlan.add(controls::AeMeteringMode);
+	result->metadataPlan.add(controls::AeConstraintMode);
+	result->metadataPlan.add(controls::AeExposureMode);
+	result->metadataPlan.add(controls::ExposureValue);
+	result->metadataPlan.add(controls::AwbEnable);
+	result->metadataPlan.add(controls::AwbMode);
+	result->metadataPlan.add(controls::ColourGains);
+	result->metadataPlan.add(controls::Brightness);
+	result->metadataPlan.add(controls::Contrast);
+	result->metadataPlan.add(controls::Saturation);
+	result->metadataPlan.add(controls::Sharpness);
+	result->metadataPlan.add(controls::draft::NoiseReductionMode);
+	result->metadataPlan.add(controls::FrameDuration);
+	result->metadataPlan.add(controls::SensorTemperature);
+	result->metadataPlan.add(controls::LensPosition);
+	result->metadataPlan.add(controls::DigitalGain);
+	result->metadataPlan.add(controls::AeState);
+	result->metadataPlan.add(controls::Lux);
+	result->metadataPlan.add(controls::ColourTemperature);
+	result->metadataPlan.add(controls::SensorBlackLevels);
+	result->metadataPlan.add(controls::FocusFoM);
+	result->metadataPlan.add(controls::ColourCorrectionMatrix);
+	result->metadataPlan.add(controls::AfState);
+	result->metadataPlan.add(controls::AfPauseState);
+	result->metadataPlan.add(controls::HdrMode);
+	result->metadataPlan.add(controls::HdrChannel);
+	result->metadataPlan.add(controls::FrameDurationLimits);
+
 	return platformInit(params, result);
 }
 
diff --git a/src/ipa/rpi/pisp/pisp.cpp b/src/ipa/rpi/pisp/pisp.cpp
index bb50a9e05..a640bec32 100644
--- a/src/ipa/rpi/pisp/pisp.cpp
+++ b/src/ipa/rpi/pisp/pisp.cpp
@@ -267,8 +267,7 @@  private:
 	HdrStatus lastStitchHdrStatus_;
 };
 
-int32_t IpaPiSP::platformInit(const InitParams &params,
-			      [[maybe_unused]] InitResult *result)
+int32_t IpaPiSP::platformInit(const InitParams &params, InitResult *result)
 {
 	const std::string &target = controller_.getTarget();
 	if (target != "pisp") {
@@ -301,6 +300,8 @@  int32_t IpaPiSP::platformInit(const InitParams &params,
 
 	setDefaultConfig();
 
+	result->metadataPlan.add(controls::rpi::PispStatsOutput, sizeof(pisp_statistics));
+
 	return 0;
 }
 
diff --git a/src/ipa/rpi/vc4/vc4.cpp b/src/ipa/rpi/vc4/vc4.cpp
index ba43e4741..d02e06589 100644
--- a/src/ipa/rpi/vc4/vc4.cpp
+++ b/src/ipa/rpi/vc4/vc4.cpp
@@ -83,7 +83,7 @@  private:
 	void *lsTable_;
 };
 
-int32_t IpaVc4::platformInit([[maybe_unused]] const InitParams &params, [[maybe_unused]] InitResult *result)
+int32_t IpaVc4::platformInit([[maybe_unused]] const InitParams &params, InitResult *result)
 {
 	const std::string &target = controller_.getTarget();
 
@@ -94,6 +94,8 @@  int32_t IpaVc4::platformInit([[maybe_unused]] const InitParams &params, [[maybe_
 		return -EINVAL;
 	}
 
+	result->metadataPlan.add(controls::rpi::Bcm2835StatsOutput, sizeof(bcm2835_isp_stats));
+
 	return 0;
 }
 
diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp
index c46bb0ebe..47a499be6 100644
--- a/src/ipa/simple/algorithms/agc.cpp
+++ b/src/ipa/simple/algorithms/agc.cpp
@@ -41,6 +41,14 @@  Agc::Agc()
 {
 }
 
+int Agc::init(IPAContext &context, [[maybe_unused]] const YamlObject &tuningData)
+{
+	context.metadataPlan.add(controls::ExposureTime);
+	context.metadataPlan.add(controls::AnalogueGain);
+
+	return 0;
+}
+
 void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV)
 {
 	/*
diff --git a/src/ipa/simple/algorithms/agc.h b/src/ipa/simple/algorithms/agc.h
index 112d9f5a1..00e70ea70 100644
--- a/src/ipa/simple/algorithms/agc.h
+++ b/src/ipa/simple/algorithms/agc.h
@@ -19,6 +19,7 @@  public:
 	Agc();
 	~Agc() = default;
 
+	int init(IPAContext &context, const YamlObject &tuningData) override;
 	void process(IPAContext &context, const uint32_t frame,
 		     IPAFrameContext &frameContext,
 		     const SwIspStats *stats,
diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp
index cf567e894..cce4585f9 100644
--- a/src/ipa/simple/algorithms/awb.cpp
+++ b/src/ipa/simple/algorithms/awb.cpp
@@ -25,6 +25,14 @@  LOG_DEFINE_CATEGORY(IPASoftAwb)
 
 namespace ipa::soft::algorithms {
 
+int Awb::init(IPAContext &context, [[maybe_unused]] const YamlObject &tuningData)
+{
+	context.metadataPlan.add(controls::ColourGains);
+	context.metadataPlan.add(controls::ColourTemperature);
+
+	return 0;
+}
+
 int Awb::configure(IPAContext &context,
 		   [[maybe_unused]] const IPAConfigInfo &configInfo)
 {
diff --git a/src/ipa/simple/algorithms/awb.h b/src/ipa/simple/algorithms/awb.h
index ad993f39c..b8ae63dcb 100644
--- a/src/ipa/simple/algorithms/awb.h
+++ b/src/ipa/simple/algorithms/awb.h
@@ -19,6 +19,7 @@  public:
 	Awb() = default;
 	~Awb() = default;
 
+	int init(IPAContext &context, const YamlObject &tuningData) override;
 	int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
 	void prepare(IPAContext &context,
 		     const uint32_t frame,
diff --git a/src/ipa/simple/algorithms/blc.cpp b/src/ipa/simple/algorithms/blc.cpp
index 8c1e9ed08..4b9284e54 100644
--- a/src/ipa/simple/algorithms/blc.cpp
+++ b/src/ipa/simple/algorithms/blc.cpp
@@ -34,6 +34,9 @@  int BlackLevel::init([[maybe_unused]] IPAContext &context,
 		 */
 		definedLevel_ = blackLevel.value() >> 8;
 	}
+
+	context.metadataPlan.add(controls::SensorBlackLevels);
+
 	return 0;
 }
 
diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp
index 0a98406c1..3cab8eaa4 100644
--- a/src/ipa/simple/algorithms/ccm.cpp
+++ b/src/ipa/simple/algorithms/ccm.cpp
@@ -39,6 +39,9 @@  int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData
 	context.ccmEnabled = true;
 	context.ctrlMap[&controls::Saturation] = ControlInfo(0.0f, 2.0f, 1.0f);
 
+	context.metadataPlan.add(controls::ColourCorrectionMatrix);
+	context.metadataPlan.add(controls::Saturation);
+
 	return 0;
 }
 
diff --git a/src/ipa/simple/algorithms/lut.cpp b/src/ipa/simple/algorithms/lut.cpp
index d1d5f7271..0f99be544 100644
--- a/src/ipa/simple/algorithms/lut.cpp
+++ b/src/ipa/simple/algorithms/lut.cpp
@@ -28,6 +28,9 @@  int Lut::init(IPAContext &context,
 	      [[maybe_unused]] const YamlObject &tuningData)
 {
 	context.ctrlMap[&controls::Contrast] = ControlInfo(0.0f, 2.0f, 1.0f);
+
+	context.metadataPlan.add(controls::Contrast);
+
 	return 0;
 }
 
diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h
index a471b80ae..f4ecf43db 100644
--- a/src/ipa/simple/ipa_context.h
+++ b/src/ipa/simple/ipa_context.h
@@ -12,6 +12,7 @@ 
 #include <stdint.h>
 
 #include <libcamera/controls.h>
+#include <libcamera/metadata_list_plan.h>
 
 #include "libcamera/internal/matrix.h"
 #include "libcamera/internal/vector.h"
@@ -97,6 +98,7 @@  struct IPAContext {
 	IPAActiveState activeState;
 	FCQueue<IPAFrameContext> frameContexts;
 	ControlInfoMap::Map ctrlMap;
+	MetadataListPlan metadataPlan; // TODO: only needed during init(), how could be removed?
 	bool ccmEnabled = false;
 };
 
diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp
index c94c4cd55..db721199d 100644
--- a/src/ipa/simple/soft_simple.cpp
+++ b/src/ipa/simple/soft_simple.cpp
@@ -56,7 +56,8 @@  public:
 		 const IPACameraSensorInfo &sensorInfo,
 		 const ControlInfoMap &sensorControls,
 		 ControlInfoMap *ipaControls,
-		 bool *ccmEnabled) override;
+		 bool *ccmEnabled,
+		 MetadataListPlan *metadataPlan) override;
 	int configure(const IPAConfigInfo &configInfo) override;
 
 	int start() override;
@@ -96,7 +97,8 @@  int IPASoftSimple::init(const IPASettings &settings,
 			const IPACameraSensorInfo &sensorInfo,
 			const ControlInfoMap &sensorControls,
 			ControlInfoMap *ipaControls,
-			bool *ccmEnabled)
+			bool *ccmEnabled,
+			MetadataListPlan *metadataPlan)
 {
 	camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel);
 	if (!camHelper_) {
@@ -190,6 +192,8 @@  int IPASoftSimple::init(const IPASettings &settings,
 		return -EINVAL;
 	}
 
+	*metadataPlan = std::move(context_.metadataPlan);
+
 	return 0;
 }
 
diff --git a/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp b/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp
index ecda426a6..1c6da2006 100644
--- a/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp
+++ b/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp
@@ -166,6 +166,8 @@  int ISICameraData::init()
 
 	properties_ = sensor_->properties();
 
+	metadataPlan_.add(controls::SensorTimestamp);
+
 	return 0;
 }
 
diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
index e31e3879d..b4a4283f4 100644
--- a/src/libcamera/pipeline/ipu3/ipu3.cpp
+++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
@@ -1078,6 +1078,11 @@  int PipelineHandlerIPU3::registerCameras()
 		if (ret)
 			continue;
 
+		data->metadataPlan_.add(controls::draft::PipelineDepth);
+		data->metadataPlan_.add(controls::draft::TestPatternMode);
+		data->metadataPlan_.add(controls::ScalerCrop);
+		data->metadataPlan_.add(controls::SensorTimestamp);
+
 		const CameraSensorProperties::SensorDelays &delays = cio2->sensor()->sensorDelays();
 		std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
 			{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
@@ -1187,7 +1192,7 @@  int IPU3CameraData::loadIPA()
 		ipa_->configurationFile(sensor->model() + ".yaml", "uncalibrated.yaml");
 
 	ret = ipa_->init(IPASettings{ ipaTuningFile, sensor->model() },
-			 sensorInfo, sensor->controls(), &ipaControls_);
+			 sensorInfo, sensor->controls(), &ipaControls_, &metadataPlan_);
 	if (ret) {
 		LOG(IPU3, Error) << "Failed to initialise the IPU3 IPA";
 		return ret;
diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
index 4acc091bd..19980f6d2 100644
--- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp
+++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
@@ -402,7 +402,7 @@  int MaliC55CameraData::loadIPA()
 
 	ControlInfoMap ipaControls;
 	ret = ipa_->init({ ipaTuningFile, sensor_->model() }, ipaConfig,
-			 &ipaControls);
+			 &ipaControls, &metadataPlan_);
 	if (ret) {
 		LOG(MaliC55, Error) << "Failed to initialise the Mali-C55 IPA";
 		return ret;
diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
index 675f0a749..34be87087 100644
--- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp
+++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
@@ -394,7 +394,7 @@  int RkISP1CameraData::loadIPA(unsigned int hwRevision)
 	}
 
 	ret = ipa_->init({ ipaTuningFile, sensor_->model() }, hwRevision,
-			 sensorInfo, sensor_->controls(), &ipaControls_);
+			 sensorInfo, sensor_->controls(), &ipaControls_, &metadataPlan_);
 	if (ret < 0) {
 		LOG(RkISP1, Error) << "IPA initialization failure";
 		return ret;
@@ -1333,6 +1333,8 @@  int PipelineHandlerRkISP1::createCamera(MediaEntity *sensor)
 
 	updateControls(data.get());
 
+	data->metadataPlan_.add(controls::SensorTimestamp);
+
 	std::set<Stream *> streams{
 		&data->mainPathStream_,
 		&data->selfPathStream_,
diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
index 1f13e5230..98507a152 100644
--- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
+++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
@@ -589,6 +589,9 @@  int PipelineHandlerBase::configure(Camera *camera, CameraConfiguration *config)
 
 	data->controlInfo_ = ControlInfoMap(std::move(ctrlMap), result.controlInfo.idmap());
 
+	/* Update `rpi::ScalerCrops` size for the corrent configuration. */
+	data->metadataPlan_.add(controls::rpi::ScalerCrops, config->size());
+
 	/* Setup the Video Mux/Bridge entities. */
 	for (auto &[device, link] : data->bridgeDevices_) {
 		/*
@@ -832,6 +835,10 @@  int PipelineHandlerBase::registerCamera(std::unique_ptr<RPi::CameraData> &camera
 	/* Initialize the camera properties. */
 	data->properties_ = data->sensor_->properties();
 
+	data->metadataPlan_ = std::move(result.metadataPlan);
+	data->metadataPlan_.add(controls::SensorTimestamp);
+	data->metadataPlan_.add(controls::ScalerCrop);
+
 	/*
 	 * The V4L2_CID_NOTIFY_GAINS control, if present, is used to inform the
 	 * sensor of the colour gains. It is defined to be a linear gain where
diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
index efb07051b..05387ca7c 100644
--- a/src/libcamera/pipeline/simple/simple.cpp
+++ b/src/libcamera/pipeline/simple/simple.cpp
@@ -592,7 +592,7 @@  int SimpleCameraData::init()
 	 * Instantiate Soft ISP if this is enabled for the given driver and no converter is used.
 	 */
 	if (!converter_ && pipe->swIspEnabled()) {
-		swIsp_ = std::make_unique<SoftwareIsp>(pipe, sensor_.get(), &controlInfo_);
+		swIsp_ = std::make_unique<SoftwareIsp>(pipe, sensor_.get(), &controlInfo_, &metadataPlan_);
 		if (!swIsp_->isValid()) {
 			LOG(SimplePipeline, Warning)
 				<< "Failed to create software ISP, disabling software debayering";
diff --git a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
index 58aa0eb4c..e0036e3b5 100644
--- a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
+++ b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
@@ -603,6 +603,8 @@  int UVCCameraData::init(MediaDevice *media)
 
 	controlInfo_ = ControlInfoMap(std::move(ctrls), controls::controls);
 
+	metadataPlan_.add(controls::SensorTimestamp);
+
 	/*
 	 * Close to allow camera to go into runtime-suspend, video_ will be
 	 * re-opened from acquireDevice() and validate().
diff --git a/src/libcamera/pipeline/vimc/vimc.cpp b/src/libcamera/pipeline/vimc/vimc.cpp
index 07273bd2b..f8a29da41 100644
--- a/src/libcamera/pipeline/vimc/vimc.cpp
+++ b/src/libcamera/pipeline/vimc/vimc.cpp
@@ -590,6 +590,8 @@  int VimcCameraData::init()
 
 	controlInfo_ = ControlInfoMap(std::move(ctrls), controls::controls);
 
+	metadataPlan_.add(controls::SensorTimestamp);
+
 	/* Initialize the camera properties. */
 	properties_ = sensor_->properties();
 
diff --git a/src/libcamera/pipeline/virtual/config_parser.cpp b/src/libcamera/pipeline/virtual/config_parser.cpp
index 1d3d9ba87..f10aea7bf 100644
--- a/src/libcamera/pipeline/virtual/config_parser.cpp
+++ b/src/libcamera/pipeline/virtual/config_parser.cpp
@@ -65,6 +65,9 @@  ConfigParser::parseConfigFile(File &file, PipelineHandler *pipe)
 		controls[&controls::draft::FaceDetectMode] = ControlInfo(supportedFaceDetectModes);
 
 		data->controlInfo_ = ControlInfoMap(std::move(controls), controls::controls);
+
+		data->metadataPlan_.add(controls::SensorTimestamp);
+
 		configurations.push_back(std::move(data));
 	}
 
diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
index 28e2a360e..6da69daf9 100644
--- a/src/libcamera/software_isp/software_isp.cpp
+++ b/src/libcamera/software_isp/software_isp.cpp
@@ -71,10 +71,11 @@  LOG_DEFINE_CATEGORY(SoftwareIsp)
  * \param[in] pipe The pipeline handler in use
  * \param[in] sensor Pointer to the CameraSensor instance owned by the pipeline
  * \param[out] ipaControls The IPA controls to update
+ * \param[out] metadataPlan The metadata plan to update
  * handler
  */
 SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,
-			 ControlInfoMap *ipaControls)
+			 ControlInfoMap *ipaControls, MetadataListPlan *metadataPlan)
 	: dmaHeap_(DmaBufAllocator::DmaBufAllocatorFlag::CmaHeap |
 		   DmaBufAllocator::DmaBufAllocatorFlag::SystemHeap |
 		   DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf)
@@ -146,7 +147,8 @@  SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,
 			 sensorInfo,
 			 sensor->controls(),
 			 ipaControls,
-			 &ccmEnabled_);
+			 &ccmEnabled_,
+			 metadataPlan);
 	if (ret) {
 		LOG(SoftwareIsp, Error) << "IPA init failed";
 		debayer_.reset();