[libcamera-devel,v3,2/2] libcamera: pipeline: simple: converter: Use generic converter interface
diff mbox series

Message ID 20221117185204.11625-2-xavier.roumegue@oss.nxp.com
State Accepted
Headers show
Series
  • [libcamera-devel,v3,1/2] libcamera: Declare generic converter interface
Related show

Commit Message

Xavier Roumegue Nov. 17, 2022, 6:52 p.m. UTC
Move the simple converter implementation to a generic V4L2 M2M class
derived from the converter interface. This latter could be used by
other pipeline implementations and as base class for customized V4L2 M2M
converters.

Signed-off-by: Xavier Roumegue <xavier.roumegue@oss.nxp.com>
Reviewed-by: Jacopo Mondi <jacopo@jmondi.org>
---
 .../internal/converter/converter_v4l2_m2m.h   |  18 +--
 .../libcamera/internal/converter/meson.build  |   5 +
 include/libcamera/internal/meson.build        |   2 +
 .../converter_v4l2_m2m.cpp}                   | 153 +++++++++++-------
 src/libcamera/converter/meson.build           |   5 +
 src/libcamera/meson.build                     |   1 +
 src/libcamera/pipeline/simple/meson.build     |   1 -
 src/libcamera/pipeline/simple/simple.cpp      |   6 +-
 8 files changed, 123 insertions(+), 68 deletions(-)
 rename src/libcamera/pipeline/simple/converter.h => include/libcamera/internal/converter/converter_v4l2_m2m.h (83%)
 create mode 100644 include/libcamera/internal/converter/meson.build
 rename src/libcamera/{pipeline/simple/converter.cpp => converter/converter_v4l2_m2m.cpp} (68%)
 create mode 100644 src/libcamera/converter/meson.build

Comments

Kieran Bingham Dec. 7, 2022, 12:55 p.m. UTC | #1
Quoting Xavier Roumegue via libcamera-devel (2022-11-17 18:52:04)
> Move the simple converter implementation to a generic V4L2 M2M class
> derived from the converter interface. This latter could be used by
> other pipeline implementations and as base class for customized V4L2 M2M
> converters.
> 

I like the sound of this!


