[{"id":25251,"web_url":"https://patchwork.libcamera.org/comment/25251/","msgid":"<YzucGM4lebLeIaEo@pendragon.ideasonboard.com>","date":"2022-10-04T02:36:08","subject":"Re: [libcamera-devel] [PATCH 14/14] libcamera: pipeline: rkisp1:\n\tAdd converter support","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Xavier,\n\nThank you for the patch.\n\nOn Thu, Sep 08, 2022 at 08:48:50PM +0200, Xavier Roumegue via libcamera-devel wrote:\n> This adds a converter, if any present in the system, on each streams\n> (self and main paths). In case a configuration file is successfully\n> loaded, the converter use is getting compulsory, such as a dewarping map\n> is unconditionally applied. Otherwise, the converter is only used if the\n> stream configuration requires it.\n\nBefore doing a full review of this, I'm wondering what use case(s) you\nenvision for scaling with the dewarper. The ISP has a scaler at the\noutput, are there cases where you think scaling in the dewarper would\nhave an advantage ?\n\n> Signed-off-by: Xavier Roumegue <xavier.roumegue@oss.nxp.com>\n> ---\n>  src/libcamera/pipeline/rkisp1/rkisp1.cpp      | 126 +++---\n>  src/libcamera/pipeline/rkisp1/rkisp1_path.cpp | 374 +++++++++++++++++-\n>  src/libcamera/pipeline/rkisp1/rkisp1_path.h   |  54 ++-\n>  3 files changed, 485 insertions(+), 69 deletions(-)\n> \n> diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> index c1522ca6..6bdf5a3a 100644\n> --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> @@ -1,6 +1,7 @@\n>  /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>  /*\n>   * Copyright (C) 2019, Google Inc.\n> + * Copyright 2022 NXP\n>   *\n>   * rkisp1.cpp - Pipeline handler for Rockchip ISP1\n>   */\n> @@ -194,6 +195,7 @@ private:\n>  \tCamera *activeCamera_;\n>  \n>  \tconst MediaPad *ispSink_;\n> +\tMediaDevice *converter_;\n>  };\n>  \n>  RkISP1Frames::RkISP1Frames(PipelineHandler *pipe)\n> @@ -449,57 +451,44 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()\n>  \n>  \tbool mainPathAvailable = true;\n>  \tbool selfPathAvailable = data_->selfPath_;\n> +\tconst std::array<Status, 2> cameraStatus = { Valid, Adjusted };\n> +\n>  \tfor (unsigned int index : order) {\n>  \t\tStreamConfiguration &cfg = config_[index];\n>  \n> -\t\t/* Try to match stream without adjusting configuration. */\n> -\t\tif (mainPathAvailable) {\n> -\t\t\tStreamConfiguration tryCfg = cfg;\n> -\t\t\tif (data_->mainPath_->validate(&tryCfg) == Valid) {\n> -\t\t\t\tmainPathAvailable = false;\n> -\t\t\t\tcfg = tryCfg;\n> -\t\t\t\tcfg.setStream(const_cast<Stream *>(&data_->mainPathStream_));\n> -\t\t\t\tcontinue;\n> -\t\t\t}\n> -\t\t}\n> +\t\tfor (auto &_status : cameraStatus) {\n> +\t\t\tStatus pipeStatus;\n>  \n> -\t\tif (selfPathAvailable) {\n> -\t\t\tStreamConfiguration tryCfg = cfg;\n> -\t\t\tif (data_->selfPath_->validate(&tryCfg) == Valid) {\n> -\t\t\t\tselfPathAvailable = false;\n> -\t\t\t\tcfg = tryCfg;\n> -\t\t\t\tcfg.setStream(const_cast<Stream *>(&data_->selfPathStream_));\n> -\t\t\t\tcontinue;\n> -\t\t\t}\n> -\t\t}\n> +\t\t\tif (mainPathAvailable) {\n> +\t\t\t\tStreamConfiguration tryCfg = cfg;\n> +\t\t\t\tpipeStatus = data_->mainPath_->validate(&tryCfg);\n>  \n> -\t\t/* Try to match stream allowing adjusting configuration. */\n> -\t\tif (mainPathAvailable) {\n> -\t\t\tStreamConfiguration tryCfg = cfg;\n> -\t\t\tif (data_->mainPath_->validate(&tryCfg) == Adjusted) {\n> -\t\t\t\tmainPathAvailable = false;\n> -\t\t\t\tcfg = tryCfg;\n> -\t\t\t\tcfg.setStream(const_cast<Stream *>(&data_->mainPathStream_));\n> -\t\t\t\tstatus = Adjusted;\n> -\t\t\t\tcontinue;\n> +\t\t\t\tif (pipeStatus == _status) {\n> +\t\t\t\t\tmainPathAvailable = false;\n> +\t\t\t\t\tcfg = tryCfg;\n> +\t\t\t\t\tcfg.setStream(const_cast<Stream *>(&data_->mainPathStream_));\n> +\t\t\t\t\tstatus = _status;\n> +\t\t\t\t\tbreak;\n> +\t\t\t\t}\n>  \t\t\t}\n> -\t\t}\n>  \n> -\t\tif (selfPathAvailable) {\n> -\t\t\tStreamConfiguration tryCfg = cfg;\n> -\t\t\tif (data_->selfPath_->validate(&tryCfg) == Adjusted) {\n> -\t\t\t\tselfPathAvailable = false;\n> -\t\t\t\tcfg = tryCfg;\n> -\t\t\t\tcfg.setStream(const_cast<Stream *>(&data_->selfPathStream_));\n> -\t\t\t\tstatus = Adjusted;\n> -\t\t\t\tcontinue;\n> +\t\t\tif (selfPathAvailable) {\n> +\t\t\t\tStreamConfiguration tryCfg = cfg;\n> +\t\t\t\tpipeStatus = data_->selfPath_->validate(&tryCfg);\n> +\n> +\t\t\t\tif (pipeStatus == _status) {\n> +\t\t\t\t\tselfPathAvailable = false;\n> +\t\t\t\t\tcfg = tryCfg;\n> +\t\t\t\t\tcfg.setStream(const_cast<Stream *>(&data_->selfPathStream_));\n> +\t\t\t\t\tstatus = _status;\n> +\t\t\t\t\tbreak;\n> +\t\t\t\t}\n>  \t\t\t}\n> +\t\t\t/* All paths rejected configuration. */\n> +\t\t\tLOG(RkISP1, Debug) << \"Camera configuration not supported \"\n> +\t\t\t\t\t   << cfg.toString();\n> +\t\t\treturn Invalid;\n>  \t\t}\n> -\n> -\t\t/* All paths rejected configuraiton. */\n> -\t\tLOG(RkISP1, Debug) << \"Camera configuration not supported \"\n> -\t\t\t\t   << cfg.toString();\n> -\t\treturn Invalid;\n>  \t}\n>  \n>  \t/* Select the sensor format. */\n> @@ -680,15 +669,21 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)\n>  \n>  \tstd::map<unsigned int, IPAStream> streamConfig;\n>  \n> -\tfor (const StreamConfiguration &cfg : *config) {\n> +\tfor (StreamConfiguration &cfg : *config) {\n> +\t\tsize_t idx = 0;\n> +\t\tStreamConfiguration internalCfg;\n>  \t\tif (cfg.stream() == &data->mainPathStream_) {\n> +\t\t\tidx = 0;\n>  \t\t\tret = mainPath_.configure(cfg, format);\n> -\t\t\tstreamConfig[0] = IPAStream(cfg.pixelFormat,\n> -\t\t\t\t\t\t    cfg.size);\n> +\t\t\tinternalCfg = mainPath_.internalStream();\n> +\t\t\tstreamConfig[idx] = IPAStream(internalCfg.pixelFormat,\n> +\t\t\t\t\t\t      internalCfg.size);\n>  \t\t} else if (hasSelfPath_) {\n> +\t\t\tidx = 1;\n>  \t\t\tret = selfPath_.configure(cfg, format);\n> -\t\t\tstreamConfig[1] = IPAStream(cfg.pixelFormat,\n> -\t\t\t\t\t\t    cfg.size);\n> +\t\t\tinternalCfg = selfPath_.internalStream();\n> +\t\t\tstreamConfig[idx] = IPAStream(internalCfg.pixelFormat,\n> +\t\t\t\t\t\t      internalCfg.size);\n>  \t\t} else {\n>  \t\t\treturn -ENODEV;\n>  \t\t}\n> @@ -754,6 +749,20 @@ int PipelineHandlerRkISP1::allocateBuffers(Camera *camera)\n>  \t\tdata->selfPathStream_.configuration().bufferCount,\n>  \t});\n>  \n> +\tif (data->mainPath_->isEnabled()) {\n> +\t\tret = data->mainPath_->allocateBuffers(\n> +\t\t\tdata->mainPathStream_.configuration().bufferCount);\n> +\t\tif (ret < 0)\n> +\t\t\tgoto error;\n> +\t}\n> +\n> +\tif (hasSelfPath_ && data->selfPath_->isEnabled()) {\n> +\t\tret = data->selfPath_->allocateBuffers(\n> +\t\t\tdata->selfPathStream_.configuration().bufferCount);\n> +\t\tif (ret < 0)\n> +\t\t\tgoto error;\n> +\t}\n> +\n>  \tret = param_->allocateBuffers(maxCount, &paramBuffers_);\n>  \tif (ret < 0)\n>  \t\tgoto error;\n> @@ -813,6 +822,12 @@ int PipelineHandlerRkISP1::freeBuffers(Camera *camera)\n>  \tif (stat_->releaseBuffers())\n>  \t\tLOG(RkISP1, Error) << \"Failed to release stat buffers\";\n>  \n> +\tif (hasSelfPath_ && data->selfPath_->isEnabled())\n> +\t\tdata->selfPath_->releaseBuffers();\n> +\n> +\tif (data->mainPath_->isEnabled())\n> +\t\tdata->mainPath_->releaseBuffers();\n> +\n>  \treturn 0;\n>  }\n>  \n> @@ -1055,6 +1070,19 @@ bool PipelineHandlerRkISP1::match(DeviceEnumerator *enumerator)\n>  \n>  \thasSelfPath_ = !!media_->getEntityByName(\"rkisp1_selfpath\");\n>  \n> +\t/* Seek for a converter */\n> +\tfor (auto converterName : ConverterFactory::names()) {\n> +\t\tLOG(RkISP1, Debug)\n> +\t\t\t<< \"Trying \" << converterName << \" converter\";\n> +\t\tDeviceMatch converterMatch(converterName);\n> +\t\tconverter_ = acquireMediaDevice(enumerator, converterMatch);\n> +\t\tif (converter_) {\n> +\t\t\tLOG(RkISP1, Debug)\n> +\t\t\t\t<< \"Get support for \" << converterName << \" converter\";\n> +\t\t\tbreak;\n> +\t\t}\n> +\t}\n> +\n>  \t/* Create the V4L2 subdevices we will need. */\n>  \tisp_ = V4L2Subdevice::fromEntityName(media_, \"rkisp1_isp\");\n>  \tif (isp_->open() < 0)\n> @@ -1086,10 +1114,10 @@ bool PipelineHandlerRkISP1::match(DeviceEnumerator *enumerator)\n>  \t\treturn false;\n>  \n>  \t/* Locate and open the ISP main and self paths. */\n> -\tif (!mainPath_.init(media_))\n> +\tif (!mainPath_.init(media_, converter_))\n>  \t\treturn false;\n>  \n> -\tif (hasSelfPath_ && !selfPath_.init(media_))\n> +\tif (hasSelfPath_ && !selfPath_.init(media_, converter_))\n>  \t\treturn false;\n>  \n>  \tmainPath_.bufferReady().connect(this, &PipelineHandlerRkISP1::bufferReady);\n> diff --git a/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp b/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp\n> index 2d38f0fb..c19a1e69 100644\n> --- a/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp\n> +++ b/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp\n> @@ -1,6 +1,7 @@\n>  /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>  /*\n>   * Copyright (C) 2020, Google Inc.\n> + * Copyright 2022 NXP\n>   *\n>   * rkisp1path.cpp - Rockchip ISP1 path helper\n>   */\n> @@ -28,7 +29,46 @@ RkISP1Path::RkISP1Path(const char *name, const Span<const PixelFormat> &formats,\n>  {\n>  }\n>  \n> -bool RkISP1Path::init(MediaDevice *media)\n> +void RkISP1Path::initConverter(MediaDevice *mediaConverter)\n> +{\n> +\thasConverter_ = false;\n> +\tuseConverter_ = false;\n> +\n> +\tif (!mediaConverter)\n> +\t\treturn;\n> +\n> +\tconverter_ = ConverterFactory::create(mediaConverter);\n> +\n> +\tif (!converter_->isValid()) {\n> +\t\tLOG(RkISP1, Warning)\n> +\t\t\t<< \"Failed to create converter, disabling format conversion\";\n> +\t\tconverter_.reset();\n> +\t} else {\n> +\t\tchar const *configFromEnv = utils::secure_getenv(\"LIBCAMERA_RKISP1_CONVERTER_FILE\");\n> +\n> +\t\tif (configFromEnv && *configFromEnv != '\\0') {\n> +\t\t\tint nrMappings;\n> +\t\t\tLOG(RkISP1, Debug)\n> +\t\t\t\t<< \"Getting pipeline converter filename as \" << std::string(configFromEnv);\n> +\t\t\tnrMappings = converter_->loadConfiguration(std::string(configFromEnv));\n> +\t\t\tif (nrMappings < 0) {\n> +\t\t\t\tLOG(RkISP1, Error)\n> +\t\t\t\t\t<< \"Error while reading converter configuration file\";\n> +\t\t\t} else {\n> +\t\t\t\tLOG(RkISP1, Debug)\n> +\t\t\t\t\t<< nrMappings << \" mapping(s) loaded\";\n> +\t\t\t\tif (nrMappings > 0)\n> +\t\t\t\t\t/* We want to force the converter use to apply the mapping */\n> +\t\t\t\t\tuseConverter_ = true;\n> +\t\t\t}\n> +\t\t}\n> +\t\tconverter_->inputBufferReady.connect(this, &RkISP1Path::pathConverterInputDone);\n> +\t\tconverter_->outputBufferReady.connect(this, &RkISP1Path::pathConverterOutputDone);\n> +\t\thasConverter_ = true;\n> +\t}\n> +}\n> +\n> +bool RkISP1Path::init(MediaDevice *media, MediaDevice *mediaConverter)\n>  {\n>  \tstd::string resizer = std::string(\"rkisp1_resizer_\") + name_ + \"path\";\n>  \tstd::string video = std::string(\"rkisp1_\") + name_ + \"path\";\n> @@ -45,10 +85,13 @@ bool RkISP1Path::init(MediaDevice *media)\n>  \tif (!link_)\n>  \t\treturn false;\n>  \n> +\tinitConverter(mediaConverter);\n> +\n> +\tvideo_->bufferReady.connect(this, &RkISP1Path::pathBufferReady);\n> +\n>  \treturn true;\n>  }\n> -\n> -StreamConfiguration RkISP1Path::generateConfiguration(const Size &resolution)\n> +StreamConfiguration RkISP1Path::generateNativeConfiguration(const Size &resolution)\n>  {\n>  \tSize maxResolution = maxResolution_.boundedToAspectRatio(resolution)\n>  \t\t\t\t\t   .boundedTo(resolution);\n> @@ -67,7 +110,165 @@ StreamConfiguration RkISP1Path::generateConfiguration(const Size &resolution)\n>  \treturn cfg;\n>  }\n>  \n> -CameraConfiguration::Status RkISP1Path::validate(StreamConfiguration *cfg)\n> +StreamConfiguration RkISP1Path::generateConfiguration(const Size &resolution)\n> +{\n> +\tStreamConfiguration cfg = generateNativeConfiguration(resolution);\n> +\tStreamFormats formats = cfg.formats();\n> +\tstd::map<PixelFormat, std::vector<SizeRange>> fullFormats;\n> +\n> +\tfor (const PixelFormat &fmt : formats.pixelformats()) {\n> +\t\tConfiguration config;\n> +\t\tSizeRange szRange;\n> +\n> +\t\tconfig.inputFormat = fmt;\n> +\t\tconfig.inputSizes = formats.range(fmt);\n> +\n> +\t\tif (!useConverter_) {\n> +\t\t\tconfig.outputFormats.push_back(fmt);\n> +\t\t\tconfig.outputSizes = config.inputSizes;\n> +\t\t\tconfig.withConverter = false;\n> +\t\t\tconfigs_.push_back(config);\n> +\t\t\tLOG(RkISP1, Debug)\n> +\t\t\t\t<< \"Pushing native format \" << fmt\n> +\t\t\t\t<< \" resolution range \" << config.inputSizes;\n> +\t\t\tfullFormats[fmt].push_back(config.inputSizes);\n> +\t\t}\n> +\n> +\t\tif (hasConverter_) {\n> +\t\t\tconfig.outputFormats = converter_->formats(config.inputFormat);\n> +\t\t\tif (config.outputFormats.empty())\n> +\t\t\t\tcontinue;\n> +\t\t\tszRange.max = converter_->sizes(config.inputSizes.max).max;\n> +\t\t\tszRange.min = converter_->sizes(config.inputSizes.min).min;\n> +\t\t\tconfig.outputSizes = szRange;\n> +\t\t\tconfig.withConverter = true;\n> +\t\t\tconfigs_.push_back(config);\n> +\t\t\tfor (auto &_fmt : config.outputFormats) {\n> +\t\t\t\tfullFormats[_fmt].push_back(config.outputSizes);\n> +\t\t\t\tLOG(RkISP1, Debug)\n> +\t\t\t\t\t<< \"Pushing converted format \" << _fmt\n> +\t\t\t\t\t<< \" resolution range \" << config.outputSizes;\n> +\t\t\t}\n> +\t\t}\n> +\t}\n> +\t/*\n> +\t * Sort the sizes and merge any consecutive overlapping ranges.\n> +\t * Imported from src/libcamera/pipeline/simple.cpp\n> +\t *\n> +\t * TODO: Apply policy of converter use or not.. We likely want to force\n> +\t * the converter use in case there is a mapping defined in the\n> +\t * configuration file if any... if so, shall we filter out resolution\n> +\t * not defined in the configuration file.. or should this policy be let\n> +\t * to the application\n> +\t * */\n> +\n> +\tfor (auto &[format, sizes] : fullFormats) {\n> +\t\tstd::sort(sizes.begin(), sizes.end(),\n> +\t\t\t  [](SizeRange &a, SizeRange &b) {\n> +\t\t\t\t  return a.min < b.min;\n> +\t\t\t  });\n> +\n> +\t\tauto cur = sizes.begin();\n> +\t\tauto next = cur;\n> +\n> +\t\twhile (++next != sizes.end()) {\n> +\t\t\tif (cur->max.width >= next->min.width &&\n> +\t\t\t    cur->max.height >= next->min.height)\n> +\t\t\t\tcur->max = next->max;\n> +\t\t\telse if (++cur != next)\n> +\t\t\t\t*cur = *next;\n> +\t\t}\n> +\n> +\t\tsizes.erase(++cur, sizes.end());\n> +\t}\n> +\n> +\tStreamConfiguration fullCfg{ StreamFormats{ fullFormats } };\n> +\tfullCfg.pixelFormat = fullFormats.begin()->first;\n> +\tfullCfg.size = fullFormats.begin()->second[0].max;\n> +\tfullCfg.bufferCount = cfg.bufferCount;\n> +\n> +\treturn fullCfg;\n> +}\n> +\n> +CameraConfiguration::Status RkISP1Path::getPipeConfiguration(\n> +\tStreamConfiguration &streamCfg,\n> +\tconst RkISP1Path::Configuration **cfg) const\n> +{\n> +\tCameraConfiguration::Status _status = CameraConfiguration::Adjusted;\n> +\n> +\tLOG(RkISP1, Debug)\n> +\t\t<< \"Looking for \"\n> +\t\t<< streamCfg.pixelFormat << \"/\" << streamCfg.size\n> +\t\t<< \" output configuration\";\n> +\t/*\n> +\t * Select the fallback configuration\n> +\t */\n> +\tfor (auto &_cfg : configs_) {\n> +\t\tif (_cfg.withConverter == useConverter_) {\n> +\t\t\t*cfg = &_cfg;\n> +\t\t\tbreak;\n> +\t\t}\n> +\t}\n> +\n> +\tPixelFormat pixelFormat = (*cfg)->outputFormats.front();\n> +\tSize size = streamCfg.size;\n> +\tsize.boundTo((*cfg)->outputSizes.max);\n> +\tsize.expandTo((*cfg)->outputSizes.min);\n> +\n> +\t/* Unless the converter use is enforced through a loaded configuration,\n> +\t * prefer a configuration without the converter if possible\n> +\t */\n> +\tstd::vector<bool> converterUse;\n> +\tif (!useConverter_)\n> +\t\tconverterUse.push_back(false);\n> +\tif (hasConverter_)\n> +\t\tconverterUse.push_back(true);\n> +\n> +\tfor (auto withConverter : converterUse) {\n> +\t\tfor (auto &_cfg : configs_) {\n> +\t\t\tauto &outFmts = _cfg.outputFormats;\n> +\t\t\tif (withConverter != _cfg.withConverter)\n> +\t\t\t\tcontinue;\n> +\n> +\t\t\tif ((std::find(outFmts.begin(),\n> +\t\t\t\t       outFmts.end(),\n> +\t\t\t\t       streamCfg.pixelFormat)) == outFmts.end())\n> +\t\t\t\tcontinue;\n> +\n> +\t\t\t*cfg = &_cfg;\n> +\t\t\tpixelFormat = streamCfg.pixelFormat;\n> +\n> +\t\t\tif (_cfg.outputSizes.contains(streamCfg.size)) {\n> +\t\t\t\t_status = CameraConfiguration::Valid;\n> +\t\t\t\tgoto done;\n> +\t\t\t}\n> +\n> +\t\t\tsize.boundTo((*cfg)->outputSizes.max);\n> +\t\t\tsize.expandTo((*cfg)->outputSizes.min);\n> +\t\t}\n> +\t}\n> +\n> +\tstreamCfg.pixelFormat = pixelFormat;\n> +\tstreamCfg.size = size;\n> +\n> +done:\n> +\tif ((*cfg)->withConverter) {\n> +\t\tstd::tie(streamCfg.stride, streamCfg.frameSize) =\n> +\t\t\tconverter_->strideAndFrameSize(streamCfg.pixelFormat,\n> +\t\t\t\t\t\t       streamCfg.size);\n> +\t\tif (streamCfg.stride == 0)\n> +\t\t\treturn CameraConfiguration::Invalid;\n> +\t}\n> +\n> +\tLOG(RkISP1, Debug)\n> +\t\t<< \"Chosen configuration: \"\n> +\t\t<< (*cfg)->inputFormat << \"/\" << (*cfg)->inputSizes\n> +\t\t<< \" --> \" << streamCfg.pixelFormat << \"/\" << streamCfg.size\n> +\t\t<< ((*cfg)->withConverter ? \" with\" : \" without\") << \" converter\";\n> +\treturn _status;\n> +}\n> +\n> +CameraConfiguration::Status RkISP1Path::nativeValidate(StreamConfiguration *cfg)\n>  {\n>  \tconst StreamConfiguration reqCfg = *cfg;\n>  \tCameraConfiguration::Status status = CameraConfiguration::Valid;\n> @@ -101,7 +302,39 @@ CameraConfiguration::Status RkISP1Path::validate(StreamConfiguration *cfg)\n>  \treturn status;\n>  }\n>  \n> -int RkISP1Path::configure(const StreamConfiguration &config,\n> +CameraConfiguration::Status RkISP1Path::validate(StreamConfiguration *cfg)\n> +{\n> +\tStreamConfiguration tryCfg = *cfg;\n> +\tStreamConfiguration internalCfg;\n> +\tconst Configuration *pipeCfg;\n> +\tCameraConfiguration::Status pipeStatus;\n> +\n> +\tpipeStatus = getPipeConfiguration(tryCfg, &pipeCfg);\n> +\tif (pipeStatus == CameraConfiguration::Invalid)\n> +\t\treturn CameraConfiguration::Invalid;\n> +\n> +\tif (!pipeCfg->withConverter) {\n> +\t\ttryCfg = *cfg;\n> +\t\tpipeStatus = nativeValidate(&tryCfg);\n> +\t\tif (pipeStatus == CameraConfiguration::Invalid)\n> +\t\t\treturn CameraConfiguration::Invalid;\n> +\t\tinternalCfg = tryCfg;\n> +\t} else {\n> +\t\tinternalCfg = tryCfg;\n> +\t\tinternalCfg.pixelFormat = pipeCfg->inputFormat;\n> +\t\tauto _status = nativeValidate(&internalCfg);\n> +\t\tif (_status == CameraConfiguration::Invalid)\n> +\t\t\treturn CameraConfiguration::Invalid;\n> +\t}\n> +\n> +\t*cfg = tryCfg;\n> +\tinternalStream_ = internalCfg;\n> +\tpipeConfig_ = pipeCfg;\n> +\n> +\treturn pipeStatus;\n> +}\n> +\n> +int RkISP1Path::nativeConfigure(const StreamConfiguration &config,\n>  \t\t\t  const V4L2SubdeviceFormat &inputFormat)\n>  {\n>  \tint ret;\n> @@ -165,6 +398,117 @@ int RkISP1Path::configure(const StreamConfiguration &config,\n>  \treturn 0;\n>  }\n>  \n> +int RkISP1Path::configure(const StreamConfiguration &config,\n> +\t\t\t  const V4L2SubdeviceFormat &inputFormat)\n> +{\n> +\tint ret;\n> +\tLOG(RkISP1, Debug)\n> +\t\t<< \"Configuring \" << name_ << \" path \"\n> +\t\t<< internalStream_.pixelFormat << \"/\" << internalStream_.size\n> +\t\t<< \" --> \" << config.pixelFormat << \"/\" << config.size\n> +\t\t<< (pipeConfig_->withConverter ? \" with\" : \" without\") << \" converter\";\n> +\n> +\tret = nativeConfigure(internalStream_, inputFormat);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tif (pipeConfig_->withConverter) {\n> +\t\tStreamConfiguration cfg = config;\n> +\t\tstd::vector<std::reference_wrapper<StreamConfiguration>> outputCfgs;\n> +\t\toutputCfgs.push_back(cfg);\n> +\t\tret = converter_->configure(internalStream_, outputCfgs);\n> +\t}\n> +\n> +\treturn ret;\n> +}\n> +\n> +int RkISP1Path::exportBuffers(unsigned int bufferCount,\n> +\t\t\t      std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n> +{\n> +\tif (pipeConfig_->withConverter)\n> +\t\treturn converter_->exportBuffers(0, bufferCount, buffers);\n> +\telse\n> +\t\treturn video_->exportBuffers(bufferCount, buffers);\n> +}\n> +\n> +int RkISP1Path::allocateBuffers(unsigned int bufferCount)\n> +{\n> +\tif (pipeConfig_->withConverter) {\n> +\t\tint ret = video_->allocateBuffers(bufferCount, &converterBuffers_);\n> +\t\tif (ret < 0)\n> +\t\t\treturn ret;\n> +\t\tif ((unsigned int)ret != bufferCount)\n> +\t\t\tLOG(RkISP1, Warning)\n> +\t\t\t\t<< \"Requested \" << bufferCount << \" but got \" << ret << \" buffers\";\n> +\n> +\t\tfor (std::unique_ptr<FrameBuffer> &buffer : converterBuffers_)\n> +\t\t\tavailableConverterBuffers_.push(buffer.get());\n> +\t} else {\n> +\t\treturn video_->importBuffers(bufferCount);\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +void RkISP1Path::releaseBuffers()\n> +{\n> +\twhile (!availableConverterBuffers_.empty())\n> +\t\tavailableConverterBuffers_.pop();\n> +\n> +\tconverterBuffers_.clear();\n> +\n> +\tvideo_->releaseBuffers();\n> +}\n> +\n> +void RkISP1Path::pathBufferReady(FrameBuffer *buffer)\n> +{\n> +\tRequest *request = buffer->request();\n> +\n> +\tif (request) {\n> +\t\tinternalBufferReady_.emit(buffer);\n> +\t} else {\n> +\t\tauto iter = converterBuffersMapping_.find(buffer);\n> +\t\tif (iter != converterBuffersMapping_.end()) {\n> +\t\t\tconverter_->queueBuffer(buffer, iter->second);\n> +\t\t\tconverterBuffersMapping_.erase(iter);\n> +\t\t} else {\n> +\t\t\tLOG(RkISP1, Error)\n> +\t\t\t\t<< \"Final buffer associated to converted buffer not found on \"\n> +\t\t\t\t<< name_ << \" path\";\n> +\t\t}\n> +\t}\n> +}\n> +\n> +void RkISP1Path::pathConverterInputDone(FrameBuffer *buffer)\n> +{\n> +\tavailableConverterBuffers_.push(buffer);\n> +}\n> +\n> +void RkISP1Path::pathConverterOutputDone(FrameBuffer *buffer)\n> +{\n> +\tinternalBufferReady_.emit(buffer);\n> +}\n> +\n> +int RkISP1Path::queueBuffer(FrameBuffer *buffer)\n> +{\n> +\tFrameBuffer *_buffer;\n> +\n> +\tif (pipeConfig_->withConverter) {\n> +\t\tif (availableConverterBuffers_.empty()) {\n> +\t\t\tLOG(RkISP1, Error)\n> +\t\t\t\t<< \"converter buffer underrun on \" << name_ << \" path\";\n> +\t\t\treturn -ENOMEM;\n> +\t\t}\n> +\t\t_buffer = availableConverterBuffers_.front();\n> +\t\tavailableConverterBuffers_.pop();\n> +\t\tconverterBuffersMapping_[_buffer] = buffer;\n> +\t} else {\n> +\t\t_buffer = buffer;\n> +\t}\n> +\n> +\treturn video_->queueBuffer(_buffer);\n> +}\n> +\n>  int RkISP1Path::start()\n>  {\n>  \tint ret;\n> @@ -172,20 +516,23 @@ int RkISP1Path::start()\n>  \tif (running_)\n>  \t\treturn -EBUSY;\n>  \n> -\t/* \\todo Make buffer count user configurable. */\n> -\tret = video_->importBuffers(RKISP1_BUFFER_COUNT);\n> -\tif (ret)\n> -\t\treturn ret;\n> -\n>  \tret = video_->streamOn();\n>  \tif (ret) {\n>  \t\tLOG(RkISP1, Error)\n>  \t\t\t<< \"Failed to start \" << name_ << \" path\";\n> -\n> -\t\tvideo_->releaseBuffers();\n>  \t\treturn ret;\n>  \t}\n>  \n> +\tif (pipeConfig_->withConverter) {\n> +\t\tret = converter_->start();\n> +\t\tif (ret) {\n> +\t\t\tLOG(RkISP1, Error)\n> +\t\t\t\t<< \"Failed to start converter on \" << name_ << \" path\";\n> +\t\t\tstop();\n> +\t\t\treturn ret;\n> +\t\t}\n> +\t}\n> +\n>  \trunning_ = true;\n>  \n>  \treturn 0;\n> @@ -199,7 +546,8 @@ void RkISP1Path::stop()\n>  \tif (video_->streamOff())\n>  \t\tLOG(RkISP1, Warning) << \"Failed to stop \" << name_ << \" path\";\n>  \n> -\tvideo_->releaseBuffers();\n> +\tif (pipeConfig_->withConverter)\n> +\t\tconverter_->stop();\n>  \n>  \trunning_ = false;\n>  }\n> diff --git a/src/libcamera/pipeline/rkisp1/rkisp1_path.h b/src/libcamera/pipeline/rkisp1/rkisp1_path.h\n> index f3f1ae39..0da3594f 100644\n> --- a/src/libcamera/pipeline/rkisp1/rkisp1_path.h\n> +++ b/src/libcamera/pipeline/rkisp1/rkisp1_path.h\n> @@ -1,6 +1,7 @@\n>  /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>  /*\n>   * Copyright (C) 2020, Google Inc.\n> + * Copyright 2022 NXP\n>   *\n>   * rkisp1path.h - Rockchip ISP1 path helper\n>   */\n> @@ -8,6 +9,7 @@\n>  #pragma once\n>  \n>  #include <memory>\n> +#include <queue>\n>  #include <vector>\n>  \n>  #include <libcamera/base/signal.h>\n> @@ -17,9 +19,11 @@\n>  #include <libcamera/geometry.h>\n>  #include <libcamera/pixel_format.h>\n>  \n> +#include \"libcamera/internal/converter.h\"\n>  #include \"libcamera/internal/media_object.h\"\n>  #include \"libcamera/internal/v4l2_videodevice.h\"\n>  \n> +\n>  namespace libcamera {\n>  \n>  class MediaDevice;\n> @@ -33,7 +37,7 @@ public:\n>  \tRkISP1Path(const char *name, const Span<const PixelFormat> &formats,\n>  \t\t   const Size &minResolution, const Size &maxResolution);\n>  \n> -\tbool init(MediaDevice *media);\n> +\tbool init(MediaDevice *media, MediaDevice *mediaConverter = nullptr);\n>  \n>  \tint setEnabled(bool enable) { return link_->setEnabled(enable); }\n>  \tbool isEnabled() const { return link_->flags() & MEDIA_LNK_FL_ENABLED; }\n> @@ -45,18 +49,31 @@ public:\n>  \t\t      const V4L2SubdeviceFormat &inputFormat);\n>  \n>  \tint exportBuffers(unsigned int bufferCount,\n> -\t\t\t  std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n> -\t{\n> -\t\treturn video_->exportBuffers(bufferCount, buffers);\n> -\t}\n> +\t\t\t  std::vector<std::unique_ptr<FrameBuffer>> *buffers);\n> +\n> +\tint allocateBuffers(unsigned int bufferCount);\n> +\tvoid releaseBuffers();\n>  \n>  \tint start();\n>  \tvoid stop();\n>  \n> -\tint queueBuffer(FrameBuffer *buffer) { return video_->queueBuffer(buffer); }\n> -\tSignal<FrameBuffer *> &bufferReady() { return video_->bufferReady; }\n> +\tint queueBuffer(FrameBuffer *buffer);\n> +\tSignal<FrameBuffer *> &bufferReady() { return internalBufferReady_; }\n> +\n> +\tStreamConfiguration internalStream() const\n> +\t{\n> +\t\treturn internalStream_;\n> +\t}\n>  \n>  private:\n> +\tstruct Configuration {\n> +\t\tSizeRange inputSizes;\n> +\t\tPixelFormat inputFormat;\n> +\t\tstd::vector<PixelFormat> outputFormats;\n> +\t\tSizeRange outputSizes;\n> +\t\tbool withConverter;\n> +\t};\n> +\n>  \tstatic constexpr unsigned int RKISP1_BUFFER_COUNT = 4;\n>  \n>  \tconst char *name_;\n> @@ -65,10 +82,33 @@ private:\n>  \tconst Span<const PixelFormat> formats_;\n>  \tconst Size minResolution_;\n>  \tconst Size maxResolution_;\n> +\tCameraConfiguration::Status getPipeConfiguration(\n> +\t\tStreamConfiguration &streamCfg,\n> +\t\tconst RkISP1Path::Configuration **cfg) const;\n> +\n> +\tStreamConfiguration generateNativeConfiguration(const Size &resolution);\n> +\tCameraConfiguration::Status nativeValidate(StreamConfiguration *cfg);\n> +\tint nativeConfigure(const StreamConfiguration &config,\n> +\t\t\t    const V4L2SubdeviceFormat &inputFormat);\n>  \n>  \tstd::unique_ptr<V4L2Subdevice> resizer_;\n>  \tstd::unique_ptr<V4L2VideoDevice> video_;\n>  \tMediaLink *link_;\n> +\n> +\tvoid initConverter(MediaDevice *mediaConverter);\n> +\tstd::unique_ptr<Converter> converter_;\n> +\tstd::vector<Configuration> configs_;\n> +\tStreamConfiguration internalStream_;\n> +\tconst Configuration *pipeConfig_;\n> +\tbool hasConverter_;\n> +\tbool useConverter_;\n> +\tSignal<FrameBuffer *> internalBufferReady_;\n> +\tstd::vector<std::unique_ptr<FrameBuffer>> converterBuffers_;\n> +\tstd::queue<FrameBuffer *> availableConverterBuffers_;\n> +\tstd::map<FrameBuffer *, FrameBuffer *> converterBuffersMapping_;\n> +\tvoid pathBufferReady(FrameBuffer *buffer);\n> +\tvoid pathConverterInputDone(FrameBuffer *buffer);\n> +\tvoid pathConverterOutputDone(FrameBuffer *buffer);\n>  };\n>  \n>  class RkISP1MainPath : public RkISP1Path","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id E3960C0DA4\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue,  4 Oct 2022 02:36:16 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 1CCC860A83;\n\tTue,  4 Oct 2022 04:36:16 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id B902D603F6\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue,  4 Oct 2022 04:36:13 +0200 (CEST)","from pendragon.ideasonboard.com (62-78-145-57.bb.dnainternet.fi\n\t[62.78.145.57])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 3CED12D9;\n\tTue,  4 Oct 2022 04:36:12 +0200 (CEST)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1664850976;\n\tbh=AJtsksS54i7EXkRjznnof+A7ocuqbh+95JB/t6GaCiE=;\n\th=Date:To:References:In-Reply-To:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=a6G6g84EuHNl2BqSSZ+ujx4ShxF3uQYw/dHZiK5b59FvuT3O8w/nqoA3UHmkaPml8\n\t1Wofx8q651RFATy2Y1E6PFYvGeDb4hBz7VX90g1qWc0fFPR/fiGpW13+6N9zCZt3kN\n\tdNIPNyLHkWZNZ2oJYmNAwxjIaZQj0U9+78ZqW2MWmw6Pb4kTSiIYsnoydBLYcRiL0L\n\t4czFq2rBuKz08Gi/cTynXdT6BdBLsJ+XV1pJYaRJK5nU5bcrT21d16QVhowlY6J0uQ\n\thKtGAdrIMZm8dK1X98GJmvq8vxD9uh3b6N5c99EN5hHHtOc1B3azbi24CcQFb+lRSm\n\tW1GbRMuysyuiw==","v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1664850973;\n\tbh=AJtsksS54i7EXkRjznnof+A7ocuqbh+95JB/t6GaCiE=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=WhR9jeQ0hbe1w7oRs7f/BL0ApVy6oIIeoq1npmMfLs0Pack4NAdbYcyU4FIHEIMNf\n\tT7UuixS/yyn3lvGqh/poA9oIXPglf/DYyWBjWr4mMoqbyxcgf73uG4SRzdhyBnwI/Z\n\tkkbPzxxKhcqfH3zKmhLFreFYlVdha18G7eq8JkgM="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"WhR9jeQ0\"; dkim-atps=neutral","Date":"Tue, 4 Oct 2022 05:36:08 +0300","To":"Xavier Roumegue <xavier.roumegue@oss.nxp.com>","Message-ID":"<YzucGM4lebLeIaEo@pendragon.ideasonboard.com>","References":"<20220908184850.1874303-1-xavier.roumegue@oss.nxp.com>\n\t<20220908184850.1874303-15-xavier.roumegue@oss.nxp.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20220908184850.1874303-15-xavier.roumegue@oss.nxp.com>","Subject":"Re: [libcamera-devel] [PATCH 14/14] libcamera: pipeline: rkisp1:\n\tAdd converter support","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","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>","From":"Laurent Pinchart via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>","Reply-To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":25339,"web_url":"https://patchwork.libcamera.org/comment/25339/","msgid":"<ec7e30be-06b8-590b-8640-856ee5d868d0@oss.nxp.com>","date":"2022-10-07T14:27:04","subject":"Re: [libcamera-devel] [PATCH 14/14] libcamera: pipeline: rkisp1:\n\tAdd converter support","submitter":{"id":107,"url":"https://patchwork.libcamera.org/api/people/107/","name":"Xavier Roumegue","email":"xavier.roumegue@oss.nxp.com"},"content":"Hi Laurent,\n\nOn 10/4/22 04:36, Laurent Pinchart wrote:\n> Hi Xavier,\n> \n> Thank you for the patch.\n> \n> On Thu, Sep 08, 2022 at 08:48:50PM +0200, Xavier Roumegue via libcamera-devel wrote:\n>> This adds a converter, if any present in the system, on each streams\n>> (self and main paths). In case a configuration file is successfully\n>> loaded, the converter use is getting compulsory, such as a dewarping map\n>> is unconditionally applied. Otherwise, the converter is only used if the\n>> stream configuration requires it.\n> \n> Before doing a full review of this, I'm wondering what use case(s) you\n> envision for scaling with the dewarper. The ISP has a scaler at the\n> output, are there cases where you think scaling in the dewarper would\n> have an advantage ?\nAssuming that rkisp1 and converter scalers have the same capabilities, \nthere is likely no reason to scale in the converter. But generaly \nspeaking, scaling in the converter might make sense if you can benefit \nfrom a converter scaler features missing in rkisp1 (hflip, vflip, \nrotation, etc...).\n\nAn interesting future feature would be to use the multiple output stream \nconverter capabilities to generate pyramid used on computer vision \nalgorithm.\n\nXavier\n\n> \n>> Signed-off-by: Xavier Roumegue <xavier.roumegue@oss.nxp.com>\n>> ---\n>>   src/libcamera/pipeline/rkisp1/rkisp1.cpp      | 126 +++---\n>>   src/libcamera/pipeline/rkisp1/rkisp1_path.cpp | 374 +++++++++++++++++-\n>>   src/libcamera/pipeline/rkisp1/rkisp1_path.h   |  54 ++-\n>>   3 files changed, 485 insertions(+), 69 deletions(-)\n>>\n>> diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n>> index c1522ca6..6bdf5a3a 100644\n>> --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n>> +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n>> @@ -1,6 +1,7 @@\n>>   /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>>   /*\n>>    * Copyright (C) 2019, Google Inc.\n>> + * Copyright 2022 NXP\n>>    *\n>>    * rkisp1.cpp - Pipeline handler for Rockchip ISP1\n>>    */\n>> @@ -194,6 +195,7 @@ private:\n>>   \tCamera *activeCamera_;\n>>   \n>>   \tconst MediaPad *ispSink_;\n>> +\tMediaDevice *converter_;\n>>   };\n>>   \n>>   RkISP1Frames::RkISP1Frames(PipelineHandler *pipe)\n>> @@ -449,57 +451,44 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()\n>>   \n>>   \tbool mainPathAvailable = true;\n>>   \tbool selfPathAvailable = data_->selfPath_;\n>> +\tconst std::array<Status, 2> cameraStatus = { Valid, Adjusted };\n>> +\n>>   \tfor (unsigned int index : order) {\n>>   \t\tStreamConfiguration &cfg = config_[index];\n>>   \n>> -\t\t/* Try to match stream without adjusting configuration. */\n>> -\t\tif (mainPathAvailable) {\n>> -\t\t\tStreamConfiguration tryCfg = cfg;\n>> -\t\t\tif (data_->mainPath_->validate(&tryCfg) == Valid) {\n>> -\t\t\t\tmainPathAvailable = false;\n>> -\t\t\t\tcfg = tryCfg;\n>> -\t\t\t\tcfg.setStream(const_cast<Stream *>(&data_->mainPathStream_));\n>> -\t\t\t\tcontinue;\n>> -\t\t\t}\n>> -\t\t}\n>> +\t\tfor (auto &_status : cameraStatus) {\n>> +\t\t\tStatus pipeStatus;\n>>   \n>> -\t\tif (selfPathAvailable) {\n>> -\t\t\tStreamConfiguration tryCfg = cfg;\n>> -\t\t\tif (data_->selfPath_->validate(&tryCfg) == Valid) {\n>> -\t\t\t\tselfPathAvailable = false;\n>> -\t\t\t\tcfg = tryCfg;\n>> -\t\t\t\tcfg.setStream(const_cast<Stream *>(&data_->selfPathStream_));\n>> -\t\t\t\tcontinue;\n>> -\t\t\t}\n>> -\t\t}\n>> +\t\t\tif (mainPathAvailable) {\n>> +\t\t\t\tStreamConfiguration tryCfg = cfg;\n>> +\t\t\t\tpipeStatus = data_->mainPath_->validate(&tryCfg);\n>>   \n>> -\t\t/* Try to match stream allowing adjusting configuration. */\n>> -\t\tif (mainPathAvailable) {\n>> -\t\t\tStreamConfiguration tryCfg = cfg;\n>> -\t\t\tif (data_->mainPath_->validate(&tryCfg) == Adjusted) {\n>> -\t\t\t\tmainPathAvailable = false;\n>> -\t\t\t\tcfg = tryCfg;\n>> -\t\t\t\tcfg.setStream(const_cast<Stream *>(&data_->mainPathStream_));\n>> -\t\t\t\tstatus = Adjusted;\n>> -\t\t\t\tcontinue;\n>> +\t\t\t\tif (pipeStatus == _status) {\n>> +\t\t\t\t\tmainPathAvailable = false;\n>> +\t\t\t\t\tcfg = tryCfg;\n>> +\t\t\t\t\tcfg.setStream(const_cast<Stream *>(&data_->mainPathStream_));\n>> +\t\t\t\t\tstatus = _status;\n>> +\t\t\t\t\tbreak;\n>> +\t\t\t\t}\n>>   \t\t\t}\n>> -\t\t}\n>>   \n>> -\t\tif (selfPathAvailable) {\n>> -\t\t\tStreamConfiguration tryCfg = cfg;\n>> -\t\t\tif (data_->selfPath_->validate(&tryCfg) == Adjusted) {\n>> -\t\t\t\tselfPathAvailable = false;\n>> -\t\t\t\tcfg = tryCfg;\n>> -\t\t\t\tcfg.setStream(const_cast<Stream *>(&data_->selfPathStream_));\n>> -\t\t\t\tstatus = Adjusted;\n>> -\t\t\t\tcontinue;\n>> +\t\t\tif (selfPathAvailable) {\n>> +\t\t\t\tStreamConfiguration tryCfg = cfg;\n>> +\t\t\t\tpipeStatus = data_->selfPath_->validate(&tryCfg);\n>> +\n>> +\t\t\t\tif (pipeStatus == _status) {\n>> +\t\t\t\t\tselfPathAvailable = false;\n>> +\t\t\t\t\tcfg = tryCfg;\n>> +\t\t\t\t\tcfg.setStream(const_cast<Stream *>(&data_->selfPathStream_));\n>> +\t\t\t\t\tstatus = _status;\n>> +\t\t\t\t\tbreak;\n>> +\t\t\t\t}\n>>   \t\t\t}\n>> +\t\t\t/* All paths rejected configuration. */\n>> +\t\t\tLOG(RkISP1, Debug) << \"Camera configuration not supported \"\n>> +\t\t\t\t\t   << cfg.toString();\n>> +\t\t\treturn Invalid;\n>>   \t\t}\n>> -\n>> -\t\t/* All paths rejected configuraiton. */\n>> -\t\tLOG(RkISP1, Debug) << \"Camera configuration not supported \"\n>> -\t\t\t\t   << cfg.toString();\n>> -\t\treturn Invalid;\n>>   \t}\n>>   \n>>   \t/* Select the sensor format. */\n>> @@ -680,15 +669,21 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)\n>>   \n>>   \tstd::map<unsigned int, IPAStream> streamConfig;\n>>   \n>> -\tfor (const StreamConfiguration &cfg : *config) {\n>> +\tfor (StreamConfiguration &cfg : *config) {\n>> +\t\tsize_t idx = 0;\n>> +\t\tStreamConfiguration internalCfg;\n>>   \t\tif (cfg.stream() == &data->mainPathStream_) {\n>> +\t\t\tidx = 0;\n>>   \t\t\tret = mainPath_.configure(cfg, format);\n>> -\t\t\tstreamConfig[0] = IPAStream(cfg.pixelFormat,\n>> -\t\t\t\t\t\t    cfg.size);\n>> +\t\t\tinternalCfg = mainPath_.internalStream();\n>> +\t\t\tstreamConfig[idx] = IPAStream(internalCfg.pixelFormat,\n>> +\t\t\t\t\t\t      internalCfg.size);\n>>   \t\t} else if (hasSelfPath_) {\n>> +\t\t\tidx = 1;\n>>   \t\t\tret = selfPath_.configure(cfg, format);\n>> -\t\t\tstreamConfig[1] = IPAStream(cfg.pixelFormat,\n>> -\t\t\t\t\t\t    cfg.size);\n>> +\t\t\tinternalCfg = selfPath_.internalStream();\n>> +\t\t\tstreamConfig[idx] = IPAStream(internalCfg.pixelFormat,\n>> +\t\t\t\t\t\t      internalCfg.size);\n>>   \t\t} else {\n>>   \t\t\treturn -ENODEV;\n>>   \t\t}\n>> @@ -754,6 +749,20 @@ int PipelineHandlerRkISP1::allocateBuffers(Camera *camera)\n>>   \t\tdata->selfPathStream_.configuration().bufferCount,\n>>   \t});\n>>   \n>> +\tif (data->mainPath_->isEnabled()) {\n>> +\t\tret = data->mainPath_->allocateBuffers(\n>> +\t\t\tdata->mainPathStream_.configuration().bufferCount);\n>> +\t\tif (ret < 0)\n>> +\t\t\tgoto error;\n>> +\t}\n>> +\n>> +\tif (hasSelfPath_ && data->selfPath_->isEnabled()) {\n>> +\t\tret = data->selfPath_->allocateBuffers(\n>> +\t\t\tdata->selfPathStream_.configuration().bufferCount);\n>> +\t\tif (ret < 0)\n>> +\t\t\tgoto error;\n>> +\t}\n>> +\n>>   \tret = param_->allocateBuffers(maxCount, &paramBuffers_);\n>>   \tif (ret < 0)\n>>   \t\tgoto error;\n>> @@ -813,6 +822,12 @@ int PipelineHandlerRkISP1::freeBuffers(Camera *camera)\n>>   \tif (stat_->releaseBuffers())\n>>   \t\tLOG(RkISP1, Error) << \"Failed to release stat buffers\";\n>>   \n>> +\tif (hasSelfPath_ && data->selfPath_->isEnabled())\n>> +\t\tdata->selfPath_->releaseBuffers();\n>> +\n>> +\tif (data->mainPath_->isEnabled())\n>> +\t\tdata->mainPath_->releaseBuffers();\n>> +\n>>   \treturn 0;\n>>   }\n>>   \n>> @@ -1055,6 +1070,19 @@ bool PipelineHandlerRkISP1::match(DeviceEnumerator *enumerator)\n>>   \n>>   \thasSelfPath_ = !!media_->getEntityByName(\"rkisp1_selfpath\");\n>>   \n>> +\t/* Seek for a converter */\n>> +\tfor (auto converterName : ConverterFactory::names()) {\n>> +\t\tLOG(RkISP1, Debug)\n>> +\t\t\t<< \"Trying \" << converterName << \" converter\";\n>> +\t\tDeviceMatch converterMatch(converterName);\n>> +\t\tconverter_ = acquireMediaDevice(enumerator, converterMatch);\n>> +\t\tif (converter_) {\n>> +\t\t\tLOG(RkISP1, Debug)\n>> +\t\t\t\t<< \"Get support for \" << converterName << \" converter\";\n>> +\t\t\tbreak;\n>> +\t\t}\n>> +\t}\n>> +\n>>   \t/* Create the V4L2 subdevices we will need. */\n>>   \tisp_ = V4L2Subdevice::fromEntityName(media_, \"rkisp1_isp\");\n>>   \tif (isp_->open() < 0)\n>> @@ -1086,10 +1114,10 @@ bool PipelineHandlerRkISP1::match(DeviceEnumerator *enumerator)\n>>   \t\treturn false;\n>>   \n>>   \t/* Locate and open the ISP main and self paths. */\n>> -\tif (!mainPath_.init(media_))\n>> +\tif (!mainPath_.init(media_, converter_))\n>>   \t\treturn false;\n>>   \n>> -\tif (hasSelfPath_ && !selfPath_.init(media_))\n>> +\tif (hasSelfPath_ && !selfPath_.init(media_, converter_))\n>>   \t\treturn false;\n>>   \n>>   \tmainPath_.bufferReady().connect(this, &PipelineHandlerRkISP1::bufferReady);\n>> diff --git a/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp b/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp\n>> index 2d38f0fb..c19a1e69 100644\n>> --- a/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp\n>> +++ b/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp\n>> @@ -1,6 +1,7 @@\n>>   /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>>   /*\n>>    * Copyright (C) 2020, Google Inc.\n>> + * Copyright 2022 NXP\n>>    *\n>>    * rkisp1path.cpp - Rockchip ISP1 path helper\n>>    */\n>> @@ -28,7 +29,46 @@ RkISP1Path::RkISP1Path(const char *name, const Span<const PixelFormat> &formats,\n>>   {\n>>   }\n>>   \n>> -bool RkISP1Path::init(MediaDevice *media)\n>> +void RkISP1Path::initConverter(MediaDevice *mediaConverter)\n>> +{\n>> +\thasConverter_ = false;\n>> +\tuseConverter_ = false;\n>> +\n>> +\tif (!mediaConverter)\n>> +\t\treturn;\n>> +\n>> +\tconverter_ = ConverterFactory::create(mediaConverter);\n>> +\n>> +\tif (!converter_->isValid()) {\n>> +\t\tLOG(RkISP1, Warning)\n>> +\t\t\t<< \"Failed to create converter, disabling format conversion\";\n>> +\t\tconverter_.reset();\n>> +\t} else {\n>> +\t\tchar const *configFromEnv = utils::secure_getenv(\"LIBCAMERA_RKISP1_CONVERTER_FILE\");\n>> +\n>> +\t\tif (configFromEnv && *configFromEnv != '\\0') {\n>> +\t\t\tint nrMappings;\n>> +\t\t\tLOG(RkISP1, Debug)\n>> +\t\t\t\t<< \"Getting pipeline converter filename as \" << std::string(configFromEnv);\n>> +\t\t\tnrMappings = converter_->loadConfiguration(std::string(configFromEnv));\n>> +\t\t\tif (nrMappings < 0) {\n>> +\t\t\t\tLOG(RkISP1, Error)\n>> +\t\t\t\t\t<< \"Error while reading converter configuration file\";\n>> +\t\t\t} else {\n>> +\t\t\t\tLOG(RkISP1, Debug)\n>> +\t\t\t\t\t<< nrMappings << \" mapping(s) loaded\";\n>> +\t\t\t\tif (nrMappings > 0)\n>> +\t\t\t\t\t/* We want to force the converter use to apply the mapping */\n>> +\t\t\t\t\tuseConverter_ = true;\n>> +\t\t\t}\n>> +\t\t}\n>> +\t\tconverter_->inputBufferReady.connect(this, &RkISP1Path::pathConverterInputDone);\n>> +\t\tconverter_->outputBufferReady.connect(this, &RkISP1Path::pathConverterOutputDone);\n>> +\t\thasConverter_ = true;\n>> +\t}\n>> +}\n>> +\n>> +bool RkISP1Path::init(MediaDevice *media, MediaDevice *mediaConverter)\n>>   {\n>>   \tstd::string resizer = std::string(\"rkisp1_resizer_\") + name_ + \"path\";\n>>   \tstd::string video = std::string(\"rkisp1_\") + name_ + \"path\";\n>> @@ -45,10 +85,13 @@ bool RkISP1Path::init(MediaDevice *media)\n>>   \tif (!link_)\n>>   \t\treturn false;\n>>   \n>> +\tinitConverter(mediaConverter);\n>> +\n>> +\tvideo_->bufferReady.connect(this, &RkISP1Path::pathBufferReady);\n>> +\n>>   \treturn true;\n>>   }\n>> -\n>> -StreamConfiguration RkISP1Path::generateConfiguration(const Size &resolution)\n>> +StreamConfiguration RkISP1Path::generateNativeConfiguration(const Size &resolution)\n>>   {\n>>   \tSize maxResolution = maxResolution_.boundedToAspectRatio(resolution)\n>>   \t\t\t\t\t   .boundedTo(resolution);\n>> @@ -67,7 +110,165 @@ StreamConfiguration RkISP1Path::generateConfiguration(const Size &resolution)\n>>   \treturn cfg;\n>>   }\n>>   \n>> -CameraConfiguration::Status RkISP1Path::validate(StreamConfiguration *cfg)\n>> +StreamConfiguration RkISP1Path::generateConfiguration(const Size &resolution)\n>> +{\n>> +\tStreamConfiguration cfg = generateNativeConfiguration(resolution);\n>> +\tStreamFormats formats = cfg.formats();\n>> +\tstd::map<PixelFormat, std::vector<SizeRange>> fullFormats;\n>> +\n>> +\tfor (const PixelFormat &fmt : formats.pixelformats()) {\n>> +\t\tConfiguration config;\n>> +\t\tSizeRange szRange;\n>> +\n>> +\t\tconfig.inputFormat = fmt;\n>> +\t\tconfig.inputSizes = formats.range(fmt);\n>> +\n>> +\t\tif (!useConverter_) {\n>> +\t\t\tconfig.outputFormats.push_back(fmt);\n>> +\t\t\tconfig.outputSizes = config.inputSizes;\n>> +\t\t\tconfig.withConverter = false;\n>> +\t\t\tconfigs_.push_back(config);\n>> +\t\t\tLOG(RkISP1, Debug)\n>> +\t\t\t\t<< \"Pushing native format \" << fmt\n>> +\t\t\t\t<< \" resolution range \" << config.inputSizes;\n>> +\t\t\tfullFormats[fmt].push_back(config.inputSizes);\n>> +\t\t}\n>> +\n>> +\t\tif (hasConverter_) {\n>> +\t\t\tconfig.outputFormats = converter_->formats(config.inputFormat);\n>> +\t\t\tif (config.outputFormats.empty())\n>> +\t\t\t\tcontinue;\n>> +\t\t\tszRange.max = converter_->sizes(config.inputSizes.max).max;\n>> +\t\t\tszRange.min = converter_->sizes(config.inputSizes.min).min;\n>> +\t\t\tconfig.outputSizes = szRange;\n>> +\t\t\tconfig.withConverter = true;\n>> +\t\t\tconfigs_.push_back(config);\n>> +\t\t\tfor (auto &_fmt : config.outputFormats) {\n>> +\t\t\t\tfullFormats[_fmt].push_back(config.outputSizes);\n>> +\t\t\t\tLOG(RkISP1, Debug)\n>> +\t\t\t\t\t<< \"Pushing converted format \" << _fmt\n>> +\t\t\t\t\t<< \" resolution range \" << config.outputSizes;\n>> +\t\t\t}\n>> +\t\t}\n>> +\t}\n>> +\t/*\n>> +\t * Sort the sizes and merge any consecutive overlapping ranges.\n>> +\t * Imported from src/libcamera/pipeline/simple.cpp\n>> +\t *\n>> +\t * TODO: Apply policy of converter use or not.. We likely want to force\n>> +\t * the converter use in case there is a mapping defined in the\n>> +\t * configuration file if any... if so, shall we filter out resolution\n>> +\t * not defined in the configuration file.. or should this policy be let\n>> +\t * to the application\n>> +\t * */\n>> +\n>> +\tfor (auto &[format, sizes] : fullFormats) {\n>> +\t\tstd::sort(sizes.begin(), sizes.end(),\n>> +\t\t\t  [](SizeRange &a, SizeRange &b) {\n>> +\t\t\t\t  return a.min < b.min;\n>> +\t\t\t  });\n>> +\n>> +\t\tauto cur = sizes.begin();\n>> +\t\tauto next = cur;\n>> +\n>> +\t\twhile (++next != sizes.end()) {\n>> +\t\t\tif (cur->max.width >= next->min.width &&\n>> +\t\t\t    cur->max.height >= next->min.height)\n>> +\t\t\t\tcur->max = next->max;\n>> +\t\t\telse if (++cur != next)\n>> +\t\t\t\t*cur = *next;\n>> +\t\t}\n>> +\n>> +\t\tsizes.erase(++cur, sizes.end());\n>> +\t}\n>> +\n>> +\tStreamConfiguration fullCfg{ StreamFormats{ fullFormats } };\n>> +\tfullCfg.pixelFormat = fullFormats.begin()->first;\n>> +\tfullCfg.size = fullFormats.begin()->second[0].max;\n>> +\tfullCfg.bufferCount = cfg.bufferCount;\n>> +\n>> +\treturn fullCfg;\n>> +}\n>> +\n>> +CameraConfiguration::Status RkISP1Path::getPipeConfiguration(\n>> +\tStreamConfiguration &streamCfg,\n>> +\tconst RkISP1Path::Configuration **cfg) const\n>> +{\n>> +\tCameraConfiguration::Status _status = CameraConfiguration::Adjusted;\n>> +\n>> +\tLOG(RkISP1, Debug)\n>> +\t\t<< \"Looking for \"\n>> +\t\t<< streamCfg.pixelFormat << \"/\" << streamCfg.size\n>> +\t\t<< \" output configuration\";\n>> +\t/*\n>> +\t * Select the fallback configuration\n>> +\t */\n>> +\tfor (auto &_cfg : configs_) {\n>> +\t\tif (_cfg.withConverter == useConverter_) {\n>> +\t\t\t*cfg = &_cfg;\n>> +\t\t\tbreak;\n>> +\t\t}\n>> +\t}\n>> +\n>> +\tPixelFormat pixelFormat = (*cfg)->outputFormats.front();\n>> +\tSize size = streamCfg.size;\n>> +\tsize.boundTo((*cfg)->outputSizes.max);\n>> +\tsize.expandTo((*cfg)->outputSizes.min);\n>> +\n>> +\t/* Unless the converter use is enforced through a loaded configuration,\n>> +\t * prefer a configuration without the converter if possible\n>> +\t */\n>> +\tstd::vector<bool> converterUse;\n>> +\tif (!useConverter_)\n>> +\t\tconverterUse.push_back(false);\n>> +\tif (hasConverter_)\n>> +\t\tconverterUse.push_back(true);\n>> +\n>> +\tfor (auto withConverter : converterUse) {\n>> +\t\tfor (auto &_cfg : configs_) {\n>> +\t\t\tauto &outFmts = _cfg.outputFormats;\n>> +\t\t\tif (withConverter != _cfg.withConverter)\n>> +\t\t\t\tcontinue;\n>> +\n>> +\t\t\tif ((std::find(outFmts.begin(),\n>> +\t\t\t\t       outFmts.end(),\n>> +\t\t\t\t       streamCfg.pixelFormat)) == outFmts.end())\n>> +\t\t\t\tcontinue;\n>> +\n>> +\t\t\t*cfg = &_cfg;\n>> +\t\t\tpixelFormat = streamCfg.pixelFormat;\n>> +\n>> +\t\t\tif (_cfg.outputSizes.contains(streamCfg.size)) {\n>> +\t\t\t\t_status = CameraConfiguration::Valid;\n>> +\t\t\t\tgoto done;\n>> +\t\t\t}\n>> +\n>> +\t\t\tsize.boundTo((*cfg)->outputSizes.max);\n>> +\t\t\tsize.expandTo((*cfg)->outputSizes.min);\n>> +\t\t}\n>> +\t}\n>> +\n>> +\tstreamCfg.pixelFormat = pixelFormat;\n>> +\tstreamCfg.size = size;\n>> +\n>> +done:\n>> +\tif ((*cfg)->withConverter) {\n>> +\t\tstd::tie(streamCfg.stride, streamCfg.frameSize) =\n>> +\t\t\tconverter_->strideAndFrameSize(streamCfg.pixelFormat,\n>> +\t\t\t\t\t\t       streamCfg.size);\n>> +\t\tif (streamCfg.stride == 0)\n>> +\t\t\treturn CameraConfiguration::Invalid;\n>> +\t}\n>> +\n>> +\tLOG(RkISP1, Debug)\n>> +\t\t<< \"Chosen configuration: \"\n>> +\t\t<< (*cfg)->inputFormat << \"/\" << (*cfg)->inputSizes\n>> +\t\t<< \" --> \" << streamCfg.pixelFormat << \"/\" << streamCfg.size\n>> +\t\t<< ((*cfg)->withConverter ? \" with\" : \" without\") << \" converter\";\n>> +\treturn _status;\n>> +}\n>> +\n>> +CameraConfiguration::Status RkISP1Path::nativeValidate(StreamConfiguration *cfg)\n>>   {\n>>   \tconst StreamConfiguration reqCfg = *cfg;\n>>   \tCameraConfiguration::Status status = CameraConfiguration::Valid;\n>> @@ -101,7 +302,39 @@ CameraConfiguration::Status RkISP1Path::validate(StreamConfiguration *cfg)\n>>   \treturn status;\n>>   }\n>>   \n>> -int RkISP1Path::configure(const StreamConfiguration &config,\n>> +CameraConfiguration::Status RkISP1Path::validate(StreamConfiguration *cfg)\n>> +{\n>> +\tStreamConfiguration tryCfg = *cfg;\n>> +\tStreamConfiguration internalCfg;\n>> +\tconst Configuration *pipeCfg;\n>> +\tCameraConfiguration::Status pipeStatus;\n>> +\n>> +\tpipeStatus = getPipeConfiguration(tryCfg, &pipeCfg);\n>> +\tif (pipeStatus == CameraConfiguration::Invalid)\n>> +\t\treturn CameraConfiguration::Invalid;\n>> +\n>> +\tif (!pipeCfg->withConverter) {\n>> +\t\ttryCfg = *cfg;\n>> +\t\tpipeStatus = nativeValidate(&tryCfg);\n>> +\t\tif (pipeStatus == CameraConfiguration::Invalid)\n>> +\t\t\treturn CameraConfiguration::Invalid;\n>> +\t\tinternalCfg = tryCfg;\n>> +\t} else {\n>> +\t\tinternalCfg = tryCfg;\n>> +\t\tinternalCfg.pixelFormat = pipeCfg->inputFormat;\n>> +\t\tauto _status = nativeValidate(&internalCfg);\n>> +\t\tif (_status == CameraConfiguration::Invalid)\n>> +\t\t\treturn CameraConfiguration::Invalid;\n>> +\t}\n>> +\n>> +\t*cfg = tryCfg;\n>> +\tinternalStream_ = internalCfg;\n>> +\tpipeConfig_ = pipeCfg;\n>> +\n>> +\treturn pipeStatus;\n>> +}\n>> +\n>> +int RkISP1Path::nativeConfigure(const StreamConfiguration &config,\n>>   \t\t\t  const V4L2SubdeviceFormat &inputFormat)\n>>   {\n>>   \tint ret;\n>> @@ -165,6 +398,117 @@ int RkISP1Path::configure(const StreamConfiguration &config,\n>>   \treturn 0;\n>>   }\n>>   \n>> +int RkISP1Path::configure(const StreamConfiguration &config,\n>> +\t\t\t  const V4L2SubdeviceFormat &inputFormat)\n>> +{\n>> +\tint ret;\n>> +\tLOG(RkISP1, Debug)\n>> +\t\t<< \"Configuring \" << name_ << \" path \"\n>> +\t\t<< internalStream_.pixelFormat << \"/\" << internalStream_.size\n>> +\t\t<< \" --> \" << config.pixelFormat << \"/\" << config.size\n>> +\t\t<< (pipeConfig_->withConverter ? \" with\" : \" without\") << \" converter\";\n>> +\n>> +\tret = nativeConfigure(internalStream_, inputFormat);\n>> +\tif (ret)\n>> +\t\treturn ret;\n>> +\n>> +\tif (pipeConfig_->withConverter) {\n>> +\t\tStreamConfiguration cfg = config;\n>> +\t\tstd::vector<std::reference_wrapper<StreamConfiguration>> outputCfgs;\n>> +\t\toutputCfgs.push_back(cfg);\n>> +\t\tret = converter_->configure(internalStream_, outputCfgs);\n>> +\t}\n>> +\n>> +\treturn ret;\n>> +}\n>> +\n>> +int RkISP1Path::exportBuffers(unsigned int bufferCount,\n>> +\t\t\t      std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n>> +{\n>> +\tif (pipeConfig_->withConverter)\n>> +\t\treturn converter_->exportBuffers(0, bufferCount, buffers);\n>> +\telse\n>> +\t\treturn video_->exportBuffers(bufferCount, buffers);\n>> +}\n>> +\n>> +int RkISP1Path::allocateBuffers(unsigned int bufferCount)\n>> +{\n>> +\tif (pipeConfig_->withConverter) {\n>> +\t\tint ret = video_->allocateBuffers(bufferCount, &converterBuffers_);\n>> +\t\tif (ret < 0)\n>> +\t\t\treturn ret;\n>> +\t\tif ((unsigned int)ret != bufferCount)\n>> +\t\t\tLOG(RkISP1, Warning)\n>> +\t\t\t\t<< \"Requested \" << bufferCount << \" but got \" << ret << \" buffers\";\n>> +\n>> +\t\tfor (std::unique_ptr<FrameBuffer> &buffer : converterBuffers_)\n>> +\t\t\tavailableConverterBuffers_.push(buffer.get());\n>> +\t} else {\n>> +\t\treturn video_->importBuffers(bufferCount);\n>> +\t}\n>> +\n>> +\treturn 0;\n>> +}\n>> +\n>> +void RkISP1Path::releaseBuffers()\n>> +{\n>> +\twhile (!availableConverterBuffers_.empty())\n>> +\t\tavailableConverterBuffers_.pop();\n>> +\n>> +\tconverterBuffers_.clear();\n>> +\n>> +\tvideo_->releaseBuffers();\n>> +}\n>> +\n>> +void RkISP1Path::pathBufferReady(FrameBuffer *buffer)\n>> +{\n>> +\tRequest *request = buffer->request();\n>> +\n>> +\tif (request) {\n>> +\t\tinternalBufferReady_.emit(buffer);\n>> +\t} else {\n>> +\t\tauto iter = converterBuffersMapping_.find(buffer);\n>> +\t\tif (iter != converterBuffersMapping_.end()) {\n>> +\t\t\tconverter_->queueBuffer(buffer, iter->second);\n>> +\t\t\tconverterBuffersMapping_.erase(iter);\n>> +\t\t} else {\n>> +\t\t\tLOG(RkISP1, Error)\n>> +\t\t\t\t<< \"Final buffer associated to converted buffer not found on \"\n>> +\t\t\t\t<< name_ << \" path\";\n>> +\t\t}\n>> +\t}\n>> +}\n>> +\n>> +void RkISP1Path::pathConverterInputDone(FrameBuffer *buffer)\n>> +{\n>> +\tavailableConverterBuffers_.push(buffer);\n>> +}\n>> +\n>> +void RkISP1Path::pathConverterOutputDone(FrameBuffer *buffer)\n>> +{\n>> +\tinternalBufferReady_.emit(buffer);\n>> +}\n>> +\n>> +int RkISP1Path::queueBuffer(FrameBuffer *buffer)\n>> +{\n>> +\tFrameBuffer *_buffer;\n>> +\n>> +\tif (pipeConfig_->withConverter) {\n>> +\t\tif (availableConverterBuffers_.empty()) {\n>> +\t\t\tLOG(RkISP1, Error)\n>> +\t\t\t\t<< \"converter buffer underrun on \" << name_ << \" path\";\n>> +\t\t\treturn -ENOMEM;\n>> +\t\t}\n>> +\t\t_buffer = availableConverterBuffers_.front();\n>> +\t\tavailableConverterBuffers_.pop();\n>> +\t\tconverterBuffersMapping_[_buffer] = buffer;\n>> +\t} else {\n>> +\t\t_buffer = buffer;\n>> +\t}\n>> +\n>> +\treturn video_->queueBuffer(_buffer);\n>> +}\n>> +\n>>   int RkISP1Path::start()\n>>   {\n>>   \tint ret;\n>> @@ -172,20 +516,23 @@ int RkISP1Path::start()\n>>   \tif (running_)\n>>   \t\treturn -EBUSY;\n>>   \n>> -\t/* \\todo Make buffer count user configurable. */\n>> -\tret = video_->importBuffers(RKISP1_BUFFER_COUNT);\n>> -\tif (ret)\n>> -\t\treturn ret;\n>> -\n>>   \tret = video_->streamOn();\n>>   \tif (ret) {\n>>   \t\tLOG(RkISP1, Error)\n>>   \t\t\t<< \"Failed to start \" << name_ << \" path\";\n>> -\n>> -\t\tvideo_->releaseBuffers();\n>>   \t\treturn ret;\n>>   \t}\n>>   \n>> +\tif (pipeConfig_->withConverter) {\n>> +\t\tret = converter_->start();\n>> +\t\tif (ret) {\n>> +\t\t\tLOG(RkISP1, Error)\n>> +\t\t\t\t<< \"Failed to start converter on \" << name_ << \" path\";\n>> +\t\t\tstop();\n>> +\t\t\treturn ret;\n>> +\t\t}\n>> +\t}\n>> +\n>>   \trunning_ = true;\n>>   \n>>   \treturn 0;\n>> @@ -199,7 +546,8 @@ void RkISP1Path::stop()\n>>   \tif (video_->streamOff())\n>>   \t\tLOG(RkISP1, Warning) << \"Failed to stop \" << name_ << \" path\";\n>>   \n>> -\tvideo_->releaseBuffers();\n>> +\tif (pipeConfig_->withConverter)\n>> +\t\tconverter_->stop();\n>>   \n>>   \trunning_ = false;\n>>   }\n>> diff --git a/src/libcamera/pipeline/rkisp1/rkisp1_path.h b/src/libcamera/pipeline/rkisp1/rkisp1_path.h\n>> index f3f1ae39..0da3594f 100644\n>> --- a/src/libcamera/pipeline/rkisp1/rkisp1_path.h\n>> +++ b/src/libcamera/pipeline/rkisp1/rkisp1_path.h\n>> @@ -1,6 +1,7 @@\n>>   /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>>   /*\n>>    * Copyright (C) 2020, Google Inc.\n>> + * Copyright 2022 NXP\n>>    *\n>>    * rkisp1path.h - Rockchip ISP1 path helper\n>>    */\n>> @@ -8,6 +9,7 @@\n>>   #pragma once\n>>   \n>>   #include <memory>\n>> +#include <queue>\n>>   #include <vector>\n>>   \n>>   #include <libcamera/base/signal.h>\n>> @@ -17,9 +19,11 @@\n>>   #include <libcamera/geometry.h>\n>>   #include <libcamera/pixel_format.h>\n>>   \n>> +#include \"libcamera/internal/converter.h\"\n>>   #include \"libcamera/internal/media_object.h\"\n>>   #include \"libcamera/internal/v4l2_videodevice.h\"\n>>   \n>> +\n>>   namespace libcamera {\n>>   \n>>   class MediaDevice;\n>> @@ -33,7 +37,7 @@ public:\n>>   \tRkISP1Path(const char *name, const Span<const PixelFormat> &formats,\n>>   \t\t   const Size &minResolution, const Size &maxResolution);\n>>   \n>> -\tbool init(MediaDevice *media);\n>> +\tbool init(MediaDevice *media, MediaDevice *mediaConverter = nullptr);\n>>   \n>>   \tint setEnabled(bool enable) { return link_->setEnabled(enable); }\n>>   \tbool isEnabled() const { return link_->flags() & MEDIA_LNK_FL_ENABLED; }\n>> @@ -45,18 +49,31 @@ public:\n>>   \t\t      const V4L2SubdeviceFormat &inputFormat);\n>>   \n>>   \tint exportBuffers(unsigned int bufferCount,\n>> -\t\t\t  std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n>> -\t{\n>> -\t\treturn video_->exportBuffers(bufferCount, buffers);\n>> -\t}\n>> +\t\t\t  std::vector<std::unique_ptr<FrameBuffer>> *buffers);\n>> +\n>> +\tint allocateBuffers(unsigned int bufferCount);\n>> +\tvoid releaseBuffers();\n>>   \n>>   \tint start();\n>>   \tvoid stop();\n>>   \n>> -\tint queueBuffer(FrameBuffer *buffer) { return video_->queueBuffer(buffer); }\n>> -\tSignal<FrameBuffer *> &bufferReady() { return video_->bufferReady; }\n>> +\tint queueBuffer(FrameBuffer *buffer);\n>> +\tSignal<FrameBuffer *> &bufferReady() { return internalBufferReady_; }\n>> +\n>> +\tStreamConfiguration internalStream() const\n>> +\t{\n>> +\t\treturn internalStream_;\n>> +\t}\n>>   \n>>   private:\n>> +\tstruct Configuration {\n>> +\t\tSizeRange inputSizes;\n>> +\t\tPixelFormat inputFormat;\n>> +\t\tstd::vector<PixelFormat> outputFormats;\n>> +\t\tSizeRange outputSizes;\n>> +\t\tbool withConverter;\n>> +\t};\n>> +\n>>   \tstatic constexpr unsigned int RKISP1_BUFFER_COUNT = 4;\n>>   \n>>   \tconst char *name_;\n>> @@ -65,10 +82,33 @@ private:\n>>   \tconst Span<const PixelFormat> formats_;\n>>   \tconst Size minResolution_;\n>>   \tconst Size maxResolution_;\n>> +\tCameraConfiguration::Status getPipeConfiguration(\n>> +\t\tStreamConfiguration &streamCfg,\n>> +\t\tconst RkISP1Path::Configuration **cfg) const;\n>> +\n>> +\tStreamConfiguration generateNativeConfiguration(const Size &resolution);\n>> +\tCameraConfiguration::Status nativeValidate(StreamConfiguration *cfg);\n>> +\tint nativeConfigure(const StreamConfiguration &config,\n>> +\t\t\t    const V4L2SubdeviceFormat &inputFormat);\n>>   \n>>   \tstd::unique_ptr<V4L2Subdevice> resizer_;\n>>   \tstd::unique_ptr<V4L2VideoDevice> video_;\n>>   \tMediaLink *link_;\n>> +\n>> +\tvoid initConverter(MediaDevice *mediaConverter);\n>> +\tstd::unique_ptr<Converter> converter_;\n>> +\tstd::vector<Configuration> configs_;\n>> +\tStreamConfiguration internalStream_;\n>> +\tconst Configuration *pipeConfig_;\n>> +\tbool hasConverter_;\n>> +\tbool useConverter_;\n>> +\tSignal<FrameBuffer *> internalBufferReady_;\n>> +\tstd::vector<std::unique_ptr<FrameBuffer>> converterBuffers_;\n>> +\tstd::queue<FrameBuffer *> availableConverterBuffers_;\n>> +\tstd::map<FrameBuffer *, FrameBuffer *> converterBuffersMapping_;\n>> +\tvoid pathBufferReady(FrameBuffer *buffer);\n>> +\tvoid pathConverterInputDone(FrameBuffer *buffer);\n>> +\tvoid pathConverterOutputDone(FrameBuffer *buffer);\n>>   };\n>>   \n>>   class RkISP1MainPath : public RkISP1Path\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 1E436C0DA4\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri,  7 Oct 2022 14:27:11 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 345A762D19;\n\tFri,  7 Oct 2022 16:27:10 +0200 (CEST)","from EUR05-AM6-obe.outbound.protection.outlook.com\n\t(mail-am6eur05on2043.outbound.protection.outlook.com [40.107.22.43])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 92F3B60A88\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri,  7 Oct 2022 16:27:08 +0200 (CEST)","from PAXPR04MB8703.eurprd04.prod.outlook.com\n\t(2603:10a6:102:21e::22)\n\tby AM9PR04MB8715.eurprd04.prod.outlook.com (2603:10a6:20b:43e::11)\n\twith Microsoft SMTP Server (version=TLS1_2,\n\tcipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.5676.32;\n\tFri, 7 Oct 2022 14:27:05 +0000","from PAXPR04MB8703.eurprd04.prod.outlook.com\n\t([fe80::4f72:a35a:8c60:63f1]) by\n\tPAXPR04MB8703.eurprd04.prod.outlook.com\n\t([fe80::4f72:a35a:8c60:63f1%6]) with mapi id 15.20.5676.034;\n\tFri, 7 Oct 2022 14:27:05 +0000"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1665152830;\n\tbh=UdxzS/Vb1Jxr23SUHTfToGe7NbMw1hCT/hMb8Ct5AJk=;\n\th=Date:To:References:In-Reply-To:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=mY0Dcv7zeFPQwVdVGfPNAgTV+uR8G6kOsmk+S1qDCa3X4R3OXS3exC+oB3BZCXvVA\n\tFsmD2xWPT9+6zorX/4xcGpecJ0cWPGwHJnz8M3bIBbsQnpzt7YMbm9BU7hQfJyDHFI\n\trmbZDxT5kFBCcZgwJtD0FlgTwyPhw6O1Gy52HMThud1ozqG7x+iv16YJVd9X6G3JzX\n\t5H29LdGqJVwVZVGJ02aEQlBhxG6JZtAOu9aKyor1SnmoZ0ZFFPwYF+iZKSUP2kGKBe\n\tIVaupC/EHuz0Z8bIQ0PGGOC13cB7h6um1/wrmHls/H5J6CYksJl1ghiwY6L/egrfg5\n\tDV1Hfq5uPGaIg==","v=1; a=rsa-sha256; c=relaxed/relaxed; d=NXP1.onmicrosoft.com;\n\ts=selector2-NXP1-onmicrosoft-com;\n\th=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck;\n\tbh=H0DLhhCcyzg12nYw3YV292kGgDEmNtoPRLUNTO1zeZs=;\n\tb=LO6h0fV8wPhVCRHWwnGAZFvAuxcKJrVLblyvNQGtXyuWI4MepPIL7HTJc5FwI57JDM6PQdYxZgD57O03WwMhwFMh9MLwStRptXGgafJg/qg8h+2DF8v/pxBDrbULPPgtKH1KWr71vrsJMrTQC5p8d7/TSfQ3R9J0mKf5t+UsbQw="],"Authentication-Results":["lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=NXP1.onmicrosoft.com\n\theader.i=@NXP1.onmicrosoft.com\n\theader.b=\"LO6h0fV8\"; dkim-atps=neutral","dkim=none (message not signed)\n\theader.d=none;dmarc=none action=none header.from=oss.nxp.com;"],"ARC-Seal":"i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none;\n\tb=RAqMUtGdY7UeUGb1cQzrgvLTm2HbNiaFQ7sBZxdipTPrXxE0h+uxQpa3FwQjE1+BBtgrqJlgQKFRGDqSh2G/cJAMSHGie+ES07QUkgSl0cCM0sx+K6BQD/U9IErxyxT9NecCfqjaxIDKwwhTLw27L8HPuxUPPl8BfpsDih14+poUThZawY0nm5fnCtdQKICLaNu30Dav8Qho4TjVyn+3dOnnX5BNYfoapmiQv/fi2AJNjoh9uyp2WVLDAAZOU7vgJU+NBQD+iYZhGb1F09E4kvRI/cqOvVYjHkihmUgQApWjKbcLm23QvNiM+I+whQU491NfpIf4jrRduWmIFhEfEQ==","ARC-Message-Signature":"i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com;\n\ts=arcselector9901;\n\th=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1;\n\tbh=H0DLhhCcyzg12nYw3YV292kGgDEmNtoPRLUNTO1zeZs=;\n\tb=lPFtw/y3ggljNQzOlb1rzzHOLFdPsw9MtlQ4pkYag8SteoQMtIb6nTigr1fArq/WLJJbyzXs1lqG80JX//PU25zoMEzsmBK6C2hmTYitCDWMqNVJ4QJDv/B+ClLTNAu1I5H2IrTjhNiN0lMbK1d3fqqF4R4HYMLXECs/4JmxAnBmDoAPnXE2s3e+rDhYwfGMlBylZtxAUOTl4JN+nhMCPDCdpbySP8KjujvpuWYyzwdlRrVxWV3xjskZ20JmQ09kbxU6doEMm0IlplYc/7PpqxqF/5ZUiH3ooPvpMgtuHxLD1Ei5DchbEB7e+0MG1xjjaE/30j6XiUz9mzT30CurGw==","ARC-Authentication-Results":"i=1; mx.microsoft.com 1; spf=pass\n\tsmtp.mailfrom=oss.nxp.com;\n\tdmarc=pass action=none header.from=oss.nxp.com; \n\tdkim=pass header.d=oss.nxp.com; arc=none","Message-ID":"<ec7e30be-06b8-590b-8640-856ee5d868d0@oss.nxp.com>","Date":"Fri, 7 Oct 2022 16:27:04 +0200","User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101\n\tThunderbird/102.3.1","Content-Language":"en-US, fr","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","References":"<20220908184850.1874303-1-xavier.roumegue@oss.nxp.com>\n\t<20220908184850.1874303-15-xavier.roumegue@oss.nxp.com>\n\t<YzucGM4lebLeIaEo@pendragon.ideasonboard.com>","In-Reply-To":"<YzucGM4lebLeIaEo@pendragon.ideasonboard.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"7bit","X-ClientProxiedBy":"PR0P264CA0135.FRAP264.PROD.OUTLOOK.COM\n\t(2603:10a6:100:1a::27) To PAXPR04MB8703.eurprd04.prod.outlook.com\n\t(2603:10a6:102:21e::22)","MIME-Version":"1.0","X-MS-Exchange-MessageSentRepresentingType":"1","X-MS-PublicTrafficType":"Email","X-MS-TrafficTypeDiagnostic":"PAXPR04MB8703:EE_|AM9PR04MB8715:EE_","X-MS-Office365-Filtering-Correlation-Id":"1eff8611-b004-47e9-a400-08daa8700259","X-MS-Exchange-SharedMailbox-RoutingAgent-Processed":"True","X-MS-Exchange-SenderADCheck":"1","X-MS-Exchange-AntiSpam-Relay":"0","X-Microsoft-Antispam":"BCL:0;","X-Microsoft-Antispam-Message-Info":"gyyDxN1Fni+srmbqfG0W/STwlFrA+QX1mioVFF5G0JLOgmMqRRRwTNx7vMJsyMxZmpEuf/gv2pAiRgEJBMbOxkFIufiz8tOipOk0dt0hLAlG7UpO6Zdpw3UrQws5Xzgk7Y3KwGNYz1KRaU1brMmLimgvwBnVr36OZgDmcgtUbzVKyd2Nhkg8Z0OsbRVUS8XhfKZEOphMfs5lzAO12LtaeLbHq70y78hnD6JSvxSiBQo0VsxAUJF9IXo+D/45+WYRNnQrK5VhxgWo5Xkd0JsFXbGo9fXhHHkyjlzaTHXYFb0qvx5UPLMqK20CRg7yWsAOvKCuQRnyJBRr+sIFqN8L55dgoInFLPj9qOWhuly/SBYjZafKItgZinFcIqR2EO7rb+nCjbeCb/siKrNpfSrNk1SFhx8feaujD1tWBJ5+R6uRq/W36Nx00txEBujuhPWtCkolAnS2WR07N4S+NE/tcFu58UCeqy7LsmveEKG0qPh9YohCc0DEhVc/qaRE6hwpDqTbXjJFdm/aViybpbLAE+Nf3WwFH7/Y5oGYhzE5lKaMs9vSgTTXvQCETzKKH9OM76nDQrWaLXaBF81ZuKow5XD2fQm0gb2nsxlMeIYxoZ8zs6zqystqFSPuPg0hWahlJfBtDuEzFjdq7gc/rgM+6aMnE87l2ajLTYQM38KzC589lDQFaOolexIPTQMZcUpLh55cPuDNQTxFm9EWjaj0bz8S4TJED/lPF8qHmsm0Zt59b3RevMqJQM0jsMMe5f/q92TuXhu6G6RGwqF947ku43s0PJ65dzfiEbeqnS49BejckiFmrJkv/LjLKmSHtR1KIOeRktRD8r2SCWASYJbOVQ==","X-Forefront-Antispam-Report":"CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:;\n\tIPV:NLI; SFV:NSPM; H:PAXPR04MB8703.eurprd04.prod.outlook.com; PTR:;\n\tCAT:NONE; \n\tSFS:(13230022)(4636009)(376002)(396003)(366004)(346002)(39860400002)(136003)(451199015)(53546011)(31696002)(86362001)(31686004)(6916009)(38100700002)(478600001)(316002)(30864003)(2906002)(2616005)(5660300002)(41300700001)(6512007)(52116002)(6506007)(4326008)(83380400001)(6486002)(186003)(8936002)(66476007)(66946007)(8676002)(66556008)(43740500002)(45980500001)(309714004);\n\tDIR:OUT; SFP:1101; ","X-MS-Exchange-AntiSpam-MessageData-ChunkCount":"1","X-MS-Exchange-AntiSpam-MessageData-0":"=?utf-8?q?/nvxzHx5LAHl3yOYKkbuToIeF?=\n\t=?utf-8?q?D1eeywjPr7Nml9tnXJU3e15uXZ8fnxw1LK5NzPmxWcXpS2/K1rKsusS0?=\n\t=?utf-8?q?3wJNV1GsJRFcIUXV/w+Pl/oh+Gap05TwAgTlESNjzhSkA41N7QS5tnI5?=\n\t=?utf-8?q?QHUr2mxjDwEYKFj/YXnBQ2SfZ89IIG8p+Oh6IfEhnkST6DznRGrrxLfj?=\n\t=?utf-8?q?BwgjaWqTIDXQG0noJuRv7VDJh+VS7MBGzjAC8+Ka/K04eZ5dVGJVGpVS?=\n\t=?utf-8?q?fQHklY9HHh0SFfVgQzy3dSvzKxduicQ8jy57GdzqcKMYjkkd5fl6DtI1?=\n\t=?utf-8?q?FPxsqn2gTTIYb0wkFIQokrmX/b14ao3Q3rSE4uYu3rw7NbQICRDAsNqw?=\n\t=?utf-8?q?bEGHIGMAv/XyS7bFOcAgMMGRaqfEoWljMm/Dq58MkAyS+bkoc+STU0xf?=\n\t=?utf-8?q?Pvpdv9v1vyBWjeLEZI8UQu3mBrATkhH8IKFm626IhuFEySdCldbMNzmm?=\n\t=?utf-8?q?Y/QEc1NE6yghlJeGs6jqCYptkr9COZjUIGw/5kKRtxJPLpck2Ts3WhKq?=\n\t=?utf-8?q?svmKjkQi5d687HXG7OlK9fDrse0a2X+HQ6feSpKlS2Gfsd9QtQuZaGfD?=\n\t=?utf-8?q?3xoToTFKo9ov9vKLJSHm0ZMs91me343Z+Fk4E+Z7rkV+C/rQyNHRdY9S?=\n\t=?utf-8?q?gKr5OCObW5irnqb/3cGBNULJ+xmZFol6AGQKSHczVqXy46rxAL44KpJX?=\n\t=?utf-8?q?2zGsGdVF3AHQDCy/G1GayFAF+uSwy0u1Dt6hyPhFh/n+IpENjsf3Vimn?=\n\t=?utf-8?q?0k6TzGLfQLzt2I6nEnGInQEVKsHqS+JvY0pPhN64v1Ww6HdZ04HK1X61?=\n\t=?utf-8?q?YA8/samSVGarJoOUh4dtEY+ZqnetfOi+uXMEsEsINqdz66dBCNMybt5B?=\n\t=?utf-8?q?HKIh0ErbwQHUR8vc/fOWi4mDncu8iXyRLAIhKs2YKubDzVbqs9yuu5Ba?=\n\t=?utf-8?q?Xx2I65fO85tdJAA4VY8V71ImJU4XaPmg5wXyAa+9slR/8iMYO5PmwXpU?=\n\t=?utf-8?q?F6zbJ/xYnwYBpLy6byw0ll6yfwrTG763CsWx+sDq9eUWjZgCPch/2qKh?=\n\t=?utf-8?q?zNGy09ohi7Xb9gw9gGxFTqfvjQiZTHKxT5bF8H3QGBbQp/lMR/MRHOUA?=\n\t=?utf-8?q?LF64o/Uz89RXH4TB7tbcq7cW/QOaZkQIHQYoLzM7mU36ZnQbOrMJHr4G?=\n\t=?utf-8?q?bxneKtrnRkWvXSHOAKFgn+vefKZstp7P7UggtsJmGPCxdZJ3VLHUg8O0?=\n\t=?utf-8?q?/oijTTFNd0BGGAMmu4bpj+s1kYVlRs3izoe6KHxIiPKEpMehbXRBE+RI?=\n\t=?utf-8?q?vdbmFTWDKCrNWC/l88XZjCGkMxuth45EK29enr4/EpKdlDhbLREIdXtX?=\n\t=?utf-8?q?nMO1Lo3TfGQfYbJLkBMK7ZVVthOl56I9eTm/Fnhvd62D47XTYN90+k5+?=\n\t=?utf-8?q?rWHpbYCfDFawZsR1n2Jj+aL+aQJOq6CSm3KEm0CgW5NOSszGsARgZl/l?=\n\t=?utf-8?q?6hlhlHOhonOeIj1ZuqlCuzBNHUs43/ajpykdLc6c1BrTCfWnZIyREiMa?=\n\t=?utf-8?q?papVIUJVkTIHT0Sc34EB1YLyaYgrzet1pkCGXV8ORS70ZhU6keK93/je?=\n\t=?utf-8?q?iF/cRXwp1D588vTWF0aJpZBKsNHH/SWOmB9ZlLgKrUmvnXpQUr7fWcDl?=\n\t=?utf-8?q?ebPGjDoorCHBZDs4pG09/2PlNPc+tVIEaguFx8AtMos/ZPU10XKpYVgl?=\n\t=?utf-8?q?Z7HqqMmCcKP5C/FSP9NPh5ao5frAflB/KdnLw=3D=3D?=","X-OriginatorOrg":"oss.nxp.com","X-MS-Exchange-CrossTenant-Network-Message-Id":"1eff8611-b004-47e9-a400-08daa8700259","X-MS-Exchange-CrossTenant-AuthSource":"PAXPR04MB8703.eurprd04.prod.outlook.com","X-MS-Exchange-CrossTenant-AuthAs":"Internal","X-MS-Exchange-CrossTenant-OriginalArrivalTime":"07 Oct 2022 14:27:05.8234\n\t(UTC)","X-MS-Exchange-CrossTenant-FromEntityHeader":"Hosted","X-MS-Exchange-CrossTenant-Id":"686ea1d3-bc2b-4c6f-a92c-d99c5c301635","X-MS-Exchange-CrossTenant-MailboxType":"HOSTED","X-MS-Exchange-CrossTenant-UserPrincipalName":"5VNY/TYKMhaEq7d6EYUPOa6ylp1PPWKzC0qPEPF+za0DBOKvuEvGOyaXf9EMlVkvYTMD3+LHArBF15RJNit2dQ==","X-MS-Exchange-Transport-CrossTenantHeadersStamped":"AM9PR04MB8715","Subject":"Re: [libcamera-devel] [PATCH 14/14] libcamera: pipeline: rkisp1:\n\tAdd converter support","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","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>","From":"\"Xavier Roumegue \\(OSS\\) via libcamera-devel\"\n\t<libcamera-devel@lists.libcamera.org>","Reply-To":"\"Xavier Roumegue \\(OSS\\)\" <xavier.roumegue@oss.nxp.com>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]