[{"id":1631,"web_url":"https://patchwork.libcamera.org/comment/1631/","msgid":"<20190519212152.GL25081@bigcity.dyn.berto.se>","date":"2019-05-19T21:21:52","subject":"Re: [libcamera-devel] [PATCH v2 6/6] libcamera: camera: Add a\n\tvalidation API to the CameraConfiguration class","submitter":{"id":5,"url":"https://patchwork.libcamera.org/api/people/5/","name":"Niklas Söderlund","email":"niklas.soderlund@ragnatech.se"},"content":"Hi Laurent,\n\nThanks for your work.\n\nOn 2019-05-19 18:00:47 +0300, Laurent Pinchart wrote:\n> The CameraConfiguration class implements a simple storage of\n> StreamConfiguration with internal validation limited to verifying that\n> the stream configurations are not empty. Extend this mechanism by\n> implementing a smart validate() method backed by pipeline handlers.\n> \n> This new mechanism changes the semantics of the camera configuration.\n> The Camera::generateConfiguration() operation still generates a default\n> configuration based on roles, but now also supports generating empty\n> configurations to be filled by applications. Applications can inspect\n> the configuration, optionally modify it, and validate it. The validation\n> implements \"try\" semantics and adjusts invalid configurations instead of\n> rejecting them completely. Applications then decide whether to accept\n> the modified configuration, or try again with a different set of\n> parameters. Once the configuration is valid, it is passed to\n> Camera::configure(), and pipeline handlers are guaranteed that the\n> configuration they receive is valid.\n> \n> A reference to the Camera may need to be stored in the\n> CameraConfiguration derived classes in order to access it from their\n> validate() implementation. This must be stored as a std::shared_ptr<> as\n> the CameraConfiguration instances belong to applications. In order to\n> make this possible, make the Camera class inherit from\n> std::shared_from_this<>.\n> \n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n\nReviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>\n\n> ---\n>  include/libcamera/camera.h               |  17 +-\n>  src/cam/main.cpp                         |   2 +-\n>  src/libcamera/camera.cpp                 |  80 +++++--\n>  src/libcamera/pipeline/ipu3/ipu3.cpp     | 255 ++++++++++++++++++-----\n>  src/libcamera/pipeline/rkisp1/rkisp1.cpp | 149 ++++++++++---\n>  src/libcamera/pipeline/uvcvideo.cpp      |  53 ++++-\n>  src/libcamera/pipeline/vimc.cpp          |  67 +++++-\n>  src/libcamera/pipeline_handler.cpp       |  17 +-\n>  test/camera/capture.cpp                  |   7 +-\n>  test/camera/configuration_default.cpp    |   4 +-\n>  test/camera/configuration_set.cpp        |   7 +-\n>  11 files changed, 516 insertions(+), 142 deletions(-)\n> \n> diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> index 144133c5de9f..8c0049a1dd94 100644\n> --- a/include/libcamera/camera.h\n> +++ b/include/libcamera/camera.h\n> @@ -25,14 +25,19 @@ class Request;\n>  class CameraConfiguration\n>  {\n>  public:\n> +\tenum Status {\n> +\t\tValid,\n> +\t\tAdjusted,\n> +\t\tInvalid,\n> +\t};\n> +\n>  \tusing iterator = std::vector<StreamConfiguration>::iterator;\n>  \tusing const_iterator = std::vector<StreamConfiguration>::const_iterator;\n>  \n> -\tCameraConfiguration();\n> +\tvirtual ~CameraConfiguration();\n>  \n>  \tvoid addConfiguration(const StreamConfiguration &cfg);\n> -\n> -\tbool isValid() const;\n> +\tvirtual Status validate() = 0;\n>  \n>  \tStreamConfiguration &at(unsigned int index);\n>  \tconst StreamConfiguration &at(unsigned int index) const;\n> @@ -53,11 +58,13 @@ public:\n>  \tbool empty() const;\n>  \tstd::size_t size() const;\n>  \n> -private:\n> +protected:\n> +\tCameraConfiguration();\n> +\n>  \tstd::vector<StreamConfiguration> config_;\n>  };\n>  \n> -class Camera final\n> +class Camera final : public std::enable_shared_from_this<Camera>\n>  {\n>  public:\n>  \tstatic std::shared_ptr<Camera> create(PipelineHandler *pipe,\n> diff --git a/src/cam/main.cpp b/src/cam/main.cpp\n> index 7550ae4f3428..23da5c687d89 100644\n> --- a/src/cam/main.cpp\n> +++ b/src/cam/main.cpp\n> @@ -116,7 +116,7 @@ static CameraConfiguration *prepareCameraConfig()\n>  \t}\n>  \n>  \tCameraConfiguration *config = camera->generateConfiguration(roles);\n> -\tif (!config || !config->isValid()) {\n> +\tif (!config || config->size() != roles.size()) {\n>  \t\tstd::cerr << \"Failed to get default stream configuration\"\n>  \t\t\t  << std::endl;\n>  \t\tdelete config;\n> diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> index 0e80691ed862..9da5d4f613f4 100644\n> --- a/src/libcamera/camera.cpp\n> +++ b/src/libcamera/camera.cpp\n> @@ -52,6 +52,28 @@ LOG_DECLARE_CATEGORY(Camera)\n>   * operator[](int) returns a reference to the StreamConfiguration based on its\n>   * insertion index. Accessing a stream configuration with an invalid index\n>   * results in undefined behaviour.\n> + *\n> + * CameraConfiguration instances are retrieved from the camera with\n> + * Camera::generateConfiguration(). Applications may then inspect the\n> + * configuration, modify it, and possibly add new stream configuration entries\n> + * with addConfiguration(). Once the camera configuration satisfies the\n> + * application, it shall be validated by a call to validate(). The validation\n> + * implements \"try\" semantics: it adjusts invalid configurations to the closest\n> + * achievable parameters instead of rejecting them completely. Applications\n> + * then decide whether to accept the modified configuration, or try again with\n> + * a different set of parameters. Once the configuration is valid, it is passed\n> + * to Camera::configure().\n> + */\n> +\n> +/**\n> + * \\enum CameraConfiguration::Status\n> + * \\brief Validity of a camera configuration\n> + * \\var CameraConfiguration::Valid\n> + * The configuration is fully valid\n> + * \\var CameraConfiguration::Adjusted\n> + * The configuration has been adjusted to a valid configuration\n> + * \\var CameraConfiguration::Invalid\n> + * The configuration is invalid and can't be adjusted automatically\n>   */\n>  \n>  /**\n> @@ -73,6 +95,10 @@ CameraConfiguration::CameraConfiguration()\n>  {\n>  }\n>  \n> +CameraConfiguration::~CameraConfiguration()\n> +{\n> +}\n> +\n>  /**\n>   * \\brief Add a stream configuration to the camera configuration\n>   * \\param[in] cfg The stream configuration\n> @@ -83,27 +109,31 @@ void CameraConfiguration::addConfiguration(const StreamConfiguration &cfg)\n>  }\n>  \n>  /**\n> - * \\brief Check if the camera configuration is valid\n> + * \\fn CameraConfiguration::validate()\n> + * \\brief Validate and possibly adjust the camera configuration\n>   *\n> - * A camera configuration is deemed to be valid if it contains at least one\n> - * stream configuration and all stream configurations contain valid information.\n> - * Stream configurations are deemed to be valid if all fields are none zero.\n> + * This method adjusts the camera configuration to the closest valid\n> + * configuration and returns the validation status.\n>   *\n> - * \\return True if the configuration is valid\n> + * \\todo: Define exactly when to return each status code. Should stream\n> + * parameters set to 0 by the caller be adjusted without returning Adjusted ?\n> + * This would potentially be useful for applications but would get in the way\n> + * in Camera::configure(). Do we need an extra status code to signal this ?\n> + *\n> + * \\todo: Handle validation of buffers count when refactoring the buffers API.\n> + *\n> + * \\return A CameraConfiguration::Status value that describes the validation\n> + * status.\n> + * \\retval CameraConfiguration::Invalid The configuration is invalid and can't\n> + * be adjusted. This may only occur in extreme cases such as when the\n> + * configuration is empty.\n> + * \\retval CameraConfigutation::Adjusted The configuration has been adjusted\n> + * and is now valid. Parameters may have changed for any stream, and stream\n> + * configurations may have been removed. The caller shall check the\n> + * configuration carefully.\n> + * \\retval CameraConfiguration::Valid The configuration was already valid and\n> + * hasn't been adjusted.\n>   */\n> -bool CameraConfiguration::isValid() const\n> -{\n> -\tif (empty())\n> -\t\treturn false;\n> -\n> -\tfor (const StreamConfiguration &cfg : config_) {\n> -\t\tif (cfg.size.width == 0 || cfg.size.height == 0 ||\n> -\t\t    cfg.pixelFormat == 0 || cfg.bufferCount == 0)\n> -\t\t\treturn false;\n> -\t}\n> -\n> -\treturn true;\n> -}\n>  \n>  /**\n>   * \\brief Retrieve a reference to a stream configuration\n> @@ -218,6 +248,11 @@ std::size_t CameraConfiguration::size() const\n>  \treturn config_.size();\n>  }\n>  \n> +/**\n> + * \\var CameraConfiguration::config_\n> + * \\brief The vector of stream configurations\n> + */\n> +\n>  /**\n>   * \\class Camera\n>   * \\brief Camera device\n> @@ -575,10 +610,9 @@ CameraConfiguration *Camera::generateConfiguration(const StreamRoles &roles)\n>   * The caller specifies which streams are to be involved and their configuration\n>   * by populating \\a config.\n>   *\n> - * The easiest way to populate the array of config is to fetch an initial\n> - * configuration from the camera with generateConfiguration() and then change\n> - * the parameters to fit the caller's need and once all the streams parameters\n> - * are configured hand that over to configure() to actually setup the camera.\n> + * The configuration is created by generateConfiguration(), and adjusted by the\n> + * caller with CameraConfiguration::validate(). This method only accepts fully\n> + * valid configurations and returns an error if \\a config is not valid.\n>   *\n>   * Exclusive access to the camera shall be ensured by a call to acquire() prior\n>   * to calling this function, otherwise an -EACCES error will be returned.\n> @@ -603,7 +637,7 @@ int Camera::configure(CameraConfiguration *config)\n>  \tif (!stateBetween(CameraAcquired, CameraConfigured))\n>  \t\treturn -EACCES;\n>  \n> -\tif (!config->isValid()) {\n> +\tif (config->validate() != CameraConfiguration::Valid) {\n>  \t\tLOG(Camera, Error)\n>  \t\t\t<< \"Can't configure camera with invalid configuration\";\n>  \t\treturn -EINVAL;\n> diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp\n> index 5b46fb68ced2..56265385a1e7 100644\n> --- a/src/libcamera/pipeline/ipu3/ipu3.cpp\n> +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp\n> @@ -164,6 +164,33 @@ public:\n>  \tIPU3Stream vfStream_;\n>  };\n>  \n> +class IPU3CameraConfiguration : public CameraConfiguration\n> +{\n> +public:\n> +\tIPU3CameraConfiguration(Camera *camera, IPU3CameraData *data);\n> +\n> +\tStatus validate() override;\n> +\n> +\tconst V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }\n> +\tconst std::vector<const IPU3Stream *> &streams() { return streams_; }\n> +\n> +private:\n> +\tstatic constexpr unsigned int IPU3_BUFFER_COUNT = 4;\n> +\n> +\tvoid adjustStream(unsigned int index, bool scale);\n> +\n> +\t/*\n> +\t * The IPU3CameraData instance is guaranteed to be valid as long as the\n> +\t * corresponding Camera instance is valid. In order to borrow a\n> +\t * reference to the camera data, store a new reference to the camera.\n> +\t */\n> +\tstd::shared_ptr<Camera> camera_;\n> +\tconst IPU3CameraData *data_;\n> +\n> +\tV4L2SubdeviceFormat sensorFormat_;\n> +\tstd::vector<const IPU3Stream *> streams_;\n> +};\n> +\n>  class PipelineHandlerIPU3 : public PipelineHandler\n>  {\n>  public:\n> @@ -186,8 +213,6 @@ public:\n>  \tbool match(DeviceEnumerator *enumerator) override;\n>  \n>  private:\n> -\tstatic constexpr unsigned int IPU3_BUFFER_COUNT = 4;\n> -\n>  \tIPU3CameraData *cameraData(const Camera *camera)\n>  \t{\n>  \t\treturn static_cast<IPU3CameraData *>(\n> @@ -202,6 +227,153 @@ private:\n>  \tMediaDevice *imguMediaDev_;\n>  };\n>  \n> +IPU3CameraConfiguration::IPU3CameraConfiguration(Camera *camera,\n> +\t\t\t\t\t\t IPU3CameraData *data)\n> +\t: CameraConfiguration()\n> +{\n> +\tcamera_ = camera->shared_from_this();\n> +\tdata_ = data;\n> +}\n> +\n> +void IPU3CameraConfiguration::adjustStream(unsigned int index, bool scale)\n> +{\n> +\tStreamConfiguration &cfg = config_[index];\n> +\n> +\t/* The only pixel format the driver supports is NV12. */\n> +\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> +\n> +\tif (scale) {\n> +\t\t/*\n> +\t\t * Provide a suitable default that matches the sensor aspect\n> +\t\t * ratio.\n> +\t\t */\n> +\t\tif (!cfg.size.width || !cfg.size.height) {\n> +\t\t\tcfg.size.width = 1280;\n> +\t\t\tcfg.size.height = 1280 * sensorFormat_.size.height\n> +\t\t\t\t\t/ sensorFormat_.size.width;\n> +\t\t}\n> +\n> +\t\t/*\n> +\t\t * \\todo: Clamp the size to the hardware bounds when we will\n> +\t\t * figure them out.\n> +\t\t *\n> +\t\t * \\todo: Handle the scaler (BDS) restrictions. The BDS can\n> +\t\t * only scale with the same factor in both directions, and the\n> +\t\t * scaling factor is limited to a multiple of 1/32. At the\n> +\t\t * moment the ImgU driver hides these constraints by applying\n> +\t\t * additional cropping, this should be fixed on the driver\n> +\t\t * side, and cropping should be exposed to us.\n> +\t\t */\n> +\t} else {\n> +\t\t/*\n> +\t\t * \\todo: Properly support cropping when the ImgU driver\n> +\t\t * interface will be cleaned up.\n> +\t\t */\n> +\t\tcfg.size = sensorFormat_.size;\n> +\t}\n> +\n> +\t/*\n> +\t * Clamp the size to match the ImgU alignment constraints. The width\n> +\t * shall be a multiple of 8 pixels and the height a multiple of 4\n> +\t * pixels.\n> +\t */\n> +\tif (cfg.size.width % 8 || cfg.size.height % 4) {\n> +\t\tcfg.size.width &= ~7;\n> +\t\tcfg.size.height &= ~3;\n> +\t}\n> +\n> +\tcfg.bufferCount = IPU3_BUFFER_COUNT;\n> +}\n> +\n> +CameraConfiguration::Status IPU3CameraConfiguration::validate()\n> +{\n> +\tconst CameraSensor *sensor = data_->cio2_.sensor_;\n> +\tStatus status = Valid;\n> +\n> +\tif (config_.empty())\n> +\t\treturn Invalid;\n> +\n> +\t/* Cap the number of entries to the available streams. */\n> +\tif (config_.size() > 2) {\n> +\t\tconfig_.resize(2);\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\t/*\n> +\t * Select the sensor format by collecting the maximum width and height\n> +\t * and picking the closest larger match, as the IPU3 can downscale\n> +\t * only. If no resolution is requested for any stream, or if no sensor\n> +\t * resolution is large enough, pick the largest one.\n> +\t */\n> +\tSize size = {};\n> +\n> +\tfor (const StreamConfiguration &cfg : config_) {\n> +\t\tif (cfg.size.width > size.width)\n> +\t\t\tsize.width = cfg.size.width;\n> +\t\tif (cfg.size.height > size.height)\n> +\t\t\tsize.height = cfg.size.height;\n> +\t}\n> +\n> +\tif (!size.width || !size.height)\n> +\t\tsize = sensor->resolution();\n> +\n> +\tsensorFormat_ = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR10_1X10,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG10_1X10,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG10_1X10,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB10_1X10 },\n> +\t\t\t\t\t  size);\n> +\tif (!sensorFormat_.size.width || !sensorFormat_.size.height)\n> +\t\tsensorFormat_.size = sensor->resolution();\n> +\n> +\t/*\n> +\t * Verify and update all configuration entries, and assign a stream to\n> +\t * each of them. The viewfinder stream can scale, while the output\n> +\t * stream can crop only, so select the output stream when the requested\n> +\t * resolution is equal to the sensor resolution, and the viewfinder\n> +\t * stream otherwise.\n> +\t */\n> +\tstd::set<const IPU3Stream *> availableStreams = {\n> +\t\t&data_->outStream_,\n> +\t\t&data_->vfStream_,\n> +\t};\n> +\n> +\tstreams_.clear();\n> +\tstreams_.reserve(config_.size());\n> +\n> +\tfor (unsigned int i = 0; i < config_.size(); ++i) {\n> +\t\tStreamConfiguration &cfg = config_[i];\n> +\t\tconst unsigned int pixelFormat = cfg.pixelFormat;\n> +\t\tconst Size size = cfg.size;\n> +\t\tconst IPU3Stream *stream;\n> +\n> +\t\tif (cfg.size == sensorFormat_.size)\n> +\t\t\tstream = &data_->outStream_;\n> +\t\telse\n> +\t\t\tstream = &data_->vfStream_;\n> +\n> +\t\tif (availableStreams.find(stream) == availableStreams.end())\n> +\t\t\tstream = *availableStreams.begin();\n> +\n> +\t\tLOG(IPU3, Debug)\n> +\t\t\t<< \"Assigned '\" << stream->name_ << \"' to stream \" << i;\n> +\n> +\t\tbool scale = stream == &data_->vfStream_;\n> +\t\tadjustStream(i, scale);\n> +\n> +\t\tif (cfg.pixelFormat != pixelFormat || cfg.size != size) {\n> +\t\t\tLOG(IPU3, Debug)\n> +\t\t\t\t<< \"Stream \" << i << \" configuration adjusted to \"\n> +\t\t\t\t<< cfg.toString();\n> +\t\t\tstatus = Adjusted;\n> +\t\t}\n> +\n> +\t\tstreams_.push_back(stream);\n> +\t\tavailableStreams.erase(stream);\n> +\t}\n> +\n> +\treturn status;\n> +}\n> +\n>  PipelineHandlerIPU3::PipelineHandlerIPU3(CameraManager *manager)\n>  \t: PipelineHandler(manager), cio2MediaDev_(nullptr), imguMediaDev_(nullptr)\n>  {\n> @@ -211,12 +383,14 @@ CameraConfiguration *PipelineHandlerIPU3::generateConfiguration(Camera *camera,\n>  \tconst StreamRoles &roles)\n>  {\n>  \tIPU3CameraData *data = cameraData(camera);\n> -\tCameraConfiguration *config = new CameraConfiguration();\n> +\tIPU3CameraConfiguration *config;\n>  \tstd::set<IPU3Stream *> streams = {\n>  \t\t&data->outStream_,\n>  \t\t&data->vfStream_,\n>  \t};\n>  \n> +\tconfig = new IPU3CameraConfiguration(camera, data);\n> +\n>  \tfor (const StreamRole role : roles) {\n>  \t\tStreamConfiguration cfg = {};\n>  \t\tIPU3Stream *stream = nullptr;\n> @@ -296,71 +470,25 @@ CameraConfiguration *PipelineHandlerIPU3::generateConfiguration(Camera *camera,\n>  \n>  \t\tstreams.erase(stream);\n>  \n> -\t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> -\t\tcfg.bufferCount = IPU3_BUFFER_COUNT;\n> -\n>  \t\tconfig->addConfiguration(cfg);\n>  \t}\n>  \n> +\tconfig->validate();\n> +\n>  \treturn config;\n>  }\n>  \n> -int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n> +int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *c)\n>  {\n> +\tIPU3CameraConfiguration *config =\n> +\t\tstatic_cast<IPU3CameraConfiguration *>(c);\n>  \tIPU3CameraData *data = cameraData(camera);\n>  \tIPU3Stream *outStream = &data->outStream_;\n>  \tIPU3Stream *vfStream = &data->vfStream_;\n>  \tCIO2Device *cio2 = &data->cio2_;\n>  \tImgUDevice *imgu = data->imgu_;\n> -\tSize sensorSize = {};\n>  \tint ret;\n>  \n> -\toutStream->active_ = false;\n> -\tvfStream->active_ = false;\n> -\tfor (StreamConfiguration &cfg : *config) {\n> -\t\t/*\n> -\t\t * Pick a stream for the configuration entry.\n> -\t\t * \\todo: This is a naive temporary implementation that will be\n> -\t\t * reworked when implementing camera configuration validation.\n> -\t\t */\n> -\t\tIPU3Stream *stream = vfStream->active_ ? outStream : vfStream;\n> -\n> -\t\t/*\n> -\t\t * Verify that the requested size respects the IPU3 alignment\n> -\t\t * requirements (the image width shall be a multiple of 8\n> -\t\t * pixels and its height a multiple of 4 pixels) and the camera\n> -\t\t * maximum sizes.\n> -\t\t *\n> -\t\t * \\todo: Consider the BDS scaling factor requirements: \"the\n> -\t\t * downscaling factor must be an integer value multiple of 1/32\"\n> -\t\t */\n> -\t\tif (cfg.size.width % 8 || cfg.size.height % 4) {\n> -\t\t\tLOG(IPU3, Error)\n> -\t\t\t\t<< \"Invalid stream size: bad alignment\";\n> -\t\t\treturn -EINVAL;\n> -\t\t}\n> -\n> -\t\tconst Size &resolution = cio2->sensor_->resolution();\n> -\t\tif (cfg.size.width > resolution.width ||\n> -\t\t    cfg.size.height > resolution.height) {\n> -\t\t\tLOG(IPU3, Error)\n> -\t\t\t\t<< \"Invalid stream size: larger than sensor resolution\";\n> -\t\t\treturn -EINVAL;\n> -\t\t}\n> -\n> -\t\t/*\n> -\t\t * Collect the maximum width and height: IPU3 can downscale\n> -\t\t * only.\n> -\t\t */\n> -\t\tif (cfg.size.width > sensorSize.width)\n> -\t\t\tsensorSize.width = cfg.size.width;\n> -\t\tif (cfg.size.height > sensorSize.height)\n> -\t\t\tsensorSize.height = cfg.size.height;\n> -\n> -\t\tstream->active_ = true;\n> -\t\tcfg.setStream(stream);\n> -\t}\n> -\n>  \t/*\n>  \t * \\todo: Enable links selectively based on the requested streams.\n>  \t * As of now, enable all links unconditionally.\n> @@ -373,6 +501,7 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n>  \t * Pass the requested stream size to the CIO2 unit and get back the\n>  \t * adjusted format to be propagated to the ImgU output devices.\n>  \t */\n> +\tconst Size &sensorSize = config->sensorFormat().size;\n>  \tV4L2DeviceFormat cio2Format = {};\n>  \tret = cio2->configure(sensorSize, &cio2Format);\n>  \tif (ret)\n> @@ -383,8 +512,22 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n>  \t\treturn ret;\n>  \n>  \t/* Apply the format to the configured streams output devices. */\n> -\tfor (StreamConfiguration &cfg : *config) {\n> -\t\tIPU3Stream *stream = static_cast<IPU3Stream *>(cfg.stream());\n> +\toutStream->active_ = false;\n> +\tvfStream->active_ = false;\n> +\n> +\tfor (unsigned int i = 0; i < config->size(); ++i) {\n> +\t\t/*\n> +\t\t * Use a const_cast<> here instead of storing a mutable stream\n> +\t\t * pointer in the configuration to let the compiler catch\n> +\t\t * unwanted modifications of camera data in the configuration\n> +\t\t * validate() implementation.\n> +\t\t */\n> +\t\tIPU3Stream *stream = const_cast<IPU3Stream *>(config->streams()[i]);\n> +\t\tStreamConfiguration &cfg = (*config)[i];\n> +\n> +\t\tstream->active_ = true;\n> +\t\tcfg.setStream(stream);\n> +\n>  \t\tret = imgu->configureOutput(stream->device_, cfg);\n>  \t\tif (ret)\n>  \t\t\treturn ret;\n> diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> index a1a4f205b4aa..42944c64189b 100644\n> --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> @@ -5,6 +5,7 @@\n>   * rkisp1.cpp - Pipeline handler for Rockchip ISP1\n>   */\n>  \n> +#include <algorithm>\n>  #include <iomanip>\n>  #include <memory>\n>  #include <vector>\n> @@ -45,6 +46,29 @@ public:\n>  \tCameraSensor *sensor_;\n>  };\n>  \n> +class RkISP1CameraConfiguration : public CameraConfiguration\n> +{\n> +public:\n> +\tRkISP1CameraConfiguration(Camera *camera, RkISP1CameraData *data);\n> +\n> +\tStatus validate() override;\n> +\n> +\tconst V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }\n> +\n> +private:\n> +\tstatic constexpr unsigned int RKISP1_BUFFER_COUNT = 4;\n> +\n> +\t/*\n> +\t * The RkISP1CameraData instance is guaranteed to be valid as long as the\n> +\t * corresponding Camera instance is valid. In order to borrow a\n> +\t * reference to the camera data, store a new reference to the camera.\n> +\t */\n> +\tstd::shared_ptr<Camera> camera_;\n> +\tconst RkISP1CameraData *data_;\n> +\n> +\tV4L2SubdeviceFormat sensorFormat_;\n> +};\n> +\n>  class PipelineHandlerRkISP1 : public PipelineHandler\n>  {\n>  public:\n> @@ -68,8 +92,6 @@ public:\n>  \tbool match(DeviceEnumerator *enumerator) override;\n>  \n>  private:\n> -\tstatic constexpr unsigned int RKISP1_BUFFER_COUNT = 4;\n> -\n>  \tRkISP1CameraData *cameraData(const Camera *camera)\n>  \t{\n>  \t\treturn static_cast<RkISP1CameraData *>(\n> @@ -88,6 +110,95 @@ private:\n>  \tCamera *activeCamera_;\n>  };\n>  \n> +RkISP1CameraConfiguration::RkISP1CameraConfiguration(Camera *camera,\n> +\t\t\t\t\t\t     RkISP1CameraData *data)\n> +\t: CameraConfiguration()\n> +{\n> +\tcamera_ = camera->shared_from_this();\n> +\tdata_ = data;\n> +}\n> +\n> +CameraConfiguration::Status RkISP1CameraConfiguration::validate()\n> +{\n> +\tstatic const std::array<unsigned int, 8> formats{\n> +\t\tV4L2_PIX_FMT_YUYV,\n> +\t\tV4L2_PIX_FMT_YVYU,\n> +\t\tV4L2_PIX_FMT_VYUY,\n> +\t\tV4L2_PIX_FMT_NV16,\n> +\t\tV4L2_PIX_FMT_NV61,\n> +\t\tV4L2_PIX_FMT_NV21,\n> +\t\tV4L2_PIX_FMT_NV12,\n> +\t\tV4L2_PIX_FMT_GREY,\n> +\t};\n> +\n> +\tconst CameraSensor *sensor = data_->sensor_;\n> +\tStatus status = Valid;\n> +\n> +\tif (config_.empty())\n> +\t\treturn Invalid;\n> +\n> +\t/* Cap the number of entries to the available streams. */\n> +\tif (config_.size() > 1) {\n> +\t\tconfig_.resize(1);\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\tStreamConfiguration &cfg = config_[0];\n> +\n> +\t/* Adjust the pixel format. */\n> +\tif (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==\n> +\t    formats.end()) {\n> +\t\tLOG(RkISP1, Debug) << \"Adjusting format to NV12\";\n> +\t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\t/* Select the sensor format. */\n> +\tsensorFormat_ = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR12_1X12,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG12_1X12,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG12_1X12,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB12_1X12,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SBGGR10_1X10,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG10_1X10,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG10_1X10,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB10_1X10,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SBGGR8_1X8,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG8_1X8,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG8_1X8,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB8_1X8 },\n> +\t\t\t\t\t  cfg.size);\n> +\tif (!sensorFormat_.size.width || !sensorFormat_.size.height)\n> +\t\tsensorFormat_.size = sensor->resolution();\n> +\n> +\t/*\n> +\t * Provide a suitable default that matches the sensor aspect\n> +\t * ratio and clamp the size to the hardware bounds.\n> +\t *\n> +\t * \\todo: Check the hardware alignment constraints.\n> +\t */\n> +\tconst Size size = cfg.size;\n> +\n> +\tif (!cfg.size.width || !cfg.size.height) {\n> +\t\tcfg.size.width = 1280;\n> +\t\tcfg.size.height = 1280 * sensorFormat_.size.height\n> +\t\t\t\t/ sensorFormat_.size.width;\n> +\t}\n> +\n> +\tcfg.size.width = std::max(32U, std::min(4416U, cfg.size.width));\n> +\tcfg.size.height = std::max(16U, std::min(3312U, cfg.size.height));\n> +\n> +\tif (cfg.size != size) {\n> +\t\tLOG(RkISP1, Debug)\n> +\t\t\t<< \"Adjusting size from \" << size.toString()\n> +\t\t\t<< \" to \" << cfg.size.toString();\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\tcfg.bufferCount = RKISP1_BUFFER_COUNT;\n> +\n> +\treturn status;\n> +}\n> +\n>  PipelineHandlerRkISP1::PipelineHandlerRkISP1(CameraManager *manager)\n>  \t: PipelineHandler(manager), dphy_(nullptr), isp_(nullptr),\n>  \t  video_(nullptr)\n> @@ -109,37 +220,31 @@ CameraConfiguration *PipelineHandlerRkISP1::generateConfiguration(Camera *camera\n>  \tconst StreamRoles &roles)\n>  {\n>  \tRkISP1CameraData *data = cameraData(camera);\n> -\tCameraConfiguration *config = new CameraConfiguration();\n> +\tCameraConfiguration *config = new RkISP1CameraConfiguration(camera, data);\n>  \n>  \tif (!roles.empty()) {\n>  \t\tStreamConfiguration cfg{};\n>  \n>  \t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n>  \t\tcfg.size = data->sensor_->resolution();\n> -\t\tcfg.bufferCount = RKISP1_BUFFER_COUNT;\n>  \n>  \t\tconfig->addConfiguration(cfg);\n>  \t}\n>  \n> +\tconfig->validate();\n> +\n>  \treturn config;\n>  }\n>  \n> -int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *config)\n> +int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)\n>  {\n> +\tRkISP1CameraConfiguration *config =\n> +\t\tstatic_cast<RkISP1CameraConfiguration *>(c);\n>  \tRkISP1CameraData *data = cameraData(camera);\n>  \tStreamConfiguration &cfg = config->at(0);\n>  \tCameraSensor *sensor = data->sensor_;\n>  \tint ret;\n>  \n> -\t/* Verify the configuration. */\n> -\tconst Size &resolution = sensor->resolution();\n> -\tif (cfg.size.width > resolution.width ||\n> -\t    cfg.size.height > resolution.height) {\n> -\t\tLOG(RkISP1, Error)\n> -\t\t\t<< \"Invalid stream size: larger than sensor resolution\";\n> -\t\treturn -EINVAL;\n> -\t}\n> -\n>  \t/*\n>  \t * Configure the sensor links: enable the link corresponding to this\n>  \t * camera and disable all the other sensor links.\n> @@ -167,21 +272,7 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *config\n>  \t * Configure the format on the sensor output and propagate it through\n>  \t * the pipeline.\n>  \t */\n> -\tV4L2SubdeviceFormat format;\n> -\tformat = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR12_1X12,\n> -\t\t\t\t     MEDIA_BUS_FMT_SGBRG12_1X12,\n> -\t\t\t\t     MEDIA_BUS_FMT_SGRBG12_1X12,\n> -\t\t\t\t     MEDIA_BUS_FMT_SRGGB12_1X12,\n> -\t\t\t\t     MEDIA_BUS_FMT_SBGGR10_1X10,\n> -\t\t\t\t     MEDIA_BUS_FMT_SGBRG10_1X10,\n> -\t\t\t\t     MEDIA_BUS_FMT_SGRBG10_1X10,\n> -\t\t\t\t     MEDIA_BUS_FMT_SRGGB10_1X10,\n> -\t\t\t\t     MEDIA_BUS_FMT_SBGGR8_1X8,\n> -\t\t\t\t     MEDIA_BUS_FMT_SGBRG8_1X8,\n> -\t\t\t\t     MEDIA_BUS_FMT_SGRBG8_1X8,\n> -\t\t\t\t     MEDIA_BUS_FMT_SRGGB8_1X8 },\n> -\t\t\t\t   cfg.size);\n> -\n> +\tV4L2SubdeviceFormat format = config->sensorFormat();\n>  \tLOG(RkISP1, Debug) << \"Configuring sensor with \" << format.toString();\n>  \n>  \tret = sensor->setFormat(&format);\n> diff --git a/src/libcamera/pipeline/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo.cpp\n> index 8254e1fdac1e..f7ffeb439cf3 100644\n> --- a/src/libcamera/pipeline/uvcvideo.cpp\n> +++ b/src/libcamera/pipeline/uvcvideo.cpp\n> @@ -39,6 +39,14 @@ public:\n>  \tStream stream_;\n>  };\n>  \n> +class UVCCameraConfiguration : public CameraConfiguration\n> +{\n> +public:\n> +\tUVCCameraConfiguration();\n> +\n> +\tStatus validate() override;\n> +};\n> +\n>  class PipelineHandlerUVC : public PipelineHandler\n>  {\n>  public:\n> @@ -68,6 +76,45 @@ private:\n>  \t}\n>  };\n>  \n> +UVCCameraConfiguration::UVCCameraConfiguration()\n> +\t: CameraConfiguration()\n> +{\n> +}\n> +\n> +CameraConfiguration::Status UVCCameraConfiguration::validate()\n> +{\n> +\tStatus status = Valid;\n> +\n> +\tif (config_.empty())\n> +\t\treturn Invalid;\n> +\n> +\t/* Cap the number of entries to the available streams. */\n> +\tif (config_.size() > 1) {\n> +\t\tconfig_.resize(1);\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\tStreamConfiguration &cfg = config_[0];\n> +\n> +\t/* \\todo: Validate the configuration against the device capabilities. */\n> +\tconst unsigned int pixelFormat = cfg.pixelFormat;\n> +\tconst Size size = cfg.size;\n> +\n> +\tcfg.pixelFormat = V4L2_PIX_FMT_YUYV;\n> +\tcfg.size = { 640, 480 };\n> +\n> +\tif (cfg.pixelFormat != pixelFormat || cfg.size != size) {\n> +\t\tLOG(UVC, Debug)\n> +\t\t\t<< \"Adjusting configuration from \" << cfg.toString()\n> +\t\t\t<< \" to \" << cfg.size.toString() << \"-YUYV\";\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\tcfg.bufferCount = 4;\n> +\n> +\treturn status;\n> +}\n> +\n>  PipelineHandlerUVC::PipelineHandlerUVC(CameraManager *manager)\n>  \t: PipelineHandler(manager)\n>  {\n> @@ -76,10 +123,10 @@ PipelineHandlerUVC::PipelineHandlerUVC(CameraManager *manager)\n>  CameraConfiguration *PipelineHandlerUVC::generateConfiguration(Camera *camera,\n>  \tconst StreamRoles &roles)\n>  {\n> -\tCameraConfiguration *config = new CameraConfiguration();\n> +\tCameraConfiguration *config = new UVCCameraConfiguration();\n>  \n>  \tif (!roles.empty()) {\n> -\t\tStreamConfiguration cfg;\n> +\t\tStreamConfiguration cfg{};\n>  \n>  \t\tcfg.pixelFormat = V4L2_PIX_FMT_YUYV;\n>  \t\tcfg.size = { 640, 480 };\n> @@ -88,6 +135,8 @@ CameraConfiguration *PipelineHandlerUVC::generateConfiguration(Camera *camera,\n>  \t\tconfig->addConfiguration(cfg);\n>  \t}\n>  \n> +\tconfig->validate();\n> +\n>  \treturn config;\n>  }\n>  \n> diff --git a/src/libcamera/pipeline/vimc.cpp b/src/libcamera/pipeline/vimc.cpp\n> index 2bf85d0a0b32..2a61d2893e3a 100644\n> --- a/src/libcamera/pipeline/vimc.cpp\n> +++ b/src/libcamera/pipeline/vimc.cpp\n> @@ -5,6 +5,8 @@\n>   * vimc.cpp - Pipeline handler for the vimc device\n>   */\n>  \n> +#include <algorithm>\n> +\n>  #include <libcamera/camera.h>\n>  #include <libcamera/request.h>\n>  #include <libcamera/stream.h>\n> @@ -39,6 +41,14 @@ public:\n>  \tStream stream_;\n>  };\n>  \n> +class VimcCameraConfiguration : public CameraConfiguration\n> +{\n> +public:\n> +\tVimcCameraConfiguration();\n> +\n> +\tStatus validate() override;\n> +};\n> +\n>  class PipelineHandlerVimc : public PipelineHandler\n>  {\n>  public:\n> @@ -68,6 +78,57 @@ private:\n>  \t}\n>  };\n>  \n> +VimcCameraConfiguration::VimcCameraConfiguration()\n> +\t: CameraConfiguration()\n> +{\n> +}\n> +\n> +CameraConfiguration::Status VimcCameraConfiguration::validate()\n> +{\n> +\tstatic const std::array<unsigned int, 3> formats{\n> +\t\tV4L2_PIX_FMT_BGR24,\n> +\t\tV4L2_PIX_FMT_RGB24,\n> +\t\tV4L2_PIX_FMT_ARGB32,\n> +\t};\n> +\n> +\tStatus status = Valid;\n> +\n> +\tif (config_.empty())\n> +\t\treturn Invalid;\n> +\n> +\t/* Cap the number of entries to the available streams. */\n> +\tif (config_.size() > 1) {\n> +\t\tconfig_.resize(1);\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\tStreamConfiguration &cfg = config_[0];\n> +\n> +\t/* Adjust the pixel format. */\n> +\tif (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==\n> +\t    formats.end()) {\n> +\t\tLOG(VIMC, Debug) << \"Adjusting format to RGB24\";\n> +\t\tcfg.pixelFormat = V4L2_PIX_FMT_RGB24;\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\t/* Clamp the size based on the device limits. */\n> +\tconst Size size = cfg.size;\n> +\n> +\tcfg.size.width = std::max(16U, std::min(4096U, cfg.size.width));\n> +\tcfg.size.height = std::max(16U, std::min(2160U, cfg.size.height));\n> +\n> +\tif (cfg.size != size) {\n> +\t\tLOG(VIMC, Debug)\n> +\t\t\t<< \"Adjusting size to \" << cfg.size.toString();\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\tcfg.bufferCount = 4;\n> +\n> +\treturn status;\n> +}\n> +\n>  PipelineHandlerVimc::PipelineHandlerVimc(CameraManager *manager)\n>  \t: PipelineHandler(manager)\n>  {\n> @@ -76,10 +137,10 @@ PipelineHandlerVimc::PipelineHandlerVimc(CameraManager *manager)\n>  CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera,\n>  \tconst StreamRoles &roles)\n>  {\n> -\tCameraConfiguration *config = new CameraConfiguration();\n> +\tCameraConfiguration *config = new VimcCameraConfiguration();\n>  \n>  \tif (!roles.empty()) {\n> -\t\tStreamConfiguration cfg;\n> +\t\tStreamConfiguration cfg{};\n>  \n>  \t\tcfg.pixelFormat = V4L2_PIX_FMT_RGB24;\n>  \t\tcfg.size = { 640, 480 };\n> @@ -88,6 +149,8 @@ CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera,\n>  \t\tconfig->addConfiguration(cfg);\n>  \t}\n>  \n> +\tconfig->validate();\n> +\n>  \treturn config;\n>  }\n>  \n> diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp\n> index de46e98880a2..dd56907d817e 100644\n> --- a/src/libcamera/pipeline_handler.cpp\n> +++ b/src/libcamera/pipeline_handler.cpp\n> @@ -248,17 +248,14 @@ void PipelineHandler::unlock()\n>   * is the Camera class which will receive configuration to apply from the\n>   * application.\n>   *\n> - * Each pipeline handler implementation is responsible for validating\n> - * that the configuration requested in \\a config can be achieved\n> - * exactly. Any difference in pixel format, frame size or any other\n> - * parameter shall result in the -EINVAL error being returned, and no\n> - * change in configuration being applied to the pipeline. If\n> - * configuration of a subset of the streams can't be satisfied, the\n> - * whole configuration is considered invalid.\n> + * The configuration is guaranteed to have been validated with\n> + * CameraConfiguration::valid(). The pipeline handler implementation shall not\n> + * perform further validation and may rely on any custom field stored in its\n> + * custom CameraConfiguration derived class.\n>   *\n> - * Once the configuration is validated and the camera configured, the pipeline\n> - * handler shall associate a Stream instance to each StreamConfiguration entry\n> - * in the CameraConfiguration with the StreamConfiguration::setStream() method.\n> + * When configuring the camera the pipeline handler shall associate a Stream\n> + * instance to each StreamConfiguration entry in the CameraConfiguration using\n> + * the StreamConfiguration::setStream() method.\n>   *\n>   * \\return 0 on success or a negative error code otherwise\n>   */\n> diff --git a/test/camera/capture.cpp b/test/camera/capture.cpp\n> index 137aa649a505..f122f79bb1ec 100644\n> --- a/test/camera/capture.cpp\n> +++ b/test/camera/capture.cpp\n> @@ -45,7 +45,7 @@ protected:\n>  \t\tCameraTest::init();\n>  \n>  \t\tconfig_ = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> -\t\tif (!config_) {\n> +\t\tif (!config_ || config_->size() != 1) {\n>  \t\t\tcout << \"Failed to generate default configuration\" << endl;\n>  \t\t\tCameraTest::cleanup();\n>  \t\t\treturn TestFail;\n> @@ -58,11 +58,6 @@ protected:\n>  \t{\n>  \t\tStreamConfiguration &cfg = config_->at(0);\n>  \n> -\t\tif (!config_->isValid()) {\n> -\t\t\tcout << \"Failed to read default configuration\" << endl;\n> -\t\t\treturn TestFail;\n> -\t\t}\n> -\n>  \t\tif (camera_->acquire()) {\n>  \t\t\tcout << \"Failed to acquire the camera\" << endl;\n>  \t\t\treturn TestFail;\n> diff --git a/test/camera/configuration_default.cpp b/test/camera/configuration_default.cpp\n> index d5cefc1127c9..81055da1d513 100644\n> --- a/test/camera/configuration_default.cpp\n> +++ b/test/camera/configuration_default.cpp\n> @@ -22,7 +22,7 @@ protected:\n>  \n>  \t\t/* Test asking for configuration for a video stream. */\n>  \t\tconfig = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> -\t\tif (!config || !config->isValid()) {\n> +\t\tif (!config || config->size() != 1) {\n>  \t\t\tcout << \"Default configuration invalid\" << endl;\n>  \t\t\tdelete config;\n>  \t\t\treturn TestFail;\n> @@ -35,7 +35,7 @@ protected:\n>  \t\t * stream roles returns an empty camera configuration.\n>  \t\t */\n>  \t\tconfig = camera_->generateConfiguration({});\n> -\t\tif (!config || config->isValid()) {\n> +\t\tif (!config || config->size() != 0) {\n>  \t\t\tcout << \"Failed to retrieve configuration for empty roles list\"\n>  \t\t\t     << endl;\n>  \t\t\tdelete config;\n> diff --git a/test/camera/configuration_set.cpp b/test/camera/configuration_set.cpp\n> index 23c611a93355..a4e2da16a88b 100644\n> --- a/test/camera/configuration_set.cpp\n> +++ b/test/camera/configuration_set.cpp\n> @@ -21,7 +21,7 @@ protected:\n>  \t\tCameraTest::init();\n>  \n>  \t\tconfig_ = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> -\t\tif (!config_) {\n> +\t\tif (!config_ || config_->size() != 1) {\n>  \t\t\tcout << \"Failed to generate default configuration\" << endl;\n>  \t\t\tCameraTest::cleanup();\n>  \t\t\treturn TestFail;\n> @@ -34,11 +34,6 @@ protected:\n>  \t{\n>  \t\tStreamConfiguration &cfg = config_->at(0);\n>  \n> -\t\tif (!config_->isValid()) {\n> -\t\t\tcout << \"Failed to read default configuration\" << endl;\n> -\t\t\treturn TestFail;\n> -\t\t}\n> -\n>  \t\tif (camera_->acquire()) {\n>  \t\t\tcout << \"Failed to acquire the camera\" << endl;\n>  \t\t\treturn TestFail;\n> -- \n> Regards,\n> \n> Laurent Pinchart\n> \n> _______________________________________________\n> libcamera-devel mailing list\n> libcamera-devel@lists.libcamera.org\n> https://lists.libcamera.org/listinfo/libcamera-devel","headers":{"Return-Path":"<niklas.soderlund@ragnatech.se>","Received":["from mail-lf1-x144.google.com (mail-lf1-x144.google.com\n\t[IPv6:2a00:1450:4864:20::144])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id D22E660E4C\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSun, 19 May 2019 23:21:54 +0200 (CEST)","by mail-lf1-x144.google.com with SMTP id u27so8800948lfg.10\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSun, 19 May 2019 14:21:54 -0700 (PDT)","from localhost (89-233-230-99.cust.bredband2.com. [89.233.230.99])\n\tby smtp.gmail.com with ESMTPSA id\n\th25sm3297855ljb.80.2019.05.19.14.21.52\n\t(version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256);\n\tSun, 19 May 2019 14:21:52 -0700 (PDT)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=ragnatech-se.20150623.gappssmtp.com; s=20150623;\n\th=date:from:to:cc:subject:message-id:references:mime-version\n\t:content-disposition:content-transfer-encoding:in-reply-to\n\t:user-agent; bh=GSnSo331dC0qv0OIu/FW83cgxYnV1BSLsZqg0fVWsvw=;\n\tb=d5WOyKjj3Zos0mfysdfirEKIucLaqoEfvd+UkcbsPumMYGuVpzLN2VJqtIs9HsdpiF\n\tDD9Kt8PRTKpFbFj0VN6szbmORvrPUfWi4iiPnkq3NzZsGLB3A51PwdA/jfGOIYX/ZGp/\n\tjsgsZXlNQr9dNm56yLqy1Em8Ibz4YqQVbnozGf9dQ5chSe/2U/DnRqPoyrVYCeRYz1Co\n\tIYiuGYwPTzrwuqnH8oHOArkchDd0GGM2tdTbP584b08CO0UhI6kBKZUbEXVgzuqYehV/\n\tuy1HJvCf/WzBuJHbJRpCsvxvPhG35uyQ89zo7B36byneWj1Y8q2BKXrGGJPHV8oeuxvG\n\tFIhg==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20161025;\n\th=x-gm-message-state:date:from:to:cc:subject:message-id:references\n\t:mime-version:content-disposition:content-transfer-encoding\n\t:in-reply-to:user-agent;\n\tbh=GSnSo331dC0qv0OIu/FW83cgxYnV1BSLsZqg0fVWsvw=;\n\tb=mwRkNiWZLnll8qB3xxKoz32215YVqaMxI/IGnGWTUrtK/Q9mExTy3lHVoizMkn2/vJ\n\tfGoujFc+dIJwkze3o8YnZZQiwSwXwidPajKOvk2LdHpkQDNqJeTThq9FNvngcuQOcfx4\n\thvNIyeGzoyy/cfo44wr76/XAeSDg1tJzKK3z4QLa/jkfeXNn4xL5s2dQK0LjNIW30PpZ\n\tRgPAXl4b18DRNzPCKKMpd3HZx/nJ2Km7kQ4XolLIJpeZOIXvNLz7EMmzRSIP4HVcSb7L\n\tr6iKpVzWaQlVlfdBcSOrHOVD1utmGbcAlWnR8wnvulkVu5nWAMr9NRJzxk0PKKFU6LoH\n\twZwg==","X-Gm-Message-State":"APjAAAUP9/zQHYhLJlRpgRSX7b59T08tuol+Hztz85XL8PdQ+GAasaOA\n\tlfucKssBl0cDZtAyh5EDhIpb7hzX2Zs=","X-Google-Smtp-Source":"APXvYqzHxJJODft6WN8QgctZVTqQjHLdzMbOD85M1Kt69sM4xMxAkJHlXbEloigKw0KN3FFKGIeYJw==","X-Received":"by 2002:a19:3f4b:: with SMTP id\n\tm72mr33756887lfa.32.1558300914040; \n\tSun, 19 May 2019 14:21:54 -0700 (PDT)","Date":"Sun, 19 May 2019 23:21:52 +0200","From":"Niklas =?iso-8859-1?q?S=F6derlund?= <niklas.soderlund@ragnatech.se>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20190519212152.GL25081@bigcity.dyn.berto.se>","References":"<20190519150047.12444-1-laurent.pinchart@ideasonboard.com>\n\t<20190519150047.12444-7-laurent.pinchart@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=iso-8859-1","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<20190519150047.12444-7-laurent.pinchart@ideasonboard.com>","User-Agent":"Mutt/1.11.3 (2019-02-01)","Subject":"Re: [libcamera-devel] [PATCH v2 6/6] libcamera: camera: Add a\n\tvalidation API to the CameraConfiguration class","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.23","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Sun, 19 May 2019 21:21:55 -0000"}},{"id":1634,"web_url":"https://patchwork.libcamera.org/comment/1634/","msgid":"<20190520220506.5yubhl2rqmk3ajdr@uno.localdomain>","date":"2019-05-20T22:05:06","subject":"Re: [libcamera-devel] [PATCH v2 6/6] libcamera: camera: Add a\n\tvalidation API to the CameraConfiguration class","submitter":{"id":3,"url":"https://patchwork.libcamera.org/api/people/3/","name":"Jacopo Mondi","email":"jacopo@jmondi.org"},"content":"Hi Laurent,\n\nIn order to validate if the API is dump-proof enough, I'm starting\nthe review from the last patch, so I might be missing some pieces\nhere and there...\n\nOn Sun, May 19, 2019 at 06:00:47PM +0300, Laurent Pinchart wrote:\n> The CameraConfiguration class implements a simple storage of\n> StreamConfiguration with internal validation limited to verifying that\n> the stream configurations are not empty. Extend this mechanism by\n> implementing a smart validate() method backed by pipeline handlers.\n>\n> This new mechanism changes the semantics of the camera configuration.\n\ns/semantics/semantic ?\n\n> The Camera::generateConfiguration() operation still generates a default\n> configuration based on roles, but now also supports generating empty\n> configurations to be filled by applications. Applications can inspect\n> the configuration, optionally modify it, and validate it. The validation\n> implements \"try\" semantics and adjusts invalid configurations instead of\n> rejecting them completely. Applications then decide whether to accept\n> the modified configuration, or try again with a different set of\n> parameters. Once the configuration is valid, it is passed to\n> Camera::configure(), and pipeline handlers are guaranteed that the\n> configuration they receive is valid.\n>\n> A reference to the Camera may need to be stored in the\n> CameraConfiguration derived classes in order to access it from their\n> validate() implementation. This must be stored as a std::shared_ptr<> as\n> the CameraConfiguration instances belong to applications. In order to\n> make this possible, make the Camera class inherit from\n> std::shared_from_this<>.\n\nIf I got this right we'll have a\n\nCameraConfiguration::validate() and a\nCamera.configure()\n\nand CameraConfiguration has to keep a reference to the Camera to\naccess it.\n\nCan we provide a Camera::validate(StreamConfiguration *) instead?\nWe could handle the shared reference in the Camera and not let\nCameraConfiguration subclasses have to deal with it in this way?\n\n>\n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> ---\n>  include/libcamera/camera.h               |  17 +-\n>  src/cam/main.cpp                         |   2 +-\n>  src/libcamera/camera.cpp                 |  80 +++++--\n>  src/libcamera/pipeline/ipu3/ipu3.cpp     | 255 ++++++++++++++++++-----\n>  src/libcamera/pipeline/rkisp1/rkisp1.cpp | 149 ++++++++++---\n>  src/libcamera/pipeline/uvcvideo.cpp      |  53 ++++-\n>  src/libcamera/pipeline/vimc.cpp          |  67 +++++-\n>  src/libcamera/pipeline_handler.cpp       |  17 +-\n>  test/camera/capture.cpp                  |   7 +-\n>  test/camera/configuration_default.cpp    |   4 +-\n>  test/camera/configuration_set.cpp        |   7 +-\n>  11 files changed, 516 insertions(+), 142 deletions(-)\n>\n> diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> index 144133c5de9f..8c0049a1dd94 100644\n> --- a/include/libcamera/camera.h\n> +++ b/include/libcamera/camera.h\n> @@ -25,14 +25,19 @@ class Request;\n>  class CameraConfiguration\n>  {\n>  public:\n> +\tenum Status {\n> +\t\tValid,\n> +\t\tAdjusted,\n> +\t\tInvalid,\n> +\t};\n> +\n>  \tusing iterator = std::vector<StreamConfiguration>::iterator;\n>  \tusing const_iterator = std::vector<StreamConfiguration>::const_iterator;\n>\n> -\tCameraConfiguration();\n> +\tvirtual ~CameraConfiguration();\n>\n>  \tvoid addConfiguration(const StreamConfiguration &cfg);\n> -\n> -\tbool isValid() const;\n> +\tvirtual Status validate() = 0;\n>\n>  \tStreamConfiguration &at(unsigned int index);\n>  \tconst StreamConfiguration &at(unsigned int index) const;\n> @@ -53,11 +58,13 @@ public:\n>  \tbool empty() const;\n>  \tstd::size_t size() const;\n>\n> -private:\n> +protected:\n> +\tCameraConfiguration();\n> +\n>  \tstd::vector<StreamConfiguration> config_;\n>  };\n>\n> -class Camera final\n> +class Camera final : public std::enable_shared_from_this<Camera>\n>  {\n>  public:\n>  \tstatic std::shared_ptr<Camera> create(PipelineHandler *pipe,\n> diff --git a/src/cam/main.cpp b/src/cam/main.cpp\n> index 7550ae4f3428..23da5c687d89 100644\n> --- a/src/cam/main.cpp\n> +++ b/src/cam/main.cpp\n> @@ -116,7 +116,7 @@ static CameraConfiguration *prepareCameraConfig()\n>  \t}\n>\n>  \tCameraConfiguration *config = camera->generateConfiguration(roles);\n> -\tif (!config || !config->isValid()) {\n> +\tif (!config || config->size() != roles.size()) {\n>  \t\tstd::cerr << \"Failed to get default stream configuration\"\n>  \t\t\t  << std::endl;\n>  \t\tdelete config;\n> diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> index 0e80691ed862..9da5d4f613f4 100644\n> --- a/src/libcamera/camera.cpp\n> +++ b/src/libcamera/camera.cpp\n> @@ -52,6 +52,28 @@ LOG_DECLARE_CATEGORY(Camera)\n>   * operator[](int) returns a reference to the StreamConfiguration based on its\n>   * insertion index. Accessing a stream configuration with an invalid index\n>   * results in undefined behaviour.\n> + *\n> + * CameraConfiguration instances are retrieved from the camera with\n> + * Camera::generateConfiguration(). Applications may then inspect the\n> + * configuration, modify it, and possibly add new stream configuration entries\n> + * with addConfiguration(). Once the camera configuration satisfies the\n> + * application, it shall be validated by a call to validate(). The validation\n> + * implements \"try\" semantics: it adjusts invalid configurations to the closest\n> + * achievable parameters instead of rejecting them completely. Applications\n> + * then decide whether to accept the modified configuration, or try again with\n> + * a different set of parameters. Once the configuration is valid, it is passed\n> + * to Camera::configure().\n> + */\n> +\n> +/**\n> + * \\enum CameraConfiguration::Status\n> + * \\brief Validity of a camera configuration\n> + * \\var CameraConfiguration::Valid\n> + * The configuration is fully valid\n> + * \\var CameraConfiguration::Adjusted\n> + * The configuration has been adjusted to a valid configuration\n> + * \\var CameraConfiguration::Invalid\n> + * The configuration is invalid and can't be adjusted automatically\n>   */\n>\n>  /**\n> @@ -73,6 +95,10 @@ CameraConfiguration::CameraConfiguration()\n>  {\n>  }\n>\n> +CameraConfiguration::~CameraConfiguration()\n> +{\n> +}\n> +\n>  /**\n>   * \\brief Add a stream configuration to the camera configuration\n>   * \\param[in] cfg The stream configuration\n> @@ -83,27 +109,31 @@ void CameraConfiguration::addConfiguration(const StreamConfiguration &cfg)\n>  }\n>\n>  /**\n> - * \\brief Check if the camera configuration is valid\n> + * \\fn CameraConfiguration::validate()\n> + * \\brief Validate and possibly adjust the camera configuration\n>   *\n> - * A camera configuration is deemed to be valid if it contains at least one\n> - * stream configuration and all stream configurations contain valid information.\n> - * Stream configurations are deemed to be valid if all fields are none zero.\n> + * This method adjusts the camera configuration to the closest valid\n> + * configuration and returns the validation status.\n>   *\n> - * \\return True if the configuration is valid\n> + * \\todo: Define exactly when to return each status code. Should stream\n> + * parameters set to 0 by the caller be adjusted without returning Adjusted ?\n> + * This would potentially be useful for applications but would get in the way\n> + * in Camera::configure(). Do we need an extra status code to signal this ?\n\n\nI'm not sure I did get why it gets in the way of configure()\n\n> + *\n> + * \\todo: Handle validation of buffers count when refactoring the buffers API.\n> + *\n> + * \\return A CameraConfiguration::Status value that describes the validation\n> + * status.\n> + * \\retval CameraConfiguration::Invalid The configuration is invalid and can't\n> + * be adjusted. This may only occur in extreme cases such as when the\n> + * configuration is empty.\n\nnit: instead of describing how the returned configuration looks like,\ndo you have an example of application provided parameters which might\ntrigger and invalid use case ?\n\n> + * \\retval CameraConfigutation::Adjusted The configuration has been adjusted\n> + * and is now valid. Parameters may have changed for any stream, and stream\n> + * configurations may have been removed. The caller shall check the\n> + * configuration carefully.\n> + * \\retval CameraConfiguration::Valid The configuration was already valid and\n> + * hasn't been adjusted.\n>   */\n> -bool CameraConfiguration::isValid() const\n> -{\n> -\tif (empty())\n> -\t\treturn false;\n> -\n> -\tfor (const StreamConfiguration &cfg : config_) {\n> -\t\tif (cfg.size.width == 0 || cfg.size.height == 0 ||\n> -\t\t    cfg.pixelFormat == 0 || cfg.bufferCount == 0)\n> -\t\t\treturn false;\n> -\t}\n> -\n> -\treturn true;\n> -}\n>\n>  /**\n>   * \\brief Retrieve a reference to a stream configuration\n> @@ -218,6 +248,11 @@ std::size_t CameraConfiguration::size() const\n>  \treturn config_.size();\n>  }\n>\n> +/**\n> + * \\var CameraConfiguration::config_\n> + * \\brief The vector of stream configurations\n> + */\n> +\n>  /**\n>   * \\class Camera\n>   * \\brief Camera device\n> @@ -575,10 +610,9 @@ CameraConfiguration *Camera::generateConfiguration(const StreamRoles &roles)\n>   * The caller specifies which streams are to be involved and their configuration\n>   * by populating \\a config.\n>   *\n> - * The easiest way to populate the array of config is to fetch an initial\n> - * configuration from the camera with generateConfiguration() and then change\n> - * the parameters to fit the caller's need and once all the streams parameters\n> - * are configured hand that over to configure() to actually setup the camera.\n> + * The configuration is created by generateConfiguration(), and adjusted by the\n> + * caller with CameraConfiguration::validate(). This method only accepts fully\n> + * valid configurations and returns an error if \\a config is not valid.\n>   *\n>   * Exclusive access to the camera shall be ensured by a call to acquire() prior\n>   * to calling this function, otherwise an -EACCES error will be returned.\n> @@ -603,7 +637,7 @@ int Camera::configure(CameraConfiguration *config)\n>  \tif (!stateBetween(CameraAcquired, CameraConfigured))\n>  \t\treturn -EACCES;\n>\n> -\tif (!config->isValid()) {\n> +\tif (config->validate() != CameraConfiguration::Valid) {\n>  \t\tLOG(Camera, Error)\n>  \t\t\t<< \"Can't configure camera with invalid configuration\";\n>  \t\treturn -EINVAL;\n> diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp\n> index 5b46fb68ced2..56265385a1e7 100644\n> --- a/src/libcamera/pipeline/ipu3/ipu3.cpp\n> +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp\n> @@ -164,6 +164,33 @@ public:\n>  \tIPU3Stream vfStream_;\n>  };\n>\n> +class IPU3CameraConfiguration : public CameraConfiguration\n> +{\n> +public:\n> +\tIPU3CameraConfiguration(Camera *camera, IPU3CameraData *data);\n> +\n> +\tStatus validate() override;\n> +\n> +\tconst V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }\n> +\tconst std::vector<const IPU3Stream *> &streams() { return streams_; }\n> +\n> +private:\n> +\tstatic constexpr unsigned int IPU3_BUFFER_COUNT = 4;\n> +\n> +\tvoid adjustStream(unsigned int index, bool scale);\n> +\n> +\t/*\n> +\t * The IPU3CameraData instance is guaranteed to be valid as long as the\n> +\t * corresponding Camera instance is valid. In order to borrow a\n> +\t * reference to the camera data, store a new reference to the camera.\n> +\t */\n> +\tstd::shared_ptr<Camera> camera_;\n> +\tconst IPU3CameraData *data_;\n> +\n> +\tV4L2SubdeviceFormat sensorFormat_;\n> +\tstd::vector<const IPU3Stream *> streams_;\n> +};\n> +\n>  class PipelineHandlerIPU3 : public PipelineHandler\n>  {\n>  public:\n> @@ -186,8 +213,6 @@ public:\n>  \tbool match(DeviceEnumerator *enumerator) override;\n>\n>  private:\n> -\tstatic constexpr unsigned int IPU3_BUFFER_COUNT = 4;\n> -\n>  \tIPU3CameraData *cameraData(const Camera *camera)\n>  \t{\n>  \t\treturn static_cast<IPU3CameraData *>(\n> @@ -202,6 +227,153 @@ private:\n>  \tMediaDevice *imguMediaDev_;\n>  };\n>\n> +IPU3CameraConfiguration::IPU3CameraConfiguration(Camera *camera,\n> +\t\t\t\t\t\t IPU3CameraData *data)\n> +\t: CameraConfiguration()\n> +{\n> +\tcamera_ = camera->shared_from_this();\n> +\tdata_ = data;\n> +}\n> +\n> +void IPU3CameraConfiguration::adjustStream(unsigned int index, bool scale)\n\nCould you pass here the StreanConfiguration & instead of i ?\n\n> +{\n> +\tStreamConfiguration &cfg = config_[index];\n> +\n> +\t/* The only pixel format the driver supports is NV12. */\n> +\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> +\n> +\tif (scale) {\n> +\t\t/*\n> +\t\t * Provide a suitable default that matches the sensor aspect\n> +\t\t * ratio.\n> +\t\t */\n> +\t\tif (!cfg.size.width || !cfg.size.height) {\n> +\t\t\tcfg.size.width = 1280;\n> +\t\t\tcfg.size.height = 1280 * sensorFormat_.size.height\n> +\t\t\t\t\t/ sensorFormat_.size.width;\n> +\t\t}\n> +\n> +\t\t/*\n> +\t\t * \\todo: Clamp the size to the hardware bounds when we will\n> +\t\t * figure them out.\n> +\t\t *\n> +\t\t * \\todo: Handle the scaler (BDS) restrictions. The BDS can\n> +\t\t * only scale with the same factor in both directions, and the\n> +\t\t * scaling factor is limited to a multiple of 1/32. At the\n> +\t\t * moment the ImgU driver hides these constraints by applying\n> +\t\t * additional cropping, this should be fixed on the driver\n> +\t\t * side, and cropping should be exposed to us.\n> +\t\t */\n> +\t} else {\n> +\t\t/*\n> +\t\t * \\todo: Properly support cropping when the ImgU driver\n> +\t\t * interface will be cleaned up.\n> +\t\t */\n> +\t\tcfg.size = sensorFormat_.size;\n> +\t}\n> +\n> +\t/*\n> +\t * Clamp the size to match the ImgU alignment constraints. The width\n> +\t * shall be a multiple of 8 pixels and the height a multiple of 4\n> +\t * pixels.\n> +\t */\n> +\tif (cfg.size.width % 8 || cfg.size.height % 4) {\n> +\t\tcfg.size.width &= ~7;\n\nSame question as below, is it safe to use the sensor provided sizes?\nIn here, in example, 2592 is a multiple of 8, but I'm not sure it\nworks well.\n\n(sorry, you should read below first)\n\n> +\t\tcfg.size.height &= ~3;\n> +\t}\n> +\n> +\tcfg.bufferCount = IPU3_BUFFER_COUNT;\n> +}\n> +\n> +CameraConfiguration::Status IPU3CameraConfiguration::validate()\n> +{\n> +\tconst CameraSensor *sensor = data_->cio2_.sensor_;\n> +\tStatus status = Valid;\n> +\n> +\tif (config_.empty())\n> +\t\treturn Invalid;\n> +\n> +\t/* Cap the number of entries to the available streams. */\n> +\tif (config_.size() > 2) {\n> +\t\tconfig_.resize(2);\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\t/*\n> +\t * Select the sensor format by collecting the maximum width and height\n> +\t * and picking the closest larger match, as the IPU3 can downscale\n> +\t * only. If no resolution is requested for any stream, or if no sensor\n> +\t * resolution is large enough, pick the largest one.\n> +\t */\n> +\tSize size = {};\n> +\n> +\tfor (const StreamConfiguration &cfg : config_) {\n> +\t\tif (cfg.size.width > size.width)\n> +\t\t\tsize.width = cfg.size.width;\n> +\t\tif (cfg.size.height > size.height)\n> +\t\t\tsize.height = cfg.size.height;\n> +\t}\n> +\n> +\tif (!size.width || !size.height)\n> +\t\tsize = sensor->resolution();\n> +\n> +\tsensorFormat_ = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR10_1X10,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG10_1X10,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG10_1X10,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB10_1X10 },\n> +\t\t\t\t\t  size);\n> +\tif (!sensorFormat_.size.width || !sensorFormat_.size.height)\n> +\t\tsensorFormat_.size = sensor->resolution();\n> +\n> +\t/*\n> +\t * Verify and update all configuration entries, and assign a stream to\n> +\t * each of them. The viewfinder stream can scale, while the output\n> +\t * stream can crop only, so select the output stream when the requested\n> +\t * resolution is equal to the sensor resolution, and the viewfinder\n> +\t * stream otherwise.\n> +\t */\n> +\tstd::set<const IPU3Stream *> availableStreams = {\n> +\t\t&data_->outStream_,\n> +\t\t&data_->vfStream_,\n> +\t};\n> +\n> +\tstreams_.clear();\n> +\tstreams_.reserve(config_.size());\n> +\n> +\tfor (unsigned int i = 0; i < config_.size(); ++i) {\n> +\t\tStreamConfiguration &cfg = config_[i];\n\n\tfor (StreamConfiguration &cfg : config_) ?\n\n> +\t\tconst unsigned int pixelFormat = cfg.pixelFormat;\n> +\t\tconst Size size = cfg.size;\n> +\t\tconst IPU3Stream *stream;\n> +\n> +\t\tif (cfg.size == sensorFormat_.size)\n\n(here, more precisely)\n\nI'm not sure about this. I always seen the IPU3 used with maximum size\n(for ov5670) as 2560x1920 while the sensor resolution is actually a\nbit larger 2592x1944. Same for the back ov13858 camera. I never had the\nfull sensor resolution working properly and I assumed it was related to\nsome IPU3 alignement or constraints I didn't know about. Also, the\nIntel provided xml configuration file does limit the maximum\navailable resolution to 2560x1920, but that could be for other reasons\nmaybe... Have you tested capture at sensor resolution sizes?\n\n> +\t\t\tstream = &data_->outStream_;\n> +\t\telse\n> +\t\t\tstream = &data_->vfStream_;\n> +\n> +\t\tif (availableStreams.find(stream) == availableStreams.end())\n> +\t\t\tstream = *availableStreams.begin();\n\nThis works as long as we have 2 streams only, maybe a todo ?\n\n> +\n> +\t\tLOG(IPU3, Debug)\n> +\t\t\t<< \"Assigned '\" << stream->name_ << \"' to stream \" << i;\n\nAh, maybe it's better not to use the range-based for loop, you need i\n\n> +\n> +\t\tbool scale = stream == &data_->vfStream_;\n> +\t\tadjustStream(i, scale);\n> +\n> +\t\tif (cfg.pixelFormat != pixelFormat || cfg.size != size) {\n> +\t\t\tLOG(IPU3, Debug)\n> +\t\t\t\t<< \"Stream \" << i << \" configuration adjusted to \"\n> +\t\t\t\t<< cfg.toString();\n> +\t\t\tstatus = Adjusted;\n> +\t\t}\n> +\n> +\t\tstreams_.push_back(stream);\n> +\t\tavailableStreams.erase(stream);\n> +\t}\n> +\n> +\treturn status;\n> +}\n> +\n>  PipelineHandlerIPU3::PipelineHandlerIPU3(CameraManager *manager)\n>  \t: PipelineHandler(manager), cio2MediaDev_(nullptr), imguMediaDev_(nullptr)\n>  {\n> @@ -211,12 +383,14 @@ CameraConfiguration *PipelineHandlerIPU3::generateConfiguration(Camera *camera,\n>  \tconst StreamRoles &roles)\n>  {\n>  \tIPU3CameraData *data = cameraData(camera);\n> -\tCameraConfiguration *config = new CameraConfiguration();\n> +\tIPU3CameraConfiguration *config;\n>  \tstd::set<IPU3Stream *> streams = {\n>  \t\t&data->outStream_,\n>  \t\t&data->vfStream_,\n>  \t};\n>\n> +\tconfig = new IPU3CameraConfiguration(camera, data);\n> +\n>  \tfor (const StreamRole role : roles) {\n>  \t\tStreamConfiguration cfg = {};\n>  \t\tIPU3Stream *stream = nullptr;\n> @@ -296,71 +470,25 @@ CameraConfiguration *PipelineHandlerIPU3::generateConfiguration(Camera *camera,\n>\n>  \t\tstreams.erase(stream);\n>\n> -\t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> -\t\tcfg.bufferCount = IPU3_BUFFER_COUNT;\n> -\n>  \t\tconfig->addConfiguration(cfg);\n>  \t}\n>\n> +\tconfig->validate();\n> +\n\nIf the idea of providing a Camera::validate(CameraConfiguration *) has\nany ground, we should then have a private CameraConfiguration::validate()\noperation accessible by the Camera class through a friend declarator,\nso that pipeline handlers implementation cannot call it, restricting\nthat method to the application facing API only. The\nCameraConfiguration subclasses' validate() implementation could then\ncall into the same operation that pipeline handler would use here at\ngenerateConfiguration() time as you did here, if desirable.\n\nAlso, again if Camera::validate(CameraConfiguration *) makes any\nsense, we might want to group basic validations, if any. So we could\nmake Camera::validate() call into the private CameraConfiguration::validate()\noperation, which performs basic operations, and calls into a protected\nvirtual PipelineHandler::__validate() (or whatever name is more\nappropriate)\n\nWould this be doable?\n\n>  \treturn config;\n>  }\n>\n> -int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n> +int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *c)\n>  {\n> +\tIPU3CameraConfiguration *config =\n> +\t\tstatic_cast<IPU3CameraConfiguration *>(c);\n>  \tIPU3CameraData *data = cameraData(camera);\n>  \tIPU3Stream *outStream = &data->outStream_;\n>  \tIPU3Stream *vfStream = &data->vfStream_;\n>  \tCIO2Device *cio2 = &data->cio2_;\n>  \tImgUDevice *imgu = data->imgu_;\n> -\tSize sensorSize = {};\n>  \tint ret;\n>\n> -\toutStream->active_ = false;\n> -\tvfStream->active_ = false;\n> -\tfor (StreamConfiguration &cfg : *config) {\n> -\t\t/*\n> -\t\t * Pick a stream for the configuration entry.\n> -\t\t * \\todo: This is a naive temporary implementation that will be\n> -\t\t * reworked when implementing camera configuration validation.\n> -\t\t */\n> -\t\tIPU3Stream *stream = vfStream->active_ ? outStream : vfStream;\n> -\n> -\t\t/*\n> -\t\t * Verify that the requested size respects the IPU3 alignment\n> -\t\t * requirements (the image width shall be a multiple of 8\n> -\t\t * pixels and its height a multiple of 4 pixels) and the camera\n> -\t\t * maximum sizes.\n> -\t\t *\n> -\t\t * \\todo: Consider the BDS scaling factor requirements: \"the\n> -\t\t * downscaling factor must be an integer value multiple of 1/32\"\n> -\t\t */\n> -\t\tif (cfg.size.width % 8 || cfg.size.height % 4) {\n> -\t\t\tLOG(IPU3, Error)\n> -\t\t\t\t<< \"Invalid stream size: bad alignment\";\n> -\t\t\treturn -EINVAL;\n> -\t\t}\n> -\n> -\t\tconst Size &resolution = cio2->sensor_->resolution();\n> -\t\tif (cfg.size.width > resolution.width ||\n> -\t\t    cfg.size.height > resolution.height) {\n> -\t\t\tLOG(IPU3, Error)\n> -\t\t\t\t<< \"Invalid stream size: larger than sensor resolution\";\n> -\t\t\treturn -EINVAL;\n> -\t\t}\n> -\n> -\t\t/*\n> -\t\t * Collect the maximum width and height: IPU3 can downscale\n> -\t\t * only.\n> -\t\t */\n> -\t\tif (cfg.size.width > sensorSize.width)\n> -\t\t\tsensorSize.width = cfg.size.width;\n> -\t\tif (cfg.size.height > sensorSize.height)\n> -\t\t\tsensorSize.height = cfg.size.height;\n> -\n> -\t\tstream->active_ = true;\n> -\t\tcfg.setStream(stream);\n> -\t}\n> -\n>  \t/*\n>  \t * \\todo: Enable links selectively based on the requested streams.\n>  \t * As of now, enable all links unconditionally.\n> @@ -373,6 +501,7 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n>  \t * Pass the requested stream size to the CIO2 unit and get back the\n>  \t * adjusted format to be propagated to the ImgU output devices.\n>  \t */\n> +\tconst Size &sensorSize = config->sensorFormat().size;\n>  \tV4L2DeviceFormat cio2Format = {};\n>  \tret = cio2->configure(sensorSize, &cio2Format);\n>  \tif (ret)\n> @@ -383,8 +512,22 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n>  \t\treturn ret;\n>\n>  \t/* Apply the format to the configured streams output devices. */\n> -\tfor (StreamConfiguration &cfg : *config) {\n> -\t\tIPU3Stream *stream = static_cast<IPU3Stream *>(cfg.stream());\n> +\toutStream->active_ = false;\n> +\tvfStream->active_ = false;\n> +\n> +\tfor (unsigned int i = 0; i < config->size(); ++i) {\n> +\t\t/*\n> +\t\t * Use a const_cast<> here instead of storing a mutable stream\n> +\t\t * pointer in the configuration to let the compiler catch\n> +\t\t * unwanted modifications of camera data in the configuration\n> +\t\t * validate() implementation.\n> +\t\t */\n> +\t\tIPU3Stream *stream = const_cast<IPU3Stream *>(config->streams()[i]);\n> +\t\tStreamConfiguration &cfg = (*config)[i];\n\nnit: the fact you can get both Stream * and StreamConfiguration * by index\nit's a bit ugly. What about config->streams(i) and\nconfig->configuration(i).\n\nI think what's fishy lies in the fact IPu3CameraConfiguration indexes both\nStream and Configurations in two different vactors and both accessed by\nindex. Do you need Streams in CameraConfiguration ? Can't you store\nthem in CameraData, as 'activeStreams_' maybe?\n\n> +\n> +\t\tstream->active_ = true;\n> +\t\tcfg.setStream(stream);\n> +\n>  \t\tret = imgu->configureOutput(stream->device_, cfg);\n>  \t\tif (ret)\n>  \t\t\treturn ret;\n> diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> index a1a4f205b4aa..42944c64189b 100644\n> --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n\nAn impressive rework, I like where it's going even if it puts\na bit of a burden on pipeline handlers, it's for the benefit of the\napplication facing APIs.\n\nThanks\n  j\n\n> @@ -5,6 +5,7 @@\n>   * rkisp1.cpp - Pipeline handler for Rockchip ISP1\n>   */\n>\n> +#include <algorithm>\n>  #include <iomanip>\n>  #include <memory>\n>  #include <vector>\n> @@ -45,6 +46,29 @@ public:\n>  \tCameraSensor *sensor_;\n>  };\n>\n> +class RkISP1CameraConfiguration : public CameraConfiguration\n> +{\n> +public:\n> +\tRkISP1CameraConfiguration(Camera *camera, RkISP1CameraData *data);\n> +\n> +\tStatus validate() override;\n> +\n> +\tconst V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }\n> +\n> +private:\n> +\tstatic constexpr unsigned int RKISP1_BUFFER_COUNT = 4;\n> +\n> +\t/*\n> +\t * The RkISP1CameraData instance is guaranteed to be valid as long as the\n> +\t * corresponding Camera instance is valid. In order to borrow a\n> +\t * reference to the camera data, store a new reference to the camera.\n> +\t */\n> +\tstd::shared_ptr<Camera> camera_;\n> +\tconst RkISP1CameraData *data_;\n> +\n> +\tV4L2SubdeviceFormat sensorFormat_;\n> +};\n> +\n>  class PipelineHandlerRkISP1 : public PipelineHandler\n>  {\n>  public:\n> @@ -68,8 +92,6 @@ public:\n>  \tbool match(DeviceEnumerator *enumerator) override;\n>\n>  private:\n> -\tstatic constexpr unsigned int RKISP1_BUFFER_COUNT = 4;\n> -\n>  \tRkISP1CameraData *cameraData(const Camera *camera)\n>  \t{\n>  \t\treturn static_cast<RkISP1CameraData *>(\n> @@ -88,6 +110,95 @@ private:\n>  \tCamera *activeCamera_;\n>  };\n>\n> +RkISP1CameraConfiguration::RkISP1CameraConfiguration(Camera *camera,\n> +\t\t\t\t\t\t     RkISP1CameraData *data)\n> +\t: CameraConfiguration()\n> +{\n> +\tcamera_ = camera->shared_from_this();\n> +\tdata_ = data;\n> +}\n> +\n> +CameraConfiguration::Status RkISP1CameraConfiguration::validate()\n> +{\n> +\tstatic const std::array<unsigned int, 8> formats{\n> +\t\tV4L2_PIX_FMT_YUYV,\n> +\t\tV4L2_PIX_FMT_YVYU,\n> +\t\tV4L2_PIX_FMT_VYUY,\n> +\t\tV4L2_PIX_FMT_NV16,\n> +\t\tV4L2_PIX_FMT_NV61,\n> +\t\tV4L2_PIX_FMT_NV21,\n> +\t\tV4L2_PIX_FMT_NV12,\n> +\t\tV4L2_PIX_FMT_GREY,\n> +\t};\n> +\n> +\tconst CameraSensor *sensor = data_->sensor_;\n> +\tStatus status = Valid;\n> +\n> +\tif (config_.empty())\n> +\t\treturn Invalid;\n> +\n> +\t/* Cap the number of entries to the available streams. */\n> +\tif (config_.size() > 1) {\n> +\t\tconfig_.resize(1);\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\tStreamConfiguration &cfg = config_[0];\n> +\n> +\t/* Adjust the pixel format. */\n> +\tif (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==\n> +\t    formats.end()) {\n> +\t\tLOG(RkISP1, Debug) << \"Adjusting format to NV12\";\n> +\t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\t/* Select the sensor format. */\n> +\tsensorFormat_ = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR12_1X12,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG12_1X12,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG12_1X12,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB12_1X12,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SBGGR10_1X10,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG10_1X10,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG10_1X10,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB10_1X10,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SBGGR8_1X8,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG8_1X8,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG8_1X8,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB8_1X8 },\n> +\t\t\t\t\t  cfg.size);\n> +\tif (!sensorFormat_.size.width || !sensorFormat_.size.height)\n> +\t\tsensorFormat_.size = sensor->resolution();\n> +\n> +\t/*\n> +\t * Provide a suitable default that matches the sensor aspect\n> +\t * ratio and clamp the size to the hardware bounds.\n> +\t *\n> +\t * \\todo: Check the hardware alignment constraints.\n> +\t */\n> +\tconst Size size = cfg.size;\n> +\n> +\tif (!cfg.size.width || !cfg.size.height) {\n> +\t\tcfg.size.width = 1280;\n> +\t\tcfg.size.height = 1280 * sensorFormat_.size.height\n> +\t\t\t\t/ sensorFormat_.size.width;\n> +\t}\n> +\n> +\tcfg.size.width = std::max(32U, std::min(4416U, cfg.size.width));\n> +\tcfg.size.height = std::max(16U, std::min(3312U, cfg.size.height));\n> +\n> +\tif (cfg.size != size) {\n> +\t\tLOG(RkISP1, Debug)\n> +\t\t\t<< \"Adjusting size from \" << size.toString()\n> +\t\t\t<< \" to \" << cfg.size.toString();\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\tcfg.bufferCount = RKISP1_BUFFER_COUNT;\n> +\n> +\treturn status;\n> +}\n> +\n>  PipelineHandlerRkISP1::PipelineHandlerRkISP1(CameraManager *manager)\n>  \t: PipelineHandler(manager), dphy_(nullptr), isp_(nullptr),\n>  \t  video_(nullptr)\n> @@ -109,37 +220,31 @@ CameraConfiguration *PipelineHandlerRkISP1::generateConfiguration(Camera *camera\n>  \tconst StreamRoles &roles)\n>  {\n>  \tRkISP1CameraData *data = cameraData(camera);\n> -\tCameraConfiguration *config = new CameraConfiguration();\n> +\tCameraConfiguration *config = new RkISP1CameraConfiguration(camera, data);\n>\n>  \tif (!roles.empty()) {\n>  \t\tStreamConfiguration cfg{};\n>\n>  \t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n>  \t\tcfg.size = data->sensor_->resolution();\n> -\t\tcfg.bufferCount = RKISP1_BUFFER_COUNT;\n>\n>  \t\tconfig->addConfiguration(cfg);\n>  \t}\n>\n> +\tconfig->validate();\n> +\n>  \treturn config;\n>  }\n>\n> -int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *config)\n> +int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)\n>  {\n> +\tRkISP1CameraConfiguration *config =\n> +\t\tstatic_cast<RkISP1CameraConfiguration *>(c);\n>  \tRkISP1CameraData *data = cameraData(camera);\n>  \tStreamConfiguration &cfg = config->at(0);\n>  \tCameraSensor *sensor = data->sensor_;\n>  \tint ret;\n>\n> -\t/* Verify the configuration. */\n> -\tconst Size &resolution = sensor->resolution();\n> -\tif (cfg.size.width > resolution.width ||\n> -\t    cfg.size.height > resolution.height) {\n> -\t\tLOG(RkISP1, Error)\n> -\t\t\t<< \"Invalid stream size: larger than sensor resolution\";\n> -\t\treturn -EINVAL;\n> -\t}\n> -\n>  \t/*\n>  \t * Configure the sensor links: enable the link corresponding to this\n>  \t * camera and disable all the other sensor links.\n> @@ -167,21 +272,7 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *config\n>  \t * Configure the format on the sensor output and propagate it through\n>  \t * the pipeline.\n>  \t */\n> -\tV4L2SubdeviceFormat format;\n> -\tformat = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR12_1X12,\n> -\t\t\t\t     MEDIA_BUS_FMT_SGBRG12_1X12,\n> -\t\t\t\t     MEDIA_BUS_FMT_SGRBG12_1X12,\n> -\t\t\t\t     MEDIA_BUS_FMT_SRGGB12_1X12,\n> -\t\t\t\t     MEDIA_BUS_FMT_SBGGR10_1X10,\n> -\t\t\t\t     MEDIA_BUS_FMT_SGBRG10_1X10,\n> -\t\t\t\t     MEDIA_BUS_FMT_SGRBG10_1X10,\n> -\t\t\t\t     MEDIA_BUS_FMT_SRGGB10_1X10,\n> -\t\t\t\t     MEDIA_BUS_FMT_SBGGR8_1X8,\n> -\t\t\t\t     MEDIA_BUS_FMT_SGBRG8_1X8,\n> -\t\t\t\t     MEDIA_BUS_FMT_SGRBG8_1X8,\n> -\t\t\t\t     MEDIA_BUS_FMT_SRGGB8_1X8 },\n> -\t\t\t\t   cfg.size);\n> -\n> +\tV4L2SubdeviceFormat format = config->sensorFormat();\n>  \tLOG(RkISP1, Debug) << \"Configuring sensor with \" << format.toString();\n>\n>  \tret = sensor->setFormat(&format);\n> diff --git a/src/libcamera/pipeline/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo.cpp\n> index 8254e1fdac1e..f7ffeb439cf3 100644\n> --- a/src/libcamera/pipeline/uvcvideo.cpp\n> +++ b/src/libcamera/pipeline/uvcvideo.cpp\n> @@ -39,6 +39,14 @@ public:\n>  \tStream stream_;\n>  };\n>\n> +class UVCCameraConfiguration : public CameraConfiguration\n> +{\n> +public:\n> +\tUVCCameraConfiguration();\n> +\n> +\tStatus validate() override;\n> +};\n> +\n>  class PipelineHandlerUVC : public PipelineHandler\n>  {\n>  public:\n> @@ -68,6 +76,45 @@ private:\n>  \t}\n>  };\n>\n> +UVCCameraConfiguration::UVCCameraConfiguration()\n> +\t: CameraConfiguration()\n> +{\n> +}\n> +\n> +CameraConfiguration::Status UVCCameraConfiguration::validate()\n> +{\n> +\tStatus status = Valid;\n> +\n> +\tif (config_.empty())\n> +\t\treturn Invalid;\n> +\n> +\t/* Cap the number of entries to the available streams. */\n> +\tif (config_.size() > 1) {\n> +\t\tconfig_.resize(1);\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\tStreamConfiguration &cfg = config_[0];\n> +\n> +\t/* \\todo: Validate the configuration against the device capabilities. */\n> +\tconst unsigned int pixelFormat = cfg.pixelFormat;\n> +\tconst Size size = cfg.size;\n> +\n> +\tcfg.pixelFormat = V4L2_PIX_FMT_YUYV;\n> +\tcfg.size = { 640, 480 };\n> +\n> +\tif (cfg.pixelFormat != pixelFormat || cfg.size != size) {\n> +\t\tLOG(UVC, Debug)\n> +\t\t\t<< \"Adjusting configuration from \" << cfg.toString()\n> +\t\t\t<< \" to \" << cfg.size.toString() << \"-YUYV\";\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\tcfg.bufferCount = 4;\n> +\n> +\treturn status;\n> +}\n> +\n>  PipelineHandlerUVC::PipelineHandlerUVC(CameraManager *manager)\n>  \t: PipelineHandler(manager)\n>  {\n> @@ -76,10 +123,10 @@ PipelineHandlerUVC::PipelineHandlerUVC(CameraManager *manager)\n>  CameraConfiguration *PipelineHandlerUVC::generateConfiguration(Camera *camera,\n>  \tconst StreamRoles &roles)\n>  {\n> -\tCameraConfiguration *config = new CameraConfiguration();\n> +\tCameraConfiguration *config = new UVCCameraConfiguration();\n>\n>  \tif (!roles.empty()) {\n> -\t\tStreamConfiguration cfg;\n> +\t\tStreamConfiguration cfg{};\n>\n>  \t\tcfg.pixelFormat = V4L2_PIX_FMT_YUYV;\n>  \t\tcfg.size = { 640, 480 };\n> @@ -88,6 +135,8 @@ CameraConfiguration *PipelineHandlerUVC::generateConfiguration(Camera *camera,\n>  \t\tconfig->addConfiguration(cfg);\n>  \t}\n>\n> +\tconfig->validate();\n> +\n>  \treturn config;\n>  }\n>\n> diff --git a/src/libcamera/pipeline/vimc.cpp b/src/libcamera/pipeline/vimc.cpp\n> index 2bf85d0a0b32..2a61d2893e3a 100644\n> --- a/src/libcamera/pipeline/vimc.cpp\n> +++ b/src/libcamera/pipeline/vimc.cpp\n> @@ -5,6 +5,8 @@\n>   * vimc.cpp - Pipeline handler for the vimc device\n>   */\n>\n> +#include <algorithm>\n> +\n>  #include <libcamera/camera.h>\n>  #include <libcamera/request.h>\n>  #include <libcamera/stream.h>\n> @@ -39,6 +41,14 @@ public:\n>  \tStream stream_;\n>  };\n>\n> +class VimcCameraConfiguration : public CameraConfiguration\n> +{\n> +public:\n> +\tVimcCameraConfiguration();\n> +\n> +\tStatus validate() override;\n> +};\n> +\n>  class PipelineHandlerVimc : public PipelineHandler\n>  {\n>  public:\n> @@ -68,6 +78,57 @@ private:\n>  \t}\n>  };\n>\n> +VimcCameraConfiguration::VimcCameraConfiguration()\n> +\t: CameraConfiguration()\n> +{\n> +}\n> +\n> +CameraConfiguration::Status VimcCameraConfiguration::validate()\n> +{\n> +\tstatic const std::array<unsigned int, 3> formats{\n> +\t\tV4L2_PIX_FMT_BGR24,\n> +\t\tV4L2_PIX_FMT_RGB24,\n> +\t\tV4L2_PIX_FMT_ARGB32,\n> +\t};\n> +\n> +\tStatus status = Valid;\n> +\n> +\tif (config_.empty())\n> +\t\treturn Invalid;\n> +\n> +\t/* Cap the number of entries to the available streams. */\n> +\tif (config_.size() > 1) {\n> +\t\tconfig_.resize(1);\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\tStreamConfiguration &cfg = config_[0];\n> +\n> +\t/* Adjust the pixel format. */\n> +\tif (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==\n> +\t    formats.end()) {\n> +\t\tLOG(VIMC, Debug) << \"Adjusting format to RGB24\";\n> +\t\tcfg.pixelFormat = V4L2_PIX_FMT_RGB24;\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\t/* Clamp the size based on the device limits. */\n> +\tconst Size size = cfg.size;\n> +\n> +\tcfg.size.width = std::max(16U, std::min(4096U, cfg.size.width));\n> +\tcfg.size.height = std::max(16U, std::min(2160U, cfg.size.height));\n> +\n> +\tif (cfg.size != size) {\n> +\t\tLOG(VIMC, Debug)\n> +\t\t\t<< \"Adjusting size to \" << cfg.size.toString();\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\tcfg.bufferCount = 4;\n> +\n> +\treturn status;\n> +}\n> +\n>  PipelineHandlerVimc::PipelineHandlerVimc(CameraManager *manager)\n>  \t: PipelineHandler(manager)\n>  {\n> @@ -76,10 +137,10 @@ PipelineHandlerVimc::PipelineHandlerVimc(CameraManager *manager)\n>  CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera,\n>  \tconst StreamRoles &roles)\n>  {\n> -\tCameraConfiguration *config = new CameraConfiguration();\n> +\tCameraConfiguration *config = new VimcCameraConfiguration();\n>\n>  \tif (!roles.empty()) {\n> -\t\tStreamConfiguration cfg;\n> +\t\tStreamConfiguration cfg{};\n>\n>  \t\tcfg.pixelFormat = V4L2_PIX_FMT_RGB24;\n>  \t\tcfg.size = { 640, 480 };\n> @@ -88,6 +149,8 @@ CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera,\n>  \t\tconfig->addConfiguration(cfg);\n>  \t}\n>\n> +\tconfig->validate();\n> +\n>  \treturn config;\n>  }\n>\n> diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp\n> index de46e98880a2..dd56907d817e 100644\n> --- a/src/libcamera/pipeline_handler.cpp\n> +++ b/src/libcamera/pipeline_handler.cpp\n> @@ -248,17 +248,14 @@ void PipelineHandler::unlock()\n>   * is the Camera class which will receive configuration to apply from the\n>   * application.\n>   *\n> - * Each pipeline handler implementation is responsible for validating\n> - * that the configuration requested in \\a config can be achieved\n> - * exactly. Any difference in pixel format, frame size or any other\n> - * parameter shall result in the -EINVAL error being returned, and no\n> - * change in configuration being applied to the pipeline. If\n> - * configuration of a subset of the streams can't be satisfied, the\n> - * whole configuration is considered invalid.\n> + * The configuration is guaranteed to have been validated with\n> + * CameraConfiguration::valid(). The pipeline handler implementation shall not\n> + * perform further validation and may rely on any custom field stored in its\n> + * custom CameraConfiguration derived class.\n>   *\n> - * Once the configuration is validated and the camera configured, the pipeline\n> - * handler shall associate a Stream instance to each StreamConfiguration entry\n> - * in the CameraConfiguration with the StreamConfiguration::setStream() method.\n> + * When configuring the camera the pipeline handler shall associate a Stream\n> + * instance to each StreamConfiguration entry in the CameraConfiguration using\n> + * the StreamConfiguration::setStream() method.\n>   *\n>   * \\return 0 on success or a negative error code otherwise\n>   */\n> diff --git a/test/camera/capture.cpp b/test/camera/capture.cpp\n> index 137aa649a505..f122f79bb1ec 100644\n> --- a/test/camera/capture.cpp\n> +++ b/test/camera/capture.cpp\n> @@ -45,7 +45,7 @@ protected:\n>  \t\tCameraTest::init();\n>\n>  \t\tconfig_ = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> -\t\tif (!config_) {\n> +\t\tif (!config_ || config_->size() != 1) {\n>  \t\t\tcout << \"Failed to generate default configuration\" << endl;\n>  \t\t\tCameraTest::cleanup();\n>  \t\t\treturn TestFail;\n> @@ -58,11 +58,6 @@ protected:\n>  \t{\n>  \t\tStreamConfiguration &cfg = config_->at(0);\n>\n> -\t\tif (!config_->isValid()) {\n> -\t\t\tcout << \"Failed to read default configuration\" << endl;\n> -\t\t\treturn TestFail;\n> -\t\t}\n> -\n>  \t\tif (camera_->acquire()) {\n>  \t\t\tcout << \"Failed to acquire the camera\" << endl;\n>  \t\t\treturn TestFail;\n> diff --git a/test/camera/configuration_default.cpp b/test/camera/configuration_default.cpp\n> index d5cefc1127c9..81055da1d513 100644\n> --- a/test/camera/configuration_default.cpp\n> +++ b/test/camera/configuration_default.cpp\n> @@ -22,7 +22,7 @@ protected:\n>\n>  \t\t/* Test asking for configuration for a video stream. */\n>  \t\tconfig = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> -\t\tif (!config || !config->isValid()) {\n> +\t\tif (!config || config->size() != 1) {\n>  \t\t\tcout << \"Default configuration invalid\" << endl;\n>  \t\t\tdelete config;\n>  \t\t\treturn TestFail;\n> @@ -35,7 +35,7 @@ protected:\n>  \t\t * stream roles returns an empty camera configuration.\n>  \t\t */\n>  \t\tconfig = camera_->generateConfiguration({});\n> -\t\tif (!config || config->isValid()) {\n> +\t\tif (!config || config->size() != 0) {\n>  \t\t\tcout << \"Failed to retrieve configuration for empty roles list\"\n>  \t\t\t     << endl;\n>  \t\t\tdelete config;\n> diff --git a/test/camera/configuration_set.cpp b/test/camera/configuration_set.cpp\n> index 23c611a93355..a4e2da16a88b 100644\n> --- a/test/camera/configuration_set.cpp\n> +++ b/test/camera/configuration_set.cpp\n> @@ -21,7 +21,7 @@ protected:\n>  \t\tCameraTest::init();\n>\n>  \t\tconfig_ = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> -\t\tif (!config_) {\n> +\t\tif (!config_ || config_->size() != 1) {\n>  \t\t\tcout << \"Failed to generate default configuration\" << endl;\n>  \t\t\tCameraTest::cleanup();\n>  \t\t\treturn TestFail;\n> @@ -34,11 +34,6 @@ protected:\n>  \t{\n>  \t\tStreamConfiguration &cfg = config_->at(0);\n>\n> -\t\tif (!config_->isValid()) {\n> -\t\t\tcout << \"Failed to read default configuration\" << endl;\n> -\t\t\treturn TestFail;\n> -\t\t}\n> -\n>  \t\tif (camera_->acquire()) {\n>  \t\t\tcout << \"Failed to acquire the camera\" << endl;\n>  \t\t\treturn TestFail;\n> --\n> Regards,\n>\n> Laurent Pinchart\n>\n> _______________________________________________\n> libcamera-devel mailing list\n> libcamera-devel@lists.libcamera.org\n> https://lists.libcamera.org/listinfo/libcamera-devel","headers":{"Return-Path":"<jacopo@jmondi.org>","Received":["from relay9-d.mail.gandi.net (relay9-d.mail.gandi.net\n\t[217.70.183.199])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id A737B60103\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 21 May 2019 00:04:00 +0200 (CEST)","from uno.localdomain (2-224-242-101.ip172.fastwebnet.it\n\t[2.224.242.101]) (Authenticated sender: jacopo@jmondi.org)\n\tby relay9-d.mail.gandi.net (Postfix) with ESMTPSA id D4192FF806;\n\tMon, 20 May 2019 22:03:59 +0000 (UTC)"],"X-Originating-IP":"2.224.242.101","Date":"Tue, 21 May 2019 00:05:06 +0200","From":"Jacopo Mondi <jacopo@jmondi.org>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20190520220506.5yubhl2rqmk3ajdr@uno.localdomain>","References":"<20190519150047.12444-1-laurent.pinchart@ideasonboard.com>\n\t<20190519150047.12444-7-laurent.pinchart@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"multipart/signed; micalg=pgp-sha256;\n\tprotocol=\"application/pgp-signature\"; boundary=\"pzot7vze2cykuerj\"","Content-Disposition":"inline","In-Reply-To":"<20190519150047.12444-7-laurent.pinchart@ideasonboard.com>","User-Agent":"NeoMutt/20180716","Subject":"Re: [libcamera-devel] [PATCH v2 6/6] libcamera: camera: Add a\n\tvalidation API to the CameraConfiguration class","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.23","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Mon, 20 May 2019 22:04:00 -0000"}},{"id":1635,"web_url":"https://patchwork.libcamera.org/comment/1635/","msgid":"<20190521084950.z5ahk3fiptltok7i@uno.localdomain>","date":"2019-05-21T08:49:50","subject":"Re: [libcamera-devel] [PATCH v2 6/6] libcamera: camera: Add a\n\tvalidation API to the CameraConfiguration class","submitter":{"id":3,"url":"https://patchwork.libcamera.org/api/people/3/","name":"Jacopo Mondi","email":"jacopo@jmondi.org"},"content":"Hi Laurent,\n   one more thing, sorry\n\nOn Sun, May 19, 2019 at 06:00:47PM +0300, Laurent Pinchart wrote:\n> The CameraConfiguration class implements a simple storage of\n> StreamConfiguration with internal validation limited to verifying that\n> the stream configurations are not empty. Extend this mechanism by\n> implementing a smart validate() method backed by pipeline handlers.\n>\n> This new mechanism changes the semantics of the camera configuration.\n> The Camera::generateConfiguration() operation still generates a default\n> configuration based on roles, but now also supports generating empty\n> configurations to be filled by applications. Applications can inspect\n> the configuration, optionally modify it, and validate it. The validation\n> implements \"try\" semantics and adjusts invalid configurations instead of\n> rejecting them completely. Applications then decide whether to accept\n> the modified configuration, or try again with a different set of\n> parameters. Once the configuration is valid, it is passed to\n> Camera::configure(), and pipeline handlers are guaranteed that the\n> configuration they receive is valid.\n>\n> A reference to the Camera may need to be stored in the\n> CameraConfiguration derived classes in order to access it from their\n> validate() implementation. This must be stored as a std::shared_ptr<> as\n> the CameraConfiguration instances belong to applications. In order to\n> make this possible, make the Camera class inherit from\n> std::shared_from_this<>.\n>\n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> ---\n>  include/libcamera/camera.h               |  17 +-\n>  src/cam/main.cpp                         |   2 +-\n>  src/libcamera/camera.cpp                 |  80 +++++--\n>  src/libcamera/pipeline/ipu3/ipu3.cpp     | 255 ++++++++++++++++++-----\n>  src/libcamera/pipeline/rkisp1/rkisp1.cpp | 149 ++++++++++---\n>  src/libcamera/pipeline/uvcvideo.cpp      |  53 ++++-\n>  src/libcamera/pipeline/vimc.cpp          |  67 +++++-\n>  src/libcamera/pipeline_handler.cpp       |  17 +-\n>  test/camera/capture.cpp                  |   7 +-\n>  test/camera/configuration_default.cpp    |   4 +-\n>  test/camera/configuration_set.cpp        |   7 +-\n>  11 files changed, 516 insertions(+), 142 deletions(-)\n>\n> diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> index 144133c5de9f..8c0049a1dd94 100644\n> --- a/include/libcamera/camera.h\n> +++ b/include/libcamera/camera.h\n> @@ -25,14 +25,19 @@ class Request;\n>  class CameraConfiguration\n>  {\n>  public:\n> +\tenum Status {\n> +\t\tValid,\n> +\t\tAdjusted,\n> +\t\tInvalid,\n> +\t};\n> +\n>  \tusing iterator = std::vector<StreamConfiguration>::iterator;\n>  \tusing const_iterator = std::vector<StreamConfiguration>::const_iterator;\n>\n> -\tCameraConfiguration();\n> +\tvirtual ~CameraConfiguration();\n>\n>  \tvoid addConfiguration(const StreamConfiguration &cfg);\n> -\n> -\tbool isValid() const;\n> +\tvirtual Status validate() = 0;\n>\n>  \tStreamConfiguration &at(unsigned int index);\n>  \tconst StreamConfiguration &at(unsigned int index) const;\n> @@ -53,11 +58,13 @@ public:\n>  \tbool empty() const;\n>  \tstd::size_t size() const;\n>\n> -private:\n> +protected:\n> +\tCameraConfiguration();\n> +\n>  \tstd::vector<StreamConfiguration> config_;\n>  };\n>\n> -class Camera final\n> +class Camera final : public std::enable_shared_from_this<Camera>\n>  {\n>  public:\n>  \tstatic std::shared_ptr<Camera> create(PipelineHandler *pipe,\n> diff --git a/src/cam/main.cpp b/src/cam/main.cpp\n> index 7550ae4f3428..23da5c687d89 100644\n> --- a/src/cam/main.cpp\n> +++ b/src/cam/main.cpp\n\nShouldn't you add a config->validate() call before calling\ncamera->configure(config) for the cam application?\n\n(I've not checked qcam, I'm sorry, but it might apply there too)\n\nThanks\n   j\n\n> @@ -116,7 +116,7 @@ static CameraConfiguration *prepareCameraConfig()\n>  \t}\n>\n>  \tCameraConfiguration *config = camera->generateConfiguration(roles);\n> -\tif (!config || !config->isValid()) {\n> +\tif (!config || config->size() != roles.size()) {\n>  \t\tstd::cerr << \"Failed to get default stream configuration\"\n>  \t\t\t  << std::endl;\n>  \t\tdelete config;\n> diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> index 0e80691ed862..9da5d4f613f4 100644\n> --- a/src/libcamera/camera.cpp\n> +++ b/src/libcamera/camera.cpp\n> @@ -52,6 +52,28 @@ LOG_DECLARE_CATEGORY(Camera)\n>   * operator[](int) returns a reference to the StreamConfiguration based on its\n>   * insertion index. Accessing a stream configuration with an invalid index\n>   * results in undefined behaviour.\n> + *\n> + * CameraConfiguration instances are retrieved from the camera with\n> + * Camera::generateConfiguration(). Applications may then inspect the\n> + * configuration, modify it, and possibly add new stream configuration entries\n> + * with addConfiguration(). Once the camera configuration satisfies the\n> + * application, it shall be validated by a call to validate(). The validation\n> + * implements \"try\" semantics: it adjusts invalid configurations to the closest\n> + * achievable parameters instead of rejecting them completely. Applications\n> + * then decide whether to accept the modified configuration, or try again with\n> + * a different set of parameters. Once the configuration is valid, it is passed\n> + * to Camera::configure().\n> + */\n> +\n> +/**\n> + * \\enum CameraConfiguration::Status\n> + * \\brief Validity of a camera configuration\n> + * \\var CameraConfiguration::Valid\n> + * The configuration is fully valid\n> + * \\var CameraConfiguration::Adjusted\n> + * The configuration has been adjusted to a valid configuration\n> + * \\var CameraConfiguration::Invalid\n> + * The configuration is invalid and can't be adjusted automatically\n>   */\n>\n>  /**\n> @@ -73,6 +95,10 @@ CameraConfiguration::CameraConfiguration()\n>  {\n>  }\n>\n> +CameraConfiguration::~CameraConfiguration()\n> +{\n> +}\n> +\n>  /**\n>   * \\brief Add a stream configuration to the camera configuration\n>   * \\param[in] cfg The stream configuration\n> @@ -83,27 +109,31 @@ void CameraConfiguration::addConfiguration(const StreamConfiguration &cfg)\n>  }\n>\n>  /**\n> - * \\brief Check if the camera configuration is valid\n> + * \\fn CameraConfiguration::validate()\n> + * \\brief Validate and possibly adjust the camera configuration\n>   *\n> - * A camera configuration is deemed to be valid if it contains at least one\n> - * stream configuration and all stream configurations contain valid information.\n> - * Stream configurations are deemed to be valid if all fields are none zero.\n> + * This method adjusts the camera configuration to the closest valid\n> + * configuration and returns the validation status.\n>   *\n> - * \\return True if the configuration is valid\n> + * \\todo: Define exactly when to return each status code. Should stream\n> + * parameters set to 0 by the caller be adjusted without returning Adjusted ?\n> + * This would potentially be useful for applications but would get in the way\n> + * in Camera::configure(). Do we need an extra status code to signal this ?\n> + *\n> + * \\todo: Handle validation of buffers count when refactoring the buffers API.\n> + *\n> + * \\return A CameraConfiguration::Status value that describes the validation\n> + * status.\n> + * \\retval CameraConfiguration::Invalid The configuration is invalid and can't\n> + * be adjusted. This may only occur in extreme cases such as when the\n> + * configuration is empty.\n> + * \\retval CameraConfigutation::Adjusted The configuration has been adjusted\n> + * and is now valid. Parameters may have changed for any stream, and stream\n> + * configurations may have been removed. The caller shall check the\n> + * configuration carefully.\n> + * \\retval CameraConfiguration::Valid The configuration was already valid and\n> + * hasn't been adjusted.\n>   */\n> -bool CameraConfiguration::isValid() const\n> -{\n> -\tif (empty())\n> -\t\treturn false;\n> -\n> -\tfor (const StreamConfiguration &cfg : config_) {\n> -\t\tif (cfg.size.width == 0 || cfg.size.height == 0 ||\n> -\t\t    cfg.pixelFormat == 0 || cfg.bufferCount == 0)\n> -\t\t\treturn false;\n> -\t}\n> -\n> -\treturn true;\n> -}\n>\n>  /**\n>   * \\brief Retrieve a reference to a stream configuration\n> @@ -218,6 +248,11 @@ std::size_t CameraConfiguration::size() const\n>  \treturn config_.size();\n>  }\n>\n> +/**\n> + * \\var CameraConfiguration::config_\n> + * \\brief The vector of stream configurations\n> + */\n> +\n>  /**\n>   * \\class Camera\n>   * \\brief Camera device\n> @@ -575,10 +610,9 @@ CameraConfiguration *Camera::generateConfiguration(const StreamRoles &roles)\n>   * The caller specifies which streams are to be involved and their configuration\n>   * by populating \\a config.\n>   *\n> - * The easiest way to populate the array of config is to fetch an initial\n> - * configuration from the camera with generateConfiguration() and then change\n> - * the parameters to fit the caller's need and once all the streams parameters\n> - * are configured hand that over to configure() to actually setup the camera.\n> + * The configuration is created by generateConfiguration(), and adjusted by the\n> + * caller with CameraConfiguration::validate(). This method only accepts fully\n> + * valid configurations and returns an error if \\a config is not valid.\n>   *\n>   * Exclusive access to the camera shall be ensured by a call to acquire() prior\n>   * to calling this function, otherwise an -EACCES error will be returned.\n> @@ -603,7 +637,7 @@ int Camera::configure(CameraConfiguration *config)\n>  \tif (!stateBetween(CameraAcquired, CameraConfigured))\n>  \t\treturn -EACCES;\n>\n> -\tif (!config->isValid()) {\n> +\tif (config->validate() != CameraConfiguration::Valid) {\n>  \t\tLOG(Camera, Error)\n>  \t\t\t<< \"Can't configure camera with invalid configuration\";\n>  \t\treturn -EINVAL;\n> diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp\n> index 5b46fb68ced2..56265385a1e7 100644\n> --- a/src/libcamera/pipeline/ipu3/ipu3.cpp\n> +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp\n> @@ -164,6 +164,33 @@ public:\n>  \tIPU3Stream vfStream_;\n>  };\n>\n> +class IPU3CameraConfiguration : public CameraConfiguration\n> +{\n> +public:\n> +\tIPU3CameraConfiguration(Camera *camera, IPU3CameraData *data);\n> +\n> +\tStatus validate() override;\n> +\n> +\tconst V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }\n> +\tconst std::vector<const IPU3Stream *> &streams() { return streams_; }\n> +\n> +private:\n> +\tstatic constexpr unsigned int IPU3_BUFFER_COUNT = 4;\n> +\n> +\tvoid adjustStream(unsigned int index, bool scale);\n> +\n> +\t/*\n> +\t * The IPU3CameraData instance is guaranteed to be valid as long as the\n> +\t * corresponding Camera instance is valid. In order to borrow a\n> +\t * reference to the camera data, store a new reference to the camera.\n> +\t */\n> +\tstd::shared_ptr<Camera> camera_;\n> +\tconst IPU3CameraData *data_;\n> +\n> +\tV4L2SubdeviceFormat sensorFormat_;\n> +\tstd::vector<const IPU3Stream *> streams_;\n> +};\n> +\n>  class PipelineHandlerIPU3 : public PipelineHandler\n>  {\n>  public:\n> @@ -186,8 +213,6 @@ public:\n>  \tbool match(DeviceEnumerator *enumerator) override;\n>\n>  private:\n> -\tstatic constexpr unsigned int IPU3_BUFFER_COUNT = 4;\n> -\n>  \tIPU3CameraData *cameraData(const Camera *camera)\n>  \t{\n>  \t\treturn static_cast<IPU3CameraData *>(\n> @@ -202,6 +227,153 @@ private:\n>  \tMediaDevice *imguMediaDev_;\n>  };\n>\n> +IPU3CameraConfiguration::IPU3CameraConfiguration(Camera *camera,\n> +\t\t\t\t\t\t IPU3CameraData *data)\n> +\t: CameraConfiguration()\n> +{\n> +\tcamera_ = camera->shared_from_this();\n> +\tdata_ = data;\n> +}\n> +\n> +void IPU3CameraConfiguration::adjustStream(unsigned int index, bool scale)\n> +{\n> +\tStreamConfiguration &cfg = config_[index];\n> +\n> +\t/* The only pixel format the driver supports is NV12. */\n> +\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> +\n> +\tif (scale) {\n> +\t\t/*\n> +\t\t * Provide a suitable default that matches the sensor aspect\n> +\t\t * ratio.\n> +\t\t */\n> +\t\tif (!cfg.size.width || !cfg.size.height) {\n> +\t\t\tcfg.size.width = 1280;\n> +\t\t\tcfg.size.height = 1280 * sensorFormat_.size.height\n> +\t\t\t\t\t/ sensorFormat_.size.width;\n> +\t\t}\n> +\n> +\t\t/*\n> +\t\t * \\todo: Clamp the size to the hardware bounds when we will\n> +\t\t * figure them out.\n> +\t\t *\n> +\t\t * \\todo: Handle the scaler (BDS) restrictions. The BDS can\n> +\t\t * only scale with the same factor in both directions, and the\n> +\t\t * scaling factor is limited to a multiple of 1/32. At the\n> +\t\t * moment the ImgU driver hides these constraints by applying\n> +\t\t * additional cropping, this should be fixed on the driver\n> +\t\t * side, and cropping should be exposed to us.\n> +\t\t */\n> +\t} else {\n> +\t\t/*\n> +\t\t * \\todo: Properly support cropping when the ImgU driver\n> +\t\t * interface will be cleaned up.\n> +\t\t */\n> +\t\tcfg.size = sensorFormat_.size;\n> +\t}\n> +\n> +\t/*\n> +\t * Clamp the size to match the ImgU alignment constraints. The width\n> +\t * shall be a multiple of 8 pixels and the height a multiple of 4\n> +\t * pixels.\n> +\t */\n> +\tif (cfg.size.width % 8 || cfg.size.height % 4) {\n> +\t\tcfg.size.width &= ~7;\n> +\t\tcfg.size.height &= ~3;\n> +\t}\n> +\n> +\tcfg.bufferCount = IPU3_BUFFER_COUNT;\n> +}\n> +\n> +CameraConfiguration::Status IPU3CameraConfiguration::validate()\n> +{\n> +\tconst CameraSensor *sensor = data_->cio2_.sensor_;\n> +\tStatus status = Valid;\n> +\n> +\tif (config_.empty())\n> +\t\treturn Invalid;\n> +\n> +\t/* Cap the number of entries to the available streams. */\n> +\tif (config_.size() > 2) {\n> +\t\tconfig_.resize(2);\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\t/*\n> +\t * Select the sensor format by collecting the maximum width and height\n> +\t * and picking the closest larger match, as the IPU3 can downscale\n> +\t * only. If no resolution is requested for any stream, or if no sensor\n> +\t * resolution is large enough, pick the largest one.\n> +\t */\n> +\tSize size = {};\n> +\n> +\tfor (const StreamConfiguration &cfg : config_) {\n> +\t\tif (cfg.size.width > size.width)\n> +\t\t\tsize.width = cfg.size.width;\n> +\t\tif (cfg.size.height > size.height)\n> +\t\t\tsize.height = cfg.size.height;\n> +\t}\n> +\n> +\tif (!size.width || !size.height)\n> +\t\tsize = sensor->resolution();\n> +\n> +\tsensorFormat_ = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR10_1X10,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG10_1X10,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG10_1X10,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB10_1X10 },\n> +\t\t\t\t\t  size);\n> +\tif (!sensorFormat_.size.width || !sensorFormat_.size.height)\n> +\t\tsensorFormat_.size = sensor->resolution();\n> +\n> +\t/*\n> +\t * Verify and update all configuration entries, and assign a stream to\n> +\t * each of them. The viewfinder stream can scale, while the output\n> +\t * stream can crop only, so select the output stream when the requested\n> +\t * resolution is equal to the sensor resolution, and the viewfinder\n> +\t * stream otherwise.\n> +\t */\n> +\tstd::set<const IPU3Stream *> availableStreams = {\n> +\t\t&data_->outStream_,\n> +\t\t&data_->vfStream_,\n> +\t};\n> +\n> +\tstreams_.clear();\n> +\tstreams_.reserve(config_.size());\n> +\n> +\tfor (unsigned int i = 0; i < config_.size(); ++i) {\n> +\t\tStreamConfiguration &cfg = config_[i];\n> +\t\tconst unsigned int pixelFormat = cfg.pixelFormat;\n> +\t\tconst Size size = cfg.size;\n> +\t\tconst IPU3Stream *stream;\n> +\n> +\t\tif (cfg.size == sensorFormat_.size)\n> +\t\t\tstream = &data_->outStream_;\n> +\t\telse\n> +\t\t\tstream = &data_->vfStream_;\n> +\n> +\t\tif (availableStreams.find(stream) == availableStreams.end())\n> +\t\t\tstream = *availableStreams.begin();\n> +\n> +\t\tLOG(IPU3, Debug)\n> +\t\t\t<< \"Assigned '\" << stream->name_ << \"' to stream \" << i;\n> +\n> +\t\tbool scale = stream == &data_->vfStream_;\n> +\t\tadjustStream(i, scale);\n> +\n> +\t\tif (cfg.pixelFormat != pixelFormat || cfg.size != size) {\n> +\t\t\tLOG(IPU3, Debug)\n> +\t\t\t\t<< \"Stream \" << i << \" configuration adjusted to \"\n> +\t\t\t\t<< cfg.toString();\n> +\t\t\tstatus = Adjusted;\n> +\t\t}\n> +\n> +\t\tstreams_.push_back(stream);\n> +\t\tavailableStreams.erase(stream);\n> +\t}\n> +\n> +\treturn status;\n> +}\n> +\n>  PipelineHandlerIPU3::PipelineHandlerIPU3(CameraManager *manager)\n>  \t: PipelineHandler(manager), cio2MediaDev_(nullptr), imguMediaDev_(nullptr)\n>  {\n> @@ -211,12 +383,14 @@ CameraConfiguration *PipelineHandlerIPU3::generateConfiguration(Camera *camera,\n>  \tconst StreamRoles &roles)\n>  {\n>  \tIPU3CameraData *data = cameraData(camera);\n> -\tCameraConfiguration *config = new CameraConfiguration();\n> +\tIPU3CameraConfiguration *config;\n>  \tstd::set<IPU3Stream *> streams = {\n>  \t\t&data->outStream_,\n>  \t\t&data->vfStream_,\n>  \t};\n>\n> +\tconfig = new IPU3CameraConfiguration(camera, data);\n> +\n>  \tfor (const StreamRole role : roles) {\n>  \t\tStreamConfiguration cfg = {};\n>  \t\tIPU3Stream *stream = nullptr;\n> @@ -296,71 +470,25 @@ CameraConfiguration *PipelineHandlerIPU3::generateConfiguration(Camera *camera,\n>\n>  \t\tstreams.erase(stream);\n>\n> -\t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> -\t\tcfg.bufferCount = IPU3_BUFFER_COUNT;\n> -\n>  \t\tconfig->addConfiguration(cfg);\n>  \t}\n>\n> +\tconfig->validate();\n> +\n>  \treturn config;\n>  }\n>\n> -int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n> +int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *c)\n>  {\n> +\tIPU3CameraConfiguration *config =\n> +\t\tstatic_cast<IPU3CameraConfiguration *>(c);\n>  \tIPU3CameraData *data = cameraData(camera);\n>  \tIPU3Stream *outStream = &data->outStream_;\n>  \tIPU3Stream *vfStream = &data->vfStream_;\n>  \tCIO2Device *cio2 = &data->cio2_;\n>  \tImgUDevice *imgu = data->imgu_;\n> -\tSize sensorSize = {};\n>  \tint ret;\n>\n> -\toutStream->active_ = false;\n> -\tvfStream->active_ = false;\n> -\tfor (StreamConfiguration &cfg : *config) {\n> -\t\t/*\n> -\t\t * Pick a stream for the configuration entry.\n> -\t\t * \\todo: This is a naive temporary implementation that will be\n> -\t\t * reworked when implementing camera configuration validation.\n> -\t\t */\n> -\t\tIPU3Stream *stream = vfStream->active_ ? outStream : vfStream;\n> -\n> -\t\t/*\n> -\t\t * Verify that the requested size respects the IPU3 alignment\n> -\t\t * requirements (the image width shall be a multiple of 8\n> -\t\t * pixels and its height a multiple of 4 pixels) and the camera\n> -\t\t * maximum sizes.\n> -\t\t *\n> -\t\t * \\todo: Consider the BDS scaling factor requirements: \"the\n> -\t\t * downscaling factor must be an integer value multiple of 1/32\"\n> -\t\t */\n> -\t\tif (cfg.size.width % 8 || cfg.size.height % 4) {\n> -\t\t\tLOG(IPU3, Error)\n> -\t\t\t\t<< \"Invalid stream size: bad alignment\";\n> -\t\t\treturn -EINVAL;\n> -\t\t}\n> -\n> -\t\tconst Size &resolution = cio2->sensor_->resolution();\n> -\t\tif (cfg.size.width > resolution.width ||\n> -\t\t    cfg.size.height > resolution.height) {\n> -\t\t\tLOG(IPU3, Error)\n> -\t\t\t\t<< \"Invalid stream size: larger than sensor resolution\";\n> -\t\t\treturn -EINVAL;\n> -\t\t}\n> -\n> -\t\t/*\n> -\t\t * Collect the maximum width and height: IPU3 can downscale\n> -\t\t * only.\n> -\t\t */\n> -\t\tif (cfg.size.width > sensorSize.width)\n> -\t\t\tsensorSize.width = cfg.size.width;\n> -\t\tif (cfg.size.height > sensorSize.height)\n> -\t\t\tsensorSize.height = cfg.size.height;\n> -\n> -\t\tstream->active_ = true;\n> -\t\tcfg.setStream(stream);\n> -\t}\n> -\n>  \t/*\n>  \t * \\todo: Enable links selectively based on the requested streams.\n>  \t * As of now, enable all links unconditionally.\n> @@ -373,6 +501,7 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n>  \t * Pass the requested stream size to the CIO2 unit and get back the\n>  \t * adjusted format to be propagated to the ImgU output devices.\n>  \t */\n> +\tconst Size &sensorSize = config->sensorFormat().size;\n>  \tV4L2DeviceFormat cio2Format = {};\n>  \tret = cio2->configure(sensorSize, &cio2Format);\n>  \tif (ret)\n> @@ -383,8 +512,22 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n>  \t\treturn ret;\n>\n>  \t/* Apply the format to the configured streams output devices. */\n> -\tfor (StreamConfiguration &cfg : *config) {\n> -\t\tIPU3Stream *stream = static_cast<IPU3Stream *>(cfg.stream());\n> +\toutStream->active_ = false;\n> +\tvfStream->active_ = false;\n> +\n> +\tfor (unsigned int i = 0; i < config->size(); ++i) {\n> +\t\t/*\n> +\t\t * Use a const_cast<> here instead of storing a mutable stream\n> +\t\t * pointer in the configuration to let the compiler catch\n> +\t\t * unwanted modifications of camera data in the configuration\n> +\t\t * validate() implementation.\n> +\t\t */\n> +\t\tIPU3Stream *stream = const_cast<IPU3Stream *>(config->streams()[i]);\n> +\t\tStreamConfiguration &cfg = (*config)[i];\n> +\n> +\t\tstream->active_ = true;\n> +\t\tcfg.setStream(stream);\n> +\n>  \t\tret = imgu->configureOutput(stream->device_, cfg);\n>  \t\tif (ret)\n>  \t\t\treturn ret;\n> diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> index a1a4f205b4aa..42944c64189b 100644\n> --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> @@ -5,6 +5,7 @@\n>   * rkisp1.cpp - Pipeline handler for Rockchip ISP1\n>   */\n>\n> +#include <algorithm>\n>  #include <iomanip>\n>  #include <memory>\n>  #include <vector>\n> @@ -45,6 +46,29 @@ public:\n>  \tCameraSensor *sensor_;\n>  };\n>\n> +class RkISP1CameraConfiguration : public CameraConfiguration\n> +{\n> +public:\n> +\tRkISP1CameraConfiguration(Camera *camera, RkISP1CameraData *data);\n> +\n> +\tStatus validate() override;\n> +\n> +\tconst V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }\n> +\n> +private:\n> +\tstatic constexpr unsigned int RKISP1_BUFFER_COUNT = 4;\n> +\n> +\t/*\n> +\t * The RkISP1CameraData instance is guaranteed to be valid as long as the\n> +\t * corresponding Camera instance is valid. In order to borrow a\n> +\t * reference to the camera data, store a new reference to the camera.\n> +\t */\n> +\tstd::shared_ptr<Camera> camera_;\n> +\tconst RkISP1CameraData *data_;\n> +\n> +\tV4L2SubdeviceFormat sensorFormat_;\n> +};\n> +\n>  class PipelineHandlerRkISP1 : public PipelineHandler\n>  {\n>  public:\n> @@ -68,8 +92,6 @@ public:\n>  \tbool match(DeviceEnumerator *enumerator) override;\n>\n>  private:\n> -\tstatic constexpr unsigned int RKISP1_BUFFER_COUNT = 4;\n> -\n>  \tRkISP1CameraData *cameraData(const Camera *camera)\n>  \t{\n>  \t\treturn static_cast<RkISP1CameraData *>(\n> @@ -88,6 +110,95 @@ private:\n>  \tCamera *activeCamera_;\n>  };\n>\n> +RkISP1CameraConfiguration::RkISP1CameraConfiguration(Camera *camera,\n> +\t\t\t\t\t\t     RkISP1CameraData *data)\n> +\t: CameraConfiguration()\n> +{\n> +\tcamera_ = camera->shared_from_this();\n> +\tdata_ = data;\n> +}\n> +\n> +CameraConfiguration::Status RkISP1CameraConfiguration::validate()\n> +{\n> +\tstatic const std::array<unsigned int, 8> formats{\n> +\t\tV4L2_PIX_FMT_YUYV,\n> +\t\tV4L2_PIX_FMT_YVYU,\n> +\t\tV4L2_PIX_FMT_VYUY,\n> +\t\tV4L2_PIX_FMT_NV16,\n> +\t\tV4L2_PIX_FMT_NV61,\n> +\t\tV4L2_PIX_FMT_NV21,\n> +\t\tV4L2_PIX_FMT_NV12,\n> +\t\tV4L2_PIX_FMT_GREY,\n> +\t};\n> +\n> +\tconst CameraSensor *sensor = data_->sensor_;\n> +\tStatus status = Valid;\n> +\n> +\tif (config_.empty())\n> +\t\treturn Invalid;\n> +\n> +\t/* Cap the number of entries to the available streams. */\n> +\tif (config_.size() > 1) {\n> +\t\tconfig_.resize(1);\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\tStreamConfiguration &cfg = config_[0];\n> +\n> +\t/* Adjust the pixel format. */\n> +\tif (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==\n> +\t    formats.end()) {\n> +\t\tLOG(RkISP1, Debug) << \"Adjusting format to NV12\";\n> +\t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\t/* Select the sensor format. */\n> +\tsensorFormat_ = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR12_1X12,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG12_1X12,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG12_1X12,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB12_1X12,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SBGGR10_1X10,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG10_1X10,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG10_1X10,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB10_1X10,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SBGGR8_1X8,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG8_1X8,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG8_1X8,\n> +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB8_1X8 },\n> +\t\t\t\t\t  cfg.size);\n> +\tif (!sensorFormat_.size.width || !sensorFormat_.size.height)\n> +\t\tsensorFormat_.size = sensor->resolution();\n> +\n> +\t/*\n> +\t * Provide a suitable default that matches the sensor aspect\n> +\t * ratio and clamp the size to the hardware bounds.\n> +\t *\n> +\t * \\todo: Check the hardware alignment constraints.\n> +\t */\n> +\tconst Size size = cfg.size;\n> +\n> +\tif (!cfg.size.width || !cfg.size.height) {\n> +\t\tcfg.size.width = 1280;\n> +\t\tcfg.size.height = 1280 * sensorFormat_.size.height\n> +\t\t\t\t/ sensorFormat_.size.width;\n> +\t}\n> +\n> +\tcfg.size.width = std::max(32U, std::min(4416U, cfg.size.width));\n> +\tcfg.size.height = std::max(16U, std::min(3312U, cfg.size.height));\n> +\n> +\tif (cfg.size != size) {\n> +\t\tLOG(RkISP1, Debug)\n> +\t\t\t<< \"Adjusting size from \" << size.toString()\n> +\t\t\t<< \" to \" << cfg.size.toString();\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\tcfg.bufferCount = RKISP1_BUFFER_COUNT;\n> +\n> +\treturn status;\n> +}\n> +\n>  PipelineHandlerRkISP1::PipelineHandlerRkISP1(CameraManager *manager)\n>  \t: PipelineHandler(manager), dphy_(nullptr), isp_(nullptr),\n>  \t  video_(nullptr)\n> @@ -109,37 +220,31 @@ CameraConfiguration *PipelineHandlerRkISP1::generateConfiguration(Camera *camera\n>  \tconst StreamRoles &roles)\n>  {\n>  \tRkISP1CameraData *data = cameraData(camera);\n> -\tCameraConfiguration *config = new CameraConfiguration();\n> +\tCameraConfiguration *config = new RkISP1CameraConfiguration(camera, data);\n>\n>  \tif (!roles.empty()) {\n>  \t\tStreamConfiguration cfg{};\n>\n>  \t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n>  \t\tcfg.size = data->sensor_->resolution();\n> -\t\tcfg.bufferCount = RKISP1_BUFFER_COUNT;\n>\n>  \t\tconfig->addConfiguration(cfg);\n>  \t}\n>\n> +\tconfig->validate();\n> +\n>  \treturn config;\n>  }\n>\n> -int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *config)\n> +int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)\n>  {\n> +\tRkISP1CameraConfiguration *config =\n> +\t\tstatic_cast<RkISP1CameraConfiguration *>(c);\n>  \tRkISP1CameraData *data = cameraData(camera);\n>  \tStreamConfiguration &cfg = config->at(0);\n>  \tCameraSensor *sensor = data->sensor_;\n>  \tint ret;\n>\n> -\t/* Verify the configuration. */\n> -\tconst Size &resolution = sensor->resolution();\n> -\tif (cfg.size.width > resolution.width ||\n> -\t    cfg.size.height > resolution.height) {\n> -\t\tLOG(RkISP1, Error)\n> -\t\t\t<< \"Invalid stream size: larger than sensor resolution\";\n> -\t\treturn -EINVAL;\n> -\t}\n> -\n>  \t/*\n>  \t * Configure the sensor links: enable the link corresponding to this\n>  \t * camera and disable all the other sensor links.\n> @@ -167,21 +272,7 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *config\n>  \t * Configure the format on the sensor output and propagate it through\n>  \t * the pipeline.\n>  \t */\n> -\tV4L2SubdeviceFormat format;\n> -\tformat = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR12_1X12,\n> -\t\t\t\t     MEDIA_BUS_FMT_SGBRG12_1X12,\n> -\t\t\t\t     MEDIA_BUS_FMT_SGRBG12_1X12,\n> -\t\t\t\t     MEDIA_BUS_FMT_SRGGB12_1X12,\n> -\t\t\t\t     MEDIA_BUS_FMT_SBGGR10_1X10,\n> -\t\t\t\t     MEDIA_BUS_FMT_SGBRG10_1X10,\n> -\t\t\t\t     MEDIA_BUS_FMT_SGRBG10_1X10,\n> -\t\t\t\t     MEDIA_BUS_FMT_SRGGB10_1X10,\n> -\t\t\t\t     MEDIA_BUS_FMT_SBGGR8_1X8,\n> -\t\t\t\t     MEDIA_BUS_FMT_SGBRG8_1X8,\n> -\t\t\t\t     MEDIA_BUS_FMT_SGRBG8_1X8,\n> -\t\t\t\t     MEDIA_BUS_FMT_SRGGB8_1X8 },\n> -\t\t\t\t   cfg.size);\n> -\n> +\tV4L2SubdeviceFormat format = config->sensorFormat();\n>  \tLOG(RkISP1, Debug) << \"Configuring sensor with \" << format.toString();\n>\n>  \tret = sensor->setFormat(&format);\n> diff --git a/src/libcamera/pipeline/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo.cpp\n> index 8254e1fdac1e..f7ffeb439cf3 100644\n> --- a/src/libcamera/pipeline/uvcvideo.cpp\n> +++ b/src/libcamera/pipeline/uvcvideo.cpp\n> @@ -39,6 +39,14 @@ public:\n>  \tStream stream_;\n>  };\n>\n> +class UVCCameraConfiguration : public CameraConfiguration\n> +{\n> +public:\n> +\tUVCCameraConfiguration();\n> +\n> +\tStatus validate() override;\n> +};\n> +\n>  class PipelineHandlerUVC : public PipelineHandler\n>  {\n>  public:\n> @@ -68,6 +76,45 @@ private:\n>  \t}\n>  };\n>\n> +UVCCameraConfiguration::UVCCameraConfiguration()\n> +\t: CameraConfiguration()\n> +{\n> +}\n> +\n> +CameraConfiguration::Status UVCCameraConfiguration::validate()\n> +{\n> +\tStatus status = Valid;\n> +\n> +\tif (config_.empty())\n> +\t\treturn Invalid;\n> +\n> +\t/* Cap the number of entries to the available streams. */\n> +\tif (config_.size() > 1) {\n> +\t\tconfig_.resize(1);\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\tStreamConfiguration &cfg = config_[0];\n> +\n> +\t/* \\todo: Validate the configuration against the device capabilities. */\n> +\tconst unsigned int pixelFormat = cfg.pixelFormat;\n> +\tconst Size size = cfg.size;\n> +\n> +\tcfg.pixelFormat = V4L2_PIX_FMT_YUYV;\n> +\tcfg.size = { 640, 480 };\n> +\n> +\tif (cfg.pixelFormat != pixelFormat || cfg.size != size) {\n> +\t\tLOG(UVC, Debug)\n> +\t\t\t<< \"Adjusting configuration from \" << cfg.toString()\n> +\t\t\t<< \" to \" << cfg.size.toString() << \"-YUYV\";\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\tcfg.bufferCount = 4;\n> +\n> +\treturn status;\n> +}\n> +\n>  PipelineHandlerUVC::PipelineHandlerUVC(CameraManager *manager)\n>  \t: PipelineHandler(manager)\n>  {\n> @@ -76,10 +123,10 @@ PipelineHandlerUVC::PipelineHandlerUVC(CameraManager *manager)\n>  CameraConfiguration *PipelineHandlerUVC::generateConfiguration(Camera *camera,\n>  \tconst StreamRoles &roles)\n>  {\n> -\tCameraConfiguration *config = new CameraConfiguration();\n> +\tCameraConfiguration *config = new UVCCameraConfiguration();\n>\n>  \tif (!roles.empty()) {\n> -\t\tStreamConfiguration cfg;\n> +\t\tStreamConfiguration cfg{};\n>\n>  \t\tcfg.pixelFormat = V4L2_PIX_FMT_YUYV;\n>  \t\tcfg.size = { 640, 480 };\n> @@ -88,6 +135,8 @@ CameraConfiguration *PipelineHandlerUVC::generateConfiguration(Camera *camera,\n>  \t\tconfig->addConfiguration(cfg);\n>  \t}\n>\n> +\tconfig->validate();\n> +\n>  \treturn config;\n>  }\n>\n> diff --git a/src/libcamera/pipeline/vimc.cpp b/src/libcamera/pipeline/vimc.cpp\n> index 2bf85d0a0b32..2a61d2893e3a 100644\n> --- a/src/libcamera/pipeline/vimc.cpp\n> +++ b/src/libcamera/pipeline/vimc.cpp\n> @@ -5,6 +5,8 @@\n>   * vimc.cpp - Pipeline handler for the vimc device\n>   */\n>\n> +#include <algorithm>\n> +\n>  #include <libcamera/camera.h>\n>  #include <libcamera/request.h>\n>  #include <libcamera/stream.h>\n> @@ -39,6 +41,14 @@ public:\n>  \tStream stream_;\n>  };\n>\n> +class VimcCameraConfiguration : public CameraConfiguration\n> +{\n> +public:\n> +\tVimcCameraConfiguration();\n> +\n> +\tStatus validate() override;\n> +};\n> +\n>  class PipelineHandlerVimc : public PipelineHandler\n>  {\n>  public:\n> @@ -68,6 +78,57 @@ private:\n>  \t}\n>  };\n>\n> +VimcCameraConfiguration::VimcCameraConfiguration()\n> +\t: CameraConfiguration()\n> +{\n> +}\n> +\n> +CameraConfiguration::Status VimcCameraConfiguration::validate()\n> +{\n> +\tstatic const std::array<unsigned int, 3> formats{\n> +\t\tV4L2_PIX_FMT_BGR24,\n> +\t\tV4L2_PIX_FMT_RGB24,\n> +\t\tV4L2_PIX_FMT_ARGB32,\n> +\t};\n> +\n> +\tStatus status = Valid;\n> +\n> +\tif (config_.empty())\n> +\t\treturn Invalid;\n> +\n> +\t/* Cap the number of entries to the available streams. */\n> +\tif (config_.size() > 1) {\n> +\t\tconfig_.resize(1);\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\tStreamConfiguration &cfg = config_[0];\n> +\n> +\t/* Adjust the pixel format. */\n> +\tif (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==\n> +\t    formats.end()) {\n> +\t\tLOG(VIMC, Debug) << \"Adjusting format to RGB24\";\n> +\t\tcfg.pixelFormat = V4L2_PIX_FMT_RGB24;\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\t/* Clamp the size based on the device limits. */\n> +\tconst Size size = cfg.size;\n> +\n> +\tcfg.size.width = std::max(16U, std::min(4096U, cfg.size.width));\n> +\tcfg.size.height = std::max(16U, std::min(2160U, cfg.size.height));\n> +\n> +\tif (cfg.size != size) {\n> +\t\tLOG(VIMC, Debug)\n> +\t\t\t<< \"Adjusting size to \" << cfg.size.toString();\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\tcfg.bufferCount = 4;\n> +\n> +\treturn status;\n> +}\n> +\n>  PipelineHandlerVimc::PipelineHandlerVimc(CameraManager *manager)\n>  \t: PipelineHandler(manager)\n>  {\n> @@ -76,10 +137,10 @@ PipelineHandlerVimc::PipelineHandlerVimc(CameraManager *manager)\n>  CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera,\n>  \tconst StreamRoles &roles)\n>  {\n> -\tCameraConfiguration *config = new CameraConfiguration();\n> +\tCameraConfiguration *config = new VimcCameraConfiguration();\n>\n>  \tif (!roles.empty()) {\n> -\t\tStreamConfiguration cfg;\n> +\t\tStreamConfiguration cfg{};\n>\n>  \t\tcfg.pixelFormat = V4L2_PIX_FMT_RGB24;\n>  \t\tcfg.size = { 640, 480 };\n> @@ -88,6 +149,8 @@ CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera,\n>  \t\tconfig->addConfiguration(cfg);\n>  \t}\n>\n> +\tconfig->validate();\n> +\n>  \treturn config;\n>  }\n>\n> diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp\n> index de46e98880a2..dd56907d817e 100644\n> --- a/src/libcamera/pipeline_handler.cpp\n> +++ b/src/libcamera/pipeline_handler.cpp\n> @@ -248,17 +248,14 @@ void PipelineHandler::unlock()\n>   * is the Camera class which will receive configuration to apply from the\n>   * application.\n>   *\n> - * Each pipeline handler implementation is responsible for validating\n> - * that the configuration requested in \\a config can be achieved\n> - * exactly. Any difference in pixel format, frame size or any other\n> - * parameter shall result in the -EINVAL error being returned, and no\n> - * change in configuration being applied to the pipeline. If\n> - * configuration of a subset of the streams can't be satisfied, the\n> - * whole configuration is considered invalid.\n> + * The configuration is guaranteed to have been validated with\n> + * CameraConfiguration::valid(). The pipeline handler implementation shall not\n> + * perform further validation and may rely on any custom field stored in its\n> + * custom CameraConfiguration derived class.\n>   *\n> - * Once the configuration is validated and the camera configured, the pipeline\n> - * handler shall associate a Stream instance to each StreamConfiguration entry\n> - * in the CameraConfiguration with the StreamConfiguration::setStream() method.\n> + * When configuring the camera the pipeline handler shall associate a Stream\n> + * instance to each StreamConfiguration entry in the CameraConfiguration using\n> + * the StreamConfiguration::setStream() method.\n>   *\n>   * \\return 0 on success or a negative error code otherwise\n>   */\n> diff --git a/test/camera/capture.cpp b/test/camera/capture.cpp\n> index 137aa649a505..f122f79bb1ec 100644\n> --- a/test/camera/capture.cpp\n> +++ b/test/camera/capture.cpp\n> @@ -45,7 +45,7 @@ protected:\n>  \t\tCameraTest::init();\n>\n>  \t\tconfig_ = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> -\t\tif (!config_) {\n> +\t\tif (!config_ || config_->size() != 1) {\n>  \t\t\tcout << \"Failed to generate default configuration\" << endl;\n>  \t\t\tCameraTest::cleanup();\n>  \t\t\treturn TestFail;\n> @@ -58,11 +58,6 @@ protected:\n>  \t{\n>  \t\tStreamConfiguration &cfg = config_->at(0);\n>\n> -\t\tif (!config_->isValid()) {\n> -\t\t\tcout << \"Failed to read default configuration\" << endl;\n> -\t\t\treturn TestFail;\n> -\t\t}\n> -\n>  \t\tif (camera_->acquire()) {\n>  \t\t\tcout << \"Failed to acquire the camera\" << endl;\n>  \t\t\treturn TestFail;\n> diff --git a/test/camera/configuration_default.cpp b/test/camera/configuration_default.cpp\n> index d5cefc1127c9..81055da1d513 100644\n> --- a/test/camera/configuration_default.cpp\n> +++ b/test/camera/configuration_default.cpp\n> @@ -22,7 +22,7 @@ protected:\n>\n>  \t\t/* Test asking for configuration for a video stream. */\n>  \t\tconfig = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> -\t\tif (!config || !config->isValid()) {\n> +\t\tif (!config || config->size() != 1) {\n>  \t\t\tcout << \"Default configuration invalid\" << endl;\n>  \t\t\tdelete config;\n>  \t\t\treturn TestFail;\n> @@ -35,7 +35,7 @@ protected:\n>  \t\t * stream roles returns an empty camera configuration.\n>  \t\t */\n>  \t\tconfig = camera_->generateConfiguration({});\n> -\t\tif (!config || config->isValid()) {\n> +\t\tif (!config || config->size() != 0) {\n>  \t\t\tcout << \"Failed to retrieve configuration for empty roles list\"\n>  \t\t\t     << endl;\n>  \t\t\tdelete config;\n> diff --git a/test/camera/configuration_set.cpp b/test/camera/configuration_set.cpp\n> index 23c611a93355..a4e2da16a88b 100644\n> --- a/test/camera/configuration_set.cpp\n> +++ b/test/camera/configuration_set.cpp\n> @@ -21,7 +21,7 @@ protected:\n>  \t\tCameraTest::init();\n>\n>  \t\tconfig_ = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> -\t\tif (!config_) {\n> +\t\tif (!config_ || config_->size() != 1) {\n>  \t\t\tcout << \"Failed to generate default configuration\" << endl;\n>  \t\t\tCameraTest::cleanup();\n>  \t\t\treturn TestFail;\n> @@ -34,11 +34,6 @@ protected:\n>  \t{\n>  \t\tStreamConfiguration &cfg = config_->at(0);\n>\n> -\t\tif (!config_->isValid()) {\n> -\t\t\tcout << \"Failed to read default configuration\" << endl;\n> -\t\t\treturn TestFail;\n> -\t\t}\n> -\n>  \t\tif (camera_->acquire()) {\n>  \t\t\tcout << \"Failed to acquire the camera\" << endl;\n>  \t\t\treturn TestFail;\n> --\n> Regards,\n>\n> Laurent Pinchart\n>\n> _______________________________________________\n> libcamera-devel mailing list\n> libcamera-devel@lists.libcamera.org\n> https://lists.libcamera.org/listinfo/libcamera-devel","headers":{"Return-Path":"<jacopo@jmondi.org>","Received":["from relay10.mail.gandi.net (relay10.mail.gandi.net\n\t[217.70.178.230])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id D4EDA60C1D\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 21 May 2019 10:48:52 +0200 (CEST)","from uno.localdomain (2-224-242-101.ip172.fastwebnet.it\n\t[2.224.242.101]) (Authenticated sender: jacopo@jmondi.org)\n\tby relay10.mail.gandi.net (Postfix) with ESMTPSA id 0B52024000A;\n\tTue, 21 May 2019 08:48:51 +0000 (UTC)"],"Date":"Tue, 21 May 2019 10:49:50 +0200","From":"Jacopo Mondi <jacopo@jmondi.org>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20190521084950.z5ahk3fiptltok7i@uno.localdomain>","References":"<20190519150047.12444-1-laurent.pinchart@ideasonboard.com>\n\t<20190519150047.12444-7-laurent.pinchart@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"multipart/signed; micalg=pgp-sha256;\n\tprotocol=\"application/pgp-signature\"; boundary=\"u4uwgzml7oxagyid\"","Content-Disposition":"inline","In-Reply-To":"<20190519150047.12444-7-laurent.pinchart@ideasonboard.com>","User-Agent":"NeoMutt/20180716","Subject":"Re: [libcamera-devel] [PATCH v2 6/6] libcamera: camera: Add a\n\tvalidation API to the CameraConfiguration class","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.23","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Tue, 21 May 2019 08:48:53 -0000"}},{"id":1640,"web_url":"https://patchwork.libcamera.org/comment/1640/","msgid":"<20190521124955.GC5674@pendragon.ideasonboard.com>","date":"2019-05-21T12:49:55","subject":"Re: [libcamera-devel] [PATCH v2 6/6] libcamera: camera: Add a\n\tvalidation API to the CameraConfiguration class","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Jacopo,\n\nOn Tue, May 21, 2019 at 12:05:06AM +0200, Jacopo Mondi wrote:\n> Hi Laurent,\n> \n> In order to validate if the API is dump-proof enough, I'm starting\n> the review from the last patch, so I might be missing some pieces\n> here and there...\n> \n> On Sun, May 19, 2019 at 06:00:47PM +0300, Laurent Pinchart wrote:\n> > The CameraConfiguration class implements a simple storage of\n> > StreamConfiguration with internal validation limited to verifying that\n> > the stream configurations are not empty. Extend this mechanism by\n> > implementing a smart validate() method backed by pipeline handlers.\n> >\n> > This new mechanism changes the semantics of the camera configuration.\n> \n> s/semantics/semantic ?\n\nI think you're right, I'll fix that.\n\n> > The Camera::generateConfiguration() operation still generates a default\n> > configuration based on roles, but now also supports generating empty\n> > configurations to be filled by applications. Applications can inspect\n> > the configuration, optionally modify it, and validate it. The validation\n> > implements \"try\" semantics and adjusts invalid configurations instead of\n> > rejecting them completely. Applications then decide whether to accept\n> > the modified configuration, or try again with a different set of\n> > parameters. Once the configuration is valid, it is passed to\n> > Camera::configure(), and pipeline handlers are guaranteed that the\n> > configuration they receive is valid.\n> >\n> > A reference to the Camera may need to be stored in the\n> > CameraConfiguration derived classes in order to access it from their\n> > validate() implementation. This must be stored as a std::shared_ptr<> as\n> > the CameraConfiguration instances belong to applications. In order to\n> > make this possible, make the Camera class inherit from\n> > std::shared_from_this<>.\n> \n> If I got this right we'll have a\n> \n> CameraConfiguration::validate() and a\n> Camera.configure()\n> \n> and CameraConfiguration has to keep a reference to the Camera to\n> access it.\n> \n> Can we provide a Camera::validate(StreamConfiguration *) instead?\n> We could handle the shared reference in the Camera and not let\n> CameraConfiguration subclasses have to deal with it in this way?\n\nI assume you meant a Camera::validate(CameraConfiguration *), as we want\nto validate the whole configuration, not stream per stream (the idea of\nthis series is to support cross-stream validation constraints).\n\nFirst of all, the method would need to be called\nCamera::validateConfiguration(). This would need to be delegated to\nPipelineHandler::validateConfiguration(Camera *, CameraConfiguration *).\nThe pipeline handler would then need to cast the camera configuration to\nits custom configuration derived class (opening the door to applications\ncalling Camera::validateConfiguration() with a configuration for the\nwrong camera, but that's already possible with Camera::configure() I\nsupposed). We could try that, but I think it would make the API less\nclean for applications, for little benefits in my opinion (but we may\nhave a different view of the benefits :-)).\n\n> >\n> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> > ---\n> >  include/libcamera/camera.h               |  17 +-\n> >  src/cam/main.cpp                         |   2 +-\n> >  src/libcamera/camera.cpp                 |  80 +++++--\n> >  src/libcamera/pipeline/ipu3/ipu3.cpp     | 255 ++++++++++++++++++-----\n> >  src/libcamera/pipeline/rkisp1/rkisp1.cpp | 149 ++++++++++---\n> >  src/libcamera/pipeline/uvcvideo.cpp      |  53 ++++-\n> >  src/libcamera/pipeline/vimc.cpp          |  67 +++++-\n> >  src/libcamera/pipeline_handler.cpp       |  17 +-\n> >  test/camera/capture.cpp                  |   7 +-\n> >  test/camera/configuration_default.cpp    |   4 +-\n> >  test/camera/configuration_set.cpp        |   7 +-\n> >  11 files changed, 516 insertions(+), 142 deletions(-)\n> >\n> > diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> > index 144133c5de9f..8c0049a1dd94 100644\n> > --- a/include/libcamera/camera.h\n> > +++ b/include/libcamera/camera.h\n> > @@ -25,14 +25,19 @@ class Request;\n> >  class CameraConfiguration\n> >  {\n> >  public:\n> > +\tenum Status {\n> > +\t\tValid,\n> > +\t\tAdjusted,\n> > +\t\tInvalid,\n> > +\t};\n> > +\n> >  \tusing iterator = std::vector<StreamConfiguration>::iterator;\n> >  \tusing const_iterator = std::vector<StreamConfiguration>::const_iterator;\n> >\n> > -\tCameraConfiguration();\n> > +\tvirtual ~CameraConfiguration();\n> >\n> >  \tvoid addConfiguration(const StreamConfiguration &cfg);\n> > -\n> > -\tbool isValid() const;\n> > +\tvirtual Status validate() = 0;\n> >\n> >  \tStreamConfiguration &at(unsigned int index);\n> >  \tconst StreamConfiguration &at(unsigned int index) const;\n> > @@ -53,11 +58,13 @@ public:\n> >  \tbool empty() const;\n> >  \tstd::size_t size() const;\n> >\n> > -private:\n> > +protected:\n> > +\tCameraConfiguration();\n> > +\n> >  \tstd::vector<StreamConfiguration> config_;\n> >  };\n> >\n> > -class Camera final\n> > +class Camera final : public std::enable_shared_from_this<Camera>\n> >  {\n> >  public:\n> >  \tstatic std::shared_ptr<Camera> create(PipelineHandler *pipe,\n> > diff --git a/src/cam/main.cpp b/src/cam/main.cpp\n> > index 7550ae4f3428..23da5c687d89 100644\n> > --- a/src/cam/main.cpp\n> > +++ b/src/cam/main.cpp\n> > @@ -116,7 +116,7 @@ static CameraConfiguration *prepareCameraConfig()\n> >  \t}\n> >\n> >  \tCameraConfiguration *config = camera->generateConfiguration(roles);\n> > -\tif (!config || !config->isValid()) {\n> > +\tif (!config || config->size() != roles.size()) {\n> >  \t\tstd::cerr << \"Failed to get default stream configuration\"\n> >  \t\t\t  << std::endl;\n> >  \t\tdelete config;\n> > diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> > index 0e80691ed862..9da5d4f613f4 100644\n> > --- a/src/libcamera/camera.cpp\n> > +++ b/src/libcamera/camera.cpp\n> > @@ -52,6 +52,28 @@ LOG_DECLARE_CATEGORY(Camera)\n> >   * operator[](int) returns a reference to the StreamConfiguration based on its\n> >   * insertion index. Accessing a stream configuration with an invalid index\n> >   * results in undefined behaviour.\n> > + *\n> > + * CameraConfiguration instances are retrieved from the camera with\n> > + * Camera::generateConfiguration(). Applications may then inspect the\n> > + * configuration, modify it, and possibly add new stream configuration entries\n> > + * with addConfiguration(). Once the camera configuration satisfies the\n> > + * application, it shall be validated by a call to validate(). The validation\n> > + * implements \"try\" semantics: it adjusts invalid configurations to the closest\n> > + * achievable parameters instead of rejecting them completely. Applications\n> > + * then decide whether to accept the modified configuration, or try again with\n> > + * a different set of parameters. Once the configuration is valid, it is passed\n> > + * to Camera::configure().\n> > + */\n> > +\n> > +/**\n> > + * \\enum CameraConfiguration::Status\n> > + * \\brief Validity of a camera configuration\n> > + * \\var CameraConfiguration::Valid\n> > + * The configuration is fully valid\n> > + * \\var CameraConfiguration::Adjusted\n> > + * The configuration has been adjusted to a valid configuration\n> > + * \\var CameraConfiguration::Invalid\n> > + * The configuration is invalid and can't be adjusted automatically\n> >   */\n> >\n> >  /**\n> > @@ -73,6 +95,10 @@ CameraConfiguration::CameraConfiguration()\n> >  {\n> >  }\n> >\n> > +CameraConfiguration::~CameraConfiguration()\n> > +{\n> > +}\n> > +\n> >  /**\n> >   * \\brief Add a stream configuration to the camera configuration\n> >   * \\param[in] cfg The stream configuration\n> > @@ -83,27 +109,31 @@ void CameraConfiguration::addConfiguration(const StreamConfiguration &cfg)\n> >  }\n> >\n> >  /**\n> > - * \\brief Check if the camera configuration is valid\n> > + * \\fn CameraConfiguration::validate()\n> > + * \\brief Validate and possibly adjust the camera configuration\n> >   *\n> > - * A camera configuration is deemed to be valid if it contains at least one\n> > - * stream configuration and all stream configurations contain valid information.\n> > - * Stream configurations are deemed to be valid if all fields are none zero.\n> > + * This method adjusts the camera configuration to the closest valid\n> > + * configuration and returns the validation status.\n> >   *\n> > - * \\return True if the configuration is valid\n> > + * \\todo: Define exactly when to return each status code. Should stream\n> > + * parameters set to 0 by the caller be adjusted without returning Adjusted ?\n> > + * This would potentially be useful for applications but would get in the way\n> > + * in Camera::configure(). Do we need an extra status code to signal this ?\n> \n> I'm not sure I did get why it gets in the way of configure()\n\nBecause Camera::configure() calls validate() and returns an error if the\nstatus is Adjusted or Invalid, as the application is responsible for\npassing a fully valid configuration to Camera::configure().\n\n> > + *\n> > + * \\todo: Handle validation of buffers count when refactoring the buffers API.\n> > + *\n> > + * \\return A CameraConfiguration::Status value that describes the validation\n> > + * status.\n> > + * \\retval CameraConfiguration::Invalid The configuration is invalid and can't\n> > + * be adjusted. This may only occur in extreme cases such as when the\n> > + * configuration is empty.\n> \n> nit: instead of describing how the returned configuration looks like,\n> do you have an example of application provided parameters which might\n> trigger and invalid use case ?\n\nNot at the moment, no. I have no other case in mind, but we may find\nsome in the future.\n\n> > + * \\retval CameraConfigutation::Adjusted The configuration has been adjusted\n> > + * and is now valid. Parameters may have changed for any stream, and stream\n> > + * configurations may have been removed. The caller shall check the\n> > + * configuration carefully.\n> > + * \\retval CameraConfiguration::Valid The configuration was already valid and\n> > + * hasn't been adjusted.\n> >   */\n> > -bool CameraConfiguration::isValid() const\n> > -{\n> > -\tif (empty())\n> > -\t\treturn false;\n> > -\n> > -\tfor (const StreamConfiguration &cfg : config_) {\n> > -\t\tif (cfg.size.width == 0 || cfg.size.height == 0 ||\n> > -\t\t    cfg.pixelFormat == 0 || cfg.bufferCount == 0)\n> > -\t\t\treturn false;\n> > -\t}\n> > -\n> > -\treturn true;\n> > -}\n> >\n> >  /**\n> >   * \\brief Retrieve a reference to a stream configuration\n> > @@ -218,6 +248,11 @@ std::size_t CameraConfiguration::size() const\n> >  \treturn config_.size();\n> >  }\n> >\n> > +/**\n> > + * \\var CameraConfiguration::config_\n> > + * \\brief The vector of stream configurations\n> > + */\n> > +\n> >  /**\n> >   * \\class Camera\n> >   * \\brief Camera device\n> > @@ -575,10 +610,9 @@ CameraConfiguration *Camera::generateConfiguration(const StreamRoles &roles)\n> >   * The caller specifies which streams are to be involved and their configuration\n> >   * by populating \\a config.\n> >   *\n> > - * The easiest way to populate the array of config is to fetch an initial\n> > - * configuration from the camera with generateConfiguration() and then change\n> > - * the parameters to fit the caller's need and once all the streams parameters\n> > - * are configured hand that over to configure() to actually setup the camera.\n> > + * The configuration is created by generateConfiguration(), and adjusted by the\n> > + * caller with CameraConfiguration::validate(). This method only accepts fully\n> > + * valid configurations and returns an error if \\a config is not valid.\n> >   *\n> >   * Exclusive access to the camera shall be ensured by a call to acquire() prior\n> >   * to calling this function, otherwise an -EACCES error will be returned.\n> > @@ -603,7 +637,7 @@ int Camera::configure(CameraConfiguration *config)\n> >  \tif (!stateBetween(CameraAcquired, CameraConfigured))\n> >  \t\treturn -EACCES;\n> >\n> > -\tif (!config->isValid()) {\n> > +\tif (config->validate() != CameraConfiguration::Valid) {\n> >  \t\tLOG(Camera, Error)\n> >  \t\t\t<< \"Can't configure camera with invalid configuration\";\n> >  \t\treturn -EINVAL;\n> > diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp\n> > index 5b46fb68ced2..56265385a1e7 100644\n> > --- a/src/libcamera/pipeline/ipu3/ipu3.cpp\n> > +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp\n> > @@ -164,6 +164,33 @@ public:\n> >  \tIPU3Stream vfStream_;\n> >  };\n> >\n> > +class IPU3CameraConfiguration : public CameraConfiguration\n> > +{\n> > +public:\n> > +\tIPU3CameraConfiguration(Camera *camera, IPU3CameraData *data);\n> > +\n> > +\tStatus validate() override;\n> > +\n> > +\tconst V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }\n> > +\tconst std::vector<const IPU3Stream *> &streams() { return streams_; }\n> > +\n> > +private:\n> > +\tstatic constexpr unsigned int IPU3_BUFFER_COUNT = 4;\n> > +\n> > +\tvoid adjustStream(unsigned int index, bool scale);\n> > +\n> > +\t/*\n> > +\t * The IPU3CameraData instance is guaranteed to be valid as long as the\n> > +\t * corresponding Camera instance is valid. In order to borrow a\n> > +\t * reference to the camera data, store a new reference to the camera.\n> > +\t */\n> > +\tstd::shared_ptr<Camera> camera_;\n> > +\tconst IPU3CameraData *data_;\n> > +\n> > +\tV4L2SubdeviceFormat sensorFormat_;\n> > +\tstd::vector<const IPU3Stream *> streams_;\n> > +};\n> > +\n> >  class PipelineHandlerIPU3 : public PipelineHandler\n> >  {\n> >  public:\n> > @@ -186,8 +213,6 @@ public:\n> >  \tbool match(DeviceEnumerator *enumerator) override;\n> >\n> >  private:\n> > -\tstatic constexpr unsigned int IPU3_BUFFER_COUNT = 4;\n> > -\n> >  \tIPU3CameraData *cameraData(const Camera *camera)\n> >  \t{\n> >  \t\treturn static_cast<IPU3CameraData *>(\n> > @@ -202,6 +227,153 @@ private:\n> >  \tMediaDevice *imguMediaDev_;\n> >  };\n> >\n> > +IPU3CameraConfiguration::IPU3CameraConfiguration(Camera *camera,\n> > +\t\t\t\t\t\t IPU3CameraData *data)\n> > +\t: CameraConfiguration()\n> > +{\n> > +\tcamera_ = camera->shared_from_this();\n> > +\tdata_ = data;\n> > +}\n> > +\n> > +void IPU3CameraConfiguration::adjustStream(unsigned int index, bool scale)\n> \n> Could you pass here the StreanConfiguration & instead of i ?\n\nI had another use for the index before, now that it has gone, I'll pass\nthe stream configuration.\n\n> > +{\n> > +\tStreamConfiguration &cfg = config_[index];\n> > +\n> > +\t/* The only pixel format the driver supports is NV12. */\n> > +\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> > +\n> > +\tif (scale) {\n> > +\t\t/*\n> > +\t\t * Provide a suitable default that matches the sensor aspect\n> > +\t\t * ratio.\n> > +\t\t */\n> > +\t\tif (!cfg.size.width || !cfg.size.height) {\n> > +\t\t\tcfg.size.width = 1280;\n> > +\t\t\tcfg.size.height = 1280 * sensorFormat_.size.height\n> > +\t\t\t\t\t/ sensorFormat_.size.width;\n> > +\t\t}\n> > +\n> > +\t\t/*\n> > +\t\t * \\todo: Clamp the size to the hardware bounds when we will\n> > +\t\t * figure them out.\n> > +\t\t *\n> > +\t\t * \\todo: Handle the scaler (BDS) restrictions. The BDS can\n> > +\t\t * only scale with the same factor in both directions, and the\n> > +\t\t * scaling factor is limited to a multiple of 1/32. At the\n> > +\t\t * moment the ImgU driver hides these constraints by applying\n> > +\t\t * additional cropping, this should be fixed on the driver\n> > +\t\t * side, and cropping should be exposed to us.\n> > +\t\t */\n> > +\t} else {\n> > +\t\t/*\n> > +\t\t * \\todo: Properly support cropping when the ImgU driver\n> > +\t\t * interface will be cleaned up.\n> > +\t\t */\n> > +\t\tcfg.size = sensorFormat_.size;\n> > +\t}\n> > +\n> > +\t/*\n> > +\t * Clamp the size to match the ImgU alignment constraints. The width\n> > +\t * shall be a multiple of 8 pixels and the height a multiple of 4\n> > +\t * pixels.\n> > +\t */\n> > +\tif (cfg.size.width % 8 || cfg.size.height % 4) {\n> > +\t\tcfg.size.width &= ~7;\n> \n> Same question as below, is it safe to use the sensor provided sizes?\n> In here, in example, 2592 is a multiple of 8, but I'm not sure it\n> works well.\n> \n> (sorry, you should read below first)\n\nI'll reply below :-)\n\n> > +\t\tcfg.size.height &= ~3;\n> > +\t}\n> > +\n> > +\tcfg.bufferCount = IPU3_BUFFER_COUNT;\n> > +}\n> > +\n> > +CameraConfiguration::Status IPU3CameraConfiguration::validate()\n> > +{\n> > +\tconst CameraSensor *sensor = data_->cio2_.sensor_;\n> > +\tStatus status = Valid;\n> > +\n> > +\tif (config_.empty())\n> > +\t\treturn Invalid;\n> > +\n> > +\t/* Cap the number of entries to the available streams. */\n> > +\tif (config_.size() > 2) {\n> > +\t\tconfig_.resize(2);\n> > +\t\tstatus = Adjusted;\n> > +\t}\n> > +\n> > +\t/*\n> > +\t * Select the sensor format by collecting the maximum width and height\n> > +\t * and picking the closest larger match, as the IPU3 can downscale\n> > +\t * only. If no resolution is requested for any stream, or if no sensor\n> > +\t * resolution is large enough, pick the largest one.\n> > +\t */\n> > +\tSize size = {};\n> > +\n> > +\tfor (const StreamConfiguration &cfg : config_) {\n> > +\t\tif (cfg.size.width > size.width)\n> > +\t\t\tsize.width = cfg.size.width;\n> > +\t\tif (cfg.size.height > size.height)\n> > +\t\t\tsize.height = cfg.size.height;\n> > +\t}\n> > +\n> > +\tif (!size.width || !size.height)\n> > +\t\tsize = sensor->resolution();\n> > +\n> > +\tsensorFormat_ = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR10_1X10,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG10_1X10,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG10_1X10,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB10_1X10 },\n> > +\t\t\t\t\t  size);\n> > +\tif (!sensorFormat_.size.width || !sensorFormat_.size.height)\n> > +\t\tsensorFormat_.size = sensor->resolution();\n> > +\n> > +\t/*\n> > +\t * Verify and update all configuration entries, and assign a stream to\n> > +\t * each of them. The viewfinder stream can scale, while the output\n> > +\t * stream can crop only, so select the output stream when the requested\n> > +\t * resolution is equal to the sensor resolution, and the viewfinder\n> > +\t * stream otherwise.\n> > +\t */\n> > +\tstd::set<const IPU3Stream *> availableStreams = {\n> > +\t\t&data_->outStream_,\n> > +\t\t&data_->vfStream_,\n> > +\t};\n> > +\n> > +\tstreams_.clear();\n> > +\tstreams_.reserve(config_.size());\n> > +\n> > +\tfor (unsigned int i = 0; i < config_.size(); ++i) {\n> > +\t\tStreamConfiguration &cfg = config_[i];\n> \n> \tfor (StreamConfiguration &cfg : config_) ?\n\nYou answered this yourself below :-)\n\n> > +\t\tconst unsigned int pixelFormat = cfg.pixelFormat;\n> > +\t\tconst Size size = cfg.size;\n> > +\t\tconst IPU3Stream *stream;\n> > +\n> > +\t\tif (cfg.size == sensorFormat_.size)\n> \n> (here, more precisely)\n> \n> I'm not sure about this. I always seen the IPU3 used with maximum size\n> (for ov5670) as 2560x1920 while the sensor resolution is actually a\n> bit larger 2592x1944. Same for the back ov13858 camera. I never had the\n> full sensor resolution working properly and I assumed it was related to\n> some IPU3 alignement or constraints I didn't know about. Also, the\n> Intel provided xml configuration file does limit the maximum\n> available resolution to 2560x1920, but that could be for other reasons\n> maybe... Have you tested capture at sensor resolution sizes?\n\nNot yet. I'm 100% confident that this code isn't good enough :-) What\nwe're missing is documentation from Intel, and then I'll fix it.\n\n> > +\t\t\tstream = &data_->outStream_;\n> > +\t\telse\n> > +\t\t\tstream = &data_->vfStream_;\n> > +\n> > +\t\tif (availableStreams.find(stream) == availableStreams.end())\n> > +\t\t\tstream = *availableStreams.begin();\n> \n> This works as long as we have 2 streams only, maybe a todo ?\n> \n> > +\n> > +\t\tLOG(IPU3, Debug)\n> > +\t\t\t<< \"Assigned '\" << stream->name_ << \"' to stream \" << i;\n> \n> Ah, maybe it's better not to use the range-based for loop, you need i\n> \n> > +\n> > +\t\tbool scale = stream == &data_->vfStream_;\n> > +\t\tadjustStream(i, scale);\n> > +\n> > +\t\tif (cfg.pixelFormat != pixelFormat || cfg.size != size) {\n> > +\t\t\tLOG(IPU3, Debug)\n> > +\t\t\t\t<< \"Stream \" << i << \" configuration adjusted to \"\n> > +\t\t\t\t<< cfg.toString();\n> > +\t\t\tstatus = Adjusted;\n> > +\t\t}\n> > +\n> > +\t\tstreams_.push_back(stream);\n> > +\t\tavailableStreams.erase(stream);\n> > +\t}\n> > +\n> > +\treturn status;\n> > +}\n> > +\n> >  PipelineHandlerIPU3::PipelineHandlerIPU3(CameraManager *manager)\n> >  \t: PipelineHandler(manager), cio2MediaDev_(nullptr), imguMediaDev_(nullptr)\n> >  {\n> > @@ -211,12 +383,14 @@ CameraConfiguration *PipelineHandlerIPU3::generateConfiguration(Camera *camera,\n> >  \tconst StreamRoles &roles)\n> >  {\n> >  \tIPU3CameraData *data = cameraData(camera);\n> > -\tCameraConfiguration *config = new CameraConfiguration();\n> > +\tIPU3CameraConfiguration *config;\n> >  \tstd::set<IPU3Stream *> streams = {\n> >  \t\t&data->outStream_,\n> >  \t\t&data->vfStream_,\n> >  \t};\n> >\n> > +\tconfig = new IPU3CameraConfiguration(camera, data);\n> > +\n> >  \tfor (const StreamRole role : roles) {\n> >  \t\tStreamConfiguration cfg = {};\n> >  \t\tIPU3Stream *stream = nullptr;\n> > @@ -296,71 +470,25 @@ CameraConfiguration *PipelineHandlerIPU3::generateConfiguration(Camera *camera,\n> >\n> >  \t\tstreams.erase(stream);\n> >\n> > -\t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> > -\t\tcfg.bufferCount = IPU3_BUFFER_COUNT;\n> > -\n> >  \t\tconfig->addConfiguration(cfg);\n> >  \t}\n> >\n> > +\tconfig->validate();\n> > +\n> \n> If the idea of providing a Camera::validate(CameraConfiguration *) has\n> any ground, we should then have a private CameraConfiguration::validate()\n> operation accessible by the Camera class through a friend declarator,\n> so that pipeline handlers implementation cannot call it, restricting\n> that method to the application facing API only. The\n\nThat's not very neat, as it would polute the application-facing headers\nwith friends :-( I think we should try to remove them instead.\n\n> CameraConfiguration subclasses' validate() implementation could then\n> call into the same operation that pipeline handler would use here at\n> generateConfiguration() time as you did here, if desirable.\n> \n> Also, again if Camera::validate(CameraConfiguration *) makes any\n> sense, we might want to group basic validations, if any. So we could\n> make Camera::validate() call into the private CameraConfiguration::validate()\n> operation, which performs basic operations, and calls into a protected\n> virtual PipelineHandler::__validate() (or whatever name is more\n> appropriate)\n> \n> Would this be doable?\n\nIf you manage to find a good API that I like, sure :-) What you describe\nhere make the API more cumbersome in my opinion.\n\n> >  \treturn config;\n> >  }\n> >\n> > -int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n> > +int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *c)\n> >  {\n> > +\tIPU3CameraConfiguration *config =\n> > +\t\tstatic_cast<IPU3CameraConfiguration *>(c);\n> >  \tIPU3CameraData *data = cameraData(camera);\n> >  \tIPU3Stream *outStream = &data->outStream_;\n> >  \tIPU3Stream *vfStream = &data->vfStream_;\n> >  \tCIO2Device *cio2 = &data->cio2_;\n> >  \tImgUDevice *imgu = data->imgu_;\n> > -\tSize sensorSize = {};\n> >  \tint ret;\n> >\n> > -\toutStream->active_ = false;\n> > -\tvfStream->active_ = false;\n> > -\tfor (StreamConfiguration &cfg : *config) {\n> > -\t\t/*\n> > -\t\t * Pick a stream for the configuration entry.\n> > -\t\t * \\todo: This is a naive temporary implementation that will be\n> > -\t\t * reworked when implementing camera configuration validation.\n> > -\t\t */\n> > -\t\tIPU3Stream *stream = vfStream->active_ ? outStream : vfStream;\n> > -\n> > -\t\t/*\n> > -\t\t * Verify that the requested size respects the IPU3 alignment\n> > -\t\t * requirements (the image width shall be a multiple of 8\n> > -\t\t * pixels and its height a multiple of 4 pixels) and the camera\n> > -\t\t * maximum sizes.\n> > -\t\t *\n> > -\t\t * \\todo: Consider the BDS scaling factor requirements: \"the\n> > -\t\t * downscaling factor must be an integer value multiple of 1/32\"\n> > -\t\t */\n> > -\t\tif (cfg.size.width % 8 || cfg.size.height % 4) {\n> > -\t\t\tLOG(IPU3, Error)\n> > -\t\t\t\t<< \"Invalid stream size: bad alignment\";\n> > -\t\t\treturn -EINVAL;\n> > -\t\t}\n> > -\n> > -\t\tconst Size &resolution = cio2->sensor_->resolution();\n> > -\t\tif (cfg.size.width > resolution.width ||\n> > -\t\t    cfg.size.height > resolution.height) {\n> > -\t\t\tLOG(IPU3, Error)\n> > -\t\t\t\t<< \"Invalid stream size: larger than sensor resolution\";\n> > -\t\t\treturn -EINVAL;\n> > -\t\t}\n> > -\n> > -\t\t/*\n> > -\t\t * Collect the maximum width and height: IPU3 can downscale\n> > -\t\t * only.\n> > -\t\t */\n> > -\t\tif (cfg.size.width > sensorSize.width)\n> > -\t\t\tsensorSize.width = cfg.size.width;\n> > -\t\tif (cfg.size.height > sensorSize.height)\n> > -\t\t\tsensorSize.height = cfg.size.height;\n> > -\n> > -\t\tstream->active_ = true;\n> > -\t\tcfg.setStream(stream);\n> > -\t}\n> > -\n> >  \t/*\n> >  \t * \\todo: Enable links selectively based on the requested streams.\n> >  \t * As of now, enable all links unconditionally.\n> > @@ -373,6 +501,7 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n> >  \t * Pass the requested stream size to the CIO2 unit and get back the\n> >  \t * adjusted format to be propagated to the ImgU output devices.\n> >  \t */\n> > +\tconst Size &sensorSize = config->sensorFormat().size;\n> >  \tV4L2DeviceFormat cio2Format = {};\n> >  \tret = cio2->configure(sensorSize, &cio2Format);\n> >  \tif (ret)\n> > @@ -383,8 +512,22 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n> >  \t\treturn ret;\n> >\n> >  \t/* Apply the format to the configured streams output devices. */\n> > -\tfor (StreamConfiguration &cfg : *config) {\n> > -\t\tIPU3Stream *stream = static_cast<IPU3Stream *>(cfg.stream());\n> > +\toutStream->active_ = false;\n> > +\tvfStream->active_ = false;\n> > +\n> > +\tfor (unsigned int i = 0; i < config->size(); ++i) {\n> > +\t\t/*\n> > +\t\t * Use a const_cast<> here instead of storing a mutable stream\n> > +\t\t * pointer in the configuration to let the compiler catch\n> > +\t\t * unwanted modifications of camera data in the configuration\n> > +\t\t * validate() implementation.\n> > +\t\t */\n> > +\t\tIPU3Stream *stream = const_cast<IPU3Stream *>(config->streams()[i]);\n> > +\t\tStreamConfiguration &cfg = (*config)[i];\n> \n> nit: the fact you can get both Stream * and StreamConfiguration * by index\n> it's a bit ugly. What about config->streams(i) and\n> config->configuration(i).\n> \n> I think what's fishy lies in the fact IPu3CameraConfiguration indexes both\n> Stream and Configurations in two different vactors and both accessed by\n> index. Do you need Streams in CameraConfiguration ? Can't you store\n> them in CameraData, as 'activeStreams_' maybe?\n\nI can't, because configuration objects are separated from the active\nstate of the camera. You can create as many configuration you want, toy\nwith them in any way, and finally destroy them, without any side effect.\nThe API guarantees that Camera::configure() will associate streams with\nconfiguration entries, but I think pipeline handlers should be able to\ndo it ahead of time too, in validate(), if it's easier for them. I thus\ndon't think that storing that information in CameraData is a good idea.\n\nFurthermore, I would like to move the Stream class away from the\napplication-facing API, so more refactoring is to come. I could however,\nin the meantime, replace IPU3CameraConfiguration::streams() with\nIPU3CameraConfiguration::stream(unsigned int index) if you think that's\ndesirable (let's remember that that method is private to the IPU3), but\na CameraConfiguration::configuration(unsigned int index) would make the\nAPI more complex for applications I think (it would need to be called\nCameraConfiguration::streamConfiguration(), and wouldn't let\napplications iterate over the stream configurations using a range-based\nloop).\n\n> > +\n> > +\t\tstream->active_ = true;\n> > +\t\tcfg.setStream(stream);\n> > +\n> >  \t\tret = imgu->configureOutput(stream->device_, cfg);\n> >  \t\tif (ret)\n> >  \t\t\treturn ret;\n> > diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> > index a1a4f205b4aa..42944c64189b 100644\n> > --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> > +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> \n> An impressive rework, I like where it's going even if it puts\n> a bit of a burden on pipeline handlers, it's for the benefit of the\n> application facing APIs.\n\nThank you.\n\n> > @@ -5,6 +5,7 @@\n> >   * rkisp1.cpp - Pipeline handler for Rockchip ISP1\n> >   */\n> >\n> > +#include <algorithm>\n> >  #include <iomanip>\n> >  #include <memory>\n> >  #include <vector>\n> > @@ -45,6 +46,29 @@ public:\n> >  \tCameraSensor *sensor_;\n> >  };\n> >\n> > +class RkISP1CameraConfiguration : public CameraConfiguration\n> > +{\n> > +public:\n> > +\tRkISP1CameraConfiguration(Camera *camera, RkISP1CameraData *data);\n> > +\n> > +\tStatus validate() override;\n> > +\n> > +\tconst V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }\n> > +\n> > +private:\n> > +\tstatic constexpr unsigned int RKISP1_BUFFER_COUNT = 4;\n> > +\n> > +\t/*\n> > +\t * The RkISP1CameraData instance is guaranteed to be valid as long as the\n> > +\t * corresponding Camera instance is valid. In order to borrow a\n> > +\t * reference to the camera data, store a new reference to the camera.\n> > +\t */\n> > +\tstd::shared_ptr<Camera> camera_;\n> > +\tconst RkISP1CameraData *data_;\n> > +\n> > +\tV4L2SubdeviceFormat sensorFormat_;\n> > +};\n> > +\n> >  class PipelineHandlerRkISP1 : public PipelineHandler\n> >  {\n> >  public:\n> > @@ -68,8 +92,6 @@ public:\n> >  \tbool match(DeviceEnumerator *enumerator) override;\n> >\n> >  private:\n> > -\tstatic constexpr unsigned int RKISP1_BUFFER_COUNT = 4;\n> > -\n> >  \tRkISP1CameraData *cameraData(const Camera *camera)\n> >  \t{\n> >  \t\treturn static_cast<RkISP1CameraData *>(\n> > @@ -88,6 +110,95 @@ private:\n> >  \tCamera *activeCamera_;\n> >  };\n> >\n> > +RkISP1CameraConfiguration::RkISP1CameraConfiguration(Camera *camera,\n> > +\t\t\t\t\t\t     RkISP1CameraData *data)\n> > +\t: CameraConfiguration()\n> > +{\n> > +\tcamera_ = camera->shared_from_this();\n> > +\tdata_ = data;\n> > +}\n> > +\n> > +CameraConfiguration::Status RkISP1CameraConfiguration::validate()\n> > +{\n> > +\tstatic const std::array<unsigned int, 8> formats{\n> > +\t\tV4L2_PIX_FMT_YUYV,\n> > +\t\tV4L2_PIX_FMT_YVYU,\n> > +\t\tV4L2_PIX_FMT_VYUY,\n> > +\t\tV4L2_PIX_FMT_NV16,\n> > +\t\tV4L2_PIX_FMT_NV61,\n> > +\t\tV4L2_PIX_FMT_NV21,\n> > +\t\tV4L2_PIX_FMT_NV12,\n> > +\t\tV4L2_PIX_FMT_GREY,\n> > +\t};\n> > +\n> > +\tconst CameraSensor *sensor = data_->sensor_;\n> > +\tStatus status = Valid;\n> > +\n> > +\tif (config_.empty())\n> > +\t\treturn Invalid;\n> > +\n> > +\t/* Cap the number of entries to the available streams. */\n> > +\tif (config_.size() > 1) {\n> > +\t\tconfig_.resize(1);\n> > +\t\tstatus = Adjusted;\n> > +\t}\n> > +\n> > +\tStreamConfiguration &cfg = config_[0];\n> > +\n> > +\t/* Adjust the pixel format. */\n> > +\tif (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==\n> > +\t    formats.end()) {\n> > +\t\tLOG(RkISP1, Debug) << \"Adjusting format to NV12\";\n> > +\t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> > +\t\tstatus = Adjusted;\n> > +\t}\n> > +\n> > +\t/* Select the sensor format. */\n> > +\tsensorFormat_ = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR12_1X12,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG12_1X12,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG12_1X12,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB12_1X12,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SBGGR10_1X10,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG10_1X10,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG10_1X10,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB10_1X10,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SBGGR8_1X8,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG8_1X8,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG8_1X8,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB8_1X8 },\n> > +\t\t\t\t\t  cfg.size);\n> > +\tif (!sensorFormat_.size.width || !sensorFormat_.size.height)\n> > +\t\tsensorFormat_.size = sensor->resolution();\n> > +\n> > +\t/*\n> > +\t * Provide a suitable default that matches the sensor aspect\n> > +\t * ratio and clamp the size to the hardware bounds.\n> > +\t *\n> > +\t * \\todo: Check the hardware alignment constraints.\n> > +\t */\n> > +\tconst Size size = cfg.size;\n> > +\n> > +\tif (!cfg.size.width || !cfg.size.height) {\n> > +\t\tcfg.size.width = 1280;\n> > +\t\tcfg.size.height = 1280 * sensorFormat_.size.height\n> > +\t\t\t\t/ sensorFormat_.size.width;\n> > +\t}\n> > +\n> > +\tcfg.size.width = std::max(32U, std::min(4416U, cfg.size.width));\n> > +\tcfg.size.height = std::max(16U, std::min(3312U, cfg.size.height));\n> > +\n> > +\tif (cfg.size != size) {\n> > +\t\tLOG(RkISP1, Debug)\n> > +\t\t\t<< \"Adjusting size from \" << size.toString()\n> > +\t\t\t<< \" to \" << cfg.size.toString();\n> > +\t\tstatus = Adjusted;\n> > +\t}\n> > +\n> > +\tcfg.bufferCount = RKISP1_BUFFER_COUNT;\n> > +\n> > +\treturn status;\n> > +}\n> > +\n> >  PipelineHandlerRkISP1::PipelineHandlerRkISP1(CameraManager *manager)\n> >  \t: PipelineHandler(manager), dphy_(nullptr), isp_(nullptr),\n> >  \t  video_(nullptr)\n> > @@ -109,37 +220,31 @@ CameraConfiguration *PipelineHandlerRkISP1::generateConfiguration(Camera *camera\n> >  \tconst StreamRoles &roles)\n> >  {\n> >  \tRkISP1CameraData *data = cameraData(camera);\n> > -\tCameraConfiguration *config = new CameraConfiguration();\n> > +\tCameraConfiguration *config = new RkISP1CameraConfiguration(camera, data);\n> >\n> >  \tif (!roles.empty()) {\n> >  \t\tStreamConfiguration cfg{};\n> >\n> >  \t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> >  \t\tcfg.size = data->sensor_->resolution();\n> > -\t\tcfg.bufferCount = RKISP1_BUFFER_COUNT;\n> >\n> >  \t\tconfig->addConfiguration(cfg);\n> >  \t}\n> >\n> > +\tconfig->validate();\n> > +\n> >  \treturn config;\n> >  }\n> >\n> > -int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *config)\n> > +int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)\n> >  {\n> > +\tRkISP1CameraConfiguration *config =\n> > +\t\tstatic_cast<RkISP1CameraConfiguration *>(c);\n> >  \tRkISP1CameraData *data = cameraData(camera);\n> >  \tStreamConfiguration &cfg = config->at(0);\n> >  \tCameraSensor *sensor = data->sensor_;\n> >  \tint ret;\n> >\n> > -\t/* Verify the configuration. */\n> > -\tconst Size &resolution = sensor->resolution();\n> > -\tif (cfg.size.width > resolution.width ||\n> > -\t    cfg.size.height > resolution.height) {\n> > -\t\tLOG(RkISP1, Error)\n> > -\t\t\t<< \"Invalid stream size: larger than sensor resolution\";\n> > -\t\treturn -EINVAL;\n> > -\t}\n> > -\n> >  \t/*\n> >  \t * Configure the sensor links: enable the link corresponding to this\n> >  \t * camera and disable all the other sensor links.\n> > @@ -167,21 +272,7 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *config\n> >  \t * Configure the format on the sensor output and propagate it through\n> >  \t * the pipeline.\n> >  \t */\n> > -\tV4L2SubdeviceFormat format;\n> > -\tformat = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR12_1X12,\n> > -\t\t\t\t     MEDIA_BUS_FMT_SGBRG12_1X12,\n> > -\t\t\t\t     MEDIA_BUS_FMT_SGRBG12_1X12,\n> > -\t\t\t\t     MEDIA_BUS_FMT_SRGGB12_1X12,\n> > -\t\t\t\t     MEDIA_BUS_FMT_SBGGR10_1X10,\n> > -\t\t\t\t     MEDIA_BUS_FMT_SGBRG10_1X10,\n> > -\t\t\t\t     MEDIA_BUS_FMT_SGRBG10_1X10,\n> > -\t\t\t\t     MEDIA_BUS_FMT_SRGGB10_1X10,\n> > -\t\t\t\t     MEDIA_BUS_FMT_SBGGR8_1X8,\n> > -\t\t\t\t     MEDIA_BUS_FMT_SGBRG8_1X8,\n> > -\t\t\t\t     MEDIA_BUS_FMT_SGRBG8_1X8,\n> > -\t\t\t\t     MEDIA_BUS_FMT_SRGGB8_1X8 },\n> > -\t\t\t\t   cfg.size);\n> > -\n> > +\tV4L2SubdeviceFormat format = config->sensorFormat();\n> >  \tLOG(RkISP1, Debug) << \"Configuring sensor with \" << format.toString();\n> >\n> >  \tret = sensor->setFormat(&format);\n> > diff --git a/src/libcamera/pipeline/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo.cpp\n> > index 8254e1fdac1e..f7ffeb439cf3 100644\n> > --- a/src/libcamera/pipeline/uvcvideo.cpp\n> > +++ b/src/libcamera/pipeline/uvcvideo.cpp\n> > @@ -39,6 +39,14 @@ public:\n> >  \tStream stream_;\n> >  };\n> >\n> > +class UVCCameraConfiguration : public CameraConfiguration\n> > +{\n> > +public:\n> > +\tUVCCameraConfiguration();\n> > +\n> > +\tStatus validate() override;\n> > +};\n> > +\n> >  class PipelineHandlerUVC : public PipelineHandler\n> >  {\n> >  public:\n> > @@ -68,6 +76,45 @@ private:\n> >  \t}\n> >  };\n> >\n> > +UVCCameraConfiguration::UVCCameraConfiguration()\n> > +\t: CameraConfiguration()\n> > +{\n> > +}\n> > +\n> > +CameraConfiguration::Status UVCCameraConfiguration::validate()\n> > +{\n> > +\tStatus status = Valid;\n> > +\n> > +\tif (config_.empty())\n> > +\t\treturn Invalid;\n> > +\n> > +\t/* Cap the number of entries to the available streams. */\n> > +\tif (config_.size() > 1) {\n> > +\t\tconfig_.resize(1);\n> > +\t\tstatus = Adjusted;\n> > +\t}\n> > +\n> > +\tStreamConfiguration &cfg = config_[0];\n> > +\n> > +\t/* \\todo: Validate the configuration against the device capabilities. */\n> > +\tconst unsigned int pixelFormat = cfg.pixelFormat;\n> > +\tconst Size size = cfg.size;\n> > +\n> > +\tcfg.pixelFormat = V4L2_PIX_FMT_YUYV;\n> > +\tcfg.size = { 640, 480 };\n> > +\n> > +\tif (cfg.pixelFormat != pixelFormat || cfg.size != size) {\n> > +\t\tLOG(UVC, Debug)\n> > +\t\t\t<< \"Adjusting configuration from \" << cfg.toString()\n> > +\t\t\t<< \" to \" << cfg.size.toString() << \"-YUYV\";\n> > +\t\tstatus = Adjusted;\n> > +\t}\n> > +\n> > +\tcfg.bufferCount = 4;\n> > +\n> > +\treturn status;\n> > +}\n> > +\n> >  PipelineHandlerUVC::PipelineHandlerUVC(CameraManager *manager)\n> >  \t: PipelineHandler(manager)\n> >  {\n> > @@ -76,10 +123,10 @@ PipelineHandlerUVC::PipelineHandlerUVC(CameraManager *manager)\n> >  CameraConfiguration *PipelineHandlerUVC::generateConfiguration(Camera *camera,\n> >  \tconst StreamRoles &roles)\n> >  {\n> > -\tCameraConfiguration *config = new CameraConfiguration();\n> > +\tCameraConfiguration *config = new UVCCameraConfiguration();\n> >\n> >  \tif (!roles.empty()) {\n> > -\t\tStreamConfiguration cfg;\n> > +\t\tStreamConfiguration cfg{};\n> >\n> >  \t\tcfg.pixelFormat = V4L2_PIX_FMT_YUYV;\n> >  \t\tcfg.size = { 640, 480 };\n> > @@ -88,6 +135,8 @@ CameraConfiguration *PipelineHandlerUVC::generateConfiguration(Camera *camera,\n> >  \t\tconfig->addConfiguration(cfg);\n> >  \t}\n> >\n> > +\tconfig->validate();\n> > +\n> >  \treturn config;\n> >  }\n> >\n> > diff --git a/src/libcamera/pipeline/vimc.cpp b/src/libcamera/pipeline/vimc.cpp\n> > index 2bf85d0a0b32..2a61d2893e3a 100644\n> > --- a/src/libcamera/pipeline/vimc.cpp\n> > +++ b/src/libcamera/pipeline/vimc.cpp\n> > @@ -5,6 +5,8 @@\n> >   * vimc.cpp - Pipeline handler for the vimc device\n> >   */\n> >\n> > +#include <algorithm>\n> > +\n> >  #include <libcamera/camera.h>\n> >  #include <libcamera/request.h>\n> >  #include <libcamera/stream.h>\n> > @@ -39,6 +41,14 @@ public:\n> >  \tStream stream_;\n> >  };\n> >\n> > +class VimcCameraConfiguration : public CameraConfiguration\n> > +{\n> > +public:\n> > +\tVimcCameraConfiguration();\n> > +\n> > +\tStatus validate() override;\n> > +};\n> > +\n> >  class PipelineHandlerVimc : public PipelineHandler\n> >  {\n> >  public:\n> > @@ -68,6 +78,57 @@ private:\n> >  \t}\n> >  };\n> >\n> > +VimcCameraConfiguration::VimcCameraConfiguration()\n> > +\t: CameraConfiguration()\n> > +{\n> > +}\n> > +\n> > +CameraConfiguration::Status VimcCameraConfiguration::validate()\n> > +{\n> > +\tstatic const std::array<unsigned int, 3> formats{\n> > +\t\tV4L2_PIX_FMT_BGR24,\n> > +\t\tV4L2_PIX_FMT_RGB24,\n> > +\t\tV4L2_PIX_FMT_ARGB32,\n> > +\t};\n> > +\n> > +\tStatus status = Valid;\n> > +\n> > +\tif (config_.empty())\n> > +\t\treturn Invalid;\n> > +\n> > +\t/* Cap the number of entries to the available streams. */\n> > +\tif (config_.size() > 1) {\n> > +\t\tconfig_.resize(1);\n> > +\t\tstatus = Adjusted;\n> > +\t}\n> > +\n> > +\tStreamConfiguration &cfg = config_[0];\n> > +\n> > +\t/* Adjust the pixel format. */\n> > +\tif (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==\n> > +\t    formats.end()) {\n> > +\t\tLOG(VIMC, Debug) << \"Adjusting format to RGB24\";\n> > +\t\tcfg.pixelFormat = V4L2_PIX_FMT_RGB24;\n> > +\t\tstatus = Adjusted;\n> > +\t}\n> > +\n> > +\t/* Clamp the size based on the device limits. */\n> > +\tconst Size size = cfg.size;\n> > +\n> > +\tcfg.size.width = std::max(16U, std::min(4096U, cfg.size.width));\n> > +\tcfg.size.height = std::max(16U, std::min(2160U, cfg.size.height));\n> > +\n> > +\tif (cfg.size != size) {\n> > +\t\tLOG(VIMC, Debug)\n> > +\t\t\t<< \"Adjusting size to \" << cfg.size.toString();\n> > +\t\tstatus = Adjusted;\n> > +\t}\n> > +\n> > +\tcfg.bufferCount = 4;\n> > +\n> > +\treturn status;\n> > +}\n> > +\n> >  PipelineHandlerVimc::PipelineHandlerVimc(CameraManager *manager)\n> >  \t: PipelineHandler(manager)\n> >  {\n> > @@ -76,10 +137,10 @@ PipelineHandlerVimc::PipelineHandlerVimc(CameraManager *manager)\n> >  CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera,\n> >  \tconst StreamRoles &roles)\n> >  {\n> > -\tCameraConfiguration *config = new CameraConfiguration();\n> > +\tCameraConfiguration *config = new VimcCameraConfiguration();\n> >\n> >  \tif (!roles.empty()) {\n> > -\t\tStreamConfiguration cfg;\n> > +\t\tStreamConfiguration cfg{};\n> >\n> >  \t\tcfg.pixelFormat = V4L2_PIX_FMT_RGB24;\n> >  \t\tcfg.size = { 640, 480 };\n> > @@ -88,6 +149,8 @@ CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera,\n> >  \t\tconfig->addConfiguration(cfg);\n> >  \t}\n> >\n> > +\tconfig->validate();\n> > +\n> >  \treturn config;\n> >  }\n> >\n> > diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp\n> > index de46e98880a2..dd56907d817e 100644\n> > --- a/src/libcamera/pipeline_handler.cpp\n> > +++ b/src/libcamera/pipeline_handler.cpp\n> > @@ -248,17 +248,14 @@ void PipelineHandler::unlock()\n> >   * is the Camera class which will receive configuration to apply from the\n> >   * application.\n> >   *\n> > - * Each pipeline handler implementation is responsible for validating\n> > - * that the configuration requested in \\a config can be achieved\n> > - * exactly. Any difference in pixel format, frame size or any other\n> > - * parameter shall result in the -EINVAL error being returned, and no\n> > - * change in configuration being applied to the pipeline. If\n> > - * configuration of a subset of the streams can't be satisfied, the\n> > - * whole configuration is considered invalid.\n> > + * The configuration is guaranteed to have been validated with\n> > + * CameraConfiguration::valid(). The pipeline handler implementation shall not\n> > + * perform further validation and may rely on any custom field stored in its\n> > + * custom CameraConfiguration derived class.\n> >   *\n> > - * Once the configuration is validated and the camera configured, the pipeline\n> > - * handler shall associate a Stream instance to each StreamConfiguration entry\n> > - * in the CameraConfiguration with the StreamConfiguration::setStream() method.\n> > + * When configuring the camera the pipeline handler shall associate a Stream\n> > + * instance to each StreamConfiguration entry in the CameraConfiguration using\n> > + * the StreamConfiguration::setStream() method.\n> >   *\n> >   * \\return 0 on success or a negative error code otherwise\n> >   */\n> > diff --git a/test/camera/capture.cpp b/test/camera/capture.cpp\n> > index 137aa649a505..f122f79bb1ec 100644\n> > --- a/test/camera/capture.cpp\n> > +++ b/test/camera/capture.cpp\n> > @@ -45,7 +45,7 @@ protected:\n> >  \t\tCameraTest::init();\n> >\n> >  \t\tconfig_ = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> > -\t\tif (!config_) {\n> > +\t\tif (!config_ || config_->size() != 1) {\n> >  \t\t\tcout << \"Failed to generate default configuration\" << endl;\n> >  \t\t\tCameraTest::cleanup();\n> >  \t\t\treturn TestFail;\n> > @@ -58,11 +58,6 @@ protected:\n> >  \t{\n> >  \t\tStreamConfiguration &cfg = config_->at(0);\n> >\n> > -\t\tif (!config_->isValid()) {\n> > -\t\t\tcout << \"Failed to read default configuration\" << endl;\n> > -\t\t\treturn TestFail;\n> > -\t\t}\n> > -\n> >  \t\tif (camera_->acquire()) {\n> >  \t\t\tcout << \"Failed to acquire the camera\" << endl;\n> >  \t\t\treturn TestFail;\n> > diff --git a/test/camera/configuration_default.cpp b/test/camera/configuration_default.cpp\n> > index d5cefc1127c9..81055da1d513 100644\n> > --- a/test/camera/configuration_default.cpp\n> > +++ b/test/camera/configuration_default.cpp\n> > @@ -22,7 +22,7 @@ protected:\n> >\n> >  \t\t/* Test asking for configuration for a video stream. */\n> >  \t\tconfig = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> > -\t\tif (!config || !config->isValid()) {\n> > +\t\tif (!config || config->size() != 1) {\n> >  \t\t\tcout << \"Default configuration invalid\" << endl;\n> >  \t\t\tdelete config;\n> >  \t\t\treturn TestFail;\n> > @@ -35,7 +35,7 @@ protected:\n> >  \t\t * stream roles returns an empty camera configuration.\n> >  \t\t */\n> >  \t\tconfig = camera_->generateConfiguration({});\n> > -\t\tif (!config || config->isValid()) {\n> > +\t\tif (!config || config->size() != 0) {\n> >  \t\t\tcout << \"Failed to retrieve configuration for empty roles list\"\n> >  \t\t\t     << endl;\n> >  \t\t\tdelete config;\n> > diff --git a/test/camera/configuration_set.cpp b/test/camera/configuration_set.cpp\n> > index 23c611a93355..a4e2da16a88b 100644\n> > --- a/test/camera/configuration_set.cpp\n> > +++ b/test/camera/configuration_set.cpp\n> > @@ -21,7 +21,7 @@ protected:\n> >  \t\tCameraTest::init();\n> >\n> >  \t\tconfig_ = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> > -\t\tif (!config_) {\n> > +\t\tif (!config_ || config_->size() != 1) {\n> >  \t\t\tcout << \"Failed to generate default configuration\" << endl;\n> >  \t\t\tCameraTest::cleanup();\n> >  \t\t\treturn TestFail;\n> > @@ -34,11 +34,6 @@ protected:\n> >  \t{\n> >  \t\tStreamConfiguration &cfg = config_->at(0);\n> >\n> > -\t\tif (!config_->isValid()) {\n> > -\t\t\tcout << \"Failed to read default configuration\" << endl;\n> > -\t\t\treturn TestFail;\n> > -\t\t}\n> > -\n> >  \t\tif (camera_->acquire()) {\n> >  \t\t\tcout << \"Failed to acquire the camera\" << endl;\n> >  \t\t\treturn TestFail;","headers":{"Return-Path":"<laurent.pinchart@ideasonboard.com>","Received":["from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 951F760C27\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 21 May 2019 14:50:12 +0200 (CEST)","from pendragon.ideasonboard.com\n\t(dfj612yhrgyx302h3jwwy-3.rev.dnainternet.fi\n\t[IPv6:2001:14ba:21f5:5b00:ce28:277f:58d7:3ca4])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id C6BBB52C;\n\tTue, 21 May 2019 14:50:11 +0200 (CEST)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1558443012;\n\tbh=89cRGBM6KmZgPIj0t4xRKD3itZWtPdRwkjuFwmuAgu0=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=U2Naz/v/lYpcpoJtnNjuYehZTKKn3pGnDbsJOtk2bE9B8r4LTE4Arh+yNoKd8yv1P\n\tddj5u113FSy1s3uQGLKxmriqWs0NNz95GoFkfwi8IOlTu83F0p0gQLzUXmWpbcqQFm\n\ty48Bvi10cUZ6iraaJIjyMJ5fJLL6l6hEoK+0yzxU=","Date":"Tue, 21 May 2019 15:49:55 +0300","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Jacopo Mondi <jacopo@jmondi.org>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20190521124955.GC5674@pendragon.ideasonboard.com>","References":"<20190519150047.12444-1-laurent.pinchart@ideasonboard.com>\n\t<20190519150047.12444-7-laurent.pinchart@ideasonboard.com>\n\t<20190520220506.5yubhl2rqmk3ajdr@uno.localdomain>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20190520220506.5yubhl2rqmk3ajdr@uno.localdomain>","User-Agent":"Mutt/1.10.1 (2018-07-13)","Subject":"Re: [libcamera-devel] [PATCH v2 6/6] libcamera: camera: Add a\n\tvalidation API to the CameraConfiguration class","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.23","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Tue, 21 May 2019 12:50:12 -0000"}},{"id":1641,"web_url":"https://patchwork.libcamera.org/comment/1641/","msgid":"<20190521125109.GD5674@pendragon.ideasonboard.com>","date":"2019-05-21T12:51:09","subject":"Re: [libcamera-devel] [PATCH v2 6/6] libcamera: camera: Add a\n\tvalidation API to the CameraConfiguration class","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Jacopo,\n\nOn Tue, May 21, 2019 at 10:49:50AM +0200, Jacopo Mondi wrote:\n> Hi Laurent,\n>    one more thing, sorry\n> \n> On Sun, May 19, 2019 at 06:00:47PM +0300, Laurent Pinchart wrote:\n> > The CameraConfiguration class implements a simple storage of\n> > StreamConfiguration with internal validation limited to verifying that\n> > the stream configurations are not empty. Extend this mechanism by\n> > implementing a smart validate() method backed by pipeline handlers.\n> >\n> > This new mechanism changes the semantics of the camera configuration.\n> > The Camera::generateConfiguration() operation still generates a default\n> > configuration based on roles, but now also supports generating empty\n> > configurations to be filled by applications. Applications can inspect\n> > the configuration, optionally modify it, and validate it. The validation\n> > implements \"try\" semantics and adjusts invalid configurations instead of\n> > rejecting them completely. Applications then decide whether to accept\n> > the modified configuration, or try again with a different set of\n> > parameters. Once the configuration is valid, it is passed to\n> > Camera::configure(), and pipeline handlers are guaranteed that the\n> > configuration they receive is valid.\n> >\n> > A reference to the Camera may need to be stored in the\n> > CameraConfiguration derived classes in order to access it from their\n> > validate() implementation. This must be stored as a std::shared_ptr<> as\n> > the CameraConfiguration instances belong to applications. In order to\n> > make this possible, make the Camera class inherit from\n> > std::shared_from_this<>.\n> >\n> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> > ---\n> >  include/libcamera/camera.h               |  17 +-\n> >  src/cam/main.cpp                         |   2 +-\n> >  src/libcamera/camera.cpp                 |  80 +++++--\n> >  src/libcamera/pipeline/ipu3/ipu3.cpp     | 255 ++++++++++++++++++-----\n> >  src/libcamera/pipeline/rkisp1/rkisp1.cpp | 149 ++++++++++---\n> >  src/libcamera/pipeline/uvcvideo.cpp      |  53 ++++-\n> >  src/libcamera/pipeline/vimc.cpp          |  67 +++++-\n> >  src/libcamera/pipeline_handler.cpp       |  17 +-\n> >  test/camera/capture.cpp                  |   7 +-\n> >  test/camera/configuration_default.cpp    |   4 +-\n> >  test/camera/configuration_set.cpp        |   7 +-\n> >  11 files changed, 516 insertions(+), 142 deletions(-)\n> >\n> > diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> > index 144133c5de9f..8c0049a1dd94 100644\n> > --- a/include/libcamera/camera.h\n> > +++ b/include/libcamera/camera.h\n> > @@ -25,14 +25,19 @@ class Request;\n> >  class CameraConfiguration\n> >  {\n> >  public:\n> > +\tenum Status {\n> > +\t\tValid,\n> > +\t\tAdjusted,\n> > +\t\tInvalid,\n> > +\t};\n> > +\n> >  \tusing iterator = std::vector<StreamConfiguration>::iterator;\n> >  \tusing const_iterator = std::vector<StreamConfiguration>::const_iterator;\n> >\n> > -\tCameraConfiguration();\n> > +\tvirtual ~CameraConfiguration();\n> >\n> >  \tvoid addConfiguration(const StreamConfiguration &cfg);\n> > -\n> > -\tbool isValid() const;\n> > +\tvirtual Status validate() = 0;\n> >\n> >  \tStreamConfiguration &at(unsigned int index);\n> >  \tconst StreamConfiguration &at(unsigned int index) const;\n> > @@ -53,11 +58,13 @@ public:\n> >  \tbool empty() const;\n> >  \tstd::size_t size() const;\n> >\n> > -private:\n> > +protected:\n> > +\tCameraConfiguration();\n> > +\n> >  \tstd::vector<StreamConfiguration> config_;\n> >  };\n> >\n> > -class Camera final\n> > +class Camera final : public std::enable_shared_from_this<Camera>\n> >  {\n> >  public:\n> >  \tstatic std::shared_ptr<Camera> create(PipelineHandler *pipe,\n> > diff --git a/src/cam/main.cpp b/src/cam/main.cpp\n> > index 7550ae4f3428..23da5c687d89 100644\n> > --- a/src/cam/main.cpp\n> > +++ b/src/cam/main.cpp\n> \n> Shouldn't you add a config->validate() call before calling\n> camera->configure(config) for the cam application?\n\nNiklas has submitted a patch for them, do you think I should squash it\nwith this one, or apply it separately ?\n\n> (I've not checked qcam, I'm sorry, but it might apply there too)\n\nThat should be fixed too, yes.\n\n> > @@ -116,7 +116,7 @@ static CameraConfiguration *prepareCameraConfig()\n> >  \t}\n> >\n> >  \tCameraConfiguration *config = camera->generateConfiguration(roles);\n> > -\tif (!config || !config->isValid()) {\n> > +\tif (!config || config->size() != roles.size()) {\n> >  \t\tstd::cerr << \"Failed to get default stream configuration\"\n> >  \t\t\t  << std::endl;\n> >  \t\tdelete config;\n> > diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> > index 0e80691ed862..9da5d4f613f4 100644\n> > --- a/src/libcamera/camera.cpp\n> > +++ b/src/libcamera/camera.cpp\n> > @@ -52,6 +52,28 @@ LOG_DECLARE_CATEGORY(Camera)\n> >   * operator[](int) returns a reference to the StreamConfiguration based on its\n> >   * insertion index. Accessing a stream configuration with an invalid index\n> >   * results in undefined behaviour.\n> > + *\n> > + * CameraConfiguration instances are retrieved from the camera with\n> > + * Camera::generateConfiguration(). Applications may then inspect the\n> > + * configuration, modify it, and possibly add new stream configuration entries\n> > + * with addConfiguration(). Once the camera configuration satisfies the\n> > + * application, it shall be validated by a call to validate(). The validation\n> > + * implements \"try\" semantics: it adjusts invalid configurations to the closest\n> > + * achievable parameters instead of rejecting them completely. Applications\n> > + * then decide whether to accept the modified configuration, or try again with\n> > + * a different set of parameters. Once the configuration is valid, it is passed\n> > + * to Camera::configure().\n> > + */\n> > +\n> > +/**\n> > + * \\enum CameraConfiguration::Status\n> > + * \\brief Validity of a camera configuration\n> > + * \\var CameraConfiguration::Valid\n> > + * The configuration is fully valid\n> > + * \\var CameraConfiguration::Adjusted\n> > + * The configuration has been adjusted to a valid configuration\n> > + * \\var CameraConfiguration::Invalid\n> > + * The configuration is invalid and can't be adjusted automatically\n> >   */\n> >\n> >  /**\n> > @@ -73,6 +95,10 @@ CameraConfiguration::CameraConfiguration()\n> >  {\n> >  }\n> >\n> > +CameraConfiguration::~CameraConfiguration()\n> > +{\n> > +}\n> > +\n> >  /**\n> >   * \\brief Add a stream configuration to the camera configuration\n> >   * \\param[in] cfg The stream configuration\n> > @@ -83,27 +109,31 @@ void CameraConfiguration::addConfiguration(const StreamConfiguration &cfg)\n> >  }\n> >\n> >  /**\n> > - * \\brief Check if the camera configuration is valid\n> > + * \\fn CameraConfiguration::validate()\n> > + * \\brief Validate and possibly adjust the camera configuration\n> >   *\n> > - * A camera configuration is deemed to be valid if it contains at least one\n> > - * stream configuration and all stream configurations contain valid information.\n> > - * Stream configurations are deemed to be valid if all fields are none zero.\n> > + * This method adjusts the camera configuration to the closest valid\n> > + * configuration and returns the validation status.\n> >   *\n> > - * \\return True if the configuration is valid\n> > + * \\todo: Define exactly when to return each status code. Should stream\n> > + * parameters set to 0 by the caller be adjusted without returning Adjusted ?\n> > + * This would potentially be useful for applications but would get in the way\n> > + * in Camera::configure(). Do we need an extra status code to signal this ?\n> > + *\n> > + * \\todo: Handle validation of buffers count when refactoring the buffers API.\n> > + *\n> > + * \\return A CameraConfiguration::Status value that describes the validation\n> > + * status.\n> > + * \\retval CameraConfiguration::Invalid The configuration is invalid and can't\n> > + * be adjusted. This may only occur in extreme cases such as when the\n> > + * configuration is empty.\n> > + * \\retval CameraConfigutation::Adjusted The configuration has been adjusted\n> > + * and is now valid. Parameters may have changed for any stream, and stream\n> > + * configurations may have been removed. The caller shall check the\n> > + * configuration carefully.\n> > + * \\retval CameraConfiguration::Valid The configuration was already valid and\n> > + * hasn't been adjusted.\n> >   */\n> > -bool CameraConfiguration::isValid() const\n> > -{\n> > -\tif (empty())\n> > -\t\treturn false;\n> > -\n> > -\tfor (const StreamConfiguration &cfg : config_) {\n> > -\t\tif (cfg.size.width == 0 || cfg.size.height == 0 ||\n> > -\t\t    cfg.pixelFormat == 0 || cfg.bufferCount == 0)\n> > -\t\t\treturn false;\n> > -\t}\n> > -\n> > -\treturn true;\n> > -}\n> >\n> >  /**\n> >   * \\brief Retrieve a reference to a stream configuration\n> > @@ -218,6 +248,11 @@ std::size_t CameraConfiguration::size() const\n> >  \treturn config_.size();\n> >  }\n> >\n> > +/**\n> > + * \\var CameraConfiguration::config_\n> > + * \\brief The vector of stream configurations\n> > + */\n> > +\n> >  /**\n> >   * \\class Camera\n> >   * \\brief Camera device\n> > @@ -575,10 +610,9 @@ CameraConfiguration *Camera::generateConfiguration(const StreamRoles &roles)\n> >   * The caller specifies which streams are to be involved and their configuration\n> >   * by populating \\a config.\n> >   *\n> > - * The easiest way to populate the array of config is to fetch an initial\n> > - * configuration from the camera with generateConfiguration() and then change\n> > - * the parameters to fit the caller's need and once all the streams parameters\n> > - * are configured hand that over to configure() to actually setup the camera.\n> > + * The configuration is created by generateConfiguration(), and adjusted by the\n> > + * caller with CameraConfiguration::validate(). This method only accepts fully\n> > + * valid configurations and returns an error if \\a config is not valid.\n> >   *\n> >   * Exclusive access to the camera shall be ensured by a call to acquire() prior\n> >   * to calling this function, otherwise an -EACCES error will be returned.\n> > @@ -603,7 +637,7 @@ int Camera::configure(CameraConfiguration *config)\n> >  \tif (!stateBetween(CameraAcquired, CameraConfigured))\n> >  \t\treturn -EACCES;\n> >\n> > -\tif (!config->isValid()) {\n> > +\tif (config->validate() != CameraConfiguration::Valid) {\n> >  \t\tLOG(Camera, Error)\n> >  \t\t\t<< \"Can't configure camera with invalid configuration\";\n> >  \t\treturn -EINVAL;\n> > diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp\n> > index 5b46fb68ced2..56265385a1e7 100644\n> > --- a/src/libcamera/pipeline/ipu3/ipu3.cpp\n> > +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp\n> > @@ -164,6 +164,33 @@ public:\n> >  \tIPU3Stream vfStream_;\n> >  };\n> >\n> > +class IPU3CameraConfiguration : public CameraConfiguration\n> > +{\n> > +public:\n> > +\tIPU3CameraConfiguration(Camera *camera, IPU3CameraData *data);\n> > +\n> > +\tStatus validate() override;\n> > +\n> > +\tconst V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }\n> > +\tconst std::vector<const IPU3Stream *> &streams() { return streams_; }\n> > +\n> > +private:\n> > +\tstatic constexpr unsigned int IPU3_BUFFER_COUNT = 4;\n> > +\n> > +\tvoid adjustStream(unsigned int index, bool scale);\n> > +\n> > +\t/*\n> > +\t * The IPU3CameraData instance is guaranteed to be valid as long as the\n> > +\t * corresponding Camera instance is valid. In order to borrow a\n> > +\t * reference to the camera data, store a new reference to the camera.\n> > +\t */\n> > +\tstd::shared_ptr<Camera> camera_;\n> > +\tconst IPU3CameraData *data_;\n> > +\n> > +\tV4L2SubdeviceFormat sensorFormat_;\n> > +\tstd::vector<const IPU3Stream *> streams_;\n> > +};\n> > +\n> >  class PipelineHandlerIPU3 : public PipelineHandler\n> >  {\n> >  public:\n> > @@ -186,8 +213,6 @@ public:\n> >  \tbool match(DeviceEnumerator *enumerator) override;\n> >\n> >  private:\n> > -\tstatic constexpr unsigned int IPU3_BUFFER_COUNT = 4;\n> > -\n> >  \tIPU3CameraData *cameraData(const Camera *camera)\n> >  \t{\n> >  \t\treturn static_cast<IPU3CameraData *>(\n> > @@ -202,6 +227,153 @@ private:\n> >  \tMediaDevice *imguMediaDev_;\n> >  };\n> >\n> > +IPU3CameraConfiguration::IPU3CameraConfiguration(Camera *camera,\n> > +\t\t\t\t\t\t IPU3CameraData *data)\n> > +\t: CameraConfiguration()\n> > +{\n> > +\tcamera_ = camera->shared_from_this();\n> > +\tdata_ = data;\n> > +}\n> > +\n> > +void IPU3CameraConfiguration::adjustStream(unsigned int index, bool scale)\n> > +{\n> > +\tStreamConfiguration &cfg = config_[index];\n> > +\n> > +\t/* The only pixel format the driver supports is NV12. */\n> > +\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> > +\n> > +\tif (scale) {\n> > +\t\t/*\n> > +\t\t * Provide a suitable default that matches the sensor aspect\n> > +\t\t * ratio.\n> > +\t\t */\n> > +\t\tif (!cfg.size.width || !cfg.size.height) {\n> > +\t\t\tcfg.size.width = 1280;\n> > +\t\t\tcfg.size.height = 1280 * sensorFormat_.size.height\n> > +\t\t\t\t\t/ sensorFormat_.size.width;\n> > +\t\t}\n> > +\n> > +\t\t/*\n> > +\t\t * \\todo: Clamp the size to the hardware bounds when we will\n> > +\t\t * figure them out.\n> > +\t\t *\n> > +\t\t * \\todo: Handle the scaler (BDS) restrictions. The BDS can\n> > +\t\t * only scale with the same factor in both directions, and the\n> > +\t\t * scaling factor is limited to a multiple of 1/32. At the\n> > +\t\t * moment the ImgU driver hides these constraints by applying\n> > +\t\t * additional cropping, this should be fixed on the driver\n> > +\t\t * side, and cropping should be exposed to us.\n> > +\t\t */\n> > +\t} else {\n> > +\t\t/*\n> > +\t\t * \\todo: Properly support cropping when the ImgU driver\n> > +\t\t * interface will be cleaned up.\n> > +\t\t */\n> > +\t\tcfg.size = sensorFormat_.size;\n> > +\t}\n> > +\n> > +\t/*\n> > +\t * Clamp the size to match the ImgU alignment constraints. The width\n> > +\t * shall be a multiple of 8 pixels and the height a multiple of 4\n> > +\t * pixels.\n> > +\t */\n> > +\tif (cfg.size.width % 8 || cfg.size.height % 4) {\n> > +\t\tcfg.size.width &= ~7;\n> > +\t\tcfg.size.height &= ~3;\n> > +\t}\n> > +\n> > +\tcfg.bufferCount = IPU3_BUFFER_COUNT;\n> > +}\n> > +\n> > +CameraConfiguration::Status IPU3CameraConfiguration::validate()\n> > +{\n> > +\tconst CameraSensor *sensor = data_->cio2_.sensor_;\n> > +\tStatus status = Valid;\n> > +\n> > +\tif (config_.empty())\n> > +\t\treturn Invalid;\n> > +\n> > +\t/* Cap the number of entries to the available streams. */\n> > +\tif (config_.size() > 2) {\n> > +\t\tconfig_.resize(2);\n> > +\t\tstatus = Adjusted;\n> > +\t}\n> > +\n> > +\t/*\n> > +\t * Select the sensor format by collecting the maximum width and height\n> > +\t * and picking the closest larger match, as the IPU3 can downscale\n> > +\t * only. If no resolution is requested for any stream, or if no sensor\n> > +\t * resolution is large enough, pick the largest one.\n> > +\t */\n> > +\tSize size = {};\n> > +\n> > +\tfor (const StreamConfiguration &cfg : config_) {\n> > +\t\tif (cfg.size.width > size.width)\n> > +\t\t\tsize.width = cfg.size.width;\n> > +\t\tif (cfg.size.height > size.height)\n> > +\t\t\tsize.height = cfg.size.height;\n> > +\t}\n> > +\n> > +\tif (!size.width || !size.height)\n> > +\t\tsize = sensor->resolution();\n> > +\n> > +\tsensorFormat_ = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR10_1X10,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG10_1X10,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG10_1X10,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB10_1X10 },\n> > +\t\t\t\t\t  size);\n> > +\tif (!sensorFormat_.size.width || !sensorFormat_.size.height)\n> > +\t\tsensorFormat_.size = sensor->resolution();\n> > +\n> > +\t/*\n> > +\t * Verify and update all configuration entries, and assign a stream to\n> > +\t * each of them. The viewfinder stream can scale, while the output\n> > +\t * stream can crop only, so select the output stream when the requested\n> > +\t * resolution is equal to the sensor resolution, and the viewfinder\n> > +\t * stream otherwise.\n> > +\t */\n> > +\tstd::set<const IPU3Stream *> availableStreams = {\n> > +\t\t&data_->outStream_,\n> > +\t\t&data_->vfStream_,\n> > +\t};\n> > +\n> > +\tstreams_.clear();\n> > +\tstreams_.reserve(config_.size());\n> > +\n> > +\tfor (unsigned int i = 0; i < config_.size(); ++i) {\n> > +\t\tStreamConfiguration &cfg = config_[i];\n> > +\t\tconst unsigned int pixelFormat = cfg.pixelFormat;\n> > +\t\tconst Size size = cfg.size;\n> > +\t\tconst IPU3Stream *stream;\n> > +\n> > +\t\tif (cfg.size == sensorFormat_.size)\n> > +\t\t\tstream = &data_->outStream_;\n> > +\t\telse\n> > +\t\t\tstream = &data_->vfStream_;\n> > +\n> > +\t\tif (availableStreams.find(stream) == availableStreams.end())\n> > +\t\t\tstream = *availableStreams.begin();\n> > +\n> > +\t\tLOG(IPU3, Debug)\n> > +\t\t\t<< \"Assigned '\" << stream->name_ << \"' to stream \" << i;\n> > +\n> > +\t\tbool scale = stream == &data_->vfStream_;\n> > +\t\tadjustStream(i, scale);\n> > +\n> > +\t\tif (cfg.pixelFormat != pixelFormat || cfg.size != size) {\n> > +\t\t\tLOG(IPU3, Debug)\n> > +\t\t\t\t<< \"Stream \" << i << \" configuration adjusted to \"\n> > +\t\t\t\t<< cfg.toString();\n> > +\t\t\tstatus = Adjusted;\n> > +\t\t}\n> > +\n> > +\t\tstreams_.push_back(stream);\n> > +\t\tavailableStreams.erase(stream);\n> > +\t}\n> > +\n> > +\treturn status;\n> > +}\n> > +\n> >  PipelineHandlerIPU3::PipelineHandlerIPU3(CameraManager *manager)\n> >  \t: PipelineHandler(manager), cio2MediaDev_(nullptr), imguMediaDev_(nullptr)\n> >  {\n> > @@ -211,12 +383,14 @@ CameraConfiguration *PipelineHandlerIPU3::generateConfiguration(Camera *camera,\n> >  \tconst StreamRoles &roles)\n> >  {\n> >  \tIPU3CameraData *data = cameraData(camera);\n> > -\tCameraConfiguration *config = new CameraConfiguration();\n> > +\tIPU3CameraConfiguration *config;\n> >  \tstd::set<IPU3Stream *> streams = {\n> >  \t\t&data->outStream_,\n> >  \t\t&data->vfStream_,\n> >  \t};\n> >\n> > +\tconfig = new IPU3CameraConfiguration(camera, data);\n> > +\n> >  \tfor (const StreamRole role : roles) {\n> >  \t\tStreamConfiguration cfg = {};\n> >  \t\tIPU3Stream *stream = nullptr;\n> > @@ -296,71 +470,25 @@ CameraConfiguration *PipelineHandlerIPU3::generateConfiguration(Camera *camera,\n> >\n> >  \t\tstreams.erase(stream);\n> >\n> > -\t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> > -\t\tcfg.bufferCount = IPU3_BUFFER_COUNT;\n> > -\n> >  \t\tconfig->addConfiguration(cfg);\n> >  \t}\n> >\n> > +\tconfig->validate();\n> > +\n> >  \treturn config;\n> >  }\n> >\n> > -int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n> > +int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *c)\n> >  {\n> > +\tIPU3CameraConfiguration *config =\n> > +\t\tstatic_cast<IPU3CameraConfiguration *>(c);\n> >  \tIPU3CameraData *data = cameraData(camera);\n> >  \tIPU3Stream *outStream = &data->outStream_;\n> >  \tIPU3Stream *vfStream = &data->vfStream_;\n> >  \tCIO2Device *cio2 = &data->cio2_;\n> >  \tImgUDevice *imgu = data->imgu_;\n> > -\tSize sensorSize = {};\n> >  \tint ret;\n> >\n> > -\toutStream->active_ = false;\n> > -\tvfStream->active_ = false;\n> > -\tfor (StreamConfiguration &cfg : *config) {\n> > -\t\t/*\n> > -\t\t * Pick a stream for the configuration entry.\n> > -\t\t * \\todo: This is a naive temporary implementation that will be\n> > -\t\t * reworked when implementing camera configuration validation.\n> > -\t\t */\n> > -\t\tIPU3Stream *stream = vfStream->active_ ? outStream : vfStream;\n> > -\n> > -\t\t/*\n> > -\t\t * Verify that the requested size respects the IPU3 alignment\n> > -\t\t * requirements (the image width shall be a multiple of 8\n> > -\t\t * pixels and its height a multiple of 4 pixels) and the camera\n> > -\t\t * maximum sizes.\n> > -\t\t *\n> > -\t\t * \\todo: Consider the BDS scaling factor requirements: \"the\n> > -\t\t * downscaling factor must be an integer value multiple of 1/32\"\n> > -\t\t */\n> > -\t\tif (cfg.size.width % 8 || cfg.size.height % 4) {\n> > -\t\t\tLOG(IPU3, Error)\n> > -\t\t\t\t<< \"Invalid stream size: bad alignment\";\n> > -\t\t\treturn -EINVAL;\n> > -\t\t}\n> > -\n> > -\t\tconst Size &resolution = cio2->sensor_->resolution();\n> > -\t\tif (cfg.size.width > resolution.width ||\n> > -\t\t    cfg.size.height > resolution.height) {\n> > -\t\t\tLOG(IPU3, Error)\n> > -\t\t\t\t<< \"Invalid stream size: larger than sensor resolution\";\n> > -\t\t\treturn -EINVAL;\n> > -\t\t}\n> > -\n> > -\t\t/*\n> > -\t\t * Collect the maximum width and height: IPU3 can downscale\n> > -\t\t * only.\n> > -\t\t */\n> > -\t\tif (cfg.size.width > sensorSize.width)\n> > -\t\t\tsensorSize.width = cfg.size.width;\n> > -\t\tif (cfg.size.height > sensorSize.height)\n> > -\t\t\tsensorSize.height = cfg.size.height;\n> > -\n> > -\t\tstream->active_ = true;\n> > -\t\tcfg.setStream(stream);\n> > -\t}\n> > -\n> >  \t/*\n> >  \t * \\todo: Enable links selectively based on the requested streams.\n> >  \t * As of now, enable all links unconditionally.\n> > @@ -373,6 +501,7 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n> >  \t * Pass the requested stream size to the CIO2 unit and get back the\n> >  \t * adjusted format to be propagated to the ImgU output devices.\n> >  \t */\n> > +\tconst Size &sensorSize = config->sensorFormat().size;\n> >  \tV4L2DeviceFormat cio2Format = {};\n> >  \tret = cio2->configure(sensorSize, &cio2Format);\n> >  \tif (ret)\n> > @@ -383,8 +512,22 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n> >  \t\treturn ret;\n> >\n> >  \t/* Apply the format to the configured streams output devices. */\n> > -\tfor (StreamConfiguration &cfg : *config) {\n> > -\t\tIPU3Stream *stream = static_cast<IPU3Stream *>(cfg.stream());\n> > +\toutStream->active_ = false;\n> > +\tvfStream->active_ = false;\n> > +\n> > +\tfor (unsigned int i = 0; i < config->size(); ++i) {\n> > +\t\t/*\n> > +\t\t * Use a const_cast<> here instead of storing a mutable stream\n> > +\t\t * pointer in the configuration to let the compiler catch\n> > +\t\t * unwanted modifications of camera data in the configuration\n> > +\t\t * validate() implementation.\n> > +\t\t */\n> > +\t\tIPU3Stream *stream = const_cast<IPU3Stream *>(config->streams()[i]);\n> > +\t\tStreamConfiguration &cfg = (*config)[i];\n> > +\n> > +\t\tstream->active_ = true;\n> > +\t\tcfg.setStream(stream);\n> > +\n> >  \t\tret = imgu->configureOutput(stream->device_, cfg);\n> >  \t\tif (ret)\n> >  \t\t\treturn ret;\n> > diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> > index a1a4f205b4aa..42944c64189b 100644\n> > --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> > +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> > @@ -5,6 +5,7 @@\n> >   * rkisp1.cpp - Pipeline handler for Rockchip ISP1\n> >   */\n> >\n> > +#include <algorithm>\n> >  #include <iomanip>\n> >  #include <memory>\n> >  #include <vector>\n> > @@ -45,6 +46,29 @@ public:\n> >  \tCameraSensor *sensor_;\n> >  };\n> >\n> > +class RkISP1CameraConfiguration : public CameraConfiguration\n> > +{\n> > +public:\n> > +\tRkISP1CameraConfiguration(Camera *camera, RkISP1CameraData *data);\n> > +\n> > +\tStatus validate() override;\n> > +\n> > +\tconst V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }\n> > +\n> > +private:\n> > +\tstatic constexpr unsigned int RKISP1_BUFFER_COUNT = 4;\n> > +\n> > +\t/*\n> > +\t * The RkISP1CameraData instance is guaranteed to be valid as long as the\n> > +\t * corresponding Camera instance is valid. In order to borrow a\n> > +\t * reference to the camera data, store a new reference to the camera.\n> > +\t */\n> > +\tstd::shared_ptr<Camera> camera_;\n> > +\tconst RkISP1CameraData *data_;\n> > +\n> > +\tV4L2SubdeviceFormat sensorFormat_;\n> > +};\n> > +\n> >  class PipelineHandlerRkISP1 : public PipelineHandler\n> >  {\n> >  public:\n> > @@ -68,8 +92,6 @@ public:\n> >  \tbool match(DeviceEnumerator *enumerator) override;\n> >\n> >  private:\n> > -\tstatic constexpr unsigned int RKISP1_BUFFER_COUNT = 4;\n> > -\n> >  \tRkISP1CameraData *cameraData(const Camera *camera)\n> >  \t{\n> >  \t\treturn static_cast<RkISP1CameraData *>(\n> > @@ -88,6 +110,95 @@ private:\n> >  \tCamera *activeCamera_;\n> >  };\n> >\n> > +RkISP1CameraConfiguration::RkISP1CameraConfiguration(Camera *camera,\n> > +\t\t\t\t\t\t     RkISP1CameraData *data)\n> > +\t: CameraConfiguration()\n> > +{\n> > +\tcamera_ = camera->shared_from_this();\n> > +\tdata_ = data;\n> > +}\n> > +\n> > +CameraConfiguration::Status RkISP1CameraConfiguration::validate()\n> > +{\n> > +\tstatic const std::array<unsigned int, 8> formats{\n> > +\t\tV4L2_PIX_FMT_YUYV,\n> > +\t\tV4L2_PIX_FMT_YVYU,\n> > +\t\tV4L2_PIX_FMT_VYUY,\n> > +\t\tV4L2_PIX_FMT_NV16,\n> > +\t\tV4L2_PIX_FMT_NV61,\n> > +\t\tV4L2_PIX_FMT_NV21,\n> > +\t\tV4L2_PIX_FMT_NV12,\n> > +\t\tV4L2_PIX_FMT_GREY,\n> > +\t};\n> > +\n> > +\tconst CameraSensor *sensor = data_->sensor_;\n> > +\tStatus status = Valid;\n> > +\n> > +\tif (config_.empty())\n> > +\t\treturn Invalid;\n> > +\n> > +\t/* Cap the number of entries to the available streams. */\n> > +\tif (config_.size() > 1) {\n> > +\t\tconfig_.resize(1);\n> > +\t\tstatus = Adjusted;\n> > +\t}\n> > +\n> > +\tStreamConfiguration &cfg = config_[0];\n> > +\n> > +\t/* Adjust the pixel format. */\n> > +\tif (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==\n> > +\t    formats.end()) {\n> > +\t\tLOG(RkISP1, Debug) << \"Adjusting format to NV12\";\n> > +\t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> > +\t\tstatus = Adjusted;\n> > +\t}\n> > +\n> > +\t/* Select the sensor format. */\n> > +\tsensorFormat_ = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR12_1X12,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG12_1X12,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG12_1X12,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB12_1X12,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SBGGR10_1X10,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG10_1X10,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG10_1X10,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB10_1X10,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SBGGR8_1X8,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG8_1X8,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG8_1X8,\n> > +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB8_1X8 },\n> > +\t\t\t\t\t  cfg.size);\n> > +\tif (!sensorFormat_.size.width || !sensorFormat_.size.height)\n> > +\t\tsensorFormat_.size = sensor->resolution();\n> > +\n> > +\t/*\n> > +\t * Provide a suitable default that matches the sensor aspect\n> > +\t * ratio and clamp the size to the hardware bounds.\n> > +\t *\n> > +\t * \\todo: Check the hardware alignment constraints.\n> > +\t */\n> > +\tconst Size size = cfg.size;\n> > +\n> > +\tif (!cfg.size.width || !cfg.size.height) {\n> > +\t\tcfg.size.width = 1280;\n> > +\t\tcfg.size.height = 1280 * sensorFormat_.size.height\n> > +\t\t\t\t/ sensorFormat_.size.width;\n> > +\t}\n> > +\n> > +\tcfg.size.width = std::max(32U, std::min(4416U, cfg.size.width));\n> > +\tcfg.size.height = std::max(16U, std::min(3312U, cfg.size.height));\n> > +\n> > +\tif (cfg.size != size) {\n> > +\t\tLOG(RkISP1, Debug)\n> > +\t\t\t<< \"Adjusting size from \" << size.toString()\n> > +\t\t\t<< \" to \" << cfg.size.toString();\n> > +\t\tstatus = Adjusted;\n> > +\t}\n> > +\n> > +\tcfg.bufferCount = RKISP1_BUFFER_COUNT;\n> > +\n> > +\treturn status;\n> > +}\n> > +\n> >  PipelineHandlerRkISP1::PipelineHandlerRkISP1(CameraManager *manager)\n> >  \t: PipelineHandler(manager), dphy_(nullptr), isp_(nullptr),\n> >  \t  video_(nullptr)\n> > @@ -109,37 +220,31 @@ CameraConfiguration *PipelineHandlerRkISP1::generateConfiguration(Camera *camera\n> >  \tconst StreamRoles &roles)\n> >  {\n> >  \tRkISP1CameraData *data = cameraData(camera);\n> > -\tCameraConfiguration *config = new CameraConfiguration();\n> > +\tCameraConfiguration *config = new RkISP1CameraConfiguration(camera, data);\n> >\n> >  \tif (!roles.empty()) {\n> >  \t\tStreamConfiguration cfg{};\n> >\n> >  \t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> >  \t\tcfg.size = data->sensor_->resolution();\n> > -\t\tcfg.bufferCount = RKISP1_BUFFER_COUNT;\n> >\n> >  \t\tconfig->addConfiguration(cfg);\n> >  \t}\n> >\n> > +\tconfig->validate();\n> > +\n> >  \treturn config;\n> >  }\n> >\n> > -int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *config)\n> > +int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)\n> >  {\n> > +\tRkISP1CameraConfiguration *config =\n> > +\t\tstatic_cast<RkISP1CameraConfiguration *>(c);\n> >  \tRkISP1CameraData *data = cameraData(camera);\n> >  \tStreamConfiguration &cfg = config->at(0);\n> >  \tCameraSensor *sensor = data->sensor_;\n> >  \tint ret;\n> >\n> > -\t/* Verify the configuration. */\n> > -\tconst Size &resolution = sensor->resolution();\n> > -\tif (cfg.size.width > resolution.width ||\n> > -\t    cfg.size.height > resolution.height) {\n> > -\t\tLOG(RkISP1, Error)\n> > -\t\t\t<< \"Invalid stream size: larger than sensor resolution\";\n> > -\t\treturn -EINVAL;\n> > -\t}\n> > -\n> >  \t/*\n> >  \t * Configure the sensor links: enable the link corresponding to this\n> >  \t * camera and disable all the other sensor links.\n> > @@ -167,21 +272,7 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *config\n> >  \t * Configure the format on the sensor output and propagate it through\n> >  \t * the pipeline.\n> >  \t */\n> > -\tV4L2SubdeviceFormat format;\n> > -\tformat = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR12_1X12,\n> > -\t\t\t\t     MEDIA_BUS_FMT_SGBRG12_1X12,\n> > -\t\t\t\t     MEDIA_BUS_FMT_SGRBG12_1X12,\n> > -\t\t\t\t     MEDIA_BUS_FMT_SRGGB12_1X12,\n> > -\t\t\t\t     MEDIA_BUS_FMT_SBGGR10_1X10,\n> > -\t\t\t\t     MEDIA_BUS_FMT_SGBRG10_1X10,\n> > -\t\t\t\t     MEDIA_BUS_FMT_SGRBG10_1X10,\n> > -\t\t\t\t     MEDIA_BUS_FMT_SRGGB10_1X10,\n> > -\t\t\t\t     MEDIA_BUS_FMT_SBGGR8_1X8,\n> > -\t\t\t\t     MEDIA_BUS_FMT_SGBRG8_1X8,\n> > -\t\t\t\t     MEDIA_BUS_FMT_SGRBG8_1X8,\n> > -\t\t\t\t     MEDIA_BUS_FMT_SRGGB8_1X8 },\n> > -\t\t\t\t   cfg.size);\n> > -\n> > +\tV4L2SubdeviceFormat format = config->sensorFormat();\n> >  \tLOG(RkISP1, Debug) << \"Configuring sensor with \" << format.toString();\n> >\n> >  \tret = sensor->setFormat(&format);\n> > diff --git a/src/libcamera/pipeline/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo.cpp\n> > index 8254e1fdac1e..f7ffeb439cf3 100644\n> > --- a/src/libcamera/pipeline/uvcvideo.cpp\n> > +++ b/src/libcamera/pipeline/uvcvideo.cpp\n> > @@ -39,6 +39,14 @@ public:\n> >  \tStream stream_;\n> >  };\n> >\n> > +class UVCCameraConfiguration : public CameraConfiguration\n> > +{\n> > +public:\n> > +\tUVCCameraConfiguration();\n> > +\n> > +\tStatus validate() override;\n> > +};\n> > +\n> >  class PipelineHandlerUVC : public PipelineHandler\n> >  {\n> >  public:\n> > @@ -68,6 +76,45 @@ private:\n> >  \t}\n> >  };\n> >\n> > +UVCCameraConfiguration::UVCCameraConfiguration()\n> > +\t: CameraConfiguration()\n> > +{\n> > +}\n> > +\n> > +CameraConfiguration::Status UVCCameraConfiguration::validate()\n> > +{\n> > +\tStatus status = Valid;\n> > +\n> > +\tif (config_.empty())\n> > +\t\treturn Invalid;\n> > +\n> > +\t/* Cap the number of entries to the available streams. */\n> > +\tif (config_.size() > 1) {\n> > +\t\tconfig_.resize(1);\n> > +\t\tstatus = Adjusted;\n> > +\t}\n> > +\n> > +\tStreamConfiguration &cfg = config_[0];\n> > +\n> > +\t/* \\todo: Validate the configuration against the device capabilities. */\n> > +\tconst unsigned int pixelFormat = cfg.pixelFormat;\n> > +\tconst Size size = cfg.size;\n> > +\n> > +\tcfg.pixelFormat = V4L2_PIX_FMT_YUYV;\n> > +\tcfg.size = { 640, 480 };\n> > +\n> > +\tif (cfg.pixelFormat != pixelFormat || cfg.size != size) {\n> > +\t\tLOG(UVC, Debug)\n> > +\t\t\t<< \"Adjusting configuration from \" << cfg.toString()\n> > +\t\t\t<< \" to \" << cfg.size.toString() << \"-YUYV\";\n> > +\t\tstatus = Adjusted;\n> > +\t}\n> > +\n> > +\tcfg.bufferCount = 4;\n> > +\n> > +\treturn status;\n> > +}\n> > +\n> >  PipelineHandlerUVC::PipelineHandlerUVC(CameraManager *manager)\n> >  \t: PipelineHandler(manager)\n> >  {\n> > @@ -76,10 +123,10 @@ PipelineHandlerUVC::PipelineHandlerUVC(CameraManager *manager)\n> >  CameraConfiguration *PipelineHandlerUVC::generateConfiguration(Camera *camera,\n> >  \tconst StreamRoles &roles)\n> >  {\n> > -\tCameraConfiguration *config = new CameraConfiguration();\n> > +\tCameraConfiguration *config = new UVCCameraConfiguration();\n> >\n> >  \tif (!roles.empty()) {\n> > -\t\tStreamConfiguration cfg;\n> > +\t\tStreamConfiguration cfg{};\n> >\n> >  \t\tcfg.pixelFormat = V4L2_PIX_FMT_YUYV;\n> >  \t\tcfg.size = { 640, 480 };\n> > @@ -88,6 +135,8 @@ CameraConfiguration *PipelineHandlerUVC::generateConfiguration(Camera *camera,\n> >  \t\tconfig->addConfiguration(cfg);\n> >  \t}\n> >\n> > +\tconfig->validate();\n> > +\n> >  \treturn config;\n> >  }\n> >\n> > diff --git a/src/libcamera/pipeline/vimc.cpp b/src/libcamera/pipeline/vimc.cpp\n> > index 2bf85d0a0b32..2a61d2893e3a 100644\n> > --- a/src/libcamera/pipeline/vimc.cpp\n> > +++ b/src/libcamera/pipeline/vimc.cpp\n> > @@ -5,6 +5,8 @@\n> >   * vimc.cpp - Pipeline handler for the vimc device\n> >   */\n> >\n> > +#include <algorithm>\n> > +\n> >  #include <libcamera/camera.h>\n> >  #include <libcamera/request.h>\n> >  #include <libcamera/stream.h>\n> > @@ -39,6 +41,14 @@ public:\n> >  \tStream stream_;\n> >  };\n> >\n> > +class VimcCameraConfiguration : public CameraConfiguration\n> > +{\n> > +public:\n> > +\tVimcCameraConfiguration();\n> > +\n> > +\tStatus validate() override;\n> > +};\n> > +\n> >  class PipelineHandlerVimc : public PipelineHandler\n> >  {\n> >  public:\n> > @@ -68,6 +78,57 @@ private:\n> >  \t}\n> >  };\n> >\n> > +VimcCameraConfiguration::VimcCameraConfiguration()\n> > +\t: CameraConfiguration()\n> > +{\n> > +}\n> > +\n> > +CameraConfiguration::Status VimcCameraConfiguration::validate()\n> > +{\n> > +\tstatic const std::array<unsigned int, 3> formats{\n> > +\t\tV4L2_PIX_FMT_BGR24,\n> > +\t\tV4L2_PIX_FMT_RGB24,\n> > +\t\tV4L2_PIX_FMT_ARGB32,\n> > +\t};\n> > +\n> > +\tStatus status = Valid;\n> > +\n> > +\tif (config_.empty())\n> > +\t\treturn Invalid;\n> > +\n> > +\t/* Cap the number of entries to the available streams. */\n> > +\tif (config_.size() > 1) {\n> > +\t\tconfig_.resize(1);\n> > +\t\tstatus = Adjusted;\n> > +\t}\n> > +\n> > +\tStreamConfiguration &cfg = config_[0];\n> > +\n> > +\t/* Adjust the pixel format. */\n> > +\tif (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==\n> > +\t    formats.end()) {\n> > +\t\tLOG(VIMC, Debug) << \"Adjusting format to RGB24\";\n> > +\t\tcfg.pixelFormat = V4L2_PIX_FMT_RGB24;\n> > +\t\tstatus = Adjusted;\n> > +\t}\n> > +\n> > +\t/* Clamp the size based on the device limits. */\n> > +\tconst Size size = cfg.size;\n> > +\n> > +\tcfg.size.width = std::max(16U, std::min(4096U, cfg.size.width));\n> > +\tcfg.size.height = std::max(16U, std::min(2160U, cfg.size.height));\n> > +\n> > +\tif (cfg.size != size) {\n> > +\t\tLOG(VIMC, Debug)\n> > +\t\t\t<< \"Adjusting size to \" << cfg.size.toString();\n> > +\t\tstatus = Adjusted;\n> > +\t}\n> > +\n> > +\tcfg.bufferCount = 4;\n> > +\n> > +\treturn status;\n> > +}\n> > +\n> >  PipelineHandlerVimc::PipelineHandlerVimc(CameraManager *manager)\n> >  \t: PipelineHandler(manager)\n> >  {\n> > @@ -76,10 +137,10 @@ PipelineHandlerVimc::PipelineHandlerVimc(CameraManager *manager)\n> >  CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera,\n> >  \tconst StreamRoles &roles)\n> >  {\n> > -\tCameraConfiguration *config = new CameraConfiguration();\n> > +\tCameraConfiguration *config = new VimcCameraConfiguration();\n> >\n> >  \tif (!roles.empty()) {\n> > -\t\tStreamConfiguration cfg;\n> > +\t\tStreamConfiguration cfg{};\n> >\n> >  \t\tcfg.pixelFormat = V4L2_PIX_FMT_RGB24;\n> >  \t\tcfg.size = { 640, 480 };\n> > @@ -88,6 +149,8 @@ CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera,\n> >  \t\tconfig->addConfiguration(cfg);\n> >  \t}\n> >\n> > +\tconfig->validate();\n> > +\n> >  \treturn config;\n> >  }\n> >\n> > diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp\n> > index de46e98880a2..dd56907d817e 100644\n> > --- a/src/libcamera/pipeline_handler.cpp\n> > +++ b/src/libcamera/pipeline_handler.cpp\n> > @@ -248,17 +248,14 @@ void PipelineHandler::unlock()\n> >   * is the Camera class which will receive configuration to apply from the\n> >   * application.\n> >   *\n> > - * Each pipeline handler implementation is responsible for validating\n> > - * that the configuration requested in \\a config can be achieved\n> > - * exactly. Any difference in pixel format, frame size or any other\n> > - * parameter shall result in the -EINVAL error being returned, and no\n> > - * change in configuration being applied to the pipeline. If\n> > - * configuration of a subset of the streams can't be satisfied, the\n> > - * whole configuration is considered invalid.\n> > + * The configuration is guaranteed to have been validated with\n> > + * CameraConfiguration::valid(). The pipeline handler implementation shall not\n> > + * perform further validation and may rely on any custom field stored in its\n> > + * custom CameraConfiguration derived class.\n> >   *\n> > - * Once the configuration is validated and the camera configured, the pipeline\n> > - * handler shall associate a Stream instance to each StreamConfiguration entry\n> > - * in the CameraConfiguration with the StreamConfiguration::setStream() method.\n> > + * When configuring the camera the pipeline handler shall associate a Stream\n> > + * instance to each StreamConfiguration entry in the CameraConfiguration using\n> > + * the StreamConfiguration::setStream() method.\n> >   *\n> >   * \\return 0 on success or a negative error code otherwise\n> >   */\n> > diff --git a/test/camera/capture.cpp b/test/camera/capture.cpp\n> > index 137aa649a505..f122f79bb1ec 100644\n> > --- a/test/camera/capture.cpp\n> > +++ b/test/camera/capture.cpp\n> > @@ -45,7 +45,7 @@ protected:\n> >  \t\tCameraTest::init();\n> >\n> >  \t\tconfig_ = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> > -\t\tif (!config_) {\n> > +\t\tif (!config_ || config_->size() != 1) {\n> >  \t\t\tcout << \"Failed to generate default configuration\" << endl;\n> >  \t\t\tCameraTest::cleanup();\n> >  \t\t\treturn TestFail;\n> > @@ -58,11 +58,6 @@ protected:\n> >  \t{\n> >  \t\tStreamConfiguration &cfg = config_->at(0);\n> >\n> > -\t\tif (!config_->isValid()) {\n> > -\t\t\tcout << \"Failed to read default configuration\" << endl;\n> > -\t\t\treturn TestFail;\n> > -\t\t}\n> > -\n> >  \t\tif (camera_->acquire()) {\n> >  \t\t\tcout << \"Failed to acquire the camera\" << endl;\n> >  \t\t\treturn TestFail;\n> > diff --git a/test/camera/configuration_default.cpp b/test/camera/configuration_default.cpp\n> > index d5cefc1127c9..81055da1d513 100644\n> > --- a/test/camera/configuration_default.cpp\n> > +++ b/test/camera/configuration_default.cpp\n> > @@ -22,7 +22,7 @@ protected:\n> >\n> >  \t\t/* Test asking for configuration for a video stream. */\n> >  \t\tconfig = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> > -\t\tif (!config || !config->isValid()) {\n> > +\t\tif (!config || config->size() != 1) {\n> >  \t\t\tcout << \"Default configuration invalid\" << endl;\n> >  \t\t\tdelete config;\n> >  \t\t\treturn TestFail;\n> > @@ -35,7 +35,7 @@ protected:\n> >  \t\t * stream roles returns an empty camera configuration.\n> >  \t\t */\n> >  \t\tconfig = camera_->generateConfiguration({});\n> > -\t\tif (!config || config->isValid()) {\n> > +\t\tif (!config || config->size() != 0) {\n> >  \t\t\tcout << \"Failed to retrieve configuration for empty roles list\"\n> >  \t\t\t     << endl;\n> >  \t\t\tdelete config;\n> > diff --git a/test/camera/configuration_set.cpp b/test/camera/configuration_set.cpp\n> > index 23c611a93355..a4e2da16a88b 100644\n> > --- a/test/camera/configuration_set.cpp\n> > +++ b/test/camera/configuration_set.cpp\n> > @@ -21,7 +21,7 @@ protected:\n> >  \t\tCameraTest::init();\n> >\n> >  \t\tconfig_ = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> > -\t\tif (!config_) {\n> > +\t\tif (!config_ || config_->size() != 1) {\n> >  \t\t\tcout << \"Failed to generate default configuration\" << endl;\n> >  \t\t\tCameraTest::cleanup();\n> >  \t\t\treturn TestFail;\n> > @@ -34,11 +34,6 @@ protected:\n> >  \t{\n> >  \t\tStreamConfiguration &cfg = config_->at(0);\n> >\n> > -\t\tif (!config_->isValid()) {\n> > -\t\t\tcout << \"Failed to read default configuration\" << endl;\n> > -\t\t\treturn TestFail;\n> > -\t\t}\n> > -\n> >  \t\tif (camera_->acquire()) {\n> >  \t\t\tcout << \"Failed to acquire the camera\" << endl;\n> >  \t\t\treturn TestFail;\n> > --\n> > Regards,\n> >\n> > Laurent Pinchart\n> >\n> > _______________________________________________\n> > libcamera-devel mailing list\n> > libcamera-devel@lists.libcamera.org\n> > https://lists.libcamera.org/listinfo/libcamera-devel","headers":{"Return-Path":"<laurent.pinchart@ideasonboard.com>","Received":["from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 8B92D60C27\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 21 May 2019 14:51:34 +0200 (CEST)","from pendragon.ideasonboard.com\n\t(dfj612yhrgyx302h3jwwy-3.rev.dnainternet.fi\n\t[IPv6:2001:14ba:21f5:5b00:ce28:277f:58d7:3ca4])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id F1F8852C;\n\tTue, 21 May 2019 14:51:33 +0200 (CEST)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1558443094;\n\tbh=kCjg2DBBl25yHbx80M/z1hiYGtHjeQOveXxbNiPdC7E=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=hw2UtL8EfoML5SS+lTi3CPaj4HHOMHvGLl+5UV5xPoGkf/qztNwY+9fkuRyQmvd06\n\tsXOgd6UBOaSu/iyQKda4XvjQGvSXEgUS2ZgZRLCaMCEeRAQFj0vBXhZyl1QoImns1Z\n\tmMn7NQ5e+gses3rg0jzPp6fTvXQvUgTm3zYH7Hps=","Date":"Tue, 21 May 2019 15:51:09 +0300","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Jacopo Mondi <jacopo@jmondi.org>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20190521125109.GD5674@pendragon.ideasonboard.com>","References":"<20190519150047.12444-1-laurent.pinchart@ideasonboard.com>\n\t<20190519150047.12444-7-laurent.pinchart@ideasonboard.com>\n\t<20190521084950.z5ahk3fiptltok7i@uno.localdomain>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20190521084950.z5ahk3fiptltok7i@uno.localdomain>","User-Agent":"Mutt/1.10.1 (2018-07-13)","Subject":"Re: [libcamera-devel] [PATCH v2 6/6] libcamera: camera: Add a\n\tvalidation API to the CameraConfiguration class","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.23","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Tue, 21 May 2019 12:51:34 -0000"}},{"id":1653,"web_url":"https://patchwork.libcamera.org/comment/1653/","msgid":"<20190521214329.lbtggxmqzc5mi2rn@uno.localdomain>","date":"2019-05-21T21:43:29","subject":"Re: [libcamera-devel] [PATCH v2 6/6] libcamera: camera: Add a\n\tvalidation API to the CameraConfiguration class","submitter":{"id":3,"url":"https://patchwork.libcamera.org/api/people/3/","name":"Jacopo Mondi","email":"jacopo@jmondi.org"},"content":"Hi Laurent,\n\nOn Tue, May 21, 2019 at 03:49:55PM +0300, Laurent Pinchart wrote:\n> Hi Jacopo,\n>\n> On Tue, May 21, 2019 at 12:05:06AM +0200, Jacopo Mondi wrote:\n> > Hi Laurent,\n> >\n> > In order to validate if the API is dump-proof enough, I'm starting\n> > the review from the last patch, so I might be missing some pieces\n> > here and there...\n> >\n> > On Sun, May 19, 2019 at 06:00:47PM +0300, Laurent Pinchart wrote:\n> > > The CameraConfiguration class implements a simple storage of\n> > > StreamConfiguration with internal validation limited to verifying that\n> > > the stream configurations are not empty. Extend this mechanism by\n> > > implementing a smart validate() method backed by pipeline handlers.\n> > >\n> > > This new mechanism changes the semantics of the camera configuration.\n> >\n> > s/semantics/semantic ?\n>\n> I think you're right, I'll fix that.\n>\n> > > The Camera::generateConfiguration() operation still generates a default\n> > > configuration based on roles, but now also supports generating empty\n> > > configurations to be filled by applications. Applications can inspect\n> > > the configuration, optionally modify it, and validate it. The validation\n> > > implements \"try\" semantics and adjusts invalid configurations instead of\n> > > rejecting them completely. Applications then decide whether to accept\n> > > the modified configuration, or try again with a different set of\n> > > parameters. Once the configuration is valid, it is passed to\n> > > Camera::configure(), and pipeline handlers are guaranteed that the\n> > > configuration they receive is valid.\n> > >\n> > > A reference to the Camera may need to be stored in the\n> > > CameraConfiguration derived classes in order to access it from their\n> > > validate() implementation. This must be stored as a std::shared_ptr<> as\n> > > the CameraConfiguration instances belong to applications. In order to\n> > > make this possible, make the Camera class inherit from\n> > > std::shared_from_this<>.\n> >\n> > If I got this right we'll have a\n> >\n> > CameraConfiguration::validate() and a\n> > Camera.configure()\n> >\n> > and CameraConfiguration has to keep a reference to the Camera to\n> > access it.\n> >\n> > Can we provide a Camera::validate(StreamConfiguration *) instead?\n> > We could handle the shared reference in the Camera and not let\n> > CameraConfiguration subclasses have to deal with it in this way?\n>\n> I assume you meant a Camera::validate(CameraConfiguration *), as we want\n> to validate the whole configuration, not stream per stream (the idea of\n> this series is to support cross-stream validation constraints).\n\nYes indeed, sorry.\n\n>\n> First of all, the method would need to be called\n> Camera::validateConfiguration(). This would need to be delegated to\n\nwell, camera->validate(config); is quite self-explanatory to me :)\n\n> PipelineHandler::validateConfiguration(Camera *, CameraConfiguration *).\n\nwhy so?\n\nCan't\nStatus Camera::validate(CameraConfiguration *config)\n{\n        return config->validate();\n}\n\nValidate will stay pure virtual, and the derived classes\nimplementation will be called, as it already happens at\nCamera::configure() time.\n\nAm I missing something obvious here?\n\n> The pipeline handler would then need to cast the camera configuration to\n> its custom configuration derived class (opening the door to applications\n> calling Camera::validateConfiguration() with a configuration for the\n> wrong camera, but that's already possible with Camera::configure() I\n> supposed). We could try that, but I think it would make the API less\n> clean for applications, for little benefits in my opinion (but we may\n> have a different view of the benefits :-)).\n>\n> > >\n> > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> > > ---\n> > >  include/libcamera/camera.h               |  17 +-\n> > >  src/cam/main.cpp                         |   2 +-\n> > >  src/libcamera/camera.cpp                 |  80 +++++--\n> > >  src/libcamera/pipeline/ipu3/ipu3.cpp     | 255 ++++++++++++++++++-----\n> > >  src/libcamera/pipeline/rkisp1/rkisp1.cpp | 149 ++++++++++---\n> > >  src/libcamera/pipeline/uvcvideo.cpp      |  53 ++++-\n> > >  src/libcamera/pipeline/vimc.cpp          |  67 +++++-\n> > >  src/libcamera/pipeline_handler.cpp       |  17 +-\n> > >  test/camera/capture.cpp                  |   7 +-\n> > >  test/camera/configuration_default.cpp    |   4 +-\n> > >  test/camera/configuration_set.cpp        |   7 +-\n> > >  11 files changed, 516 insertions(+), 142 deletions(-)\n> > >\n> > > diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> > > index 144133c5de9f..8c0049a1dd94 100644\n> > > --- a/include/libcamera/camera.h\n> > > +++ b/include/libcamera/camera.h\n> > > @@ -25,14 +25,19 @@ class Request;\n> > >  class CameraConfiguration\n> > >  {\n> > >  public:\n> > > +\tenum Status {\n> > > +\t\tValid,\n> > > +\t\tAdjusted,\n> > > +\t\tInvalid,\n> > > +\t};\n> > > +\n> > >  \tusing iterator = std::vector<StreamConfiguration>::iterator;\n> > >  \tusing const_iterator = std::vector<StreamConfiguration>::const_iterator;\n> > >\n> > > -\tCameraConfiguration();\n> > > +\tvirtual ~CameraConfiguration();\n> > >\n> > >  \tvoid addConfiguration(const StreamConfiguration &cfg);\n> > > -\n> > > -\tbool isValid() const;\n> > > +\tvirtual Status validate() = 0;\n> > >\n> > >  \tStreamConfiguration &at(unsigned int index);\n> > >  \tconst StreamConfiguration &at(unsigned int index) const;\n> > > @@ -53,11 +58,13 @@ public:\n> > >  \tbool empty() const;\n> > >  \tstd::size_t size() const;\n> > >\n> > > -private:\n> > > +protected:\n> > > +\tCameraConfiguration();\n> > > +\n> > >  \tstd::vector<StreamConfiguration> config_;\n> > >  };\n> > >\n> > > -class Camera final\n> > > +class Camera final : public std::enable_shared_from_this<Camera>\n> > >  {\n> > >  public:\n> > >  \tstatic std::shared_ptr<Camera> create(PipelineHandler *pipe,\n> > > diff --git a/src/cam/main.cpp b/src/cam/main.cpp\n> > > index 7550ae4f3428..23da5c687d89 100644\n> > > --- a/src/cam/main.cpp\n> > > +++ b/src/cam/main.cpp\n> > > @@ -116,7 +116,7 @@ static CameraConfiguration *prepareCameraConfig()\n> > >  \t}\n> > >\n> > >  \tCameraConfiguration *config = camera->generateConfiguration(roles);\n> > > -\tif (!config || !config->isValid()) {\n> > > +\tif (!config || config->size() != roles.size()) {\n> > >  \t\tstd::cerr << \"Failed to get default stream configuration\"\n> > >  \t\t\t  << std::endl;\n> > >  \t\tdelete config;\n> > > diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> > > index 0e80691ed862..9da5d4f613f4 100644\n> > > --- a/src/libcamera/camera.cpp\n> > > +++ b/src/libcamera/camera.cpp\n> > > @@ -52,6 +52,28 @@ LOG_DECLARE_CATEGORY(Camera)\n> > >   * operator[](int) returns a reference to the StreamConfiguration based on its\n> > >   * insertion index. Accessing a stream configuration with an invalid index\n> > >   * results in undefined behaviour.\n> > > + *\n> > > + * CameraConfiguration instances are retrieved from the camera with\n> > > + * Camera::generateConfiguration(). Applications may then inspect the\n> > > + * configuration, modify it, and possibly add new stream configuration entries\n> > > + * with addConfiguration(). Once the camera configuration satisfies the\n> > > + * application, it shall be validated by a call to validate(). The validation\n> > > + * implements \"try\" semantics: it adjusts invalid configurations to the closest\n> > > + * achievable parameters instead of rejecting them completely. Applications\n> > > + * then decide whether to accept the modified configuration, or try again with\n> > > + * a different set of parameters. Once the configuration is valid, it is passed\n> > > + * to Camera::configure().\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\enum CameraConfiguration::Status\n> > > + * \\brief Validity of a camera configuration\n> > > + * \\var CameraConfiguration::Valid\n> > > + * The configuration is fully valid\n> > > + * \\var CameraConfiguration::Adjusted\n> > > + * The configuration has been adjusted to a valid configuration\n> > > + * \\var CameraConfiguration::Invalid\n> > > + * The configuration is invalid and can't be adjusted automatically\n> > >   */\n> > >\n> > >  /**\n> > > @@ -73,6 +95,10 @@ CameraConfiguration::CameraConfiguration()\n> > >  {\n> > >  }\n> > >\n> > > +CameraConfiguration::~CameraConfiguration()\n> > > +{\n> > > +}\n> > > +\n> > >  /**\n> > >   * \\brief Add a stream configuration to the camera configuration\n> > >   * \\param[in] cfg The stream configuration\n> > > @@ -83,27 +109,31 @@ void CameraConfiguration::addConfiguration(const StreamConfiguration &cfg)\n> > >  }\n> > >\n> > >  /**\n> > > - * \\brief Check if the camera configuration is valid\n> > > + * \\fn CameraConfiguration::validate()\n> > > + * \\brief Validate and possibly adjust the camera configuration\n> > >   *\n> > > - * A camera configuration is deemed to be valid if it contains at least one\n> > > - * stream configuration and all stream configurations contain valid information.\n> > > - * Stream configurations are deemed to be valid if all fields are none zero.\n> > > + * This method adjusts the camera configuration to the closest valid\n> > > + * configuration and returns the validation status.\n> > >   *\n> > > - * \\return True if the configuration is valid\n> > > + * \\todo: Define exactly when to return each status code. Should stream\n> > > + * parameters set to 0 by the caller be adjusted without returning Adjusted ?\n> > > + * This would potentially be useful for applications but would get in the way\n> > > + * in Camera::configure(). Do we need an extra status code to signal this ?\n> >\n> > I'm not sure I did get why it gets in the way of configure()\n>\n> Because Camera::configure() calls validate() and returns an error if the\n> status is Adjusted or Invalid, as the application is responsible for\n> passing a fully valid configuration to Camera::configure().\n>\n> > > + *\n> > > + * \\todo: Handle validation of buffers count when refactoring the buffers API.\n> > > + *\n> > > + * \\return A CameraConfiguration::Status value that describes the validation\n> > > + * status.\n> > > + * \\retval CameraConfiguration::Invalid The configuration is invalid and can't\n> > > + * be adjusted. This may only occur in extreme cases such as when the\n> > > + * configuration is empty.\n> >\n> > nit: instead of describing how the returned configuration looks like,\n> > do you have an example of application provided parameters which might\n> > trigger and invalid use case ?\n>\n> Not at the moment, no. I have no other case in mind, but we may find\n> some in the future.\n>\n> > > + * \\retval CameraConfigutation::Adjusted The configuration has been adjusted\n> > > + * and is now valid. Parameters may have changed for any stream, and stream\n> > > + * configurations may have been removed. The caller shall check the\n> > > + * configuration carefully.\n> > > + * \\retval CameraConfiguration::Valid The configuration was already valid and\n> > > + * hasn't been adjusted.\n> > >   */\n> > > -bool CameraConfiguration::isValid() const\n> > > -{\n> > > -\tif (empty())\n> > > -\t\treturn false;\n> > > -\n> > > -\tfor (const StreamConfiguration &cfg : config_) {\n> > > -\t\tif (cfg.size.width == 0 || cfg.size.height == 0 ||\n> > > -\t\t    cfg.pixelFormat == 0 || cfg.bufferCount == 0)\n> > > -\t\t\treturn false;\n> > > -\t}\n> > > -\n> > > -\treturn true;\n> > > -}\n> > >\n> > >  /**\n> > >   * \\brief Retrieve a reference to a stream configuration\n> > > @@ -218,6 +248,11 @@ std::size_t CameraConfiguration::size() const\n> > >  \treturn config_.size();\n> > >  }\n> > >\n> > > +/**\n> > > + * \\var CameraConfiguration::config_\n> > > + * \\brief The vector of stream configurations\n> > > + */\n> > > +\n> > >  /**\n> > >   * \\class Camera\n> > >   * \\brief Camera device\n> > > @@ -575,10 +610,9 @@ CameraConfiguration *Camera::generateConfiguration(const StreamRoles &roles)\n> > >   * The caller specifies which streams are to be involved and their configuration\n> > >   * by populating \\a config.\n> > >   *\n> > > - * The easiest way to populate the array of config is to fetch an initial\n> > > - * configuration from the camera with generateConfiguration() and then change\n> > > - * the parameters to fit the caller's need and once all the streams parameters\n> > > - * are configured hand that over to configure() to actually setup the camera.\n> > > + * The configuration is created by generateConfiguration(), and adjusted by the\n> > > + * caller with CameraConfiguration::validate(). This method only accepts fully\n> > > + * valid configurations and returns an error if \\a config is not valid.\n> > >   *\n> > >   * Exclusive access to the camera shall be ensured by a call to acquire() prior\n> > >   * to calling this function, otherwise an -EACCES error will be returned.\n> > > @@ -603,7 +637,7 @@ int Camera::configure(CameraConfiguration *config)\n> > >  \tif (!stateBetween(CameraAcquired, CameraConfigured))\n> > >  \t\treturn -EACCES;\n> > >\n> > > -\tif (!config->isValid()) {\n> > > +\tif (config->validate() != CameraConfiguration::Valid) {\n\nHave you considered the idea of using flags for the\nCameraConfiguration status?\n\nSo that here we could\n\n        Status status = config->status();\n        if (status == CameraConfiguration::Initialized)\n                status = config->validate();\n\n        if (status != CameraConfiguration::Valid)\n  \t\tLOG(Camera, Error)\n  \t\t\t<< \"Can't configure camera with invalid configuration\";\n  \t\treturn -EINVAL;\n        }\n\nand make sure we call validate() only if required ?\n\nAlso, could this help with the idea of initalizing parameters set to 0\nyou mentioned in a \\todo in a previous patch? If not, this could be\ndone later.\n\n> > > diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp\n>\n\n[snip]\n\n> > > +\t\tconst unsigned int pixelFormat = cfg.pixelFormat;\n> > > +\t\tconst Size size = cfg.size;\n> > > +\t\tconst IPU3Stream *stream;\n> > > +\n> > > +\t\tif (cfg.size == sensorFormat_.size)\n> >\n> > (here, more precisely)\n> >\n> > I'm not sure about this. I always seen the IPU3 used with maximum size\n> > (for ov5670) as 2560x1920 while the sensor resolution is actually a\n> > bit larger 2592x1944. Same for the back ov13858 camera. I never had the\n> > full sensor resolution working properly and I assumed it was related to\n> > some IPU3 alignement or constraints I didn't know about. Also, the\n> > Intel provided xml configuration file does limit the maximum\n> > available resolution to 2560x1920, but that could be for other reasons\n> > maybe... Have you tested capture at sensor resolution sizes?\n>\n> Not yet. I'm 100% confident that this code isn't good enough :-) What\n> we're missing is documentation from Intel, and then I'll fix it.\n>\n\nFurthermore, this would use the output stream only if applications\nask for 2592x1944, which is quite unusual as a resolution for an\napplication that's willing to display an image.\n\nThe previous logic regarding stream assignment was based on the\nrequested stream roles at streamConfiguration() time (now\ngenerateConfiguration()). Here it is only based on sizes, and it assumes\nthe capture video node 'output' size should always be used at full\nsensor resolution, otherwise we scale, and this prevents an\napplication to ask for a stream from the 'output' node (whose IPU3\npipe might be set in 'video_mode' maybe, even if I'm still not sure what\nthat parameter does, maybe video stabilization?) at a resolution <\nthan the sensor's one. Should we cache the roles assigned to a\nconfiguration at generateConfiguration() time in StreamConfiguration\nto be used as hint here?\n\n> > > +\t\t\tstream = &data_->outStream_;\n\n[snip]\n\n> >\n> > If the idea of providing a Camera::validate(CameraConfiguration *) has\n> > any ground, we should then have a private CameraConfiguration::validate()\n> > operation accessible by the Camera class through a friend declarator,\n> > so that pipeline handlers implementation cannot call it, restricting\n> > that method to the application facing API only. The\n>\n> That's not very neat, as it would polute the application-facing headers\n> with friends :-( I think we should try to remove them instead.\n\nCamera friends of CameraConfiguration does not sound so alien to me\nand $(git grep friend include/ | wc -l) returns 13 already :)\n\n>\n> > CameraConfiguration subclasses' validate() implementation could then\n> > call into the same operation that pipeline handler would use here at\n> > generateConfiguration() time as you did here, if desirable.\n> >\n> > Also, again if Camera::validate(CameraConfiguration *) makes any\n> > sense, we might want to group basic validations, if any. So we could\n> > make Camera::validate() call into the private CameraConfiguration::validate()\n> > operation, which performs basic operations, and calls into a protected\n> > virtual PipelineHandler::__validate() (or whatever name is more\n> > appropriate)\n> >\n> > Would this be doable?\n>\n> If you manage to find a good API that I like, sure :-) What you describe\n> here make the API more cumbersome in my opinion.\n\nI'll repropose it here as a discussion point, but won't block your v3\nif it has no traction, but I still think a\n\n        CameraConfiguration *config = camera->generateConfigurations(roles);\n        if (!config)\n                return -EINVAL;\n\n        ret = camera->validate(config);\n        if (ret)\n                return ret;\n\n        ret = camera->apply(config);\n        if (ret)\n                return ret;\n\nit's slightly nicer :)\n\nPros are that we could centralize validation of common config\nparameters in Camera::validate() and that camera could pass itself as\na shared pointer to a new\nCameraConfiguration::validate(shared_ptr<Camera *> camera) so that we\ndon't need to shared_from_this() in pipeline handlers, even if at the\nmoment I don't see the camera reference being used in any validate()\nimplementation.\n\n>\n> > >  \treturn config;\n> > >  }\n> > >\n> > > -int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n> > > +int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *c)\n> > >  {\n> > > +\tIPU3CameraConfiguration *config =\n> > > +\t\tstatic_cast<IPU3CameraConfiguration *>(c);\n> > >  \tIPU3CameraData *data = cameraData(camera);\n> > >  \tIPU3Stream *outStream = &data->outStream_;\n> > >  \tIPU3Stream *vfStream = &data->vfStream_;\n> > >  \tCIO2Device *cio2 = &data->cio2_;\n> > >  \tImgUDevice *imgu = data->imgu_;\n> > > -\tSize sensorSize = {};\n> > >  \tint ret;\n> > >\n> > > -\toutStream->active_ = false;\n> > > -\tvfStream->active_ = false;\n> > > -\tfor (StreamConfiguration &cfg : *config) {\n> > > -\t\t/*\n> > > -\t\t * Pick a stream for the configuration entry.\n> > > -\t\t * \\todo: This is a naive temporary implementation that will be\n> > > -\t\t * reworked when implementing camera configuration validation.\n> > > -\t\t */\n> > > -\t\tIPU3Stream *stream = vfStream->active_ ? outStream : vfStream;\n> > > -\n> > > -\t\t/*\n> > > -\t\t * Verify that the requested size respects the IPU3 alignment\n> > > -\t\t * requirements (the image width shall be a multiple of 8\n> > > -\t\t * pixels and its height a multiple of 4 pixels) and the camera\n> > > -\t\t * maximum sizes.\n> > > -\t\t *\n> > > -\t\t * \\todo: Consider the BDS scaling factor requirements: \"the\n> > > -\t\t * downscaling factor must be an integer value multiple of 1/32\"\n> > > -\t\t */\n> > > -\t\tif (cfg.size.width % 8 || cfg.size.height % 4) {\n> > > -\t\t\tLOG(IPU3, Error)\n> > > -\t\t\t\t<< \"Invalid stream size: bad alignment\";\n> > > -\t\t\treturn -EINVAL;\n> > > -\t\t}\n> > > -\n> > > -\t\tconst Size &resolution = cio2->sensor_->resolution();\n> > > -\t\tif (cfg.size.width > resolution.width ||\n> > > -\t\t    cfg.size.height > resolution.height) {\n> > > -\t\t\tLOG(IPU3, Error)\n> > > -\t\t\t\t<< \"Invalid stream size: larger than sensor resolution\";\n> > > -\t\t\treturn -EINVAL;\n> > > -\t\t}\n> > > -\n> > > -\t\t/*\n> > > -\t\t * Collect the maximum width and height: IPU3 can downscale\n> > > -\t\t * only.\n> > > -\t\t */\n> > > -\t\tif (cfg.size.width > sensorSize.width)\n> > > -\t\t\tsensorSize.width = cfg.size.width;\n> > > -\t\tif (cfg.size.height > sensorSize.height)\n> > > -\t\t\tsensorSize.height = cfg.size.height;\n> > > -\n> > > -\t\tstream->active_ = true;\n> > > -\t\tcfg.setStream(stream);\n> > > -\t}\n> > > -\n> > >  \t/*\n> > >  \t * \\todo: Enable links selectively based on the requested streams.\n> > >  \t * As of now, enable all links unconditionally.\n> > > @@ -373,6 +501,7 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n> > >  \t * Pass the requested stream size to the CIO2 unit and get back the\n> > >  \t * adjusted format to be propagated to the ImgU output devices.\n> > >  \t */\n> > > +\tconst Size &sensorSize = config->sensorFormat().size;\n> > >  \tV4L2DeviceFormat cio2Format = {};\n> > >  \tret = cio2->configure(sensorSize, &cio2Format);\n> > >  \tif (ret)\n> > > @@ -383,8 +512,22 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n> > >  \t\treturn ret;\n> > >\n> > >  \t/* Apply the format to the configured streams output devices. */\n> > > -\tfor (StreamConfiguration &cfg : *config) {\n> > > -\t\tIPU3Stream *stream = static_cast<IPU3Stream *>(cfg.stream());\n> > > +\toutStream->active_ = false;\n> > > +\tvfStream->active_ = false;\n> > > +\n> > > +\tfor (unsigned int i = 0; i < config->size(); ++i) {\n> > > +\t\t/*\n> > > +\t\t * Use a const_cast<> here instead of storing a mutable stream\n> > > +\t\t * pointer in the configuration to let the compiler catch\n> > > +\t\t * unwanted modifications of camera data in the configuration\n> > > +\t\t * validate() implementation.\n> > > +\t\t */\n> > > +\t\tIPU3Stream *stream = const_cast<IPU3Stream *>(config->streams()[i]);\n> > > +\t\tStreamConfiguration &cfg = (*config)[i];\n> >\n> > nit: the fact you can get both Stream * and StreamConfiguration * by index\n> > it's a bit ugly. What about config->streams(i) and\n> > config->configuration(i).\n> >\n> > I think what's fishy lies in the fact IPu3CameraConfiguration indexes both\n> > Stream and Configurations in two different vactors and both accessed by\n> > index. Do you need Streams in CameraConfiguration ? Can't you store\n> > them in CameraData, as 'activeStreams_' maybe?\n>\n> I can't, because configuration objects are separated from the active\n> state of the camera. You can create as many configuration you want, toy\n> with them in any way, and finally destroy them, without any side effect.\n> The API guarantees that Camera::configure() will associate streams with\n> configuration entries, but I think pipeline handlers should be able to\n> do it ahead of time too, in validate(), if it's easier for them. I thus\n> don't think that storing that information in CameraData is a good idea.\n\nI see, the streams are pushed in the CameraConfigure::streams_ while\nwalking configs_ at validate() time, and then the two are actually\nassociated at configure() time.\n\nWhat about a map<CameraConfiguration *, std::vector<Stream *>> in\nCameraData :p ? (not sure this is better at all actually).\n\n>\n> Furthermore, I would like to move the Stream class away from the\n> application-facing API, so more refactoring is to come. I could however,\n> in the meantime, replace IPU3CameraConfiguration::streams() with\n> IPU3CameraConfiguration::stream(unsigned int index) if you think that's\n> desirable (let's remember that that method is private to the IPU3), but\n> a CameraConfiguration::configuration(unsigned int index) would make the\n> API more complex for applications I think (it would need to be called\n> CameraConfiguration::streamConfiguration(), and wouldn't let\n> applications iterate over the stream configurations using a range-based\n> loop).\n>\n\nIndeed, the let's keep configuration[i] and configuration.streams()[i]\nfor now.\n\nPlease bear with me a little more on this...\nThanks\n   j\n\n> > > +\n> > > +\t\tstream->active_ = true;\n> > > +\t\tcfg.setStream(stream);\n> > > +\n> > >  \t\tret = imgu->configureOutput(stream->device_, cfg);\n> > >  \t\tif (ret)\n> > >  \t\t\treturn ret;\n> > > diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> > > index a1a4f205b4aa..42944c64189b 100644\n> > > --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> > > +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> >\n> > An impressive rework, I like where it's going even if it puts\n> > a bit of a burden on pipeline handlers, it's for the benefit of the\n> > application facing APIs.\n>\n> Thank you.\n>\n> > > @@ -5,6 +5,7 @@\n> > >   * rkisp1.cpp - Pipeline handler for Rockchip ISP1\n> > >   */\n> > >\n> > > +#include <algorithm>\n> > >  #include <iomanip>\n> > >  #include <memory>\n> > >  #include <vector>\n> > > @@ -45,6 +46,29 @@ public:\n> > >  \tCameraSensor *sensor_;\n> > >  };\n> > >\n> > > +class RkISP1CameraConfiguration : public CameraConfiguration\n> > > +{\n> > > +public:\n> > > +\tRkISP1CameraConfiguration(Camera *camera, RkISP1CameraData *data);\n> > > +\n> > > +\tStatus validate() override;\n> > > +\n> > > +\tconst V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }\n> > > +\n> > > +private:\n> > > +\tstatic constexpr unsigned int RKISP1_BUFFER_COUNT = 4;\n> > > +\n> > > +\t/*\n> > > +\t * The RkISP1CameraData instance is guaranteed to be valid as long as the\n> > > +\t * corresponding Camera instance is valid. In order to borrow a\n> > > +\t * reference to the camera data, store a new reference to the camera.\n> > > +\t */\n> > > +\tstd::shared_ptr<Camera> camera_;\n> > > +\tconst RkISP1CameraData *data_;\n> > > +\n> > > +\tV4L2SubdeviceFormat sensorFormat_;\n> > > +};\n> > > +\n> > >  class PipelineHandlerRkISP1 : public PipelineHandler\n> > >  {\n> > >  public:\n> > > @@ -68,8 +92,6 @@ public:\n> > >  \tbool match(DeviceEnumerator *enumerator) override;\n> > >\n> > >  private:\n> > > -\tstatic constexpr unsigned int RKISP1_BUFFER_COUNT = 4;\n> > > -\n> > >  \tRkISP1CameraData *cameraData(const Camera *camera)\n> > >  \t{\n> > >  \t\treturn static_cast<RkISP1CameraData *>(\n> > > @@ -88,6 +110,95 @@ private:\n> > >  \tCamera *activeCamera_;\n> > >  };\n> > >\n> > > +RkISP1CameraConfiguration::RkISP1CameraConfiguration(Camera *camera,\n> > > +\t\t\t\t\t\t     RkISP1CameraData *data)\n> > > +\t: CameraConfiguration()\n> > > +{\n> > > +\tcamera_ = camera->shared_from_this();\n> > > +\tdata_ = data;\n> > > +}\n> > > +\n> > > +CameraConfiguration::Status RkISP1CameraConfiguration::validate()\n> > > +{\n> > > +\tstatic const std::array<unsigned int, 8> formats{\n> > > +\t\tV4L2_PIX_FMT_YUYV,\n> > > +\t\tV4L2_PIX_FMT_YVYU,\n> > > +\t\tV4L2_PIX_FMT_VYUY,\n> > > +\t\tV4L2_PIX_FMT_NV16,\n> > > +\t\tV4L2_PIX_FMT_NV61,\n> > > +\t\tV4L2_PIX_FMT_NV21,\n> > > +\t\tV4L2_PIX_FMT_NV12,\n> > > +\t\tV4L2_PIX_FMT_GREY,\n> > > +\t};\n> > > +\n> > > +\tconst CameraSensor *sensor = data_->sensor_;\n> > > +\tStatus status = Valid;\n> > > +\n> > > +\tif (config_.empty())\n> > > +\t\treturn Invalid;\n> > > +\n> > > +\t/* Cap the number of entries to the available streams. */\n> > > +\tif (config_.size() > 1) {\n> > > +\t\tconfig_.resize(1);\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\tStreamConfiguration &cfg = config_[0];\n> > > +\n> > > +\t/* Adjust the pixel format. */\n> > > +\tif (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==\n> > > +\t    formats.end()) {\n> > > +\t\tLOG(RkISP1, Debug) << \"Adjusting format to NV12\";\n> > > +\t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\t/* Select the sensor format. */\n> > > +\tsensorFormat_ = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR12_1X12,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG12_1X12,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG12_1X12,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB12_1X12,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SBGGR10_1X10,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG10_1X10,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG10_1X10,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB10_1X10,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SBGGR8_1X8,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG8_1X8,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG8_1X8,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB8_1X8 },\n> > > +\t\t\t\t\t  cfg.size);\n> > > +\tif (!sensorFormat_.size.width || !sensorFormat_.size.height)\n> > > +\t\tsensorFormat_.size = sensor->resolution();\n> > > +\n> > > +\t/*\n> > > +\t * Provide a suitable default that matches the sensor aspect\n> > > +\t * ratio and clamp the size to the hardware bounds.\n> > > +\t *\n> > > +\t * \\todo: Check the hardware alignment constraints.\n> > > +\t */\n> > > +\tconst Size size = cfg.size;\n> > > +\n> > > +\tif (!cfg.size.width || !cfg.size.height) {\n> > > +\t\tcfg.size.width = 1280;\n> > > +\t\tcfg.size.height = 1280 * sensorFormat_.size.height\n> > > +\t\t\t\t/ sensorFormat_.size.width;\n> > > +\t}\n> > > +\n> > > +\tcfg.size.width = std::max(32U, std::min(4416U, cfg.size.width));\n> > > +\tcfg.size.height = std::max(16U, std::min(3312U, cfg.size.height));\n> > > +\n> > > +\tif (cfg.size != size) {\n> > > +\t\tLOG(RkISP1, Debug)\n> > > +\t\t\t<< \"Adjusting size from \" << size.toString()\n> > > +\t\t\t<< \" to \" << cfg.size.toString();\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\tcfg.bufferCount = RKISP1_BUFFER_COUNT;\n> > > +\n> > > +\treturn status;\n> > > +}\n> > > +\n> > >  PipelineHandlerRkISP1::PipelineHandlerRkISP1(CameraManager *manager)\n> > >  \t: PipelineHandler(manager), dphy_(nullptr), isp_(nullptr),\n> > >  \t  video_(nullptr)\n> > > @@ -109,37 +220,31 @@ CameraConfiguration *PipelineHandlerRkISP1::generateConfiguration(Camera *camera\n> > >  \tconst StreamRoles &roles)\n> > >  {\n> > >  \tRkISP1CameraData *data = cameraData(camera);\n> > > -\tCameraConfiguration *config = new CameraConfiguration();\n> > > +\tCameraConfiguration *config = new RkISP1CameraConfiguration(camera, data);\n> > >\n> > >  \tif (!roles.empty()) {\n> > >  \t\tStreamConfiguration cfg{};\n> > >\n> > >  \t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> > >  \t\tcfg.size = data->sensor_->resolution();\n> > > -\t\tcfg.bufferCount = RKISP1_BUFFER_COUNT;\n> > >\n> > >  \t\tconfig->addConfiguration(cfg);\n> > >  \t}\n> > >\n> > > +\tconfig->validate();\n> > > +\n> > >  \treturn config;\n> > >  }\n> > >\n> > > -int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *config)\n> > > +int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)\n> > >  {\n> > > +\tRkISP1CameraConfiguration *config =\n> > > +\t\tstatic_cast<RkISP1CameraConfiguration *>(c);\n> > >  \tRkISP1CameraData *data = cameraData(camera);\n> > >  \tStreamConfiguration &cfg = config->at(0);\n> > >  \tCameraSensor *sensor = data->sensor_;\n> > >  \tint ret;\n> > >\n> > > -\t/* Verify the configuration. */\n> > > -\tconst Size &resolution = sensor->resolution();\n> > > -\tif (cfg.size.width > resolution.width ||\n> > > -\t    cfg.size.height > resolution.height) {\n> > > -\t\tLOG(RkISP1, Error)\n> > > -\t\t\t<< \"Invalid stream size: larger than sensor resolution\";\n> > > -\t\treturn -EINVAL;\n> > > -\t}\n> > > -\n> > >  \t/*\n> > >  \t * Configure the sensor links: enable the link corresponding to this\n> > >  \t * camera and disable all the other sensor links.\n> > > @@ -167,21 +272,7 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *config\n> > >  \t * Configure the format on the sensor output and propagate it through\n> > >  \t * the pipeline.\n> > >  \t */\n> > > -\tV4L2SubdeviceFormat format;\n> > > -\tformat = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR12_1X12,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SGBRG12_1X12,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SGRBG12_1X12,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SRGGB12_1X12,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SBGGR10_1X10,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SGBRG10_1X10,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SGRBG10_1X10,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SRGGB10_1X10,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SBGGR8_1X8,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SGBRG8_1X8,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SGRBG8_1X8,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SRGGB8_1X8 },\n> > > -\t\t\t\t   cfg.size);\n> > > -\n> > > +\tV4L2SubdeviceFormat format = config->sensorFormat();\n> > >  \tLOG(RkISP1, Debug) << \"Configuring sensor with \" << format.toString();\n> > >\n> > >  \tret = sensor->setFormat(&format);\n> > > diff --git a/src/libcamera/pipeline/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo.cpp\n> > > index 8254e1fdac1e..f7ffeb439cf3 100644\n> > > --- a/src/libcamera/pipeline/uvcvideo.cpp\n> > > +++ b/src/libcamera/pipeline/uvcvideo.cpp\n> > > @@ -39,6 +39,14 @@ public:\n> > >  \tStream stream_;\n> > >  };\n> > >\n> > > +class UVCCameraConfiguration : public CameraConfiguration\n> > > +{\n> > > +public:\n> > > +\tUVCCameraConfiguration();\n> > > +\n> > > +\tStatus validate() override;\n> > > +};\n> > > +\n> > >  class PipelineHandlerUVC : public PipelineHandler\n> > >  {\n> > >  public:\n> > > @@ -68,6 +76,45 @@ private:\n> > >  \t}\n> > >  };\n> > >\n> > > +UVCCameraConfiguration::UVCCameraConfiguration()\n> > > +\t: CameraConfiguration()\n> > > +{\n> > > +}\n> > > +\n> > > +CameraConfiguration::Status UVCCameraConfiguration::validate()\n> > > +{\n> > > +\tStatus status = Valid;\n> > > +\n> > > +\tif (config_.empty())\n> > > +\t\treturn Invalid;\n> > > +\n> > > +\t/* Cap the number of entries to the available streams. */\n> > > +\tif (config_.size() > 1) {\n> > > +\t\tconfig_.resize(1);\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\tStreamConfiguration &cfg = config_[0];\n> > > +\n> > > +\t/* \\todo: Validate the configuration against the device capabilities. */\n> > > +\tconst unsigned int pixelFormat = cfg.pixelFormat;\n> > > +\tconst Size size = cfg.size;\n> > > +\n> > > +\tcfg.pixelFormat = V4L2_PIX_FMT_YUYV;\n> > > +\tcfg.size = { 640, 480 };\n> > > +\n> > > +\tif (cfg.pixelFormat != pixelFormat || cfg.size != size) {\n> > > +\t\tLOG(UVC, Debug)\n> > > +\t\t\t<< \"Adjusting configuration from \" << cfg.toString()\n> > > +\t\t\t<< \" to \" << cfg.size.toString() << \"-YUYV\";\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\tcfg.bufferCount = 4;\n> > > +\n> > > +\treturn status;\n> > > +}\n> > > +\n> > >  PipelineHandlerUVC::PipelineHandlerUVC(CameraManager *manager)\n> > >  \t: PipelineHandler(manager)\n> > >  {\n> > > @@ -76,10 +123,10 @@ PipelineHandlerUVC::PipelineHandlerUVC(CameraManager *manager)\n> > >  CameraConfiguration *PipelineHandlerUVC::generateConfiguration(Camera *camera,\n> > >  \tconst StreamRoles &roles)\n> > >  {\n> > > -\tCameraConfiguration *config = new CameraConfiguration();\n> > > +\tCameraConfiguration *config = new UVCCameraConfiguration();\n> > >\n> > >  \tif (!roles.empty()) {\n> > > -\t\tStreamConfiguration cfg;\n> > > +\t\tStreamConfiguration cfg{};\n> > >\n> > >  \t\tcfg.pixelFormat = V4L2_PIX_FMT_YUYV;\n> > >  \t\tcfg.size = { 640, 480 };\n> > > @@ -88,6 +135,8 @@ CameraConfiguration *PipelineHandlerUVC::generateConfiguration(Camera *camera,\n> > >  \t\tconfig->addConfiguration(cfg);\n> > >  \t}\n> > >\n> > > +\tconfig->validate();\n> > > +\n> > >  \treturn config;\n> > >  }\n> > >\n> > > diff --git a/src/libcamera/pipeline/vimc.cpp b/src/libcamera/pipeline/vimc.cpp\n> > > index 2bf85d0a0b32..2a61d2893e3a 100644\n> > > --- a/src/libcamera/pipeline/vimc.cpp\n> > > +++ b/src/libcamera/pipeline/vimc.cpp\n> > > @@ -5,6 +5,8 @@\n> > >   * vimc.cpp - Pipeline handler for the vimc device\n> > >   */\n> > >\n> > > +#include <algorithm>\n> > > +\n> > >  #include <libcamera/camera.h>\n> > >  #include <libcamera/request.h>\n> > >  #include <libcamera/stream.h>\n> > > @@ -39,6 +41,14 @@ public:\n> > >  \tStream stream_;\n> > >  };\n> > >\n> > > +class VimcCameraConfiguration : public CameraConfiguration\n> > > +{\n> > > +public:\n> > > +\tVimcCameraConfiguration();\n> > > +\n> > > +\tStatus validate() override;\n> > > +};\n> > > +\n> > >  class PipelineHandlerVimc : public PipelineHandler\n> > >  {\n> > >  public:\n> > > @@ -68,6 +78,57 @@ private:\n> > >  \t}\n> > >  };\n> > >\n> > > +VimcCameraConfiguration::VimcCameraConfiguration()\n> > > +\t: CameraConfiguration()\n> > > +{\n> > > +}\n> > > +\n> > > +CameraConfiguration::Status VimcCameraConfiguration::validate()\n> > > +{\n> > > +\tstatic const std::array<unsigned int, 3> formats{\n> > > +\t\tV4L2_PIX_FMT_BGR24,\n> > > +\t\tV4L2_PIX_FMT_RGB24,\n> > > +\t\tV4L2_PIX_FMT_ARGB32,\n> > > +\t};\n> > > +\n> > > +\tStatus status = Valid;\n> > > +\n> > > +\tif (config_.empty())\n> > > +\t\treturn Invalid;\n> > > +\n> > > +\t/* Cap the number of entries to the available streams. */\n> > > +\tif (config_.size() > 1) {\n> > > +\t\tconfig_.resize(1);\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\tStreamConfiguration &cfg = config_[0];\n> > > +\n> > > +\t/* Adjust the pixel format. */\n> > > +\tif (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==\n> > > +\t    formats.end()) {\n> > > +\t\tLOG(VIMC, Debug) << \"Adjusting format to RGB24\";\n> > > +\t\tcfg.pixelFormat = V4L2_PIX_FMT_RGB24;\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\t/* Clamp the size based on the device limits. */\n> > > +\tconst Size size = cfg.size;\n> > > +\n> > > +\tcfg.size.width = std::max(16U, std::min(4096U, cfg.size.width));\n> > > +\tcfg.size.height = std::max(16U, std::min(2160U, cfg.size.height));\n> > > +\n> > > +\tif (cfg.size != size) {\n> > > +\t\tLOG(VIMC, Debug)\n> > > +\t\t\t<< \"Adjusting size to \" << cfg.size.toString();\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\tcfg.bufferCount = 4;\n> > > +\n> > > +\treturn status;\n> > > +}\n> > > +\n> > >  PipelineHandlerVimc::PipelineHandlerVimc(CameraManager *manager)\n> > >  \t: PipelineHandler(manager)\n> > >  {\n> > > @@ -76,10 +137,10 @@ PipelineHandlerVimc::PipelineHandlerVimc(CameraManager *manager)\n> > >  CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera,\n> > >  \tconst StreamRoles &roles)\n> > >  {\n> > > -\tCameraConfiguration *config = new CameraConfiguration();\n> > > +\tCameraConfiguration *config = new VimcCameraConfiguration();\n> > >\n> > >  \tif (!roles.empty()) {\n> > > -\t\tStreamConfiguration cfg;\n> > > +\t\tStreamConfiguration cfg{};\n> > >\n> > >  \t\tcfg.pixelFormat = V4L2_PIX_FMT_RGB24;\n> > >  \t\tcfg.size = { 640, 480 };\n> > > @@ -88,6 +149,8 @@ CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera,\n> > >  \t\tconfig->addConfiguration(cfg);\n> > >  \t}\n> > >\n> > > +\tconfig->validate();\n> > > +\n> > >  \treturn config;\n> > >  }\n> > >\n> > > diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp\n> > > index de46e98880a2..dd56907d817e 100644\n> > > --- a/src/libcamera/pipeline_handler.cpp\n> > > +++ b/src/libcamera/pipeline_handler.cpp\n> > > @@ -248,17 +248,14 @@ void PipelineHandler::unlock()\n> > >   * is the Camera class which will receive configuration to apply from the\n> > >   * application.\n> > >   *\n> > > - * Each pipeline handler implementation is responsible for validating\n> > > - * that the configuration requested in \\a config can be achieved\n> > > - * exactly. Any difference in pixel format, frame size or any other\n> > > - * parameter shall result in the -EINVAL error being returned, and no\n> > > - * change in configuration being applied to the pipeline. If\n> > > - * configuration of a subset of the streams can't be satisfied, the\n> > > - * whole configuration is considered invalid.\n> > > + * The configuration is guaranteed to have been validated with\n> > > + * CameraConfiguration::valid(). The pipeline handler implementation shall not\n> > > + * perform further validation and may rely on any custom field stored in its\n> > > + * custom CameraConfiguration derived class.\n> > >   *\n> > > - * Once the configuration is validated and the camera configured, the pipeline\n> > > - * handler shall associate a Stream instance to each StreamConfiguration entry\n> > > - * in the CameraConfiguration with the StreamConfiguration::setStream() method.\n> > > + * When configuring the camera the pipeline handler shall associate a Stream\n> > > + * instance to each StreamConfiguration entry in the CameraConfiguration using\n> > > + * the StreamConfiguration::setStream() method.\n> > >   *\n> > >   * \\return 0 on success or a negative error code otherwise\n> > >   */\n> > > diff --git a/test/camera/capture.cpp b/test/camera/capture.cpp\n> > > index 137aa649a505..f122f79bb1ec 100644\n> > > --- a/test/camera/capture.cpp\n> > > +++ b/test/camera/capture.cpp\n> > > @@ -45,7 +45,7 @@ protected:\n> > >  \t\tCameraTest::init();\n> > >\n> > >  \t\tconfig_ = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> > > -\t\tif (!config_) {\n> > > +\t\tif (!config_ || config_->size() != 1) {\n> > >  \t\t\tcout << \"Failed to generate default configuration\" << endl;\n> > >  \t\t\tCameraTest::cleanup();\n> > >  \t\t\treturn TestFail;\n> > > @@ -58,11 +58,6 @@ protected:\n> > >  \t{\n> > >  \t\tStreamConfiguration &cfg = config_->at(0);\n> > >\n> > > -\t\tif (!config_->isValid()) {\n> > > -\t\t\tcout << \"Failed to read default configuration\" << endl;\n> > > -\t\t\treturn TestFail;\n> > > -\t\t}\n> > > -\n> > >  \t\tif (camera_->acquire()) {\n> > >  \t\t\tcout << \"Failed to acquire the camera\" << endl;\n> > >  \t\t\treturn TestFail;\n> > > diff --git a/test/camera/configuration_default.cpp b/test/camera/configuration_default.cpp\n> > > index d5cefc1127c9..81055da1d513 100644\n> > > --- a/test/camera/configuration_default.cpp\n> > > +++ b/test/camera/configuration_default.cpp\n> > > @@ -22,7 +22,7 @@ protected:\n> > >\n> > >  \t\t/* Test asking for configuration for a video stream. */\n> > >  \t\tconfig = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> > > -\t\tif (!config || !config->isValid()) {\n> > > +\t\tif (!config || config->size() != 1) {\n> > >  \t\t\tcout << \"Default configuration invalid\" << endl;\n> > >  \t\t\tdelete config;\n> > >  \t\t\treturn TestFail;\n> > > @@ -35,7 +35,7 @@ protected:\n> > >  \t\t * stream roles returns an empty camera configuration.\n> > >  \t\t */\n> > >  \t\tconfig = camera_->generateConfiguration({});\n> > > -\t\tif (!config || config->isValid()) {\n> > > +\t\tif (!config || config->size() != 0) {\n> > >  \t\t\tcout << \"Failed to retrieve configuration for empty roles list\"\n> > >  \t\t\t     << endl;\n> > >  \t\t\tdelete config;\n> > > diff --git a/test/camera/configuration_set.cpp b/test/camera/configuration_set.cpp\n> > > index 23c611a93355..a4e2da16a88b 100644\n> > > --- a/test/camera/configuration_set.cpp\n> > > +++ b/test/camera/configuration_set.cpp\n> > > @@ -21,7 +21,7 @@ protected:\n> > >  \t\tCameraTest::init();\n> > >\n> > >  \t\tconfig_ = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> > > -\t\tif (!config_) {\n> > > +\t\tif (!config_ || config_->size() != 1) {\n> > >  \t\t\tcout << \"Failed to generate default configuration\" << endl;\n> > >  \t\t\tCameraTest::cleanup();\n> > >  \t\t\treturn TestFail;\n> > > @@ -34,11 +34,6 @@ protected:\n> > >  \t{\n> > >  \t\tStreamConfiguration &cfg = config_->at(0);\n> > >\n> > > -\t\tif (!config_->isValid()) {\n> > > -\t\t\tcout << \"Failed to read default configuration\" << endl;\n> > > -\t\t\treturn TestFail;\n> > > -\t\t}\n> > > -\n> > >  \t\tif (camera_->acquire()) {\n> > >  \t\t\tcout << \"Failed to acquire the camera\" << endl;\n> > >  \t\t\treturn TestFail;\n>\n> --\n> Regards,\n>\n> Laurent Pinchart","headers":{"Return-Path":"<jacopo@jmondi.org>","Received":["from relay5-d.mail.gandi.net (relay5-d.mail.gandi.net\n\t[217.70.183.197])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 6CBB760C40\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 21 May 2019 23:42:24 +0200 (CEST)","from uno.localdomain (2-224-242-101.ip172.fastwebnet.it\n\t[2.224.242.101]) (Authenticated sender: jacopo@jmondi.org)\n\tby relay5-d.mail.gandi.net (Postfix) with ESMTPSA id 9F7CF1C0002;\n\tTue, 21 May 2019 21:42:23 +0000 (UTC)"],"X-Originating-IP":"2.224.242.101","Date":"Tue, 21 May 2019 23:43:29 +0200","From":"Jacopo Mondi <jacopo@jmondi.org>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20190521214329.lbtggxmqzc5mi2rn@uno.localdomain>","References":"<20190519150047.12444-1-laurent.pinchart@ideasonboard.com>\n\t<20190519150047.12444-7-laurent.pinchart@ideasonboard.com>\n\t<20190520220506.5yubhl2rqmk3ajdr@uno.localdomain>\n\t<20190521124955.GC5674@pendragon.ideasonboard.com>","MIME-Version":"1.0","Content-Type":"multipart/signed; micalg=pgp-sha256;\n\tprotocol=\"application/pgp-signature\"; boundary=\"7xbhjgfohtyjh6sr\"","Content-Disposition":"inline","In-Reply-To":"<20190521124955.GC5674@pendragon.ideasonboard.com>","User-Agent":"NeoMutt/20180716","Subject":"Re: [libcamera-devel] [PATCH v2 6/6] libcamera: camera: Add a\n\tvalidation API to the CameraConfiguration class","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.23","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Tue, 21 May 2019 21:42:24 -0000"}},{"id":1658,"web_url":"https://patchwork.libcamera.org/comment/1658/","msgid":"<20190522105516.GB1651@bigcity.dyn.berto.se>","date":"2019-05-22T10:55:16","subject":"Re: [libcamera-devel] [PATCH v2 6/6] libcamera: camera: Add a\n\tvalidation API to the CameraConfiguration class","submitter":{"id":5,"url":"https://patchwork.libcamera.org/api/people/5/","name":"Niklas Söderlund","email":"niklas.soderlund@ragnatech.se"},"content":"Hi,\n\nOn 2019-05-21 15:51:09 +0300, Laurent Pinchart wrote:\n> Hi Jacopo,\n> \n> On Tue, May 21, 2019 at 10:49:50AM +0200, Jacopo Mondi wrote:\n> > Hi Laurent,\n> >    one more thing, sorry\n> > \n> > On Sun, May 19, 2019 at 06:00:47PM +0300, Laurent Pinchart wrote:\n> > > The CameraConfiguration class implements a simple storage of\n> > > StreamConfiguration with internal validation limited to verifying that\n> > > the stream configurations are not empty. Extend this mechanism by\n> > > implementing a smart validate() method backed by pipeline handlers.\n> > >\n> > > This new mechanism changes the semantics of the camera configuration.\n> > > The Camera::generateConfiguration() operation still generates a default\n> > > configuration based on roles, but now also supports generating empty\n> > > configurations to be filled by applications. Applications can inspect\n> > > the configuration, optionally modify it, and validate it. The validation\n> > > implements \"try\" semantics and adjusts invalid configurations instead of\n> > > rejecting them completely. Applications then decide whether to accept\n> > > the modified configuration, or try again with a different set of\n> > > parameters. Once the configuration is valid, it is passed to\n> > > Camera::configure(), and pipeline handlers are guaranteed that the\n> > > configuration they receive is valid.\n> > >\n> > > A reference to the Camera may need to be stored in the\n> > > CameraConfiguration derived classes in order to access it from their\n> > > validate() implementation. This must be stored as a std::shared_ptr<> as\n> > > the CameraConfiguration instances belong to applications. In order to\n> > > make this possible, make the Camera class inherit from\n> > > std::shared_from_this<>.\n> > >\n> > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> > > ---\n> > >  include/libcamera/camera.h               |  17 +-\n> > >  src/cam/main.cpp                         |   2 +-\n> > >  src/libcamera/camera.cpp                 |  80 +++++--\n> > >  src/libcamera/pipeline/ipu3/ipu3.cpp     | 255 ++++++++++++++++++-----\n> > >  src/libcamera/pipeline/rkisp1/rkisp1.cpp | 149 ++++++++++---\n> > >  src/libcamera/pipeline/uvcvideo.cpp      |  53 ++++-\n> > >  src/libcamera/pipeline/vimc.cpp          |  67 +++++-\n> > >  src/libcamera/pipeline_handler.cpp       |  17 +-\n> > >  test/camera/capture.cpp                  |   7 +-\n> > >  test/camera/configuration_default.cpp    |   4 +-\n> > >  test/camera/configuration_set.cpp        |   7 +-\n> > >  11 files changed, 516 insertions(+), 142 deletions(-)\n> > >\n> > > diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> > > index 144133c5de9f..8c0049a1dd94 100644\n> > > --- a/include/libcamera/camera.h\n> > > +++ b/include/libcamera/camera.h\n> > > @@ -25,14 +25,19 @@ class Request;\n> > >  class CameraConfiguration\n> > >  {\n> > >  public:\n> > > +\tenum Status {\n> > > +\t\tValid,\n> > > +\t\tAdjusted,\n> > > +\t\tInvalid,\n> > > +\t};\n> > > +\n> > >  \tusing iterator = std::vector<StreamConfiguration>::iterator;\n> > >  \tusing const_iterator = std::vector<StreamConfiguration>::const_iterator;\n> > >\n> > > -\tCameraConfiguration();\n> > > +\tvirtual ~CameraConfiguration();\n> > >\n> > >  \tvoid addConfiguration(const StreamConfiguration &cfg);\n> > > -\n> > > -\tbool isValid() const;\n> > > +\tvirtual Status validate() = 0;\n> > >\n> > >  \tStreamConfiguration &at(unsigned int index);\n> > >  \tconst StreamConfiguration &at(unsigned int index) const;\n> > > @@ -53,11 +58,13 @@ public:\n> > >  \tbool empty() const;\n> > >  \tstd::size_t size() const;\n> > >\n> > > -private:\n> > > +protected:\n> > > +\tCameraConfiguration();\n> > > +\n> > >  \tstd::vector<StreamConfiguration> config_;\n> > >  };\n> > >\n> > > -class Camera final\n> > > +class Camera final : public std::enable_shared_from_this<Camera>\n> > >  {\n> > >  public:\n> > >  \tstatic std::shared_ptr<Camera> create(PipelineHandler *pipe,\n> > > diff --git a/src/cam/main.cpp b/src/cam/main.cpp\n> > > index 7550ae4f3428..23da5c687d89 100644\n> > > --- a/src/cam/main.cpp\n> > > +++ b/src/cam/main.cpp\n> > \n> > Shouldn't you add a config->validate() call before calling\n> > camera->configure(config) for the cam application?\n> \n> Niklas has submitted a patch for them, do you think I should squash it\n> with this one, or apply it separately ?\n\nFor cam in it's current form there is no strict need to call validate at \nthis point. The current design is that cam should fail if the format is \nadjusted. We might wish to change that in the future (like I do in some \npatch IIRC).\n\nIf you wish to squash that change here I'm fine doing so. I think \nhowever that it's fine for this patch to move forward with or without \nthat change.\n\n> \n> > (I've not checked qcam, I'm sorry, but it might apply there too)\n> \n> That should be fixed too, yes.\n> \n> > > @@ -116,7 +116,7 @@ static CameraConfiguration *prepareCameraConfig()\n> > >  \t}\n> > >\n> > >  \tCameraConfiguration *config = camera->generateConfiguration(roles);\n> > > -\tif (!config || !config->isValid()) {\n> > > +\tif (!config || config->size() != roles.size()) {\n> > >  \t\tstd::cerr << \"Failed to get default stream configuration\"\n> > >  \t\t\t  << std::endl;\n> > >  \t\tdelete config;\n> > > diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> > > index 0e80691ed862..9da5d4f613f4 100644\n> > > --- a/src/libcamera/camera.cpp\n> > > +++ b/src/libcamera/camera.cpp\n> > > @@ -52,6 +52,28 @@ LOG_DECLARE_CATEGORY(Camera)\n> > >   * operator[](int) returns a reference to the StreamConfiguration based on its\n> > >   * insertion index. Accessing a stream configuration with an invalid index\n> > >   * results in undefined behaviour.\n> > > + *\n> > > + * CameraConfiguration instances are retrieved from the camera with\n> > > + * Camera::generateConfiguration(). Applications may then inspect the\n> > > + * configuration, modify it, and possibly add new stream configuration entries\n> > > + * with addConfiguration(). Once the camera configuration satisfies the\n> > > + * application, it shall be validated by a call to validate(). The validation\n> > > + * implements \"try\" semantics: it adjusts invalid configurations to the closest\n> > > + * achievable parameters instead of rejecting them completely. Applications\n> > > + * then decide whether to accept the modified configuration, or try again with\n> > > + * a different set of parameters. Once the configuration is valid, it is passed\n> > > + * to Camera::configure().\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\enum CameraConfiguration::Status\n> > > + * \\brief Validity of a camera configuration\n> > > + * \\var CameraConfiguration::Valid\n> > > + * The configuration is fully valid\n> > > + * \\var CameraConfiguration::Adjusted\n> > > + * The configuration has been adjusted to a valid configuration\n> > > + * \\var CameraConfiguration::Invalid\n> > > + * The configuration is invalid and can't be adjusted automatically\n> > >   */\n> > >\n> > >  /**\n> > > @@ -73,6 +95,10 @@ CameraConfiguration::CameraConfiguration()\n> > >  {\n> > >  }\n> > >\n> > > +CameraConfiguration::~CameraConfiguration()\n> > > +{\n> > > +}\n> > > +\n> > >  /**\n> > >   * \\brief Add a stream configuration to the camera configuration\n> > >   * \\param[in] cfg The stream configuration\n> > > @@ -83,27 +109,31 @@ void CameraConfiguration::addConfiguration(const StreamConfiguration &cfg)\n> > >  }\n> > >\n> > >  /**\n> > > - * \\brief Check if the camera configuration is valid\n> > > + * \\fn CameraConfiguration::validate()\n> > > + * \\brief Validate and possibly adjust the camera configuration\n> > >   *\n> > > - * A camera configuration is deemed to be valid if it contains at least one\n> > > - * stream configuration and all stream configurations contain valid information.\n> > > - * Stream configurations are deemed to be valid if all fields are none zero.\n> > > + * This method adjusts the camera configuration to the closest valid\n> > > + * configuration and returns the validation status.\n> > >   *\n> > > - * \\return True if the configuration is valid\n> > > + * \\todo: Define exactly when to return each status code. Should stream\n> > > + * parameters set to 0 by the caller be adjusted without returning Adjusted ?\n> > > + * This would potentially be useful for applications but would get in the way\n> > > + * in Camera::configure(). Do we need an extra status code to signal this ?\n> > > + *\n> > > + * \\todo: Handle validation of buffers count when refactoring the buffers API.\n> > > + *\n> > > + * \\return A CameraConfiguration::Status value that describes the validation\n> > > + * status.\n> > > + * \\retval CameraConfiguration::Invalid The configuration is invalid and can't\n> > > + * be adjusted. This may only occur in extreme cases such as when the\n> > > + * configuration is empty.\n> > > + * \\retval CameraConfigutation::Adjusted The configuration has been adjusted\n> > > + * and is now valid. Parameters may have changed for any stream, and stream\n> > > + * configurations may have been removed. The caller shall check the\n> > > + * configuration carefully.\n> > > + * \\retval CameraConfiguration::Valid The configuration was already valid and\n> > > + * hasn't been adjusted.\n> > >   */\n> > > -bool CameraConfiguration::isValid() const\n> > > -{\n> > > -\tif (empty())\n> > > -\t\treturn false;\n> > > -\n> > > -\tfor (const StreamConfiguration &cfg : config_) {\n> > > -\t\tif (cfg.size.width == 0 || cfg.size.height == 0 ||\n> > > -\t\t    cfg.pixelFormat == 0 || cfg.bufferCount == 0)\n> > > -\t\t\treturn false;\n> > > -\t}\n> > > -\n> > > -\treturn true;\n> > > -}\n> > >\n> > >  /**\n> > >   * \\brief Retrieve a reference to a stream configuration\n> > > @@ -218,6 +248,11 @@ std::size_t CameraConfiguration::size() const\n> > >  \treturn config_.size();\n> > >  }\n> > >\n> > > +/**\n> > > + * \\var CameraConfiguration::config_\n> > > + * \\brief The vector of stream configurations\n> > > + */\n> > > +\n> > >  /**\n> > >   * \\class Camera\n> > >   * \\brief Camera device\n> > > @@ -575,10 +610,9 @@ CameraConfiguration *Camera::generateConfiguration(const StreamRoles &roles)\n> > >   * The caller specifies which streams are to be involved and their configuration\n> > >   * by populating \\a config.\n> > >   *\n> > > - * The easiest way to populate the array of config is to fetch an initial\n> > > - * configuration from the camera with generateConfiguration() and then change\n> > > - * the parameters to fit the caller's need and once all the streams parameters\n> > > - * are configured hand that over to configure() to actually setup the camera.\n> > > + * The configuration is created by generateConfiguration(), and adjusted by the\n> > > + * caller with CameraConfiguration::validate(). This method only accepts fully\n> > > + * valid configurations and returns an error if \\a config is not valid.\n> > >   *\n> > >   * Exclusive access to the camera shall be ensured by a call to acquire() prior\n> > >   * to calling this function, otherwise an -EACCES error will be returned.\n> > > @@ -603,7 +637,7 @@ int Camera::configure(CameraConfiguration *config)\n> > >  \tif (!stateBetween(CameraAcquired, CameraConfigured))\n> > >  \t\treturn -EACCES;\n> > >\n> > > -\tif (!config->isValid()) {\n> > > +\tif (config->validate() != CameraConfiguration::Valid) {\n> > >  \t\tLOG(Camera, Error)\n> > >  \t\t\t<< \"Can't configure camera with invalid configuration\";\n> > >  \t\treturn -EINVAL;\n> > > diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp\n> > > index 5b46fb68ced2..56265385a1e7 100644\n> > > --- a/src/libcamera/pipeline/ipu3/ipu3.cpp\n> > > +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp\n> > > @@ -164,6 +164,33 @@ public:\n> > >  \tIPU3Stream vfStream_;\n> > >  };\n> > >\n> > > +class IPU3CameraConfiguration : public CameraConfiguration\n> > > +{\n> > > +public:\n> > > +\tIPU3CameraConfiguration(Camera *camera, IPU3CameraData *data);\n> > > +\n> > > +\tStatus validate() override;\n> > > +\n> > > +\tconst V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }\n> > > +\tconst std::vector<const IPU3Stream *> &streams() { return streams_; }\n> > > +\n> > > +private:\n> > > +\tstatic constexpr unsigned int IPU3_BUFFER_COUNT = 4;\n> > > +\n> > > +\tvoid adjustStream(unsigned int index, bool scale);\n> > > +\n> > > +\t/*\n> > > +\t * The IPU3CameraData instance is guaranteed to be valid as long as the\n> > > +\t * corresponding Camera instance is valid. In order to borrow a\n> > > +\t * reference to the camera data, store a new reference to the camera.\n> > > +\t */\n> > > +\tstd::shared_ptr<Camera> camera_;\n> > > +\tconst IPU3CameraData *data_;\n> > > +\n> > > +\tV4L2SubdeviceFormat sensorFormat_;\n> > > +\tstd::vector<const IPU3Stream *> streams_;\n> > > +};\n> > > +\n> > >  class PipelineHandlerIPU3 : public PipelineHandler\n> > >  {\n> > >  public:\n> > > @@ -186,8 +213,6 @@ public:\n> > >  \tbool match(DeviceEnumerator *enumerator) override;\n> > >\n> > >  private:\n> > > -\tstatic constexpr unsigned int IPU3_BUFFER_COUNT = 4;\n> > > -\n> > >  \tIPU3CameraData *cameraData(const Camera *camera)\n> > >  \t{\n> > >  \t\treturn static_cast<IPU3CameraData *>(\n> > > @@ -202,6 +227,153 @@ private:\n> > >  \tMediaDevice *imguMediaDev_;\n> > >  };\n> > >\n> > > +IPU3CameraConfiguration::IPU3CameraConfiguration(Camera *camera,\n> > > +\t\t\t\t\t\t IPU3CameraData *data)\n> > > +\t: CameraConfiguration()\n> > > +{\n> > > +\tcamera_ = camera->shared_from_this();\n> > > +\tdata_ = data;\n> > > +}\n> > > +\n> > > +void IPU3CameraConfiguration::adjustStream(unsigned int index, bool scale)\n> > > +{\n> > > +\tStreamConfiguration &cfg = config_[index];\n> > > +\n> > > +\t/* The only pixel format the driver supports is NV12. */\n> > > +\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> > > +\n> > > +\tif (scale) {\n> > > +\t\t/*\n> > > +\t\t * Provide a suitable default that matches the sensor aspect\n> > > +\t\t * ratio.\n> > > +\t\t */\n> > > +\t\tif (!cfg.size.width || !cfg.size.height) {\n> > > +\t\t\tcfg.size.width = 1280;\n> > > +\t\t\tcfg.size.height = 1280 * sensorFormat_.size.height\n> > > +\t\t\t\t\t/ sensorFormat_.size.width;\n> > > +\t\t}\n> > > +\n> > > +\t\t/*\n> > > +\t\t * \\todo: Clamp the size to the hardware bounds when we will\n> > > +\t\t * figure them out.\n> > > +\t\t *\n> > > +\t\t * \\todo: Handle the scaler (BDS) restrictions. The BDS can\n> > > +\t\t * only scale with the same factor in both directions, and the\n> > > +\t\t * scaling factor is limited to a multiple of 1/32. At the\n> > > +\t\t * moment the ImgU driver hides these constraints by applying\n> > > +\t\t * additional cropping, this should be fixed on the driver\n> > > +\t\t * side, and cropping should be exposed to us.\n> > > +\t\t */\n> > > +\t} else {\n> > > +\t\t/*\n> > > +\t\t * \\todo: Properly support cropping when the ImgU driver\n> > > +\t\t * interface will be cleaned up.\n> > > +\t\t */\n> > > +\t\tcfg.size = sensorFormat_.size;\n> > > +\t}\n> > > +\n> > > +\t/*\n> > > +\t * Clamp the size to match the ImgU alignment constraints. The width\n> > > +\t * shall be a multiple of 8 pixels and the height a multiple of 4\n> > > +\t * pixels.\n> > > +\t */\n> > > +\tif (cfg.size.width % 8 || cfg.size.height % 4) {\n> > > +\t\tcfg.size.width &= ~7;\n> > > +\t\tcfg.size.height &= ~3;\n> > > +\t}\n> > > +\n> > > +\tcfg.bufferCount = IPU3_BUFFER_COUNT;\n> > > +}\n> > > +\n> > > +CameraConfiguration::Status IPU3CameraConfiguration::validate()\n> > > +{\n> > > +\tconst CameraSensor *sensor = data_->cio2_.sensor_;\n> > > +\tStatus status = Valid;\n> > > +\n> > > +\tif (config_.empty())\n> > > +\t\treturn Invalid;\n> > > +\n> > > +\t/* Cap the number of entries to the available streams. */\n> > > +\tif (config_.size() > 2) {\n> > > +\t\tconfig_.resize(2);\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\t/*\n> > > +\t * Select the sensor format by collecting the maximum width and height\n> > > +\t * and picking the closest larger match, as the IPU3 can downscale\n> > > +\t * only. If no resolution is requested for any stream, or if no sensor\n> > > +\t * resolution is large enough, pick the largest one.\n> > > +\t */\n> > > +\tSize size = {};\n> > > +\n> > > +\tfor (const StreamConfiguration &cfg : config_) {\n> > > +\t\tif (cfg.size.width > size.width)\n> > > +\t\t\tsize.width = cfg.size.width;\n> > > +\t\tif (cfg.size.height > size.height)\n> > > +\t\t\tsize.height = cfg.size.height;\n> > > +\t}\n> > > +\n> > > +\tif (!size.width || !size.height)\n> > > +\t\tsize = sensor->resolution();\n> > > +\n> > > +\tsensorFormat_ = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR10_1X10,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG10_1X10,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG10_1X10,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB10_1X10 },\n> > > +\t\t\t\t\t  size);\n> > > +\tif (!sensorFormat_.size.width || !sensorFormat_.size.height)\n> > > +\t\tsensorFormat_.size = sensor->resolution();\n> > > +\n> > > +\t/*\n> > > +\t * Verify and update all configuration entries, and assign a stream to\n> > > +\t * each of them. The viewfinder stream can scale, while the output\n> > > +\t * stream can crop only, so select the output stream when the requested\n> > > +\t * resolution is equal to the sensor resolution, and the viewfinder\n> > > +\t * stream otherwise.\n> > > +\t */\n> > > +\tstd::set<const IPU3Stream *> availableStreams = {\n> > > +\t\t&data_->outStream_,\n> > > +\t\t&data_->vfStream_,\n> > > +\t};\n> > > +\n> > > +\tstreams_.clear();\n> > > +\tstreams_.reserve(config_.size());\n> > > +\n> > > +\tfor (unsigned int i = 0; i < config_.size(); ++i) {\n> > > +\t\tStreamConfiguration &cfg = config_[i];\n> > > +\t\tconst unsigned int pixelFormat = cfg.pixelFormat;\n> > > +\t\tconst Size size = cfg.size;\n> > > +\t\tconst IPU3Stream *stream;\n> > > +\n> > > +\t\tif (cfg.size == sensorFormat_.size)\n> > > +\t\t\tstream = &data_->outStream_;\n> > > +\t\telse\n> > > +\t\t\tstream = &data_->vfStream_;\n> > > +\n> > > +\t\tif (availableStreams.find(stream) == availableStreams.end())\n> > > +\t\t\tstream = *availableStreams.begin();\n> > > +\n> > > +\t\tLOG(IPU3, Debug)\n> > > +\t\t\t<< \"Assigned '\" << stream->name_ << \"' to stream \" << i;\n> > > +\n> > > +\t\tbool scale = stream == &data_->vfStream_;\n> > > +\t\tadjustStream(i, scale);\n> > > +\n> > > +\t\tif (cfg.pixelFormat != pixelFormat || cfg.size != size) {\n> > > +\t\t\tLOG(IPU3, Debug)\n> > > +\t\t\t\t<< \"Stream \" << i << \" configuration adjusted to \"\n> > > +\t\t\t\t<< cfg.toString();\n> > > +\t\t\tstatus = Adjusted;\n> > > +\t\t}\n> > > +\n> > > +\t\tstreams_.push_back(stream);\n> > > +\t\tavailableStreams.erase(stream);\n> > > +\t}\n> > > +\n> > > +\treturn status;\n> > > +}\n> > > +\n> > >  PipelineHandlerIPU3::PipelineHandlerIPU3(CameraManager *manager)\n> > >  \t: PipelineHandler(manager), cio2MediaDev_(nullptr), imguMediaDev_(nullptr)\n> > >  {\n> > > @@ -211,12 +383,14 @@ CameraConfiguration *PipelineHandlerIPU3::generateConfiguration(Camera *camera,\n> > >  \tconst StreamRoles &roles)\n> > >  {\n> > >  \tIPU3CameraData *data = cameraData(camera);\n> > > -\tCameraConfiguration *config = new CameraConfiguration();\n> > > +\tIPU3CameraConfiguration *config;\n> > >  \tstd::set<IPU3Stream *> streams = {\n> > >  \t\t&data->outStream_,\n> > >  \t\t&data->vfStream_,\n> > >  \t};\n> > >\n> > > +\tconfig = new IPU3CameraConfiguration(camera, data);\n> > > +\n> > >  \tfor (const StreamRole role : roles) {\n> > >  \t\tStreamConfiguration cfg = {};\n> > >  \t\tIPU3Stream *stream = nullptr;\n> > > @@ -296,71 +470,25 @@ CameraConfiguration *PipelineHandlerIPU3::generateConfiguration(Camera *camera,\n> > >\n> > >  \t\tstreams.erase(stream);\n> > >\n> > > -\t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> > > -\t\tcfg.bufferCount = IPU3_BUFFER_COUNT;\n> > > -\n> > >  \t\tconfig->addConfiguration(cfg);\n> > >  \t}\n> > >\n> > > +\tconfig->validate();\n> > > +\n> > >  \treturn config;\n> > >  }\n> > >\n> > > -int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n> > > +int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *c)\n> > >  {\n> > > +\tIPU3CameraConfiguration *config =\n> > > +\t\tstatic_cast<IPU3CameraConfiguration *>(c);\n> > >  \tIPU3CameraData *data = cameraData(camera);\n> > >  \tIPU3Stream *outStream = &data->outStream_;\n> > >  \tIPU3Stream *vfStream = &data->vfStream_;\n> > >  \tCIO2Device *cio2 = &data->cio2_;\n> > >  \tImgUDevice *imgu = data->imgu_;\n> > > -\tSize sensorSize = {};\n> > >  \tint ret;\n> > >\n> > > -\toutStream->active_ = false;\n> > > -\tvfStream->active_ = false;\n> > > -\tfor (StreamConfiguration &cfg : *config) {\n> > > -\t\t/*\n> > > -\t\t * Pick a stream for the configuration entry.\n> > > -\t\t * \\todo: This is a naive temporary implementation that will be\n> > > -\t\t * reworked when implementing camera configuration validation.\n> > > -\t\t */\n> > > -\t\tIPU3Stream *stream = vfStream->active_ ? outStream : vfStream;\n> > > -\n> > > -\t\t/*\n> > > -\t\t * Verify that the requested size respects the IPU3 alignment\n> > > -\t\t * requirements (the image width shall be a multiple of 8\n> > > -\t\t * pixels and its height a multiple of 4 pixels) and the camera\n> > > -\t\t * maximum sizes.\n> > > -\t\t *\n> > > -\t\t * \\todo: Consider the BDS scaling factor requirements: \"the\n> > > -\t\t * downscaling factor must be an integer value multiple of 1/32\"\n> > > -\t\t */\n> > > -\t\tif (cfg.size.width % 8 || cfg.size.height % 4) {\n> > > -\t\t\tLOG(IPU3, Error)\n> > > -\t\t\t\t<< \"Invalid stream size: bad alignment\";\n> > > -\t\t\treturn -EINVAL;\n> > > -\t\t}\n> > > -\n> > > -\t\tconst Size &resolution = cio2->sensor_->resolution();\n> > > -\t\tif (cfg.size.width > resolution.width ||\n> > > -\t\t    cfg.size.height > resolution.height) {\n> > > -\t\t\tLOG(IPU3, Error)\n> > > -\t\t\t\t<< \"Invalid stream size: larger than sensor resolution\";\n> > > -\t\t\treturn -EINVAL;\n> > > -\t\t}\n> > > -\n> > > -\t\t/*\n> > > -\t\t * Collect the maximum width and height: IPU3 can downscale\n> > > -\t\t * only.\n> > > -\t\t */\n> > > -\t\tif (cfg.size.width > sensorSize.width)\n> > > -\t\t\tsensorSize.width = cfg.size.width;\n> > > -\t\tif (cfg.size.height > sensorSize.height)\n> > > -\t\t\tsensorSize.height = cfg.size.height;\n> > > -\n> > > -\t\tstream->active_ = true;\n> > > -\t\tcfg.setStream(stream);\n> > > -\t}\n> > > -\n> > >  \t/*\n> > >  \t * \\todo: Enable links selectively based on the requested streams.\n> > >  \t * As of now, enable all links unconditionally.\n> > > @@ -373,6 +501,7 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n> > >  \t * Pass the requested stream size to the CIO2 unit and get back the\n> > >  \t * adjusted format to be propagated to the ImgU output devices.\n> > >  \t */\n> > > +\tconst Size &sensorSize = config->sensorFormat().size;\n> > >  \tV4L2DeviceFormat cio2Format = {};\n> > >  \tret = cio2->configure(sensorSize, &cio2Format);\n> > >  \tif (ret)\n> > > @@ -383,8 +512,22 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n> > >  \t\treturn ret;\n> > >\n> > >  \t/* Apply the format to the configured streams output devices. */\n> > > -\tfor (StreamConfiguration &cfg : *config) {\n> > > -\t\tIPU3Stream *stream = static_cast<IPU3Stream *>(cfg.stream());\n> > > +\toutStream->active_ = false;\n> > > +\tvfStream->active_ = false;\n> > > +\n> > > +\tfor (unsigned int i = 0; i < config->size(); ++i) {\n> > > +\t\t/*\n> > > +\t\t * Use a const_cast<> here instead of storing a mutable stream\n> > > +\t\t * pointer in the configuration to let the compiler catch\n> > > +\t\t * unwanted modifications of camera data in the configuration\n> > > +\t\t * validate() implementation.\n> > > +\t\t */\n> > > +\t\tIPU3Stream *stream = const_cast<IPU3Stream *>(config->streams()[i]);\n> > > +\t\tStreamConfiguration &cfg = (*config)[i];\n> > > +\n> > > +\t\tstream->active_ = true;\n> > > +\t\tcfg.setStream(stream);\n> > > +\n> > >  \t\tret = imgu->configureOutput(stream->device_, cfg);\n> > >  \t\tif (ret)\n> > >  \t\t\treturn ret;\n> > > diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> > > index a1a4f205b4aa..42944c64189b 100644\n> > > --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> > > +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> > > @@ -5,6 +5,7 @@\n> > >   * rkisp1.cpp - Pipeline handler for Rockchip ISP1\n> > >   */\n> > >\n> > > +#include <algorithm>\n> > >  #include <iomanip>\n> > >  #include <memory>\n> > >  #include <vector>\n> > > @@ -45,6 +46,29 @@ public:\n> > >  \tCameraSensor *sensor_;\n> > >  };\n> > >\n> > > +class RkISP1CameraConfiguration : public CameraConfiguration\n> > > +{\n> > > +public:\n> > > +\tRkISP1CameraConfiguration(Camera *camera, RkISP1CameraData *data);\n> > > +\n> > > +\tStatus validate() override;\n> > > +\n> > > +\tconst V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }\n> > > +\n> > > +private:\n> > > +\tstatic constexpr unsigned int RKISP1_BUFFER_COUNT = 4;\n> > > +\n> > > +\t/*\n> > > +\t * The RkISP1CameraData instance is guaranteed to be valid as long as the\n> > > +\t * corresponding Camera instance is valid. In order to borrow a\n> > > +\t * reference to the camera data, store a new reference to the camera.\n> > > +\t */\n> > > +\tstd::shared_ptr<Camera> camera_;\n> > > +\tconst RkISP1CameraData *data_;\n> > > +\n> > > +\tV4L2SubdeviceFormat sensorFormat_;\n> > > +};\n> > > +\n> > >  class PipelineHandlerRkISP1 : public PipelineHandler\n> > >  {\n> > >  public:\n> > > @@ -68,8 +92,6 @@ public:\n> > >  \tbool match(DeviceEnumerator *enumerator) override;\n> > >\n> > >  private:\n> > > -\tstatic constexpr unsigned int RKISP1_BUFFER_COUNT = 4;\n> > > -\n> > >  \tRkISP1CameraData *cameraData(const Camera *camera)\n> > >  \t{\n> > >  \t\treturn static_cast<RkISP1CameraData *>(\n> > > @@ -88,6 +110,95 @@ private:\n> > >  \tCamera *activeCamera_;\n> > >  };\n> > >\n> > > +RkISP1CameraConfiguration::RkISP1CameraConfiguration(Camera *camera,\n> > > +\t\t\t\t\t\t     RkISP1CameraData *data)\n> > > +\t: CameraConfiguration()\n> > > +{\n> > > +\tcamera_ = camera->shared_from_this();\n> > > +\tdata_ = data;\n> > > +}\n> > > +\n> > > +CameraConfiguration::Status RkISP1CameraConfiguration::validate()\n> > > +{\n> > > +\tstatic const std::array<unsigned int, 8> formats{\n> > > +\t\tV4L2_PIX_FMT_YUYV,\n> > > +\t\tV4L2_PIX_FMT_YVYU,\n> > > +\t\tV4L2_PIX_FMT_VYUY,\n> > > +\t\tV4L2_PIX_FMT_NV16,\n> > > +\t\tV4L2_PIX_FMT_NV61,\n> > > +\t\tV4L2_PIX_FMT_NV21,\n> > > +\t\tV4L2_PIX_FMT_NV12,\n> > > +\t\tV4L2_PIX_FMT_GREY,\n> > > +\t};\n> > > +\n> > > +\tconst CameraSensor *sensor = data_->sensor_;\n> > > +\tStatus status = Valid;\n> > > +\n> > > +\tif (config_.empty())\n> > > +\t\treturn Invalid;\n> > > +\n> > > +\t/* Cap the number of entries to the available streams. */\n> > > +\tif (config_.size() > 1) {\n> > > +\t\tconfig_.resize(1);\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\tStreamConfiguration &cfg = config_[0];\n> > > +\n> > > +\t/* Adjust the pixel format. */\n> > > +\tif (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==\n> > > +\t    formats.end()) {\n> > > +\t\tLOG(RkISP1, Debug) << \"Adjusting format to NV12\";\n> > > +\t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\t/* Select the sensor format. */\n> > > +\tsensorFormat_ = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR12_1X12,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG12_1X12,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG12_1X12,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB12_1X12,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SBGGR10_1X10,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG10_1X10,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG10_1X10,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB10_1X10,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SBGGR8_1X8,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG8_1X8,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG8_1X8,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB8_1X8 },\n> > > +\t\t\t\t\t  cfg.size);\n> > > +\tif (!sensorFormat_.size.width || !sensorFormat_.size.height)\n> > > +\t\tsensorFormat_.size = sensor->resolution();\n> > > +\n> > > +\t/*\n> > > +\t * Provide a suitable default that matches the sensor aspect\n> > > +\t * ratio and clamp the size to the hardware bounds.\n> > > +\t *\n> > > +\t * \\todo: Check the hardware alignment constraints.\n> > > +\t */\n> > > +\tconst Size size = cfg.size;\n> > > +\n> > > +\tif (!cfg.size.width || !cfg.size.height) {\n> > > +\t\tcfg.size.width = 1280;\n> > > +\t\tcfg.size.height = 1280 * sensorFormat_.size.height\n> > > +\t\t\t\t/ sensorFormat_.size.width;\n> > > +\t}\n> > > +\n> > > +\tcfg.size.width = std::max(32U, std::min(4416U, cfg.size.width));\n> > > +\tcfg.size.height = std::max(16U, std::min(3312U, cfg.size.height));\n> > > +\n> > > +\tif (cfg.size != size) {\n> > > +\t\tLOG(RkISP1, Debug)\n> > > +\t\t\t<< \"Adjusting size from \" << size.toString()\n> > > +\t\t\t<< \" to \" << cfg.size.toString();\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\tcfg.bufferCount = RKISP1_BUFFER_COUNT;\n> > > +\n> > > +\treturn status;\n> > > +}\n> > > +\n> > >  PipelineHandlerRkISP1::PipelineHandlerRkISP1(CameraManager *manager)\n> > >  \t: PipelineHandler(manager), dphy_(nullptr), isp_(nullptr),\n> > >  \t  video_(nullptr)\n> > > @@ -109,37 +220,31 @@ CameraConfiguration *PipelineHandlerRkISP1::generateConfiguration(Camera *camera\n> > >  \tconst StreamRoles &roles)\n> > >  {\n> > >  \tRkISP1CameraData *data = cameraData(camera);\n> > > -\tCameraConfiguration *config = new CameraConfiguration();\n> > > +\tCameraConfiguration *config = new RkISP1CameraConfiguration(camera, data);\n> > >\n> > >  \tif (!roles.empty()) {\n> > >  \t\tStreamConfiguration cfg{};\n> > >\n> > >  \t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> > >  \t\tcfg.size = data->sensor_->resolution();\n> > > -\t\tcfg.bufferCount = RKISP1_BUFFER_COUNT;\n> > >\n> > >  \t\tconfig->addConfiguration(cfg);\n> > >  \t}\n> > >\n> > > +\tconfig->validate();\n> > > +\n> > >  \treturn config;\n> > >  }\n> > >\n> > > -int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *config)\n> > > +int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)\n> > >  {\n> > > +\tRkISP1CameraConfiguration *config =\n> > > +\t\tstatic_cast<RkISP1CameraConfiguration *>(c);\n> > >  \tRkISP1CameraData *data = cameraData(camera);\n> > >  \tStreamConfiguration &cfg = config->at(0);\n> > >  \tCameraSensor *sensor = data->sensor_;\n> > >  \tint ret;\n> > >\n> > > -\t/* Verify the configuration. */\n> > > -\tconst Size &resolution = sensor->resolution();\n> > > -\tif (cfg.size.width > resolution.width ||\n> > > -\t    cfg.size.height > resolution.height) {\n> > > -\t\tLOG(RkISP1, Error)\n> > > -\t\t\t<< \"Invalid stream size: larger than sensor resolution\";\n> > > -\t\treturn -EINVAL;\n> > > -\t}\n> > > -\n> > >  \t/*\n> > >  \t * Configure the sensor links: enable the link corresponding to this\n> > >  \t * camera and disable all the other sensor links.\n> > > @@ -167,21 +272,7 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *config\n> > >  \t * Configure the format on the sensor output and propagate it through\n> > >  \t * the pipeline.\n> > >  \t */\n> > > -\tV4L2SubdeviceFormat format;\n> > > -\tformat = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR12_1X12,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SGBRG12_1X12,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SGRBG12_1X12,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SRGGB12_1X12,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SBGGR10_1X10,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SGBRG10_1X10,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SGRBG10_1X10,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SRGGB10_1X10,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SBGGR8_1X8,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SGBRG8_1X8,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SGRBG8_1X8,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SRGGB8_1X8 },\n> > > -\t\t\t\t   cfg.size);\n> > > -\n> > > +\tV4L2SubdeviceFormat format = config->sensorFormat();\n> > >  \tLOG(RkISP1, Debug) << \"Configuring sensor with \" << format.toString();\n> > >\n> > >  \tret = sensor->setFormat(&format);\n> > > diff --git a/src/libcamera/pipeline/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo.cpp\n> > > index 8254e1fdac1e..f7ffeb439cf3 100644\n> > > --- a/src/libcamera/pipeline/uvcvideo.cpp\n> > > +++ b/src/libcamera/pipeline/uvcvideo.cpp\n> > > @@ -39,6 +39,14 @@ public:\n> > >  \tStream stream_;\n> > >  };\n> > >\n> > > +class UVCCameraConfiguration : public CameraConfiguration\n> > > +{\n> > > +public:\n> > > +\tUVCCameraConfiguration();\n> > > +\n> > > +\tStatus validate() override;\n> > > +};\n> > > +\n> > >  class PipelineHandlerUVC : public PipelineHandler\n> > >  {\n> > >  public:\n> > > @@ -68,6 +76,45 @@ private:\n> > >  \t}\n> > >  };\n> > >\n> > > +UVCCameraConfiguration::UVCCameraConfiguration()\n> > > +\t: CameraConfiguration()\n> > > +{\n> > > +}\n> > > +\n> > > +CameraConfiguration::Status UVCCameraConfiguration::validate()\n> > > +{\n> > > +\tStatus status = Valid;\n> > > +\n> > > +\tif (config_.empty())\n> > > +\t\treturn Invalid;\n> > > +\n> > > +\t/* Cap the number of entries to the available streams. */\n> > > +\tif (config_.size() > 1) {\n> > > +\t\tconfig_.resize(1);\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\tStreamConfiguration &cfg = config_[0];\n> > > +\n> > > +\t/* \\todo: Validate the configuration against the device capabilities. */\n> > > +\tconst unsigned int pixelFormat = cfg.pixelFormat;\n> > > +\tconst Size size = cfg.size;\n> > > +\n> > > +\tcfg.pixelFormat = V4L2_PIX_FMT_YUYV;\n> > > +\tcfg.size = { 640, 480 };\n> > > +\n> > > +\tif (cfg.pixelFormat != pixelFormat || cfg.size != size) {\n> > > +\t\tLOG(UVC, Debug)\n> > > +\t\t\t<< \"Adjusting configuration from \" << cfg.toString()\n> > > +\t\t\t<< \" to \" << cfg.size.toString() << \"-YUYV\";\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\tcfg.bufferCount = 4;\n> > > +\n> > > +\treturn status;\n> > > +}\n> > > +\n> > >  PipelineHandlerUVC::PipelineHandlerUVC(CameraManager *manager)\n> > >  \t: PipelineHandler(manager)\n> > >  {\n> > > @@ -76,10 +123,10 @@ PipelineHandlerUVC::PipelineHandlerUVC(CameraManager *manager)\n> > >  CameraConfiguration *PipelineHandlerUVC::generateConfiguration(Camera *camera,\n> > >  \tconst StreamRoles &roles)\n> > >  {\n> > > -\tCameraConfiguration *config = new CameraConfiguration();\n> > > +\tCameraConfiguration *config = new UVCCameraConfiguration();\n> > >\n> > >  \tif (!roles.empty()) {\n> > > -\t\tStreamConfiguration cfg;\n> > > +\t\tStreamConfiguration cfg{};\n> > >\n> > >  \t\tcfg.pixelFormat = V4L2_PIX_FMT_YUYV;\n> > >  \t\tcfg.size = { 640, 480 };\n> > > @@ -88,6 +135,8 @@ CameraConfiguration *PipelineHandlerUVC::generateConfiguration(Camera *camera,\n> > >  \t\tconfig->addConfiguration(cfg);\n> > >  \t}\n> > >\n> > > +\tconfig->validate();\n> > > +\n> > >  \treturn config;\n> > >  }\n> > >\n> > > diff --git a/src/libcamera/pipeline/vimc.cpp b/src/libcamera/pipeline/vimc.cpp\n> > > index 2bf85d0a0b32..2a61d2893e3a 100644\n> > > --- a/src/libcamera/pipeline/vimc.cpp\n> > > +++ b/src/libcamera/pipeline/vimc.cpp\n> > > @@ -5,6 +5,8 @@\n> > >   * vimc.cpp - Pipeline handler for the vimc device\n> > >   */\n> > >\n> > > +#include <algorithm>\n> > > +\n> > >  #include <libcamera/camera.h>\n> > >  #include <libcamera/request.h>\n> > >  #include <libcamera/stream.h>\n> > > @@ -39,6 +41,14 @@ public:\n> > >  \tStream stream_;\n> > >  };\n> > >\n> > > +class VimcCameraConfiguration : public CameraConfiguration\n> > > +{\n> > > +public:\n> > > +\tVimcCameraConfiguration();\n> > > +\n> > > +\tStatus validate() override;\n> > > +};\n> > > +\n> > >  class PipelineHandlerVimc : public PipelineHandler\n> > >  {\n> > >  public:\n> > > @@ -68,6 +78,57 @@ private:\n> > >  \t}\n> > >  };\n> > >\n> > > +VimcCameraConfiguration::VimcCameraConfiguration()\n> > > +\t: CameraConfiguration()\n> > > +{\n> > > +}\n> > > +\n> > > +CameraConfiguration::Status VimcCameraConfiguration::validate()\n> > > +{\n> > > +\tstatic const std::array<unsigned int, 3> formats{\n> > > +\t\tV4L2_PIX_FMT_BGR24,\n> > > +\t\tV4L2_PIX_FMT_RGB24,\n> > > +\t\tV4L2_PIX_FMT_ARGB32,\n> > > +\t};\n> > > +\n> > > +\tStatus status = Valid;\n> > > +\n> > > +\tif (config_.empty())\n> > > +\t\treturn Invalid;\n> > > +\n> > > +\t/* Cap the number of entries to the available streams. */\n> > > +\tif (config_.size() > 1) {\n> > > +\t\tconfig_.resize(1);\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\tStreamConfiguration &cfg = config_[0];\n> > > +\n> > > +\t/* Adjust the pixel format. */\n> > > +\tif (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==\n> > > +\t    formats.end()) {\n> > > +\t\tLOG(VIMC, Debug) << \"Adjusting format to RGB24\";\n> > > +\t\tcfg.pixelFormat = V4L2_PIX_FMT_RGB24;\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\t/* Clamp the size based on the device limits. */\n> > > +\tconst Size size = cfg.size;\n> > > +\n> > > +\tcfg.size.width = std::max(16U, std::min(4096U, cfg.size.width));\n> > > +\tcfg.size.height = std::max(16U, std::min(2160U, cfg.size.height));\n> > > +\n> > > +\tif (cfg.size != size) {\n> > > +\t\tLOG(VIMC, Debug)\n> > > +\t\t\t<< \"Adjusting size to \" << cfg.size.toString();\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\tcfg.bufferCount = 4;\n> > > +\n> > > +\treturn status;\n> > > +}\n> > > +\n> > >  PipelineHandlerVimc::PipelineHandlerVimc(CameraManager *manager)\n> > >  \t: PipelineHandler(manager)\n> > >  {\n> > > @@ -76,10 +137,10 @@ PipelineHandlerVimc::PipelineHandlerVimc(CameraManager *manager)\n> > >  CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera,\n> > >  \tconst StreamRoles &roles)\n> > >  {\n> > > -\tCameraConfiguration *config = new CameraConfiguration();\n> > > +\tCameraConfiguration *config = new VimcCameraConfiguration();\n> > >\n> > >  \tif (!roles.empty()) {\n> > > -\t\tStreamConfiguration cfg;\n> > > +\t\tStreamConfiguration cfg{};\n> > >\n> > >  \t\tcfg.pixelFormat = V4L2_PIX_FMT_RGB24;\n> > >  \t\tcfg.size = { 640, 480 };\n> > > @@ -88,6 +149,8 @@ CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera,\n> > >  \t\tconfig->addConfiguration(cfg);\n> > >  \t}\n> > >\n> > > +\tconfig->validate();\n> > > +\n> > >  \treturn config;\n> > >  }\n> > >\n> > > diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp\n> > > index de46e98880a2..dd56907d817e 100644\n> > > --- a/src/libcamera/pipeline_handler.cpp\n> > > +++ b/src/libcamera/pipeline_handler.cpp\n> > > @@ -248,17 +248,14 @@ void PipelineHandler::unlock()\n> > >   * is the Camera class which will receive configuration to apply from the\n> > >   * application.\n> > >   *\n> > > - * Each pipeline handler implementation is responsible for validating\n> > > - * that the configuration requested in \\a config can be achieved\n> > > - * exactly. Any difference in pixel format, frame size or any other\n> > > - * parameter shall result in the -EINVAL error being returned, and no\n> > > - * change in configuration being applied to the pipeline. If\n> > > - * configuration of a subset of the streams can't be satisfied, the\n> > > - * whole configuration is considered invalid.\n> > > + * The configuration is guaranteed to have been validated with\n> > > + * CameraConfiguration::valid(). The pipeline handler implementation shall not\n> > > + * perform further validation and may rely on any custom field stored in its\n> > > + * custom CameraConfiguration derived class.\n> > >   *\n> > > - * Once the configuration is validated and the camera configured, the pipeline\n> > > - * handler shall associate a Stream instance to each StreamConfiguration entry\n> > > - * in the CameraConfiguration with the StreamConfiguration::setStream() method.\n> > > + * When configuring the camera the pipeline handler shall associate a Stream\n> > > + * instance to each StreamConfiguration entry in the CameraConfiguration using\n> > > + * the StreamConfiguration::setStream() method.\n> > >   *\n> > >   * \\return 0 on success or a negative error code otherwise\n> > >   */\n> > > diff --git a/test/camera/capture.cpp b/test/camera/capture.cpp\n> > > index 137aa649a505..f122f79bb1ec 100644\n> > > --- a/test/camera/capture.cpp\n> > > +++ b/test/camera/capture.cpp\n> > > @@ -45,7 +45,7 @@ protected:\n> > >  \t\tCameraTest::init();\n> > >\n> > >  \t\tconfig_ = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> > > -\t\tif (!config_) {\n> > > +\t\tif (!config_ || config_->size() != 1) {\n> > >  \t\t\tcout << \"Failed to generate default configuration\" << endl;\n> > >  \t\t\tCameraTest::cleanup();\n> > >  \t\t\treturn TestFail;\n> > > @@ -58,11 +58,6 @@ protected:\n> > >  \t{\n> > >  \t\tStreamConfiguration &cfg = config_->at(0);\n> > >\n> > > -\t\tif (!config_->isValid()) {\n> > > -\t\t\tcout << \"Failed to read default configuration\" << endl;\n> > > -\t\t\treturn TestFail;\n> > > -\t\t}\n> > > -\n> > >  \t\tif (camera_->acquire()) {\n> > >  \t\t\tcout << \"Failed to acquire the camera\" << endl;\n> > >  \t\t\treturn TestFail;\n> > > diff --git a/test/camera/configuration_default.cpp b/test/camera/configuration_default.cpp\n> > > index d5cefc1127c9..81055da1d513 100644\n> > > --- a/test/camera/configuration_default.cpp\n> > > +++ b/test/camera/configuration_default.cpp\n> > > @@ -22,7 +22,7 @@ protected:\n> > >\n> > >  \t\t/* Test asking for configuration for a video stream. */\n> > >  \t\tconfig = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> > > -\t\tif (!config || !config->isValid()) {\n> > > +\t\tif (!config || config->size() != 1) {\n> > >  \t\t\tcout << \"Default configuration invalid\" << endl;\n> > >  \t\t\tdelete config;\n> > >  \t\t\treturn TestFail;\n> > > @@ -35,7 +35,7 @@ protected:\n> > >  \t\t * stream roles returns an empty camera configuration.\n> > >  \t\t */\n> > >  \t\tconfig = camera_->generateConfiguration({});\n> > > -\t\tif (!config || config->isValid()) {\n> > > +\t\tif (!config || config->size() != 0) {\n> > >  \t\t\tcout << \"Failed to retrieve configuration for empty roles list\"\n> > >  \t\t\t     << endl;\n> > >  \t\t\tdelete config;\n> > > diff --git a/test/camera/configuration_set.cpp b/test/camera/configuration_set.cpp\n> > > index 23c611a93355..a4e2da16a88b 100644\n> > > --- a/test/camera/configuration_set.cpp\n> > > +++ b/test/camera/configuration_set.cpp\n> > > @@ -21,7 +21,7 @@ protected:\n> > >  \t\tCameraTest::init();\n> > >\n> > >  \t\tconfig_ = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> > > -\t\tif (!config_) {\n> > > +\t\tif (!config_ || config_->size() != 1) {\n> > >  \t\t\tcout << \"Failed to generate default configuration\" << endl;\n> > >  \t\t\tCameraTest::cleanup();\n> > >  \t\t\treturn TestFail;\n> > > @@ -34,11 +34,6 @@ protected:\n> > >  \t{\n> > >  \t\tStreamConfiguration &cfg = config_->at(0);\n> > >\n> > > -\t\tif (!config_->isValid()) {\n> > > -\t\t\tcout << \"Failed to read default configuration\" << endl;\n> > > -\t\t\treturn TestFail;\n> > > -\t\t}\n> > > -\n> > >  \t\tif (camera_->acquire()) {\n> > >  \t\t\tcout << \"Failed to acquire the camera\" << endl;\n> > >  \t\t\treturn TestFail;\n> > > --\n> > > Regards,\n> > >\n> > > Laurent Pinchart\n> > >\n> > > _______________________________________________\n> > > libcamera-devel mailing list\n> > > libcamera-devel@lists.libcamera.org\n> > > https://lists.libcamera.org/listinfo/libcamera-devel\n> \n> \n> \n> -- \n> Regards,\n> \n> Laurent Pinchart\n> _______________________________________________\n> libcamera-devel mailing list\n> libcamera-devel@lists.libcamera.org\n> https://lists.libcamera.org/listinfo/libcamera-devel","headers":{"Return-Path":"<niklas.soderlund@ragnatech.se>","Received":["from mail-lj1-x241.google.com (mail-lj1-x241.google.com\n\t[IPv6:2a00:1450:4864:20::241])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 1AA1960C02\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 22 May 2019 12:55:19 +0200 (CEST)","by mail-lj1-x241.google.com with SMTP id r76so1613694lja.12\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 22 May 2019 03:55:19 -0700 (PDT)","from localhost (89-233-230-99.cust.bredband2.com. [89.233.230.99])\n\tby smtp.gmail.com with ESMTPSA id\n\t78sm6268740lje.81.2019.05.22.03.55.16\n\t(version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256);\n\tWed, 22 May 2019 03:55:16 -0700 (PDT)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=ragnatech-se.20150623.gappssmtp.com; s=20150623;\n\th=date:from:to:cc:subject:message-id:references:mime-version\n\t:content-disposition:content-transfer-encoding:in-reply-to\n\t:user-agent; bh=gMxjbeofT9R0VVaqYbwOcgJrtEWdfQDmitWXTKSinX0=;\n\tb=gPTkJJaE4A7Qzl9Rz6WVxldaOkZ5lUlXzDJXcb2fg+RhaZWiGrDvENtZUo3dbeFzhh\n\tY2L9aTqRToNeneq9GmjmLiQ9PWCquQwbVouHkBhlU3nvQSAhMuaS2TD+M8YmyUHrB/5Q\n\tpESNlDaxwzUbr7CPMT+DD2F9UnDeJNsu9sirlDoRID3xnkinbhfoRcVF5gtVuRrzCuxM\n\ta0VfeDDIK2EbgkXZHNDI6eIaalPPpmxyswS9H//dCy1dSvFokKOJRPuPHpx3/0pmCNvP\n\tFLLl+dPq5qBAA1CVKh3uJK/FC+cttLZJDD/17lwDGqyW0HiXodksHaisaBpvXdKJNoQ3\n\tmdbQ==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20161025;\n\th=x-gm-message-state:date:from:to:cc:subject:message-id:references\n\t:mime-version:content-disposition:content-transfer-encoding\n\t:in-reply-to:user-agent;\n\tbh=gMxjbeofT9R0VVaqYbwOcgJrtEWdfQDmitWXTKSinX0=;\n\tb=DzvHIhP+m5TYMoIjb994SbVffzc/or9n857HGlKhxGyQ7tEZ23xuuPrsrVK0BNDzBL\n\t0GFrjaZUKa4no6iFYEEgNnjfadDSN3SrkGc1yWIH7P32Qn8qxBubZvbRIcxDmCMx+1tQ\n\tLFWDgrFRieTCE2JHuSny7Wur5j9ZWOG4NmQvj3s+mli4E+OM7CbZuMwZuEZ+2oUTZv8g\n\tQiPpjHOMmcGstgAoLVWpQis+2VCbQnKimRl+4hQ5xU+lFRnkHewDkqOX6RnDNdI459Xs\n\te+WnlR925wuBDSy7eaieeF53s08Vys75YuzN/43mYEwLT5kdwlGnZDPcEVgthzES13l6\n\tE8Hw==","X-Gm-Message-State":"APjAAAWbYg4qwBFvZfjZF/2PxmFmA91mmSTkQKGLUG1dFcX6PJ9yw2u0\n\t/ZqEi9sd1ili6hYxt8CFNMvaiQ==","X-Google-Smtp-Source":"APXvYqwhaHfrS/fyrgO52Rw0BN+P5pCaZXHJ4NAf1JK8I5OLy6jMtll74aaTpCAFmBVXBqExmGSUqA==","X-Received":"by 2002:a2e:9d09:: with SMTP id\n\tt9mr11149021lji.151.1558522518063; \n\tWed, 22 May 2019 03:55:18 -0700 (PDT)","Date":"Wed, 22 May 2019 12:55:16 +0200","From":"Niklas =?iso-8859-1?q?S=F6derlund?= <niklas.soderlund@ragnatech.se>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"Jacopo Mondi <jacopo@jmondi.org>, libcamera-devel@lists.libcamera.org","Message-ID":"<20190522105516.GB1651@bigcity.dyn.berto.se>","References":"<20190519150047.12444-1-laurent.pinchart@ideasonboard.com>\n\t<20190519150047.12444-7-laurent.pinchart@ideasonboard.com>\n\t<20190521084950.z5ahk3fiptltok7i@uno.localdomain>\n\t<20190521125109.GD5674@pendragon.ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=iso-8859-1","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<20190521125109.GD5674@pendragon.ideasonboard.com>","User-Agent":"Mutt/1.11.4 (2019-03-13)","Subject":"Re: [libcamera-devel] [PATCH v2 6/6] libcamera: camera: Add a\n\tvalidation API to the CameraConfiguration class","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.23","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Wed, 22 May 2019 10:55:19 -0000"}},{"id":1659,"web_url":"https://patchwork.libcamera.org/comment/1659/","msgid":"<20190522110230.GC1651@bigcity.dyn.berto.se>","date":"2019-05-22T11:02:30","subject":"Re: [libcamera-devel] [PATCH v2 6/6] libcamera: camera: Add a\n\tvalidation API to the CameraConfiguration class","submitter":{"id":5,"url":"https://patchwork.libcamera.org/api/people/5/","name":"Niklas Söderlund","email":"niklas.soderlund@ragnatech.se"},"content":"On 2019-05-21 15:49:55 +0300, Laurent Pinchart wrote:\n> Hi Jacopo,\n> \n> On Tue, May 21, 2019 at 12:05:06AM +0200, Jacopo Mondi wrote:\n> > Hi Laurent,\n> > \n> > In order to validate if the API is dump-proof enough, I'm starting\n> > the review from the last patch, so I might be missing some pieces\n> > here and there...\n> > \n> > On Sun, May 19, 2019 at 06:00:47PM +0300, Laurent Pinchart wrote:\n> > > The CameraConfiguration class implements a simple storage of\n> > > StreamConfiguration with internal validation limited to verifying that\n> > > the stream configurations are not empty. Extend this mechanism by\n> > > implementing a smart validate() method backed by pipeline handlers.\n> > >\n> > > This new mechanism changes the semantics of the camera configuration.\n> > \n> > s/semantics/semantic ?\n> \n> I think you're right, I'll fix that.\n> \n> > > The Camera::generateConfiguration() operation still generates a default\n> > > configuration based on roles, but now also supports generating empty\n> > > configurations to be filled by applications. Applications can inspect\n> > > the configuration, optionally modify it, and validate it. The validation\n> > > implements \"try\" semantics and adjusts invalid configurations instead of\n> > > rejecting them completely. Applications then decide whether to accept\n> > > the modified configuration, or try again with a different set of\n> > > parameters. Once the configuration is valid, it is passed to\n> > > Camera::configure(), and pipeline handlers are guaranteed that the\n> > > configuration they receive is valid.\n> > >\n> > > A reference to the Camera may need to be stored in the\n> > > CameraConfiguration derived classes in order to access it from their\n> > > validate() implementation. This must be stored as a std::shared_ptr<> as\n> > > the CameraConfiguration instances belong to applications. In order to\n> > > make this possible, make the Camera class inherit from\n> > > std::shared_from_this<>.\n> > \n> > If I got this right we'll have a\n> > \n> > CameraConfiguration::validate() and a\n> > Camera.configure()\n> > \n> > and CameraConfiguration has to keep a reference to the Camera to\n> > access it.\n> > \n> > Can we provide a Camera::validate(StreamConfiguration *) instead?\n> > We could handle the shared reference in the Camera and not let\n> > CameraConfiguration subclasses have to deal with it in this way?\n> \n> I assume you meant a Camera::validate(CameraConfiguration *), as we want\n> to validate the whole configuration, not stream per stream (the idea of\n> this series is to support cross-stream validation constraints).\n> \n> First of all, the method would need to be called\n> Camera::validateConfiguration(). This would need to be delegated to\n> PipelineHandler::validateConfiguration(Camera *, CameraConfiguration *).\n> The pipeline handler would then need to cast the camera configuration to\n> its custom configuration derived class (opening the door to applications\n> calling Camera::validateConfiguration() with a configuration for the\n> wrong camera, but that's already possible with Camera::configure() I\n> supposed). We could try that, but I think it would make the API less\n> clean for applications, for little benefits in my opinion (but we may\n> have a different view of the benefits :-)).\n> \n> > >\n> > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> > > ---\n> > >  include/libcamera/camera.h               |  17 +-\n> > >  src/cam/main.cpp                         |   2 +-\n> > >  src/libcamera/camera.cpp                 |  80 +++++--\n> > >  src/libcamera/pipeline/ipu3/ipu3.cpp     | 255 ++++++++++++++++++-----\n> > >  src/libcamera/pipeline/rkisp1/rkisp1.cpp | 149 ++++++++++---\n> > >  src/libcamera/pipeline/uvcvideo.cpp      |  53 ++++-\n> > >  src/libcamera/pipeline/vimc.cpp          |  67 +++++-\n> > >  src/libcamera/pipeline_handler.cpp       |  17 +-\n> > >  test/camera/capture.cpp                  |   7 +-\n> > >  test/camera/configuration_default.cpp    |   4 +-\n> > >  test/camera/configuration_set.cpp        |   7 +-\n> > >  11 files changed, 516 insertions(+), 142 deletions(-)\n> > >\n> > > diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> > > index 144133c5de9f..8c0049a1dd94 100644\n> > > --- a/include/libcamera/camera.h\n> > > +++ b/include/libcamera/camera.h\n> > > @@ -25,14 +25,19 @@ class Request;\n> > >  class CameraConfiguration\n> > >  {\n> > >  public:\n> > > +\tenum Status {\n> > > +\t\tValid,\n> > > +\t\tAdjusted,\n> > > +\t\tInvalid,\n> > > +\t};\n> > > +\n> > >  \tusing iterator = std::vector<StreamConfiguration>::iterator;\n> > >  \tusing const_iterator = std::vector<StreamConfiguration>::const_iterator;\n> > >\n> > > -\tCameraConfiguration();\n> > > +\tvirtual ~CameraConfiguration();\n> > >\n> > >  \tvoid addConfiguration(const StreamConfiguration &cfg);\n> > > -\n> > > -\tbool isValid() const;\n> > > +\tvirtual Status validate() = 0;\n> > >\n> > >  \tStreamConfiguration &at(unsigned int index);\n> > >  \tconst StreamConfiguration &at(unsigned int index) const;\n> > > @@ -53,11 +58,13 @@ public:\n> > >  \tbool empty() const;\n> > >  \tstd::size_t size() const;\n> > >\n> > > -private:\n> > > +protected:\n> > > +\tCameraConfiguration();\n> > > +\n> > >  \tstd::vector<StreamConfiguration> config_;\n> > >  };\n> > >\n> > > -class Camera final\n> > > +class Camera final : public std::enable_shared_from_this<Camera>\n> > >  {\n> > >  public:\n> > >  \tstatic std::shared_ptr<Camera> create(PipelineHandler *pipe,\n> > > diff --git a/src/cam/main.cpp b/src/cam/main.cpp\n> > > index 7550ae4f3428..23da5c687d89 100644\n> > > --- a/src/cam/main.cpp\n> > > +++ b/src/cam/main.cpp\n> > > @@ -116,7 +116,7 @@ static CameraConfiguration *prepareCameraConfig()\n> > >  \t}\n> > >\n> > >  \tCameraConfiguration *config = camera->generateConfiguration(roles);\n> > > -\tif (!config || !config->isValid()) {\n> > > +\tif (!config || config->size() != roles.size()) {\n> > >  \t\tstd::cerr << \"Failed to get default stream configuration\"\n> > >  \t\t\t  << std::endl;\n> > >  \t\tdelete config;\n> > > diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> > > index 0e80691ed862..9da5d4f613f4 100644\n> > > --- a/src/libcamera/camera.cpp\n> > > +++ b/src/libcamera/camera.cpp\n> > > @@ -52,6 +52,28 @@ LOG_DECLARE_CATEGORY(Camera)\n> > >   * operator[](int) returns a reference to the StreamConfiguration based on its\n> > >   * insertion index. Accessing a stream configuration with an invalid index\n> > >   * results in undefined behaviour.\n> > > + *\n> > > + * CameraConfiguration instances are retrieved from the camera with\n> > > + * Camera::generateConfiguration(). Applications may then inspect the\n> > > + * configuration, modify it, and possibly add new stream configuration entries\n> > > + * with addConfiguration(). Once the camera configuration satisfies the\n> > > + * application, it shall be validated by a call to validate(). The validation\n> > > + * implements \"try\" semantics: it adjusts invalid configurations to the closest\n> > > + * achievable parameters instead of rejecting them completely. Applications\n> > > + * then decide whether to accept the modified configuration, or try again with\n> > > + * a different set of parameters. Once the configuration is valid, it is passed\n> > > + * to Camera::configure().\n> > > + */\n> > > +\n> > > +/**\n> > > + * \\enum CameraConfiguration::Status\n> > > + * \\brief Validity of a camera configuration\n> > > + * \\var CameraConfiguration::Valid\n> > > + * The configuration is fully valid\n> > > + * \\var CameraConfiguration::Adjusted\n> > > + * The configuration has been adjusted to a valid configuration\n> > > + * \\var CameraConfiguration::Invalid\n> > > + * The configuration is invalid and can't be adjusted automatically\n> > >   */\n> > >\n> > >  /**\n> > > @@ -73,6 +95,10 @@ CameraConfiguration::CameraConfiguration()\n> > >  {\n> > >  }\n> > >\n> > > +CameraConfiguration::~CameraConfiguration()\n> > > +{\n> > > +}\n> > > +\n> > >  /**\n> > >   * \\brief Add a stream configuration to the camera configuration\n> > >   * \\param[in] cfg The stream configuration\n> > > @@ -83,27 +109,31 @@ void CameraConfiguration::addConfiguration(const StreamConfiguration &cfg)\n> > >  }\n> > >\n> > >  /**\n> > > - * \\brief Check if the camera configuration is valid\n> > > + * \\fn CameraConfiguration::validate()\n> > > + * \\brief Validate and possibly adjust the camera configuration\n> > >   *\n> > > - * A camera configuration is deemed to be valid if it contains at least one\n> > > - * stream configuration and all stream configurations contain valid information.\n> > > - * Stream configurations are deemed to be valid if all fields are none zero.\n> > > + * This method adjusts the camera configuration to the closest valid\n> > > + * configuration and returns the validation status.\n> > >   *\n> > > - * \\return True if the configuration is valid\n> > > + * \\todo: Define exactly when to return each status code. Should stream\n> > > + * parameters set to 0 by the caller be adjusted without returning Adjusted ?\n> > > + * This would potentially be useful for applications but would get in the way\n> > > + * in Camera::configure(). Do we need an extra status code to signal this ?\n> > \n> > I'm not sure I did get why it gets in the way of configure()\n> \n> Because Camera::configure() calls validate() and returns an error if the\n> status is Adjusted or Invalid, as the application is responsible for\n> passing a fully valid configuration to Camera::configure().\n> \n> > > + *\n> > > + * \\todo: Handle validation of buffers count when refactoring the buffers API.\n> > > + *\n> > > + * \\return A CameraConfiguration::Status value that describes the validation\n> > > + * status.\n> > > + * \\retval CameraConfiguration::Invalid The configuration is invalid and can't\n> > > + * be adjusted. This may only occur in extreme cases such as when the\n> > > + * configuration is empty.\n> > \n> > nit: instead of describing how the returned configuration looks like,\n> > do you have an example of application provided parameters which might\n> > trigger and invalid use case ?\n> \n> Not at the moment, no. I have no other case in mind, but we may find\n> some in the future.\n> \n> > > + * \\retval CameraConfigutation::Adjusted The configuration has been adjusted\n> > > + * and is now valid. Parameters may have changed for any stream, and stream\n> > > + * configurations may have been removed. The caller shall check the\n> > > + * configuration carefully.\n> > > + * \\retval CameraConfiguration::Valid The configuration was already valid and\n> > > + * hasn't been adjusted.\n> > >   */\n> > > -bool CameraConfiguration::isValid() const\n> > > -{\n> > > -\tif (empty())\n> > > -\t\treturn false;\n> > > -\n> > > -\tfor (const StreamConfiguration &cfg : config_) {\n> > > -\t\tif (cfg.size.width == 0 || cfg.size.height == 0 ||\n> > > -\t\t    cfg.pixelFormat == 0 || cfg.bufferCount == 0)\n> > > -\t\t\treturn false;\n> > > -\t}\n> > > -\n> > > -\treturn true;\n> > > -}\n> > >\n> > >  /**\n> > >   * \\brief Retrieve a reference to a stream configuration\n> > > @@ -218,6 +248,11 @@ std::size_t CameraConfiguration::size() const\n> > >  \treturn config_.size();\n> > >  }\n> > >\n> > > +/**\n> > > + * \\var CameraConfiguration::config_\n> > > + * \\brief The vector of stream configurations\n> > > + */\n> > > +\n> > >  /**\n> > >   * \\class Camera\n> > >   * \\brief Camera device\n> > > @@ -575,10 +610,9 @@ CameraConfiguration *Camera::generateConfiguration(const StreamRoles &roles)\n> > >   * The caller specifies which streams are to be involved and their configuration\n> > >   * by populating \\a config.\n> > >   *\n> > > - * The easiest way to populate the array of config is to fetch an initial\n> > > - * configuration from the camera with generateConfiguration() and then change\n> > > - * the parameters to fit the caller's need and once all the streams parameters\n> > > - * are configured hand that over to configure() to actually setup the camera.\n> > > + * The configuration is created by generateConfiguration(), and adjusted by the\n> > > + * caller with CameraConfiguration::validate(). This method only accepts fully\n> > > + * valid configurations and returns an error if \\a config is not valid.\n> > >   *\n> > >   * Exclusive access to the camera shall be ensured by a call to acquire() prior\n> > >   * to calling this function, otherwise an -EACCES error will be returned.\n> > > @@ -603,7 +637,7 @@ int Camera::configure(CameraConfiguration *config)\n> > >  \tif (!stateBetween(CameraAcquired, CameraConfigured))\n> > >  \t\treturn -EACCES;\n> > >\n> > > -\tif (!config->isValid()) {\n> > > +\tif (config->validate() != CameraConfiguration::Valid) {\n> > >  \t\tLOG(Camera, Error)\n> > >  \t\t\t<< \"Can't configure camera with invalid configuration\";\n> > >  \t\treturn -EINVAL;\n> > > diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp\n> > > index 5b46fb68ced2..56265385a1e7 100644\n> > > --- a/src/libcamera/pipeline/ipu3/ipu3.cpp\n> > > +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp\n> > > @@ -164,6 +164,33 @@ public:\n> > >  \tIPU3Stream vfStream_;\n> > >  };\n> > >\n> > > +class IPU3CameraConfiguration : public CameraConfiguration\n> > > +{\n> > > +public:\n> > > +\tIPU3CameraConfiguration(Camera *camera, IPU3CameraData *data);\n> > > +\n> > > +\tStatus validate() override;\n> > > +\n> > > +\tconst V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }\n> > > +\tconst std::vector<const IPU3Stream *> &streams() { return streams_; }\n> > > +\n> > > +private:\n> > > +\tstatic constexpr unsigned int IPU3_BUFFER_COUNT = 4;\n> > > +\n> > > +\tvoid adjustStream(unsigned int index, bool scale);\n> > > +\n> > > +\t/*\n> > > +\t * The IPU3CameraData instance is guaranteed to be valid as long as the\n> > > +\t * corresponding Camera instance is valid. In order to borrow a\n> > > +\t * reference to the camera data, store a new reference to the camera.\n> > > +\t */\n> > > +\tstd::shared_ptr<Camera> camera_;\n> > > +\tconst IPU3CameraData *data_;\n> > > +\n> > > +\tV4L2SubdeviceFormat sensorFormat_;\n> > > +\tstd::vector<const IPU3Stream *> streams_;\n> > > +};\n> > > +\n> > >  class PipelineHandlerIPU3 : public PipelineHandler\n> > >  {\n> > >  public:\n> > > @@ -186,8 +213,6 @@ public:\n> > >  \tbool match(DeviceEnumerator *enumerator) override;\n> > >\n> > >  private:\n> > > -\tstatic constexpr unsigned int IPU3_BUFFER_COUNT = 4;\n> > > -\n> > >  \tIPU3CameraData *cameraData(const Camera *camera)\n> > >  \t{\n> > >  \t\treturn static_cast<IPU3CameraData *>(\n> > > @@ -202,6 +227,153 @@ private:\n> > >  \tMediaDevice *imguMediaDev_;\n> > >  };\n> > >\n> > > +IPU3CameraConfiguration::IPU3CameraConfiguration(Camera *camera,\n> > > +\t\t\t\t\t\t IPU3CameraData *data)\n> > > +\t: CameraConfiguration()\n> > > +{\n> > > +\tcamera_ = camera->shared_from_this();\n> > > +\tdata_ = data;\n> > > +}\n> > > +\n> > > +void IPU3CameraConfiguration::adjustStream(unsigned int index, bool scale)\n> > \n> > Could you pass here the StreanConfiguration & instead of i ?\n> \n> I had another use for the index before, now that it has gone, I'll pass\n> the stream configuration.\n> \n> > > +{\n> > > +\tStreamConfiguration &cfg = config_[index];\n> > > +\n> > > +\t/* The only pixel format the driver supports is NV12. */\n> > > +\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> > > +\n> > > +\tif (scale) {\n> > > +\t\t/*\n> > > +\t\t * Provide a suitable default that matches the sensor aspect\n> > > +\t\t * ratio.\n> > > +\t\t */\n> > > +\t\tif (!cfg.size.width || !cfg.size.height) {\n> > > +\t\t\tcfg.size.width = 1280;\n> > > +\t\t\tcfg.size.height = 1280 * sensorFormat_.size.height\n> > > +\t\t\t\t\t/ sensorFormat_.size.width;\n> > > +\t\t}\n> > > +\n> > > +\t\t/*\n> > > +\t\t * \\todo: Clamp the size to the hardware bounds when we will\n> > > +\t\t * figure them out.\n> > > +\t\t *\n> > > +\t\t * \\todo: Handle the scaler (BDS) restrictions. The BDS can\n> > > +\t\t * only scale with the same factor in both directions, and the\n> > > +\t\t * scaling factor is limited to a multiple of 1/32. At the\n> > > +\t\t * moment the ImgU driver hides these constraints by applying\n> > > +\t\t * additional cropping, this should be fixed on the driver\n> > > +\t\t * side, and cropping should be exposed to us.\n> > > +\t\t */\n> > > +\t} else {\n> > > +\t\t/*\n> > > +\t\t * \\todo: Properly support cropping when the ImgU driver\n> > > +\t\t * interface will be cleaned up.\n> > > +\t\t */\n> > > +\t\tcfg.size = sensorFormat_.size;\n> > > +\t}\n> > > +\n> > > +\t/*\n> > > +\t * Clamp the size to match the ImgU alignment constraints. The width\n> > > +\t * shall be a multiple of 8 pixels and the height a multiple of 4\n> > > +\t * pixels.\n> > > +\t */\n> > > +\tif (cfg.size.width % 8 || cfg.size.height % 4) {\n> > > +\t\tcfg.size.width &= ~7;\n> > \n> > Same question as below, is it safe to use the sensor provided sizes?\n> > In here, in example, 2592 is a multiple of 8, but I'm not sure it\n> > works well.\n> > \n> > (sorry, you should read below first)\n> \n> I'll reply below :-)\n> \n> > > +\t\tcfg.size.height &= ~3;\n> > > +\t}\n> > > +\n> > > +\tcfg.bufferCount = IPU3_BUFFER_COUNT;\n> > > +}\n> > > +\n> > > +CameraConfiguration::Status IPU3CameraConfiguration::validate()\n> > > +{\n> > > +\tconst CameraSensor *sensor = data_->cio2_.sensor_;\n> > > +\tStatus status = Valid;\n> > > +\n> > > +\tif (config_.empty())\n> > > +\t\treturn Invalid;\n> > > +\n> > > +\t/* Cap the number of entries to the available streams. */\n> > > +\tif (config_.size() > 2) {\n> > > +\t\tconfig_.resize(2);\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\t/*\n> > > +\t * Select the sensor format by collecting the maximum width and height\n> > > +\t * and picking the closest larger match, as the IPU3 can downscale\n> > > +\t * only. If no resolution is requested for any stream, or if no sensor\n> > > +\t * resolution is large enough, pick the largest one.\n> > > +\t */\n> > > +\tSize size = {};\n> > > +\n> > > +\tfor (const StreamConfiguration &cfg : config_) {\n> > > +\t\tif (cfg.size.width > size.width)\n> > > +\t\t\tsize.width = cfg.size.width;\n> > > +\t\tif (cfg.size.height > size.height)\n> > > +\t\t\tsize.height = cfg.size.height;\n> > > +\t}\n> > > +\n> > > +\tif (!size.width || !size.height)\n> > > +\t\tsize = sensor->resolution();\n> > > +\n> > > +\tsensorFormat_ = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR10_1X10,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG10_1X10,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG10_1X10,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB10_1X10 },\n> > > +\t\t\t\t\t  size);\n> > > +\tif (!sensorFormat_.size.width || !sensorFormat_.size.height)\n> > > +\t\tsensorFormat_.size = sensor->resolution();\n> > > +\n> > > +\t/*\n> > > +\t * Verify and update all configuration entries, and assign a stream to\n> > > +\t * each of them. The viewfinder stream can scale, while the output\n> > > +\t * stream can crop only, so select the output stream when the requested\n> > > +\t * resolution is equal to the sensor resolution, and the viewfinder\n> > > +\t * stream otherwise.\n> > > +\t */\n> > > +\tstd::set<const IPU3Stream *> availableStreams = {\n> > > +\t\t&data_->outStream_,\n> > > +\t\t&data_->vfStream_,\n> > > +\t};\n> > > +\n> > > +\tstreams_.clear();\n> > > +\tstreams_.reserve(config_.size());\n> > > +\n> > > +\tfor (unsigned int i = 0; i < config_.size(); ++i) {\n> > > +\t\tStreamConfiguration &cfg = config_[i];\n> > \n> > \tfor (StreamConfiguration &cfg : config_) ?\n> \n> You answered this yourself below :-)\n> \n> > > +\t\tconst unsigned int pixelFormat = cfg.pixelFormat;\n> > > +\t\tconst Size size = cfg.size;\n> > > +\t\tconst IPU3Stream *stream;\n> > > +\n> > > +\t\tif (cfg.size == sensorFormat_.size)\n> > \n> > (here, more precisely)\n> > \n> > I'm not sure about this. I always seen the IPU3 used with maximum size\n> > (for ov5670) as 2560x1920 while the sensor resolution is actually a\n> > bit larger 2592x1944. Same for the back ov13858 camera. I never had the\n> > full sensor resolution working properly and I assumed it was related to\n> > some IPU3 alignement or constraints I didn't know about. Also, the\n> > Intel provided xml configuration file does limit the maximum\n> > available resolution to 2560x1920, but that could be for other reasons\n> > maybe... Have you tested capture at sensor resolution sizes?\n> \n> Not yet. I'm 100% confident that this code isn't good enough :-) What\n> we're missing is documentation from Intel, and then I'll fix it.\n> \n> > > +\t\t\tstream = &data_->outStream_;\n> > > +\t\telse\n> > > +\t\t\tstream = &data_->vfStream_;\n> > > +\n> > > +\t\tif (availableStreams.find(stream) == availableStreams.end())\n> > > +\t\t\tstream = *availableStreams.begin();\n> > \n> > This works as long as we have 2 streams only, maybe a todo ?\n> > \n> > > +\n> > > +\t\tLOG(IPU3, Debug)\n> > > +\t\t\t<< \"Assigned '\" << stream->name_ << \"' to stream \" << i;\n> > \n> > Ah, maybe it's better not to use the range-based for loop, you need i\n> > \n> > > +\n> > > +\t\tbool scale = stream == &data_->vfStream_;\n> > > +\t\tadjustStream(i, scale);\n> > > +\n> > > +\t\tif (cfg.pixelFormat != pixelFormat || cfg.size != size) {\n> > > +\t\t\tLOG(IPU3, Debug)\n> > > +\t\t\t\t<< \"Stream \" << i << \" configuration adjusted to \"\n> > > +\t\t\t\t<< cfg.toString();\n> > > +\t\t\tstatus = Adjusted;\n> > > +\t\t}\n> > > +\n> > > +\t\tstreams_.push_back(stream);\n> > > +\t\tavailableStreams.erase(stream);\n> > > +\t}\n> > > +\n> > > +\treturn status;\n> > > +}\n> > > +\n> > >  PipelineHandlerIPU3::PipelineHandlerIPU3(CameraManager *manager)\n> > >  \t: PipelineHandler(manager), cio2MediaDev_(nullptr), imguMediaDev_(nullptr)\n> > >  {\n> > > @@ -211,12 +383,14 @@ CameraConfiguration *PipelineHandlerIPU3::generateConfiguration(Camera *camera,\n> > >  \tconst StreamRoles &roles)\n> > >  {\n> > >  \tIPU3CameraData *data = cameraData(camera);\n> > > -\tCameraConfiguration *config = new CameraConfiguration();\n> > > +\tIPU3CameraConfiguration *config;\n> > >  \tstd::set<IPU3Stream *> streams = {\n> > >  \t\t&data->outStream_,\n> > >  \t\t&data->vfStream_,\n> > >  \t};\n> > >\n> > > +\tconfig = new IPU3CameraConfiguration(camera, data);\n> > > +\n> > >  \tfor (const StreamRole role : roles) {\n> > >  \t\tStreamConfiguration cfg = {};\n> > >  \t\tIPU3Stream *stream = nullptr;\n> > > @@ -296,71 +470,25 @@ CameraConfiguration *PipelineHandlerIPU3::generateConfiguration(Camera *camera,\n> > >\n> > >  \t\tstreams.erase(stream);\n> > >\n> > > -\t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> > > -\t\tcfg.bufferCount = IPU3_BUFFER_COUNT;\n> > > -\n> > >  \t\tconfig->addConfiguration(cfg);\n> > >  \t}\n> > >\n> > > +\tconfig->validate();\n> > > +\n> > \n> > If the idea of providing a Camera::validate(CameraConfiguration *) has\n> > any ground, we should then have a private CameraConfiguration::validate()\n> > operation accessible by the Camera class through a friend declarator,\n> > so that pipeline handlers implementation cannot call it, restricting\n> > that method to the application facing API only. The\n> \n> That's not very neat, as it would polute the application-facing headers\n> with friends :-( I think we should try to remove them instead.\n\nI agree that we should try to remove friend statements wherever \npossible. And once we are a bit more API stable maybe even set out to \nredesign parts to get rid of them completely ;-)\n\n> \n> > CameraConfiguration subclasses' validate() implementation could then\n> > call into the same operation that pipeline handler would use here at\n> > generateConfiguration() time as you did here, if desirable.\n> > \n> > Also, again if Camera::validate(CameraConfiguration *) makes any\n> > sense, we might want to group basic validations, if any. So we could\n> > make Camera::validate() call into the private CameraConfiguration::validate()\n> > operation, which performs basic operations, and calls into a protected\n> > virtual PipelineHandler::__validate() (or whatever name is more\n> > appropriate)\n> > \n> > Would this be doable?\n> \n> If you manage to find a good API that I like, sure :-) What you describe\n> here make the API more cumbersome in my opinion.\n> \n> > >  \treturn config;\n> > >  }\n> > >\n> > > -int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n> > > +int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *c)\n> > >  {\n> > > +\tIPU3CameraConfiguration *config =\n> > > +\t\tstatic_cast<IPU3CameraConfiguration *>(c);\n> > >  \tIPU3CameraData *data = cameraData(camera);\n> > >  \tIPU3Stream *outStream = &data->outStream_;\n> > >  \tIPU3Stream *vfStream = &data->vfStream_;\n> > >  \tCIO2Device *cio2 = &data->cio2_;\n> > >  \tImgUDevice *imgu = data->imgu_;\n> > > -\tSize sensorSize = {};\n> > >  \tint ret;\n> > >\n> > > -\toutStream->active_ = false;\n> > > -\tvfStream->active_ = false;\n> > > -\tfor (StreamConfiguration &cfg : *config) {\n> > > -\t\t/*\n> > > -\t\t * Pick a stream for the configuration entry.\n> > > -\t\t * \\todo: This is a naive temporary implementation that will be\n> > > -\t\t * reworked when implementing camera configuration validation.\n> > > -\t\t */\n> > > -\t\tIPU3Stream *stream = vfStream->active_ ? outStream : vfStream;\n> > > -\n> > > -\t\t/*\n> > > -\t\t * Verify that the requested size respects the IPU3 alignment\n> > > -\t\t * requirements (the image width shall be a multiple of 8\n> > > -\t\t * pixels and its height a multiple of 4 pixels) and the camera\n> > > -\t\t * maximum sizes.\n> > > -\t\t *\n> > > -\t\t * \\todo: Consider the BDS scaling factor requirements: \"the\n> > > -\t\t * downscaling factor must be an integer value multiple of 1/32\"\n> > > -\t\t */\n> > > -\t\tif (cfg.size.width % 8 || cfg.size.height % 4) {\n> > > -\t\t\tLOG(IPU3, Error)\n> > > -\t\t\t\t<< \"Invalid stream size: bad alignment\";\n> > > -\t\t\treturn -EINVAL;\n> > > -\t\t}\n> > > -\n> > > -\t\tconst Size &resolution = cio2->sensor_->resolution();\n> > > -\t\tif (cfg.size.width > resolution.width ||\n> > > -\t\t    cfg.size.height > resolution.height) {\n> > > -\t\t\tLOG(IPU3, Error)\n> > > -\t\t\t\t<< \"Invalid stream size: larger than sensor resolution\";\n> > > -\t\t\treturn -EINVAL;\n> > > -\t\t}\n> > > -\n> > > -\t\t/*\n> > > -\t\t * Collect the maximum width and height: IPU3 can downscale\n> > > -\t\t * only.\n> > > -\t\t */\n> > > -\t\tif (cfg.size.width > sensorSize.width)\n> > > -\t\t\tsensorSize.width = cfg.size.width;\n> > > -\t\tif (cfg.size.height > sensorSize.height)\n> > > -\t\t\tsensorSize.height = cfg.size.height;\n> > > -\n> > > -\t\tstream->active_ = true;\n> > > -\t\tcfg.setStream(stream);\n> > > -\t}\n> > > -\n> > >  \t/*\n> > >  \t * \\todo: Enable links selectively based on the requested streams.\n> > >  \t * As of now, enable all links unconditionally.\n> > > @@ -373,6 +501,7 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n> > >  \t * Pass the requested stream size to the CIO2 unit and get back the\n> > >  \t * adjusted format to be propagated to the ImgU output devices.\n> > >  \t */\n> > > +\tconst Size &sensorSize = config->sensorFormat().size;\n> > >  \tV4L2DeviceFormat cio2Format = {};\n> > >  \tret = cio2->configure(sensorSize, &cio2Format);\n> > >  \tif (ret)\n> > > @@ -383,8 +512,22 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n> > >  \t\treturn ret;\n> > >\n> > >  \t/* Apply the format to the configured streams output devices. */\n> > > -\tfor (StreamConfiguration &cfg : *config) {\n> > > -\t\tIPU3Stream *stream = static_cast<IPU3Stream *>(cfg.stream());\n> > > +\toutStream->active_ = false;\n> > > +\tvfStream->active_ = false;\n> > > +\n> > > +\tfor (unsigned int i = 0; i < config->size(); ++i) {\n> > > +\t\t/*\n> > > +\t\t * Use a const_cast<> here instead of storing a mutable stream\n> > > +\t\t * pointer in the configuration to let the compiler catch\n> > > +\t\t * unwanted modifications of camera data in the configuration\n> > > +\t\t * validate() implementation.\n> > > +\t\t */\n> > > +\t\tIPU3Stream *stream = const_cast<IPU3Stream *>(config->streams()[i]);\n> > > +\t\tStreamConfiguration &cfg = (*config)[i];\n> > \n> > nit: the fact you can get both Stream * and StreamConfiguration * by index\n> > it's a bit ugly. What about config->streams(i) and\n> > config->configuration(i).\n> > \n> > I think what's fishy lies in the fact IPu3CameraConfiguration indexes both\n> > Stream and Configurations in two different vactors and both accessed by\n> > index. Do you need Streams in CameraConfiguration ? Can't you store\n> > them in CameraData, as 'activeStreams_' maybe?\n> \n> I can't, because configuration objects are separated from the active\n> state of the camera. You can create as many configuration you want, toy\n> with them in any way, and finally destroy them, without any side effect.\n> The API guarantees that Camera::configure() will associate streams with\n> configuration entries, but I think pipeline handlers should be able to\n> do it ahead of time too, in validate(), if it's easier for them. I thus\n> don't think that storing that information in CameraData is a good idea.\n> \n> Furthermore, I would like to move the Stream class away from the\n> application-facing API, so more refactoring is to come. I could however,\n> in the meantime, replace IPU3CameraConfiguration::streams() with\n> IPU3CameraConfiguration::stream(unsigned int index) if you think that's\n> desirable (let's remember that that method is private to the IPU3), but\n> a CameraConfiguration::configuration(unsigned int index) would make the\n> API more complex for applications I think (it would need to be called\n> CameraConfiguration::streamConfiguration(), and wouldn't let\n> applications iterate over the stream configurations using a range-based\n> loop).\n> \n> > > +\n> > > +\t\tstream->active_ = true;\n> > > +\t\tcfg.setStream(stream);\n> > > +\n> > >  \t\tret = imgu->configureOutput(stream->device_, cfg);\n> > >  \t\tif (ret)\n> > >  \t\t\treturn ret;\n> > > diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> > > index a1a4f205b4aa..42944c64189b 100644\n> > > --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> > > +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> > \n> > An impressive rework, I like where it's going even if it puts\n> > a bit of a burden on pipeline handlers, it's for the benefit of the\n> > application facing APIs.\n> \n> Thank you.\n> \n> > > @@ -5,6 +5,7 @@\n> > >   * rkisp1.cpp - Pipeline handler for Rockchip ISP1\n> > >   */\n> > >\n> > > +#include <algorithm>\n> > >  #include <iomanip>\n> > >  #include <memory>\n> > >  #include <vector>\n> > > @@ -45,6 +46,29 @@ public:\n> > >  \tCameraSensor *sensor_;\n> > >  };\n> > >\n> > > +class RkISP1CameraConfiguration : public CameraConfiguration\n> > > +{\n> > > +public:\n> > > +\tRkISP1CameraConfiguration(Camera *camera, RkISP1CameraData *data);\n> > > +\n> > > +\tStatus validate() override;\n> > > +\n> > > +\tconst V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }\n> > > +\n> > > +private:\n> > > +\tstatic constexpr unsigned int RKISP1_BUFFER_COUNT = 4;\n> > > +\n> > > +\t/*\n> > > +\t * The RkISP1CameraData instance is guaranteed to be valid as long as the\n> > > +\t * corresponding Camera instance is valid. In order to borrow a\n> > > +\t * reference to the camera data, store a new reference to the camera.\n> > > +\t */\n> > > +\tstd::shared_ptr<Camera> camera_;\n> > > +\tconst RkISP1CameraData *data_;\n> > > +\n> > > +\tV4L2SubdeviceFormat sensorFormat_;\n> > > +};\n> > > +\n> > >  class PipelineHandlerRkISP1 : public PipelineHandler\n> > >  {\n> > >  public:\n> > > @@ -68,8 +92,6 @@ public:\n> > >  \tbool match(DeviceEnumerator *enumerator) override;\n> > >\n> > >  private:\n> > > -\tstatic constexpr unsigned int RKISP1_BUFFER_COUNT = 4;\n> > > -\n> > >  \tRkISP1CameraData *cameraData(const Camera *camera)\n> > >  \t{\n> > >  \t\treturn static_cast<RkISP1CameraData *>(\n> > > @@ -88,6 +110,95 @@ private:\n> > >  \tCamera *activeCamera_;\n> > >  };\n> > >\n> > > +RkISP1CameraConfiguration::RkISP1CameraConfiguration(Camera *camera,\n> > > +\t\t\t\t\t\t     RkISP1CameraData *data)\n> > > +\t: CameraConfiguration()\n> > > +{\n> > > +\tcamera_ = camera->shared_from_this();\n> > > +\tdata_ = data;\n> > > +}\n> > > +\n> > > +CameraConfiguration::Status RkISP1CameraConfiguration::validate()\n> > > +{\n> > > +\tstatic const std::array<unsigned int, 8> formats{\n> > > +\t\tV4L2_PIX_FMT_YUYV,\n> > > +\t\tV4L2_PIX_FMT_YVYU,\n> > > +\t\tV4L2_PIX_FMT_VYUY,\n> > > +\t\tV4L2_PIX_FMT_NV16,\n> > > +\t\tV4L2_PIX_FMT_NV61,\n> > > +\t\tV4L2_PIX_FMT_NV21,\n> > > +\t\tV4L2_PIX_FMT_NV12,\n> > > +\t\tV4L2_PIX_FMT_GREY,\n> > > +\t};\n> > > +\n> > > +\tconst CameraSensor *sensor = data_->sensor_;\n> > > +\tStatus status = Valid;\n> > > +\n> > > +\tif (config_.empty())\n> > > +\t\treturn Invalid;\n> > > +\n> > > +\t/* Cap the number of entries to the available streams. */\n> > > +\tif (config_.size() > 1) {\n> > > +\t\tconfig_.resize(1);\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\tStreamConfiguration &cfg = config_[0];\n> > > +\n> > > +\t/* Adjust the pixel format. */\n> > > +\tif (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==\n> > > +\t    formats.end()) {\n> > > +\t\tLOG(RkISP1, Debug) << \"Adjusting format to NV12\";\n> > > +\t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\t/* Select the sensor format. */\n> > > +\tsensorFormat_ = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR12_1X12,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG12_1X12,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG12_1X12,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB12_1X12,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SBGGR10_1X10,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG10_1X10,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG10_1X10,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB10_1X10,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SBGGR8_1X8,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG8_1X8,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG8_1X8,\n> > > +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB8_1X8 },\n> > > +\t\t\t\t\t  cfg.size);\n> > > +\tif (!sensorFormat_.size.width || !sensorFormat_.size.height)\n> > > +\t\tsensorFormat_.size = sensor->resolution();\n> > > +\n> > > +\t/*\n> > > +\t * Provide a suitable default that matches the sensor aspect\n> > > +\t * ratio and clamp the size to the hardware bounds.\n> > > +\t *\n> > > +\t * \\todo: Check the hardware alignment constraints.\n> > > +\t */\n> > > +\tconst Size size = cfg.size;\n> > > +\n> > > +\tif (!cfg.size.width || !cfg.size.height) {\n> > > +\t\tcfg.size.width = 1280;\n> > > +\t\tcfg.size.height = 1280 * sensorFormat_.size.height\n> > > +\t\t\t\t/ sensorFormat_.size.width;\n> > > +\t}\n> > > +\n> > > +\tcfg.size.width = std::max(32U, std::min(4416U, cfg.size.width));\n> > > +\tcfg.size.height = std::max(16U, std::min(3312U, cfg.size.height));\n> > > +\n> > > +\tif (cfg.size != size) {\n> > > +\t\tLOG(RkISP1, Debug)\n> > > +\t\t\t<< \"Adjusting size from \" << size.toString()\n> > > +\t\t\t<< \" to \" << cfg.size.toString();\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\tcfg.bufferCount = RKISP1_BUFFER_COUNT;\n> > > +\n> > > +\treturn status;\n> > > +}\n> > > +\n> > >  PipelineHandlerRkISP1::PipelineHandlerRkISP1(CameraManager *manager)\n> > >  \t: PipelineHandler(manager), dphy_(nullptr), isp_(nullptr),\n> > >  \t  video_(nullptr)\n> > > @@ -109,37 +220,31 @@ CameraConfiguration *PipelineHandlerRkISP1::generateConfiguration(Camera *camera\n> > >  \tconst StreamRoles &roles)\n> > >  {\n> > >  \tRkISP1CameraData *data = cameraData(camera);\n> > > -\tCameraConfiguration *config = new CameraConfiguration();\n> > > +\tCameraConfiguration *config = new RkISP1CameraConfiguration(camera, data);\n> > >\n> > >  \tif (!roles.empty()) {\n> > >  \t\tStreamConfiguration cfg{};\n> > >\n> > >  \t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> > >  \t\tcfg.size = data->sensor_->resolution();\n> > > -\t\tcfg.bufferCount = RKISP1_BUFFER_COUNT;\n> > >\n> > >  \t\tconfig->addConfiguration(cfg);\n> > >  \t}\n> > >\n> > > +\tconfig->validate();\n> > > +\n> > >  \treturn config;\n> > >  }\n> > >\n> > > -int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *config)\n> > > +int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)\n> > >  {\n> > > +\tRkISP1CameraConfiguration *config =\n> > > +\t\tstatic_cast<RkISP1CameraConfiguration *>(c);\n> > >  \tRkISP1CameraData *data = cameraData(camera);\n> > >  \tStreamConfiguration &cfg = config->at(0);\n> > >  \tCameraSensor *sensor = data->sensor_;\n> > >  \tint ret;\n> > >\n> > > -\t/* Verify the configuration. */\n> > > -\tconst Size &resolution = sensor->resolution();\n> > > -\tif (cfg.size.width > resolution.width ||\n> > > -\t    cfg.size.height > resolution.height) {\n> > > -\t\tLOG(RkISP1, Error)\n> > > -\t\t\t<< \"Invalid stream size: larger than sensor resolution\";\n> > > -\t\treturn -EINVAL;\n> > > -\t}\n> > > -\n> > >  \t/*\n> > >  \t * Configure the sensor links: enable the link corresponding to this\n> > >  \t * camera and disable all the other sensor links.\n> > > @@ -167,21 +272,7 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *config\n> > >  \t * Configure the format on the sensor output and propagate it through\n> > >  \t * the pipeline.\n> > >  \t */\n> > > -\tV4L2SubdeviceFormat format;\n> > > -\tformat = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR12_1X12,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SGBRG12_1X12,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SGRBG12_1X12,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SRGGB12_1X12,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SBGGR10_1X10,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SGBRG10_1X10,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SGRBG10_1X10,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SRGGB10_1X10,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SBGGR8_1X8,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SGBRG8_1X8,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SGRBG8_1X8,\n> > > -\t\t\t\t     MEDIA_BUS_FMT_SRGGB8_1X8 },\n> > > -\t\t\t\t   cfg.size);\n> > > -\n> > > +\tV4L2SubdeviceFormat format = config->sensorFormat();\n> > >  \tLOG(RkISP1, Debug) << \"Configuring sensor with \" << format.toString();\n> > >\n> > >  \tret = sensor->setFormat(&format);\n> > > diff --git a/src/libcamera/pipeline/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo.cpp\n> > > index 8254e1fdac1e..f7ffeb439cf3 100644\n> > > --- a/src/libcamera/pipeline/uvcvideo.cpp\n> > > +++ b/src/libcamera/pipeline/uvcvideo.cpp\n> > > @@ -39,6 +39,14 @@ public:\n> > >  \tStream stream_;\n> > >  };\n> > >\n> > > +class UVCCameraConfiguration : public CameraConfiguration\n> > > +{\n> > > +public:\n> > > +\tUVCCameraConfiguration();\n> > > +\n> > > +\tStatus validate() override;\n> > > +};\n> > > +\n> > >  class PipelineHandlerUVC : public PipelineHandler\n> > >  {\n> > >  public:\n> > > @@ -68,6 +76,45 @@ private:\n> > >  \t}\n> > >  };\n> > >\n> > > +UVCCameraConfiguration::UVCCameraConfiguration()\n> > > +\t: CameraConfiguration()\n> > > +{\n> > > +}\n> > > +\n> > > +CameraConfiguration::Status UVCCameraConfiguration::validate()\n> > > +{\n> > > +\tStatus status = Valid;\n> > > +\n> > > +\tif (config_.empty())\n> > > +\t\treturn Invalid;\n> > > +\n> > > +\t/* Cap the number of entries to the available streams. */\n> > > +\tif (config_.size() > 1) {\n> > > +\t\tconfig_.resize(1);\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\tStreamConfiguration &cfg = config_[0];\n> > > +\n> > > +\t/* \\todo: Validate the configuration against the device capabilities. */\n> > > +\tconst unsigned int pixelFormat = cfg.pixelFormat;\n> > > +\tconst Size size = cfg.size;\n> > > +\n> > > +\tcfg.pixelFormat = V4L2_PIX_FMT_YUYV;\n> > > +\tcfg.size = { 640, 480 };\n> > > +\n> > > +\tif (cfg.pixelFormat != pixelFormat || cfg.size != size) {\n> > > +\t\tLOG(UVC, Debug)\n> > > +\t\t\t<< \"Adjusting configuration from \" << cfg.toString()\n> > > +\t\t\t<< \" to \" << cfg.size.toString() << \"-YUYV\";\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\tcfg.bufferCount = 4;\n> > > +\n> > > +\treturn status;\n> > > +}\n> > > +\n> > >  PipelineHandlerUVC::PipelineHandlerUVC(CameraManager *manager)\n> > >  \t: PipelineHandler(manager)\n> > >  {\n> > > @@ -76,10 +123,10 @@ PipelineHandlerUVC::PipelineHandlerUVC(CameraManager *manager)\n> > >  CameraConfiguration *PipelineHandlerUVC::generateConfiguration(Camera *camera,\n> > >  \tconst StreamRoles &roles)\n> > >  {\n> > > -\tCameraConfiguration *config = new CameraConfiguration();\n> > > +\tCameraConfiguration *config = new UVCCameraConfiguration();\n> > >\n> > >  \tif (!roles.empty()) {\n> > > -\t\tStreamConfiguration cfg;\n> > > +\t\tStreamConfiguration cfg{};\n> > >\n> > >  \t\tcfg.pixelFormat = V4L2_PIX_FMT_YUYV;\n> > >  \t\tcfg.size = { 640, 480 };\n> > > @@ -88,6 +135,8 @@ CameraConfiguration *PipelineHandlerUVC::generateConfiguration(Camera *camera,\n> > >  \t\tconfig->addConfiguration(cfg);\n> > >  \t}\n> > >\n> > > +\tconfig->validate();\n> > > +\n> > >  \treturn config;\n> > >  }\n> > >\n> > > diff --git a/src/libcamera/pipeline/vimc.cpp b/src/libcamera/pipeline/vimc.cpp\n> > > index 2bf85d0a0b32..2a61d2893e3a 100644\n> > > --- a/src/libcamera/pipeline/vimc.cpp\n> > > +++ b/src/libcamera/pipeline/vimc.cpp\n> > > @@ -5,6 +5,8 @@\n> > >   * vimc.cpp - Pipeline handler for the vimc device\n> > >   */\n> > >\n> > > +#include <algorithm>\n> > > +\n> > >  #include <libcamera/camera.h>\n> > >  #include <libcamera/request.h>\n> > >  #include <libcamera/stream.h>\n> > > @@ -39,6 +41,14 @@ public:\n> > >  \tStream stream_;\n> > >  };\n> > >\n> > > +class VimcCameraConfiguration : public CameraConfiguration\n> > > +{\n> > > +public:\n> > > +\tVimcCameraConfiguration();\n> > > +\n> > > +\tStatus validate() override;\n> > > +};\n> > > +\n> > >  class PipelineHandlerVimc : public PipelineHandler\n> > >  {\n> > >  public:\n> > > @@ -68,6 +78,57 @@ private:\n> > >  \t}\n> > >  };\n> > >\n> > > +VimcCameraConfiguration::VimcCameraConfiguration()\n> > > +\t: CameraConfiguration()\n> > > +{\n> > > +}\n> > > +\n> > > +CameraConfiguration::Status VimcCameraConfiguration::validate()\n> > > +{\n> > > +\tstatic const std::array<unsigned int, 3> formats{\n> > > +\t\tV4L2_PIX_FMT_BGR24,\n> > > +\t\tV4L2_PIX_FMT_RGB24,\n> > > +\t\tV4L2_PIX_FMT_ARGB32,\n> > > +\t};\n> > > +\n> > > +\tStatus status = Valid;\n> > > +\n> > > +\tif (config_.empty())\n> > > +\t\treturn Invalid;\n> > > +\n> > > +\t/* Cap the number of entries to the available streams. */\n> > > +\tif (config_.size() > 1) {\n> > > +\t\tconfig_.resize(1);\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\tStreamConfiguration &cfg = config_[0];\n> > > +\n> > > +\t/* Adjust the pixel format. */\n> > > +\tif (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==\n> > > +\t    formats.end()) {\n> > > +\t\tLOG(VIMC, Debug) << \"Adjusting format to RGB24\";\n> > > +\t\tcfg.pixelFormat = V4L2_PIX_FMT_RGB24;\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\t/* Clamp the size based on the device limits. */\n> > > +\tconst Size size = cfg.size;\n> > > +\n> > > +\tcfg.size.width = std::max(16U, std::min(4096U, cfg.size.width));\n> > > +\tcfg.size.height = std::max(16U, std::min(2160U, cfg.size.height));\n> > > +\n> > > +\tif (cfg.size != size) {\n> > > +\t\tLOG(VIMC, Debug)\n> > > +\t\t\t<< \"Adjusting size to \" << cfg.size.toString();\n> > > +\t\tstatus = Adjusted;\n> > > +\t}\n> > > +\n> > > +\tcfg.bufferCount = 4;\n> > > +\n> > > +\treturn status;\n> > > +}\n> > > +\n> > >  PipelineHandlerVimc::PipelineHandlerVimc(CameraManager *manager)\n> > >  \t: PipelineHandler(manager)\n> > >  {\n> > > @@ -76,10 +137,10 @@ PipelineHandlerVimc::PipelineHandlerVimc(CameraManager *manager)\n> > >  CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera,\n> > >  \tconst StreamRoles &roles)\n> > >  {\n> > > -\tCameraConfiguration *config = new CameraConfiguration();\n> > > +\tCameraConfiguration *config = new VimcCameraConfiguration();\n> > >\n> > >  \tif (!roles.empty()) {\n> > > -\t\tStreamConfiguration cfg;\n> > > +\t\tStreamConfiguration cfg{};\n> > >\n> > >  \t\tcfg.pixelFormat = V4L2_PIX_FMT_RGB24;\n> > >  \t\tcfg.size = { 640, 480 };\n> > > @@ -88,6 +149,8 @@ CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera,\n> > >  \t\tconfig->addConfiguration(cfg);\n> > >  \t}\n> > >\n> > > +\tconfig->validate();\n> > > +\n> > >  \treturn config;\n> > >  }\n> > >\n> > > diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp\n> > > index de46e98880a2..dd56907d817e 100644\n> > > --- a/src/libcamera/pipeline_handler.cpp\n> > > +++ b/src/libcamera/pipeline_handler.cpp\n> > > @@ -248,17 +248,14 @@ void PipelineHandler::unlock()\n> > >   * is the Camera class which will receive configuration to apply from the\n> > >   * application.\n> > >   *\n> > > - * Each pipeline handler implementation is responsible for validating\n> > > - * that the configuration requested in \\a config can be achieved\n> > > - * exactly. Any difference in pixel format, frame size or any other\n> > > - * parameter shall result in the -EINVAL error being returned, and no\n> > > - * change in configuration being applied to the pipeline. If\n> > > - * configuration of a subset of the streams can't be satisfied, the\n> > > - * whole configuration is considered invalid.\n> > > + * The configuration is guaranteed to have been validated with\n> > > + * CameraConfiguration::valid(). The pipeline handler implementation shall not\n> > > + * perform further validation and may rely on any custom field stored in its\n> > > + * custom CameraConfiguration derived class.\n> > >   *\n> > > - * Once the configuration is validated and the camera configured, the pipeline\n> > > - * handler shall associate a Stream instance to each StreamConfiguration entry\n> > > - * in the CameraConfiguration with the StreamConfiguration::setStream() method.\n> > > + * When configuring the camera the pipeline handler shall associate a Stream\n> > > + * instance to each StreamConfiguration entry in the CameraConfiguration using\n> > > + * the StreamConfiguration::setStream() method.\n> > >   *\n> > >   * \\return 0 on success or a negative error code otherwise\n> > >   */\n> > > diff --git a/test/camera/capture.cpp b/test/camera/capture.cpp\n> > > index 137aa649a505..f122f79bb1ec 100644\n> > > --- a/test/camera/capture.cpp\n> > > +++ b/test/camera/capture.cpp\n> > > @@ -45,7 +45,7 @@ protected:\n> > >  \t\tCameraTest::init();\n> > >\n> > >  \t\tconfig_ = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> > > -\t\tif (!config_) {\n> > > +\t\tif (!config_ || config_->size() != 1) {\n> > >  \t\t\tcout << \"Failed to generate default configuration\" << endl;\n> > >  \t\t\tCameraTest::cleanup();\n> > >  \t\t\treturn TestFail;\n> > > @@ -58,11 +58,6 @@ protected:\n> > >  \t{\n> > >  \t\tStreamConfiguration &cfg = config_->at(0);\n> > >\n> > > -\t\tif (!config_->isValid()) {\n> > > -\t\t\tcout << \"Failed to read default configuration\" << endl;\n> > > -\t\t\treturn TestFail;\n> > > -\t\t}\n> > > -\n> > >  \t\tif (camera_->acquire()) {\n> > >  \t\t\tcout << \"Failed to acquire the camera\" << endl;\n> > >  \t\t\treturn TestFail;\n> > > diff --git a/test/camera/configuration_default.cpp b/test/camera/configuration_default.cpp\n> > > index d5cefc1127c9..81055da1d513 100644\n> > > --- a/test/camera/configuration_default.cpp\n> > > +++ b/test/camera/configuration_default.cpp\n> > > @@ -22,7 +22,7 @@ protected:\n> > >\n> > >  \t\t/* Test asking for configuration for a video stream. */\n> > >  \t\tconfig = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> > > -\t\tif (!config || !config->isValid()) {\n> > > +\t\tif (!config || config->size() != 1) {\n> > >  \t\t\tcout << \"Default configuration invalid\" << endl;\n> > >  \t\t\tdelete config;\n> > >  \t\t\treturn TestFail;\n> > > @@ -35,7 +35,7 @@ protected:\n> > >  \t\t * stream roles returns an empty camera configuration.\n> > >  \t\t */\n> > >  \t\tconfig = camera_->generateConfiguration({});\n> > > -\t\tif (!config || config->isValid()) {\n> > > +\t\tif (!config || config->size() != 0) {\n> > >  \t\t\tcout << \"Failed to retrieve configuration for empty roles list\"\n> > >  \t\t\t     << endl;\n> > >  \t\t\tdelete config;\n> > > diff --git a/test/camera/configuration_set.cpp b/test/camera/configuration_set.cpp\n> > > index 23c611a93355..a4e2da16a88b 100644\n> > > --- a/test/camera/configuration_set.cpp\n> > > +++ b/test/camera/configuration_set.cpp\n> > > @@ -21,7 +21,7 @@ protected:\n> > >  \t\tCameraTest::init();\n> > >\n> > >  \t\tconfig_ = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> > > -\t\tif (!config_) {\n> > > +\t\tif (!config_ || config_->size() != 1) {\n> > >  \t\t\tcout << \"Failed to generate default configuration\" << endl;\n> > >  \t\t\tCameraTest::cleanup();\n> > >  \t\t\treturn TestFail;\n> > > @@ -34,11 +34,6 @@ protected:\n> > >  \t{\n> > >  \t\tStreamConfiguration &cfg = config_->at(0);\n> > >\n> > > -\t\tif (!config_->isValid()) {\n> > > -\t\t\tcout << \"Failed to read default configuration\" << endl;\n> > > -\t\t\treturn TestFail;\n> > > -\t\t}\n> > > -\n> > >  \t\tif (camera_->acquire()) {\n> > >  \t\t\tcout << \"Failed to acquire the camera\" << endl;\n> > >  \t\t\treturn TestFail;\n> \n> -- \n> Regards,\n> \n> Laurent Pinchart\n> _______________________________________________\n> libcamera-devel mailing list\n> libcamera-devel@lists.libcamera.org\n> https://lists.libcamera.org/listinfo/libcamera-devel","headers":{"Return-Path":"<niklas.soderlund@ragnatech.se>","Received":["from mail-lj1-x242.google.com (mail-lj1-x242.google.com\n\t[IPv6:2a00:1450:4864:20::242])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 4037360C02\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 22 May 2019 13:02:32 +0200 (CEST)","by mail-lj1-x242.google.com with SMTP id w1so1693827ljw.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 22 May 2019 04:02:32 -0700 (PDT)","from localhost (89-233-230-99.cust.bredband2.com. [89.233.230.99])\n\tby smtp.gmail.com with ESMTPSA id\n\tv4sm246303lfi.49.2019.05.22.04.02.30\n\t(version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256);\n\tWed, 22 May 2019 04:02:30 -0700 (PDT)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=ragnatech-se.20150623.gappssmtp.com; s=20150623;\n\th=date:from:to:cc:subject:message-id:references:mime-version\n\t:content-disposition:content-transfer-encoding:in-reply-to\n\t:user-agent; bh=rAn6/rccx/PhhsoHvlxXOIi6qADSG7KnoP0CAamLWew=;\n\tb=KJTkf50loAIQ1qNry6ICQqpvhYp5kyCYASbLw/FvZelZxGZiefTtpJGUXq2hlW5xWh\n\tTuXziSdSu76/Zn8KjYdNQM9Ea7NCWVnxS9WTx6x26bkQCjIwe0/ENDcf62ZLaXbk0EpB\n\t+CsPXJoahTElRyPbMsLuN0KFssNbxdu38CCWXZfsi5ArGw5Qf+oA5PB+OWdFcIDq1VYU\n\tHhW/tbrEWf3QoWvPB9znJUV8YkXHhvEn7iMFHLe3gmPUqoBJUteVRBegYD5PLMoAYG22\n\t9jaq9DX5rojzxMvRrbhO0TiT+MA/mjfbRR8Wm/x/+9ZWtAYnM0z4oV2HVKVwd/ESYrTq\n\tyRWA==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20161025;\n\th=x-gm-message-state:date:from:to:cc:subject:message-id:references\n\t:mime-version:content-disposition:content-transfer-encoding\n\t:in-reply-to:user-agent;\n\tbh=rAn6/rccx/PhhsoHvlxXOIi6qADSG7KnoP0CAamLWew=;\n\tb=uL4Jx2KONFOuMEpRz1dhydcxL/qXGcL3qO1ENA+K8P3TpSkEEHMfqe8LHeOBKORulX\n\tpmEAxPSEnjBvEeKS0ZxjnR9mBFU9acLVIgXUFCj8uQ+B2DMV5CNi+tirO3lxdOB611kr\n\t4t5DLki287Cb02YIU/nywKvgEbjUqNOcnL7/ucu4UwPDP5JoGOetovYHlxM0Hc8KI1SK\n\tTbnQjOpn/KYPTB/FgeKyLmqEbw/ipMKB+jU/wEpaG3HlvilZoscu9wCB+anrvNtyymbD\n\tzVI1DTynLHg7ESf/i7cwZ8NOXerszv/10zCub7b/mV9eycuRuFkUKxwI5/+vnjdr5lk3\n\tF1rw==","X-Gm-Message-State":"APjAAAUQ6V1kZKCF1df4Cy2n5HuYVQpQzEEEdhSQQat9she+F81jxaV/\n\tGvvcfeaE+L+7DvPDHtVMT+i+5A==","X-Google-Smtp-Source":"APXvYqxTIoOh7qcla9QnRgenyxJm/Puqv4i5PlptssMy/mHkrBbuBkCFFDvqT81gAAhW8p8V21AL2g==","X-Received":"by 2002:a2e:8154:: with SMTP id\n\tt20mr3408253ljg.180.1558522951300; \n\tWed, 22 May 2019 04:02:31 -0700 (PDT)","Date":"Wed, 22 May 2019 13:02:30 +0200","From":"Niklas =?iso-8859-1?q?S=F6derlund?= <niklas.soderlund@ragnatech.se>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"Jacopo Mondi <jacopo@jmondi.org>, libcamera-devel@lists.libcamera.org","Message-ID":"<20190522110230.GC1651@bigcity.dyn.berto.se>","References":"<20190519150047.12444-1-laurent.pinchart@ideasonboard.com>\n\t<20190519150047.12444-7-laurent.pinchart@ideasonboard.com>\n\t<20190520220506.5yubhl2rqmk3ajdr@uno.localdomain>\n\t<20190521124955.GC5674@pendragon.ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=iso-8859-1","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<20190521124955.GC5674@pendragon.ideasonboard.com>","User-Agent":"Mutt/1.11.4 (2019-03-13)","Subject":"Re: [libcamera-devel] [PATCH v2 6/6] libcamera: camera: Add a\n\tvalidation API to the CameraConfiguration class","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.23","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Wed, 22 May 2019 11:02:32 -0000"}},{"id":1664,"web_url":"https://patchwork.libcamera.org/comment/1664/","msgid":"<20190522161908.GC8868@pendragon.ideasonboard.com>","date":"2019-05-22T16:19:08","subject":"Re: [libcamera-devel] [PATCH v2 6/6] libcamera: camera: Add a\n\tvalidation API to the CameraConfiguration class","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Niklas,\n\nOn Wed, May 22, 2019 at 12:55:16PM +0200, Niklas Söderlund wrote:\n> On 2019-05-21 15:51:09 +0300, Laurent Pinchart wrote:\n> > On Tue, May 21, 2019 at 10:49:50AM +0200, Jacopo Mondi wrote:\n> >> On Sun, May 19, 2019 at 06:00:47PM +0300, Laurent Pinchart wrote:\n> >>> The CameraConfiguration class implements a simple storage of\n> >>> StreamConfiguration with internal validation limited to verifying that\n> >>> the stream configurations are not empty. Extend this mechanism by\n> >>> implementing a smart validate() method backed by pipeline handlers.\n> >>>\n> >>> This new mechanism changes the semantics of the camera configuration.\n> >>> The Camera::generateConfiguration() operation still generates a default\n> >>> configuration based on roles, but now also supports generating empty\n> >>> configurations to be filled by applications. Applications can inspect\n> >>> the configuration, optionally modify it, and validate it. The validation\n> >>> implements \"try\" semantics and adjusts invalid configurations instead of\n> >>> rejecting them completely. Applications then decide whether to accept\n> >>> the modified configuration, or try again with a different set of\n> >>> parameters. Once the configuration is valid, it is passed to\n> >>> Camera::configure(), and pipeline handlers are guaranteed that the\n> >>> configuration they receive is valid.\n> >>>\n> >>> A reference to the Camera may need to be stored in the\n> >>> CameraConfiguration derived classes in order to access it from their\n> >>> validate() implementation. This must be stored as a std::shared_ptr<> as\n> >>> the CameraConfiguration instances belong to applications. In order to\n> >>> make this possible, make the Camera class inherit from\n> >>> std::shared_from_this<>.\n> >>>\n> >>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> >>> ---\n> >>>  include/libcamera/camera.h               |  17 +-\n> >>>  src/cam/main.cpp                         |   2 +-\n> >>>  src/libcamera/camera.cpp                 |  80 +++++--\n> >>>  src/libcamera/pipeline/ipu3/ipu3.cpp     | 255 ++++++++++++++++++-----\n> >>>  src/libcamera/pipeline/rkisp1/rkisp1.cpp | 149 ++++++++++---\n> >>>  src/libcamera/pipeline/uvcvideo.cpp      |  53 ++++-\n> >>>  src/libcamera/pipeline/vimc.cpp          |  67 +++++-\n> >>>  src/libcamera/pipeline_handler.cpp       |  17 +-\n> >>>  test/camera/capture.cpp                  |   7 +-\n> >>>  test/camera/configuration_default.cpp    |   4 +-\n> >>>  test/camera/configuration_set.cpp        |   7 +-\n> >>>  11 files changed, 516 insertions(+), 142 deletions(-)\n> >>>\n> >>> diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> >>> index 144133c5de9f..8c0049a1dd94 100644\n> >>> --- a/include/libcamera/camera.h\n> >>> +++ b/include/libcamera/camera.h\n> >>> @@ -25,14 +25,19 @@ class Request;\n> >>>  class CameraConfiguration\n> >>>  {\n> >>>  public:\n> >>> +\tenum Status {\n> >>> +\t\tValid,\n> >>> +\t\tAdjusted,\n> >>> +\t\tInvalid,\n> >>> +\t};\n> >>> +\n> >>>  \tusing iterator = std::vector<StreamConfiguration>::iterator;\n> >>>  \tusing const_iterator = std::vector<StreamConfiguration>::const_iterator;\n> >>>\n> >>> -\tCameraConfiguration();\n> >>> +\tvirtual ~CameraConfiguration();\n> >>>\n> >>>  \tvoid addConfiguration(const StreamConfiguration &cfg);\n> >>> -\n> >>> -\tbool isValid() const;\n> >>> +\tvirtual Status validate() = 0;\n> >>>\n> >>>  \tStreamConfiguration &at(unsigned int index);\n> >>>  \tconst StreamConfiguration &at(unsigned int index) const;\n> >>> @@ -53,11 +58,13 @@ public:\n> >>>  \tbool empty() const;\n> >>>  \tstd::size_t size() const;\n> >>>\n> >>> -private:\n> >>> +protected:\n> >>> +\tCameraConfiguration();\n> >>> +\n> >>>  \tstd::vector<StreamConfiguration> config_;\n> >>>  };\n> >>>\n> >>> -class Camera final\n> >>> +class Camera final : public std::enable_shared_from_this<Camera>\n> >>>  {\n> >>>  public:\n> >>>  \tstatic std::shared_ptr<Camera> create(PipelineHandler *pipe,\n> >>> diff --git a/src/cam/main.cpp b/src/cam/main.cpp\n> >>> index 7550ae4f3428..23da5c687d89 100644\n> >>> --- a/src/cam/main.cpp\n> >>> +++ b/src/cam/main.cpp\n> >> \n> >> Shouldn't you add a config->validate() call before calling\n> >> camera->configure(config) for the cam application?\n> > \n> > Niklas has submitted a patch for them, do you think I should squash it\n> > with this one, or apply it separately ?\n> \n> For cam in it's current form there is no strict need to call validate at \n> this point. The current design is that cam should fail if the format is \n> adjusted. We might wish to change that in the future (like I do in some \n> patch IIRC).\n> \n> If you wish to squash that change here I'm fine doing so. I think \n> however that it's fine for this patch to move forward with or without \n> that change.\n\nThen let's do it on top.\n\n> >> (I've not checked qcam, I'm sorry, but it might apply there too)\n> > \n> > That should be fixed too, yes.\n> > \n> >>> @@ -116,7 +116,7 @@ static CameraConfiguration *prepareCameraConfig()\n> >>>  \t}\n> >>>\n> >>>  \tCameraConfiguration *config = camera->generateConfiguration(roles);\n> >>> -\tif (!config || !config->isValid()) {\n> >>> +\tif (!config || config->size() != roles.size()) {\n> >>>  \t\tstd::cerr << \"Failed to get default stream configuration\"\n> >>>  \t\t\t  << std::endl;\n> >>>  \t\tdelete config;\n> >>> diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> >>> index 0e80691ed862..9da5d4f613f4 100644\n> >>> --- a/src/libcamera/camera.cpp\n> >>> +++ b/src/libcamera/camera.cpp\n> >>> @@ -52,6 +52,28 @@ LOG_DECLARE_CATEGORY(Camera)\n> >>>   * operator[](int) returns a reference to the StreamConfiguration based on its\n> >>>   * insertion index. Accessing a stream configuration with an invalid index\n> >>>   * results in undefined behaviour.\n> >>> + *\n> >>> + * CameraConfiguration instances are retrieved from the camera with\n> >>> + * Camera::generateConfiguration(). Applications may then inspect the\n> >>> + * configuration, modify it, and possibly add new stream configuration entries\n> >>> + * with addConfiguration(). Once the camera configuration satisfies the\n> >>> + * application, it shall be validated by a call to validate(). The validation\n> >>> + * implements \"try\" semantics: it adjusts invalid configurations to the closest\n> >>> + * achievable parameters instead of rejecting them completely. Applications\n> >>> + * then decide whether to accept the modified configuration, or try again with\n> >>> + * a different set of parameters. Once the configuration is valid, it is passed\n> >>> + * to Camera::configure().\n> >>> + */\n> >>> +\n> >>> +/**\n> >>> + * \\enum CameraConfiguration::Status\n> >>> + * \\brief Validity of a camera configuration\n> >>> + * \\var CameraConfiguration::Valid\n> >>> + * The configuration is fully valid\n> >>> + * \\var CameraConfiguration::Adjusted\n> >>> + * The configuration has been adjusted to a valid configuration\n> >>> + * \\var CameraConfiguration::Invalid\n> >>> + * The configuration is invalid and can't be adjusted automatically\n> >>>   */\n> >>>\n> >>>  /**\n> >>> @@ -73,6 +95,10 @@ CameraConfiguration::CameraConfiguration()\n> >>>  {\n> >>>  }\n> >>>\n> >>> +CameraConfiguration::~CameraConfiguration()\n> >>> +{\n> >>> +}\n> >>> +\n> >>>  /**\n> >>>   * \\brief Add a stream configuration to the camera configuration\n> >>>   * \\param[in] cfg The stream configuration\n> >>> @@ -83,27 +109,31 @@ void CameraConfiguration::addConfiguration(const StreamConfiguration &cfg)\n> >>>  }\n> >>>\n> >>>  /**\n> >>> - * \\brief Check if the camera configuration is valid\n> >>> + * \\fn CameraConfiguration::validate()\n> >>> + * \\brief Validate and possibly adjust the camera configuration\n> >>>   *\n> >>> - * A camera configuration is deemed to be valid if it contains at least one\n> >>> - * stream configuration and all stream configurations contain valid information.\n> >>> - * Stream configurations are deemed to be valid if all fields are none zero.\n> >>> + * This method adjusts the camera configuration to the closest valid\n> >>> + * configuration and returns the validation status.\n> >>>   *\n> >>> - * \\return True if the configuration is valid\n> >>> + * \\todo: Define exactly when to return each status code. Should stream\n> >>> + * parameters set to 0 by the caller be adjusted without returning Adjusted ?\n> >>> + * This would potentially be useful for applications but would get in the way\n> >>> + * in Camera::configure(). Do we need an extra status code to signal this ?\n> >>> + *\n> >>> + * \\todo: Handle validation of buffers count when refactoring the buffers API.\n> >>> + *\n> >>> + * \\return A CameraConfiguration::Status value that describes the validation\n> >>> + * status.\n> >>> + * \\retval CameraConfiguration::Invalid The configuration is invalid and can't\n> >>> + * be adjusted. This may only occur in extreme cases such as when the\n> >>> + * configuration is empty.\n> >>> + * \\retval CameraConfigutation::Adjusted The configuration has been adjusted\n> >>> + * and is now valid. Parameters may have changed for any stream, and stream\n> >>> + * configurations may have been removed. The caller shall check the\n> >>> + * configuration carefully.\n> >>> + * \\retval CameraConfiguration::Valid The configuration was already valid and\n> >>> + * hasn't been adjusted.\n> >>>   */\n> >>> -bool CameraConfiguration::isValid() const\n> >>> -{\n> >>> -\tif (empty())\n> >>> -\t\treturn false;\n> >>> -\n> >>> -\tfor (const StreamConfiguration &cfg : config_) {\n> >>> -\t\tif (cfg.size.width == 0 || cfg.size.height == 0 ||\n> >>> -\t\t    cfg.pixelFormat == 0 || cfg.bufferCount == 0)\n> >>> -\t\t\treturn false;\n> >>> -\t}\n> >>> -\n> >>> -\treturn true;\n> >>> -}\n> >>>\n> >>>  /**\n> >>>   * \\brief Retrieve a reference to a stream configuration\n> >>> @@ -218,6 +248,11 @@ std::size_t CameraConfiguration::size() const\n> >>>  \treturn config_.size();\n> >>>  }\n> >>>\n> >>> +/**\n> >>> + * \\var CameraConfiguration::config_\n> >>> + * \\brief The vector of stream configurations\n> >>> + */\n> >>> +\n> >>>  /**\n> >>>   * \\class Camera\n> >>>   * \\brief Camera device\n> >>> @@ -575,10 +610,9 @@ CameraConfiguration *Camera::generateConfiguration(const StreamRoles &roles)\n> >>>   * The caller specifies which streams are to be involved and their configuration\n> >>>   * by populating \\a config.\n> >>>   *\n> >>> - * The easiest way to populate the array of config is to fetch an initial\n> >>> - * configuration from the camera with generateConfiguration() and then change\n> >>> - * the parameters to fit the caller's need and once all the streams parameters\n> >>> - * are configured hand that over to configure() to actually setup the camera.\n> >>> + * The configuration is created by generateConfiguration(), and adjusted by the\n> >>> + * caller with CameraConfiguration::validate(). This method only accepts fully\n> >>> + * valid configurations and returns an error if \\a config is not valid.\n> >>>   *\n> >>>   * Exclusive access to the camera shall be ensured by a call to acquire() prior\n> >>>   * to calling this function, otherwise an -EACCES error will be returned.\n> >>> @@ -603,7 +637,7 @@ int Camera::configure(CameraConfiguration *config)\n> >>>  \tif (!stateBetween(CameraAcquired, CameraConfigured))\n> >>>  \t\treturn -EACCES;\n> >>>\n> >>> -\tif (!config->isValid()) {\n> >>> +\tif (config->validate() != CameraConfiguration::Valid) {\n> >>>  \t\tLOG(Camera, Error)\n> >>>  \t\t\t<< \"Can't configure camera with invalid configuration\";\n> >>>  \t\treturn -EINVAL;\n> >>> diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp\n> >>> index 5b46fb68ced2..56265385a1e7 100644\n> >>> --- a/src/libcamera/pipeline/ipu3/ipu3.cpp\n> >>> +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp\n> >>> @@ -164,6 +164,33 @@ public:\n> >>>  \tIPU3Stream vfStream_;\n> >>>  };\n> >>>\n> >>> +class IPU3CameraConfiguration : public CameraConfiguration\n> >>> +{\n> >>> +public:\n> >>> +\tIPU3CameraConfiguration(Camera *camera, IPU3CameraData *data);\n> >>> +\n> >>> +\tStatus validate() override;\n> >>> +\n> >>> +\tconst V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }\n> >>> +\tconst std::vector<const IPU3Stream *> &streams() { return streams_; }\n> >>> +\n> >>> +private:\n> >>> +\tstatic constexpr unsigned int IPU3_BUFFER_COUNT = 4;\n> >>> +\n> >>> +\tvoid adjustStream(unsigned int index, bool scale);\n> >>> +\n> >>> +\t/*\n> >>> +\t * The IPU3CameraData instance is guaranteed to be valid as long as the\n> >>> +\t * corresponding Camera instance is valid. In order to borrow a\n> >>> +\t * reference to the camera data, store a new reference to the camera.\n> >>> +\t */\n> >>> +\tstd::shared_ptr<Camera> camera_;\n> >>> +\tconst IPU3CameraData *data_;\n> >>> +\n> >>> +\tV4L2SubdeviceFormat sensorFormat_;\n> >>> +\tstd::vector<const IPU3Stream *> streams_;\n> >>> +};\n> >>> +\n> >>>  class PipelineHandlerIPU3 : public PipelineHandler\n> >>>  {\n> >>>  public:\n> >>> @@ -186,8 +213,6 @@ public:\n> >>>  \tbool match(DeviceEnumerator *enumerator) override;\n> >>>\n> >>>  private:\n> >>> -\tstatic constexpr unsigned int IPU3_BUFFER_COUNT = 4;\n> >>> -\n> >>>  \tIPU3CameraData *cameraData(const Camera *camera)\n> >>>  \t{\n> >>>  \t\treturn static_cast<IPU3CameraData *>(\n> >>> @@ -202,6 +227,153 @@ private:\n> >>>  \tMediaDevice *imguMediaDev_;\n> >>>  };\n> >>>\n> >>> +IPU3CameraConfiguration::IPU3CameraConfiguration(Camera *camera,\n> >>> +\t\t\t\t\t\t IPU3CameraData *data)\n> >>> +\t: CameraConfiguration()\n> >>> +{\n> >>> +\tcamera_ = camera->shared_from_this();\n> >>> +\tdata_ = data;\n> >>> +}\n> >>> +\n> >>> +void IPU3CameraConfiguration::adjustStream(unsigned int index, bool scale)\n> >>> +{\n> >>> +\tStreamConfiguration &cfg = config_[index];\n> >>> +\n> >>> +\t/* The only pixel format the driver supports is NV12. */\n> >>> +\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> >>> +\n> >>> +\tif (scale) {\n> >>> +\t\t/*\n> >>> +\t\t * Provide a suitable default that matches the sensor aspect\n> >>> +\t\t * ratio.\n> >>> +\t\t */\n> >>> +\t\tif (!cfg.size.width || !cfg.size.height) {\n> >>> +\t\t\tcfg.size.width = 1280;\n> >>> +\t\t\tcfg.size.height = 1280 * sensorFormat_.size.height\n> >>> +\t\t\t\t\t/ sensorFormat_.size.width;\n> >>> +\t\t}\n> >>> +\n> >>> +\t\t/*\n> >>> +\t\t * \\todo: Clamp the size to the hardware bounds when we will\n> >>> +\t\t * figure them out.\n> >>> +\t\t *\n> >>> +\t\t * \\todo: Handle the scaler (BDS) restrictions. The BDS can\n> >>> +\t\t * only scale with the same factor in both directions, and the\n> >>> +\t\t * scaling factor is limited to a multiple of 1/32. At the\n> >>> +\t\t * moment the ImgU driver hides these constraints by applying\n> >>> +\t\t * additional cropping, this should be fixed on the driver\n> >>> +\t\t * side, and cropping should be exposed to us.\n> >>> +\t\t */\n> >>> +\t} else {\n> >>> +\t\t/*\n> >>> +\t\t * \\todo: Properly support cropping when the ImgU driver\n> >>> +\t\t * interface will be cleaned up.\n> >>> +\t\t */\n> >>> +\t\tcfg.size = sensorFormat_.size;\n> >>> +\t}\n> >>> +\n> >>> +\t/*\n> >>> +\t * Clamp the size to match the ImgU alignment constraints. The width\n> >>> +\t * shall be a multiple of 8 pixels and the height a multiple of 4\n> >>> +\t * pixels.\n> >>> +\t */\n> >>> +\tif (cfg.size.width % 8 || cfg.size.height % 4) {\n> >>> +\t\tcfg.size.width &= ~7;\n> >>> +\t\tcfg.size.height &= ~3;\n> >>> +\t}\n> >>> +\n> >>> +\tcfg.bufferCount = IPU3_BUFFER_COUNT;\n> >>> +}\n> >>> +\n> >>> +CameraConfiguration::Status IPU3CameraConfiguration::validate()\n> >>> +{\n> >>> +\tconst CameraSensor *sensor = data_->cio2_.sensor_;\n> >>> +\tStatus status = Valid;\n> >>> +\n> >>> +\tif (config_.empty())\n> >>> +\t\treturn Invalid;\n> >>> +\n> >>> +\t/* Cap the number of entries to the available streams. */\n> >>> +\tif (config_.size() > 2) {\n> >>> +\t\tconfig_.resize(2);\n> >>> +\t\tstatus = Adjusted;\n> >>> +\t}\n> >>> +\n> >>> +\t/*\n> >>> +\t * Select the sensor format by collecting the maximum width and height\n> >>> +\t * and picking the closest larger match, as the IPU3 can downscale\n> >>> +\t * only. If no resolution is requested for any stream, or if no sensor\n> >>> +\t * resolution is large enough, pick the largest one.\n> >>> +\t */\n> >>> +\tSize size = {};\n> >>> +\n> >>> +\tfor (const StreamConfiguration &cfg : config_) {\n> >>> +\t\tif (cfg.size.width > size.width)\n> >>> +\t\t\tsize.width = cfg.size.width;\n> >>> +\t\tif (cfg.size.height > size.height)\n> >>> +\t\t\tsize.height = cfg.size.height;\n> >>> +\t}\n> >>> +\n> >>> +\tif (!size.width || !size.height)\n> >>> +\t\tsize = sensor->resolution();\n> >>> +\n> >>> +\tsensorFormat_ = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR10_1X10,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG10_1X10,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG10_1X10,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB10_1X10 },\n> >>> +\t\t\t\t\t  size);\n> >>> +\tif (!sensorFormat_.size.width || !sensorFormat_.size.height)\n> >>> +\t\tsensorFormat_.size = sensor->resolution();\n> >>> +\n> >>> +\t/*\n> >>> +\t * Verify and update all configuration entries, and assign a stream to\n> >>> +\t * each of them. The viewfinder stream can scale, while the output\n> >>> +\t * stream can crop only, so select the output stream when the requested\n> >>> +\t * resolution is equal to the sensor resolution, and the viewfinder\n> >>> +\t * stream otherwise.\n> >>> +\t */\n> >>> +\tstd::set<const IPU3Stream *> availableStreams = {\n> >>> +\t\t&data_->outStream_,\n> >>> +\t\t&data_->vfStream_,\n> >>> +\t};\n> >>> +\n> >>> +\tstreams_.clear();\n> >>> +\tstreams_.reserve(config_.size());\n> >>> +\n> >>> +\tfor (unsigned int i = 0; i < config_.size(); ++i) {\n> >>> +\t\tStreamConfiguration &cfg = config_[i];\n> >>> +\t\tconst unsigned int pixelFormat = cfg.pixelFormat;\n> >>> +\t\tconst Size size = cfg.size;\n> >>> +\t\tconst IPU3Stream *stream;\n> >>> +\n> >>> +\t\tif (cfg.size == sensorFormat_.size)\n> >>> +\t\t\tstream = &data_->outStream_;\n> >>> +\t\telse\n> >>> +\t\t\tstream = &data_->vfStream_;\n> >>> +\n> >>> +\t\tif (availableStreams.find(stream) == availableStreams.end())\n> >>> +\t\t\tstream = *availableStreams.begin();\n> >>> +\n> >>> +\t\tLOG(IPU3, Debug)\n> >>> +\t\t\t<< \"Assigned '\" << stream->name_ << \"' to stream \" << i;\n> >>> +\n> >>> +\t\tbool scale = stream == &data_->vfStream_;\n> >>> +\t\tadjustStream(i, scale);\n> >>> +\n> >>> +\t\tif (cfg.pixelFormat != pixelFormat || cfg.size != size) {\n> >>> +\t\t\tLOG(IPU3, Debug)\n> >>> +\t\t\t\t<< \"Stream \" << i << \" configuration adjusted to \"\n> >>> +\t\t\t\t<< cfg.toString();\n> >>> +\t\t\tstatus = Adjusted;\n> >>> +\t\t}\n> >>> +\n> >>> +\t\tstreams_.push_back(stream);\n> >>> +\t\tavailableStreams.erase(stream);\n> >>> +\t}\n> >>> +\n> >>> +\treturn status;\n> >>> +}\n> >>> +\n> >>>  PipelineHandlerIPU3::PipelineHandlerIPU3(CameraManager *manager)\n> >>>  \t: PipelineHandler(manager), cio2MediaDev_(nullptr), imguMediaDev_(nullptr)\n> >>>  {\n> >>> @@ -211,12 +383,14 @@ CameraConfiguration *PipelineHandlerIPU3::generateConfiguration(Camera *camera,\n> >>>  \tconst StreamRoles &roles)\n> >>>  {\n> >>>  \tIPU3CameraData *data = cameraData(camera);\n> >>> -\tCameraConfiguration *config = new CameraConfiguration();\n> >>> +\tIPU3CameraConfiguration *config;\n> >>>  \tstd::set<IPU3Stream *> streams = {\n> >>>  \t\t&data->outStream_,\n> >>>  \t\t&data->vfStream_,\n> >>>  \t};\n> >>>\n> >>> +\tconfig = new IPU3CameraConfiguration(camera, data);\n> >>> +\n> >>>  \tfor (const StreamRole role : roles) {\n> >>>  \t\tStreamConfiguration cfg = {};\n> >>>  \t\tIPU3Stream *stream = nullptr;\n> >>> @@ -296,71 +470,25 @@ CameraConfiguration *PipelineHandlerIPU3::generateConfiguration(Camera *camera,\n> >>>\n> >>>  \t\tstreams.erase(stream);\n> >>>\n> >>> -\t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> >>> -\t\tcfg.bufferCount = IPU3_BUFFER_COUNT;\n> >>> -\n> >>>  \t\tconfig->addConfiguration(cfg);\n> >>>  \t}\n> >>>\n> >>> +\tconfig->validate();\n> >>> +\n> >>>  \treturn config;\n> >>>  }\n> >>>\n> >>> -int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n> >>> +int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *c)\n> >>>  {\n> >>> +\tIPU3CameraConfiguration *config =\n> >>> +\t\tstatic_cast<IPU3CameraConfiguration *>(c);\n> >>>  \tIPU3CameraData *data = cameraData(camera);\n> >>>  \tIPU3Stream *outStream = &data->outStream_;\n> >>>  \tIPU3Stream *vfStream = &data->vfStream_;\n> >>>  \tCIO2Device *cio2 = &data->cio2_;\n> >>>  \tImgUDevice *imgu = data->imgu_;\n> >>> -\tSize sensorSize = {};\n> >>>  \tint ret;\n> >>>\n> >>> -\toutStream->active_ = false;\n> >>> -\tvfStream->active_ = false;\n> >>> -\tfor (StreamConfiguration &cfg : *config) {\n> >>> -\t\t/*\n> >>> -\t\t * Pick a stream for the configuration entry.\n> >>> -\t\t * \\todo: This is a naive temporary implementation that will be\n> >>> -\t\t * reworked when implementing camera configuration validation.\n> >>> -\t\t */\n> >>> -\t\tIPU3Stream *stream = vfStream->active_ ? outStream : vfStream;\n> >>> -\n> >>> -\t\t/*\n> >>> -\t\t * Verify that the requested size respects the IPU3 alignment\n> >>> -\t\t * requirements (the image width shall be a multiple of 8\n> >>> -\t\t * pixels and its height a multiple of 4 pixels) and the camera\n> >>> -\t\t * maximum sizes.\n> >>> -\t\t *\n> >>> -\t\t * \\todo: Consider the BDS scaling factor requirements: \"the\n> >>> -\t\t * downscaling factor must be an integer value multiple of 1/32\"\n> >>> -\t\t */\n> >>> -\t\tif (cfg.size.width % 8 || cfg.size.height % 4) {\n> >>> -\t\t\tLOG(IPU3, Error)\n> >>> -\t\t\t\t<< \"Invalid stream size: bad alignment\";\n> >>> -\t\t\treturn -EINVAL;\n> >>> -\t\t}\n> >>> -\n> >>> -\t\tconst Size &resolution = cio2->sensor_->resolution();\n> >>> -\t\tif (cfg.size.width > resolution.width ||\n> >>> -\t\t    cfg.size.height > resolution.height) {\n> >>> -\t\t\tLOG(IPU3, Error)\n> >>> -\t\t\t\t<< \"Invalid stream size: larger than sensor resolution\";\n> >>> -\t\t\treturn -EINVAL;\n> >>> -\t\t}\n> >>> -\n> >>> -\t\t/*\n> >>> -\t\t * Collect the maximum width and height: IPU3 can downscale\n> >>> -\t\t * only.\n> >>> -\t\t */\n> >>> -\t\tif (cfg.size.width > sensorSize.width)\n> >>> -\t\t\tsensorSize.width = cfg.size.width;\n> >>> -\t\tif (cfg.size.height > sensorSize.height)\n> >>> -\t\t\tsensorSize.height = cfg.size.height;\n> >>> -\n> >>> -\t\tstream->active_ = true;\n> >>> -\t\tcfg.setStream(stream);\n> >>> -\t}\n> >>> -\n> >>>  \t/*\n> >>>  \t * \\todo: Enable links selectively based on the requested streams.\n> >>>  \t * As of now, enable all links unconditionally.\n> >>> @@ -373,6 +501,7 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n> >>>  \t * Pass the requested stream size to the CIO2 unit and get back the\n> >>>  \t * adjusted format to be propagated to the ImgU output devices.\n> >>>  \t */\n> >>> +\tconst Size &sensorSize = config->sensorFormat().size;\n> >>>  \tV4L2DeviceFormat cio2Format = {};\n> >>>  \tret = cio2->configure(sensorSize, &cio2Format);\n> >>>  \tif (ret)\n> >>> @@ -383,8 +512,22 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n> >>>  \t\treturn ret;\n> >>>\n> >>>  \t/* Apply the format to the configured streams output devices. */\n> >>> -\tfor (StreamConfiguration &cfg : *config) {\n> >>> -\t\tIPU3Stream *stream = static_cast<IPU3Stream *>(cfg.stream());\n> >>> +\toutStream->active_ = false;\n> >>> +\tvfStream->active_ = false;\n> >>> +\n> >>> +\tfor (unsigned int i = 0; i < config->size(); ++i) {\n> >>> +\t\t/*\n> >>> +\t\t * Use a const_cast<> here instead of storing a mutable stream\n> >>> +\t\t * pointer in the configuration to let the compiler catch\n> >>> +\t\t * unwanted modifications of camera data in the configuration\n> >>> +\t\t * validate() implementation.\n> >>> +\t\t */\n> >>> +\t\tIPU3Stream *stream = const_cast<IPU3Stream *>(config->streams()[i]);\n> >>> +\t\tStreamConfiguration &cfg = (*config)[i];\n> >>> +\n> >>> +\t\tstream->active_ = true;\n> >>> +\t\tcfg.setStream(stream);\n> >>> +\n> >>>  \t\tret = imgu->configureOutput(stream->device_, cfg);\n> >>>  \t\tif (ret)\n> >>>  \t\t\treturn ret;\n> >>> diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> >>> index a1a4f205b4aa..42944c64189b 100644\n> >>> --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> >>> +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> >>> @@ -5,6 +5,7 @@\n> >>>   * rkisp1.cpp - Pipeline handler for Rockchip ISP1\n> >>>   */\n> >>>\n> >>> +#include <algorithm>\n> >>>  #include <iomanip>\n> >>>  #include <memory>\n> >>>  #include <vector>\n> >>> @@ -45,6 +46,29 @@ public:\n> >>>  \tCameraSensor *sensor_;\n> >>>  };\n> >>>\n> >>> +class RkISP1CameraConfiguration : public CameraConfiguration\n> >>> +{\n> >>> +public:\n> >>> +\tRkISP1CameraConfiguration(Camera *camera, RkISP1CameraData *data);\n> >>> +\n> >>> +\tStatus validate() override;\n> >>> +\n> >>> +\tconst V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }\n> >>> +\n> >>> +private:\n> >>> +\tstatic constexpr unsigned int RKISP1_BUFFER_COUNT = 4;\n> >>> +\n> >>> +\t/*\n> >>> +\t * The RkISP1CameraData instance is guaranteed to be valid as long as the\n> >>> +\t * corresponding Camera instance is valid. In order to borrow a\n> >>> +\t * reference to the camera data, store a new reference to the camera.\n> >>> +\t */\n> >>> +\tstd::shared_ptr<Camera> camera_;\n> >>> +\tconst RkISP1CameraData *data_;\n> >>> +\n> >>> +\tV4L2SubdeviceFormat sensorFormat_;\n> >>> +};\n> >>> +\n> >>>  class PipelineHandlerRkISP1 : public PipelineHandler\n> >>>  {\n> >>>  public:\n> >>> @@ -68,8 +92,6 @@ public:\n> >>>  \tbool match(DeviceEnumerator *enumerator) override;\n> >>>\n> >>>  private:\n> >>> -\tstatic constexpr unsigned int RKISP1_BUFFER_COUNT = 4;\n> >>> -\n> >>>  \tRkISP1CameraData *cameraData(const Camera *camera)\n> >>>  \t{\n> >>>  \t\treturn static_cast<RkISP1CameraData *>(\n> >>> @@ -88,6 +110,95 @@ private:\n> >>>  \tCamera *activeCamera_;\n> >>>  };\n> >>>\n> >>> +RkISP1CameraConfiguration::RkISP1CameraConfiguration(Camera *camera,\n> >>> +\t\t\t\t\t\t     RkISP1CameraData *data)\n> >>> +\t: CameraConfiguration()\n> >>> +{\n> >>> +\tcamera_ = camera->shared_from_this();\n> >>> +\tdata_ = data;\n> >>> +}\n> >>> +\n> >>> +CameraConfiguration::Status RkISP1CameraConfiguration::validate()\n> >>> +{\n> >>> +\tstatic const std::array<unsigned int, 8> formats{\n> >>> +\t\tV4L2_PIX_FMT_YUYV,\n> >>> +\t\tV4L2_PIX_FMT_YVYU,\n> >>> +\t\tV4L2_PIX_FMT_VYUY,\n> >>> +\t\tV4L2_PIX_FMT_NV16,\n> >>> +\t\tV4L2_PIX_FMT_NV61,\n> >>> +\t\tV4L2_PIX_FMT_NV21,\n> >>> +\t\tV4L2_PIX_FMT_NV12,\n> >>> +\t\tV4L2_PIX_FMT_GREY,\n> >>> +\t};\n> >>> +\n> >>> +\tconst CameraSensor *sensor = data_->sensor_;\n> >>> +\tStatus status = Valid;\n> >>> +\n> >>> +\tif (config_.empty())\n> >>> +\t\treturn Invalid;\n> >>> +\n> >>> +\t/* Cap the number of entries to the available streams. */\n> >>> +\tif (config_.size() > 1) {\n> >>> +\t\tconfig_.resize(1);\n> >>> +\t\tstatus = Adjusted;\n> >>> +\t}\n> >>> +\n> >>> +\tStreamConfiguration &cfg = config_[0];\n> >>> +\n> >>> +\t/* Adjust the pixel format. */\n> >>> +\tif (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==\n> >>> +\t    formats.end()) {\n> >>> +\t\tLOG(RkISP1, Debug) << \"Adjusting format to NV12\";\n> >>> +\t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> >>> +\t\tstatus = Adjusted;\n> >>> +\t}\n> >>> +\n> >>> +\t/* Select the sensor format. */\n> >>> +\tsensorFormat_ = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR12_1X12,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG12_1X12,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG12_1X12,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB12_1X12,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SBGGR10_1X10,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG10_1X10,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG10_1X10,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB10_1X10,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SBGGR8_1X8,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG8_1X8,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG8_1X8,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB8_1X8 },\n> >>> +\t\t\t\t\t  cfg.size);\n> >>> +\tif (!sensorFormat_.size.width || !sensorFormat_.size.height)\n> >>> +\t\tsensorFormat_.size = sensor->resolution();\n> >>> +\n> >>> +\t/*\n> >>> +\t * Provide a suitable default that matches the sensor aspect\n> >>> +\t * ratio and clamp the size to the hardware bounds.\n> >>> +\t *\n> >>> +\t * \\todo: Check the hardware alignment constraints.\n> >>> +\t */\n> >>> +\tconst Size size = cfg.size;\n> >>> +\n> >>> +\tif (!cfg.size.width || !cfg.size.height) {\n> >>> +\t\tcfg.size.width = 1280;\n> >>> +\t\tcfg.size.height = 1280 * sensorFormat_.size.height\n> >>> +\t\t\t\t/ sensorFormat_.size.width;\n> >>> +\t}\n> >>> +\n> >>> +\tcfg.size.width = std::max(32U, std::min(4416U, cfg.size.width));\n> >>> +\tcfg.size.height = std::max(16U, std::min(3312U, cfg.size.height));\n> >>> +\n> >>> +\tif (cfg.size != size) {\n> >>> +\t\tLOG(RkISP1, Debug)\n> >>> +\t\t\t<< \"Adjusting size from \" << size.toString()\n> >>> +\t\t\t<< \" to \" << cfg.size.toString();\n> >>> +\t\tstatus = Adjusted;\n> >>> +\t}\n> >>> +\n> >>> +\tcfg.bufferCount = RKISP1_BUFFER_COUNT;\n> >>> +\n> >>> +\treturn status;\n> >>> +}\n> >>> +\n> >>>  PipelineHandlerRkISP1::PipelineHandlerRkISP1(CameraManager *manager)\n> >>>  \t: PipelineHandler(manager), dphy_(nullptr), isp_(nullptr),\n> >>>  \t  video_(nullptr)\n> >>> @@ -109,37 +220,31 @@ CameraConfiguration *PipelineHandlerRkISP1::generateConfiguration(Camera *camera\n> >>>  \tconst StreamRoles &roles)\n> >>>  {\n> >>>  \tRkISP1CameraData *data = cameraData(camera);\n> >>> -\tCameraConfiguration *config = new CameraConfiguration();\n> >>> +\tCameraConfiguration *config = new RkISP1CameraConfiguration(camera, data);\n> >>>\n> >>>  \tif (!roles.empty()) {\n> >>>  \t\tStreamConfiguration cfg{};\n> >>>\n> >>>  \t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> >>>  \t\tcfg.size = data->sensor_->resolution();\n> >>> -\t\tcfg.bufferCount = RKISP1_BUFFER_COUNT;\n> >>>\n> >>>  \t\tconfig->addConfiguration(cfg);\n> >>>  \t}\n> >>>\n> >>> +\tconfig->validate();\n> >>> +\n> >>>  \treturn config;\n> >>>  }\n> >>>\n> >>> -int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *config)\n> >>> +int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)\n> >>>  {\n> >>> +\tRkISP1CameraConfiguration *config =\n> >>> +\t\tstatic_cast<RkISP1CameraConfiguration *>(c);\n> >>>  \tRkISP1CameraData *data = cameraData(camera);\n> >>>  \tStreamConfiguration &cfg = config->at(0);\n> >>>  \tCameraSensor *sensor = data->sensor_;\n> >>>  \tint ret;\n> >>>\n> >>> -\t/* Verify the configuration. */\n> >>> -\tconst Size &resolution = sensor->resolution();\n> >>> -\tif (cfg.size.width > resolution.width ||\n> >>> -\t    cfg.size.height > resolution.height) {\n> >>> -\t\tLOG(RkISP1, Error)\n> >>> -\t\t\t<< \"Invalid stream size: larger than sensor resolution\";\n> >>> -\t\treturn -EINVAL;\n> >>> -\t}\n> >>> -\n> >>>  \t/*\n> >>>  \t * Configure the sensor links: enable the link corresponding to this\n> >>>  \t * camera and disable all the other sensor links.\n> >>> @@ -167,21 +272,7 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *config\n> >>>  \t * Configure the format on the sensor output and propagate it through\n> >>>  \t * the pipeline.\n> >>>  \t */\n> >>> -\tV4L2SubdeviceFormat format;\n> >>> -\tformat = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR12_1X12,\n> >>> -\t\t\t\t     MEDIA_BUS_FMT_SGBRG12_1X12,\n> >>> -\t\t\t\t     MEDIA_BUS_FMT_SGRBG12_1X12,\n> >>> -\t\t\t\t     MEDIA_BUS_FMT_SRGGB12_1X12,\n> >>> -\t\t\t\t     MEDIA_BUS_FMT_SBGGR10_1X10,\n> >>> -\t\t\t\t     MEDIA_BUS_FMT_SGBRG10_1X10,\n> >>> -\t\t\t\t     MEDIA_BUS_FMT_SGRBG10_1X10,\n> >>> -\t\t\t\t     MEDIA_BUS_FMT_SRGGB10_1X10,\n> >>> -\t\t\t\t     MEDIA_BUS_FMT_SBGGR8_1X8,\n> >>> -\t\t\t\t     MEDIA_BUS_FMT_SGBRG8_1X8,\n> >>> -\t\t\t\t     MEDIA_BUS_FMT_SGRBG8_1X8,\n> >>> -\t\t\t\t     MEDIA_BUS_FMT_SRGGB8_1X8 },\n> >>> -\t\t\t\t   cfg.size);\n> >>> -\n> >>> +\tV4L2SubdeviceFormat format = config->sensorFormat();\n> >>>  \tLOG(RkISP1, Debug) << \"Configuring sensor with \" << format.toString();\n> >>>\n> >>>  \tret = sensor->setFormat(&format);\n> >>> diff --git a/src/libcamera/pipeline/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo.cpp\n> >>> index 8254e1fdac1e..f7ffeb439cf3 100644\n> >>> --- a/src/libcamera/pipeline/uvcvideo.cpp\n> >>> +++ b/src/libcamera/pipeline/uvcvideo.cpp\n> >>> @@ -39,6 +39,14 @@ public:\n> >>>  \tStream stream_;\n> >>>  };\n> >>>\n> >>> +class UVCCameraConfiguration : public CameraConfiguration\n> >>> +{\n> >>> +public:\n> >>> +\tUVCCameraConfiguration();\n> >>> +\n> >>> +\tStatus validate() override;\n> >>> +};\n> >>> +\n> >>>  class PipelineHandlerUVC : public PipelineHandler\n> >>>  {\n> >>>  public:\n> >>> @@ -68,6 +76,45 @@ private:\n> >>>  \t}\n> >>>  };\n> >>>\n> >>> +UVCCameraConfiguration::UVCCameraConfiguration()\n> >>> +\t: CameraConfiguration()\n> >>> +{\n> >>> +}\n> >>> +\n> >>> +CameraConfiguration::Status UVCCameraConfiguration::validate()\n> >>> +{\n> >>> +\tStatus status = Valid;\n> >>> +\n> >>> +\tif (config_.empty())\n> >>> +\t\treturn Invalid;\n> >>> +\n> >>> +\t/* Cap the number of entries to the available streams. */\n> >>> +\tif (config_.size() > 1) {\n> >>> +\t\tconfig_.resize(1);\n> >>> +\t\tstatus = Adjusted;\n> >>> +\t}\n> >>> +\n> >>> +\tStreamConfiguration &cfg = config_[0];\n> >>> +\n> >>> +\t/* \\todo: Validate the configuration against the device capabilities. */\n> >>> +\tconst unsigned int pixelFormat = cfg.pixelFormat;\n> >>> +\tconst Size size = cfg.size;\n> >>> +\n> >>> +\tcfg.pixelFormat = V4L2_PIX_FMT_YUYV;\n> >>> +\tcfg.size = { 640, 480 };\n> >>> +\n> >>> +\tif (cfg.pixelFormat != pixelFormat || cfg.size != size) {\n> >>> +\t\tLOG(UVC, Debug)\n> >>> +\t\t\t<< \"Adjusting configuration from \" << cfg.toString()\n> >>> +\t\t\t<< \" to \" << cfg.size.toString() << \"-YUYV\";\n> >>> +\t\tstatus = Adjusted;\n> >>> +\t}\n> >>> +\n> >>> +\tcfg.bufferCount = 4;\n> >>> +\n> >>> +\treturn status;\n> >>> +}\n> >>> +\n> >>>  PipelineHandlerUVC::PipelineHandlerUVC(CameraManager *manager)\n> >>>  \t: PipelineHandler(manager)\n> >>>  {\n> >>> @@ -76,10 +123,10 @@ PipelineHandlerUVC::PipelineHandlerUVC(CameraManager *manager)\n> >>>  CameraConfiguration *PipelineHandlerUVC::generateConfiguration(Camera *camera,\n> >>>  \tconst StreamRoles &roles)\n> >>>  {\n> >>> -\tCameraConfiguration *config = new CameraConfiguration();\n> >>> +\tCameraConfiguration *config = new UVCCameraConfiguration();\n> >>>\n> >>>  \tif (!roles.empty()) {\n> >>> -\t\tStreamConfiguration cfg;\n> >>> +\t\tStreamConfiguration cfg{};\n> >>>\n> >>>  \t\tcfg.pixelFormat = V4L2_PIX_FMT_YUYV;\n> >>>  \t\tcfg.size = { 640, 480 };\n> >>> @@ -88,6 +135,8 @@ CameraConfiguration *PipelineHandlerUVC::generateConfiguration(Camera *camera,\n> >>>  \t\tconfig->addConfiguration(cfg);\n> >>>  \t}\n> >>>\n> >>> +\tconfig->validate();\n> >>> +\n> >>>  \treturn config;\n> >>>  }\n> >>>\n> >>> diff --git a/src/libcamera/pipeline/vimc.cpp b/src/libcamera/pipeline/vimc.cpp\n> >>> index 2bf85d0a0b32..2a61d2893e3a 100644\n> >>> --- a/src/libcamera/pipeline/vimc.cpp\n> >>> +++ b/src/libcamera/pipeline/vimc.cpp\n> >>> @@ -5,6 +5,8 @@\n> >>>   * vimc.cpp - Pipeline handler for the vimc device\n> >>>   */\n> >>>\n> >>> +#include <algorithm>\n> >>> +\n> >>>  #include <libcamera/camera.h>\n> >>>  #include <libcamera/request.h>\n> >>>  #include <libcamera/stream.h>\n> >>> @@ -39,6 +41,14 @@ public:\n> >>>  \tStream stream_;\n> >>>  };\n> >>>\n> >>> +class VimcCameraConfiguration : public CameraConfiguration\n> >>> +{\n> >>> +public:\n> >>> +\tVimcCameraConfiguration();\n> >>> +\n> >>> +\tStatus validate() override;\n> >>> +};\n> >>> +\n> >>>  class PipelineHandlerVimc : public PipelineHandler\n> >>>  {\n> >>>  public:\n> >>> @@ -68,6 +78,57 @@ private:\n> >>>  \t}\n> >>>  };\n> >>>\n> >>> +VimcCameraConfiguration::VimcCameraConfiguration()\n> >>> +\t: CameraConfiguration()\n> >>> +{\n> >>> +}\n> >>> +\n> >>> +CameraConfiguration::Status VimcCameraConfiguration::validate()\n> >>> +{\n> >>> +\tstatic const std::array<unsigned int, 3> formats{\n> >>> +\t\tV4L2_PIX_FMT_BGR24,\n> >>> +\t\tV4L2_PIX_FMT_RGB24,\n> >>> +\t\tV4L2_PIX_FMT_ARGB32,\n> >>> +\t};\n> >>> +\n> >>> +\tStatus status = Valid;\n> >>> +\n> >>> +\tif (config_.empty())\n> >>> +\t\treturn Invalid;\n> >>> +\n> >>> +\t/* Cap the number of entries to the available streams. */\n> >>> +\tif (config_.size() > 1) {\n> >>> +\t\tconfig_.resize(1);\n> >>> +\t\tstatus = Adjusted;\n> >>> +\t}\n> >>> +\n> >>> +\tStreamConfiguration &cfg = config_[0];\n> >>> +\n> >>> +\t/* Adjust the pixel format. */\n> >>> +\tif (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==\n> >>> +\t    formats.end()) {\n> >>> +\t\tLOG(VIMC, Debug) << \"Adjusting format to RGB24\";\n> >>> +\t\tcfg.pixelFormat = V4L2_PIX_FMT_RGB24;\n> >>> +\t\tstatus = Adjusted;\n> >>> +\t}\n> >>> +\n> >>> +\t/* Clamp the size based on the device limits. */\n> >>> +\tconst Size size = cfg.size;\n> >>> +\n> >>> +\tcfg.size.width = std::max(16U, std::min(4096U, cfg.size.width));\n> >>> +\tcfg.size.height = std::max(16U, std::min(2160U, cfg.size.height));\n> >>> +\n> >>> +\tif (cfg.size != size) {\n> >>> +\t\tLOG(VIMC, Debug)\n> >>> +\t\t\t<< \"Adjusting size to \" << cfg.size.toString();\n> >>> +\t\tstatus = Adjusted;\n> >>> +\t}\n> >>> +\n> >>> +\tcfg.bufferCount = 4;\n> >>> +\n> >>> +\treturn status;\n> >>> +}\n> >>> +\n> >>>  PipelineHandlerVimc::PipelineHandlerVimc(CameraManager *manager)\n> >>>  \t: PipelineHandler(manager)\n> >>>  {\n> >>> @@ -76,10 +137,10 @@ PipelineHandlerVimc::PipelineHandlerVimc(CameraManager *manager)\n> >>>  CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera,\n> >>>  \tconst StreamRoles &roles)\n> >>>  {\n> >>> -\tCameraConfiguration *config = new CameraConfiguration();\n> >>> +\tCameraConfiguration *config = new VimcCameraConfiguration();\n> >>>\n> >>>  \tif (!roles.empty()) {\n> >>> -\t\tStreamConfiguration cfg;\n> >>> +\t\tStreamConfiguration cfg{};\n> >>>\n> >>>  \t\tcfg.pixelFormat = V4L2_PIX_FMT_RGB24;\n> >>>  \t\tcfg.size = { 640, 480 };\n> >>> @@ -88,6 +149,8 @@ CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera,\n> >>>  \t\tconfig->addConfiguration(cfg);\n> >>>  \t}\n> >>>\n> >>> +\tconfig->validate();\n> >>> +\n> >>>  \treturn config;\n> >>>  }\n> >>>\n> >>> diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp\n> >>> index de46e98880a2..dd56907d817e 100644\n> >>> --- a/src/libcamera/pipeline_handler.cpp\n> >>> +++ b/src/libcamera/pipeline_handler.cpp\n> >>> @@ -248,17 +248,14 @@ void PipelineHandler::unlock()\n> >>>   * is the Camera class which will receive configuration to apply from the\n> >>>   * application.\n> >>>   *\n> >>> - * Each pipeline handler implementation is responsible for validating\n> >>> - * that the configuration requested in \\a config can be achieved\n> >>> - * exactly. Any difference in pixel format, frame size or any other\n> >>> - * parameter shall result in the -EINVAL error being returned, and no\n> >>> - * change in configuration being applied to the pipeline. If\n> >>> - * configuration of a subset of the streams can't be satisfied, the\n> >>> - * whole configuration is considered invalid.\n> >>> + * The configuration is guaranteed to have been validated with\n> >>> + * CameraConfiguration::valid(). The pipeline handler implementation shall not\n> >>> + * perform further validation and may rely on any custom field stored in its\n> >>> + * custom CameraConfiguration derived class.\n> >>>   *\n> >>> - * Once the configuration is validated and the camera configured, the pipeline\n> >>> - * handler shall associate a Stream instance to each StreamConfiguration entry\n> >>> - * in the CameraConfiguration with the StreamConfiguration::setStream() method.\n> >>> + * When configuring the camera the pipeline handler shall associate a Stream\n> >>> + * instance to each StreamConfiguration entry in the CameraConfiguration using\n> >>> + * the StreamConfiguration::setStream() method.\n> >>>   *\n> >>>   * \\return 0 on success or a negative error code otherwise\n> >>>   */\n> >>> diff --git a/test/camera/capture.cpp b/test/camera/capture.cpp\n> >>> index 137aa649a505..f122f79bb1ec 100644\n> >>> --- a/test/camera/capture.cpp\n> >>> +++ b/test/camera/capture.cpp\n> >>> @@ -45,7 +45,7 @@ protected:\n> >>>  \t\tCameraTest::init();\n> >>>\n> >>>  \t\tconfig_ = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> >>> -\t\tif (!config_) {\n> >>> +\t\tif (!config_ || config_->size() != 1) {\n> >>>  \t\t\tcout << \"Failed to generate default configuration\" << endl;\n> >>>  \t\t\tCameraTest::cleanup();\n> >>>  \t\t\treturn TestFail;\n> >>> @@ -58,11 +58,6 @@ protected:\n> >>>  \t{\n> >>>  \t\tStreamConfiguration &cfg = config_->at(0);\n> >>>\n> >>> -\t\tif (!config_->isValid()) {\n> >>> -\t\t\tcout << \"Failed to read default configuration\" << endl;\n> >>> -\t\t\treturn TestFail;\n> >>> -\t\t}\n> >>> -\n> >>>  \t\tif (camera_->acquire()) {\n> >>>  \t\t\tcout << \"Failed to acquire the camera\" << endl;\n> >>>  \t\t\treturn TestFail;\n> >>> diff --git a/test/camera/configuration_default.cpp b/test/camera/configuration_default.cpp\n> >>> index d5cefc1127c9..81055da1d513 100644\n> >>> --- a/test/camera/configuration_default.cpp\n> >>> +++ b/test/camera/configuration_default.cpp\n> >>> @@ -22,7 +22,7 @@ protected:\n> >>>\n> >>>  \t\t/* Test asking for configuration for a video stream. */\n> >>>  \t\tconfig = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> >>> -\t\tif (!config || !config->isValid()) {\n> >>> +\t\tif (!config || config->size() != 1) {\n> >>>  \t\t\tcout << \"Default configuration invalid\" << endl;\n> >>>  \t\t\tdelete config;\n> >>>  \t\t\treturn TestFail;\n> >>> @@ -35,7 +35,7 @@ protected:\n> >>>  \t\t * stream roles returns an empty camera configuration.\n> >>>  \t\t */\n> >>>  \t\tconfig = camera_->generateConfiguration({});\n> >>> -\t\tif (!config || config->isValid()) {\n> >>> +\t\tif (!config || config->size() != 0) {\n> >>>  \t\t\tcout << \"Failed to retrieve configuration for empty roles list\"\n> >>>  \t\t\t     << endl;\n> >>>  \t\t\tdelete config;\n> >>> diff --git a/test/camera/configuration_set.cpp b/test/camera/configuration_set.cpp\n> >>> index 23c611a93355..a4e2da16a88b 100644\n> >>> --- a/test/camera/configuration_set.cpp\n> >>> +++ b/test/camera/configuration_set.cpp\n> >>> @@ -21,7 +21,7 @@ protected:\n> >>>  \t\tCameraTest::init();\n> >>>\n> >>>  \t\tconfig_ = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> >>> -\t\tif (!config_) {\n> >>> +\t\tif (!config_ || config_->size() != 1) {\n> >>>  \t\t\tcout << \"Failed to generate default configuration\" << endl;\n> >>>  \t\t\tCameraTest::cleanup();\n> >>>  \t\t\treturn TestFail;\n> >>> @@ -34,11 +34,6 @@ protected:\n> >>>  \t{\n> >>>  \t\tStreamConfiguration &cfg = config_->at(0);\n> >>>\n> >>> -\t\tif (!config_->isValid()) {\n> >>> -\t\t\tcout << \"Failed to read default configuration\" << endl;\n> >>> -\t\t\treturn TestFail;\n> >>> -\t\t}\n> >>> -\n> >>>  \t\tif (camera_->acquire()) {\n> >>>  \t\t\tcout << \"Failed to acquire the camera\" << endl;\n> >>>  \t\t\treturn TestFail;","headers":{"Return-Path":"<laurent.pinchart@ideasonboard.com>","Received":["from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 197C860C02\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 22 May 2019 18:19:26 +0200 (CEST)","from pendragon.ideasonboard.com\n\t(dfj612yhrgyx302h3jwwy-3.rev.dnainternet.fi\n\t[IPv6:2001:14ba:21f5:5b00:ce28:277f:58d7:3ca4])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 7D38C443;\n\tWed, 22 May 2019 18:19:25 +0200 (CEST)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1558541965;\n\tbh=fU4Fs3oEfJ7sqQGPYu+k7+B0J3lbb6nwsKzXVjuIIJU=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=rX6NAjt0L3Gf9+lXJnDpw1GUQrBBMrfFnuuZVnL/mXNFgNdI+hhzlCmM8pAUjJXfe\n\tamaF+cbvRX6en8LQeDulP2fs26S6MQ7iLhMiH0qdackmlcW3wXlBr+yFnmHbCZmSqY\n\tqgaiR734se5sbdkDWhrLOWq9ptuzXIOe8ZBVDyIQ=","Date":"Wed, 22 May 2019 19:19:08 +0300","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Niklas =?utf-8?q?S=C3=B6derlund?= <niklas.soderlund@ragnatech.se>","Cc":"Jacopo Mondi <jacopo@jmondi.org>, libcamera-devel@lists.libcamera.org","Message-ID":"<20190522161908.GC8868@pendragon.ideasonboard.com>","References":"<20190519150047.12444-1-laurent.pinchart@ideasonboard.com>\n\t<20190519150047.12444-7-laurent.pinchart@ideasonboard.com>\n\t<20190521084950.z5ahk3fiptltok7i@uno.localdomain>\n\t<20190521125109.GD5674@pendragon.ideasonboard.com>\n\t<20190522105516.GB1651@bigcity.dyn.berto.se>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<20190522105516.GB1651@bigcity.dyn.berto.se>","User-Agent":"Mutt/1.10.1 (2018-07-13)","Subject":"Re: [libcamera-devel] [PATCH v2 6/6] libcamera: camera: Add a\n\tvalidation API to the CameraConfiguration class","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.23","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Wed, 22 May 2019 16:19:26 -0000"}},{"id":1665,"web_url":"https://patchwork.libcamera.org/comment/1665/","msgid":"<20190522163446.GD8868@pendragon.ideasonboard.com>","date":"2019-05-22T16:34:46","subject":"Re: [libcamera-devel] [PATCH v2 6/6] libcamera: camera: Add a\n\tvalidation API to the CameraConfiguration class","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Jacopo,\n\nOn Tue, May 21, 2019 at 11:43:29PM +0200, Jacopo Mondi wrote:\n> On Tue, May 21, 2019 at 03:49:55PM +0300, Laurent Pinchart wrote:\n> > On Tue, May 21, 2019 at 12:05:06AM +0200, Jacopo Mondi wrote:\n> >> Hi Laurent,\n> >>\n> >> In order to validate if the API is dump-proof enough, I'm starting\n> >> the review from the last patch, so I might be missing some pieces\n> >> here and there...\n> >>\n> >> On Sun, May 19, 2019 at 06:00:47PM +0300, Laurent Pinchart wrote:\n> >>> The CameraConfiguration class implements a simple storage of\n> >>> StreamConfiguration with internal validation limited to verifying that\n> >>> the stream configurations are not empty. Extend this mechanism by\n> >>> implementing a smart validate() method backed by pipeline handlers.\n> >>>\n> >>> This new mechanism changes the semantics of the camera configuration.\n> >>\n> >> s/semantics/semantic ?\n> >\n> > I think you're right, I'll fix that.\n> >\n> >>> The Camera::generateConfiguration() operation still generates a default\n> >>> configuration based on roles, but now also supports generating empty\n> >>> configurations to be filled by applications. Applications can inspect\n> >>> the configuration, optionally modify it, and validate it. The validation\n> >>> implements \"try\" semantics and adjusts invalid configurations instead of\n> >>> rejecting them completely. Applications then decide whether to accept\n> >>> the modified configuration, or try again with a different set of\n> >>> parameters. Once the configuration is valid, it is passed to\n> >>> Camera::configure(), and pipeline handlers are guaranteed that the\n> >>> configuration they receive is valid.\n> >>>\n> >>> A reference to the Camera may need to be stored in the\n> >>> CameraConfiguration derived classes in order to access it from their\n> >>> validate() implementation. This must be stored as a std::shared_ptr<> as\n> >>> the CameraConfiguration instances belong to applications. In order to\n> >>> make this possible, make the Camera class inherit from\n> >>> std::shared_from_this<>.\n> >>\n> >> If I got this right we'll have a\n> >>\n> >> CameraConfiguration::validate() and a\n> >> Camera.configure()\n> >>\n> >> and CameraConfiguration has to keep a reference to the Camera to\n> >> access it.\n> >>\n> >> Can we provide a Camera::validate(StreamConfiguration *) instead?\n> >> We could handle the shared reference in the Camera and not let\n> >> CameraConfiguration subclasses have to deal with it in this way?\n> >\n> > I assume you meant a Camera::validate(CameraConfiguration *), as we want\n> > to validate the whole configuration, not stream per stream (the idea of\n> > this series is to support cross-stream validation constraints).\n> \n> Yes indeed, sorry.\n> \n> >\n> > First of all, the method would need to be called\n> > Camera::validateConfiguration(). This would need to be delegated to\n> \n> well, camera->validate(config); is quite self-explanatory to me :)\n\nYes, but Camera::validate() isn't. A method should be named based on the\naction it performs, and if there's no qualifier for the action, I think\nit should refer to the object the method belongs to by default.\n\n> > PipelineHandler::validateConfiguration(Camera *, CameraConfiguration *).\n> \n> why so?\n> \n> Can't\n> Status Camera::validate(CameraConfiguration *config)\n> {\n>         return config->validate();\n> }\n> \n> Validate will stay pure virtual, and the derived classes\n> implementation will be called, as it already happens at\n> Camera::configure() time.\n> \n> Am I missing something obvious here?\n\nOh, then I missed something. I thought your point was to remove the need\nfor storing a shared pointer to the camera in the CameraConfiguration\nderived classes. With the above construct we will still need them, so\nwhat's the advantage over calling config->validate() directly in the\napplication ?\n\n> > The pipeline handler would then need to cast the camera configuration to\n> > its custom configuration derived class (opening the door to applications\n> > calling Camera::validateConfiguration() with a configuration for the\n> > wrong camera, but that's already possible with Camera::configure() I\n> > supposed). We could try that, but I think it would make the API less\n> > clean for applications, for little benefits in my opinion (but we may\n> > have a different view of the benefits :-)).\n> >\n> >>>\n> >>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> >>> ---\n> >>>  include/libcamera/camera.h               |  17 +-\n> >>>  src/cam/main.cpp                         |   2 +-\n> >>>  src/libcamera/camera.cpp                 |  80 +++++--\n> >>>  src/libcamera/pipeline/ipu3/ipu3.cpp     | 255 ++++++++++++++++++-----\n> >>>  src/libcamera/pipeline/rkisp1/rkisp1.cpp | 149 ++++++++++---\n> >>>  src/libcamera/pipeline/uvcvideo.cpp      |  53 ++++-\n> >>>  src/libcamera/pipeline/vimc.cpp          |  67 +++++-\n> >>>  src/libcamera/pipeline_handler.cpp       |  17 +-\n> >>>  test/camera/capture.cpp                  |   7 +-\n> >>>  test/camera/configuration_default.cpp    |   4 +-\n> >>>  test/camera/configuration_set.cpp        |   7 +-\n> >>>  11 files changed, 516 insertions(+), 142 deletions(-)\n> >>>\n> >>> diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> >>> index 144133c5de9f..8c0049a1dd94 100644\n> >>> --- a/include/libcamera/camera.h\n> >>> +++ b/include/libcamera/camera.h\n> >>> @@ -25,14 +25,19 @@ class Request;\n> >>>  class CameraConfiguration\n> >>>  {\n> >>>  public:\n> >>> +\tenum Status {\n> >>> +\t\tValid,\n> >>> +\t\tAdjusted,\n> >>> +\t\tInvalid,\n> >>> +\t};\n> >>> +\n> >>>  \tusing iterator = std::vector<StreamConfiguration>::iterator;\n> >>>  \tusing const_iterator = std::vector<StreamConfiguration>::const_iterator;\n> >>>\n> >>> -\tCameraConfiguration();\n> >>> +\tvirtual ~CameraConfiguration();\n> >>>\n> >>>  \tvoid addConfiguration(const StreamConfiguration &cfg);\n> >>> -\n> >>> -\tbool isValid() const;\n> >>> +\tvirtual Status validate() = 0;\n> >>>\n> >>>  \tStreamConfiguration &at(unsigned int index);\n> >>>  \tconst StreamConfiguration &at(unsigned int index) const;\n> >>> @@ -53,11 +58,13 @@ public:\n> >>>  \tbool empty() const;\n> >>>  \tstd::size_t size() const;\n> >>>\n> >>> -private:\n> >>> +protected:\n> >>> +\tCameraConfiguration();\n> >>> +\n> >>>  \tstd::vector<StreamConfiguration> config_;\n> >>>  };\n> >>>\n> >>> -class Camera final\n> >>> +class Camera final : public std::enable_shared_from_this<Camera>\n> >>>  {\n> >>>  public:\n> >>>  \tstatic std::shared_ptr<Camera> create(PipelineHandler *pipe,\n> >>> diff --git a/src/cam/main.cpp b/src/cam/main.cpp\n> >>> index 7550ae4f3428..23da5c687d89 100644\n> >>> --- a/src/cam/main.cpp\n> >>> +++ b/src/cam/main.cpp\n> >>> @@ -116,7 +116,7 @@ static CameraConfiguration *prepareCameraConfig()\n> >>>  \t}\n> >>>\n> >>>  \tCameraConfiguration *config = camera->generateConfiguration(roles);\n> >>> -\tif (!config || !config->isValid()) {\n> >>> +\tif (!config || config->size() != roles.size()) {\n> >>>  \t\tstd::cerr << \"Failed to get default stream configuration\"\n> >>>  \t\t\t  << std::endl;\n> >>>  \t\tdelete config;\n> >>> diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> >>> index 0e80691ed862..9da5d4f613f4 100644\n> >>> --- a/src/libcamera/camera.cpp\n> >>> +++ b/src/libcamera/camera.cpp\n> >>> @@ -52,6 +52,28 @@ LOG_DECLARE_CATEGORY(Camera)\n> >>>   * operator[](int) returns a reference to the StreamConfiguration based on its\n> >>>   * insertion index. Accessing a stream configuration with an invalid index\n> >>>   * results in undefined behaviour.\n> >>> + *\n> >>> + * CameraConfiguration instances are retrieved from the camera with\n> >>> + * Camera::generateConfiguration(). Applications may then inspect the\n> >>> + * configuration, modify it, and possibly add new stream configuration entries\n> >>> + * with addConfiguration(). Once the camera configuration satisfies the\n> >>> + * application, it shall be validated by a call to validate(). The validation\n> >>> + * implements \"try\" semantics: it adjusts invalid configurations to the closest\n> >>> + * achievable parameters instead of rejecting them completely. Applications\n> >>> + * then decide whether to accept the modified configuration, or try again with\n> >>> + * a different set of parameters. Once the configuration is valid, it is passed\n> >>> + * to Camera::configure().\n> >>> + */\n> >>> +\n> >>> +/**\n> >>> + * \\enum CameraConfiguration::Status\n> >>> + * \\brief Validity of a camera configuration\n> >>> + * \\var CameraConfiguration::Valid\n> >>> + * The configuration is fully valid\n> >>> + * \\var CameraConfiguration::Adjusted\n> >>> + * The configuration has been adjusted to a valid configuration\n> >>> + * \\var CameraConfiguration::Invalid\n> >>> + * The configuration is invalid and can't be adjusted automatically\n> >>>   */\n> >>>\n> >>>  /**\n> >>> @@ -73,6 +95,10 @@ CameraConfiguration::CameraConfiguration()\n> >>>  {\n> >>>  }\n> >>>\n> >>> +CameraConfiguration::~CameraConfiguration()\n> >>> +{\n> >>> +}\n> >>> +\n> >>>  /**\n> >>>   * \\brief Add a stream configuration to the camera configuration\n> >>>   * \\param[in] cfg The stream configuration\n> >>> @@ -83,27 +109,31 @@ void CameraConfiguration::addConfiguration(const StreamConfiguration &cfg)\n> >>>  }\n> >>>\n> >>>  /**\n> >>> - * \\brief Check if the camera configuration is valid\n> >>> + * \\fn CameraConfiguration::validate()\n> >>> + * \\brief Validate and possibly adjust the camera configuration\n> >>>   *\n> >>> - * A camera configuration is deemed to be valid if it contains at least one\n> >>> - * stream configuration and all stream configurations contain valid information.\n> >>> - * Stream configurations are deemed to be valid if all fields are none zero.\n> >>> + * This method adjusts the camera configuration to the closest valid\n> >>> + * configuration and returns the validation status.\n> >>>   *\n> >>> - * \\return True if the configuration is valid\n> >>> + * \\todo: Define exactly when to return each status code. Should stream\n> >>> + * parameters set to 0 by the caller be adjusted without returning Adjusted ?\n> >>> + * This would potentially be useful for applications but would get in the way\n> >>> + * in Camera::configure(). Do we need an extra status code to signal this ?\n> >>\n> >> I'm not sure I did get why it gets in the way of configure()\n> >\n> > Because Camera::configure() calls validate() and returns an error if the\n> > status is Adjusted or Invalid, as the application is responsible for\n> > passing a fully valid configuration to Camera::configure().\n> >\n> >>> + *\n> >>> + * \\todo: Handle validation of buffers count when refactoring the buffers API.\n> >>> + *\n> >>> + * \\return A CameraConfiguration::Status value that describes the validation\n> >>> + * status.\n> >>> + * \\retval CameraConfiguration::Invalid The configuration is invalid and can't\n> >>> + * be adjusted. This may only occur in extreme cases such as when the\n> >>> + * configuration is empty.\n> >>\n> >> nit: instead of describing how the returned configuration looks like,\n> >> do you have an example of application provided parameters which might\n> >> trigger and invalid use case ?\n> >\n> > Not at the moment, no. I have no other case in mind, but we may find\n> > some in the future.\n> >\n> >>> + * \\retval CameraConfigutation::Adjusted The configuration has been adjusted\n> >>> + * and is now valid. Parameters may have changed for any stream, and stream\n> >>> + * configurations may have been removed. The caller shall check the\n> >>> + * configuration carefully.\n> >>> + * \\retval CameraConfiguration::Valid The configuration was already valid and\n> >>> + * hasn't been adjusted.\n> >>>   */\n> >>> -bool CameraConfiguration::isValid() const\n> >>> -{\n> >>> -\tif (empty())\n> >>> -\t\treturn false;\n> >>> -\n> >>> -\tfor (const StreamConfiguration &cfg : config_) {\n> >>> -\t\tif (cfg.size.width == 0 || cfg.size.height == 0 ||\n> >>> -\t\t    cfg.pixelFormat == 0 || cfg.bufferCount == 0)\n> >>> -\t\t\treturn false;\n> >>> -\t}\n> >>> -\n> >>> -\treturn true;\n> >>> -}\n> >>>\n> >>>  /**\n> >>>   * \\brief Retrieve a reference to a stream configuration\n> >>> @@ -218,6 +248,11 @@ std::size_t CameraConfiguration::size() const\n> >>>  \treturn config_.size();\n> >>>  }\n> >>>\n> >>> +/**\n> >>> + * \\var CameraConfiguration::config_\n> >>> + * \\brief The vector of stream configurations\n> >>> + */\n> >>> +\n> >>>  /**\n> >>>   * \\class Camera\n> >>>   * \\brief Camera device\n> >>> @@ -575,10 +610,9 @@ CameraConfiguration *Camera::generateConfiguration(const StreamRoles &roles)\n> >>>   * The caller specifies which streams are to be involved and their configuration\n> >>>   * by populating \\a config.\n> >>>   *\n> >>> - * The easiest way to populate the array of config is to fetch an initial\n> >>> - * configuration from the camera with generateConfiguration() and then change\n> >>> - * the parameters to fit the caller's need and once all the streams parameters\n> >>> - * are configured hand that over to configure() to actually setup the camera.\n> >>> + * The configuration is created by generateConfiguration(), and adjusted by the\n> >>> + * caller with CameraConfiguration::validate(). This method only accepts fully\n> >>> + * valid configurations and returns an error if \\a config is not valid.\n> >>>   *\n> >>>   * Exclusive access to the camera shall be ensured by a call to acquire() prior\n> >>>   * to calling this function, otherwise an -EACCES error will be returned.\n> >>> @@ -603,7 +637,7 @@ int Camera::configure(CameraConfiguration *config)\n> >>>  \tif (!stateBetween(CameraAcquired, CameraConfigured))\n> >>>  \t\treturn -EACCES;\n> >>>\n> >>> -\tif (!config->isValid()) {\n> >>> +\tif (config->validate() != CameraConfiguration::Valid) {\n> \n> Have you considered the idea of using flags for the\n> CameraConfiguration status?\n> \n> So that here we could\n> \n>         Status status = config->status();\n>         if (status == CameraConfiguration::Initialized)\n>                 status = config->validate();\n> \n>         if (status != CameraConfiguration::Valid)\n>   \t\tLOG(Camera, Error)\n>   \t\t\t<< \"Can't configure camera with invalid configuration\";\n>   \t\treturn -EINVAL;\n>         }\n> \n> and make sure we call validate() only if required ?\n\nYes I have, I wanted to cache validation status, but unfortunately it\nwouldn't be easy. We need to ensure that the CameraConfiguration isn't\nchanged after being validated, or at least to make sure we catch changes\nto the configuration after validation to reset the status to\nunvalidated. This could be done, but we would have to modify the\nStreamConfiguration structure, turn it into a class, and add accessors\nfor all fields in order to detect changes. I decided not to go that way\nto keep it simple. If you think it's worth a try, we could experiment\nwith this, but I would then do it on top of this series, as nothing goes\nin the wrong direction (in my opinion) from that point of view.\n\n> Also, could this help with the idea of initalizing parameters set to 0\n> you mentioned in a \\todo in a previous patch? If not, this could be\n> done later.\n\nPossibly, but I think we first need to define what to do with 0\nparameters :-)\n\n> >>> diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp\n> >\n> \n> [snip]\n> \n> >>> +\t\tconst unsigned int pixelFormat = cfg.pixelFormat;\n> >>> +\t\tconst Size size = cfg.size;\n> >>> +\t\tconst IPU3Stream *stream;\n> >>> +\n> >>> +\t\tif (cfg.size == sensorFormat_.size)\n> >>\n> >> (here, more precisely)\n> >>\n> >> I'm not sure about this. I always seen the IPU3 used with maximum size\n> >> (for ov5670) as 2560x1920 while the sensor resolution is actually a\n> >> bit larger 2592x1944. Same for the back ov13858 camera. I never had the\n> >> full sensor resolution working properly and I assumed it was related to\n> >> some IPU3 alignement or constraints I didn't know about. Also, the\n> >> Intel provided xml configuration file does limit the maximum\n> >> available resolution to 2560x1920, but that could be for other reasons\n> >> maybe... Have you tested capture at sensor resolution sizes?\n> >\n> > Not yet. I'm 100% confident that this code isn't good enough :-) What\n> > we're missing is documentation from Intel, and then I'll fix it.\n> \n> Furthermore, this would use the output stream only if applications\n> ask for 2592x1944, which is quite unusual as a resolution for an\n> application that's willing to display an image.\n> \n> The previous logic regarding stream assignment was based on the\n> requested stream roles at streamConfiguration() time (now\n> generateConfiguration()). Here it is only based on sizes, and it assumes\n> the capture video node 'output' size should always be used at full\n> sensor resolution, otherwise we scale, and this prevents an\n> application to ask for a stream from the 'output' node (whose IPU3\n> pipe might be set in 'video_mode' maybe, even if I'm still not sure what\n> that parameter does, maybe video stabilization?) at a resolution <\n> than the sensor's one.\n\nBut I don't think you can do that, as the output stream can't scale.\n\n> Should we cache the roles assigned to a configuration at\n> generateConfiguration() time in StreamConfiguration to be used as hint\n> here?\n\nWe are trying not to do so, because the application can modify the size\nfor the stream, which would render the role invalid. The design idea was\nto use roles only to compute a default configuration, and then to only\nbase decisions on the stream configuration. Don't forget that an\napplication can get hold of an empty CameraConfiguration and fill it\nitself, so there would be no role in that case.\n\nI'm not saying this is the perfect API, and we may need to reconsider\nthis later. We will also need to decide how to handle cropping, which is\ncompletely ignored at the moment.\n\n> >>> +\t\t\tstream = &data_->outStream_;\n> \n> [snip]\n> \n> >> If the idea of providing a Camera::validate(CameraConfiguration *) has\n> >> any ground, we should then have a private CameraConfiguration::validate()\n> >> operation accessible by the Camera class through a friend declarator,\n> >> so that pipeline handlers implementation cannot call it, restricting\n> >> that method to the application facing API only. The\n> >\n> > That's not very neat, as it would polute the application-facing headers\n> > with friends :-( I think we should try to remove them instead.\n> \n> Camera friends of CameraConfiguration does not sound so alien to me\n> and $(git grep friend include/ | wc -l) returns 13 already :)\n\nIt's not completely alien, no, but I think we should still try to\nminimise them :-) However, I don't think this should be the only point\nto be taken into account to decide whether or not to go in the direction\nyou propose.\n\n> >> CameraConfiguration subclasses' validate() implementation could then\n> >> call into the same operation that pipeline handler would use here at\n> >> generateConfiguration() time as you did here, if desirable.\n> >>\n> >> Also, again if Camera::validate(CameraConfiguration *) makes any\n> >> sense, we might want to group basic validations, if any. So we could\n> >> make Camera::validate() call into the private CameraConfiguration::validate()\n> >> operation, which performs basic operations, and calls into a protected\n> >> virtual PipelineHandler::__validate() (or whatever name is more\n> >> appropriate)\n> >>\n> >> Would this be doable?\n> >\n> > If you manage to find a good API that I like, sure :-) What you describe\n> > here make the API more cumbersome in my opinion.\n> \n> I'll repropose it here as a discussion point, but won't block your v3\n> if it has no traction, but I still think a\n> \n>         CameraConfiguration *config = camera->generateConfigurations(roles);\n>         if (!config)\n>                 return -EINVAL;\n> \n>         ret = camera->validate(config);\n>         if (ret)\n>                 return ret;\n> \n>         ret = camera->apply(config);\n>         if (ret)\n>                 return ret;\n> \n> it's slightly nicer :)\n\nI would say camera->validateConfiguration() and\ncamera->applyConfiguration() (for the reasons described above), or just\nstick with camera->configure() for the last one. I however don't\nunderstand, as noted above, what camera->validateConfiguration() would\ngive us if it's just a wrapper around config->validate().\n\n> Pros are that we could centralize validation of common config\n> parameters in Camera::validate()\n\nNow that answers the previous question :-) What common validation do you\nforesee that would apply to all pipeline handlers ? I think I would\ninstead envision configuration validation helpers that could selectively\nbe used by pipeline handlers, based on their needs (and likely depending\non the architecture of the underlying hardware).\n\n> and that camera could pass itself as a shared pointer to a new\n> CameraConfiguration::validate(shared_ptr<Camera *> camera) so that we\n> don't need to shared_from_this() in pipeline handlers, even if at the\n> moment I don't see the camera reference being used in any validate()\n> implementation.\n\nNote that I still would like to make Camera inherit from\nstd::enable_shared_from_this in order to fix (or work around, possibly)\nthe compilation issue we have with clang++ and libc++.\n\n> >>>  \treturn config;\n> >>>  }\n> >>>\n> >>> -int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n> >>> +int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *c)\n> >>>  {\n> >>> +\tIPU3CameraConfiguration *config =\n> >>> +\t\tstatic_cast<IPU3CameraConfiguration *>(c);\n> >>>  \tIPU3CameraData *data = cameraData(camera);\n> >>>  \tIPU3Stream *outStream = &data->outStream_;\n> >>>  \tIPU3Stream *vfStream = &data->vfStream_;\n> >>>  \tCIO2Device *cio2 = &data->cio2_;\n> >>>  \tImgUDevice *imgu = data->imgu_;\n> >>> -\tSize sensorSize = {};\n> >>>  \tint ret;\n> >>>\n> >>> -\toutStream->active_ = false;\n> >>> -\tvfStream->active_ = false;\n> >>> -\tfor (StreamConfiguration &cfg : *config) {\n> >>> -\t\t/*\n> >>> -\t\t * Pick a stream for the configuration entry.\n> >>> -\t\t * \\todo: This is a naive temporary implementation that will be\n> >>> -\t\t * reworked when implementing camera configuration validation.\n> >>> -\t\t */\n> >>> -\t\tIPU3Stream *stream = vfStream->active_ ? outStream : vfStream;\n> >>> -\n> >>> -\t\t/*\n> >>> -\t\t * Verify that the requested size respects the IPU3 alignment\n> >>> -\t\t * requirements (the image width shall be a multiple of 8\n> >>> -\t\t * pixels and its height a multiple of 4 pixels) and the camera\n> >>> -\t\t * maximum sizes.\n> >>> -\t\t *\n> >>> -\t\t * \\todo: Consider the BDS scaling factor requirements: \"the\n> >>> -\t\t * downscaling factor must be an integer value multiple of 1/32\"\n> >>> -\t\t */\n> >>> -\t\tif (cfg.size.width % 8 || cfg.size.height % 4) {\n> >>> -\t\t\tLOG(IPU3, Error)\n> >>> -\t\t\t\t<< \"Invalid stream size: bad alignment\";\n> >>> -\t\t\treturn -EINVAL;\n> >>> -\t\t}\n> >>> -\n> >>> -\t\tconst Size &resolution = cio2->sensor_->resolution();\n> >>> -\t\tif (cfg.size.width > resolution.width ||\n> >>> -\t\t    cfg.size.height > resolution.height) {\n> >>> -\t\t\tLOG(IPU3, Error)\n> >>> -\t\t\t\t<< \"Invalid stream size: larger than sensor resolution\";\n> >>> -\t\t\treturn -EINVAL;\n> >>> -\t\t}\n> >>> -\n> >>> -\t\t/*\n> >>> -\t\t * Collect the maximum width and height: IPU3 can downscale\n> >>> -\t\t * only.\n> >>> -\t\t */\n> >>> -\t\tif (cfg.size.width > sensorSize.width)\n> >>> -\t\t\tsensorSize.width = cfg.size.width;\n> >>> -\t\tif (cfg.size.height > sensorSize.height)\n> >>> -\t\t\tsensorSize.height = cfg.size.height;\n> >>> -\n> >>> -\t\tstream->active_ = true;\n> >>> -\t\tcfg.setStream(stream);\n> >>> -\t}\n> >>> -\n> >>>  \t/*\n> >>>  \t * \\todo: Enable links selectively based on the requested streams.\n> >>>  \t * As of now, enable all links unconditionally.\n> >>> @@ -373,6 +501,7 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n> >>>  \t * Pass the requested stream size to the CIO2 unit and get back the\n> >>>  \t * adjusted format to be propagated to the ImgU output devices.\n> >>>  \t */\n> >>> +\tconst Size &sensorSize = config->sensorFormat().size;\n> >>>  \tV4L2DeviceFormat cio2Format = {};\n> >>>  \tret = cio2->configure(sensorSize, &cio2Format);\n> >>>  \tif (ret)\n> >>> @@ -383,8 +512,22 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *config)\n> >>>  \t\treturn ret;\n> >>>\n> >>>  \t/* Apply the format to the configured streams output devices. */\n> >>> -\tfor (StreamConfiguration &cfg : *config) {\n> >>> -\t\tIPU3Stream *stream = static_cast<IPU3Stream *>(cfg.stream());\n> >>> +\toutStream->active_ = false;\n> >>> +\tvfStream->active_ = false;\n> >>> +\n> >>> +\tfor (unsigned int i = 0; i < config->size(); ++i) {\n> >>> +\t\t/*\n> >>> +\t\t * Use a const_cast<> here instead of storing a mutable stream\n> >>> +\t\t * pointer in the configuration to let the compiler catch\n> >>> +\t\t * unwanted modifications of camera data in the configuration\n> >>> +\t\t * validate() implementation.\n> >>> +\t\t */\n> >>> +\t\tIPU3Stream *stream = const_cast<IPU3Stream *>(config->streams()[i]);\n> >>> +\t\tStreamConfiguration &cfg = (*config)[i];\n> >>\n> >> nit: the fact you can get both Stream * and StreamConfiguration * by index\n> >> it's a bit ugly. What about config->streams(i) and\n> >> config->configuration(i).\n> >>\n> >> I think what's fishy lies in the fact IPu3CameraConfiguration indexes both\n> >> Stream and Configurations in two different vactors and both accessed by\n> >> index. Do you need Streams in CameraConfiguration ? Can't you store\n> >> them in CameraData, as 'activeStreams_' maybe?\n> >\n> > I can't, because configuration objects are separated from the active\n> > state of the camera. You can create as many configuration you want, toy\n> > with them in any way, and finally destroy them, without any side effect.\n> > The API guarantees that Camera::configure() will associate streams with\n> > configuration entries, but I think pipeline handlers should be able to\n> > do it ahead of time too, in validate(), if it's easier for them. I thus\n> > don't think that storing that information in CameraData is a good idea.\n> \n> I see, the streams are pushed in the CameraConfigure::streams_ while\n> walking configs_ at validate() time, and then the two are actually\n> associated at configure() time.\n> \n> What about a map<CameraConfiguration *, std::vector<Stream *>> in\n> CameraData :p ? (not sure this is better at all actually).\n\nYou would have to remove map entries when CameraConfiguration instances\nare deleted. I don't think we're going in the right direction :-)\n\n> > Furthermore, I would like to move the Stream class away from the\n> > application-facing API, so more refactoring is to come. I could however,\n> > in the meantime, replace IPU3CameraConfiguration::streams() with\n> > IPU3CameraConfiguration::stream(unsigned int index) if you think that's\n> > desirable (let's remember that that method is private to the IPU3), but\n> > a CameraConfiguration::configuration(unsigned int index) would make the\n> > API more complex for applications I think (it would need to be called\n> > CameraConfiguration::streamConfiguration(), and wouldn't let\n> > applications iterate over the stream configurations using a range-based\n> > loop).\n> >\n> \n> Indeed, the let's keep configuration[i] and configuration.streams()[i]\n> for now.\n> \n> Please bear with me a little more on this...\n\nSure :-)\n\n> >>> +\n> >>> +\t\tstream->active_ = true;\n> >>> +\t\tcfg.setStream(stream);\n> >>> +\n> >>>  \t\tret = imgu->configureOutput(stream->device_, cfg);\n> >>>  \t\tif (ret)\n> >>>  \t\t\treturn ret;\n> >>> diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> >>> index a1a4f205b4aa..42944c64189b 100644\n> >>> --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> >>> +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> >>\n> >> An impressive rework, I like where it's going even if it puts\n> >> a bit of a burden on pipeline handlers, it's for the benefit of the\n> >> application facing APIs.\n> >\n> > Thank you.\n> >\n> >>> @@ -5,6 +5,7 @@\n> >>>   * rkisp1.cpp - Pipeline handler for Rockchip ISP1\n> >>>   */\n> >>>\n> >>> +#include <algorithm>\n> >>>  #include <iomanip>\n> >>>  #include <memory>\n> >>>  #include <vector>\n> >>> @@ -45,6 +46,29 @@ public:\n> >>>  \tCameraSensor *sensor_;\n> >>>  };\n> >>>\n> >>> +class RkISP1CameraConfiguration : public CameraConfiguration\n> >>> +{\n> >>> +public:\n> >>> +\tRkISP1CameraConfiguration(Camera *camera, RkISP1CameraData *data);\n> >>> +\n> >>> +\tStatus validate() override;\n> >>> +\n> >>> +\tconst V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }\n> >>> +\n> >>> +private:\n> >>> +\tstatic constexpr unsigned int RKISP1_BUFFER_COUNT = 4;\n> >>> +\n> >>> +\t/*\n> >>> +\t * The RkISP1CameraData instance is guaranteed to be valid as long as the\n> >>> +\t * corresponding Camera instance is valid. In order to borrow a\n> >>> +\t * reference to the camera data, store a new reference to the camera.\n> >>> +\t */\n> >>> +\tstd::shared_ptr<Camera> camera_;\n> >>> +\tconst RkISP1CameraData *data_;\n> >>> +\n> >>> +\tV4L2SubdeviceFormat sensorFormat_;\n> >>> +};\n> >>> +\n> >>>  class PipelineHandlerRkISP1 : public PipelineHandler\n> >>>  {\n> >>>  public:\n> >>> @@ -68,8 +92,6 @@ public:\n> >>>  \tbool match(DeviceEnumerator *enumerator) override;\n> >>>\n> >>>  private:\n> >>> -\tstatic constexpr unsigned int RKISP1_BUFFER_COUNT = 4;\n> >>> -\n> >>>  \tRkISP1CameraData *cameraData(const Camera *camera)\n> >>>  \t{\n> >>>  \t\treturn static_cast<RkISP1CameraData *>(\n> >>> @@ -88,6 +110,95 @@ private:\n> >>>  \tCamera *activeCamera_;\n> >>>  };\n> >>>\n> >>> +RkISP1CameraConfiguration::RkISP1CameraConfiguration(Camera *camera,\n> >>> +\t\t\t\t\t\t     RkISP1CameraData *data)\n> >>> +\t: CameraConfiguration()\n> >>> +{\n> >>> +\tcamera_ = camera->shared_from_this();\n> >>> +\tdata_ = data;\n> >>> +}\n> >>> +\n> >>> +CameraConfiguration::Status RkISP1CameraConfiguration::validate()\n> >>> +{\n> >>> +\tstatic const std::array<unsigned int, 8> formats{\n> >>> +\t\tV4L2_PIX_FMT_YUYV,\n> >>> +\t\tV4L2_PIX_FMT_YVYU,\n> >>> +\t\tV4L2_PIX_FMT_VYUY,\n> >>> +\t\tV4L2_PIX_FMT_NV16,\n> >>> +\t\tV4L2_PIX_FMT_NV61,\n> >>> +\t\tV4L2_PIX_FMT_NV21,\n> >>> +\t\tV4L2_PIX_FMT_NV12,\n> >>> +\t\tV4L2_PIX_FMT_GREY,\n> >>> +\t};\n> >>> +\n> >>> +\tconst CameraSensor *sensor = data_->sensor_;\n> >>> +\tStatus status = Valid;\n> >>> +\n> >>> +\tif (config_.empty())\n> >>> +\t\treturn Invalid;\n> >>> +\n> >>> +\t/* Cap the number of entries to the available streams. */\n> >>> +\tif (config_.size() > 1) {\n> >>> +\t\tconfig_.resize(1);\n> >>> +\t\tstatus = Adjusted;\n> >>> +\t}\n> >>> +\n> >>> +\tStreamConfiguration &cfg = config_[0];\n> >>> +\n> >>> +\t/* Adjust the pixel format. */\n> >>> +\tif (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==\n> >>> +\t    formats.end()) {\n> >>> +\t\tLOG(RkISP1, Debug) << \"Adjusting format to NV12\";\n> >>> +\t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> >>> +\t\tstatus = Adjusted;\n> >>> +\t}\n> >>> +\n> >>> +\t/* Select the sensor format. */\n> >>> +\tsensorFormat_ = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR12_1X12,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG12_1X12,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG12_1X12,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB12_1X12,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SBGGR10_1X10,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG10_1X10,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG10_1X10,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB10_1X10,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SBGGR8_1X8,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SGBRG8_1X8,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SGRBG8_1X8,\n> >>> +\t\t\t\t\t    MEDIA_BUS_FMT_SRGGB8_1X8 },\n> >>> +\t\t\t\t\t  cfg.size);\n> >>> +\tif (!sensorFormat_.size.width || !sensorFormat_.size.height)\n> >>> +\t\tsensorFormat_.size = sensor->resolution();\n> >>> +\n> >>> +\t/*\n> >>> +\t * Provide a suitable default that matches the sensor aspect\n> >>> +\t * ratio and clamp the size to the hardware bounds.\n> >>> +\t *\n> >>> +\t * \\todo: Check the hardware alignment constraints.\n> >>> +\t */\n> >>> +\tconst Size size = cfg.size;\n> >>> +\n> >>> +\tif (!cfg.size.width || !cfg.size.height) {\n> >>> +\t\tcfg.size.width = 1280;\n> >>> +\t\tcfg.size.height = 1280 * sensorFormat_.size.height\n> >>> +\t\t\t\t/ sensorFormat_.size.width;\n> >>> +\t}\n> >>> +\n> >>> +\tcfg.size.width = std::max(32U, std::min(4416U, cfg.size.width));\n> >>> +\tcfg.size.height = std::max(16U, std::min(3312U, cfg.size.height));\n> >>> +\n> >>> +\tif (cfg.size != size) {\n> >>> +\t\tLOG(RkISP1, Debug)\n> >>> +\t\t\t<< \"Adjusting size from \" << size.toString()\n> >>> +\t\t\t<< \" to \" << cfg.size.toString();\n> >>> +\t\tstatus = Adjusted;\n> >>> +\t}\n> >>> +\n> >>> +\tcfg.bufferCount = RKISP1_BUFFER_COUNT;\n> >>> +\n> >>> +\treturn status;\n> >>> +}\n> >>> +\n> >>>  PipelineHandlerRkISP1::PipelineHandlerRkISP1(CameraManager *manager)\n> >>>  \t: PipelineHandler(manager), dphy_(nullptr), isp_(nullptr),\n> >>>  \t  video_(nullptr)\n> >>> @@ -109,37 +220,31 @@ CameraConfiguration *PipelineHandlerRkISP1::generateConfiguration(Camera *camera\n> >>>  \tconst StreamRoles &roles)\n> >>>  {\n> >>>  \tRkISP1CameraData *data = cameraData(camera);\n> >>> -\tCameraConfiguration *config = new CameraConfiguration();\n> >>> +\tCameraConfiguration *config = new RkISP1CameraConfiguration(camera, data);\n> >>>\n> >>>  \tif (!roles.empty()) {\n> >>>  \t\tStreamConfiguration cfg{};\n> >>>\n> >>>  \t\tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n> >>>  \t\tcfg.size = data->sensor_->resolution();\n> >>> -\t\tcfg.bufferCount = RKISP1_BUFFER_COUNT;\n> >>>\n> >>>  \t\tconfig->addConfiguration(cfg);\n> >>>  \t}\n> >>>\n> >>> +\tconfig->validate();\n> >>> +\n> >>>  \treturn config;\n> >>>  }\n> >>>\n> >>> -int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *config)\n> >>> +int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)\n> >>>  {\n> >>> +\tRkISP1CameraConfiguration *config =\n> >>> +\t\tstatic_cast<RkISP1CameraConfiguration *>(c);\n> >>>  \tRkISP1CameraData *data = cameraData(camera);\n> >>>  \tStreamConfiguration &cfg = config->at(0);\n> >>>  \tCameraSensor *sensor = data->sensor_;\n> >>>  \tint ret;\n> >>>\n> >>> -\t/* Verify the configuration. */\n> >>> -\tconst Size &resolution = sensor->resolution();\n> >>> -\tif (cfg.size.width > resolution.width ||\n> >>> -\t    cfg.size.height > resolution.height) {\n> >>> -\t\tLOG(RkISP1, Error)\n> >>> -\t\t\t<< \"Invalid stream size: larger than sensor resolution\";\n> >>> -\t\treturn -EINVAL;\n> >>> -\t}\n> >>> -\n> >>>  \t/*\n> >>>  \t * Configure the sensor links: enable the link corresponding to this\n> >>>  \t * camera and disable all the other sensor links.\n> >>> @@ -167,21 +272,7 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *config\n> >>>  \t * Configure the format on the sensor output and propagate it through\n> >>>  \t * the pipeline.\n> >>>  \t */\n> >>> -\tV4L2SubdeviceFormat format;\n> >>> -\tformat = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR12_1X12,\n> >>> -\t\t\t\t     MEDIA_BUS_FMT_SGBRG12_1X12,\n> >>> -\t\t\t\t     MEDIA_BUS_FMT_SGRBG12_1X12,\n> >>> -\t\t\t\t     MEDIA_BUS_FMT_SRGGB12_1X12,\n> >>> -\t\t\t\t     MEDIA_BUS_FMT_SBGGR10_1X10,\n> >>> -\t\t\t\t     MEDIA_BUS_FMT_SGBRG10_1X10,\n> >>> -\t\t\t\t     MEDIA_BUS_FMT_SGRBG10_1X10,\n> >>> -\t\t\t\t     MEDIA_BUS_FMT_SRGGB10_1X10,\n> >>> -\t\t\t\t     MEDIA_BUS_FMT_SBGGR8_1X8,\n> >>> -\t\t\t\t     MEDIA_BUS_FMT_SGBRG8_1X8,\n> >>> -\t\t\t\t     MEDIA_BUS_FMT_SGRBG8_1X8,\n> >>> -\t\t\t\t     MEDIA_BUS_FMT_SRGGB8_1X8 },\n> >>> -\t\t\t\t   cfg.size);\n> >>> -\n> >>> +\tV4L2SubdeviceFormat format = config->sensorFormat();\n> >>>  \tLOG(RkISP1, Debug) << \"Configuring sensor with \" << format.toString();\n> >>>\n> >>>  \tret = sensor->setFormat(&format);\n> >>> diff --git a/src/libcamera/pipeline/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo.cpp\n> >>> index 8254e1fdac1e..f7ffeb439cf3 100644\n> >>> --- a/src/libcamera/pipeline/uvcvideo.cpp\n> >>> +++ b/src/libcamera/pipeline/uvcvideo.cpp\n> >>> @@ -39,6 +39,14 @@ public:\n> >>>  \tStream stream_;\n> >>>  };\n> >>>\n> >>> +class UVCCameraConfiguration : public CameraConfiguration\n> >>> +{\n> >>> +public:\n> >>> +\tUVCCameraConfiguration();\n> >>> +\n> >>> +\tStatus validate() override;\n> >>> +};\n> >>> +\n> >>>  class PipelineHandlerUVC : public PipelineHandler\n> >>>  {\n> >>>  public:\n> >>> @@ -68,6 +76,45 @@ private:\n> >>>  \t}\n> >>>  };\n> >>>\n> >>> +UVCCameraConfiguration::UVCCameraConfiguration()\n> >>> +\t: CameraConfiguration()\n> >>> +{\n> >>> +}\n> >>> +\n> >>> +CameraConfiguration::Status UVCCameraConfiguration::validate()\n> >>> +{\n> >>> +\tStatus status = Valid;\n> >>> +\n> >>> +\tif (config_.empty())\n> >>> +\t\treturn Invalid;\n> >>> +\n> >>> +\t/* Cap the number of entries to the available streams. */\n> >>> +\tif (config_.size() > 1) {\n> >>> +\t\tconfig_.resize(1);\n> >>> +\t\tstatus = Adjusted;\n> >>> +\t}\n> >>> +\n> >>> +\tStreamConfiguration &cfg = config_[0];\n> >>> +\n> >>> +\t/* \\todo: Validate the configuration against the device capabilities. */\n> >>> +\tconst unsigned int pixelFormat = cfg.pixelFormat;\n> >>> +\tconst Size size = cfg.size;\n> >>> +\n> >>> +\tcfg.pixelFormat = V4L2_PIX_FMT_YUYV;\n> >>> +\tcfg.size = { 640, 480 };\n> >>> +\n> >>> +\tif (cfg.pixelFormat != pixelFormat || cfg.size != size) {\n> >>> +\t\tLOG(UVC, Debug)\n> >>> +\t\t\t<< \"Adjusting configuration from \" << cfg.toString()\n> >>> +\t\t\t<< \" to \" << cfg.size.toString() << \"-YUYV\";\n> >>> +\t\tstatus = Adjusted;\n> >>> +\t}\n> >>> +\n> >>> +\tcfg.bufferCount = 4;\n> >>> +\n> >>> +\treturn status;\n> >>> +}\n> >>> +\n> >>>  PipelineHandlerUVC::PipelineHandlerUVC(CameraManager *manager)\n> >>>  \t: PipelineHandler(manager)\n> >>>  {\n> >>> @@ -76,10 +123,10 @@ PipelineHandlerUVC::PipelineHandlerUVC(CameraManager *manager)\n> >>>  CameraConfiguration *PipelineHandlerUVC::generateConfiguration(Camera *camera,\n> >>>  \tconst StreamRoles &roles)\n> >>>  {\n> >>> -\tCameraConfiguration *config = new CameraConfiguration();\n> >>> +\tCameraConfiguration *config = new UVCCameraConfiguration();\n> >>>\n> >>>  \tif (!roles.empty()) {\n> >>> -\t\tStreamConfiguration cfg;\n> >>> +\t\tStreamConfiguration cfg{};\n> >>>\n> >>>  \t\tcfg.pixelFormat = V4L2_PIX_FMT_YUYV;\n> >>>  \t\tcfg.size = { 640, 480 };\n> >>> @@ -88,6 +135,8 @@ CameraConfiguration *PipelineHandlerUVC::generateConfiguration(Camera *camera,\n> >>>  \t\tconfig->addConfiguration(cfg);\n> >>>  \t}\n> >>>\n> >>> +\tconfig->validate();\n> >>> +\n> >>>  \treturn config;\n> >>>  }\n> >>>\n> >>> diff --git a/src/libcamera/pipeline/vimc.cpp b/src/libcamera/pipeline/vimc.cpp\n> >>> index 2bf85d0a0b32..2a61d2893e3a 100644\n> >>> --- a/src/libcamera/pipeline/vimc.cpp\n> >>> +++ b/src/libcamera/pipeline/vimc.cpp\n> >>> @@ -5,6 +5,8 @@\n> >>>   * vimc.cpp - Pipeline handler for the vimc device\n> >>>   */\n> >>>\n> >>> +#include <algorithm>\n> >>> +\n> >>>  #include <libcamera/camera.h>\n> >>>  #include <libcamera/request.h>\n> >>>  #include <libcamera/stream.h>\n> >>> @@ -39,6 +41,14 @@ public:\n> >>>  \tStream stream_;\n> >>>  };\n> >>>\n> >>> +class VimcCameraConfiguration : public CameraConfiguration\n> >>> +{\n> >>> +public:\n> >>> +\tVimcCameraConfiguration();\n> >>> +\n> >>> +\tStatus validate() override;\n> >>> +};\n> >>> +\n> >>>  class PipelineHandlerVimc : public PipelineHandler\n> >>>  {\n> >>>  public:\n> >>> @@ -68,6 +78,57 @@ private:\n> >>>  \t}\n> >>>  };\n> >>>\n> >>> +VimcCameraConfiguration::VimcCameraConfiguration()\n> >>> +\t: CameraConfiguration()\n> >>> +{\n> >>> +}\n> >>> +\n> >>> +CameraConfiguration::Status VimcCameraConfiguration::validate()\n> >>> +{\n> >>> +\tstatic const std::array<unsigned int, 3> formats{\n> >>> +\t\tV4L2_PIX_FMT_BGR24,\n> >>> +\t\tV4L2_PIX_FMT_RGB24,\n> >>> +\t\tV4L2_PIX_FMT_ARGB32,\n> >>> +\t};\n> >>> +\n> >>> +\tStatus status = Valid;\n> >>> +\n> >>> +\tif (config_.empty())\n> >>> +\t\treturn Invalid;\n> >>> +\n> >>> +\t/* Cap the number of entries to the available streams. */\n> >>> +\tif (config_.size() > 1) {\n> >>> +\t\tconfig_.resize(1);\n> >>> +\t\tstatus = Adjusted;\n> >>> +\t}\n> >>> +\n> >>> +\tStreamConfiguration &cfg = config_[0];\n> >>> +\n> >>> +\t/* Adjust the pixel format. */\n> >>> +\tif (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==\n> >>> +\t    formats.end()) {\n> >>> +\t\tLOG(VIMC, Debug) << \"Adjusting format to RGB24\";\n> >>> +\t\tcfg.pixelFormat = V4L2_PIX_FMT_RGB24;\n> >>> +\t\tstatus = Adjusted;\n> >>> +\t}\n> >>> +\n> >>> +\t/* Clamp the size based on the device limits. */\n> >>> +\tconst Size size = cfg.size;\n> >>> +\n> >>> +\tcfg.size.width = std::max(16U, std::min(4096U, cfg.size.width));\n> >>> +\tcfg.size.height = std::max(16U, std::min(2160U, cfg.size.height));\n> >>> +\n> >>> +\tif (cfg.size != size) {\n> >>> +\t\tLOG(VIMC, Debug)\n> >>> +\t\t\t<< \"Adjusting size to \" << cfg.size.toString();\n> >>> +\t\tstatus = Adjusted;\n> >>> +\t}\n> >>> +\n> >>> +\tcfg.bufferCount = 4;\n> >>> +\n> >>> +\treturn status;\n> >>> +}\n> >>> +\n> >>>  PipelineHandlerVimc::PipelineHandlerVimc(CameraManager *manager)\n> >>>  \t: PipelineHandler(manager)\n> >>>  {\n> >>> @@ -76,10 +137,10 @@ PipelineHandlerVimc::PipelineHandlerVimc(CameraManager *manager)\n> >>>  CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera,\n> >>>  \tconst StreamRoles &roles)\n> >>>  {\n> >>> -\tCameraConfiguration *config = new CameraConfiguration();\n> >>> +\tCameraConfiguration *config = new VimcCameraConfiguration();\n> >>>\n> >>>  \tif (!roles.empty()) {\n> >>> -\t\tStreamConfiguration cfg;\n> >>> +\t\tStreamConfiguration cfg{};\n> >>>\n> >>>  \t\tcfg.pixelFormat = V4L2_PIX_FMT_RGB24;\n> >>>  \t\tcfg.size = { 640, 480 };\n> >>> @@ -88,6 +149,8 @@ CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera,\n> >>>  \t\tconfig->addConfiguration(cfg);\n> >>>  \t}\n> >>>\n> >>> +\tconfig->validate();\n> >>> +\n> >>>  \treturn config;\n> >>>  }\n> >>>\n> >>> diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp\n> >>> index de46e98880a2..dd56907d817e 100644\n> >>> --- a/src/libcamera/pipeline_handler.cpp\n> >>> +++ b/src/libcamera/pipeline_handler.cpp\n> >>> @@ -248,17 +248,14 @@ void PipelineHandler::unlock()\n> >>>   * is the Camera class which will receive configuration to apply from the\n> >>>   * application.\n> >>>   *\n> >>> - * Each pipeline handler implementation is responsible for validating\n> >>> - * that the configuration requested in \\a config can be achieved\n> >>> - * exactly. Any difference in pixel format, frame size or any other\n> >>> - * parameter shall result in the -EINVAL error being returned, and no\n> >>> - * change in configuration being applied to the pipeline. If\n> >>> - * configuration of a subset of the streams can't be satisfied, the\n> >>> - * whole configuration is considered invalid.\n> >>> + * The configuration is guaranteed to have been validated with\n> >>> + * CameraConfiguration::valid(). The pipeline handler implementation shall not\n> >>> + * perform further validation and may rely on any custom field stored in its\n> >>> + * custom CameraConfiguration derived class.\n> >>>   *\n> >>> - * Once the configuration is validated and the camera configured, the pipeline\n> >>> - * handler shall associate a Stream instance to each StreamConfiguration entry\n> >>> - * in the CameraConfiguration with the StreamConfiguration::setStream() method.\n> >>> + * When configuring the camera the pipeline handler shall associate a Stream\n> >>> + * instance to each StreamConfiguration entry in the CameraConfiguration using\n> >>> + * the StreamConfiguration::setStream() method.\n> >>>   *\n> >>>   * \\return 0 on success or a negative error code otherwise\n> >>>   */\n> >>> diff --git a/test/camera/capture.cpp b/test/camera/capture.cpp\n> >>> index 137aa649a505..f122f79bb1ec 100644\n> >>> --- a/test/camera/capture.cpp\n> >>> +++ b/test/camera/capture.cpp\n> >>> @@ -45,7 +45,7 @@ protected:\n> >>>  \t\tCameraTest::init();\n> >>>\n> >>>  \t\tconfig_ = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> >>> -\t\tif (!config_) {\n> >>> +\t\tif (!config_ || config_->size() != 1) {\n> >>>  \t\t\tcout << \"Failed to generate default configuration\" << endl;\n> >>>  \t\t\tCameraTest::cleanup();\n> >>>  \t\t\treturn TestFail;\n> >>> @@ -58,11 +58,6 @@ protected:\n> >>>  \t{\n> >>>  \t\tStreamConfiguration &cfg = config_->at(0);\n> >>>\n> >>> -\t\tif (!config_->isValid()) {\n> >>> -\t\t\tcout << \"Failed to read default configuration\" << endl;\n> >>> -\t\t\treturn TestFail;\n> >>> -\t\t}\n> >>> -\n> >>>  \t\tif (camera_->acquire()) {\n> >>>  \t\t\tcout << \"Failed to acquire the camera\" << endl;\n> >>>  \t\t\treturn TestFail;\n> >>> diff --git a/test/camera/configuration_default.cpp b/test/camera/configuration_default.cpp\n> >>> index d5cefc1127c9..81055da1d513 100644\n> >>> --- a/test/camera/configuration_default.cpp\n> >>> +++ b/test/camera/configuration_default.cpp\n> >>> @@ -22,7 +22,7 @@ protected:\n> >>>\n> >>>  \t\t/* Test asking for configuration for a video stream. */\n> >>>  \t\tconfig = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> >>> -\t\tif (!config || !config->isValid()) {\n> >>> +\t\tif (!config || config->size() != 1) {\n> >>>  \t\t\tcout << \"Default configuration invalid\" << endl;\n> >>>  \t\t\tdelete config;\n> >>>  \t\t\treturn TestFail;\n> >>> @@ -35,7 +35,7 @@ protected:\n> >>>  \t\t * stream roles returns an empty camera configuration.\n> >>>  \t\t */\n> >>>  \t\tconfig = camera_->generateConfiguration({});\n> >>> -\t\tif (!config || config->isValid()) {\n> >>> +\t\tif (!config || config->size() != 0) {\n> >>>  \t\t\tcout << \"Failed to retrieve configuration for empty roles list\"\n> >>>  \t\t\t     << endl;\n> >>>  \t\t\tdelete config;\n> >>> diff --git a/test/camera/configuration_set.cpp b/test/camera/configuration_set.cpp\n> >>> index 23c611a93355..a4e2da16a88b 100644\n> >>> --- a/test/camera/configuration_set.cpp\n> >>> +++ b/test/camera/configuration_set.cpp\n> >>> @@ -21,7 +21,7 @@ protected:\n> >>>  \t\tCameraTest::init();\n> >>>\n> >>>  \t\tconfig_ = camera_->generateConfiguration({ StreamRole::VideoRecording });\n> >>> -\t\tif (!config_) {\n> >>> +\t\tif (!config_ || config_->size() != 1) {\n> >>>  \t\t\tcout << \"Failed to generate default configuration\" << endl;\n> >>>  \t\t\tCameraTest::cleanup();\n> >>>  \t\t\treturn TestFail;\n> >>> @@ -34,11 +34,6 @@ protected:\n> >>>  \t{\n> >>>  \t\tStreamConfiguration &cfg = config_->at(0);\n> >>>\n> >>> -\t\tif (!config_->isValid()) {\n> >>> -\t\t\tcout << \"Failed to read default configuration\" << endl;\n> >>> -\t\t\treturn TestFail;\n> >>> -\t\t}\n> >>> -\n> >>>  \t\tif (camera_->acquire()) {\n> >>>  \t\t\tcout << \"Failed to acquire the camera\" << endl;\n> >>>  \t\t\treturn TestFail;","headers":{"Return-Path":"<laurent.pinchart@ideasonboard.com>","Received":["from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id A54B460C02\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 22 May 2019 18:35:03 +0200 (CEST)","from pendragon.ideasonboard.com\n\t(dfj612yhrgyx302h3jwwy-3.rev.dnainternet.fi\n\t[IPv6:2001:14ba:21f5:5b00:ce28:277f:58d7:3ca4])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id F27FC443;\n\tWed, 22 May 2019 18:35:02 +0200 (CEST)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1558542903;\n\tbh=+mW0VwT9rfw+eDJFHSWIx9WsHzZLgUEfC+3LwyesHLs=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=DC3N6X/Kt9+/lqQxP22uW39Fa3vkTnEpXIb01dPfm28nJZPUUVzBIQY8M+Ti8UpRn\n\tZXKw7HwZ33D29p9x2U9szbiQ/ppUFCk7TdPcqPdoEDz73bt7yolVLcV/bHePPl4en9\n\twFdHycrmaMoOtUBugV+Xbc2sfRjdVlB1avtit2zY=","Date":"Wed, 22 May 2019 19:34:46 +0300","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Jacopo Mondi <jacopo@jmondi.org>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20190522163446.GD8868@pendragon.ideasonboard.com>","References":"<20190519150047.12444-1-laurent.pinchart@ideasonboard.com>\n\t<20190519150047.12444-7-laurent.pinchart@ideasonboard.com>\n\t<20190520220506.5yubhl2rqmk3ajdr@uno.localdomain>\n\t<20190521124955.GC5674@pendragon.ideasonboard.com>\n\t<20190521214329.lbtggxmqzc5mi2rn@uno.localdomain>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20190521214329.lbtggxmqzc5mi2rn@uno.localdomain>","User-Agent":"Mutt/1.10.1 (2018-07-13)","Subject":"Re: [libcamera-devel] [PATCH v2 6/6] libcamera: camera: Add a\n\tvalidation API to the CameraConfiguration class","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.23","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Wed, 22 May 2019 16:35:03 -0000"}}]