> Signed-off-by: Xavier Roumegue <xavier.roumegue@oss.nxp.com>
> Reviewed-by: Jacopo Mondi <jacopo@jmondi.org>
> ---
>  .../internal/converter/converter_v4l2_m2m.h   |  18 +--
>  .../libcamera/internal/converter/meson.build  |   5 +
>  include/libcamera/internal/meson.build        |   2 +
>  .../converter_v4l2_m2m.cpp}                   | 153 +++++++++++-------
>  src/libcamera/converter/meson.build           |   5 +
>  src/libcamera/meson.build                     |   1 +
>  src/libcamera/pipeline/simple/meson.build     |   1 -
>  src/libcamera/pipeline/simple/simple.cpp      |   6 +-
>  8 files changed, 123 insertions(+), 68 deletions(-)
>  rename src/libcamera/pipeline/simple/converter.h => include/libcamera/internal/converter/converter_v4l2_m2m.h (83%)
>  create mode 100644 include/libcamera/internal/converter/meson.build
>  rename src/libcamera/{pipeline/simple/converter.cpp => converter/converter_v4l2_m2m.cpp} (68%)
>  create mode 100644 src/libcamera/converter/meson.build
> 
> diff --git a/src/libcamera/pipeline/simple/converter.h b/include/libcamera/internal/converter/converter_v4l2_m2m.h
> similarity index 83%
> rename from src/libcamera/pipeline/simple/converter.h
> rename to include/libcamera/internal/converter/converter_v4l2_m2m.h
> index f0ebe2e0..ef31eeba 100644
> --- a/src/libcamera/pipeline/simple/converter.h
> +++ b/include/libcamera/internal/converter/converter_v4l2_m2m.h
> @@ -1,8 +1,9 @@
>  /* SPDX-License-Identifier: LGPL-2.1-or-later */
>  /*
>   * Copyright (C) 2020, Laurent Pinchart
> + * Copyright 2022 NXP
>   *
> - * converter.h - Format converter for simple pipeline handler
> + * converter_v4l2_m2m.h - V4l2 M2M Format converter interface
>   */
>  
>  #pragma once
> @@ -19,6 +20,8 @@
>  #include <libcamera/base/log.h>
>  #include <libcamera/base/signal.h>
>  
> +#include "libcamera/internal/converter.h"
> +
>  namespace libcamera {
>  
>  class FrameBuffer;
> @@ -28,11 +31,12 @@ class SizeRange;
>  struct StreamConfiguration;
>  class V4L2M2MDevice;
>  
> -class SimpleConverter
> +class V4L2M2MConverter : public Converter
>  {
>  public:
> -       SimpleConverter(MediaDevice *media);
> +       V4L2M2MConverter(MediaDevice *media);
>  
> +       int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; }
>         bool isValid() const { return m2m_ != nullptr; }
>  
>         std::vector<PixelFormat> formats(PixelFormat input);
> @@ -52,14 +56,11 @@ public:
>         int queueBuffers(FrameBuffer *input,
>                          const std::map<unsigned int, FrameBuffer *> &outputs);
>  
> -       Signal<FrameBuffer *> inputBufferReady;
> -       Signal<FrameBuffer *> outputBufferReady;
> -
>  private:
>         class Stream : protected Loggable
>         {
>         public:
> -               Stream(SimpleConverter *converter, unsigned int index);
> +               Stream(V4L2M2MConverter *converter, unsigned int index);
>  
>                 bool isValid() const { return m2m_ != nullptr; }
>  
> @@ -80,7 +81,7 @@ private:
>                 void captureBufferReady(FrameBuffer *buffer);
>                 void outputBufferReady(FrameBuffer *buffer);
>  
> -               SimpleConverter *converter_;
> +               V4L2M2MConverter *converter_;
>                 unsigned int index_;
>                 std::unique_ptr<V4L2M2MDevice> m2m_;
>  
> @@ -88,7 +89,6 @@ private:
>                 unsigned int outputBufferCount_;
>         };
>  
> -       std::string deviceNode_;
>         std::unique_ptr<V4L2M2MDevice> m2m_;
>  
>         std::vector<Stream> streams_;
> diff --git a/include/libcamera/internal/converter/meson.build b/include/libcamera/internal/converter/meson.build
> new file mode 100644
> index 00000000..891e79e7
> --- /dev/null
> +++ b/include/libcamera/internal/converter/meson.build
> @@ -0,0 +1,5 @@
> +# SPDX-License-Identifier: CC0-1.0
> +
> +libcamera_internal_headers += files([
> +    'converter_v4l2_m2m.h',

I'll be interested to see what other convertors end in here too.

I could imagine a GPU/OpenGL convertor, a libjpeg convertor ... (I guess
that's an encoder?, but I'd imagine the same base interface?) ... or others ...


> +])
> diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
> index 8f50d755..b9db5a8c 100644
> --- a/include/libcamera/internal/meson.build
> +++ b/include/libcamera/internal/meson.build
> @@ -45,3 +45,5 @@ libcamera_internal_headers = files([
>      'v4l2_videodevice.h',
>      'yaml_parser.h',
>  ])
> +
> +subdir('converter')
> diff --git a/src/libcamera/pipeline/simple/converter.cpp b/src/libcamera/converter/converter_v4l2_m2m.cpp
> similarity index 68%
> rename from src/libcamera/pipeline/simple/converter.cpp
> rename to src/libcamera/converter/converter_v4l2_m2m.cpp
> index acaaa64c..31acb048 100644
> --- a/src/libcamera/pipeline/simple/converter.cpp
> +++ b/src/libcamera/converter/converter_v4l2_m2m.cpp
> @@ -1,12 +1,11 @@
>  /* SPDX-License-Identifier: LGPL-2.1-or-later */
>  /*
>   * Copyright (C) 2020, Laurent Pinchart
> + * Copyright 2022 NXP
>   *
> - * converter.cpp - Format converter for simple pipeline handler
> + * converter_v4l2_m2m.cpp - V4L2 M2M Format converter
>   */
>  
> -#include "converter.h"
> -
>  #include <algorithm>
>  #include <limits.h>
>  
> @@ -20,19 +19,25 @@
>  
>  #include "libcamera/internal/media_device.h"
>  #include "libcamera/internal/v4l2_videodevice.h"
> +#include "libcamera/internal/converter/converter_v4l2_m2m.h"
> +
> +/**
> + * \file internal/converter/converter_v4l2_m2m.h
> + * \brief V4L2 M2M based converter
> + */
>  
>  namespace libcamera {
>  
> -LOG_DECLARE_CATEGORY(SimplePipeline)
> +LOG_DECLARE_CATEGORY(Converter)

Hrm.. no, we normally have one log category per component. I think this
should be the V4L2M2MConvertor log category ?

But maybe we should have more generic log categories on some occasions?
So this isn't a hard reject...


>  
>  /* -----------------------------------------------------------------------------
> - * SimpleConverter::Stream
> + * V4L2M2MConverter::Stream
>   */
>  
> -SimpleConverter::Stream::Stream(SimpleConverter *converter, unsigned int index)
> +V4L2M2MConverter::Stream::Stream(V4L2M2MConverter *converter, unsigned int index)
>         : converter_(converter), index_(index)
>  {
> -       m2m_ = std::make_unique<V4L2M2MDevice>(converter->deviceNode_);
> +       m2m_ = std::make_unique<V4L2M2MDevice>(converter->deviceNode());
>  
>         m2m_->output()->bufferReady.connect(this, &Stream::outputBufferReady);
>         m2m_->capture()->bufferReady.connect(this, &Stream::captureBufferReady);
> @@ -42,8 +47,8 @@ SimpleConverter::Stream::Stream(SimpleConverter *converter, unsigned int index)
>                 m2m_.reset();
>  }
>  
> -int SimpleConverter::Stream::configure(const StreamConfiguration &inputCfg,
> -                                      const StreamConfiguration &outputCfg)
> +int V4L2M2MConverter::Stream::configure(const StreamConfiguration &inputCfg,
> +                                       const StreamConfiguration &outputCfg)
>  {
>         V4L2PixelFormat videoFormat =
>                 m2m_->output()->toV4L2PixelFormat(inputCfg.pixelFormat);
> @@ -56,14 +61,14 @@ int SimpleConverter::Stream::configure(const StreamConfiguration &inputCfg,
>  
>         int ret = m2m_->output()->setFormat(&format);
>         if (ret < 0) {
> -               LOG(SimplePipeline, Error)
> +               LOG(Converter, Error)
>                         << "Failed to set input format: " << strerror(-ret);
>                 return ret;
>         }
>  
>         if (format.fourcc != videoFormat || format.size != inputCfg.size ||
>             format.planes[0].bpl != inputCfg.stride) {
> -               LOG(SimplePipeline, Error)
> +               LOG(Converter, Error)
>                         << "Input format not supported (requested "
>                         << inputCfg.size << "-" << videoFormat
>                         << ", got " << format << ")";
> @@ -78,13 +83,13 @@ int SimpleConverter::Stream::configure(const StreamConfiguration &inputCfg,
>  
>         ret = m2m_->capture()->setFormat(&format);
>         if (ret < 0) {
> -               LOG(SimplePipeline, Error)
> +               LOG(Converter, Error)
>                         << "Failed to set output format: " << strerror(-ret);
>                 return ret;
>         }
>  
>         if (format.fourcc != videoFormat || format.size != outputCfg.size) {
> -               LOG(SimplePipeline, Error)
> +               LOG(Converter, Error)
>                         << "Output format not supported";
>                 return -EINVAL;
>         }
> @@ -95,13 +100,13 @@ int SimpleConverter::Stream::configure(const StreamConfiguration &inputCfg,
>         return 0;
>  }
>  
> -int SimpleConverter::Stream::exportBuffers(unsigned int count,
> -                                          std::vector<std::unique_ptr<FrameBuffer>> *buffers)
> +int V4L2M2MConverter::Stream::exportBuffers(unsigned int count,
> +                                           std::vector<std::unique_ptr<FrameBuffer>> *buffers)
>  {
>         return m2m_->capture()->exportBuffers(count, buffers);
>  }
>  
> -int SimpleConverter::Stream::start()
> +int V4L2M2MConverter::Stream::start()
>  {
>         int ret = m2m_->output()->importBuffers(inputBufferCount_);
>         if (ret < 0)
> @@ -128,7 +133,7 @@ int SimpleConverter::Stream::start()
>         return 0;
>  }
>  
> -void SimpleConverter::Stream::stop()
> +void V4L2M2MConverter::Stream::stop()
>  {
>         m2m_->capture()->streamOff();
>         m2m_->output()->streamOff();
> @@ -136,8 +141,7 @@ void SimpleConverter::Stream::stop()
>         m2m_->output()->releaseBuffers();
>  }
>  
> -int SimpleConverter::Stream::queueBuffers(FrameBuffer *input,
> -                                         FrameBuffer *output)
> +int V4L2M2MConverter::Stream::queueBuffers(FrameBuffer *input, FrameBuffer *output)
>  {
>         int ret = m2m_->output()->queueBuffer(input);
>         if (ret < 0)
> @@ -150,12 +154,12 @@ int SimpleConverter::Stream::queueBuffers(FrameBuffer *input,
>         return 0;
>  }
>  
> -std::string SimpleConverter::Stream::logPrefix() const
> +std::string V4L2M2MConverter::Stream::logPrefix() const
>  {
>         return "stream" + std::to_string(index_);

I'm not yet sure if this will be unique enough if we have multiple
convertors. However given the logging infrastructure will print a file
and line number, I think it will be clear.

So I think this is ok.


>  }
>  
> -void SimpleConverter::Stream::outputBufferReady(FrameBuffer *buffer)
> +void V4L2M2MConverter::Stream::outputBufferReady(FrameBuffer *buffer)
>  {
>         auto it = converter_->queue_.find(buffer);
>         if (it == converter_->queue_.end())
> @@ -167,32 +171,34 @@ void SimpleConverter::Stream::outputBufferReady(FrameBuffer *buffer)
>         }
>  }
>  
> -void SimpleConverter::Stream::captureBufferReady(FrameBuffer *buffer)
> +void V4L2M2MConverter::Stream::captureBufferReady(FrameBuffer *buffer)
>  {
>         converter_->outputBufferReady.emit(buffer);
>  }
>  
>  /* -----------------------------------------------------------------------------
> - * SimpleConverter
> + * V4L2M2MConverter
> + */
> +
> +/**
> + * \class libcamera::V4L2M2MConverter
> + * \brief The V4L2 M2M converter implements the converter interface based on
> + * V4L2 M2M device.
> +*/
> +
> +/**
> + * \fn V4L2M2MConverter::V4L2M2MConverter
> + * \brief Construct a V4L2M2MConverter instance
> + * \param[in] media The media device implementing the converter
>   */
>  
> -SimpleConverter::SimpleConverter(MediaDevice *media)
> +V4L2M2MConverter::V4L2M2MConverter(MediaDevice *media)
> +       : Converter(media)
>  {
> -       /*
> -        * Locate the video node. There's no need to validate the pipeline
> -        * further, the caller guarantees that this is a V4L2 mem2mem device.
> -        */
> -       const std::vector<MediaEntity *> &entities = media->entities();
> -       auto it = std::find_if(entities.begin(), entities.end(),
> -                              [](MediaEntity *entity) {
> -                                      return entity->function() == MEDIA_ENT_F_IO_V4L;
> -                              });
> -       if (it == entities.end())
> +       if (deviceNode().empty())

Do we need any kind of warning here? I presume this means we were unable
to construct a suitable convertor... Or will the Converter(media)
initialiser have already printed an error in that instance?

>                 return;
>  
> -       deviceNode_ = (*it)->deviceNode();
> -
> -       m2m_ = std::make_unique<V4L2M2MDevice>(deviceNode_);
> +       m2m_ = std::make_unique<V4L2M2MDevice>(deviceNode());
>         int ret = m2m_->open();
>         if (ret < 0) {
>                 m2m_.reset();
> @@ -200,7 +206,21 @@ SimpleConverter::SimpleConverter(MediaDevice *media)
>         }
>  }
>  
> -std::vector<PixelFormat> SimpleConverter::formats(PixelFormat input)
> +/**
> + * \fn libcamera::V4L2M2MConverter::loadConfiguration
> + * \details \copydetails libcamera::Converter::loadConfiguration
> + */
> +
> +/**
> + * \fn libcamera::V4L2M2MConverter::isValid
> + * \details \copydetails libcamera::Converter::isValid
> + */
> +
> +/**
> + * \fn libcamera::V4L2M2MConverter::formats
> + * \details \copydetails libcamera::Converter::formats
> + */
> +std::vector<PixelFormat> V4L2M2MConverter::formats(PixelFormat input)
>  {
>         if (!m2m_)
>                 return {};
> @@ -215,13 +235,13 @@ std::vector<PixelFormat> SimpleConverter::formats(PixelFormat input)
>  
>         int ret = m2m_->output()->setFormat(&v4l2Format);
>         if (ret < 0) {
> -               LOG(SimplePipeline, Error)
> +               LOG(Converter, Error)
>                         << "Failed to set format: " << strerror(-ret);
>                 return {};
>         }
>  
>         if (v4l2Format.fourcc != m2m_->output()->toV4L2PixelFormat(input)) {
> -               LOG(SimplePipeline, Debug)
> +               LOG(Converter, Debug)
>                         << "Input format " << input << " not supported.";
>                 return {};
>         }
> @@ -237,7 +257,10 @@ std::vector<PixelFormat> SimpleConverter::formats(PixelFormat input)
>         return pixelFormats;
>  }
>  
> -SizeRange SimpleConverter::sizes(const Size &input)
> +/**
> + * \copydoc libcamera::Converter::sizes
> + */
> +SizeRange V4L2M2MConverter::sizes(const Size &input)
>  {
>         if (!m2m_)
>                 return {};
> @@ -252,7 +275,7 @@ SizeRange SimpleConverter::sizes(const Size &input)
>  
>         int ret = m2m_->output()->setFormat(&format);
>         if (ret < 0) {
> -               LOG(SimplePipeline, Error)
> +               LOG(Converter, Error)
>                         << "Failed to set format: " << strerror(-ret);
>                 return {};
>         }
> @@ -262,7 +285,7 @@ SizeRange SimpleConverter::sizes(const Size &input)
>         format.size = { 1, 1 };
>         ret = m2m_->capture()->setFormat(&format);
>         if (ret < 0) {
> -               LOG(SimplePipeline, Error)
> +               LOG(Converter, Error)
>                         << "Failed to set format: " << strerror(-ret);
>                 return {};
>         }
> @@ -272,7 +295,7 @@ SizeRange SimpleConverter::sizes(const Size &input)
>         format.size = { UINT_MAX, UINT_MAX };
>         ret = m2m_->capture()->setFormat(&format);
>         if (ret < 0) {
> -               LOG(SimplePipeline, Error)
> +               LOG(Converter, Error)
>                         << "Failed to set format: " << strerror(-ret);
>                 return {};
>         }
> @@ -282,9 +305,12 @@ SizeRange SimpleConverter::sizes(const Size &input)
>         return sizes;
>  }
>  
> +/**
> + * \copydoc libcamera::Converter::strideAndFrameSize
> + */
>  std::tuple<unsigned int, unsigned int>
> -SimpleConverter::strideAndFrameSize(const PixelFormat &pixelFormat,
> -                                   const Size &size)
> +V4L2M2MConverter::strideAndFrameSize(const PixelFormat &pixelFormat,
> +                                    const Size &size)
>  {
>         V4L2DeviceFormat format;
>         format.fourcc = m2m_->capture()->toV4L2PixelFormat(pixelFormat);
> @@ -297,8 +323,11 @@ SimpleConverter::strideAndFrameSize(const PixelFormat &pixelFormat,
>         return std::make_tuple(format.planes[0].bpl, format.planes[0].size);
>  }
>  
> -int SimpleConverter::configure(const StreamConfiguration &inputCfg,
> -                              const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs)
> +/**
> + * \copydoc libcamera::Converter::configure
> + */
> +int V4L2M2MConverter::configure(const StreamConfiguration &inputCfg,
> +                               const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs)
>  {
>         int ret = 0;
>  
> @@ -309,7 +338,7 @@ int SimpleConverter::configure(const StreamConfiguration &inputCfg,
>                 Stream &stream = streams_.emplace_back(this, i);
>  
>                 if (!stream.isValid()) {
> -                       LOG(SimplePipeline, Error)
> +                       LOG(Converter, Error)
>                                 << "Failed to create stream " << i;
>                         ret = -EINVAL;
>                         break;
> @@ -328,8 +357,11 @@ int SimpleConverter::configure(const StreamConfiguration &inputCfg,
>         return 0;
>  }
>  
> -int SimpleConverter::exportBuffers(unsigned int output, unsigned int count,
> -                                  std::vector<std::unique_ptr<FrameBuffer>> *buffers)
> +/**
> + * \copydoc libcamera::Converter::exportBuffers
> + */
> +int V4L2M2MConverter::exportBuffers(unsigned int output, unsigned int count,
> +                                   std::vector<std::unique_ptr<FrameBuffer>> *buffers)
>  {
>         if (output >= streams_.size())
>                 return -EINVAL;
> @@ -337,7 +369,10 @@ int SimpleConverter::exportBuffers(unsigned int output, unsigned int count,
>         return streams_[output].exportBuffers(count, buffers);
>  }
>  
> -int SimpleConverter::start()
> +/**
> + * \copydoc libcamera::Converter::start
> + */
> +int V4L2M2MConverter::start()
>  {
>         int ret;
>  
> @@ -352,14 +387,20 @@ int SimpleConverter::start()
>         return 0;
>  }
>  
> -void SimpleConverter::stop()
> +/**
> + * \copydoc libcamera::Converter::stop
> + */
> +void V4L2M2MConverter::stop()
>  {
>         for (Stream &stream : utils::reverse(streams_))
>                 stream.stop();
>  }
>  
> -int SimpleConverter::queueBuffers(FrameBuffer *input,
> -                                 const std::map<unsigned int, FrameBuffer *> &outputs)
> +/**
> + * \copydoc libcamera::Converter::queueBuffers
> + */
> +int V4L2M2MConverter::queueBuffers(FrameBuffer *input,
> +                                  const std::map<unsigned int, FrameBuffer *> &outputs)
>  {
>         unsigned int mask = 0;
>         int ret;
> @@ -402,4 +443,6 @@ int SimpleConverter::queueBuffers(FrameBuffer *input,
>         return 0;
>  }
>  
> +REGISTER_CONVERTER("v4l2_m2m", V4L2M2MConverter, "pxp")

I think the only part I'd change here is that the "pxp" might be a
statically created array or vector of 'compatible' strings, a bit like
the kernel would define.

Then adding support for a new compatible would just be a new line in a
table, rather than modifying this registration. It's not much, but it
would feel 'neater' to just have a single line patch to add an entry.


And once again, I don't think there are any blockers there. Some things
to consider and tidy up for a v4, but I'd be happy to merge at that.


Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>


> +
>  } /* namespace libcamera */
> diff --git a/src/libcamera/converter/meson.build b/src/libcamera/converter/meson.build
> new file mode 100644
> index 00000000..2aa72fe4
> --- /dev/null
> +++ b/src/libcamera/converter/meson.build
> @@ -0,0 +1,5 @@
> +# SPDX-License-Identifier: CC0-1.0
> +
> +libcamera_sources += files([
> +        'converter_v4l2_m2m.cpp'
> +])
> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
> index e9d0324e..ffc294f3 100644
> --- a/src/libcamera/meson.build
> +++ b/src/libcamera/meson.build
> @@ -62,6 +62,7 @@ libatomic = cc.find_library('atomic', required : false)
>  libthreads = dependency('threads')
>  
>  subdir('base')
> +subdir('converter')
>  subdir('ipa')
>  subdir('pipeline')
>  subdir('proxy')
> diff --git a/src/libcamera/pipeline/simple/meson.build b/src/libcamera/pipeline/simple/meson.build
> index 9c99b32f..42b0896d 100644
> --- a/src/libcamera/pipeline/simple/meson.build
> +++ b/src/libcamera/pipeline/simple/meson.build
> @@ -1,6 +1,5 @@
>  # SPDX-License-Identifier: CC0-1.0
>  
>  libcamera_sources += files([
> -    'converter.cpp',
>      'simple.cpp',
>  ])
> diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
> index a32de7f3..24ded4db 100644
> --- a/src/libcamera/pipeline/simple/simple.cpp
> +++ b/src/libcamera/pipeline/simple/simple.cpp
> @@ -30,13 +30,13 @@
>  
>  #include "libcamera/internal/camera.h"
>  #include "libcamera/internal/camera_sensor.h"
> +#include "libcamera/internal/converter.h"
>  #include "libcamera/internal/device_enumerator.h"
>  #include "libcamera/internal/media_device.h"
>  #include "libcamera/internal/pipeline_handler.h"
>  #include "libcamera/internal/v4l2_subdevice.h"
>  #include "libcamera/internal/v4l2_videodevice.h"
>  
> -#include "converter.h"
>  
>  namespace libcamera {
>  
> @@ -266,7 +266,7 @@ public:
>         std::vector<Configuration> configs_;
>         std::map<PixelFormat, std::vector<const Configuration *>> formats_;
>  
> -       std::unique_ptr<SimpleConverter> converter_;
> +       std::unique_ptr<Converter> converter_;
>         std::vector<std::unique_ptr<FrameBuffer>> converterBuffers_;
>         bool useConverter_;
>         std::queue<std::map<unsigned int, FrameBuffer *>> converterQueue_;
> @@ -492,7 +492,7 @@ int SimpleCameraData::init()
>         /* Open the converter, if any. */
>         MediaDevice *converter = pipe->converter();
>         if (converter) {
> -               converter_ = std::make_unique<SimpleConverter>(converter);
> +               converter_ = ConverterFactoryBase::create(converter);
>                 if (!converter_->isValid()) {
>                         LOG(SimplePipeline, Warning)
>                                 << "Failed to create converter, disabling format conversion";
> -- 
> 2.38.1
>
Xavier Roumegue Dec. 10, 2022, 3:15 p.m. UTC | #2
Hi Kieran,

Thanks for your review.

On 12/7/22 13:55, Kieran Bingham wrote:
> Quoting Xavier Roumegue via libcamera-devel (2022-11-17 18:52:04)
>> Move the simple converter implementation to a generic V4L2 M2M class
>> derived from the converter interface. This latter could be used by
>> other pipeline implementations and as base class for customized V4L2 M2M
>> converters.
>>
> 
> I like the sound of this!
> 
> 
>> Signed-off-by: Xavier Roumegue <xavier.roumegue@oss.nxp.com>
>> Reviewed-by: Jacopo Mondi <jacopo@jmondi.org>
>> ---
>>   .../internal/converter/converter_v4l2_m2m.h   |  18 +--
>>   .../libcamera/internal/converter/meson.build  |   5 +
>>   include/libcamera/internal/meson.build        |   2 +
>>   .../converter_v4l2_m2m.cpp}                   | 153 +++++++++++-------
>>   src/libcamera/converter/meson.build           |   5 +
>>   src/libcamera/meson.build                     |   1 +
>>   src/libcamera/pipeline/simple/meson.build     |   1 -
>>   src/libcamera/pipeline/simple/simple.cpp      |   6 +-
>>   8 files changed, 123 insertions(+), 68 deletions(-)
>>   rename src/libcamera/pipeline/simple/converter.h => include/libcamera/internal/converter/converter_v4l2_m2m.h (83%)
>>   create mode 100644 include/libcamera/internal/converter/meson.build
>>   rename src/libcamera/{pipeline/simple/converter.cpp => converter/converter_v4l2_m2m.cpp} (68%)
>>   create mode 100644 src/libcamera/converter/meson.build
>>
>> diff --git a/src/libcamera/pipeline/simple/converter.h b/include/libcamera/internal/converter/converter_v4l2_m2m.h
>> similarity index 83%
>> rename from src/libcamera/pipeline/simple/converter.h
>> rename to include/libcamera/internal/converter/converter_v4l2_m2m.h
>> index f0ebe2e0..ef31eeba 100644
>> --- a/src/libcamera/pipeline/simple/converter.h
>> +++ b/include/libcamera/internal/converter/converter_v4l2_m2m.h
>> @@ -1,8 +1,9 @@
>>   /* SPDX-License-Identifier: LGPL-2.1-or-later */
>>   /*
>>    * Copyright (C) 2020, Laurent Pinchart
>> + * Copyright 2022 NXP
>>    *
>> - * converter.h - Format converter for simple pipeline handler
>> + * converter_v4l2_m2m.h - V4l2 M2M Format converter interface
>>    */
>>   
>>   #pragma once
>> @@ -19,6 +20,8 @@
>>   #include <libcamera/base/log.h>
>>   #include <libcamera/base/signal.h>
>>   
>> +#include "libcamera/internal/converter.h"
>> +
>>   namespace libcamera {
>>   
>>   class FrameBuffer;
>> @@ -28,11 +31,12 @@ class SizeRange;
>>   struct StreamConfiguration;
>>   class V4L2M2MDevice;
>>   
>> -class SimpleConverter
>> +class V4L2M2MConverter : public Converter
>>   {
>>   public:
>> -       SimpleConverter(MediaDevice *media);
>> +       V4L2M2MConverter(MediaDevice *media);
>>   
>> +       int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; }
>>          bool isValid() const { return m2m_ != nullptr; }
>>   
>>          std::vector<PixelFormat> formats(PixelFormat input);
>> @@ -52,14 +56,11 @@ public:
>>          int queueBuffers(FrameBuffer *input,
>>                           const std::map<unsigned int, FrameBuffer *> &outputs);
>>   
>> -       Signal<FrameBuffer *> inputBufferReady;
>> -       Signal<FrameBuffer *> outputBufferReady;
>> -
>>   private:
>>          class Stream : protected Loggable
>>          {
>>          public:
>> -               Stream(SimpleConverter *converter, unsigned int index);
>> +               Stream(V4L2M2MConverter *converter, unsigned int index);
>>   
>>                  bool isValid() const { return m2m_ != nullptr; }
>>   
>> @@ -80,7 +81,7 @@ private:
>>                  void captureBufferReady(FrameBuffer *buffer);
>>                  void outputBufferReady(FrameBuffer *buffer);
>>   
>> -               SimpleConverter *converter_;
>> +               V4L2M2MConverter *converter_;
>>                  unsigned int index_;
>>                  std::unique_ptr<V4L2M2MDevice> m2m_;
>>   
>> @@ -88,7 +89,6 @@ private:
>>                  unsigned int outputBufferCount_;
>>          };
>>   
>> -       std::string deviceNode_;
>>          std::unique_ptr<V4L2M2MDevice> m2m_;
>>   
>>          std::vector<Stream> streams_;
>> diff --git a/include/libcamera/internal/converter/meson.build b/include/libcamera/internal/converter/meson.build
>> new file mode 100644
>> index 00000000..891e79e7
>> --- /dev/null
>> +++ b/include/libcamera/internal/converter/meson.build
>> @@ -0,0 +1,5 @@
>> +# SPDX-License-Identifier: CC0-1.0
>> +
>> +libcamera_internal_headers += files([
>> +    'converter_v4l2_m2m.h',
> 
> I'll be interested to see what other convertors end in here too.
> 
> I could imagine a GPU/OpenGL convertor, a libjpeg convertor ... (I guess
> that's an encoder?, but I'd imagine the same base interface?) ... or others ...
I will likely have a need for adding drm devices as converter.
> 
> 
>> +])
>> diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
>> index 8f50d755..b9db5a8c 100644
>> --- a/include/libcamera/internal/meson.build
>> +++ b/include/libcamera/internal/meson.build
>> @@ -45,3 +45,5 @@ libcamera_internal_headers = files([
>>       'v4l2_videodevice.h',
>>       'yaml_parser.h',
>>   ])
>> +
>> +subdir('converter')
>> diff --git a/src/libcamera/pipeline/simple/converter.cpp b/src/libcamera/converter/converter_v4l2_m2m.cpp
>> similarity index 68%
>> rename from src/libcamera/pipeline/simple/converter.cpp
>> rename to src/libcamera/converter/converter_v4l2_m2m.cpp
>> index acaaa64c..31acb048 100644
>> --- a/src/libcamera/pipeline/simple/converter.cpp
>> +++ b/src/libcamera/converter/converter_v4l2_m2m.cpp
>> @@ -1,12 +1,11 @@
>>   /* SPDX-License-Identifier: LGPL-2.1-or-later */
>>   /*
>>    * Copyright (C) 2020, Laurent Pinchart
>> + * Copyright 2022 NXP
>>    *
>> - * converter.cpp - Format converter for simple pipeline handler
>> + * converter_v4l2_m2m.cpp - V4L2 M2M Format converter
>>    */
>>   
>> -#include "converter.h"
>> -
>>   #include <algorithm>
>>   #include <limits.h>
>>   
>> @@ -20,19 +19,25 @@
>>   
>>   #include "libcamera/internal/media_device.h"
>>   #include "libcamera/internal/v4l2_videodevice.h"
>> +#include "libcamera/internal/converter/converter_v4l2_m2m.h"
>> +
>> +/**
>> + * \file internal/converter/converter_v4l2_m2m.h
>> + * \brief V4L2 M2M based converter
>> + */
>>   
>>   namespace libcamera {
>>   
>> -LOG_DECLARE_CATEGORY(SimplePipeline)
>> +LOG_DECLARE_CATEGORY(Converter)
> 
> Hrm.. no, we normally have one log category per component. I think this
> should be the V4L2M2MConvertor log category ?
> 
> But maybe we should have more generic log categories on some occasions?
> So this isn't a hard reject...
I initially started with a log category per component, but this was not 
really convenient as you had to deal with 3 different categories 
(generic converter, v4l2m2m, specialized m2m for handling dewarp 
device)...so I finally ended with this proposal, using an unique 
converter log category. I can revert to the legacy way if you dislike 
this proposal.

> 
> 
>>   
>>   /* -----------------------------------------------------------------------------
>> - * SimpleConverter::Stream
>> + * V4L2M2MConverter::Stream
>>    */
>>   
>> -SimpleConverter::Stream::Stream(SimpleConverter *converter, unsigned int index)
>> +V4L2M2MConverter::Stream::Stream(V4L2M2MConverter *converter, unsigned int index)
>>          : converter_(converter), index_(index)
>>   {
>> -       m2m_ = std::make_unique<V4L2M2MDevice>(converter->deviceNode_);
>> +       m2m_ = std::make_unique<V4L2M2MDevice>(converter->deviceNode());
>>   
>>          m2m_->output()->bufferReady.connect(this, &Stream::outputBufferReady);
>>          m2m_->capture()->bufferReady.connect(this, &Stream::captureBufferReady);
>> @@ -42,8 +47,8 @@ SimpleConverter::Stream::Stream(SimpleConverter *converter, unsigned int index)
>>                  m2m_.reset();
>>   }
>>   
>> -int SimpleConverter::Stream::configure(const StreamConfiguration &inputCfg,
>> -                                      const StreamConfiguration &outputCfg)
>> +int V4L2M2MConverter::Stream::configure(const StreamConfiguration &inputCfg,
>> +                                       const StreamConfiguration &outputCfg)
>>   {
>>          V4L2PixelFormat videoFormat =
>>                  m2m_->output()->toV4L2PixelFormat(inputCfg.pixelFormat);
>> @@ -56,14 +61,14 @@ int SimpleConverter::Stream::configure(const StreamConfiguration &inputCfg,
>>   
>>          int ret = m2m_->output()->setFormat(&format);
>>          if (ret < 0) {
>> -               LOG(SimplePipeline, Error)
>> +               LOG(Converter, Error)
>>                          << "Failed to set input format: " << strerror(-ret);
>>                  return ret;
>>          }
>>   
>>          if (format.fourcc != videoFormat || format.size != inputCfg.size ||
>>              format.planes[0].bpl != inputCfg.stride) {
>> -               LOG(SimplePipeline, Error)
>> +               LOG(Converter, Error)
>>                          << "Input format not supported (requested "
>>                          << inputCfg.size << "-" << videoFormat
>>                          << ", got " << format << ")";
>> @@ -78,13 +83,13 @@ int SimpleConverter::Stream::configure(const StreamConfiguration &inputCfg,
>>   
>>          ret = m2m_->capture()->setFormat(&format);
>>          if (ret < 0) {
>> -               LOG(SimplePipeline, Error)
>> +               LOG(Converter, Error)
>>                          << "Failed to set output format: " << strerror(-ret);
>>                  return ret;
>>          }
>>   
>>          if (format.fourcc != videoFormat || format.size != outputCfg.size) {
>> -               LOG(SimplePipeline, Error)
>> +               LOG(Converter, Error)
>>                          << "Output format not supported";
>>                  return -EINVAL;
>>          }
>> @@ -95,13 +100,13 @@ int SimpleConverter::Stream::configure(const StreamConfiguration &inputCfg,
>>          return 0;
>>   }
>>   
>> -int SimpleConverter::Stream::exportBuffers(unsigned int count,
>> -                                          std::vector<std::unique_ptr<FrameBuffer>> *buffers)
>> +int V4L2M2MConverter::Stream::exportBuffers(unsigned int count,
>> +                                           std::vector<std::unique_ptr<FrameBuffer>> *buffers)
>>   {
>>          return m2m_->capture()->exportBuffers(count, buffers);
>>   }
>>   
>> -int SimpleConverter::Stream::start()
>> +int V4L2M2MConverter::Stream::start()
>>   {
>>          int ret = m2m_->output()->importBuffers(inputBufferCount_);
>>          if (ret < 0)
>> @@ -128,7 +133,7 @@ int SimpleConverter::Stream::start()
>>          return 0;
>>   }
>>   
>> -void SimpleConverter::Stream::stop()
>> +void V4L2M2MConverter::Stream::stop()
>>   {
>>          m2m_->capture()->streamOff();
>>          m2m_->output()->streamOff();
>> @@ -136,8 +141,7 @@ void SimpleConverter::Stream::stop()
>>          m2m_->output()->releaseBuffers();
>>   }
>>   
>> -int SimpleConverter::Stream::queueBuffers(FrameBuffer *input,
>> -                                         FrameBuffer *output)
>> +int V4L2M2MConverter::Stream::queueBuffers(FrameBuffer *input, FrameBuffer *output)
>>   {
>>          int ret = m2m_->output()->queueBuffer(input);
>>          if (ret < 0)
>> @@ -150,12 +154,12 @@ int SimpleConverter::Stream::queueBuffers(FrameBuffer *input,
>>          return 0;
>>   }
>>   
>> -std::string SimpleConverter::Stream::logPrefix() const
>> +std::string V4L2M2MConverter::Stream::logPrefix() const
>>   {
>>          return "stream" + std::to_string(index_);
> 
> I'm not yet sure if this will be unique enough if we have multiple
> convertors. However given the logging infrastructure will print a file
> and line number, I think it will be clear.
> 
> So I think this is ok.
> 
> 
>>   }
>>   
>> -void SimpleConverter::Stream::outputBufferReady(FrameBuffer *buffer)
>> +void V4L2M2MConverter::Stream::outputBufferReady(FrameBuffer *buffer)
>>   {
>>          auto it = converter_->queue_.find(buffer);
>>          if (it == converter_->queue_.end())
>> @@ -167,32 +171,34 @@ void SimpleConverter::Stream::outputBufferReady(FrameBuffer *buffer)
>>          }
>>   }
>>   
>> -void SimpleConverter::Stream::captureBufferReady(FrameBuffer *buffer)
>> +void V4L2M2MConverter::Stream::captureBufferReady(FrameBuffer *buffer)
>>   {
>>          converter_->outputBufferReady.emit(buffer);
>>   }
>>   
>>   /* -----------------------------------------------------------------------------
>> - * SimpleConverter
>> + * V4L2M2MConverter
>> + */
>> +
>> +/**
>> + * \class libcamera::V4L2M2MConverter
>> + * \brief The V4L2 M2M converter implements the converter interface based on
>> + * V4L2 M2M device.
>> +*/
>> +
>> +/**
>> + * \fn V4L2M2MConverter::V4L2M2MConverter
>> + * \brief Construct a V4L2M2MConverter instance
>> + * \param[in] media The media device implementing the converter
>>    */
>>   
>> -SimpleConverter::SimpleConverter(MediaDevice *media)
>> +V4L2M2MConverter::V4L2M2MConverter(MediaDevice *media)
>> +       : Converter(media)
>>   {
>> -       /*
>> -        * Locate the video node. There's no need to validate the pipeline
>> -        * further, the caller guarantees that this is a V4L2 mem2mem device.
>> -        */
>> -       const std::vector<MediaEntity *> &entities = media->entities();
>> -       auto it = std::find_if(entities.begin(), entities.end(),
>> -                              [](MediaEntity *entity) {
>> -                                      return entity->function() == MEDIA_ENT_F_IO_V4L;
>> -                              });
>> -       if (it == entities.end())
>> +       if (deviceNode().empty())
> 
> Do we need any kind of warning here? I presume this means we were unable
> to construct a suitable convertor... Or will the Converter(media)
> initialiser have already printed an error in that instance?
> 
>>                  return;
>>   
>> -       deviceNode_ = (*it)->deviceNode();
>> -
>> -       m2m_ = std::make_unique<V4L2M2MDevice>(deviceNode_);
>> +       m2m_ = std::make_unique<V4L2M2MDevice>(deviceNode());
>>          int ret = m2m_->open();
>>          if (ret < 0) {
>>                  m2m_.reset();
>> @@ -200,7 +206,21 @@ SimpleConverter::SimpleConverter(MediaDevice *media)
>>          }
>>   }
>>   
>> -std::vector<PixelFormat> SimpleConverter::formats(PixelFormat input)
>> +/**
>> + * \fn libcamera::V4L2M2MConverter::loadConfiguration
>> + * \details \copydetails libcamera::Converter::loadConfiguration
>> + */
>> +
>> +/**
>> + * \fn libcamera::V4L2M2MConverter::isValid
>> + * \details \copydetails libcamera::Converter::isValid
>> + */
>> +
>> +/**
>> + * \fn libcamera::V4L2M2MConverter::formats
>> + * \details \copydetails libcamera::Converter::formats
>> + */
>> +std::vector<PixelFormat> V4L2M2MConverter::formats(PixelFormat input)
>>   {
>>          if (!m2m_)
>>                  return {};
>> @@ -215,13 +235,13 @@ std::vector<PixelFormat> SimpleConverter::formats(PixelFormat input)
>>   
>>          int ret = m2m_->output()->setFormat(&v4l2Format);
>>          if (ret < 0) {
>> -               LOG(SimplePipeline, Error)
>> +               LOG(Converter, Error)
>>                          << "Failed to set format: " << strerror(-ret);
>>                  return {};
>>          }
>>   
>>          if (v4l2Format.fourcc != m2m_->output()->toV4L2PixelFormat(input)) {
>> -               LOG(SimplePipeline, Debug)
>> +               LOG(Converter, Debug)
>>                          << "Input format " << input << " not supported.";
>>                  return {};
>>          }
>> @@ -237,7 +257,10 @@ std::vector<PixelFormat> SimpleConverter::formats(PixelFormat input)
>>          return pixelFormats;
>>   }
>>   
>> -SizeRange SimpleConverter::sizes(const Size &input)
>> +/**
>> + * \copydoc libcamera::Converter::sizes
>> + */
>> +SizeRange V4L2M2MConverter::sizes(const Size &input)
>>   {
>>          if (!m2m_)
>>                  return {};
>> @@ -252,7 +275,7 @@ SizeRange SimpleConverter::sizes(const Size &input)
>>   
>>          int ret = m2m_->output()->setFormat(&format);
>>          if (ret < 0) {
>> -               LOG(SimplePipeline, Error)
>> +               LOG(Converter, Error)
>>                          << "Failed to set format: " << strerror(-ret);
>>                  return {};
>>          }
>> @@ -262,7 +285,7 @@ SizeRange SimpleConverter::sizes(const Size &input)
>>          format.size = { 1, 1 };
>>          ret = m2m_->capture()->setFormat(&format);
>>          if (ret < 0) {
>> -               LOG(SimplePipeline, Error)
>> +               LOG(Converter, Error)
>>                          << "Failed to set format: " << strerror(-ret);
>>                  return {};
>>          }
>> @@ -272,7 +295,7 @@ SizeRange SimpleConverter::sizes(const Size &input)
>>          format.size = { UINT_MAX, UINT_MAX };
>>          ret = m2m_->capture()->setFormat(&format);
>>          if (ret < 0) {
>> -               LOG(SimplePipeline, Error)
>> +               LOG(Converter, Error)
>>                          << "Failed to set format: " << strerror(-ret);
>>                  return {};
>>          }
>> @@ -282,9 +305,12 @@ SizeRange SimpleConverter::sizes(const Size &input)
>>          return sizes;
>>   }
>>   
>> +/**
>> + * \copydoc libcamera::Converter::strideAndFrameSize
>> + */
>>   std::tuple<unsigned int, unsigned int>
>> -SimpleConverter::strideAndFrameSize(const PixelFormat &pixelFormat,
>> -                                   const Size &size)
>> +V4L2M2MConverter::strideAndFrameSize(const PixelFormat &pixelFormat,
>> +                                    const Size &size)
>>   {
>>          V4L2DeviceFormat format;
>>          format.fourcc = m2m_->capture()->toV4L2PixelFormat(pixelFormat);
>> @@ -297,8 +323,11 @@ SimpleConverter::strideAndFrameSize(const PixelFormat &pixelFormat,
>>          return std::make_tuple(format.planes[0].bpl, format.planes[0].size);
>>   }
>>   
>> -int SimpleConverter::configure(const StreamConfiguration &inputCfg,
>> -                              const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs)
>> +/**
>> + * \copydoc libcamera::Converter::configure
>> + */
>> +int V4L2M2MConverter::configure(const StreamConfiguration &inputCfg,
>> +                               const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs)
>>   {
>>          int ret = 0;
>>   
>> @@ -309,7 +338,7 @@ int SimpleConverter::configure(const StreamConfiguration &inputCfg,
>>                  Stream &stream = streams_.emplace_back(this, i);
>>   
>>                  if (!stream.isValid()) {
>> -                       LOG(SimplePipeline, Error)
>> +                       LOG(Converter, Error)
>>                                  << "Failed to create stream " << i;
>>                          ret = -EINVAL;
>>                          break;
>> @@ -328,8 +357,11 @@ int SimpleConverter::configure(const StreamConfiguration &inputCfg,
>>          return 0;
>>   }
>>   
>> -int SimpleConverter::exportBuffers(unsigned int output, unsigned int count,
>> -                                  std::vector<std::unique_ptr<FrameBuffer>> *buffers)
>> +/**
>> + * \copydoc libcamera::Converter::exportBuffers
>> + */
>> +int V4L2M2MConverter::exportBuffers(unsigned int output, unsigned int count,
>> +                                   std::vector<std::unique_ptr<FrameBuffer>> *buffers)
>>   {
>>          if (output >= streams_.size())
>>                  return -EINVAL;
>> @@ -337,7 +369,10 @@ int SimpleConverter::exportBuffers(unsigned int output, unsigned int count,
>>          return streams_[output].exportBuffers(count, buffers);
>>   }
>>   
>> -int SimpleConverter::start()
>> +/**
>> + * \copydoc libcamera::Converter::start
>> + */
>> +int V4L2M2MConverter::start()
>>   {
>>          int ret;
>>   
>> @@ -352,14 +387,20 @@ int SimpleConverter::start()
>>          return 0;
>>   }
>>   
>> -void SimpleConverter::stop()
>> +/**
>> + * \copydoc libcamera::Converter::stop
>> + */
>> +void V4L2M2MConverter::stop()
>>   {
>>          for (Stream &stream : utils::reverse(streams_))
>>                  stream.stop();
>>   }
>>   
>> -int SimpleConverter::queueBuffers(FrameBuffer *input,
>> -                                 const std::map<unsigned int, FrameBuffer *> &outputs)
>> +/**
>> + * \copydoc libcamera::Converter::queueBuffers
>> + */
>> +int V4L2M2MConverter::queueBuffers(FrameBuffer *input,
>> +                                  const std::map<unsigned int, FrameBuffer *> &outputs)
>>   {
>>          unsigned int mask = 0;
>>          int ret;
>> @@ -402,4 +443,6 @@ int SimpleConverter::queueBuffers(FrameBuffer *input,
>>          return 0;
>>   }
>>   
>> +REGISTER_CONVERTER("v4l2_m2m", V4L2M2MConverter, "pxp")
> 
> I think the only part I'd change here is that the "pxp" might be a
> statically created array or vector of 'compatible' strings, a bit like
> the kernel would define.
> 
> Then adding support for a new compatible would just be a new line in a
> table, rather than modifying this registration. It's not much, but it
> would feel 'neater' to just have a single line patch to add an entry.
> 
static std::initializer_list<std::string> aliases = {
	"pxp"
};

REGISTER_CONVERTER("v4l2_m2m", V4L2M2MConverter, aliases)

Would it be better ?
> 
> And once again, I don't think there are any blockers there. Some things
> to consider and tidy up for a v4, but I'd be happy to merge at that.
> 
> 
> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> 
> 
>> +
>>   } /* namespace libcamera */
>> diff --git a/src/libcamera/converter/meson.build b/src/libcamera/converter/meson.build
>> new file mode 100644
>> index 00000000..2aa72fe4
>> --- /dev/null
>> +++ b/src/libcamera/converter/meson.build
>> @@ -0,0 +1,5 @@
>> +# SPDX-License-Identifier: CC0-1.0
>> +
>> +libcamera_sources += files([
>> +        'converter_v4l2_m2m.cpp'
>> +])
>> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
>> index e9d0324e..ffc294f3 100644
>> --- a/src/libcamera/meson.build
>> +++ b/src/libcamera/meson.build
>> @@ -62,6 +62,7 @@ libatomic = cc.find_library('atomic', required : false)
>>   libthreads = dependency('threads')
>>   
>>   subdir('base')
>> +subdir('converter')
>>   subdir('ipa')
>>   subdir('pipeline')
>>   subdir('proxy')
>> diff --git a/src/libcamera/pipeline/simple/meson.build b/src/libcamera/pipeline/simple/meson.build
>> index 9c99b32f..42b0896d 100644
>> --- a/src/libcamera/pipeline/simple/meson.build
>> +++ b/src/libcamera/pipeline/simple/meson.build
>> @@ -1,6 +1,5 @@
>>   # SPDX-License-Identifier: CC0-1.0
>>   
>>   libcamera_sources += files([
>> -    'converter.cpp',
>>       'simple.cpp',
>>   ])
>> diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
>> index a32de7f3..24ded4db 100644
>> --- a/src/libcamera/pipeline/simple/simple.cpp
>> +++ b/src/libcamera/pipeline/simple/simple.cpp
>> @@ -30,13 +30,13 @@
>>   
>>   #include "libcamera/internal/camera.h"
>>   #include "libcamera/internal/camera_sensor.h"
>> +#include "libcamera/internal/converter.h"
>>   #include "libcamera/internal/device_enumerator.h"
>>   #include "libcamera/internal/media_device.h"
>>   #include "libcamera/internal/pipeline_handler.h"
>>   #include "libcamera/internal/v4l2_subdevice.h"
>>   #include "libcamera/internal/v4l2_videodevice.h"
>>   
>> -#include "converter.h"
>>   
>>   namespace libcamera {
>>   
>> @@ -266,7 +266,7 @@ public:
>>          std::vector<Configuration> configs_;
>>          std::map<PixelFormat, std::vector<const Configuration *>> formats_;
>>   
>> -       std::unique_ptr<SimpleConverter> converter_;
>> +       std::unique_ptr<Converter> converter_;
>>          std::vector<std::unique_ptr<FrameBuffer>> converterBuffers_;
>>          bool useConverter_;
>>          std::queue<std::map<unsigned int, FrameBuffer *>> converterQueue_;
>> @@ -492,7 +492,7 @@ int SimpleCameraData::init()
>>          /* Open the converter, if any. */
>>          MediaDevice *converter = pipe->converter();
>>          if (converter) {
>> -               converter_ = std::make_unique<SimpleConverter>(converter);
>> +               converter_ = ConverterFactoryBase::create(converter);
>>                  if (!converter_->isValid()) {
>>                          LOG(SimplePipeline, Warning)
>>                                  << "Failed to create converter, disabling format conversion";
>> -- 
>> 2.38.1
>>
Kieran Bingham Dec. 12, 2022, 5:25 p.m. UTC | #3
Hi Xavier,

Quoting Xavier Roumegue (OSS) (2022-12-10 15:15:03)
> Hi Kieran,
> 
> Thanks for your review.
> 
> On 12/7/22 13:55, Kieran Bingham wrote:
> > Quoting Xavier Roumegue via libcamera-devel (2022-11-17 18:52:04)
> >> Move the simple converter implementation to a generic V4L2 M2M class
> >> derived from the converter interface. This latter could be used by
> >> other pipeline implementations and as base class for customized V4L2 M2M
> >> converters.
> >>
> > 
> > I like the sound of this!
> > 
> > 
> >> Signed-off-by: Xavier Roumegue <xavier.roumegue@oss.nxp.com>
> >> Reviewed-by: Jacopo Mondi <jacopo@jmondi.org>
> >> ---
> >>   .../internal/converter/converter_v4l2_m2m.h   |  18 +--
> >>   .../libcamera/internal/converter/meson.build  |   5 +
> >>   include/libcamera/internal/meson.build        |   2 +
> >>   .../converter_v4l2_m2m.cpp}                   | 153 +++++++++++-------
> >>   src/libcamera/converter/meson.build           |   5 +
> >>   src/libcamera/meson.build                     |   1 +
> >>   src/libcamera/pipeline/simple/meson.build     |   1 -
> >>   src/libcamera/pipeline/simple/simple.cpp      |   6 +-
> >>   8 files changed, 123 insertions(+), 68 deletions(-)
> >>   rename src/libcamera/pipeline/simple/converter.h => include/libcamera/internal/converter/converter_v4l2_m2m.h (83%)
> >>   create mode 100644 include/libcamera/internal/converter/meson.build
> >>   rename src/libcamera/{pipeline/simple/converter.cpp => converter/converter_v4l2_m2m.cpp} (68%)
> >>   create mode 100644 src/libcamera/converter/meson.build
> >>
> >> diff --git a/src/libcamera/pipeline/simple/converter.h b/include/libcamera/internal/converter/converter_v4l2_m2m.h
> >> similarity index 83%
> >> rename from src/libcamera/pipeline/simple/converter.h
> >> rename to include/libcamera/internal/converter/converter_v4l2_m2m.h
> >> index f0ebe2e0..ef31eeba 100644
> >> --- a/src/libcamera/pipeline/simple/converter.h
> >> +++ b/include/libcamera/internal/converter/converter_v4l2_m2m.h
> >> @@ -1,8 +1,9 @@
> >>   /* SPDX-License-Identifier: LGPL-2.1-or-later */
> >>   /*
> >>    * Copyright (C) 2020, Laurent Pinchart
> >> + * Copyright 2022 NXP
> >>    *
> >> - * converter.h - Format converter for simple pipeline handler
> >> + * converter_v4l2_m2m.h - V4l2 M2M Format converter interface
> >>    */
> >>   
> >>   #pragma once
> >> @@ -19,6 +20,8 @@
> >>   #include <libcamera/base/log.h>
> >>   #include <libcamera/base/signal.h>
> >>   
> >> +#include "libcamera/internal/converter.h"
> >> +
> >>   namespace libcamera {
> >>   
> >>   class FrameBuffer;
> >> @@ -28,11 +31,12 @@ class SizeRange;
> >>   struct StreamConfiguration;
> >>   class V4L2M2MDevice;
> >>   
> >> -class SimpleConverter
> >> +class V4L2M2MConverter : public Converter
> >>   {
> >>   public:
> >> -       SimpleConverter(MediaDevice *media);
> >> +       V4L2M2MConverter(MediaDevice *media);
> >>   
> >> +       int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; }
> >>          bool isValid() const { return m2m_ != nullptr; }
> >>   
> >>          std::vector<PixelFormat> formats(PixelFormat input);
> >> @@ -52,14 +56,11 @@ public:
> >>          int queueBuffers(FrameBuffer *input,
> >>                           const std::map<unsigned int, FrameBuffer *> &outputs);
> >>   
> >> -       Signal<FrameBuffer *> inputBufferReady;
> >> -       Signal<FrameBuffer *> outputBufferReady;
> >> -
> >>   private:
> >>          class Stream : protected Loggable
> >>          {
> >>          public:
> >> -               Stream(SimpleConverter *converter, unsigned int index);
> >> +               Stream(V4L2M2MConverter *converter, unsigned int index);
> >>   
> >>                  bool isValid() const { return m2m_ != nullptr; }
> >>   
> >> @@ -80,7 +81,7 @@ private:
> >>                  void captureBufferReady(FrameBuffer *buffer);
> >>                  void outputBufferReady(FrameBuffer *buffer);
> >>   
> >> -               SimpleConverter *converter_;
> >> +               V4L2M2MConverter *converter_;
> >>                  unsigned int index_;
> >>                  std::unique_ptr<V4L2M2MDevice> m2m_;
> >>   
> >> @@ -88,7 +89,6 @@ private:
> >>                  unsigned int outputBufferCount_;
> >>          };
> >>   
> >> -       std::string deviceNode_;
> >>          std::unique_ptr<V4L2M2MDevice> m2m_;
> >>   
> >>          std::vector<Stream> streams_;
> >> diff --git a/include/libcamera/internal/converter/meson.build b/include/libcamera/internal/converter/meson.build
> >> new file mode 100644
> >> index 00000000..891e79e7
> >> --- /dev/null
> >> +++ b/include/libcamera/internal/converter/meson.build
> >> @@ -0,0 +1,5 @@
> >> +# SPDX-License-Identifier: CC0-1.0
> >> +
> >> +libcamera_internal_headers += files([
> >> +    'converter_v4l2_m2m.h',
> > 
> > I'll be interested to see what other convertors end in here too.
> > 
> > I could imagine a GPU/OpenGL convertor, a libjpeg convertor ... (I guess
> > that's an encoder?, but I'd imagine the same base interface?) ... or others ...
> I will likely have a need for adding drm devices as converter.
> > 
> > 
> >> +])
> >> diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
> >> index 8f50d755..b9db5a8c 100644
> >> --- a/include/libcamera/internal/meson.build
> >> +++ b/include/libcamera/internal/meson.build
> >> @@ -45,3 +45,5 @@ libcamera_internal_headers = files([
> >>       'v4l2_videodevice.h',
> >>       'yaml_parser.h',
> >>   ])
> >> +
> >> +subdir('converter')
> >> diff --git a/src/libcamera/pipeline/simple/converter.cpp b/src/libcamera/converter/converter_v4l2_m2m.cpp
> >> similarity index 68%
> >> rename from src/libcamera/pipeline/simple/converter.cpp
> >> rename to src/libcamera/converter/converter_v4l2_m2m.cpp
> >> index acaaa64c..31acb048 100644
> >> --- a/src/libcamera/pipeline/simple/converter.cpp
> >> +++ b/src/libcamera/converter/converter_v4l2_m2m.cpp
> >> @@ -1,12 +1,11 @@
> >>   /* SPDX-License-Identifier: LGPL-2.1-or-later */
> >>   /*
> >>    * Copyright (C) 2020, Laurent Pinchart
> >> + * Copyright 2022 NXP
> >>    *
> >> - * converter.cpp - Format converter for simple pipeline handler
> >> + * converter_v4l2_m2m.cpp - V4L2 M2M Format converter
> >>    */
> >>   
> >> -#include "converter.h"
> >> -
> >>   #include <algorithm>
> >>   #include <limits.h>
> >>   
> >> @@ -20,19 +19,25 @@
> >>   
> >>   #include "libcamera/internal/media_device.h"
> >>   #include "libcamera/internal/v4l2_videodevice.h"
> >> +#include "libcamera/internal/converter/converter_v4l2_m2m.h"
> >> +
> >> +/**
> >> + * \file internal/converter/converter_v4l2_m2m.h
> >> + * \brief V4L2 M2M based converter
> >> + */
> >>   
> >>   namespace libcamera {
> >>   
> >> -LOG_DECLARE_CATEGORY(SimplePipeline)
> >> +LOG_DECLARE_CATEGORY(Converter)
> > 
> > Hrm.. no, we normally have one log category per component. I think this
> > should be the V4L2M2MConvertor log category ?
> > 
> > But maybe we should have more generic log categories on some occasions?
> > So this isn't a hard reject...

> I initially started with a log category per component, but this was not 
> really convenient as you had to deal with 3 different categories 
> (generic converter, v4l2m2m, specialized m2m for handling dewarp 
> device)...so I finally ended with this proposal, using an unique 
> converter log category. I can revert to the legacy way if you dislike 
> this proposal.

I think this is fine for the time being. If it becomes troublesome when
we have lots of convertors we can make it more specific then...


> >>   
> >>   /* -----------------------------------------------------------------------------
> >> - * SimpleConverter::Stream
> >> + * V4L2M2MConverter::Stream
> >>    */
> >>   
> >> -SimpleConverter::Stream::Stream(SimpleConverter *converter, unsigned int index)
> >> +V4L2M2MConverter::Stream::Stream(V4L2M2MConverter *converter, unsigned int index)
> >>          : converter_(converter), index_(index)
> >>   {
> >> -       m2m_ = std::make_unique<V4L2M2MDevice>(converter->deviceNode_);
> >> +       m2m_ = std::make_unique<V4L2M2MDevice>(converter->deviceNode());
> >>   
> >>          m2m_->output()->bufferReady.connect(this, &Stream::outputBufferReady);
> >>          m2m_->capture()->bufferReady.connect(this, &Stream::captureBufferReady);
> >> @@ -42,8 +47,8 @@ SimpleConverter::Stream::Stream(SimpleConverter *converter, unsigned int index)
> >>                  m2m_.reset();
> >>   }
> >>   
> >> -int SimpleConverter::Stream::configure(const StreamConfiguration &inputCfg,
> >> -                                      const StreamConfiguration &outputCfg)
> >> +int V4L2M2MConverter::Stream::configure(const StreamConfiguration &inputCfg,
> >> +                                       const StreamConfiguration &outputCfg)
> >>   {
> >>          V4L2PixelFormat videoFormat =
> >>                  m2m_->output()->toV4L2PixelFormat(inputCfg.pixelFormat);
> >> @@ -56,14 +61,14 @@ int SimpleConverter::Stream::configure(const StreamConfiguration &inputCfg,
> >>   
> >>          int ret = m2m_->output()->setFormat(&format);
> >>          if (ret < 0) {
> >> -               LOG(SimplePipeline, Error)
> >> +               LOG(Converter, Error)
> >>                          << "Failed to set input format: " << strerror(-ret);
> >>                  return ret;
> >>          }
> >>   
> >>          if (format.fourcc != videoFormat || format.size != inputCfg.size ||
> >>              format.planes[0].bpl != inputCfg.stride) {
> >> -               LOG(SimplePipeline, Error)
> >> +               LOG(Converter, Error)
> >>                          << "Input format not supported (requested "
> >>                          << inputCfg.size << "-" << videoFormat
> >>                          << ", got " << format << ")";
> >> @@ -78,13 +83,13 @@ int SimpleConverter::Stream::configure(const StreamConfiguration &inputCfg,
> >>   
> >>          ret = m2m_->capture()->setFormat(&format);
> >>          if (ret < 0) {
> >> -               LOG(SimplePipeline, Error)
> >> +               LOG(Converter, Error)
> >>                          << "Failed to set output format: " << strerror(-ret);
> >>                  return ret;
> >>          }
> >>   
> >>          if (format.fourcc != videoFormat || format.size != outputCfg.size) {
> >> -               LOG(SimplePipeline, Error)
> >> +               LOG(Converter, Error)
> >>                          << "Output format not supported";
> >>                  return -EINVAL;
> >>          }
> >> @@ -95,13 +100,13 @@ int SimpleConverter::Stream::configure(const StreamConfiguration &inputCfg,
> >>          return 0;
> >>   }
> >>   
> >> -int SimpleConverter::Stream::exportBuffers(unsigned int count,
> >> -                                          std::vector<std::unique_ptr<FrameBuffer>> *buffers)
> >> +int V4L2M2MConverter::Stream::exportBuffers(unsigned int count,
> >> +                                           std::vector<std::unique_ptr<FrameBuffer>> *buffers)
> >>   {
> >>          return m2m_->capture()->exportBuffers(count, buffers);
> >>   }
> >>   
> >> -int SimpleConverter::Stream::start()
> >> +int V4L2M2MConverter::Stream::start()
> >>   {
> >>          int ret = m2m_->output()->importBuffers(inputBufferCount_);
> >>          if (ret < 0)
> >> @@ -128,7 +133,7 @@ int SimpleConverter::Stream::start()
> >>          return 0;
> >>   }
> >>   
> >> -void SimpleConverter::Stream::stop()
> >> +void V4L2M2MConverter::Stream::stop()
> >>   {
> >>          m2m_->capture()->streamOff();
> >>          m2m_->output()->streamOff();
> >> @@ -136,8 +141,7 @@ void SimpleConverter::Stream::stop()
> >>          m2m_->output()->releaseBuffers();
> >>   }
> >>   
> >> -int SimpleConverter::Stream::queueBuffers(FrameBuffer *input,
> >> -                                         FrameBuffer *output)
> >> +int V4L2M2MConverter::Stream::queueBuffers(FrameBuffer *input, FrameBuffer *output)
> >>   {
> >>          int ret = m2m_->output()->queueBuffer(input);
> >>          if (ret < 0)
> >> @@ -150,12 +154,12 @@ int SimpleConverter::Stream::queueBuffers(FrameBuffer *input,
> >>          return 0;
> >>   }
> >>   
> >> -std::string SimpleConverter::Stream::logPrefix() const
> >> +std::string V4L2M2MConverter::Stream::logPrefix() const
> >>   {
> >>          return "stream" + std::to_string(index_);
> > 
> > I'm not yet sure if this will be unique enough if we have multiple
> > convertors. However given the logging infrastructure will print a file
> > and line number, I think it will be clear.
> > 
> > So I think this is ok.
> > 
> > 
> >>   }
> >>   
> >> -void SimpleConverter::Stream::outputBufferReady(FrameBuffer *buffer)
> >> +void V4L2M2MConverter::Stream::outputBufferReady(FrameBuffer *buffer)
> >>   {
> >>          auto it = converter_->queue_.find(buffer);
> >>          if (it == converter_->queue_.end())
> >> @@ -167,32 +171,34 @@ void SimpleConverter::Stream::outputBufferReady(FrameBuffer *buffer)
> >>          }
> >>   }
> >>   
> >> -void SimpleConverter::Stream::captureBufferReady(FrameBuffer *buffer)
> >> +void V4L2M2MConverter::Stream::captureBufferReady(FrameBuffer *buffer)
> >>   {
> >>          converter_->outputBufferReady.emit(buffer);
> >>   }
> >>   
> >>   /* -----------------------------------------------------------------------------
> >> - * SimpleConverter
> >> + * V4L2M2MConverter
> >> + */
> >> +
> >> +/**
> >> + * \class libcamera::V4L2M2MConverter
> >> + * \brief The V4L2 M2M converter implements the converter interface based on
> >> + * V4L2 M2M device.
> >> +*/
> >> +
> >> +/**
> >> + * \fn V4L2M2MConverter::V4L2M2MConverter
> >> + * \brief Construct a V4L2M2MConverter instance
> >> + * \param[in] media The media device implementing the converter
> >>    */
> >>   
> >> -SimpleConverter::SimpleConverter(MediaDevice *media)
> >> +V4L2M2MConverter::V4L2M2MConverter(MediaDevice *media)
> >> +       : Converter(media)
> >>   {
> >> -       /*
> >> -        * Locate the video node. There's no need to validate the pipeline
> >> -        * further, the caller guarantees that this is a V4L2 mem2mem device.
> >> -        */
> >> -       const std::vector<MediaEntity *> &entities = media->entities();
> >> -       auto it = std::find_if(entities.begin(), entities.end(),
> >> -                              [](MediaEntity *entity) {
> >> -                                      return entity->function() == MEDIA_ENT_F_IO_V4L;
> >> -                              });
> >> -       if (it == entities.end())
> >> +       if (deviceNode().empty())
> > 
> > Do we need any kind of warning here? I presume this means we were unable
> > to construct a suitable convertor... Or will the Converter(media)
> > initialiser have already printed an error in that instance?
> > 
> >>                  return;
> >>   
> >> -       deviceNode_ = (*it)->deviceNode();
> >> -
> >> -       m2m_ = std::make_unique<V4L2M2MDevice>(deviceNode_);
> >> +       m2m_ = std::make_unique<V4L2M2MDevice>(deviceNode());
> >>          int ret = m2m_->open();
> >>          if (ret < 0) {
> >>                  m2m_.reset();
> >> @@ -200,7 +206,21 @@ SimpleConverter::SimpleConverter(MediaDevice *media)
> >>          }
> >>   }
> >>   
> >> -std::vector<PixelFormat> SimpleConverter::formats(PixelFormat input)
> >> +/**
> >> + * \fn libcamera::V4L2M2MConverter::loadConfiguration
> >> + * \details \copydetails libcamera::Converter::loadConfiguration
> >> + */
> >> +
> >> +/**
> >> + * \fn libcamera::V4L2M2MConverter::isValid
> >> + * \details \copydetails libcamera::Converter::isValid
> >> + */
> >> +
> >> +/**
> >> + * \fn libcamera::V4L2M2MConverter::formats
> >> + * \details \copydetails libcamera::Converter::formats
> >> + */
> >> +std::vector<PixelFormat> V4L2M2MConverter::formats(PixelFormat input)
> >>   {
> >>          if (!m2m_)
> >>                  return {};
> >> @@ -215,13 +235,13 @@ std::vector<PixelFormat> SimpleConverter::formats(PixelFormat input)
> >>   
> >>          int ret = m2m_->output()->setFormat(&v4l2Format);
> >>          if (ret < 0) {
> >> -               LOG(SimplePipeline, Error)
> >> +               LOG(Converter, Error)
> >>                          << "Failed to set format: " << strerror(-ret);
> >>                  return {};
> >>          }
> >>   
> >>          if (v4l2Format.fourcc != m2m_->output()->toV4L2PixelFormat(input)) {
> >> -               LOG(SimplePipeline, Debug)
> >> +               LOG(Converter, Debug)
> >>                          << "Input format " << input << " not supported.";
> >>                  return {};
> >>          }
> >> @@ -237,7 +257,10 @@ std::vector<PixelFormat> SimpleConverter::formats(PixelFormat input)
> >>          return pixelFormats;
> >>   }
> >>   
> >> -SizeRange SimpleConverter::sizes(const Size &input)
> >> +/**
> >> + * \copydoc libcamera::Converter::sizes
> >> + */
> >> +SizeRange V4L2M2MConverter::sizes(const Size &input)
> >>   {
> >>          if (!m2m_)
> >>                  return {};
> >> @@ -252,7 +275,7 @@ SizeRange SimpleConverter::sizes(const Size &input)
> >>   
> >>          int ret = m2m_->output()->setFormat(&format);
> >>          if (ret < 0) {
> >> -               LOG(SimplePipeline, Error)
> >> +               LOG(Converter, Error)
> >>                          << "Failed to set format: " << strerror(-ret);
> >>                  return {};
> >>          }
> >> @@ -262,7 +285,7 @@ SizeRange SimpleConverter::sizes(const Size &input)
> >>          format.size = { 1, 1 };
> >>          ret = m2m_->capture()->setFormat(&format);
> >>          if (ret < 0) {
> >> -               LOG(SimplePipeline, Error)
> >> +               LOG(Converter, Error)
> >>                          << "Failed to set format: " << strerror(-ret);
> >>                  return {};
> >>          }
> >> @@ -272,7 +295,7 @@ SizeRange SimpleConverter::sizes(const Size &input)
> >>          format.size = { UINT_MAX, UINT_MAX };
> >>          ret = m2m_->capture()->setFormat(&format);
> >>          if (ret < 0) {
> >> -               LOG(SimplePipeline, Error)
> >> +               LOG(Converter, Error)
> >>                          << "Failed to set format: " << strerror(-ret);
> >>                  return {};
> >>          }
> >> @@ -282,9 +305,12 @@ SizeRange SimpleConverter::sizes(const Size &input)
> >>          return sizes;
> >>   }
> >>   
> >> +/**
> >> + * \copydoc libcamera::Converter::strideAndFrameSize
> >> + */
> >>   std::tuple<unsigned int, unsigned int>
> >> -SimpleConverter::strideAndFrameSize(const PixelFormat &pixelFormat,
> >> -                                   const Size &size)
> >> +V4L2M2MConverter::strideAndFrameSize(const PixelFormat &pixelFormat,
> >> +                                    const Size &size)
> >>   {
> >>          V4L2DeviceFormat format;
> >>          format.fourcc = m2m_->capture()->toV4L2PixelFormat(pixelFormat);
> >> @@ -297,8 +323,11 @@ SimpleConverter::strideAndFrameSize(const PixelFormat &pixelFormat,
> >>          return std::make_tuple(format.planes[0].bpl, format.planes[0].size);
> >>   }
> >>   
> >> -int SimpleConverter::configure(const StreamConfiguration &inputCfg,
> >> -                              const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs)
> >> +/**
> >> + * \copydoc libcamera::Converter::configure
> >> + */
> >> +int V4L2M2MConverter::configure(const StreamConfiguration &inputCfg,
> >> +                               const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs)
> >>   {
> >>          int ret = 0;
> >>   
> >> @@ -309,7 +338,7 @@ int SimpleConverter::configure(const StreamConfiguration &inputCfg,
> >>                  Stream &stream = streams_.emplace_back(this, i);
> >>   
> >>                  if (!stream.isValid()) {
> >> -                       LOG(SimplePipeline, Error)
> >> +                       LOG(Converter, Error)
> >>                                  << "Failed to create stream " << i;
> >>                          ret = -EINVAL;
> >>                          break;
> >> @@ -328,8 +357,11 @@ int SimpleConverter::configure(const StreamConfiguration &inputCfg,
> >>          return 0;
> >>   }
> >>   
> >> -int SimpleConverter::exportBuffers(unsigned int output, unsigned int count,
> >> -                                  std::vector<std::unique_ptr<FrameBuffer>> *buffers)
> >> +/**
> >> + * \copydoc libcamera::Converter::exportBuffers
> >> + */
> >> +int V4L2M2MConverter::exportBuffers(unsigned int output, unsigned int count,
> >> +                                   std::vector<std::unique_ptr<FrameBuffer>> *buffers)
> >>   {
> >>          if (output >= streams_.size())
> >>                  return -EINVAL;
> >> @@ -337,7 +369,10 @@ int SimpleConverter::exportBuffers(unsigned int output, unsigned int count,
> >>          return streams_[output].exportBuffers(count, buffers);
> >>   }
> >>   
> >> -int SimpleConverter::start()
> >> +/**
> >> + * \copydoc libcamera::Converter::start
> >> + */
> >> +int V4L2M2MConverter::start()
> >>   {
> >>          int ret;
> >>   
> >> @@ -352,14 +387,20 @@ int SimpleConverter::start()
> >>          return 0;
> >>   }
> >>   
> >> -void SimpleConverter::stop()
> >> +/**
> >> + * \copydoc libcamera::Converter::stop
> >> + */
> >> +void V4L2M2MConverter::stop()
> >>   {
> >>          for (Stream &stream : utils::reverse(streams_))
> >>                  stream.stop();
> >>   }
> >>   
> >> -int SimpleConverter::queueBuffers(FrameBuffer *input,
> >> -                                 const std::map<unsigned int, FrameBuffer *> &outputs)
> >> +/**
> >> + * \copydoc libcamera::Converter::queueBuffers
> >> + */
> >> +int V4L2M2MConverter::queueBuffers(FrameBuffer *input,
> >> +                                  const std::map<unsigned int, FrameBuffer *> &outputs)
> >>   {
> >>          unsigned int mask = 0;
> >>          int ret;
> >> @@ -402,4 +443,6 @@ int SimpleConverter::queueBuffers(FrameBuffer *input,
> >>          return 0;
> >>   }
> >>   
> >> +REGISTER_CONVERTER("v4l2_m2m", V4L2M2MConverter, "pxp")
> > 
> > I think the only part I'd change here is that the "pxp" might be a
> > statically created array or vector of 'compatible' strings, a bit like
> > the kernel would define.
> > 
> > Then adding support for a new compatible would just be a new line in a
> > table, rather than modifying this registration. It's not much, but it
> > would feel 'neater' to just have a single line patch to add an entry.
> > 
> static std::initializer_list<std::string> aliases = {
>         "pxp"

Can that have a comma at the end? (Or does the compiler complain?)

         "pxp",

It's subtle, but that lets me add the next compatible in a single line.

+	"other-compatible-m2m-device",

> };
> 
> REGISTER_CONVERTER("v4l2_m2m", V4L2M2MConverter, aliases)
> 
> Would it be better ?

Yes, that's what I was thinking of. Although I'd call 'aliases'
'compatibles' ? or 'compatible' ?

> > 
> > And once again, I don't think there are any blockers there. Some things
> > to consider and tidy up for a v4, but I'd be happy to merge at that.
> > 
> > 
> > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > 
> > 
> >> +
> >>   } /* namespace libcamera */
> >> diff --git a/src/libcamera/converter/meson.build b/src/libcamera/converter/meson.build
> >> new file mode 100644
> >> index 00000000..2aa72fe4
> >> --- /dev/null
> >> +++ b/src/libcamera/converter/meson.build
> >> @@ -0,0 +1,5 @@
> >> +# SPDX-License-Identifier: CC0-1.0
> >> +
> >> +libcamera_sources += files([
> >> +        'converter_v4l2_m2m.cpp'
> >> +])
> >> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
> >> index e9d0324e..ffc294f3 100644
> >> --- a/src/libcamera/meson.build
> >> +++ b/src/libcamera/meson.build
> >> @@ -62,6 +62,7 @@ libatomic = cc.find_library('atomic', required : false)
> >>   libthreads = dependency('threads')
> >>   
> >>   subdir('base')
> >> +subdir('converter')
> >>   subdir('ipa')
> >>   subdir('pipeline')
> >>   subdir('proxy')
> >> diff --git a/src/libcamera/pipeline/simple/meson.build b/src/libcamera/pipeline/simple/meson.build
> >> index 9c99b32f..42b0896d 100644
> >> --- a/src/libcamera/pipeline/simple/meson.build
> >> +++ b/src/libcamera/pipeline/simple/meson.build
> >> @@ -1,6 +1,5 @@
> >>   # SPDX-License-Identifier: CC0-1.0
> >>   
> >>   libcamera_sources += files([
> >> -    'converter.cpp',
> >>       'simple.cpp',
> >>   ])
> >> diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
> >> index a32de7f3..24ded4db 100644
> >> --- a/src/libcamera/pipeline/simple/simple.cpp
> >> +++ b/src/libcamera/pipeline/simple/simple.cpp
> >> @@ -30,13 +30,13 @@
> >>   
> >>   #include "libcamera/internal/camera.h"
> >>   #include "libcamera/internal/camera_sensor.h"
> >> +#include "libcamera/internal/converter.h"
> >>   #include "libcamera/internal/device_enumerator.h"
> >>   #include "libcamera/internal/media_device.h"
> >>   #include "libcamera/internal/pipeline_handler.h"
> >>   #include "libcamera/internal/v4l2_subdevice.h"
> >>   #include "libcamera/internal/v4l2_videodevice.h"
> >>   
> >> -#include "converter.h"
> >>   
> >>   namespace libcamera {
> >>   
> >> @@ -266,7 +266,7 @@ public:
> >>          std::vector<Configuration> configs_;
> >>          std::map<PixelFormat, std::vector<const Configuration *>> formats_;
> >>   
> >> -       std::unique_ptr<SimpleConverter> converter_;
> >> +       std::unique_ptr<Converter> converter_;
> >>          std::vector<std::unique_ptr<FrameBuffer>> converterBuffers_;
> >>          bool useConverter_;
> >>          std::queue<std::map<unsigned int, FrameBuffer *>> converterQueue_;
> >> @@ -492,7 +492,7 @@ int SimpleCameraData::init()
> >>          /* Open the converter, if any. */
> >>          MediaDevice *converter = pipe->converter();
> >>          if (converter) {
> >> -               converter_ = std::make_unique<SimpleConverter>(converter);
> >> +               converter_ = ConverterFactoryBase::create(converter);
> >>                  if (!converter_->isValid()) {
> >>                          LOG(SimplePipeline, Warning)
> >>                                  << "Failed to create converter, disabling format conversion";
> >> -- 
> >> 2.38.1
> >>

Patch
diff mbox series

diff --git a/src/libcamera/pipeline/simple/converter.h b/include/libcamera/internal/converter/converter_v4l2_m2m.h
similarity index 83%
rename from src/libcamera/pipeline/simple/converter.h
rename to include/libcamera/internal/converter/converter_v4l2_m2m.h
index f0ebe2e0..ef31eeba 100644
--- a/src/libcamera/pipeline/simple/converter.h
+++ b/include/libcamera/internal/converter/converter_v4l2_m2m.h
@@ -1,8 +1,9 @@ 
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2020, Laurent Pinchart
+ * Copyright 2022 NXP
  *
- * converter.h - Format converter for simple pipeline handler
+ * converter_v4l2_m2m.h - V4l2 M2M Format converter interface
  */
 
 #pragma once
@@ -19,6 +20,8 @@ 
 #include <libcamera/base/log.h>
 #include <libcamera/base/signal.h>
 
+#include "libcamera/internal/converter.h"
+
 namespace libcamera {
 
 class FrameBuffer;
@@ -28,11 +31,12 @@  class SizeRange;
 struct StreamConfiguration;
 class V4L2M2MDevice;
 
-class SimpleConverter
+class V4L2M2MConverter : public Converter
 {
 public:
-	SimpleConverter(MediaDevice *media);
+	V4L2M2MConverter(MediaDevice *media);
 
+	int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; }
 	bool isValid() const { return m2m_ != nullptr; }
 
 	std::vector<PixelFormat> formats(PixelFormat input);
@@ -52,14 +56,11 @@  public:
 	int queueBuffers(FrameBuffer *input,
 			 const std::map<unsigned int, FrameBuffer *> &outputs);
 
-	Signal<FrameBuffer *> inputBufferReady;
-	Signal<FrameBuffer *> outputBufferReady;
-
 private:
 	class Stream : protected Loggable
 	{
 	public:
-		Stream(SimpleConverter *converter, unsigned int index);
+		Stream(V4L2M2MConverter *converter, unsigned int index);
 
 		bool isValid() const { return m2m_ != nullptr; }
 
@@ -80,7 +81,7 @@  private:
 		void captureBufferReady(FrameBuffer *buffer);
 		void outputBufferReady(FrameBuffer *buffer);
 
-		SimpleConverter *converter_;
+		V4L2M2MConverter *converter_;
 		unsigned int index_;
 		std::unique_ptr<V4L2M2MDevice> m2m_;
 
@@ -88,7 +89,6 @@  private:
 		unsigned int outputBufferCount_;
 	};
 
-	std::string deviceNode_;
 	std::unique_ptr<V4L2M2MDevice> m2m_;
 
 	std::vector<Stream> streams_;
diff --git a/include/libcamera/internal/converter/meson.build b/include/libcamera/internal/converter/meson.build
new file mode 100644
index 00000000..891e79e7
--- /dev/null
+++ b/include/libcamera/internal/converter/meson.build
@@ -0,0 +1,5 @@ 
+# SPDX-License-Identifier: CC0-1.0
+
+libcamera_internal_headers += files([
+    'converter_v4l2_m2m.h',
+])
diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
index 8f50d755..b9db5a8c 100644
--- a/include/libcamera/internal/meson.build
+++ b/include/libcamera/internal/meson.build
@@ -45,3 +45,5 @@  libcamera_internal_headers = files([
     'v4l2_videodevice.h',
     'yaml_parser.h',
 ])
+
+subdir('converter')
diff --git a/src/libcamera/pipeline/simple/converter.cpp b/src/libcamera/converter/converter_v4l2_m2m.cpp
similarity index 68%
rename from src/libcamera/pipeline/simple/converter.cpp
rename to src/libcamera/converter/converter_v4l2_m2m.cpp
index acaaa64c..31acb048 100644
--- a/src/libcamera/pipeline/simple/converter.cpp
+++ b/src/libcamera/converter/converter_v4l2_m2m.cpp
@@ -1,12 +1,11 @@ 
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2020, Laurent Pinchart
+ * Copyright 2022 NXP
  *
- * converter.cpp - Format converter for simple pipeline handler
+ * converter_v4l2_m2m.cpp - V4L2 M2M Format converter
  */
 
-#include "converter.h"
-
 #include <algorithm>
 #include <limits.h>
 
@@ -20,19 +19,25 @@ 
 
 #include "libcamera/internal/media_device.h"
 #include "libcamera/internal/v4l2_videodevice.h"
+#include "libcamera/internal/converter/converter_v4l2_m2m.h"
+
+/**
+ * \file internal/converter/converter_v4l2_m2m.h
+ * \brief V4L2 M2M based converter
+ */
 
 namespace libcamera {
 
-LOG_DECLARE_CATEGORY(SimplePipeline)
+LOG_DECLARE_CATEGORY(Converter)
 
 /* -----------------------------------------------------------------------------
- * SimpleConverter::Stream
+ * V4L2M2MConverter::Stream
  */
 
-SimpleConverter::Stream::Stream(SimpleConverter *converter, unsigned int index)
+V4L2M2MConverter::Stream::Stream(V4L2M2MConverter *converter, unsigned int index)
 	: converter_(converter), index_(index)
 {
-	m2m_ = std::make_unique<V4L2M2MDevice>(converter->deviceNode_);
+	m2m_ = std::make_unique<V4L2M2MDevice>(converter->deviceNode());
 
 	m2m_->output()->bufferReady.connect(this, &Stream::outputBufferReady);
 	m2m_->capture()->bufferReady.connect(this, &Stream::captureBufferReady);
@@ -42,8 +47,8 @@  SimpleConverter::Stream::Stream(SimpleConverter *converter, unsigned int index)
 		m2m_.reset();
 }
 
-int SimpleConverter::Stream::configure(const StreamConfiguration &inputCfg,
-				       const StreamConfiguration &outputCfg)
+int V4L2M2MConverter::Stream::configure(const StreamConfiguration &inputCfg,
+					const StreamConfiguration &outputCfg)
 {
 	V4L2PixelFormat videoFormat =
 		m2m_->output()->toV4L2PixelFormat(inputCfg.pixelFormat);
@@ -56,14 +61,14 @@  int SimpleConverter::Stream::configure(const StreamConfiguration &inputCfg,
 
 	int ret = m2m_->output()->setFormat(&format);
 	if (ret < 0) {
-		LOG(SimplePipeline, Error)
+		LOG(Converter, Error)
 			<< "Failed to set input format: " << strerror(-ret);
 		return ret;
 	}
 
 	if (format.fourcc != videoFormat || format.size != inputCfg.size ||
 	    format.planes[0].bpl != inputCfg.stride) {
-		LOG(SimplePipeline, Error)
+		LOG(Converter, Error)
 			<< "Input format not supported (requested "
 			<< inputCfg.size << "-" << videoFormat
 			<< ", got " << format << ")";
@@ -78,13 +83,13 @@  int SimpleConverter::Stream::configure(const StreamConfiguration &inputCfg,
 
 	ret = m2m_->capture()->setFormat(&format);
 	if (ret < 0) {
-		LOG(SimplePipeline, Error)
+		LOG(Converter, Error)
 			<< "Failed to set output format: " << strerror(-ret);
 		return ret;
 	}
 
 	if (format.fourcc != videoFormat || format.size != outputCfg.size) {
-		LOG(SimplePipeline, Error)
+		LOG(Converter, Error)
 			<< "Output format not supported";
 		return -EINVAL;
 	}
@@ -95,13 +100,13 @@  int SimpleConverter::Stream::configure(const StreamConfiguration &inputCfg,
 	return 0;
 }
 
-int SimpleConverter::Stream::exportBuffers(unsigned int count,
-					   std::vector<std::unique_ptr<FrameBuffer>> *buffers)
+int V4L2M2MConverter::Stream::exportBuffers(unsigned int count,
+					    std::vector<std::unique_ptr<FrameBuffer>> *buffers)
 {
 	return m2m_->capture()->exportBuffers(count, buffers);
 }
 
-int SimpleConverter::Stream::start()
+int V4L2M2MConverter::Stream::start()
 {
 	int ret = m2m_->output()->importBuffers(inputBufferCount_);
 	if (ret < 0)
@@ -128,7 +133,7 @@  int SimpleConverter::Stream::start()
 	return 0;
 }
 
-void SimpleConverter::Stream::stop()
+void V4L2M2MConverter::Stream::stop()
 {
 	m2m_->capture()->streamOff();
 	m2m_->output()->streamOff();
@@ -136,8 +141,7 @@  void SimpleConverter::Stream::stop()
 	m2m_->output()->releaseBuffers();
 }
 
-int SimpleConverter::Stream::queueBuffers(FrameBuffer *input,
-					  FrameBuffer *output)
+int V4L2M2MConverter::Stream::queueBuffers(FrameBuffer *input, FrameBuffer *output)
 {
 	int ret = m2m_->output()->queueBuffer(input);
 	if (ret < 0)
@@ -150,12 +154,12 @@  int SimpleConverter::Stream::queueBuffers(FrameBuffer *input,
 	return 0;
 }
 
-std::string SimpleConverter::Stream::logPrefix() const
+std::string V4L2M2MConverter::Stream::logPrefix() const
 {
 	return "stream" + std::to_string(index_);
 }
 
-void SimpleConverter::Stream::outputBufferReady(FrameBuffer *buffer)
+void V4L2M2MConverter::Stream::outputBufferReady(FrameBuffer *buffer)
 {
 	auto it = converter_->queue_.find(buffer);
 	if (it == converter_->queue_.end())
@@ -167,32 +171,34 @@  void SimpleConverter::Stream::outputBufferReady(FrameBuffer *buffer)
 	}
 }
 
-void SimpleConverter::Stream::captureBufferReady(FrameBuffer *buffer)
+void V4L2M2MConverter::Stream::captureBufferReady(FrameBuffer *buffer)
 {
 	converter_->outputBufferReady.emit(buffer);
 }
 
 /* -----------------------------------------------------------------------------
- * SimpleConverter
+ * V4L2M2MConverter
+ */
+
+/**
+ * \class libcamera::V4L2M2MConverter
+ * \brief The V4L2 M2M converter implements the converter interface based on
+ * V4L2 M2M device.
+*/
+
+/**
+ * \fn V4L2M2MConverter::V4L2M2MConverter
+ * \brief Construct a V4L2M2MConverter instance
+ * \param[in] media The media device implementing the converter
  */
 
-SimpleConverter::SimpleConverter(MediaDevice *media)
+V4L2M2MConverter::V4L2M2MConverter(MediaDevice *media)
+	: Converter(media)
 {
-	/*
-	 * Locate the video node. There's no need to validate the pipeline
-	 * further, the caller guarantees that this is a V4L2 mem2mem device.
-	 */
-	const std::vector<MediaEntity *> &entities = media->entities();
-	auto it = std::find_if(entities.begin(), entities.end(),
-			       [](MediaEntity *entity) {
-				       return entity->function() == MEDIA_ENT_F_IO_V4L;
-			       });
-	if (it == entities.end())
+	if (deviceNode().empty())
 		return;
 
-	deviceNode_ = (*it)->deviceNode();
-
-	m2m_ = std::make_unique<V4L2M2MDevice>(deviceNode_);
+	m2m_ = std::make_unique<V4L2M2MDevice>(deviceNode());
 	int ret = m2m_->open();
 	if (ret < 0) {
 		m2m_.reset();
@@ -200,7 +206,21 @@  SimpleConverter::SimpleConverter(MediaDevice *media)
 	}
 }
 
-std::vector<PixelFormat> SimpleConverter::formats(PixelFormat input)
+/**
+ * \fn libcamera::V4L2M2MConverter::loadConfiguration
+ * \details \copydetails libcamera::Converter::loadConfiguration
+ */
+
+/**
+ * \fn libcamera::V4L2M2MConverter::isValid
+ * \details \copydetails libcamera::Converter::isValid
+ */
+
+/**
+ * \fn libcamera::V4L2M2MConverter::formats
+ * \details \copydetails libcamera::Converter::formats
+ */
+std::vector<PixelFormat> V4L2M2MConverter::formats(PixelFormat input)
 {
 	if (!m2m_)
 		return {};
@@ -215,13 +235,13 @@  std::vector<PixelFormat> SimpleConverter::formats(PixelFormat input)
 
 	int ret = m2m_->output()->setFormat(&v4l2Format);
 	if (ret < 0) {
-		LOG(SimplePipeline, Error)
+		LOG(Converter, Error)
 			<< "Failed to set format: " << strerror(-ret);
 		return {};
 	}
 
 	if (v4l2Format.fourcc != m2m_->output()->toV4L2PixelFormat(input)) {
-		LOG(SimplePipeline, Debug)
+		LOG(Converter, Debug)
 			<< "Input format " << input << " not supported.";
 		return {};
 	}
@@ -237,7 +257,10 @@  std::vector<PixelFormat> SimpleConverter::formats(PixelFormat input)
 	return pixelFormats;
 }
 
-SizeRange SimpleConverter::sizes(const Size &input)
+/**
+ * \copydoc libcamera::Converter::sizes
+ */
+SizeRange V4L2M2MConverter::sizes(const Size &input)
 {
 	if (!m2m_)
 		return {};
@@ -252,7 +275,7 @@  SizeRange SimpleConverter::sizes(const Size &input)
 
 	int ret = m2m_->output()->setFormat(&format);
 	if (ret < 0) {
-		LOG(SimplePipeline, Error)
+		LOG(Converter, Error)
 			<< "Failed to set format: " << strerror(-ret);
 		return {};
 	}
@@ -262,7 +285,7 @@  SizeRange SimpleConverter::sizes(const Size &input)
 	format.size = { 1, 1 };
 	ret = m2m_->capture()->setFormat(&format);
 	if (ret < 0) {
-		LOG(SimplePipeline, Error)
+		LOG(Converter, Error)
 			<< "Failed to set format: " << strerror(-ret);
 		return {};
 	}
@@ -272,7 +295,7 @@  SizeRange SimpleConverter::sizes(const Size &input)
 	format.size = { UINT_MAX, UINT_MAX };
 	ret = m2m_->capture()->setFormat(&format);
 	if (ret < 0) {
-		LOG(SimplePipeline, Error)
+		LOG(Converter, Error)
 			<< "Failed to set format: " << strerror(-ret);
 		return {};
 	}
@@ -282,9 +305,12 @@  SizeRange SimpleConverter::sizes(const Size &input)
 	return sizes;
 }
 
+/**
+ * \copydoc libcamera::Converter::strideAndFrameSize
+ */
 std::tuple<unsigned int, unsigned int>
-SimpleConverter::strideAndFrameSize(const PixelFormat &pixelFormat,
-				    const Size &size)
+V4L2M2MConverter::strideAndFrameSize(const PixelFormat &pixelFormat,
+				     const Size &size)
 {
 	V4L2DeviceFormat format;
 	format.fourcc = m2m_->capture()->toV4L2PixelFormat(pixelFormat);
@@ -297,8 +323,11 @@  SimpleConverter::strideAndFrameSize(const PixelFormat &pixelFormat,
 	return std::make_tuple(format.planes[0].bpl, format.planes[0].size);
 }
 
-int SimpleConverter::configure(const StreamConfiguration &inputCfg,
-			       const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs)
+/**
+ * \copydoc libcamera::Converter::configure
+ */
+int V4L2M2MConverter::configure(const StreamConfiguration &inputCfg,
+				const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs)
 {
 	int ret = 0;
 
@@ -309,7 +338,7 @@  int SimpleConverter::configure(const StreamConfiguration &inputCfg,
 		Stream &stream = streams_.emplace_back(this, i);
 
 		if (!stream.isValid()) {
-			LOG(SimplePipeline, Error)
+			LOG(Converter, Error)
 				<< "Failed to create stream " << i;
 			ret = -EINVAL;
 			break;
@@ -328,8 +357,11 @@  int SimpleConverter::configure(const StreamConfiguration &inputCfg,
 	return 0;
 }
 
-int SimpleConverter::exportBuffers(unsigned int output, unsigned int count,
-				   std::vector<std::unique_ptr<FrameBuffer>> *buffers)
+/**
+ * \copydoc libcamera::Converter::exportBuffers
+ */
+int V4L2M2MConverter::exportBuffers(unsigned int output, unsigned int count,
+				    std::vector<std::unique_ptr<FrameBuffer>> *buffers)
 {
 	if (output >= streams_.size())
 		return -EINVAL;
@@ -337,7 +369,10 @@  int SimpleConverter::exportBuffers(unsigned int output, unsigned int count,
 	return streams_[output].exportBuffers(count, buffers);
 }
 
-int SimpleConverter::start()
+/**
+ * \copydoc libcamera::Converter::start
+ */
+int V4L2M2MConverter::start()
 {
 	int ret;
 
@@ -352,14 +387,20 @@  int SimpleConverter::start()
 	return 0;
 }
 
-void SimpleConverter::stop()
+/**
+ * \copydoc libcamera::Converter::stop
+ */
+void V4L2M2MConverter::stop()
 {
 	for (Stream &stream : utils::reverse(streams_))
 		stream.stop();
 }
 
-int SimpleConverter::queueBuffers(FrameBuffer *input,
-				  const std::map<unsigned int, FrameBuffer *> &outputs)
+/**
+ * \copydoc libcamera::Converter::queueBuffers
+ */
+int V4L2M2MConverter::queueBuffers(FrameBuffer *input,
+				   const std::map<unsigned int, FrameBuffer *> &outputs)
 {
 	unsigned int mask = 0;
 	int ret;
@@ -402,4 +443,6 @@  int SimpleConverter::queueBuffers(FrameBuffer *input,
 	return 0;
 }
 
+REGISTER_CONVERTER("v4l2_m2m", V4L2M2MConverter, "pxp")
+
 } /* namespace libcamera */
diff --git a/src/libcamera/converter/meson.build b/src/libcamera/converter/meson.build
new file mode 100644
index 00000000..2aa72fe4
--- /dev/null
+++ b/src/libcamera/converter/meson.build
@@ -0,0 +1,5 @@ 
+# SPDX-License-Identifier: CC0-1.0
+
+libcamera_sources += files([
+        'converter_v4l2_m2m.cpp'
+])
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index e9d0324e..ffc294f3 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -62,6 +62,7 @@  libatomic = cc.find_library('atomic', required : false)
 libthreads = dependency('threads')
 
 subdir('base')
+subdir('converter')
 subdir('ipa')
 subdir('pipeline')
 subdir('proxy')
diff --git a/src/libcamera/pipeline/simple/meson.build b/src/libcamera/pipeline/simple/meson.build
index 9c99b32f..42b0896d 100644
--- a/src/libcamera/pipeline/simple/meson.build
+++ b/src/libcamera/pipeline/simple/meson.build
@@ -1,6 +1,5 @@ 
 # SPDX-License-Identifier: CC0-1.0
 
 libcamera_sources += files([
-    'converter.cpp',
     'simple.cpp',
 ])
diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
index a32de7f3..24ded4db 100644
--- a/src/libcamera/pipeline/simple/simple.cpp
+++ b/src/libcamera/pipeline/simple/simple.cpp
@@ -30,13 +30,13 @@ 
 
 #include "libcamera/internal/camera.h"
 #include "libcamera/internal/camera_sensor.h"
+#include "libcamera/internal/converter.h"
 #include "libcamera/internal/device_enumerator.h"
 #include "libcamera/internal/media_device.h"
 #include "libcamera/internal/pipeline_handler.h"
 #include "libcamera/internal/v4l2_subdevice.h"
 #include "libcamera/internal/v4l2_videodevice.h"
 
-#include "converter.h"
 
 namespace libcamera {
 
@@ -266,7 +266,7 @@  public:
 	std::vector<Configuration> configs_;
 	std::map<PixelFormat, std::vector<const Configuration *>> formats_;
 
-	std::unique_ptr<SimpleConverter> converter_;
+	std::unique_ptr<Converter> converter_;
 	std::vector<std::unique_ptr<FrameBuffer>> converterBuffers_;
 	bool useConverter_;
 	std::queue<std::map<unsigned int, FrameBuffer *>> converterQueue_;
@@ -492,7 +492,7 @@  int SimpleCameraData::init()
 	/* Open the converter, if any. */
 	MediaDevice *converter = pipe->converter();
 	if (converter) {
-		converter_ = std::make_unique<SimpleConverter>(converter);
+		converter_ = ConverterFactoryBase::create(converter);
 		if (!converter_->isValid()) {
 			LOG(SimplePipeline, Warning)
 				<< "Failed to create converter, disabling format conversion";