[{"id":1655,"web_url":"https://patchwork.libcamera.org/comment/1655/","msgid":"<20190521215340.dlskwnr2uzwkiiwx@uno.localdomain>","date":"2019-05-21T21:53:40","subject":"Re: [libcamera-devel] [PATCH v3 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 10:27:40PM +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 semantic of the camera configuration. The\n> 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> Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>\n\nWith comments on IPU3 roles assignement clarified and the API\ndiscussion finalized in one way or another:\n\nReviewed-by: Jacopo Mondi <jacopo@jmondi.org>\n\nThanks\n   j\n\n> ---\n> Changes since v2:\n>\n> - Pass StreamConfiguration & to IPU3CameraConfiguration::adjustStream()\n> - Refactor generateConfiguration() to save one indentation level in\n>   pipeline handlers\n> - Include <array> where needed\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     | 253 ++++++++++++++++++-----\n>  src/libcamera/pipeline/rkisp1/rkisp1.cpp | 150 +++++++++++---\n>  src/libcamera/pipeline/uvcvideo.cpp      |  51 ++++-\n>  src/libcamera/pipeline/vimc.cpp          |  66 +++++-\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, 514 insertions(+), 140 deletions(-)\n>\n> diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> index a3a7289a7aa7..fb2f7ba3423c 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 535c2420893e..5ecd7e0e38d7 100644\n> --- a/src/cam/main.cpp\n> +++ b/src/cam/main.cpp\n> @@ -116,7 +116,7 @@ static std::unique_ptr<CameraConfiguration> prepareCameraConfig()\n>  \t}\n>\n>  \tstd::unique_ptr<CameraConfiguration> 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\treturn nullptr;\n> diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> index 0e5fd7f57271..b25a80bce1a2 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> @@ -577,10 +612,9 @@ std::unique_ptr<CameraConfiguration> Camera::generateConfiguration(const StreamR\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> @@ -605,7 +639,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..05005c42106b 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(StreamConfiguration &cfg, 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,151 @@ 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(StreamConfiguration &cfg, bool scale)\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(config_[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 +381,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 +468,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 +499,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 +510,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 8b279e76c0b0..9b3eea2f6dd3 100644\n> --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> @@ -5,6 +5,8 @@\n>   * rkisp1.cpp - Pipeline handler for Rockchip ISP1\n>   */\n>\n> +#include <algorithm>\n> +#include <array>\n>  #include <iomanip>\n>  #include <memory>\n>  #include <vector>\n> @@ -45,6 +47,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 +93,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 +111,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,7 +221,7 @@ 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\treturn config;\n> @@ -117,29 +229,23 @@ CameraConfiguration *PipelineHandlerRkISP1::generateConfiguration(Camera *camera\n>  \tStreamConfiguration cfg{};\n>  \tcfg.pixelFormat = V4L2_PIX_FMT_NV12;\n>  \tcfg.size = data->sensor_->resolution();\n> -\tcfg.bufferCount = RKISP1_BUFFER_COUNT;\n>\n>  \tconfig->addConfiguration(cfg);\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 +273,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 4ffe52aa70d7..45260f34c8f5 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,7 +123,7 @@ 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\treturn config;\n> @@ -88,6 +135,8 @@ CameraConfiguration *PipelineHandlerUVC::generateConfiguration(Camera *camera,\n>\n>  \tconfig->addConfiguration(cfg);\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 ed5b1ad4502a..0e4eede351d8 100644\n> --- a/src/libcamera/pipeline/vimc.cpp\n> +++ b/src/libcamera/pipeline/vimc.cpp\n> @@ -5,6 +5,9 @@\n>   * vimc.cpp - Pipeline handler for the vimc device\n>   */\n>\n> +#include <algorithm>\n> +#include <array>\n> +\n>  #include <libcamera/camera.h>\n>  #include <libcamera/request.h>\n>  #include <libcamera/stream.h>\n> @@ -39,6 +42,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 +79,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,7 +138,7 @@ 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\treturn config;\n> @@ -88,6 +150,8 @@ CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera,\n>\n>  \tconfig->addConfiguration(cfg);\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 bb7d380cdc1a..c0835c250c65 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 8a767d2321e0..ce2ec5d02e7b 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\treturn TestFail;\n>  \t\t}\n> @@ -32,7 +32,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\treturn TestFail;\n> diff --git a/test/camera/configuration_set.cpp b/test/camera/configuration_set.cpp\n> index ece987c7752a..9f10f795a5d8 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 relay3-d.mail.gandi.net (relay3-d.mail.gandi.net\n\t[217.70.183.195])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id CB8B860C40\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 21 May 2019 23:52:34 +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 relay3-d.mail.gandi.net (Postfix) with ESMTPSA id 1694160008;\n\tTue, 21 May 2019 21:52:33 +0000 (UTC)"],"X-Originating-IP":"2.224.242.101","Date":"Tue, 21 May 2019 23:53:40 +0200","From":"Jacopo Mondi <jacopo@jmondi.org>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20190521215340.dlskwnr2uzwkiiwx@uno.localdomain>","References":"<20190521192740.28112-1-laurent.pinchart@ideasonboard.com>\n\t<20190521192740.28112-7-laurent.pinchart@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"multipart/signed; micalg=pgp-sha256;\n\tprotocol=\"application/pgp-signature\"; boundary=\"jdnspiylh72j3ixv\"","Content-Disposition":"inline","In-Reply-To":"<20190521192740.28112-7-laurent.pinchart@ideasonboard.com>","User-Agent":"NeoMutt/20180716","Subject":"Re: [libcamera-devel] [PATCH v3 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:52:35 -0000"}}]