[{"id":24989,"web_url":"https://patchwork.libcamera.org/comment/24989/","msgid":"<20220915094621.vtc5ffmdw6hjb3js@uno.localdomain>","date":"2022-09-15T09:46:21","subject":"Re: [libcamera-devel] [PATCH 06/14] libcamera: converter: Add v4l2\n\tm2m converter implementation","submitter":{"id":3,"url":"https://patchwork.libcamera.org/api/people/3/","name":"Jacopo Mondi","email":"jacopo@jmondi.org"},"content":"Hi Xavier\n\nOn Thu, Sep 08, 2022 at 08:48:42PM +0200, Xavier Roumegue via libcamera-devel wrote:\n> Introduce a converter implementation relying on a v4l2 m2m device, mostly\n> based on the current simple pipeline converter implementation.\n>\n> The main change is the introduction of Mapping object which can be\n> loaded through a configuration file which define vertices remapping\n> coordinates. Those latters can be applied by any classes derived from\n> this base class which define the apply_mapping() method.\n>\n\nI'll skip questions on things not clear to me about the converter\nimplementation as I understand this basically comes from the simple\npipeline converter implementation [*]\n\nI would then focus more on two general questions:\n1) Do we want to make a dir for converter/post-processors instead of\nplacing them in src/libcamera\n\n2) Is the format expected in the configuration file documented\nanywhere ?\n\n[*] Not true, I will ask a question anyway, see below :)\n\n> Signed-off-by: Xavier Roumegue <xavier.roumegue@oss.nxp.com>\n> ---\n>  .../libcamera/internal/converter_v4l2_m2m.h   | 120 +++++\n>  include/libcamera/internal/meson.build        |   1 +\n>  src/libcamera/converter_v4l2_m2m.cpp          | 504 ++++++++++++++++++\n>  src/libcamera/meson.build                     |   1 +\n>  4 files changed, 626 insertions(+)\n>  create mode 100644 include/libcamera/internal/converter_v4l2_m2m.h\n>  create mode 100644 src/libcamera/converter_v4l2_m2m.cpp\n>\n> diff --git a/include/libcamera/internal/converter_v4l2_m2m.h b/include/libcamera/internal/converter_v4l2_m2m.h\n> new file mode 100644\n> index 00000000..3667b128\n> --- /dev/null\n> +++ b/include/libcamera/internal/converter_v4l2_m2m.h\n> @@ -0,0 +1,120 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright 2022 NXP\n> + *\n> + * converter_v4l2_m2m.h - V4l2 M2M Format converter interface\n> + */\n> +\n> +#pragma once\n> +\n> +#include <functional>\n> +#include <map>\n> +#include <memory>\n> +#include <string>\n> +#include <tuple>\n> +#include <vector>\n> +\n> +#include <libcamera/base/log.h>\n> +#include <libcamera/base/signal.h>\n> +\n> +#include <libcamera/geometry.h>\n> +#include <libcamera/pixel_format.h>\n> +\n> +#include \"libcamera/internal/converter.h\"\n> +\n> +namespace libcamera {\n> +\n> +class FrameBuffer;\n> +class MediaDevice;\n> +class Size;\n> +class SizeRange;\n> +struct StreamConfiguration;\n> +class V4L2M2MDevice;\n> +class V4L2M2MConverter;\n> +class Converter;\n> +\n> +class V4L2M2MConverter : public Converter\n> +{\n> +protected:\n> +\tclass Mapping\n> +\t{\n> +\tpublic:\n> +\t\tMapping(const Size &input, const Size &output, const std::vector<uint32_t> &map)\n> +\t\t\t: input_(input), output_(output), map_(map) {}\n> +\t\tSize getInputSize() const { return input_; }\n> +\t\tSize getOutputSize() const { return output_; }\n> +\t\tstd::size_t getLength() const { return map_.size(); }\n> +\t\tconst uint32_t *getMapping() const { return map_.data(); }\n> +\n> +\tprivate:\n> +\t\tSize input_;\n> +\t\tSize output_;\n> +\t\tstd::vector<uint32_t> map_;\n> +\t};\n> +\n> +\tclass Stream : protected Loggable\n> +\t{\n> +\tpublic:\n> +\t\tStream(V4L2M2MConverter *converter, unsigned int index);\n> +\n> +\t\tbool isValid() const { return m2m_ != nullptr; }\n> +\n> +\t\tint configure(const StreamConfiguration &inputCfg,\n> +\t\t\t      const StreamConfiguration &outputCfg);\n> +\t\tint exportBuffers(unsigned int count,\n> +\t\t\t\t  std::vector<std::unique_ptr<FrameBuffer>> *buffers);\n> +\n> +\t\tint start();\n> +\t\tvoid stop();\n> +\n> +\t\tint queueBuffers(FrameBuffer *input, FrameBuffer *output);\n> +\t\tstd::unique_ptr<V4L2M2MDevice> m2m_;\n> +\n> +\tprotected:\n> +\t\tstd::string logPrefix() const override;\n> +\n> +\tprivate:\n> +\t\tvoid captureBufferReady(FrameBuffer *buffer);\n> +\t\tvoid outputBufferReady(FrameBuffer *buffer);\n> +\n> +\t\tV4L2M2MConverter *converter_;\n> +\t\tunsigned int index_;\n> +\n> +\t\tunsigned int inputBufferCount_;\n> +\t\tunsigned int outputBufferCount_;\n> +\t};\n> +\n> +\tstd::unique_ptr<V4L2M2MDevice> m2m_;\n> +\n> +\tstd::vector<Stream> streams_;\n> +\tstd::vector<Mapping> mappings_;\n> +\tstd::map<FrameBuffer *, unsigned int> queue_;\n> +\n> +public:\n> +\tV4L2M2MConverter(MediaDevice *media);\n> +\n> +\tint loadConfiguration(const std::string &filename) override;\n> +\n> +\tbool isValid() const { return m2m_ != nullptr; }\n> +\n> +\tstd::vector<PixelFormat> formats(PixelFormat input);\n> +\tSizeRange sizes(const Size &input);\n> +\n> +\tstd::tuple<unsigned int, unsigned int>\n> +\tstrideAndFrameSize(const PixelFormat &pixelFormat, const Size &size);\n> +\n> +\tint configure(const StreamConfiguration &inputCfg,\n> +\t\t      const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfg);\n> +\tint exportBuffers(unsigned int ouput, unsigned int count,\n> +\t\t\t  std::vector<std::unique_ptr<FrameBuffer>> *buffers);\n> +\n> +\tint start();\n> +\tvoid stop();\n> +\n> +\tint queueBuffers(FrameBuffer *input,\n> +\t\t\t const std::map<unsigned int, FrameBuffer *> &outputs);\n> +\n> +\tvirtual int applyMapping([[maybe_unused]] Stream *stream, [[maybe_unused]] Mapping &mapping) { return 0; };\n> +};\n> +\n> +} /* namespace libcamera */\n> diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build\n> index 8f50d755..132de5ef 100644\n> --- a/include/libcamera/internal/meson.build\n> +++ b/include/libcamera/internal/meson.build\n> @@ -20,6 +20,7 @@ libcamera_internal_headers = files([\n>      'control_serializer.h',\n>      'control_validator.h',\n>      'converter.h',\n> +    'converter_v4l2_m2m.h',\n>      'delayed_controls.h',\n>      'device_enumerator.h',\n>      'device_enumerator_sysfs.h',\n> diff --git a/src/libcamera/converter_v4l2_m2m.cpp b/src/libcamera/converter_v4l2_m2m.cpp\n> new file mode 100644\n> index 00000000..942e6e6f\n> --- /dev/null\n> +++ b/src/libcamera/converter_v4l2_m2m.cpp\n> @@ -0,0 +1,504 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2020, Laurent Pinchart\n> + * Copyright 2022 NXP\n> + *\n> + * converter_v4l2_m2m.cpp - V4L2 M2M Format converter\n> + */\n> +\n> +#include <algorithm>\n> +#include <limits.h>\n> +\n> +#include <libcamera/base/file.h>\n> +#include <libcamera/base/log.h>\n> +#include <libcamera/base/signal.h>\n> +#include <libcamera/base/utils.h>\n> +\n> +#include <libcamera/framebuffer.h>\n> +#include <libcamera/geometry.h>\n> +#include <libcamera/stream.h>\n> +\n> +#include \"libcamera/internal/converter_v4l2_m2m.h\"\n> +#include \"libcamera/internal/media_device.h\"\n> +#include \"libcamera/internal/v4l2_videodevice.h\"\n> +#include \"libcamera/internal/yaml_parser.h\"\n> +\n> +namespace libcamera {\n> +\n> +LOG_DECLARE_CATEGORY(Converter)\n> +\n> +/* -----------------------------------------------------------------------------\n> + * V4L2M2MConverter::Stream\n> + */\n> +\n> +V4L2M2MConverter::Stream::Stream(V4L2M2MConverter *converter, unsigned int index)\n> +\t: converter_(converter), index_(index)\n> +{\n> +\tm2m_ = std::make_unique<V4L2M2MDevice>(converter->deviceNode_);\n> +\n> +\tm2m_->output()->bufferReady.connect(this, &Stream::outputBufferReady);\n> +\tm2m_->capture()->bufferReady.connect(this, &Stream::captureBufferReady);\n> +\n> +\tint ret = m2m_->open();\n> +\tif (ret < 0)\n> +\t\tm2m_.reset();\n\nWhy are we re-creating and opening the m2m_ device everytime we create\na stream ? Isn't it the same created and open when the\nV4L2M2MConverter is created ?\n\nThanks\n  j\n\n> +}\n> +\n> +int V4L2M2MConverter::Stream::configure(const StreamConfiguration &inputCfg,\n> +\t\t\t\t\tconst StreamConfiguration &outputCfg)\n> +{\n> +\tV4L2PixelFormat videoFormat =\n> +\t\tm2m_->output()->toV4L2PixelFormat(inputCfg.pixelFormat);\n> +\n> +\tV4L2DeviceFormat format;\n> +\tformat.fourcc = videoFormat;\n> +\tformat.size = inputCfg.size;\n> +\tformat.planesCount = 1;\n> +\tformat.planes[0].bpl = inputCfg.stride;\n> +\n> +\tint ret = m2m_->output()->setFormat(&format);\n> +\tif (ret < 0) {\n> +\t\tLOG(Converter, Error)\n> +\t\t\t<< \"Failed to set input format: \" << strerror(-ret);\n> +\t\treturn ret;\n> +\t}\n> +\n> +\tif (format.fourcc != videoFormat || format.size != inputCfg.size ||\n> +\t    format.planes[0].bpl != inputCfg.stride) {\n> +\t\tLOG(Converter, Error)\n> +\t\t\t<< \"Input format not supported (requested \"\n> +\t\t\t<< inputCfg.size << \"-\" << videoFormat\n> +\t\t\t<< \", got \" << format << \")\";\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\t/* Set the pixel format and size on the output. */\n> +\tvideoFormat = m2m_->capture()->toV4L2PixelFormat(outputCfg.pixelFormat);\n> +\tformat = {};\n> +\tformat.fourcc = videoFormat;\n> +\tformat.size = outputCfg.size;\n> +\n> +\tret = m2m_->capture()->setFormat(&format);\n> +\tif (ret < 0) {\n> +\t\tLOG(Converter, Error)\n> +\t\t\t<< \"Failed to set output format: \" << strerror(-ret);\n> +\t\treturn ret;\n> +\t}\n> +\n> +\tif (format.fourcc != videoFormat || format.size != outputCfg.size) {\n> +\t\tLOG(Converter, Error)\n> +\t\t\t<< \"Output format not supported\";\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\tinputBufferCount_ = inputCfg.bufferCount;\n> +\toutputBufferCount_ = outputCfg.bufferCount;\n> +\n> +\tfor (Mapping &mapping : converter_->mappings_) {\n> +\t\tControlList ctrls;\n> +\t\tif (mapping.getInputSize() == inputCfg.size && mapping.getOutputSize() == outputCfg.size) {\n> +\t\t\tLOG(Converter, Debug)\n> +\t\t\t\t<< \"Got a configuration match \"\n> +\t\t\t\t<< inputCfg.size << \" --> \" << outputCfg.size;\n> +\t\t\tconverter_->applyMapping(this, mapping);\n> +\t\t}\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +int V4L2M2MConverter::Stream::exportBuffers(unsigned int count,\n> +\t\t\t\t\t    std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n> +{\n> +\treturn m2m_->capture()->exportBuffers(count, buffers);\n> +}\n> +\n> +int V4L2M2MConverter::Stream::start()\n> +{\n> +\tint ret = m2m_->output()->importBuffers(inputBufferCount_);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tret = m2m_->capture()->importBuffers(outputBufferCount_);\n> +\tif (ret < 0) {\n> +\t\tstop();\n> +\t\treturn ret;\n> +\t}\n> +\n> +\tret = m2m_->output()->streamOn();\n> +\tif (ret < 0) {\n> +\t\tstop();\n> +\t\treturn ret;\n> +\t}\n> +\n> +\tret = m2m_->capture()->streamOn();\n> +\tif (ret < 0) {\n> +\t\tstop();\n> +\t\treturn ret;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +void V4L2M2MConverter::Stream::stop()\n> +{\n> +\tm2m_->capture()->streamOff();\n> +\tm2m_->output()->streamOff();\n> +\tm2m_->capture()->releaseBuffers();\n> +\tm2m_->output()->releaseBuffers();\n> +}\n> +\n> +int V4L2M2MConverter::Stream::queueBuffers(FrameBuffer *input, FrameBuffer *output)\n> +{\n> +\tint ret = m2m_->output()->queueBuffer(input);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tret = m2m_->capture()->queueBuffer(output);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\treturn 0;\n> +}\n> +\n> +std::string V4L2M2MConverter::Stream::logPrefix() const\n> +{\n> +\treturn \"stream\" + std::to_string(index_);\n> +}\n> +\n> +void V4L2M2MConverter::Stream::outputBufferReady(FrameBuffer *buffer)\n> +{\n> +\tauto it = converter_->queue_.find(buffer);\n> +\tif (it == converter_->queue_.end())\n> +\t\treturn;\n> +\n> +\tif (!--it->second) {\n> +\t\tconverter_->inputBufferReady.emit(buffer);\n> +\t\tconverter_->queue_.erase(it);\n> +\t}\n> +}\n> +\n> +void V4L2M2MConverter::Stream::captureBufferReady(FrameBuffer *buffer)\n> +{\n> +\tconverter_->outputBufferReady.emit(buffer);\n> +}\n> +\n> +/* -----------------------------------------------------------------------------\n> + * V4L2M2MConverter\n> + */\n> +\n> +V4L2M2MConverter::V4L2M2MConverter(MediaDevice *media)\n> +\t: Converter(media)\n> +{\n> +\tif (deviceNode_.empty())\n> +\t\treturn;\n> +\n> +\tm2m_ = std::make_unique<V4L2M2MDevice>(deviceNode_);\n> +\tint ret = m2m_->open();\n> +\tif (ret < 0) {\n> +\t\tm2m_.reset();\n> +\t\treturn;\n> +\t}\n> +}\n> +\n> +int V4L2M2MConverter::loadConfiguration(const std::string &filename)\n> +{\n> +\tLOG(Converter, Debug)\n> +\t\t<< \"Parsing configuration file \" << filename;\n> +\n> +\tFile file(filename);\n> +\n> +\tif (!file.open(File::OpenModeFlag::ReadOnly)) {\n> +\t\tint ret = file.error();\n> +\t\tLOG(Converter, Error)\n> +\t\t\t<< \"Failed to open configuration file \"\n> +\t\t\t<< filename << \": \" << strerror(-ret);\n> +\t\treturn ret;\n> +\t}\n> +\n> +\tstd::unique_ptr<libcamera::YamlObject> data = YamlParser::parse(file);\n> +\tif (!data)\n> +\t\treturn -EINVAL;\n> +\n> +\tif (!data->contains(\"mappings\")) {\n> +\t\tLOG(Converter, Error)\n> +\t\t\t<< \"Vertex mapping key missing\";\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\tconst YamlObject &mappings = (*data)[\"mappings\"];\n> +\tif (!mappings.isList() || mappings.size() == 0) {\n> +\t\tLOG(Converter, Error)\n> +\t\t\t<< \"Invalid mappings entry\";\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\tLOG(Converter, Debug)\n> +\t\t<< \"Parsing \" << mappings.size() << \" mappings\";\n> +\tmappings_.clear();\n> +\tmappings_.reserve(mappings.size());\n> +\n> +\tfor (std::size_t i = 0; i < mappings.size(); i++) {\n> +\t\tconst YamlObject &mapping = mappings[i];\n> +\t\tif (!mapping.isDictionary()) {\n> +\t\t\tLOG(Converter, Error)\n> +\t\t\t\t<< \"Mapping is not a dictionnary\";\n> +\t\t\treturn -EINVAL;\n> +\t\t}\n> +\n> +\t\tif (!mapping.contains(\"input-resolution\")) {\n> +\t\t\tLOG(Converter, Error)\n> +\t\t\t\t<< \"Input resolution missing\";\n> +\t\t\treturn -EINVAL;\n> +\t\t}\n> +\n> +\t\tif (!mapping.contains(\"output-resolution\")) {\n> +\t\t\tLOG(Converter, Error)\n> +\t\t\t\t<< \"Output resolution missing\";\n> +\t\t\treturn -EINVAL;\n> +\t\t}\n> +\n> +\t\tif (!mapping.contains(\"mapping\")) {\n> +\t\t\tLOG(Converter, Error)\n> +\t\t\t\t<< \"Mapping table missing\";\n> +\t\t\treturn -EINVAL;\n> +\t\t}\n> +\n> +\t\tconst YamlObject &input_res = mapping[\"input-resolution\"];\n> +\t\tif (!input_res.isList() || input_res.size() != 2) {\n> +\t\t\tLOG(Converter, Error)\n> +\t\t\t\t<< \"Incorrect input resolution\";\n> +\t\t\treturn -EINVAL;\n> +\t\t}\n> +\n> +\t\tconst YamlObject &output_res = mapping[\"output-resolution\"];\n> +\t\tif (!output_res.isList() || output_res.size() != 2) {\n> +\t\t\tLOG(Converter, Error)\n> +\t\t\t\t<< \"Incorrect output resolution\";\n> +\t\t\treturn -EINVAL;\n> +\t\t}\n> +\n> +\t\tconst YamlObject &map = mapping[\"mapping\"];\n> +\t\tif (!map.isList() || map.size() == 0) {\n> +\t\t\tLOG(Converter, Error)\n> +\t\t\t\t<< \"Incorrect mapping entries\";\n> +\t\t\treturn -EINVAL;\n> +\t\t}\n> +\n> +\t\tSize input(input_res[0].get<uint32_t>(0), input_res[1].get<uint32_t>(0));\n> +\t\tSize output(output_res[0].get<uint32_t>(0), output_res[1].get<uint32_t>(0));\n> +\t\tconst auto &mapVector = map.getList<uint32_t>().value_or(utils::defopt);\n> +\n> +\t\tLOG(Converter, Debug)\n> +\t\t\t<< \"Input/Output mapping resolution \" << input << \" ---> \" << output;\n> +\t\tmappings_.emplace_back(Mapping(input, output, mapVector));\n> +\t}\n> +\n> +\treturn mappings.size();\n> +}\n> +\n> +std::vector<PixelFormat> V4L2M2MConverter::formats(PixelFormat input)\n> +{\n> +\tif (!m2m_)\n> +\t\treturn {};\n> +\n> +\t/*\n> +\t * Set the format on the input side (V4L2 output) of the converter to\n> +\t * enumerate the conversion capabilities on its output (V4L2 capture).\n> +\t */\n> +\tV4L2DeviceFormat v4l2Format;\n> +\tv4l2Format.fourcc = m2m_->output()->toV4L2PixelFormat(input);\n> +\tv4l2Format.size = { 1, 1 };\n> +\n> +\tint ret = m2m_->output()->setFormat(&v4l2Format);\n> +\tif (ret < 0) {\n> +\t\tLOG(Converter, Error)\n> +\t\t\t<< \"Failed to set format: \" << strerror(-ret);\n> +\t\treturn {};\n> +\t}\n> +\n> +\tif (v4l2Format.fourcc != m2m_->output()->toV4L2PixelFormat(input)) {\n> +\t\tLOG(Converter, Debug)\n> +\t\t\t<< \"Input format \" << input << \" not supported.\";\n> +\t\treturn {};\n> +\t}\n> +\n> +\tstd::vector<PixelFormat> pixelFormats;\n> +\n> +\tfor (const auto &format : m2m_->capture()->formats()) {\n> +\t\tPixelFormat pixelFormat = format.first.toPixelFormat();\n> +\t\tif (pixelFormat)\n> +\t\t\tpixelFormats.push_back(pixelFormat);\n> +\t}\n> +\n> +\treturn pixelFormats;\n> +}\n> +\n> +SizeRange V4L2M2MConverter::sizes(const Size &input)\n> +{\n> +\tif (!m2m_)\n> +\t\treturn {};\n> +\n> +\t/*\n> +\t * Set the size on the input side (V4L2 output) of the converter to\n> +\t * enumerate the scaling capabilities on its output (V4L2 capture).\n> +\t */\n> +\tV4L2DeviceFormat format;\n> +\tformat.fourcc = V4L2PixelFormat();\n> +\tformat.size = input;\n> +\n> +\tint ret = m2m_->output()->setFormat(&format);\n> +\tif (ret < 0) {\n> +\t\tLOG(Converter, Error)\n> +\t\t\t<< \"Failed to set format: \" << strerror(-ret);\n> +\t\treturn {};\n> +\t}\n> +\n> +\tSizeRange sizes;\n> +\n> +\tformat.size = { 1, 1 };\n> +\tret = m2m_->capture()->setFormat(&format);\n> +\tif (ret < 0) {\n> +\t\tLOG(Converter, Error)\n> +\t\t\t<< \"Failed to set format: \" << strerror(-ret);\n> +\t\treturn {};\n> +\t}\n> +\n> +\tsizes.min = format.size;\n> +\n> +\tformat.size = { UINT_MAX, UINT_MAX };\n> +\tret = m2m_->capture()->setFormat(&format);\n> +\tif (ret < 0) {\n> +\t\tLOG(Converter, Error)\n> +\t\t\t<< \"Failed to set format: \" << strerror(-ret);\n> +\t\treturn {};\n> +\t}\n> +\n> +\tsizes.max = format.size;\n> +\n> +\treturn sizes;\n> +}\n> +\n> +std::tuple<unsigned int, unsigned int>\n> +V4L2M2MConverter::strideAndFrameSize(const PixelFormat &pixelFormat,\n> +\t\t\t\t     const Size &size)\n> +{\n> +\tV4L2DeviceFormat format;\n> +\tformat.fourcc = m2m_->capture()->toV4L2PixelFormat(pixelFormat);\n> +\tformat.size = size;\n> +\n> +\tint ret = m2m_->capture()->tryFormat(&format);\n> +\tif (ret < 0)\n> +\t\treturn std::make_tuple(0, 0);\n> +\n> +\treturn std::make_tuple(format.planes[0].bpl, format.planes[0].size);\n> +}\n> +\n> +int V4L2M2MConverter::configure(const StreamConfiguration &inputCfg,\n> +\t\t\t\tconst std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs)\n> +{\n> +\tint ret = 0;\n> +\n> +\tstreams_.clear();\n> +\tstreams_.reserve(outputCfgs.size());\n> +\n> +\tfor (unsigned int i = 0; i < outputCfgs.size(); ++i) {\n> +\t\tStream &stream = streams_.emplace_back(this, i);\n> +\n> +\t\tif (!stream.isValid()) {\n> +\t\t\tLOG(Converter, Error)\n> +\t\t\t\t<< \"Failed to create stream \" << i;\n> +\t\t\tret = -EINVAL;\n> +\t\t\tbreak;\n> +\t\t}\n> +\n> +\t\tret = stream.configure(inputCfg, outputCfgs[i]);\n> +\t\tif (ret < 0)\n> +\t\t\tbreak;\n> +\t}\n> +\n> +\tif (ret < 0) {\n> +\t\tstreams_.clear();\n> +\t\treturn ret;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +int V4L2M2MConverter::exportBuffers(unsigned int output, unsigned int count,\n> +\t\t\t\t    std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n> +{\n> +\tif (output >= streams_.size())\n> +\t\treturn -EINVAL;\n> +\n> +\treturn streams_[output].exportBuffers(count, buffers);\n> +}\n> +\n> +int V4L2M2MConverter::start()\n> +{\n> +\tint ret;\n> +\n> +\tfor (Stream &stream : streams_) {\n> +\t\tret = stream.start();\n> +\t\tif (ret < 0) {\n> +\t\t\tstop();\n> +\t\t\treturn ret;\n> +\t\t}\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +void V4L2M2MConverter::stop()\n> +{\n> +\tfor (Stream &stream : utils::reverse(streams_))\n> +\t\tstream.stop();\n> +}\n> +\n> +int V4L2M2MConverter::queueBuffers(FrameBuffer *input,\n> +\t\t\t\t   const std::map<unsigned int, FrameBuffer *> &outputs)\n> +{\n> +\tunsigned int mask = 0;\n> +\tint ret;\n> +\n> +\t/*\n> +\t * Validate the outputs as a sanity check: at least one output is\n> +\t * required, all outputs must reference a valid stream and no two\n> +\t * outputs can reference the same stream.\n> +\t */\n> +\tif (outputs.empty())\n> +\t\treturn -EINVAL;\n> +\n> +\tfor (auto [index, buffer] : outputs) {\n> +\t\tif (!buffer)\n> +\t\t\treturn -EINVAL;\n> +\t\tif (index >= streams_.size())\n> +\t\t\treturn -EINVAL;\n> +\t\tif (mask & (1 << index))\n> +\t\t\treturn -EINVAL;\n> +\n> +\t\tmask |= 1 << index;\n> +\t}\n> +\n> +\t/* Queue the input and output buffers to all the streams. */\n> +\tfor (auto [index, buffer] : outputs) {\n> +\t\tret = streams_[index].queueBuffers(input, buffer);\n> +\t\tif (ret < 0)\n> +\t\t\treturn ret;\n> +\t}\n> +\n> +\t/*\n> +\t * Add the input buffer to the queue, with the number of streams as a\n> +\t * reference count. Completion of the input buffer will be signalled by\n> +\t * the stream that releases the last reference.\n> +\t */\n> +\tqueue_.emplace(std::piecewise_construct,\n> +\t\t       std::forward_as_tuple(input),\n> +\t\t       std::forward_as_tuple(outputs.size()));\n> +\n> +\treturn 0;\n> +}\n> +\n> +REGISTER_CONVERTER(\"v4l2_m2m\", V4L2M2MConverter, \"pxp\")\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\n> index a261d4b4..b12c8401 100644\n> --- a/src/libcamera/meson.build\n> +++ b/src/libcamera/meson.build\n> @@ -14,6 +14,7 @@ libcamera_sources = files([\n>      'control_serializer.cpp',\n>      'control_validator.cpp',\n>      'converter.cpp',\n> +    'converter_v4l2_m2m.cpp',\n>      'delayed_controls.cpp',\n>      'device_enumerator.cpp',\n>      'device_enumerator_sysfs.cpp',\n> --\n> 2.37.3\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 B06DAC3272\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 15 Sep 2022 09:46:26 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 2633F61FAA;\n\tThu, 15 Sep 2022 11:46:26 +0200 (CEST)","from relay5-d.mail.gandi.net (relay5-d.mail.gandi.net\n\t[IPv6:2001:4b98:dc4:8::225])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 57920600AA\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 15 Sep 2022 11:46:24 +0200 (CEST)","(Authenticated sender: jacopo@jmondi.org)\n\tby mail.gandi.net (Postfix) with ESMTPSA id 2068B1C0002;\n\tThu, 15 Sep 2022 09:46:22 +0000 (UTC)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1663235186;\n\tbh=792FhK4y1zvZ32ZDMbSwDqqw8Y2YoB9M41veIGSkWcI=;\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=vvFMx4cT2pZLM9SP+d7LNA43bT9OprNlJHdeq1WQK9ReKcNV/9dmJRV5Y1XxjAEN8\n\tz+gyyeHK5xfgPJWRgVM0RwyuCDnhoQb5I+pLHRZR5Hal73yM8H7K6BoAozvuhOY84N\n\tsJTxL7z0UTJ3ipJxHHH3dOsQVDCo7tFkWiRosvN9BdsTeYDjTPWf7wMDS6gCwtFG9/\n\tLvXLhpWQ7dfwSSQXjUH2R6E7y69F+W/RBvhR0RNk7MBTFIbtLuK8Lxrn3yqQjrysOs\n\t+vjCYLhFlNrUYBhD6m+EALi67Zgg3D4VV3Zf5kHzDLZp5IODJLozQTiVCtzBj4rBky\n\tYNuQYOXnCwPXg==","Date":"Thu, 15 Sep 2022 11:46:21 +0200","To":"Xavier Roumegue <xavier.roumegue@oss.nxp.com>","Message-ID":"<20220915094621.vtc5ffmdw6hjb3js@uno.localdomain>","References":"<20220908184850.1874303-1-xavier.roumegue@oss.nxp.com>\n\t<20220908184850.1874303-7-xavier.roumegue@oss.nxp.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20220908184850.1874303-7-xavier.roumegue@oss.nxp.com>","Subject":"Re: [libcamera-devel] [PATCH 06/14] libcamera: converter: Add v4l2\n\tm2m converter implementation","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":"Jacopo Mondi via libcamera-devel <libcamera-devel@lists.libcamera.org>","Reply-To":"Jacopo Mondi <jacopo@jmondi.org>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":24998,"web_url":"https://patchwork.libcamera.org/comment/24998/","msgid":"<47d506ef-e0a1-718c-72dc-66238b1514d7@oss.nxp.com>","date":"2022-09-19T08:45:59","subject":"Re: [libcamera-devel] [PATCH 06/14] libcamera: converter: Add v4l2\n\tm2m converter implementation","submitter":{"id":107,"url":"https://patchwork.libcamera.org/api/people/107/","name":"Xavier Roumegue","email":"xavier.roumegue@oss.nxp.com"},"content":"Hi Jacopo,\n\nOn 9/15/22 11:46, Jacopo Mondi wrote:\n> Hi Xavier\n> \n> On Thu, Sep 08, 2022 at 08:48:42PM +0200, Xavier Roumegue via libcamera-devel wrote:\n>> Introduce a converter implementation relying on a v4l2 m2m device, mostly\n>> based on the current simple pipeline converter implementation.\n>>\n>> The main change is the introduction of Mapping object which can be\n>> loaded through a configuration file which define vertices remapping\n>> coordinates. Those latters can be applied by any classes derived from\n>> this base class which define the apply_mapping() method.\n>>\n> \n> I'll skip questions on things not clear to me about the converter\n> implementation as I understand this basically comes from the simple\n> pipeline converter implementation [*]\n> \n> I would then focus more on two general questions:\n> 1) Do we want to make a dir for converter/post-processors instead of\n> placing them in src/libcamera\nMight make sense, or could be done later - that's not my call.\nI can move them to a dedicated folder on the next patch set version if there is \na willing to do so.\n> \n> 2) Is the format expected in the configuration file documented\n> anywhere ?\nNope. I will do it on the next version, hopefully on an agreed configuration \nfile format.\n> \n> [*] Not true, I will ask a question anyway, see below :)\n> \n>> Signed-off-by: Xavier Roumegue <xavier.roumegue@oss.nxp.com>\n>> ---\n>>   .../libcamera/internal/converter_v4l2_m2m.h   | 120 +++++\n>>   include/libcamera/internal/meson.build        |   1 +\n>>   src/libcamera/converter_v4l2_m2m.cpp          | 504 ++++++++++++++++++\n>>   src/libcamera/meson.build                     |   1 +\n>>   4 files changed, 626 insertions(+)\n>>   create mode 100644 include/libcamera/internal/converter_v4l2_m2m.h\n>>   create mode 100644 src/libcamera/converter_v4l2_m2m.cpp\n>>\n>> diff --git a/include/libcamera/internal/converter_v4l2_m2m.h b/include/libcamera/internal/converter_v4l2_m2m.h\n>> new file mode 100644\n>> index 00000000..3667b128\n>> --- /dev/null\n>> +++ b/include/libcamera/internal/converter_v4l2_m2m.h\n>> @@ -0,0 +1,120 @@\n>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n>> +/*\n>> + * Copyright 2022 NXP\n>> + *\n>> + * converter_v4l2_m2m.h - V4l2 M2M Format converter interface\n>> + */\n>> +\n>> +#pragma once\n>> +\n>> +#include <functional>\n>> +#include <map>\n>> +#include <memory>\n>> +#include <string>\n>> +#include <tuple>\n>> +#include <vector>\n>> +\n>> +#include <libcamera/base/log.h>\n>> +#include <libcamera/base/signal.h>\n>> +\n>> +#include <libcamera/geometry.h>\n>> +#include <libcamera/pixel_format.h>\n>> +\n>> +#include \"libcamera/internal/converter.h\"\n>> +\n>> +namespace libcamera {\n>> +\n>> +class FrameBuffer;\n>> +class MediaDevice;\n>> +class Size;\n>> +class SizeRange;\n>> +struct StreamConfiguration;\n>> +class V4L2M2MDevice;\n>> +class V4L2M2MConverter;\n>> +class Converter;\n>> +\n>> +class V4L2M2MConverter : public Converter\n>> +{\n>> +protected:\n>> +\tclass Mapping\n>> +\t{\n>> +\tpublic:\n>> +\t\tMapping(const Size &input, const Size &output, const std::vector<uint32_t> &map)\n>> +\t\t\t: input_(input), output_(output), map_(map) {}\n>> +\t\tSize getInputSize() const { return input_; }\n>> +\t\tSize getOutputSize() const { return output_; }\n>> +\t\tstd::size_t getLength() const { return map_.size(); }\n>> +\t\tconst uint32_t *getMapping() const { return map_.data(); }\n>> +\n>> +\tprivate:\n>> +\t\tSize input_;\n>> +\t\tSize output_;\n>> +\t\tstd::vector<uint32_t> map_;\n>> +\t};\n>> +\n>> +\tclass Stream : protected Loggable\n>> +\t{\n>> +\tpublic:\n>> +\t\tStream(V4L2M2MConverter *converter, unsigned int index);\n>> +\n>> +\t\tbool isValid() const { return m2m_ != nullptr; }\n>> +\n>> +\t\tint configure(const StreamConfiguration &inputCfg,\n>> +\t\t\t      const StreamConfiguration &outputCfg);\n>> +\t\tint exportBuffers(unsigned int count,\n>> +\t\t\t\t  std::vector<std::unique_ptr<FrameBuffer>> *buffers);\n>> +\n>> +\t\tint start();\n>> +\t\tvoid stop();\n>> +\n>> +\t\tint queueBuffers(FrameBuffer *input, FrameBuffer *output);\n>> +\t\tstd::unique_ptr<V4L2M2MDevice> m2m_;\n>> +\n>> +\tprotected:\n>> +\t\tstd::string logPrefix() const override;\n>> +\n>> +\tprivate:\n>> +\t\tvoid captureBufferReady(FrameBuffer *buffer);\n>> +\t\tvoid outputBufferReady(FrameBuffer *buffer);\n>> +\n>> +\t\tV4L2M2MConverter *converter_;\n>> +\t\tunsigned int index_;\n>> +\n>> +\t\tunsigned int inputBufferCount_;\n>> +\t\tunsigned int outputBufferCount_;\n>> +\t};\n>> +\n>> +\tstd::unique_ptr<V4L2M2MDevice> m2m_;\n>> +\n>> +\tstd::vector<Stream> streams_;\n>> +\tstd::vector<Mapping> mappings_;\n>> +\tstd::map<FrameBuffer *, unsigned int> queue_;\n>> +\n>> +public:\n>> +\tV4L2M2MConverter(MediaDevice *media);\n>> +\n>> +\tint loadConfiguration(const std::string &filename) override;\n>> +\n>> +\tbool isValid() const { return m2m_ != nullptr; }\n>> +\n>> +\tstd::vector<PixelFormat> formats(PixelFormat input);\n>> +\tSizeRange sizes(const Size &input);\n>> +\n>> +\tstd::tuple<unsigned int, unsigned int>\n>> +\tstrideAndFrameSize(const PixelFormat &pixelFormat, const Size &size);\n>> +\n>> +\tint configure(const StreamConfiguration &inputCfg,\n>> +\t\t      const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfg);\n>> +\tint exportBuffers(unsigned int ouput, unsigned int count,\n>> +\t\t\t  std::vector<std::unique_ptr<FrameBuffer>> *buffers);\n>> +\n>> +\tint start();\n>> +\tvoid stop();\n>> +\n>> +\tint queueBuffers(FrameBuffer *input,\n>> +\t\t\t const std::map<unsigned int, FrameBuffer *> &outputs);\n>> +\n>> +\tvirtual int applyMapping([[maybe_unused]] Stream *stream, [[maybe_unused]] Mapping &mapping) { return 0; };\n>> +};\n>> +\n>> +} /* namespace libcamera */\n>> diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build\n>> index 8f50d755..132de5ef 100644\n>> --- a/include/libcamera/internal/meson.build\n>> +++ b/include/libcamera/internal/meson.build\n>> @@ -20,6 +20,7 @@ libcamera_internal_headers = files([\n>>       'control_serializer.h',\n>>       'control_validator.h',\n>>       'converter.h',\n>> +    'converter_v4l2_m2m.h',\n>>       'delayed_controls.h',\n>>       'device_enumerator.h',\n>>       'device_enumerator_sysfs.h',\n>> diff --git a/src/libcamera/converter_v4l2_m2m.cpp b/src/libcamera/converter_v4l2_m2m.cpp\n>> new file mode 100644\n>> index 00000000..942e6e6f\n>> --- /dev/null\n>> +++ b/src/libcamera/converter_v4l2_m2m.cpp\n>> @@ -0,0 +1,504 @@\n>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n>> +/*\n>> + * Copyright (C) 2020, Laurent Pinchart\n>> + * Copyright 2022 NXP\n>> + *\n>> + * converter_v4l2_m2m.cpp - V4L2 M2M Format converter\n>> + */\n>> +\n>> +#include <algorithm>\n>> +#include <limits.h>\n>> +\n>> +#include <libcamera/base/file.h>\n>> +#include <libcamera/base/log.h>\n>> +#include <libcamera/base/signal.h>\n>> +#include <libcamera/base/utils.h>\n>> +\n>> +#include <libcamera/framebuffer.h>\n>> +#include <libcamera/geometry.h>\n>> +#include <libcamera/stream.h>\n>> +\n>> +#include \"libcamera/internal/converter_v4l2_m2m.h\"\n>> +#include \"libcamera/internal/media_device.h\"\n>> +#include \"libcamera/internal/v4l2_videodevice.h\"\n>> +#include \"libcamera/internal/yaml_parser.h\"\n>> +\n>> +namespace libcamera {\n>> +\n>> +LOG_DECLARE_CATEGORY(Converter)\n>> +\n>> +/* -----------------------------------------------------------------------------\n>> + * V4L2M2MConverter::Stream\n>> + */\n>> +\n>> +V4L2M2MConverter::Stream::Stream(V4L2M2MConverter *converter, unsigned int index)\n>> +\t: converter_(converter), index_(index)\n>> +{\n>> +\tm2m_ = std::make_unique<V4L2M2MDevice>(converter->deviceNode_);\n>> +\n>> +\tm2m_->output()->bufferReady.connect(this, &Stream::outputBufferReady);\n>> +\tm2m_->capture()->bufferReady.connect(this, &Stream::captureBufferReady);\n>> +\n>> +\tint ret = m2m_->open();\n>> +\tif (ret < 0)\n>> +\t\tm2m_.reset();\n> \n> Why are we re-creating and opening the m2m_ device everytime we create\n> a stream ? Isn't it the same created and open when the\n> V4L2M2MConverter is created ?Would it be for proper reference counting ?\n\n> \n> Thanks\n>    j\n> \n>> +}\n>> +\n>> +int V4L2M2MConverter::Stream::configure(const StreamConfiguration &inputCfg,\n>> +\t\t\t\t\tconst StreamConfiguration &outputCfg)\n>> +{\n>> +\tV4L2PixelFormat videoFormat =\n>> +\t\tm2m_->output()->toV4L2PixelFormat(inputCfg.pixelFormat);\n>> +\n>> +\tV4L2DeviceFormat format;\n>> +\tformat.fourcc = videoFormat;\n>> +\tformat.size = inputCfg.size;\n>> +\tformat.planesCount = 1;\n>> +\tformat.planes[0].bpl = inputCfg.stride;\n>> +\n>> +\tint ret = m2m_->output()->setFormat(&format);\n>> +\tif (ret < 0) {\n>> +\t\tLOG(Converter, Error)\n>> +\t\t\t<< \"Failed to set input format: \" << strerror(-ret);\n>> +\t\treturn ret;\n>> +\t}\n>> +\n>> +\tif (format.fourcc != videoFormat || format.size != inputCfg.size ||\n>> +\t    format.planes[0].bpl != inputCfg.stride) {\n>> +\t\tLOG(Converter, Error)\n>> +\t\t\t<< \"Input format not supported (requested \"\n>> +\t\t\t<< inputCfg.size << \"-\" << videoFormat\n>> +\t\t\t<< \", got \" << format << \")\";\n>> +\t\treturn -EINVAL;\n>> +\t}\n>> +\n>> +\t/* Set the pixel format and size on the output. */\n>> +\tvideoFormat = m2m_->capture()->toV4L2PixelFormat(outputCfg.pixelFormat);\n>> +\tformat = {};\n>> +\tformat.fourcc = videoFormat;\n>> +\tformat.size = outputCfg.size;\n>> +\n>> +\tret = m2m_->capture()->setFormat(&format);\n>> +\tif (ret < 0) {\n>> +\t\tLOG(Converter, Error)\n>> +\t\t\t<< \"Failed to set output format: \" << strerror(-ret);\n>> +\t\treturn ret;\n>> +\t}\n>> +\n>> +\tif (format.fourcc != videoFormat || format.size != outputCfg.size) {\n>> +\t\tLOG(Converter, Error)\n>> +\t\t\t<< \"Output format not supported\";\n>> +\t\treturn -EINVAL;\n>> +\t}\n>> +\n>> +\tinputBufferCount_ = inputCfg.bufferCount;\n>> +\toutputBufferCount_ = outputCfg.bufferCount;\n>> +\n>> +\tfor (Mapping &mapping : converter_->mappings_) {\n>> +\t\tControlList ctrls;\n>> +\t\tif (mapping.getInputSize() == inputCfg.size && mapping.getOutputSize() == outputCfg.size) {\n>> +\t\t\tLOG(Converter, Debug)\n>> +\t\t\t\t<< \"Got a configuration match \"\n>> +\t\t\t\t<< inputCfg.size << \" --> \" << outputCfg.size;\n>> +\t\t\tconverter_->applyMapping(this, mapping);\n>> +\t\t}\n>> +\t}\n>> +\n>> +\treturn 0;\n>> +}\n>> +\n>> +int V4L2M2MConverter::Stream::exportBuffers(unsigned int count,\n>> +\t\t\t\t\t    std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n>> +{\n>> +\treturn m2m_->capture()->exportBuffers(count, buffers);\n>> +}\n>> +\n>> +int V4L2M2MConverter::Stream::start()\n>> +{\n>> +\tint ret = m2m_->output()->importBuffers(inputBufferCount_);\n>> +\tif (ret < 0)\n>> +\t\treturn ret;\n>> +\n>> +\tret = m2m_->capture()->importBuffers(outputBufferCount_);\n>> +\tif (ret < 0) {\n>> +\t\tstop();\n>> +\t\treturn ret;\n>> +\t}\n>> +\n>> +\tret = m2m_->output()->streamOn();\n>> +\tif (ret < 0) {\n>> +\t\tstop();\n>> +\t\treturn ret;\n>> +\t}\n>> +\n>> +\tret = m2m_->capture()->streamOn();\n>> +\tif (ret < 0) {\n>> +\t\tstop();\n>> +\t\treturn ret;\n>> +\t}\n>> +\n>> +\treturn 0;\n>> +}\n>> +\n>> +void V4L2M2MConverter::Stream::stop()\n>> +{\n>> +\tm2m_->capture()->streamOff();\n>> +\tm2m_->output()->streamOff();\n>> +\tm2m_->capture()->releaseBuffers();\n>> +\tm2m_->output()->releaseBuffers();\n>> +}\n>> +\n>> +int V4L2M2MConverter::Stream::queueBuffers(FrameBuffer *input, FrameBuffer *output)\n>> +{\n>> +\tint ret = m2m_->output()->queueBuffer(input);\n>> +\tif (ret < 0)\n>> +\t\treturn ret;\n>> +\n>> +\tret = m2m_->capture()->queueBuffer(output);\n>> +\tif (ret < 0)\n>> +\t\treturn ret;\n>> +\n>> +\treturn 0;\n>> +}\n>> +\n>> +std::string V4L2M2MConverter::Stream::logPrefix() const\n>> +{\n>> +\treturn \"stream\" + std::to_string(index_);\n>> +}\n>> +\n>> +void V4L2M2MConverter::Stream::outputBufferReady(FrameBuffer *buffer)\n>> +{\n>> +\tauto it = converter_->queue_.find(buffer);\n>> +\tif (it == converter_->queue_.end())\n>> +\t\treturn;\n>> +\n>> +\tif (!--it->second) {\n>> +\t\tconverter_->inputBufferReady.emit(buffer);\n>> +\t\tconverter_->queue_.erase(it);\n>> +\t}\n>> +}\n>> +\n>> +void V4L2M2MConverter::Stream::captureBufferReady(FrameBuffer *buffer)\n>> +{\n>> +\tconverter_->outputBufferReady.emit(buffer);\n>> +}\n>> +\n>> +/* -----------------------------------------------------------------------------\n>> + * V4L2M2MConverter\n>> + */\n>> +\n>> +V4L2M2MConverter::V4L2M2MConverter(MediaDevice *media)\n>> +\t: Converter(media)\n>> +{\n>> +\tif (deviceNode_.empty())\n>> +\t\treturn;\n>> +\n>> +\tm2m_ = std::make_unique<V4L2M2MDevice>(deviceNode_);\n>> +\tint ret = m2m_->open();\n>> +\tif (ret < 0) {\n>> +\t\tm2m_.reset();\n>> +\t\treturn;\n>> +\t}\n>> +}\n>> +\n>> +int V4L2M2MConverter::loadConfiguration(const std::string &filename)\n>> +{\n>> +\tLOG(Converter, Debug)\n>> +\t\t<< \"Parsing configuration file \" << filename;\n>> +\n>> +\tFile file(filename);\n>> +\n>> +\tif (!file.open(File::OpenModeFlag::ReadOnly)) {\n>> +\t\tint ret = file.error();\n>> +\t\tLOG(Converter, Error)\n>> +\t\t\t<< \"Failed to open configuration file \"\n>> +\t\t\t<< filename << \": \" << strerror(-ret);\n>> +\t\treturn ret;\n>> +\t}\n>> +\n>> +\tstd::unique_ptr<libcamera::YamlObject> data = YamlParser::parse(file);\n>> +\tif (!data)\n>> +\t\treturn -EINVAL;\n>> +\n>> +\tif (!data->contains(\"mappings\")) {\n>> +\t\tLOG(Converter, Error)\n>> +\t\t\t<< \"Vertex mapping key missing\";\n>> +\t\treturn -EINVAL;\n>> +\t}\n>> +\n>> +\tconst YamlObject &mappings = (*data)[\"mappings\"];\n>> +\tif (!mappings.isList() || mappings.size() == 0) {\n>> +\t\tLOG(Converter, Error)\n>> +\t\t\t<< \"Invalid mappings entry\";\n>> +\t\treturn -EINVAL;\n>> +\t}\n>> +\n>> +\tLOG(Converter, Debug)\n>> +\t\t<< \"Parsing \" << mappings.size() << \" mappings\";\n>> +\tmappings_.clear();\n>> +\tmappings_.reserve(mappings.size());\n>> +\n>> +\tfor (std::size_t i = 0; i < mappings.size(); i++) {\n>> +\t\tconst YamlObject &mapping = mappings[i];\n>> +\t\tif (!mapping.isDictionary()) {\n>> +\t\t\tLOG(Converter, Error)\n>> +\t\t\t\t<< \"Mapping is not a dictionnary\";\n>> +\t\t\treturn -EINVAL;\n>> +\t\t}\n>> +\n>> +\t\tif (!mapping.contains(\"input-resolution\")) {\n>> +\t\t\tLOG(Converter, Error)\n>> +\t\t\t\t<< \"Input resolution missing\";\n>> +\t\t\treturn -EINVAL;\n>> +\t\t}\n>> +\n>> +\t\tif (!mapping.contains(\"output-resolution\")) {\n>> +\t\t\tLOG(Converter, Error)\n>> +\t\t\t\t<< \"Output resolution missing\";\n>> +\t\t\treturn -EINVAL;\n>> +\t\t}\n>> +\n>> +\t\tif (!mapping.contains(\"mapping\")) {\n>> +\t\t\tLOG(Converter, Error)\n>> +\t\t\t\t<< \"Mapping table missing\";\n>> +\t\t\treturn -EINVAL;\n>> +\t\t}\n>> +\n>> +\t\tconst YamlObject &input_res = mapping[\"input-resolution\"];\n>> +\t\tif (!input_res.isList() || input_res.size() != 2) {\n>> +\t\t\tLOG(Converter, Error)\n>> +\t\t\t\t<< \"Incorrect input resolution\";\n>> +\t\t\treturn -EINVAL;\n>> +\t\t}\n>> +\n>> +\t\tconst YamlObject &output_res = mapping[\"output-resolution\"];\n>> +\t\tif (!output_res.isList() || output_res.size() != 2) {\n>> +\t\t\tLOG(Converter, Error)\n>> +\t\t\t\t<< \"Incorrect output resolution\";\n>> +\t\t\treturn -EINVAL;\n>> +\t\t}\n>> +\n>> +\t\tconst YamlObject &map = mapping[\"mapping\"];\n>> +\t\tif (!map.isList() || map.size() == 0) {\n>> +\t\t\tLOG(Converter, Error)\n>> +\t\t\t\t<< \"Incorrect mapping entries\";\n>> +\t\t\treturn -EINVAL;\n>> +\t\t}\n>> +\n>> +\t\tSize input(input_res[0].get<uint32_t>(0), input_res[1].get<uint32_t>(0));\n>> +\t\tSize output(output_res[0].get<uint32_t>(0), output_res[1].get<uint32_t>(0));\n>> +\t\tconst auto &mapVector = map.getList<uint32_t>().value_or(utils::defopt);\n>> +\n>> +\t\tLOG(Converter, Debug)\n>> +\t\t\t<< \"Input/Output mapping resolution \" << input << \" ---> \" << output;\n>> +\t\tmappings_.emplace_back(Mapping(input, output, mapVector));\n>> +\t}\n>> +\n>> +\treturn mappings.size();\n>> +}\n>> +\n>> +std::vector<PixelFormat> V4L2M2MConverter::formats(PixelFormat input)\n>> +{\n>> +\tif (!m2m_)\n>> +\t\treturn {};\n>> +\n>> +\t/*\n>> +\t * Set the format on the input side (V4L2 output) of the converter to\n>> +\t * enumerate the conversion capabilities on its output (V4L2 capture).\n>> +\t */\n>> +\tV4L2DeviceFormat v4l2Format;\n>> +\tv4l2Format.fourcc = m2m_->output()->toV4L2PixelFormat(input);\n>> +\tv4l2Format.size = { 1, 1 };\n>> +\n>> +\tint ret = m2m_->output()->setFormat(&v4l2Format);\n>> +\tif (ret < 0) {\n>> +\t\tLOG(Converter, Error)\n>> +\t\t\t<< \"Failed to set format: \" << strerror(-ret);\n>> +\t\treturn {};\n>> +\t}\n>> +\n>> +\tif (v4l2Format.fourcc != m2m_->output()->toV4L2PixelFormat(input)) {\n>> +\t\tLOG(Converter, Debug)\n>> +\t\t\t<< \"Input format \" << input << \" not supported.\";\n>> +\t\treturn {};\n>> +\t}\n>> +\n>> +\tstd::vector<PixelFormat> pixelFormats;\n>> +\n>> +\tfor (const auto &format : m2m_->capture()->formats()) {\n>> +\t\tPixelFormat pixelFormat = format.first.toPixelFormat();\n>> +\t\tif (pixelFormat)\n>> +\t\t\tpixelFormats.push_back(pixelFormat);\n>> +\t}\n>> +\n>> +\treturn pixelFormats;\n>> +}\n>> +\n>> +SizeRange V4L2M2MConverter::sizes(const Size &input)\n>> +{\n>> +\tif (!m2m_)\n>> +\t\treturn {};\n>> +\n>> +\t/*\n>> +\t * Set the size on the input side (V4L2 output) of the converter to\n>> +\t * enumerate the scaling capabilities on its output (V4L2 capture).\n>> +\t */\n>> +\tV4L2DeviceFormat format;\n>> +\tformat.fourcc = V4L2PixelFormat();\n>> +\tformat.size = input;\n>> +\n>> +\tint ret = m2m_->output()->setFormat(&format);\n>> +\tif (ret < 0) {\n>> +\t\tLOG(Converter, Error)\n>> +\t\t\t<< \"Failed to set format: \" << strerror(-ret);\n>> +\t\treturn {};\n>> +\t}\n>> +\n>> +\tSizeRange sizes;\n>> +\n>> +\tformat.size = { 1, 1 };\n>> +\tret = m2m_->capture()->setFormat(&format);\n>> +\tif (ret < 0) {\n>> +\t\tLOG(Converter, Error)\n>> +\t\t\t<< \"Failed to set format: \" << strerror(-ret);\n>> +\t\treturn {};\n>> +\t}\n>> +\n>> +\tsizes.min = format.size;\n>> +\n>> +\tformat.size = { UINT_MAX, UINT_MAX };\n>> +\tret = m2m_->capture()->setFormat(&format);\n>> +\tif (ret < 0) {\n>> +\t\tLOG(Converter, Error)\n>> +\t\t\t<< \"Failed to set format: \" << strerror(-ret);\n>> +\t\treturn {};\n>> +\t}\n>> +\n>> +\tsizes.max = format.size;\n>> +\n>> +\treturn sizes;\n>> +}\n>> +\n>> +std::tuple<unsigned int, unsigned int>\n>> +V4L2M2MConverter::strideAndFrameSize(const PixelFormat &pixelFormat,\n>> +\t\t\t\t     const Size &size)\n>> +{\n>> +\tV4L2DeviceFormat format;\n>> +\tformat.fourcc = m2m_->capture()->toV4L2PixelFormat(pixelFormat);\n>> +\tformat.size = size;\n>> +\n>> +\tint ret = m2m_->capture()->tryFormat(&format);\n>> +\tif (ret < 0)\n>> +\t\treturn std::make_tuple(0, 0);\n>> +\n>> +\treturn std::make_tuple(format.planes[0].bpl, format.planes[0].size);\n>> +}\n>> +\n>> +int V4L2M2MConverter::configure(const StreamConfiguration &inputCfg,\n>> +\t\t\t\tconst std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs)\n>> +{\n>> +\tint ret = 0;\n>> +\n>> +\tstreams_.clear();\n>> +\tstreams_.reserve(outputCfgs.size());\n>> +\n>> +\tfor (unsigned int i = 0; i < outputCfgs.size(); ++i) {\n>> +\t\tStream &stream = streams_.emplace_back(this, i);\n>> +\n>> +\t\tif (!stream.isValid()) {\n>> +\t\t\tLOG(Converter, Error)\n>> +\t\t\t\t<< \"Failed to create stream \" << i;\n>> +\t\t\tret = -EINVAL;\n>> +\t\t\tbreak;\n>> +\t\t}\n>> +\n>> +\t\tret = stream.configure(inputCfg, outputCfgs[i]);\n>> +\t\tif (ret < 0)\n>> +\t\t\tbreak;\n>> +\t}\n>> +\n>> +\tif (ret < 0) {\n>> +\t\tstreams_.clear();\n>> +\t\treturn ret;\n>> +\t}\n>> +\n>> +\treturn 0;\n>> +}\n>> +\n>> +int V4L2M2MConverter::exportBuffers(unsigned int output, unsigned int count,\n>> +\t\t\t\t    std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n>> +{\n>> +\tif (output >= streams_.size())\n>> +\t\treturn -EINVAL;\n>> +\n>> +\treturn streams_[output].exportBuffers(count, buffers);\n>> +}\n>> +\n>> +int V4L2M2MConverter::start()\n>> +{\n>> +\tint ret;\n>> +\n>> +\tfor (Stream &stream : streams_) {\n>> +\t\tret = stream.start();\n>> +\t\tif (ret < 0) {\n>> +\t\t\tstop();\n>> +\t\t\treturn ret;\n>> +\t\t}\n>> +\t}\n>> +\n>> +\treturn 0;\n>> +}\n>> +\n>> +void V4L2M2MConverter::stop()\n>> +{\n>> +\tfor (Stream &stream : utils::reverse(streams_))\n>> +\t\tstream.stop();\n>> +}\n>> +\n>> +int V4L2M2MConverter::queueBuffers(FrameBuffer *input,\n>> +\t\t\t\t   const std::map<unsigned int, FrameBuffer *> &outputs)\n>> +{\n>> +\tunsigned int mask = 0;\n>> +\tint ret;\n>> +\n>> +\t/*\n>> +\t * Validate the outputs as a sanity check: at least one output is\n>> +\t * required, all outputs must reference a valid stream and no two\n>> +\t * outputs can reference the same stream.\n>> +\t */\n>> +\tif (outputs.empty())\n>> +\t\treturn -EINVAL;\n>> +\n>> +\tfor (auto [index, buffer] : outputs) {\n>> +\t\tif (!buffer)\n>> +\t\t\treturn -EINVAL;\n>> +\t\tif (index >= streams_.size())\n>> +\t\t\treturn -EINVAL;\n>> +\t\tif (mask & (1 << index))\n>> +\t\t\treturn -EINVAL;\n>> +\n>> +\t\tmask |= 1 << index;\n>> +\t}\n>> +\n>> +\t/* Queue the input and output buffers to all the streams. */\n>> +\tfor (auto [index, buffer] : outputs) {\n>> +\t\tret = streams_[index].queueBuffers(input, buffer);\n>> +\t\tif (ret < 0)\n>> +\t\t\treturn ret;\n>> +\t}\n>> +\n>> +\t/*\n>> +\t * Add the input buffer to the queue, with the number of streams as a\n>> +\t * reference count. Completion of the input buffer will be signalled by\n>> +\t * the stream that releases the last reference.\n>> +\t */\n>> +\tqueue_.emplace(std::piecewise_construct,\n>> +\t\t       std::forward_as_tuple(input),\n>> +\t\t       std::forward_as_tuple(outputs.size()));\n>> +\n>> +\treturn 0;\n>> +}\n>> +\n>> +REGISTER_CONVERTER(\"v4l2_m2m\", V4L2M2MConverter, \"pxp\")\n>> +\n>> +} /* namespace libcamera */\n>> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\n>> index a261d4b4..b12c8401 100644\n>> --- a/src/libcamera/meson.build\n>> +++ b/src/libcamera/meson.build\n>> @@ -14,6 +14,7 @@ libcamera_sources = files([\n>>       'control_serializer.cpp',\n>>       'control_validator.cpp',\n>>       'converter.cpp',\n>> +    'converter_v4l2_m2m.cpp',\n>>       'delayed_controls.cpp',\n>>       'device_enumerator.cpp',\n>>       'device_enumerator_sysfs.cpp',\n>> --\n>> 2.37.3\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 7CBEAC3272\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 19 Sep 2022 08:46:15 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id A996A62191;\n\tMon, 19 Sep 2022 10:46:14 +0200 (CEST)","from EUR01-VE1-obe.outbound.protection.outlook.com\n\t(mail-eopbgr140054.outbound.protection.outlook.com [40.107.14.54])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id AFB6662046\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 19 Sep 2022 10:46:12 +0200 (CEST)","from PAXPR04MB8703.eurprd04.prod.outlook.com\n\t(2603:10a6:102:21e::22)\n\tby AS8PR04MB7605.eurprd04.prod.outlook.com (2603:10a6:20b:292::6)\n\twith Microsoft SMTP Server (version=TLS1_2,\n\tcipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.5632.18;\n\tMon, 19 Sep 2022 08:46:10 +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%5]) with mapi id 15.20.5632.021;\n\tMon, 19 Sep 2022 08:46:10 +0000"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1663577174;\n\tbh=v0aWfn2xxwEUS3YTMej4uFM419NuO5jI6ff00CEs5Yw=;\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=RurDybbXdzIrfi3UZ+UFeGCP6qIPYIpDFKlt8X7Zaq1cfz24OBxCt7VORZaEL9hUb\n\tXC5XmVuE3PTmxoNqMObPpJ5dLxHj6omUlEL+0w5lSg9MJzarY/s9G6hx7B57L2yzXZ\n\tQ5ZjvPyXLb0kBbG/ZQ9TvCWlKpALae2yEaALQkywxgOw0+9ZgClDDx/i7kfndRq/Hd\n\teflk+wsjGzTLSbr/JDVbdAxSMAxgFAsHdKIsAusH4yOmyxuvYA27Qg0nBf48VPtIOn\n\tIlYh2QSXekZZ+Av+jG/Nx8N2m0CMpWjpvmcoTi8Yni8o18HnGZlRV2/RkTz04KdPbj\n\tLKl2RGrDosZWQ==","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=i1+3kMhkkI1OmEf/jZWt7O50ir6KAh3WfSn0CJCcuHM=;\n\tb=bDMBXppgzQaqyTJwDta/zDeF5K2/b66WAXiqZ3qbkLh2/TSLeOaJPyswWClI9nB4RqDNDXkY6mqHAlbtcpqgRnOJpai4q+4drdj7m6eALghWiGFT4Sd4/SrA6dukPd2iSl/JXPjQ5Z8/3+QT7wfIMbgj3uns1QZDJZ0y+w+Dde4="],"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=\"bDMBXppg\"; 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=j84IlSFS9k3QSWLeE+fFrEd1xZ2cnKnb6VmFvL1SSJPST5zb24u8Ibz5XoOHyYFeIbmaa5XEO4sBBj3o4RLbQErbSr63m0JBhcx6aAHoUt+vnDWYThdu4orAezBQgXCYmB4S3ui0qkJ+rQv0yoKikfYe9xqfLhIunXyxsXRAPUSU3Cg0CdTDnDLnvoP2eBU6d8u3dGQubOPQggdPvAoa270o1D/GY2B87TRGXwcn9x1FecJy4a+GqgTxpMVR9eN6Yk0gOn8UJDaOCyV7orbTm6Y1wiEcyQnNFeIfmeFp/3XAuU0BMnF6a6XyH8Xh3Xbq4iqcQGco69noioAGP6SOkg==","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=i1+3kMhkkI1OmEf/jZWt7O50ir6KAh3WfSn0CJCcuHM=;\n\tb=R/0vZ7O4eOTVjr5x68O/rWxRuEZodrvQ1NnfomlUeJTxZfQPKHhsDcQIdHSHa5UbYws/uWvpm5e75qnVlHfuT6I5N7dNzYLjt3ZH1Ki+UUBj3Ns3XLpHbLT1ugYnaDND5KqSiJZjZtPCYgdIwp2c4QLJ3YP/H2wgOWQLkbH4AhyqzOOfnchSU5JvCerdUff8JhA8E9vsCzYlbxNYigsGMvy7B4TOyIYTtYaZIkYrj9mIoCpdPuqhnfAvEJu1Gsxx5kxO/kTz/yv/DLvlG+MCl4YBUkmrV/pdE3TFZYhAb6JiMUaj9pPZSCEqKVzUnLzclFBDbES/UYbV4V+CN0MnWA==","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":"<47d506ef-e0a1-718c-72dc-66238b1514d7@oss.nxp.com>","Date":"Mon, 19 Sep 2022 10:45:59 +0200","User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101\n\tThunderbird/102.2.1","To":"Jacopo Mondi <jacopo@jmondi.org>","References":"<20220908184850.1874303-1-xavier.roumegue@oss.nxp.com>\n\t<20220908184850.1874303-7-xavier.roumegue@oss.nxp.com>\n\t<20220915094621.vtc5ffmdw6hjb3js@uno.localdomain>","Content-Language":"en-US","In-Reply-To":"<20220915094621.vtc5ffmdw6hjb3js@uno.localdomain>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"7bit","X-ClientProxiedBy":"BYAPR11CA0075.namprd11.prod.outlook.com\n\t(2603:10b6:a03:f4::16) 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_|AS8PR04MB7605:EE_","X-MS-Office365-Filtering-Correlation-Id":"1c913bf8-c700-446a-e86b-08da9a1b6618","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":"Zgt1PHuXFbdBba7usO8XWPtYcCDH2StO3L/aidLEk5DXpGQWCSChTysboW0Mu830mU/d2Z7TcYuaTWiNMOR50xdT89FJmZZiZ1IrSRa5FSRZBb7joRpaAABGd9AG9eUW0l4tjeYSlvfOVSLcgqd1CoyIlrqRzgv4wxNETjUjnEAhqnGqMGlHOE8p3ksM0UmK0+6fK/qQRTBe1wO1T9AvWdWhCrhX59HmryZZ0V77jZMz06mfj42r5bH3JlnDJQVUwwzooUN7iwSqEVXIvSIT39DCe6owZeIMnA/0FENctRt6AlH/CNUBP6cH8z7XHE2PQ5MyOe+jSjLV5Bvb81fwH9sn5wKLhuSwX7H/MTcKkOmWGNeiUrJT1Bmz9znDXyMVr9mQo05B6P2Hx8KA9mvLIH3XnVppDmsWGJiCp6eNtzbsQJLbhXVIp3tzUz5rbOkACxnYzUkZwtUE4j92B9qX/Vdak1BCAaR3Q+b9ZpdGx2cy3hd7uzalb72mxZR9adPPZM6/2ZjiGsZhHpm0qPFMFCgrC7b8P5jZ1aBuX+DONh6cFa/uJlKHIA/ior6/DNP4nEhJOBktg3tA+Y7bsrV2j3QOutP3cnIcqXIXjA7gUNiL7Qcqv4/Fba9JXKRFFdJ/sXhpVQ4v+BvDEb53gNvQLYhtI3LdtefxuAZsMbHiAsyOT4tVNc85cjrkjppjvrYnZco/mbPqUtyNiNwOuqvnwJZj8DBtG6QQdy6YXvOTHdaPRKSDg/rF/R3ysOQeu1KDxBTVcwxx0fTfD2QeLxV+2Zu6Z+tK2i05ej52BbrsuTTHlT27uuoECTrPuwLr3YL07WSiHnCTMfYeYXVUGhLqYg==","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)(396003)(136003)(346002)(366004)(39860400002)(376002)(451199015)(30864003)(2906002)(31696002)(86362001)(5660300002)(8936002)(316002)(83380400001)(6916009)(6512007)(53546011)(26005)(6506007)(186003)(6666004)(52116002)(41300700001)(2616005)(38100700002)(478600001)(38350700002)(6486002)(4326008)(31686004)(66556008)(66476007)(66946007)(8676002)(45980500001)(43740500002);\n\tDIR:OUT; SFP:1101; ","X-MS-Exchange-AntiSpam-MessageData-ChunkCount":"1","X-MS-Exchange-AntiSpam-MessageData-0":"=?utf-8?q?TDxnOp9lV466pkMPbyGdgY4Nj?=\n\t=?utf-8?q?ktDg8yVKxUO7bcGzIHdJXpY7nAsaQ0cq/oykYpHyQSAdBwOildtqar0F?=\n\t=?utf-8?q?cskOXYLD48VQsuvf4ciJIZqhNt4Fr29XH1J9LBGv1oZ/FVcS88Vm2e4I?=\n\t=?utf-8?q?dXU5z70e229GJNc1tIl4RXNkUMcl2Gyxzxony4Zhrzq/H80hkV5fJ01G?=\n\t=?utf-8?q?RFLjtgdUCsBR5GfM1qmdygibic0p0XqmAvj7VneaQQUfxS9yU4kaa3Dy?=\n\t=?utf-8?q?ok3Z673WvB0j6oJz0CdJ38Ls+Arvqo2XEAlBcI+g9cOZgTRXnX/IGjWd?=\n\t=?utf-8?q?iDb0+iQqiyJvzMZ8XEUro+ddI64iVw819z+tm6UCjnxnFEUYkhWAeT/n?=\n\t=?utf-8?q?jLUHnxcP0GYW/caJjRtsAyOX3xIbxGrM4qWvpRfDRAppsJSQqhTZax/B?=\n\t=?utf-8?q?XcG7i/efvZbokHUZ7Ve3Hpk9KxPSIigGxV2kbPU5hmjYyhtxXLxLS3Qq?=\n\t=?utf-8?q?WBiZZmZVmz42vqNYPB9RLHVV0HZFvzHFtgpsU18ATNsni4DNvusSTANa?=\n\t=?utf-8?q?i+E5o6qw9bPIHL9w1aSYSn9Q+1OUeY+gpciwYEUxhyADNOYBO/DU8B3K?=\n\t=?utf-8?q?+IriWWyjQ/vWEhsQcnlpzR0CAinPpXQXrNNwAJjNN+i6WMz05pxXrYZA?=\n\t=?utf-8?q?23KVqjVgxNAV8uJJLf5S5j5Idh1+sCH1QGjRi4E6Xgip0KvdkF1zIQT9?=\n\t=?utf-8?q?KsgofazSVBd/DE4ekijLFwyZ+4dM21fXcRjRoeACke1hOOLqqFPY6OMg?=\n\t=?utf-8?q?KFWU1RsffAgAZPcNT0UcyuvwsLlR5UXVvn+Npi3WAJTpetA3mUMe2PaJ?=\n\t=?utf-8?q?W+fdPxMnLsTzxB0p1k//5nHIXULqbXCUFSHvy85fFYP0CzKbY+rK2OUU?=\n\t=?utf-8?q?VRSCm/DjZEcEOcu+v6uXaLlfF1R8Mmy1tMHy0JYdgwPyQQ2fBLbXNm18?=\n\t=?utf-8?q?acXRNCNav7fx+Aj5YKUFJOy+5P6abvmfaPpRPXlf9CJiDmbCv4NCVYt4?=\n\t=?utf-8?q?SuZilEi4jl2zIaazUzah/zqffsp2vLu/jtsc/SYHTGu5ur4UxAcXj83+?=\n\t=?utf-8?q?JbP3fa3dlIzLCvD1ZZL5+C2z9fl4cBpJJpdBXovHFSh4mgL+sfWl5fjw?=\n\t=?utf-8?q?UOGgH0kfzQZrHDLFjyU8/YXymWgyLElVNXBx5AzeiQda4dSI+cWLK9N5?=\n\t=?utf-8?q?Gg5O6+lz80/avVzqWp89CuiYwmxFk5grYVwmb6ExSvHCwxtiN5sUilBs?=\n\t=?utf-8?q?B/EeF0try659vKwijpS29ADndwwu5D5jYQztueNkNKzVhvkpPqXW7ROe?=\n\t=?utf-8?q?HXNVPQqUGpb3bpnPgB3z9GmL4xITMOY5OOzevtwrjomBxX1j2g5QGmbf?=\n\t=?utf-8?q?9sbH79PjYQ0SUTBtceeMLlSQoJ5OAD0Yn76oRtG5VMaxpjZE7oSN0XIx?=\n\t=?utf-8?q?mzOJDqY1dlOSXxy9xQFQ8/i/WklGTtAnhA/+Bh2AR46H5TQQ5BLeYLdr?=\n\t=?utf-8?q?+gXvANUtYNpeyfd0fUxTwXmUYrppcBbA6gEKL41mMrUyLqvF5GE9CJjY?=\n\t=?utf-8?q?BZBrtj+XiKAdtXubVJYcxZf2vb57rvzTt60fW13kl220ci+rY2+PTPpr?=\n\t=?utf-8?q?xH1jnjhRIlzU08iPP6xZBOFS02zRhVitqy7HBPmTBFIpQyaXgIEoXE3J?=\n\t=?utf-8?q?Ah7o0Ic?=","X-OriginatorOrg":"oss.nxp.com","X-MS-Exchange-CrossTenant-Network-Message-Id":"1c913bf8-c700-446a-e86b-08da9a1b6618","X-MS-Exchange-CrossTenant-AuthSource":"PAXPR04MB8703.eurprd04.prod.outlook.com","X-MS-Exchange-CrossTenant-AuthAs":"Internal","X-MS-Exchange-CrossTenant-OriginalArrivalTime":"19 Sep 2022 08:46:10.0610\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":"Pn0S+LXm11+G4WJKabvFVJbg5OeZE9EIFMCqWBfhbb56TOPqekVodLHACWXQemfkoofWATIMv9kW8k/4Hj+xsw==","X-MS-Exchange-Transport-CrossTenantHeadersStamped":"AS8PR04MB7605","Subject":"Re: [libcamera-devel] [PATCH 06/14] libcamera: converter: Add v4l2\n\tm2m converter implementation","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>"}},{"id":25247,"web_url":"https://patchwork.libcamera.org/comment/25247/","msgid":"<YztorCM4P1YM6qip@pendragon.ideasonboard.com>","date":"2022-10-03T22:56:44","subject":"Re: [libcamera-devel] [PATCH 06/14] libcamera: converter: Add v4l2\n\tm2m converter implementation","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hello,\n\ns/v4l2 m2m/V4L2 M2M/ in the subject line.\n\nOn Mon, Sep 19, 2022 at 10:45:59AM +0200, Xavier Roumegue (OSS) via libcamera-devel wrote:\n> On 9/15/22 11:46, Jacopo Mondi wrote:\n> > On Thu, Sep 08, 2022 at 08:48:42PM +0200, Xavier Roumegue via libcamera-devel wrote:\n> >> Introduce a converter implementation relying on a v4l2 m2m device, mostly\n\nHere too.\n\n> >> based on the current simple pipeline converter implementation.\n> >>\n> >> The main change is the introduction of Mapping object which can be\n> >> loaded through a configuration file which define vertices remapping\n> >> coordinates. Those latters can be applied by any classes derived from\n> >> this base class which define the apply_mapping() method.\n\ns/apply_mapping() method/applyMapping() function/\n\nCould you please split this patch in two, with a first patch that copies\nthe simple pipeline handler converter with minimal changes (ideally no\nchanges beside the class rename, if there are any other changes, please\ndocument them in the commit message), and a second patch to add support\nfor the mappings ? That will be easier to review. I would actually\nsquash patch 10/14 into this one, to show that this is really just a\nmove.\n\n> > I'll skip questions on things not clear to me about the converter\n> > implementation as I understand this basically comes from the simple\n> > pipeline converter implementation [*]\n> > \n> > I would then focus more on two general questions:\n> > 1) Do we want to make a dir for converter/post-processors instead of\n> > placing them in src/libcamera\n> \n> Might make sense, or could be done later - that's not my call.\n> I can move them to a dedicated folder on the next patch set version if there is \n> a willing to do so.\n\nsrc/libcamera/converter/ sounds good to me.\n\n> > 2) Is the format expected in the configuration file documented\n> > anywhere ?\n> \n> Nope. I will do it on the next version, hopefully on an agreed configuration \n> file format.\n> \n> > [*] Not true, I will ask a question anyway, see below :)\n> > \n> >> Signed-off-by: Xavier Roumegue <xavier.roumegue@oss.nxp.com>\n> >> ---\n> >>   .../libcamera/internal/converter_v4l2_m2m.h   | 120 +++++\n> >>   include/libcamera/internal/meson.build        |   1 +\n> >>   src/libcamera/converter_v4l2_m2m.cpp          | 504 ++++++++++++++++++\n> >>   src/libcamera/meson.build                     |   1 +\n> >>   4 files changed, 626 insertions(+)\n> >>   create mode 100644 include/libcamera/internal/converter_v4l2_m2m.h\n> >>   create mode 100644 src/libcamera/converter_v4l2_m2m.cpp\n> >>\n> >> diff --git a/include/libcamera/internal/converter_v4l2_m2m.h b/include/libcamera/internal/converter_v4l2_m2m.h\n> >> new file mode 100644\n> >> index 00000000..3667b128\n> >> --- /dev/null\n> >> +++ b/include/libcamera/internal/converter_v4l2_m2m.h\n> >> @@ -0,0 +1,120 @@\n> >> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> >> +/*\n> >> + * Copyright 2022 NXP\n> >> + *\n> >> + * converter_v4l2_m2m.h - V4l2 M2M Format converter interface\n> >> + */\n> >> +\n> >> +#pragma once\n> >> +\n> >> +#include <functional>\n> >> +#include <map>\n> >> +#include <memory>\n> >> +#include <string>\n> >> +#include <tuple>\n> >> +#include <vector>\n> >> +\n> >> +#include <libcamera/base/log.h>\n> >> +#include <libcamera/base/signal.h>\n> >> +\n> >> +#include <libcamera/geometry.h>\n> >> +#include <libcamera/pixel_format.h>\n> >> +\n> >> +#include \"libcamera/internal/converter.h\"\n> >> +\n> >> +namespace libcamera {\n> >> +\n> >> +class FrameBuffer;\n> >> +class MediaDevice;\n> >> +class Size;\n> >> +class SizeRange;\n> >> +struct StreamConfiguration;\n> >> +class V4L2M2MDevice;\n> >> +class V4L2M2MConverter;\n> >> +class Converter;\n> >> +\n> >> +class V4L2M2MConverter : public Converter\n> >> +{\n> >> +protected:\n> >> +\tclass Mapping\n> >> +\t{\n> >> +\tpublic:\n> >> +\t\tMapping(const Size &input, const Size &output, const std::vector<uint32_t> &map)\n> >> +\t\t\t: input_(input), output_(output), map_(map) {}\n> >> +\t\tSize getInputSize() const { return input_; }\n> >> +\t\tSize getOutputSize() const { return output_; }\n> >> +\t\tstd::size_t getLength() const { return map_.size(); }\n> >> +\t\tconst uint32_t *getMapping() const { return map_.data(); }\n> >> +\n> >> +\tprivate:\n> >> +\t\tSize input_;\n> >> +\t\tSize output_;\n> >> +\t\tstd::vector<uint32_t> map_;\n> >> +\t};\n> >> +\n> >> +\tclass Stream : protected Loggable\n> >> +\t{\n> >> +\tpublic:\n> >> +\t\tStream(V4L2M2MConverter *converter, unsigned int index);\n> >> +\n> >> +\t\tbool isValid() const { return m2m_ != nullptr; }\n> >> +\n> >> +\t\tint configure(const StreamConfiguration &inputCfg,\n> >> +\t\t\t      const StreamConfiguration &outputCfg);\n> >> +\t\tint exportBuffers(unsigned int count,\n> >> +\t\t\t\t  std::vector<std::unique_ptr<FrameBuffer>> *buffers);\n> >> +\n> >> +\t\tint start();\n> >> +\t\tvoid stop();\n> >> +\n> >> +\t\tint queueBuffers(FrameBuffer *input, FrameBuffer *output);\n> >> +\t\tstd::unique_ptr<V4L2M2MDevice> m2m_;\n> >> +\n> >> +\tprotected:\n> >> +\t\tstd::string logPrefix() const override;\n> >> +\n> >> +\tprivate:\n> >> +\t\tvoid captureBufferReady(FrameBuffer *buffer);\n> >> +\t\tvoid outputBufferReady(FrameBuffer *buffer);\n> >> +\n> >> +\t\tV4L2M2MConverter *converter_;\n> >> +\t\tunsigned int index_;\n> >> +\n> >> +\t\tunsigned int inputBufferCount_;\n> >> +\t\tunsigned int outputBufferCount_;\n> >> +\t};\n> >> +\n> >> +\tstd::unique_ptr<V4L2M2MDevice> m2m_;\n> >> +\n> >> +\tstd::vector<Stream> streams_;\n> >> +\tstd::vector<Mapping> mappings_;\n> >> +\tstd::map<FrameBuffer *, unsigned int> queue_;\n> >> +\n> >> +public:\n> >> +\tV4L2M2MConverter(MediaDevice *media);\n> >> +\n> >> +\tint loadConfiguration(const std::string &filename) override;\n> >> +\n> >> +\tbool isValid() const { return m2m_ != nullptr; }\n> >> +\n> >> +\tstd::vector<PixelFormat> formats(PixelFormat input);\n> >> +\tSizeRange sizes(const Size &input);\n> >> +\n> >> +\tstd::tuple<unsigned int, unsigned int>\n> >> +\tstrideAndFrameSize(const PixelFormat &pixelFormat, const Size &size);\n> >> +\n> >> +\tint configure(const StreamConfiguration &inputCfg,\n> >> +\t\t      const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfg);\n> >> +\tint exportBuffers(unsigned int ouput, unsigned int count,\n> >> +\t\t\t  std::vector<std::unique_ptr<FrameBuffer>> *buffers);\n> >> +\n> >> +\tint start();\n> >> +\tvoid stop();\n> >> +\n> >> +\tint queueBuffers(FrameBuffer *input,\n> >> +\t\t\t const std::map<unsigned int, FrameBuffer *> &outputs);\n> >> +\n> >> +\tvirtual int applyMapping([[maybe_unused]] Stream *stream, [[maybe_unused]] Mapping &mapping) { return 0; };\n\nThis should be protected.\n\n> >> +};\n> >> +\n> >> +} /* namespace libcamera */\n> >> diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build\n> >> index 8f50d755..132de5ef 100644\n> >> --- a/include/libcamera/internal/meson.build\n> >> +++ b/include/libcamera/internal/meson.build\n> >> @@ -20,6 +20,7 @@ libcamera_internal_headers = files([\n> >>       'control_serializer.h',\n> >>       'control_validator.h',\n> >>       'converter.h',\n> >> +    'converter_v4l2_m2m.h',\n> >>       'delayed_controls.h',\n> >>       'device_enumerator.h',\n> >>       'device_enumerator_sysfs.h',\n> >> diff --git a/src/libcamera/converter_v4l2_m2m.cpp b/src/libcamera/converter_v4l2_m2m.cpp\n> >> new file mode 100644\n> >> index 00000000..942e6e6f\n> >> --- /dev/null\n> >> +++ b/src/libcamera/converter_v4l2_m2m.cpp\n> >> @@ -0,0 +1,504 @@\n> >> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> >> +/*\n> >> + * Copyright (C) 2020, Laurent Pinchart\n> >> + * Copyright 2022 NXP\n> >> + *\n> >> + * converter_v4l2_m2m.cpp - V4L2 M2M Format converter\n> >> + */\n> >> +\n> >> +#include <algorithm>\n> >> +#include <limits.h>\n> >> +\n> >> +#include <libcamera/base/file.h>\n> >> +#include <libcamera/base/log.h>\n> >> +#include <libcamera/base/signal.h>\n> >> +#include <libcamera/base/utils.h>\n> >> +\n> >> +#include <libcamera/framebuffer.h>\n> >> +#include <libcamera/geometry.h>\n> >> +#include <libcamera/stream.h>\n> >> +\n> >> +#include \"libcamera/internal/converter_v4l2_m2m.h\"\n> >> +#include \"libcamera/internal/media_device.h\"\n> >> +#include \"libcamera/internal/v4l2_videodevice.h\"\n> >> +#include \"libcamera/internal/yaml_parser.h\"\n> >> +\n> >> +namespace libcamera {\n> >> +\n> >> +LOG_DECLARE_CATEGORY(Converter)\n> >> +\n> >> +/* -----------------------------------------------------------------------------\n> >> + * V4L2M2MConverter::Stream\n> >> + */\n> >> +\n> >> +V4L2M2MConverter::Stream::Stream(V4L2M2MConverter *converter, unsigned int index)\n> >> +\t: converter_(converter), index_(index)\n> >> +{\n> >> +\tm2m_ = std::make_unique<V4L2M2MDevice>(converter->deviceNode_);\n> >> +\n> >> +\tm2m_->output()->bufferReady.connect(this, &Stream::outputBufferReady);\n> >> +\tm2m_->capture()->bufferReady.connect(this, &Stream::captureBufferReady);\n> >> +\n> >> +\tint ret = m2m_->open();\n> >> +\tif (ret < 0)\n> >> +\t\tm2m_.reset();\n> > \n> > Why are we re-creating and opening the m2m_ device everytime we create\n> > a stream ? Isn't it the same created and open when the\n> > V4L2M2MConverter is created ? Would it be for proper reference counting ?\n\nThat's how the V4L2 M2M API works, every open creates a separate\nprocessing context.\n\n> >> +}\n> >> +\n> >> +int V4L2M2MConverter::Stream::configure(const StreamConfiguration &inputCfg,\n> >> +\t\t\t\t\tconst StreamConfiguration &outputCfg)\n> >> +{\n> >> +\tV4L2PixelFormat videoFormat =\n> >> +\t\tm2m_->output()->toV4L2PixelFormat(inputCfg.pixelFormat);\n> >> +\n> >> +\tV4L2DeviceFormat format;\n> >> +\tformat.fourcc = videoFormat;\n> >> +\tformat.size = inputCfg.size;\n> >> +\tformat.planesCount = 1;\n> >> +\tformat.planes[0].bpl = inputCfg.stride;\n> >> +\n> >> +\tint ret = m2m_->output()->setFormat(&format);\n> >> +\tif (ret < 0) {\n> >> +\t\tLOG(Converter, Error)\n> >> +\t\t\t<< \"Failed to set input format: \" << strerror(-ret);\n> >> +\t\treturn ret;\n> >> +\t}\n> >> +\n> >> +\tif (format.fourcc != videoFormat || format.size != inputCfg.size ||\n> >> +\t    format.planes[0].bpl != inputCfg.stride) {\n> >> +\t\tLOG(Converter, Error)\n> >> +\t\t\t<< \"Input format not supported (requested \"\n> >> +\t\t\t<< inputCfg.size << \"-\" << videoFormat\n> >> +\t\t\t<< \", got \" << format << \")\";\n> >> +\t\treturn -EINVAL;\n> >> +\t}\n> >> +\n> >> +\t/* Set the pixel format and size on the output. */\n> >> +\tvideoFormat = m2m_->capture()->toV4L2PixelFormat(outputCfg.pixelFormat);\n> >> +\tformat = {};\n> >> +\tformat.fourcc = videoFormat;\n> >> +\tformat.size = outputCfg.size;\n> >> +\n> >> +\tret = m2m_->capture()->setFormat(&format);\n> >> +\tif (ret < 0) {\n> >> +\t\tLOG(Converter, Error)\n> >> +\t\t\t<< \"Failed to set output format: \" << strerror(-ret);\n> >> +\t\treturn ret;\n> >> +\t}\n> >> +\n> >> +\tif (format.fourcc != videoFormat || format.size != outputCfg.size) {\n> >> +\t\tLOG(Converter, Error)\n> >> +\t\t\t<< \"Output format not supported\";\n> >> +\t\treturn -EINVAL;\n> >> +\t}\n> >> +\n> >> +\tinputBufferCount_ = inputCfg.bufferCount;\n> >> +\toutputBufferCount_ = outputCfg.bufferCount;\n> >> +\n> >> +\tfor (Mapping &mapping : converter_->mappings_) {\n> >> +\t\tControlList ctrls;\n> >> +\t\tif (mapping.getInputSize() == inputCfg.size && mapping.getOutputSize() == outputCfg.size) {\n> >> +\t\t\tLOG(Converter, Debug)\n> >> +\t\t\t\t<< \"Got a configuration match \"\n> >> +\t\t\t\t<< inputCfg.size << \" --> \" << outputCfg.size;\n> >> +\t\t\tconverter_->applyMapping(this, mapping);\n> >> +\t\t}\n> >> +\t}\n\nThis won't work very well, as the ISP can scale to arbitrary\nresolutions. I think you should instead express the mapping in the\nconfiguration file using floating-point values, and resample it when\nconfiguring the stream. The crop rectangle, if any, will also need to be\ntaken into account, but that could be done later (a \\todo comment should\nbe added then).\n\n> >> +\n> >> +\treturn 0;\n> >> +}\n> >> +\n> >> +int V4L2M2MConverter::Stream::exportBuffers(unsigned int count,\n> >> +\t\t\t\t\t    std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n> >> +{\n> >> +\treturn m2m_->capture()->exportBuffers(count, buffers);\n> >> +}\n> >> +\n> >> +int V4L2M2MConverter::Stream::start()\n> >> +{\n> >> +\tint ret = m2m_->output()->importBuffers(inputBufferCount_);\n> >> +\tif (ret < 0)\n> >> +\t\treturn ret;\n> >> +\n> >> +\tret = m2m_->capture()->importBuffers(outputBufferCount_);\n> >> +\tif (ret < 0) {\n> >> +\t\tstop();\n> >> +\t\treturn ret;\n> >> +\t}\n> >> +\n> >> +\tret = m2m_->output()->streamOn();\n> >> +\tif (ret < 0) {\n> >> +\t\tstop();\n> >> +\t\treturn ret;\n> >> +\t}\n> >> +\n> >> +\tret = m2m_->capture()->streamOn();\n> >> +\tif (ret < 0) {\n> >> +\t\tstop();\n> >> +\t\treturn ret;\n> >> +\t}\n> >> +\n> >> +\treturn 0;\n> >> +}\n> >> +\n> >> +void V4L2M2MConverter::Stream::stop()\n> >> +{\n> >> +\tm2m_->capture()->streamOff();\n> >> +\tm2m_->output()->streamOff();\n> >> +\tm2m_->capture()->releaseBuffers();\n> >> +\tm2m_->output()->releaseBuffers();\n> >> +}\n> >> +\n> >> +int V4L2M2MConverter::Stream::queueBuffers(FrameBuffer *input, FrameBuffer *output)\n> >> +{\n> >> +\tint ret = m2m_->output()->queueBuffer(input);\n> >> +\tif (ret < 0)\n> >> +\t\treturn ret;\n> >> +\n> >> +\tret = m2m_->capture()->queueBuffer(output);\n> >> +\tif (ret < 0)\n> >> +\t\treturn ret;\n> >> +\n> >> +\treturn 0;\n> >> +}\n> >> +\n> >> +std::string V4L2M2MConverter::Stream::logPrefix() const\n> >> +{\n> >> +\treturn \"stream\" + std::to_string(index_);\n> >> +}\n> >> +\n> >> +void V4L2M2MConverter::Stream::outputBufferReady(FrameBuffer *buffer)\n> >> +{\n> >> +\tauto it = converter_->queue_.find(buffer);\n> >> +\tif (it == converter_->queue_.end())\n> >> +\t\treturn;\n> >> +\n> >> +\tif (!--it->second) {\n> >> +\t\tconverter_->inputBufferReady.emit(buffer);\n> >> +\t\tconverter_->queue_.erase(it);\n> >> +\t}\n> >> +}\n> >> +\n> >> +void V4L2M2MConverter::Stream::captureBufferReady(FrameBuffer *buffer)\n> >> +{\n> >> +\tconverter_->outputBufferReady.emit(buffer);\n> >> +}\n> >> +\n> >> +/* -----------------------------------------------------------------------------\n> >> + * V4L2M2MConverter\n> >> + */\n> >> +\n> >> +V4L2M2MConverter::V4L2M2MConverter(MediaDevice *media)\n> >> +\t: Converter(media)\n> >> +{\n> >> +\tif (deviceNode_.empty())\n> >> +\t\treturn;\n> >> +\n> >> +\tm2m_ = std::make_unique<V4L2M2MDevice>(deviceNode_);\n> >> +\tint ret = m2m_->open();\n> >> +\tif (ret < 0) {\n> >> +\t\tm2m_.reset();\n> >> +\t\treturn;\n> >> +\t}\n> >> +}\n> >> +\n> >> +int V4L2M2MConverter::loadConfiguration(const std::string &filename)\n> >> +{\n> >> +\tLOG(Converter, Debug)\n> >> +\t\t<< \"Parsing configuration file \" << filename;\n> >> +\n> >> +\tFile file(filename);\n> >> +\n> >> +\tif (!file.open(File::OpenModeFlag::ReadOnly)) {\n> >> +\t\tint ret = file.error();\n> >> +\t\tLOG(Converter, Error)\n> >> +\t\t\t<< \"Failed to open configuration file \"\n> >> +\t\t\t<< filename << \": \" << strerror(-ret);\n> >> +\t\treturn ret;\n> >> +\t}\n> >> +\n> >> +\tstd::unique_ptr<libcamera::YamlObject> data = YamlParser::parse(file);\n> >> +\tif (!data)\n> >> +\t\treturn -EINVAL;\n> >> +\n> >> +\tif (!data->contains(\"mappings\")) {\n> >> +\t\tLOG(Converter, Error)\n> >> +\t\t\t<< \"Vertex mapping key missing\";\n> >> +\t\treturn -EINVAL;\n> >> +\t}\n> >> +\n> >> +\tconst YamlObject &mappings = (*data)[\"mappings\"];\n> >> +\tif (!mappings.isList() || mappings.size() == 0) {\n> >> +\t\tLOG(Converter, Error)\n> >> +\t\t\t<< \"Invalid mappings entry\";\n> >> +\t\treturn -EINVAL;\n> >> +\t}\n> >> +\n> >> +\tLOG(Converter, Debug)\n> >> +\t\t<< \"Parsing \" << mappings.size() << \" mappings\";\n> >> +\tmappings_.clear();\n> >> +\tmappings_.reserve(mappings.size());\n> >> +\n> >> +\tfor (std::size_t i = 0; i < mappings.size(); i++) {\n> >> +\t\tconst YamlObject &mapping = mappings[i];\n> >> +\t\tif (!mapping.isDictionary()) {\n> >> +\t\t\tLOG(Converter, Error)\n> >> +\t\t\t\t<< \"Mapping is not a dictionnary\";\n> >> +\t\t\treturn -EINVAL;\n> >> +\t\t}\n> >> +\n> >> +\t\tif (!mapping.contains(\"input-resolution\")) {\n> >> +\t\t\tLOG(Converter, Error)\n> >> +\t\t\t\t<< \"Input resolution missing\";\n> >> +\t\t\treturn -EINVAL;\n> >> +\t\t}\n> >> +\n> >> +\t\tif (!mapping.contains(\"output-resolution\")) {\n> >> +\t\t\tLOG(Converter, Error)\n> >> +\t\t\t\t<< \"Output resolution missing\";\n> >> +\t\t\treturn -EINVAL;\n> >> +\t\t}\n> >> +\n> >> +\t\tif (!mapping.contains(\"mapping\")) {\n> >> +\t\t\tLOG(Converter, Error)\n> >> +\t\t\t\t<< \"Mapping table missing\";\n> >> +\t\t\treturn -EINVAL;\n> >> +\t\t}\n> >> +\n> >> +\t\tconst YamlObject &input_res = mapping[\"input-resolution\"];\n> >> +\t\tif (!input_res.isList() || input_res.size() != 2) {\n> >> +\t\t\tLOG(Converter, Error)\n> >> +\t\t\t\t<< \"Incorrect input resolution\";\n> >> +\t\t\treturn -EINVAL;\n> >> +\t\t}\n> >> +\n> >> +\t\tconst YamlObject &output_res = mapping[\"output-resolution\"];\n> >> +\t\tif (!output_res.isList() || output_res.size() != 2) {\n> >> +\t\t\tLOG(Converter, Error)\n> >> +\t\t\t\t<< \"Incorrect output resolution\";\n> >> +\t\t\treturn -EINVAL;\n> >> +\t\t}\n> >> +\n> >> +\t\tconst YamlObject &map = mapping[\"mapping\"];\n> >> +\t\tif (!map.isList() || map.size() == 0) {\n> >> +\t\t\tLOG(Converter, Error)\n> >> +\t\t\t\t<< \"Incorrect mapping entries\";\n> >> +\t\t\treturn -EINVAL;\n> >> +\t\t}\n> >> +\n> >> +\t\tSize input(input_res[0].get<uint32_t>(0), input_res[1].get<uint32_t>(0));\n\nYamlObject::get<Size>() to the rescue ?\n\n> >> +\t\tSize output(output_res[0].get<uint32_t>(0), output_res[1].get<uint32_t>(0));\n> >> +\t\tconst auto &mapVector = map.getList<uint32_t>().value_or(utils::defopt);\n> >> +\n> >> +\t\tLOG(Converter, Debug)\n> >> +\t\t\t<< \"Input/Output mapping resolution \" << input << \" ---> \" << output;\n> >> +\t\tmappings_.emplace_back(Mapping(input, output, mapVector));\n> >> +\t}\n> >> +\n> >> +\treturn mappings.size();\n> >> +}\n> >> +\n> >> +std::vector<PixelFormat> V4L2M2MConverter::formats(PixelFormat input)\n> >> +{\n> >> +\tif (!m2m_)\n> >> +\t\treturn {};\n> >> +\n> >> +\t/*\n> >> +\t * Set the format on the input side (V4L2 output) of the converter to\n> >> +\t * enumerate the conversion capabilities on its output (V4L2 capture).\n> >> +\t */\n> >> +\tV4L2DeviceFormat v4l2Format;\n> >> +\tv4l2Format.fourcc = m2m_->output()->toV4L2PixelFormat(input);\n> >> +\tv4l2Format.size = { 1, 1 };\n> >> +\n> >> +\tint ret = m2m_->output()->setFormat(&v4l2Format);\n> >> +\tif (ret < 0) {\n> >> +\t\tLOG(Converter, Error)\n> >> +\t\t\t<< \"Failed to set format: \" << strerror(-ret);\n> >> +\t\treturn {};\n> >> +\t}\n> >> +\n> >> +\tif (v4l2Format.fourcc != m2m_->output()->toV4L2PixelFormat(input)) {\n> >> +\t\tLOG(Converter, Debug)\n> >> +\t\t\t<< \"Input format \" << input << \" not supported.\";\n> >> +\t\treturn {};\n> >> +\t}\n> >> +\n> >> +\tstd::vector<PixelFormat> pixelFormats;\n> >> +\n> >> +\tfor (const auto &format : m2m_->capture()->formats()) {\n> >> +\t\tPixelFormat pixelFormat = format.first.toPixelFormat();\n> >> +\t\tif (pixelFormat)\n> >> +\t\t\tpixelFormats.push_back(pixelFormat);\n> >> +\t}\n> >> +\n> >> +\treturn pixelFormats;\n> >> +}\n> >> +\n> >> +SizeRange V4L2M2MConverter::sizes(const Size &input)\n> >> +{\n> >> +\tif (!m2m_)\n> >> +\t\treturn {};\n> >> +\n> >> +\t/*\n> >> +\t * Set the size on the input side (V4L2 output) of the converter to\n> >> +\t * enumerate the scaling capabilities on its output (V4L2 capture).\n> >> +\t */\n> >> +\tV4L2DeviceFormat format;\n> >> +\tformat.fourcc = V4L2PixelFormat();\n> >> +\tformat.size = input;\n> >> +\n> >> +\tint ret = m2m_->output()->setFormat(&format);\n> >> +\tif (ret < 0) {\n> >> +\t\tLOG(Converter, Error)\n> >> +\t\t\t<< \"Failed to set format: \" << strerror(-ret);\n> >> +\t\treturn {};\n> >> +\t}\n> >> +\n> >> +\tSizeRange sizes;\n> >> +\n> >> +\tformat.size = { 1, 1 };\n> >> +\tret = m2m_->capture()->setFormat(&format);\n> >> +\tif (ret < 0) {\n> >> +\t\tLOG(Converter, Error)\n> >> +\t\t\t<< \"Failed to set format: \" << strerror(-ret);\n> >> +\t\treturn {};\n> >> +\t}\n> >> +\n> >> +\tsizes.min = format.size;\n> >> +\n> >> +\tformat.size = { UINT_MAX, UINT_MAX };\n> >> +\tret = m2m_->capture()->setFormat(&format);\n> >> +\tif (ret < 0) {\n> >> +\t\tLOG(Converter, Error)\n> >> +\t\t\t<< \"Failed to set format: \" << strerror(-ret);\n> >> +\t\treturn {};\n> >> +\t}\n> >> +\n> >> +\tsizes.max = format.size;\n> >> +\n> >> +\treturn sizes;\n> >> +}\n> >> +\n> >> +std::tuple<unsigned int, unsigned int>\n> >> +V4L2M2MConverter::strideAndFrameSize(const PixelFormat &pixelFormat,\n> >> +\t\t\t\t     const Size &size)\n> >> +{\n> >> +\tV4L2DeviceFormat format;\n> >> +\tformat.fourcc = m2m_->capture()->toV4L2PixelFormat(pixelFormat);\n> >> +\tformat.size = size;\n> >> +\n> >> +\tint ret = m2m_->capture()->tryFormat(&format);\n> >> +\tif (ret < 0)\n> >> +\t\treturn std::make_tuple(0, 0);\n> >> +\n> >> +\treturn std::make_tuple(format.planes[0].bpl, format.planes[0].size);\n> >> +}\n> >> +\n> >> +int V4L2M2MConverter::configure(const StreamConfiguration &inputCfg,\n> >> +\t\t\t\tconst std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs)\n> >> +{\n> >> +\tint ret = 0;\n> >> +\n> >> +\tstreams_.clear();\n> >> +\tstreams_.reserve(outputCfgs.size());\n> >> +\n> >> +\tfor (unsigned int i = 0; i < outputCfgs.size(); ++i) {\n> >> +\t\tStream &stream = streams_.emplace_back(this, i);\n> >> +\n> >> +\t\tif (!stream.isValid()) {\n> >> +\t\t\tLOG(Converter, Error)\n> >> +\t\t\t\t<< \"Failed to create stream \" << i;\n> >> +\t\t\tret = -EINVAL;\n> >> +\t\t\tbreak;\n> >> +\t\t}\n> >> +\n> >> +\t\tret = stream.configure(inputCfg, outputCfgs[i]);\n> >> +\t\tif (ret < 0)\n> >> +\t\t\tbreak;\n> >> +\t}\n> >> +\n> >> +\tif (ret < 0) {\n> >> +\t\tstreams_.clear();\n> >> +\t\treturn ret;\n> >> +\t}\n> >> +\n> >> +\treturn 0;\n> >> +}\n> >> +\n> >> +int V4L2M2MConverter::exportBuffers(unsigned int output, unsigned int count,\n> >> +\t\t\t\t    std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n> >> +{\n> >> +\tif (output >= streams_.size())\n> >> +\t\treturn -EINVAL;\n> >> +\n> >> +\treturn streams_[output].exportBuffers(count, buffers);\n> >> +}\n> >> +\n> >> +int V4L2M2MConverter::start()\n> >> +{\n> >> +\tint ret;\n> >> +\n> >> +\tfor (Stream &stream : streams_) {\n> >> +\t\tret = stream.start();\n> >> +\t\tif (ret < 0) {\n> >> +\t\t\tstop();\n> >> +\t\t\treturn ret;\n> >> +\t\t}\n> >> +\t}\n> >> +\n> >> +\treturn 0;\n> >> +}\n> >> +\n> >> +void V4L2M2MConverter::stop()\n> >> +{\n> >> +\tfor (Stream &stream : utils::reverse(streams_))\n> >> +\t\tstream.stop();\n> >> +}\n> >> +\n> >> +int V4L2M2MConverter::queueBuffers(FrameBuffer *input,\n> >> +\t\t\t\t   const std::map<unsigned int, FrameBuffer *> &outputs)\n> >> +{\n> >> +\tunsigned int mask = 0;\n> >> +\tint ret;\n> >> +\n> >> +\t/*\n> >> +\t * Validate the outputs as a sanity check: at least one output is\n> >> +\t * required, all outputs must reference a valid stream and no two\n> >> +\t * outputs can reference the same stream.\n> >> +\t */\n> >> +\tif (outputs.empty())\n> >> +\t\treturn -EINVAL;\n> >> +\n> >> +\tfor (auto [index, buffer] : outputs) {\n> >> +\t\tif (!buffer)\n> >> +\t\t\treturn -EINVAL;\n> >> +\t\tif (index >= streams_.size())\n> >> +\t\t\treturn -EINVAL;\n> >> +\t\tif (mask & (1 << index))\n> >> +\t\t\treturn -EINVAL;\n> >> +\n> >> +\t\tmask |= 1 << index;\n> >> +\t}\n> >> +\n> >> +\t/* Queue the input and output buffers to all the streams. */\n> >> +\tfor (auto [index, buffer] : outputs) {\n> >> +\t\tret = streams_[index].queueBuffers(input, buffer);\n> >> +\t\tif (ret < 0)\n> >> +\t\t\treturn ret;\n> >> +\t}\n> >> +\n> >> +\t/*\n> >> +\t * Add the input buffer to the queue, with the number of streams as a\n> >> +\t * reference count. Completion of the input buffer will be signalled by\n> >> +\t * the stream that releases the last reference.\n> >> +\t */\n> >> +\tqueue_.emplace(std::piecewise_construct,\n> >> +\t\t       std::forward_as_tuple(input),\n> >> +\t\t       std::forward_as_tuple(outputs.size()));\n> >> +\n> >> +\treturn 0;\n> >> +}\n> >> +\n> >> +REGISTER_CONVERTER(\"v4l2_m2m\", V4L2M2MConverter, \"pxp\")\n> >> +\n> >> +} /* namespace libcamera */\n> >> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\n> >> index a261d4b4..b12c8401 100644\n> >> --- a/src/libcamera/meson.build\n> >> +++ b/src/libcamera/meson.build\n> >> @@ -14,6 +14,7 @@ libcamera_sources = files([\n> >>       'control_serializer.cpp',\n> >>       'control_validator.cpp',\n> >>       'converter.cpp',\n> >> +    'converter_v4l2_m2m.cpp',\n> >>       'delayed_controls.cpp',\n> >>       'device_enumerator.cpp',\n> >>       'device_enumerator_sysfs.cpp',","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 B0AD2C0DA4\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon,  3 Oct 2022 22:56:48 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 0C903601D1;\n\tTue,  4 Oct 2022 00:56:48 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 633B0601C7\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue,  4 Oct 2022 00:56:47 +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 85F599DE;\n\tTue,  4 Oct 2022 00:56:46 +0200 (CEST)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1664837808;\n\tbh=fNCbnoLK+Hf3XbfL9i6Nb49KRtgIQNyasJww2c+67jE=;\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=O6zLYCYTAZw0tup+8E2s2xmKRSMOi1pifzPwkonqXDY5pXTjHyGDl8hrReC5ctRJ3\n\tyunuvbBCm+ZSvvREvz4k7VewU/9KmN4I8oYrIM8CBQ5rfHwYv4XC/oKcJ10aIJ69Hu\n\tUW1ukN1vBraBeu9/6COhYB9yg3LnwxEPkOt6RufE0z4Wb9MAcW3Ay8B1OV7mmoBaLU\n\tMKCb132eKqZkAOy7KOdHmdfLztC1djUDYpLd79dnqrD4ALxQwH20OLcY0dzjODsr6D\n\t3cMJn1JF5zSj2XR40mI4zG4PnCbg709+5ErCoVjc1jerqyZgE0pw4nYZnID6265vNr\n\tDHm7wP+qx/+qg==","v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1664837806;\n\tbh=fNCbnoLK+Hf3XbfL9i6Nb49KRtgIQNyasJww2c+67jE=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=iPHJrvJV5MTXNrh4thfYHjVRW+5GaMqL3czyQc4KTVC7/Tm94dZslayBfLkS6c+49\n\t6oL9yQjBpX6iGEx6u04u31yFO0CDuO6pM+Iw5nR2Zf0l0+SfcpGFAvZRkflFfde4iH\n\te4u9g8P3ghjvUW7l9CekMqcsdW6jbd+lQhETxANo="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"iPHJrvJV\"; dkim-atps=neutral","Date":"Tue, 4 Oct 2022 01:56:44 +0300","To":"\"Xavier Roumegue (OSS)\" <xavier.roumegue@oss.nxp.com>","Message-ID":"<YztorCM4P1YM6qip@pendragon.ideasonboard.com>","References":"<20220908184850.1874303-1-xavier.roumegue@oss.nxp.com>\n\t<20220908184850.1874303-7-xavier.roumegue@oss.nxp.com>\n\t<20220915094621.vtc5ffmdw6hjb3js@uno.localdomain>\n\t<47d506ef-e0a1-718c-72dc-66238b1514d7@oss.nxp.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<47d506ef-e0a1-718c-72dc-66238b1514d7@oss.nxp.com>","Subject":"Re: [libcamera-devel] [PATCH 06/14] libcamera: converter: Add v4l2\n\tm2m converter implementation","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>"}}]