[{"id":4475,"web_url":"https://patchwork.libcamera.org/comment/4475/","msgid":"<20200421154704.GD2600980@oden.dyn.berto.se>","date":"2020-04-21T15:47:04","subject":"Re: [libcamera-devel] [PATCH v4 09/11] libcamera: pipeline: Add a\n\tsimple pipeline handler","submitter":{"id":5,"url":"https://patchwork.libcamera.org/api/people/5/","name":"Niklas Söderlund","email":"niklas.soderlund@ragnatech.se"},"content":"Hi Laurent,\n\nThanks for your work.\n\nOn 2020-04-04 03:44:36 +0300, Laurent Pinchart wrote:\n> From: Martijn Braam <martijn@brixit.nl>\n> \n> This new pipeline handler aims at supporting any simple device without\n> requiring any device-specific code. Simple devices are currently defined\n> as a graph made of one or multiple camera sensors and a single video\n> node, with each sensor connected to the video node through a linear\n> pipeline.\n> \n> The simple pipeline handler will automatically parse the media graph,\n> enumerate sensors, build supported stream configurations, and configure\n> the pipeline, without any device-specific knowledge. It doesn't support\n> configuration of any processing in the pipeline at the moment, but may\n> be extended to support simple processing such as format conversion or\n> scaling in the future.\n> \n> The only device-specific information in the pipeline handler is the list\n> of supported drivers, required for device matching. We may be able to\n> remove this in the future by matching with the simple pipeline handler\n> as a last resort option, after all other pipeline handlers have been\n> tried.\n> \n> Signed-off-by: Martijn Braam <martijn@brixit.nl>\n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\nReviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>\n\n> ---\n> Changes since v3:\n> \n> - Fix typos\n> - Improve comments\n> - Add local variable to increase readability\n> - Move list of supported drivers to the top of the file\n> \n> Changes since v2:\n> \n> - Log an error when setupFormats() fail\n> - Propagate getFormat() and setFormat() errors to the caller of\n>   setupFormats()\n> - Reorder variable declarations in validate()\n> - Add \\todo comment related to the selection of the default format\n> - Use log Error instead of Info if pipeline isn't valid\n> - Rebase on top of V4L2PixelFormat\n> \n> Changes since v1:\n> \n> - Rebase on top of buffer API rework\n> - Expose stream formats\n> - Rework camera data config\n> ---\n>  meson_options.txt                         |   2 +-\n>  src/libcamera/pipeline/simple/meson.build |   3 +\n>  src/libcamera/pipeline/simple/simple.cpp  | 717 ++++++++++++++++++++++\n>  3 files changed, 721 insertions(+), 1 deletion(-)\n>  create mode 100644 src/libcamera/pipeline/simple/meson.build\n>  create mode 100644 src/libcamera/pipeline/simple/simple.cpp\n> \n> diff --git a/meson_options.txt b/meson_options.txt\n> index 6464df837cc3..166429f8583e 100644\n> --- a/meson_options.txt\n> +++ b/meson_options.txt\n> @@ -14,7 +14,7 @@ option('gstreamer',\n>  \n>  option('pipelines',\n>          type : 'array',\n> -        choices : ['ipu3', 'rkisp1', 'uvcvideo', 'vimc'],\n> +        choices : ['ipu3', 'rkisp1', 'simple', 'uvcvideo', 'vimc'],\n>          description : 'Select which pipeline handlers to include')\n>  \n>  option('test',\n> diff --git a/src/libcamera/pipeline/simple/meson.build b/src/libcamera/pipeline/simple/meson.build\n> new file mode 100644\n> index 000000000000..4945a3e173cf\n> --- /dev/null\n> +++ b/src/libcamera/pipeline/simple/meson.build\n> @@ -0,0 +1,3 @@\n> +libcamera_sources += files([\n> +    'simple.cpp',\n> +])\n> diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp\n> new file mode 100644\n> index 000000000000..e4f33f6ff531\n> --- /dev/null\n> +++ b/src/libcamera/pipeline/simple/simple.cpp\n> @@ -0,0 +1,717 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2020, Laurent Pinchart\n> + * Copyright (C) 2019, Martijn Braam\n> + *\n> + * simple.cpp - Pipeline handler for simple pipelines\n> + */\n> +\n> +#include <algorithm>\n> +#include <iterator>\n> +#include <list>\n> +#include <map>\n> +#include <memory>\n> +#include <set>\n> +#include <string>\n> +#include <string.h>\n> +#include <utility>\n> +#include <vector>\n> +\n> +#include <linux/media-bus-format.h>\n> +\n> +#include <libcamera/camera.h>\n> +#include <libcamera/request.h>\n> +#include <libcamera/stream.h>\n> +\n> +#include \"camera_sensor.h\"\n> +#include \"device_enumerator.h\"\n> +#include \"log.h\"\n> +#include \"media_device.h\"\n> +#include \"pipeline_handler.h\"\n> +#include \"v4l2_subdevice.h\"\n> +#include \"v4l2_videodevice.h\"\n> +\n> +namespace libcamera {\n> +\n> +LOG_DEFINE_CATEGORY(SimplePipeline)\n> +\n> +class SimplePipelineHandler;\n> +\n> +namespace {\n> +\n> +static const char * const drivers[] = {\n> +\t\"imx7-csi\",\n> +\t\"sun6i-csi\",\n> +};\n> +\n> +} /* namespace */\n> +\n> +class SimpleCameraData : public CameraData\n> +{\n> +public:\n> +\tSimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor,\n> +\t\t\t MediaEntity *video);\n> +\n> +\tbool isValid() const { return sensor_ != nullptr; }\n> +\tstd::set<Stream *> streams() { return { &stream_ }; }\n> +\n> +\tint init();\n> +\tint setupLinks();\n> +\tint setupFormats(V4L2SubdeviceFormat *format,\n> +\t\t\t V4L2Subdevice::Whence whence);\n> +\n> +\tstruct Entity {\n> +\t\tMediaEntity *entity;\n> +\t\tMediaLink *link;\n> +\t};\n> +\n> +\tstruct Configuration {\n> +\t\tuint32_t code;\n> +\t\tPixelFormat pixelFormat;\n> +\t\tSize size;\n> +\t};\n> +\n> +\tStream stream_;\n> +\tstd::unique_ptr<CameraSensor> sensor_;\n> +\tstd::list<Entity> entities_;\n> +\n> +\tstd::vector<Configuration> configs_;\n> +\tstd::map<PixelFormat, Configuration> formats_;\n> +};\n> +\n> +class SimpleCameraConfiguration : public CameraConfiguration\n> +{\n> +public:\n> +\tSimpleCameraConfiguration(Camera *camera, SimpleCameraData *data);\n> +\n> +\tStatus validate() override;\n> +\n> +\tconst V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }\n> +\n> +private:\n> +\t/*\n> +\t * The SimpleCameraData instance is guaranteed to be valid as long as\n> +\t * the corresponding Camera instance is valid. In order to borrow a\n> +\t * reference to the camera data, store a new reference to the camera.\n> +\t */\n> +\tstd::shared_ptr<Camera> camera_;\n> +\tconst SimpleCameraData *data_;\n> +\n> +\tV4L2SubdeviceFormat sensorFormat_;\n> +};\n> +\n> +class SimplePipelineHandler : public PipelineHandler\n> +{\n> +public:\n> +\tSimplePipelineHandler(CameraManager *manager);\n> +\t~SimplePipelineHandler();\n> +\n> +\tCameraConfiguration *generateConfiguration(Camera *camera,\n> +\t\t\t\t\t\t   const StreamRoles &roles) override;\n> +\tint configure(Camera *camera, CameraConfiguration *config) override;\n> +\n> +\tint exportFrameBuffers(Camera *camera, Stream *stream,\n> +\t\t\t       std::vector<std::unique_ptr<FrameBuffer>> *buffers) override;\n> +\n> +\tint start(Camera *camera) override;\n> +\tvoid stop(Camera *camera) override;\n> +\n> +\tbool match(DeviceEnumerator *enumerator) override;\n> +\n> +\tV4L2VideoDevice *video() { return video_; }\n> +\tV4L2Subdevice *subdev(const MediaEntity *entity);\n> +\n> +protected:\n> +\tint queueRequestDevice(Camera *camera, Request *request) override;\n> +\n> +private:\n> +\tSimpleCameraData *cameraData(const Camera *camera)\n> +\t{\n> +\t\treturn static_cast<SimpleCameraData *>(\n> +\t\t\tPipelineHandler::cameraData(camera));\n> +\t}\n> +\n> +\tint initLinks();\n> +\n> +\tint createCamera(MediaEntity *sensor);\n> +\n> +\tvoid bufferReady(FrameBuffer *buffer);\n> +\n> +\tMediaDevice *media_;\n> +\tV4L2VideoDevice *video_;\n> +\tstd::map<const MediaEntity *, V4L2Subdevice> subdevs_;\n> +\n> +\tCamera *activeCamera_;\n> +};\n> +\n> +/* -----------------------------------------------------------------------------\n> + * Camera Data\n> + */\n> +\n> +SimpleCameraData::SimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor,\n> +\t\t\t\t   MediaEntity *video)\n> +\t: CameraData(pipe)\n> +{\n> +\tint ret;\n> +\n> +\t/*\n> +\t * Walk the pipeline towards the video node and store all entities\n> +\t * along the way.\n> +\t */\n> +\tMediaEntity *source = sensor;\n> +\n> +\twhile (source) {\n> +\t\t/* If we have reached the video node, we're done. */\n> +\t\tif (source == video)\n> +\t\t\tbreak;\n> +\n> +\t\t/* Use the first output pad that has links. */\n> +\t\tMediaPad *sourcePad = nullptr;\n> +\t\tfor (MediaPad *pad : source->pads()) {\n> +\t\t\tif ((pad->flags() & MEDIA_PAD_FL_SOURCE) &&\n> +\t\t\t    !pad->links().empty()) {\n> +\t\t\t\tsourcePad = pad;\n> +\t\t\t\tbreak;\n> +\t\t\t}\n> +\t\t}\n> +\n> +\t\tif (!sourcePad)\n> +\t\t\treturn;\n> +\n> +\t\t/* Use the first link that isn't immutable and disabled. */\n> +\t\tMediaLink *sourceLink = nullptr;\n> +\t\tfor (MediaLink *link : sourcePad->links()) {\n> +\t\t\tif ((link->flags() & MEDIA_LNK_FL_ENABLED) ||\n> +\t\t\t    !(link->flags() & MEDIA_LNK_FL_IMMUTABLE)) {\n> +\t\t\t\tsourceLink = link;\n> +\t\t\t\tbreak;\n> +\t\t\t}\n> +\t\t}\n> +\n> +\t\tif (!sourceLink)\n> +\t\t\treturn;\n> +\n> +\t\tentities_.push_back({ source, sourceLink });\n> +\n> +\t\tsource = sourceLink->sink()->entity();\n> +\n> +\t\t/* Avoid infinite loops. */\n> +\t\tauto iter = std::find_if(entities_.begin(), entities_.end(),\n> +\t\t\t\t\t [&](const Entity &entity) {\n> +\t\t\t\t\t\t return entity.entity == source;\n> +\t\t\t\t\t });\n> +\t\tif (iter != entities_.end()) {\n> +\t\t\tLOG(SimplePipeline, Info) << \"Loop detected in pipeline\";\n> +\t\t\treturn;\n> +\t\t}\n> +\t}\n> +\n> +\t/* We have a valid pipeline, create the camera sensor. */\n> +\tsensor_ = std::make_unique<CameraSensor>(sensor);\n> +\tret = sensor_->init();\n> +\tif (ret) {\n> +\t\tsensor_.reset();\n> +\t\treturn;\n> +\t}\n> +}\n> +\n> +int SimpleCameraData::init()\n> +{\n> +\tSimplePipelineHandler *pipe = static_cast<SimplePipelineHandler *>(pipe_);\n> +\tV4L2VideoDevice *video = pipe->video();\n> +\tint ret;\n> +\n> +\t/*\n> +\t * Enumerate the possible pipeline configurations. For each media bus\n> +\t * format supported by the sensor, propagate the formats through the\n> +\t * pipeline, and enumerate the corresponding possible V4L2 pixel\n> +\t * formats on the video node.\n> +\t */\n> +\tfor (unsigned int code : sensor_->mbusCodes()) {\n> +\t\tV4L2SubdeviceFormat format{ code, sensor_->resolution() };\n> +\n> +\t\t/*\n> +\t\t * Setup links first as some subdev drivers take active links\n> +\t\t * into account to propagate TRY formats. Such is life :-(\n> +\t\t */\n> +\t\tret = setupLinks();\n> +\t\tif (ret < 0)\n> +\t\t\treturn ret;\n> +\n> +\t\tret = setupFormats(&format, V4L2Subdevice::TryFormat);\n> +\t\tif (ret < 0) {\n> +\t\t\tLOG(SimplePipeline, Error)\n> +\t\t\t\t<< \"Failed to setup pipeline for media bus code \"\n> +\t\t\t\t<< utils::hex(code, 4);\n> +\t\t\treturn ret;\n> +\t\t}\n> +\n> +\t\tstd::map<V4L2PixelFormat, std::vector<SizeRange>> videoFormats =\n> +\t\t\tvideo->formats(format.mbus_code);\n> +\n> +\t\tLOG(SimplePipeline, Debug)\n> +\t\t\t<< \"Adding configuration for \" << format.size.toString()\n> +\t\t\t<< \" in pixel formats [ \"\n> +\t\t\t<< utils::join(videoFormats, \", \",\n> +\t\t\t\t       [](const auto &f) {\n> +\t\t\t\t\t       return f.first.toString();\n> +\t\t\t\t       })\n> +\t\t\t<< \" ]\";\n> +\n> +\t\t/*\n> +\t\t * Store the configuration in the formats_ map, mapping the\n> +\t\t * PixelFormat to the corresponding configuration. Any\n> +\t\t * previously stored value is overwritten, as the pipeline\n> +\t\t * handler currently doesn't care about how a particular\n> +\t\t * PixelFormat is achieved.\n> +\t\t */\n> +\t\tfor (const auto &videoFormat : videoFormats) {\n> +\t\t\tPixelFormat pixelFormat = video->toPixelFormat(videoFormat.first);\n> +\t\t\tif (!pixelFormat)\n> +\t\t\t\tcontinue;\n> +\n> +\t\t\tConfiguration config;\n> +\t\t\tconfig.code = code;\n> +\t\t\tconfig.pixelFormat = pixelFormat;\n> +\t\t\tconfig.size = format.size;\n> +\n> +\t\t\tformats_[pixelFormat] = config;\n> +\t\t}\n> +\t}\n> +\n> +\tif (formats_.empty()) {\n> +\t\tLOG(SimplePipeline, Error) << \"No valid configuration found\";\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +int SimpleCameraData::setupLinks()\n> +{\n> +\tint ret;\n> +\n> +\t/*\n> +\t * Configure all links along the pipeline. Some entities may not allow\n> +\t * multiple sink links to be enabled together, even on different sink\n> +\t * pads. We must thus start by disabling all sink links (but the one we\n> +\t * want to enable) before enabling the pipeline link.\n> +\t */\n> +\tfor (SimpleCameraData::Entity &e : entities_) {\n> +\t\tMediaEntity *remote = e.link->sink()->entity();\n> +\t\tfor (MediaPad *pad : remote->pads()) {\n> +\t\t\tfor (MediaLink *link : pad->links()) {\n> +\t\t\t\tif (link == e.link)\n> +\t\t\t\t\tcontinue;\n> +\n> +\t\t\t\tif ((link->flags() & MEDIA_LNK_FL_ENABLED) &&\n> +\t\t\t\t    !(link->flags() & MEDIA_LNK_FL_IMMUTABLE)) {\n> +\t\t\t\t\tret = link->setEnabled(false);\n> +\t\t\t\t\tif (ret < 0)\n> +\t\t\t\t\t\treturn ret;\n> +\t\t\t\t}\n> +\t\t\t}\n> +\t\t}\n> +\n> +\t\tif (!(e.link->flags() & MEDIA_LNK_FL_ENABLED)) {\n> +\t\t\tret = e.link->setEnabled(true);\n> +\t\t\tif (ret < 0)\n> +\t\t\t\treturn ret;\n> +\t\t}\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +int SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format,\n> +\t\t\t\t   V4L2Subdevice::Whence whence)\n> +{\n> +\tSimplePipelineHandler *pipe = static_cast<SimplePipelineHandler *>(pipe_);\n> +\tint ret;\n> +\n> +\t/*\n> +\t * Configure the format on the sensor output and propagate it through\n> +\t * the pipeline.\n> +\t */\n> +\tret = sensor_->setFormat(format);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tfor (const Entity &e : entities_) {\n> +\t\tMediaLink *link = e.link;\n> +\t\tMediaPad *source = link->source();\n> +\t\tMediaPad *sink = link->sink();\n> +\n> +\t\tif (source->entity() != sensor_->entity()) {\n> +\t\t\tV4L2Subdevice *subdev = pipe->subdev(source->entity());\n> +\t\t\tret = subdev->getFormat(source->index(), format, whence);\n> +\t\t\tif (ret < 0)\n> +\t\t\t\treturn ret;\n> +\t\t}\n> +\n> +\t\tif (sink->entity()->function() != MEDIA_ENT_F_IO_V4L) {\n> +\t\t\tV4L2Subdevice *subdev = pipe->subdev(sink->entity());\n> +\t\t\tret = subdev->setFormat(sink->index(), format, whence);\n> +\t\t\tif (ret < 0)\n> +\t\t\t\treturn ret;\n> +\t\t}\n> +\n> +\t\tLOG(SimplePipeline, Debug)\n> +\t\t\t<< \"Link '\" << source->entity()->name()\n> +\t\t\t<< \"':\" << source->index()\n> +\t\t\t<< \" -> '\" << sink->entity()->name()\n> +\t\t\t<< \"':\" << sink->index()\n> +\t\t\t<< \" configured with format \" << format->toString();\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +/* -----------------------------------------------------------------------------\n> + * Camera Configuration\n> + */\n> +\n> +SimpleCameraConfiguration::SimpleCameraConfiguration(Camera *camera,\n> +\t\t\t\t\t\t     SimpleCameraData *data)\n> +\t: CameraConfiguration(), camera_(camera->shared_from_this()),\n> +\t  data_(data)\n> +{\n> +}\n> +\n> +CameraConfiguration::Status SimpleCameraConfiguration::validate()\n> +{\n> +\tStatus status = Valid;\n> +\n> +\tif (config_.empty())\n> +\t\treturn Invalid;\n> +\n> +\t/* Cap the number of entries to the available streams. */\n> +\tif (config_.size() > 1) {\n> +\t\tconfig_.resize(1);\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\tStreamConfiguration &cfg = config_[0];\n> +\n> +\t/* Adjust the pixel format. */\n> +\tauto it = data_->formats_.find(cfg.pixelFormat);\n> +\tif (it == data_->formats_.end())\n> +\t\tit = data_->formats_.begin();\n> +\n> +\tPixelFormat pixelFormat = it->first;\n> +\tif (cfg.pixelFormat != pixelFormat) {\n> +\t\tLOG(SimplePipeline, Debug) << \"Adjusting pixel format\";\n> +\t\tcfg.pixelFormat = pixelFormat;\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\tconst SimpleCameraData::Configuration &pipeConfig = it->second;\n> +\tif (cfg.size != pipeConfig.size) {\n> +\t\tLOG(SimplePipeline, Debug)\n> +\t\t\t<< \"Adjusting size from \" << cfg.size.toString()\n> +\t\t\t<< \" to \" << pipeConfig.size.toString();\n> +\t\tcfg.size = pipeConfig.size;\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\tcfg.bufferCount = 3;\n> +\n> +\treturn status;\n> +}\n> +\n> +/* -----------------------------------------------------------------------------\n> + * Pipeline Handler\n> + */\n> +\n> +SimplePipelineHandler::SimplePipelineHandler(CameraManager *manager)\n> +\t: PipelineHandler(manager), video_(nullptr)\n> +{\n> +}\n> +\n> +SimplePipelineHandler::~SimplePipelineHandler()\n> +{\n> +\tdelete video_;\n> +}\n> +\n> +CameraConfiguration *SimplePipelineHandler::generateConfiguration(Camera *camera,\n> +\t\t\t\t\t\t\t\t  const StreamRoles &roles)\n> +{\n> +\tSimpleCameraData *data = cameraData(camera);\n> +\tCameraConfiguration *config =\n> +\t\tnew SimpleCameraConfiguration(camera, data);\n> +\n> +\tif (roles.empty())\n> +\t\treturn config;\n> +\n> +\t/* Create the formats map. */\n> +\tstd::map<PixelFormat, std::vector<SizeRange>> formats;\n> +\tstd::transform(data->formats_.begin(), data->formats_.end(),\n> +\t\t       std::inserter(formats, formats.end()),\n> +\t\t       [](const auto &format) -> decltype(formats)::value_type {\n> +\t\t\t       const PixelFormat &pixelFormat = format.first;\n> +\t\t\t       const Size &size = format.second.size;\n> +\t\t\t       return { pixelFormat, { size } };\n> +\t\t       });\n> +\n> +\t/*\n> +\t * Create the stream configuration. Take the first entry in the formats\n> +\t * map as the default, for lack of a better option.\n> +\t *\n> +\t * \\todo Implement a better way to pick the default format\n> +\t */\n> +\tStreamConfiguration cfg{ StreamFormats{ formats } };\n> +\tcfg.pixelFormat = formats.begin()->first;\n> +\tcfg.size = formats.begin()->second[0].max;\n> +\n> +\tconfig->addConfiguration(cfg);\n> +\n> +\tconfig->validate();\n> +\n> +\treturn config;\n> +}\n> +\n> +int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c)\n> +{\n> +\tSimpleCameraConfiguration *config =\n> +\t\tstatic_cast<SimpleCameraConfiguration *>(c);\n> +\tSimpleCameraData *data = cameraData(camera);\n> +\tStreamConfiguration &cfg = config->at(0);\n> +\tint ret;\n> +\n> +\t/*\n> +\t * Configure links on the pipeline and propagate formats from the\n> +\t * sensor to the video node.\n> +\t */\n> +\tret = data->setupLinks();\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tconst SimpleCameraData::Configuration &pipeConfig =\n> +\t\tdata->formats_[cfg.pixelFormat];\n> +\n> +\tV4L2SubdeviceFormat format{ pipeConfig.code, data->sensor_->resolution() };\n> +\n> +\tret = data->setupFormats(&format, V4L2Subdevice::ActiveFormat);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\t/* Configure the video node. */\n> +\tV4L2PixelFormat videoFormat = video_->toV4L2PixelFormat(cfg.pixelFormat);\n> +\n> +\tV4L2DeviceFormat outputFormat = {};\n> +\toutputFormat.fourcc = videoFormat;\n> +\toutputFormat.size = cfg.size;\n> +\n> +\tret = video_->setFormat(&outputFormat);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tif (outputFormat.size != cfg.size || outputFormat.fourcc != videoFormat) {\n> +\t\tLOG(SimplePipeline, Error)\n> +\t\t\t<< \"Unable to configure capture in \" << cfg.toString();\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\tcfg.setStream(&data->stream_);\n> +\n> +\treturn 0;\n> +}\n> +\n> +int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream,\n> +\t\t\t\t\t      std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n> +{\n> +\tunsigned int count = stream->configuration().bufferCount;\n> +\n> +\treturn video_->exportBuffers(count, buffers);\n> +}\n> +\n> +int SimplePipelineHandler::start(Camera *camera)\n> +{\n> +\tSimpleCameraData *data = cameraData(camera);\n> +\tunsigned int count = data->stream_.configuration().bufferCount;\n> +\n> +\tint ret = video_->importBuffers(count);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tret = video_->streamOn();\n> +\tif (ret < 0) {\n> +\t\tvideo_->releaseBuffers();\n> +\t\treturn ret;\n> +\t}\n> +\n> +\tactiveCamera_ = camera;\n> +\n> +\treturn 0;\n> +}\n> +\n> +void SimplePipelineHandler::stop(Camera *camera)\n> +{\n> +\tvideo_->streamOff();\n> +\tvideo_->releaseBuffers();\n> +\tactiveCamera_ = nullptr;\n> +}\n> +\n> +int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request)\n> +{\n> +\tSimpleCameraData *data = cameraData(camera);\n> +\tStream *stream = &data->stream_;\n> +\n> +\tFrameBuffer *buffer = request->findBuffer(stream);\n> +\tif (!buffer) {\n> +\t\tLOG(SimplePipeline, Error)\n> +\t\t\t<< \"Attempt to queue request with invalid stream\";\n> +\t\treturn -ENOENT;\n> +\t}\n> +\n> +\treturn video_->queueBuffer(buffer);\n> +}\n> +\n> +/* -----------------------------------------------------------------------------\n> + * Match and Setup\n> + */\n> +\n> +bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)\n> +{\n> +\tfor (const char *driver : drivers) {\n> +\t\tDeviceMatch dm(driver);\n> +\t\tmedia_ = acquireMediaDevice(enumerator, dm);\n> +\t\tif (media_)\n> +\t\t\tbreak;\n> +\t}\n> +\n> +\tif (!media_)\n> +\t\treturn false;\n> +\n> +\t/*\n> +\t * Locate sensors and video nodes. We only support pipelines with at\n> +\t * least one sensor and exactly one video capture node.\n> +\t */\n> +\tstd::vector<MediaEntity *> sensors;\n> +\tstd::vector<MediaEntity *> videos;\n> +\n> +\tfor (MediaEntity *entity : media_->entities()) {\n> +\t\tswitch (entity->function()) {\n> +\t\tcase MEDIA_ENT_F_CAM_SENSOR:\n> +\t\t\tsensors.push_back(entity);\n> +\t\t\tbreak;\n> +\n> +\t\tcase MEDIA_ENT_F_IO_V4L:\n> +\t\t\tif (entity->pads().size() == 1 &&\n> +\t\t\t    (entity->pads()[0]->flags() & MEDIA_PAD_FL_SINK))\n> +\t\t\t\tvideos.push_back(entity);\n> +\t\t\tbreak;\n> +\n> +\t\tdefault:\n> +\t\t\tbreak;\n> +\t\t}\n> +\t}\n> +\n> +\tif (sensors.empty()) {\n> +\t\tLOG(SimplePipeline, Error) << \"No sensor found\";\n> +\t\treturn false;\n> +\t}\n> +\n> +\tif (videos.size() != 1) {\n> +\t\tLOG(SimplePipeline, Error)\n> +\t\t\t<< \"Pipeline with \" << videos.size()\n> +\t\t\t<< \" video capture nodes is not supported\";\n> +\t\treturn false;\n> +\t}\n> +\n> +\t/* Locate and open the capture video node. */\n> +\tvideo_ = new V4L2VideoDevice(videos[0]);\n> +\tif (video_->open() < 0)\n> +\t\treturn false;\n> +\n> +\tif (video_->caps().isMultiplanar()) {\n> +\t\tLOG(SimplePipeline, Error)\n> +\t\t\t<< \"V4L2 multiplanar devices are not supported\";\n> +\t\treturn false;\n> +\t}\n> +\n> +\tvideo_->bufferReady.connect(this, &SimplePipelineHandler::bufferReady);\n> +\n> +\t/*\n> +\t * Create one camera data instance for each sensor and gather all\n> +\t * entities in all pipelines.\n> +\t */\n> +\tstd::vector<std::unique_ptr<SimpleCameraData>> pipelines;\n> +\tstd::set<MediaEntity *> entities;\n> +\n> +\tpipelines.reserve(sensors.size());\n> +\n> +\tfor (MediaEntity *sensor : sensors) {\n> +\t\tstd::unique_ptr<SimpleCameraData> data =\n> +\t\t\tstd::make_unique<SimpleCameraData>(this, sensor,\n> +\t\t\t\t\t\t\t   videos[0]);\n> +\t\tif (!data->isValid()) {\n> +\t\t\tLOG(SimplePipeline, Error)\n> +\t\t\t\t<< \"No valid pipeline for sensor '\"\n> +\t\t\t\t<< sensor->name() << \"', skipping\";\n> +\t\t\tcontinue;\n> +\t\t}\n> +\n> +\t\tfor (SimpleCameraData::Entity &entity : data->entities_)\n> +\t\t\tentities.insert(entity.entity);\n> +\n> +\t\tpipelines.push_back(std::move(data));\n> +\t}\n> +\n> +\tif (entities.empty())\n> +\t\treturn false;\n> +\n> +\t/* Create and open V4L2Subdev instances for all the entities. */\n> +\tfor (MediaEntity *entity : entities) {\n> +\t\tauto elem = subdevs_.emplace(std::piecewise_construct,\n> +\t\t\t\t\t     std::forward_as_tuple(entity),\n> +\t\t\t\t\t     std::forward_as_tuple(entity));\n> +\t\tV4L2Subdevice *subdev = &elem.first->second;\n> +\t\tint ret = subdev->open();\n> +\t\tif (ret < 0) {\n> +\t\t\tLOG(SimplePipeline, Error)\n> +\t\t\t\t<< \"Failed to open \" << subdev->deviceNode()\n> +\t\t\t\t<< \": \" << strerror(-ret);\n> +\t\t\treturn false;\n> +\t\t}\n> +\t}\n> +\n> +\t/* Initialize each pipeline and register a corresponding camera. */\n> +\tfor (std::unique_ptr<SimpleCameraData> &data : pipelines) {\n> +\t\tint ret = data->init();\n> +\t\tif (ret < 0)\n> +\t\t\tcontinue;\n> +\n> +\t\tstd::shared_ptr<Camera> camera =\n> +\t\t\tCamera::create(this, data->sensor_->entity()->name(),\n> +\t\t\t\t       data->streams());\n> +\t\tregisterCamera(std::move(camera), std::move(data));\n> +\t}\n> +\n> +\treturn true;\n> +}\n> +\n> +V4L2Subdevice *SimplePipelineHandler::subdev(const MediaEntity *entity)\n> +{\n> +\tauto iter = subdevs_.find(entity);\n> +\tif (iter == subdevs_.end())\n> +\t\treturn nullptr;\n> +\n> +\treturn &iter->second;\n> +}\n> +\n> +/* -----------------------------------------------------------------------------\n> + * Buffer Handling\n> + */\n> +\n> +void SimplePipelineHandler::bufferReady(FrameBuffer *buffer)\n> +{\n> +\tASSERT(activeCamera_);\n> +\tRequest *request = buffer->request();\n> +\tcompleteBuffer(activeCamera_, request, buffer);\n> +\tcompleteRequest(activeCamera_, request);\n> +}\n> +\n> +REGISTER_PIPELINE_HANDLER(SimplePipelineHandler);\n> +\n> +} /* namespace libcamera */\n> -- \n> Regards,\n> \n> Laurent Pinchart\n> \n> _______________________________________________\n> libcamera-devel mailing list\n> libcamera-devel@lists.libcamera.org\n> https://lists.libcamera.org/listinfo/libcamera-devel","headers":{"Return-Path":"<niklas.soderlund@ragnatech.se>","Received":["from mail-lj1-x22a.google.com (mail-lj1-x22a.google.com\n\t[IPv6:2a00:1450:4864:20::22a])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 3EC6D60406\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 21 Apr 2020 17:47:08 +0200 (CEST)","by mail-lj1-x22a.google.com with SMTP id y4so14461882ljn.7\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 21 Apr 2020 08:47:08 -0700 (PDT)","from localhost (h-209-203.A463.priv.bahnhof.se. [155.4.209.203])\n\tby smtp.gmail.com with ESMTPSA id\n\tf4sm2428820lfa.24.2020.04.21.08.47.04\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tTue, 21 Apr 2020 08:47:04 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key; \n\tunprotected)\n\theader.d=ragnatech-se.20150623.gappssmtp.com\n\theader.i=@ragnatech-se.20150623.gappssmtp.com header.b=\"cpDAEYl9\"; \n\tdkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=ragnatech-se.20150623.gappssmtp.com; s=20150623;\n\th=date:from:to:cc:subject:message-id:references:mime-version\n\t:content-disposition:content-transfer-encoding:in-reply-to;\n\tbh=Zem0MsDVHOV0rGpCOsvbic9ifWTjAIMVoDw8lSa5ABo=;\n\tb=cpDAEYl9+4XIJgujxIwh/4PifISx6ESG0UzPhzqCwvugimYu4mID+bvDEkKmnjv5ZJ\n\tmCTfUDAh+tbSLxxwSC8ImEHCfLk4pwq59WwpB7CBSfSQipWjDp88yaMxCgxYCT/Dz2pF\n\t4lPR62UwU018XHa0hYB2c67zTXoB1ZBz5QEK6HB55YkRlVGczE+FuegasYJHxX3rDVzB\n\tBsr6lgqHokyoOXDkOEZHHBGKmxo86P81soul/+EXvLs/DODNf98fExDC9f9R2uOHm/0Z\n\tYXldQ6QGe+HykPHtUagyuu8IrvOI24JBUSSb11EqCnR0THKVaReVcuMpo6hNW/vVgCDb\n\tx75A==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20161025;\n\th=x-gm-message-state:date:from:to:cc:subject:message-id:references\n\t:mime-version:content-disposition:content-transfer-encoding\n\t:in-reply-to;\n\tbh=Zem0MsDVHOV0rGpCOsvbic9ifWTjAIMVoDw8lSa5ABo=;\n\tb=gQP9hOdC5x2G96U2G6n8vq2YGLjgFzZBPJNhCeXYBhwE9rkideE73PISiPNQrIu5P/\n\tCGey8CY+9/+BomJZxokYIoKpXPAF43ZX0OjSxZOPVPsLuIco/v+34MIv8MtCmzwunq7k\n\tiqV2Nt4RBBCxhjormMa+NTd1ATRWAcJOEPBypATScZ/FSjpHQSBdbiI9kA8IfHYMA9Gj\n\tXBeZYL4rD3B8FexaOaR4X6BsA1PjXoufDbTnaPeQvN2kvv2zHWZHIZQuFWK0jjl4eATE\n\tRIGZi7ONj6rZrfrQjuT2lfjsC6D+sYkgr2i3A/mTya6ci27RMafnycK2O+tZHH2upLGv\n\tWIyA==","X-Gm-Message-State":"AGi0PuZ4KUS6WZDsH6B34+PsE2PVguefdgQhdYyxhRLknFka3E9iSvxf\n\tQFcYxDzsyGSP9n04wJ2I7Cf2TA==","X-Google-Smtp-Source":"APiQypKUvL2hqy+TfdOqIdayqiBF56ArCM4BropovSrQQoPJfm8zNCg0yEjLzC2VjMXYu7sobxeQYw==","X-Received":"by 2002:a2e:b605:: with SMTP id r5mr13262833ljn.40.1587484027185;\n\tTue, 21 Apr 2020 08:47:07 -0700 (PDT)","Date":"Tue, 21 Apr 2020 17:47:04 +0200","From":"Niklas =?iso-8859-1?q?S=F6derlund?= <niklas.soderlund@ragnatech.se>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org, Martijn Braam <martijn@brixit.nl>, \n\tBenjamin GAIGNARD <benjamin.gaignard@st.com>","Message-ID":"<20200421154704.GD2600980@oden.dyn.berto.se>","References":"<20200404004438.17992-1-laurent.pinchart@ideasonboard.com>\n\t<20200404004438.17992-10-laurent.pinchart@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=iso-8859-1","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<20200404004438.17992-10-laurent.pinchart@ideasonboard.com>","Subject":"Re: [libcamera-devel] [PATCH v4 09/11] libcamera: pipeline: Add a\n\tsimple pipeline handler","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Tue, 21 Apr 2020 15:47:08 -0000"}},{"id":4478,"web_url":"https://patchwork.libcamera.org/comment/4478/","msgid":"<a8cd4e2d-8086-2a50-0a9b-cd4d0fc21529@linaro.org>","date":"2020-04-21T16:35:47","subject":"Re: [libcamera-devel] [PATCH v4 09/11] libcamera: pipeline: Add a\n\tsimple pipeline handler","submitter":{"id":25,"url":"https://patchwork.libcamera.org/api/people/25/","name":"Andrey Konovalov","email":"andrey.konovalov@linaro.org"},"content":"Hi Laurent,\n\nThanks for your work!\n\nOn 21.04.2020 18:47, Niklas Söderlund wrote:\n> Hi Laurent,\n> \n> Thanks for your work.\n> \n> On 2020-04-04 03:44:36 +0300, Laurent Pinchart wrote:\n>> From: Martijn Braam <martijn@brixit.nl>\n>>\n>> This new pipeline handler aims at supporting any simple device without\n>> requiring any device-specific code. Simple devices are currently defined\n>> as a graph made of one or multiple camera sensors and a single video\n>> node, with each sensor connected to the video node through a linear\n>> pipeline.\n>>\n>> The simple pipeline handler will automatically parse the media graph,\n>> enumerate sensors, build supported stream configurations, and configure\n>> the pipeline, without any device-specific knowledge. It doesn't support\n>> configuration of any processing in the pipeline at the moment, but may\n>> be extended to support simple processing such as format conversion or\n>> scaling in the future.\n>>\n>> The only device-specific information in the pipeline handler is the list\n>> of supported drivers, required for device matching. We may be able to\n>> remove this in the future by matching with the simple pipeline handler\n>> as a last resort option, after all other pipeline handlers have been\n>> tried.\n>>\n>> Signed-off-by: Martijn Braam <martijn@brixit.nl>\n>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n>> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n> \n> Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>\n\nReviewed-by: Andrey Konovalov <andrey.konovalov@linaro.org>\n\n>> ---\n>> Changes since v3:\n>>\n>> - Fix typos\n>> - Improve comments\n>> - Add local variable to increase readability\n>> - Move list of supported drivers to the top of the file\n>>\n>> Changes since v2:\n>>\n>> - Log an error when setupFormats() fail\n>> - Propagate getFormat() and setFormat() errors to the caller of\n>>    setupFormats()\n>> - Reorder variable declarations in validate()\n>> - Add \\todo comment related to the selection of the default format\n>> - Use log Error instead of Info if pipeline isn't valid\n>> - Rebase on top of V4L2PixelFormat\n>>\n>> Changes since v1:\n>>\n>> - Rebase on top of buffer API rework\n>> - Expose stream formats\n>> - Rework camera data config\n>> ---\n>>   meson_options.txt                         |   2 +-\n>>   src/libcamera/pipeline/simple/meson.build |   3 +\n>>   src/libcamera/pipeline/simple/simple.cpp  | 717 ++++++++++++++++++++++\n>>   3 files changed, 721 insertions(+), 1 deletion(-)\n>>   create mode 100644 src/libcamera/pipeline/simple/meson.build\n>>   create mode 100644 src/libcamera/pipeline/simple/simple.cpp\n>>\n>> diff --git a/meson_options.txt b/meson_options.txt\n>> index 6464df837cc3..166429f8583e 100644\n>> --- a/meson_options.txt\n>> +++ b/meson_options.txt\n>> @@ -14,7 +14,7 @@ option('gstreamer',\n>>   \n>>   option('pipelines',\n>>           type : 'array',\n>> -        choices : ['ipu3', 'rkisp1', 'uvcvideo', 'vimc'],\n>> +        choices : ['ipu3', 'rkisp1', 'simple', 'uvcvideo', 'vimc'],\n>>           description : 'Select which pipeline handlers to include')\n>>   \n>>   option('test',\n>> diff --git a/src/libcamera/pipeline/simple/meson.build b/src/libcamera/pipeline/simple/meson.build\n>> new file mode 100644\n>> index 000000000000..4945a3e173cf\n>> --- /dev/null\n>> +++ b/src/libcamera/pipeline/simple/meson.build\n>> @@ -0,0 +1,3 @@\n>> +libcamera_sources += files([\n>> +    'simple.cpp',\n>> +])\n>> diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp\n>> new file mode 100644\n>> index 000000000000..e4f33f6ff531\n>> --- /dev/null\n>> +++ b/src/libcamera/pipeline/simple/simple.cpp\n>> @@ -0,0 +1,717 @@\n>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n>> +/*\n>> + * Copyright (C) 2020, Laurent Pinchart\n>> + * Copyright (C) 2019, Martijn Braam\n>> + *\n>> + * simple.cpp - Pipeline handler for simple pipelines\n>> + */\n>> +\n>> +#include <algorithm>\n>> +#include <iterator>\n>> +#include <list>\n>> +#include <map>\n>> +#include <memory>\n>> +#include <set>\n>> +#include <string>\n>> +#include <string.h>\n>> +#include <utility>\n>> +#include <vector>\n>> +\n>> +#include <linux/media-bus-format.h>\n>> +\n>> +#include <libcamera/camera.h>\n>> +#include <libcamera/request.h>\n>> +#include <libcamera/stream.h>\n>> +\n>> +#include \"camera_sensor.h\"\n>> +#include \"device_enumerator.h\"\n>> +#include \"log.h\"\n>> +#include \"media_device.h\"\n>> +#include \"pipeline_handler.h\"\n>> +#include \"v4l2_subdevice.h\"\n>> +#include \"v4l2_videodevice.h\"\n>> +\n>> +namespace libcamera {\n>> +\n>> +LOG_DEFINE_CATEGORY(SimplePipeline)\n>> +\n>> +class SimplePipelineHandler;\n>> +\n>> +namespace {\n>> +\n>> +static const char * const drivers[] = {\n>> +\t\"imx7-csi\",\n>> +\t\"sun6i-csi\",\n>> +};\n>> +\n>> +} /* namespace */\n>> +\n>> +class SimpleCameraData : public CameraData\n>> +{\n>> +public:\n>> +\tSimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor,\n>> +\t\t\t MediaEntity *video);\n>> +\n>> +\tbool isValid() const { return sensor_ != nullptr; }\n>> +\tstd::set<Stream *> streams() { return { &stream_ }; }\n>> +\n>> +\tint init();\n>> +\tint setupLinks();\n>> +\tint setupFormats(V4L2SubdeviceFormat *format,\n>> +\t\t\t V4L2Subdevice::Whence whence);\n>> +\n>> +\tstruct Entity {\n>> +\t\tMediaEntity *entity;\n>> +\t\tMediaLink *link;\n>> +\t};\n>> +\n>> +\tstruct Configuration {\n>> +\t\tuint32_t code;\n>> +\t\tPixelFormat pixelFormat;\n>> +\t\tSize size;\n>> +\t};\n>> +\n>> +\tStream stream_;\n>> +\tstd::unique_ptr<CameraSensor> sensor_;\n>> +\tstd::list<Entity> entities_;\n>> +\n>> +\tstd::vector<Configuration> configs_;\n>> +\tstd::map<PixelFormat, Configuration> formats_;\n>> +};\n>> +\n>> +class SimpleCameraConfiguration : public CameraConfiguration\n>> +{\n>> +public:\n>> +\tSimpleCameraConfiguration(Camera *camera, SimpleCameraData *data);\n>> +\n>> +\tStatus validate() override;\n>> +\n>> +\tconst V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }\n>> +\n>> +private:\n>> +\t/*\n>> +\t * The SimpleCameraData instance is guaranteed to be valid as long as\n>> +\t * the corresponding Camera instance is valid. In order to borrow a\n>> +\t * reference to the camera data, store a new reference to the camera.\n>> +\t */\n>> +\tstd::shared_ptr<Camera> camera_;\n>> +\tconst SimpleCameraData *data_;\n>> +\n>> +\tV4L2SubdeviceFormat sensorFormat_;\n>> +};\n>> +\n>> +class SimplePipelineHandler : public PipelineHandler\n>> +{\n>> +public:\n>> +\tSimplePipelineHandler(CameraManager *manager);\n>> +\t~SimplePipelineHandler();\n>> +\n>> +\tCameraConfiguration *generateConfiguration(Camera *camera,\n>> +\t\t\t\t\t\t   const StreamRoles &roles) override;\n>> +\tint configure(Camera *camera, CameraConfiguration *config) override;\n>> +\n>> +\tint exportFrameBuffers(Camera *camera, Stream *stream,\n>> +\t\t\t       std::vector<std::unique_ptr<FrameBuffer>> *buffers) override;\n>> +\n>> +\tint start(Camera *camera) override;\n>> +\tvoid stop(Camera *camera) override;\n>> +\n>> +\tbool match(DeviceEnumerator *enumerator) override;\n>> +\n>> +\tV4L2VideoDevice *video() { return video_; }\n>> +\tV4L2Subdevice *subdev(const MediaEntity *entity);\n>> +\n>> +protected:\n>> +\tint queueRequestDevice(Camera *camera, Request *request) override;\n>> +\n>> +private:\n>> +\tSimpleCameraData *cameraData(const Camera *camera)\n>> +\t{\n>> +\t\treturn static_cast<SimpleCameraData *>(\n>> +\t\t\tPipelineHandler::cameraData(camera));\n>> +\t}\n>> +\n>> +\tint initLinks();\n>> +\n>> +\tint createCamera(MediaEntity *sensor);\n>> +\n>> +\tvoid bufferReady(FrameBuffer *buffer);\n>> +\n>> +\tMediaDevice *media_;\n>> +\tV4L2VideoDevice *video_;\n>> +\tstd::map<const MediaEntity *, V4L2Subdevice> subdevs_;\n>> +\n>> +\tCamera *activeCamera_;\n>> +};\n>> +\n>> +/* -----------------------------------------------------------------------------\n>> + * Camera Data\n>> + */\n>> +\n>> +SimpleCameraData::SimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor,\n>> +\t\t\t\t   MediaEntity *video)\n>> +\t: CameraData(pipe)\n>> +{\n>> +\tint ret;\n>> +\n>> +\t/*\n>> +\t * Walk the pipeline towards the video node and store all entities\n>> +\t * along the way.\n>> +\t */\n>> +\tMediaEntity *source = sensor;\n>> +\n>> +\twhile (source) {\n>> +\t\t/* If we have reached the video node, we're done. */\n>> +\t\tif (source == video)\n>> +\t\t\tbreak;\n>> +\n>> +\t\t/* Use the first output pad that has links. */\n>> +\t\tMediaPad *sourcePad = nullptr;\n>> +\t\tfor (MediaPad *pad : source->pads()) {\n>> +\t\t\tif ((pad->flags() & MEDIA_PAD_FL_SOURCE) &&\n>> +\t\t\t    !pad->links().empty()) {\n>> +\t\t\t\tsourcePad = pad;\n>> +\t\t\t\tbreak;\n>> +\t\t\t}\n>> +\t\t}\n>> +\n>> +\t\tif (!sourcePad)\n>> +\t\t\treturn;\n>> +\n>> +\t\t/* Use the first link that isn't immutable and disabled. */\n>> +\t\tMediaLink *sourceLink = nullptr;\n>> +\t\tfor (MediaLink *link : sourcePad->links()) {\n>> +\t\t\tif ((link->flags() & MEDIA_LNK_FL_ENABLED) ||\n>> +\t\t\t    !(link->flags() & MEDIA_LNK_FL_IMMUTABLE)) {\n>> +\t\t\t\tsourceLink = link;\n>> +\t\t\t\tbreak;\n>> +\t\t\t}\n>> +\t\t}\n>> +\n>> +\t\tif (!sourceLink)\n>> +\t\t\treturn;\n>> +\n>> +\t\tentities_.push_back({ source, sourceLink });\n>> +\n>> +\t\tsource = sourceLink->sink()->entity();\n>> +\n>> +\t\t/* Avoid infinite loops. */\n>> +\t\tauto iter = std::find_if(entities_.begin(), entities_.end(),\n>> +\t\t\t\t\t [&](const Entity &entity) {\n>> +\t\t\t\t\t\t return entity.entity == source;\n>> +\t\t\t\t\t });\n>> +\t\tif (iter != entities_.end()) {\n>> +\t\t\tLOG(SimplePipeline, Info) << \"Loop detected in pipeline\";\n>> +\t\t\treturn;\n>> +\t\t}\n>> +\t}\n>> +\n>> +\t/* We have a valid pipeline, create the camera sensor. */\n>> +\tsensor_ = std::make_unique<CameraSensor>(sensor);\n>> +\tret = sensor_->init();\n>> +\tif (ret) {\n>> +\t\tsensor_.reset();\n>> +\t\treturn;\n>> +\t}\n>> +}\n>> +\n>> +int SimpleCameraData::init()\n>> +{\n>> +\tSimplePipelineHandler *pipe = static_cast<SimplePipelineHandler *>(pipe_);\n>> +\tV4L2VideoDevice *video = pipe->video();\n>> +\tint ret;\n>> +\n>> +\t/*\n>> +\t * Enumerate the possible pipeline configurations. For each media bus\n>> +\t * format supported by the sensor, propagate the formats through the\n>> +\t * pipeline, and enumerate the corresponding possible V4L2 pixel\n>> +\t * formats on the video node.\n>> +\t */\n>> +\tfor (unsigned int code : sensor_->mbusCodes()) {\n>> +\t\tV4L2SubdeviceFormat format{ code, sensor_->resolution() };\n>> +\n>> +\t\t/*\n>> +\t\t * Setup links first as some subdev drivers take active links\n>> +\t\t * into account to propagate TRY formats. Such is life :-(\n>> +\t\t */\n>> +\t\tret = setupLinks();\n>> +\t\tif (ret < 0)\n>> +\t\t\treturn ret;\n>> +\n>> +\t\tret = setupFormats(&format, V4L2Subdevice::TryFormat);\n>> +\t\tif (ret < 0) {\n>> +\t\t\tLOG(SimplePipeline, Error)\n>> +\t\t\t\t<< \"Failed to setup pipeline for media bus code \"\n>> +\t\t\t\t<< utils::hex(code, 4);\n>> +\t\t\treturn ret;\n>> +\t\t}\n>> +\n>> +\t\tstd::map<V4L2PixelFormat, std::vector<SizeRange>> videoFormats =\n>> +\t\t\tvideo->formats(format.mbus_code);\n>> +\n>> +\t\tLOG(SimplePipeline, Debug)\n>> +\t\t\t<< \"Adding configuration for \" << format.size.toString()\n>> +\t\t\t<< \" in pixel formats [ \"\n>> +\t\t\t<< utils::join(videoFormats, \", \",\n>> +\t\t\t\t       [](const auto &f) {\n>> +\t\t\t\t\t       return f.first.toString();\n>> +\t\t\t\t       })\n>> +\t\t\t<< \" ]\";\n>> +\n>> +\t\t/*\n>> +\t\t * Store the configuration in the formats_ map, mapping the\n>> +\t\t * PixelFormat to the corresponding configuration. Any\n>> +\t\t * previously stored value is overwritten, as the pipeline\n>> +\t\t * handler currently doesn't care about how a particular\n>> +\t\t * PixelFormat is achieved.\n>> +\t\t */\n>> +\t\tfor (const auto &videoFormat : videoFormats) {\n>> +\t\t\tPixelFormat pixelFormat = video->toPixelFormat(videoFormat.first);\n>> +\t\t\tif (!pixelFormat)\n>> +\t\t\t\tcontinue;\n>> +\n>> +\t\t\tConfiguration config;\n>> +\t\t\tconfig.code = code;\n>> +\t\t\tconfig.pixelFormat = pixelFormat;\n>> +\t\t\tconfig.size = format.size;\n>> +\n>> +\t\t\tformats_[pixelFormat] = config;\n>> +\t\t}\n>> +\t}\n>> +\n>> +\tif (formats_.empty()) {\n>> +\t\tLOG(SimplePipeline, Error) << \"No valid configuration found\";\n>> +\t\treturn -EINVAL;\n>> +\t}\n>> +\n>> +\treturn 0;\n>> +}\n>> +\n>> +int SimpleCameraData::setupLinks()\n>> +{\n>> +\tint ret;\n>> +\n>> +\t/*\n>> +\t * Configure all links along the pipeline. Some entities may not allow\n>> +\t * multiple sink links to be enabled together, even on different sink\n>> +\t * pads. We must thus start by disabling all sink links (but the one we\n>> +\t * want to enable) before enabling the pipeline link.\n>> +\t */\n>> +\tfor (SimpleCameraData::Entity &e : entities_) {\n>> +\t\tMediaEntity *remote = e.link->sink()->entity();\n>> +\t\tfor (MediaPad *pad : remote->pads()) {\n>> +\t\t\tfor (MediaLink *link : pad->links()) {\n>> +\t\t\t\tif (link == e.link)\n>> +\t\t\t\t\tcontinue;\n>> +\n>> +\t\t\t\tif ((link->flags() & MEDIA_LNK_FL_ENABLED) &&\n>> +\t\t\t\t    !(link->flags() & MEDIA_LNK_FL_IMMUTABLE)) {\n>> +\t\t\t\t\tret = link->setEnabled(false);\n>> +\t\t\t\t\tif (ret < 0)\n>> +\t\t\t\t\t\treturn ret;\n>> +\t\t\t\t}\n>> +\t\t\t}\n>> +\t\t}\n>> +\n>> +\t\tif (!(e.link->flags() & MEDIA_LNK_FL_ENABLED)) {\n>> +\t\t\tret = e.link->setEnabled(true);\n>> +\t\t\tif (ret < 0)\n>> +\t\t\t\treturn ret;\n>> +\t\t}\n>> +\t}\n>> +\n>> +\treturn 0;\n>> +}\n>> +\n>> +int SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format,\n>> +\t\t\t\t   V4L2Subdevice::Whence whence)\n>> +{\n>> +\tSimplePipelineHandler *pipe = static_cast<SimplePipelineHandler *>(pipe_);\n>> +\tint ret;\n>> +\n>> +\t/*\n>> +\t * Configure the format on the sensor output and propagate it through\n>> +\t * the pipeline.\n>> +\t */\n>> +\tret = sensor_->setFormat(format);\n>> +\tif (ret < 0)\n>> +\t\treturn ret;\n>> +\n>> +\tfor (const Entity &e : entities_) {\n>> +\t\tMediaLink *link = e.link;\n>> +\t\tMediaPad *source = link->source();\n>> +\t\tMediaPad *sink = link->sink();\n>> +\n>> +\t\tif (source->entity() != sensor_->entity()) {\n>> +\t\t\tV4L2Subdevice *subdev = pipe->subdev(source->entity());\n>> +\t\t\tret = subdev->getFormat(source->index(), format, whence);\n>> +\t\t\tif (ret < 0)\n>> +\t\t\t\treturn ret;\n>> +\t\t}\n>> +\n>> +\t\tif (sink->entity()->function() != MEDIA_ENT_F_IO_V4L) {\n>> +\t\t\tV4L2Subdevice *subdev = pipe->subdev(sink->entity());\n>> +\t\t\tret = subdev->setFormat(sink->index(), format, whence);\n>> +\t\t\tif (ret < 0)\n>> +\t\t\t\treturn ret;\n>> +\t\t}\n>> +\n>> +\t\tLOG(SimplePipeline, Debug)\n>> +\t\t\t<< \"Link '\" << source->entity()->name()\n>> +\t\t\t<< \"':\" << source->index()\n>> +\t\t\t<< \" -> '\" << sink->entity()->name()\n>> +\t\t\t<< \"':\" << sink->index()\n>> +\t\t\t<< \" configured with format \" << format->toString();\n>> +\t}\n>> +\n>> +\treturn 0;\n>> +}\n>> +\n>> +/* -----------------------------------------------------------------------------\n>> + * Camera Configuration\n>> + */\n>> +\n>> +SimpleCameraConfiguration::SimpleCameraConfiguration(Camera *camera,\n>> +\t\t\t\t\t\t     SimpleCameraData *data)\n>> +\t: CameraConfiguration(), camera_(camera->shared_from_this()),\n>> +\t  data_(data)\n>> +{\n>> +}\n>> +\n>> +CameraConfiguration::Status SimpleCameraConfiguration::validate()\n>> +{\n>> +\tStatus status = Valid;\n>> +\n>> +\tif (config_.empty())\n>> +\t\treturn Invalid;\n>> +\n>> +\t/* Cap the number of entries to the available streams. */\n>> +\tif (config_.size() > 1) {\n>> +\t\tconfig_.resize(1);\n>> +\t\tstatus = Adjusted;\n>> +\t}\n>> +\n>> +\tStreamConfiguration &cfg = config_[0];\n>> +\n>> +\t/* Adjust the pixel format. */\n>> +\tauto it = data_->formats_.find(cfg.pixelFormat);\n>> +\tif (it == data_->formats_.end())\n>> +\t\tit = data_->formats_.begin();\n>> +\n>> +\tPixelFormat pixelFormat = it->first;\n>> +\tif (cfg.pixelFormat != pixelFormat) {\n>> +\t\tLOG(SimplePipeline, Debug) << \"Adjusting pixel format\";\n>> +\t\tcfg.pixelFormat = pixelFormat;\n>> +\t\tstatus = Adjusted;\n>> +\t}\n>> +\n>> +\tconst SimpleCameraData::Configuration &pipeConfig = it->second;\n>> +\tif (cfg.size != pipeConfig.size) {\n>> +\t\tLOG(SimplePipeline, Debug)\n>> +\t\t\t<< \"Adjusting size from \" << cfg.size.toString()\n>> +\t\t\t<< \" to \" << pipeConfig.size.toString();\n>> +\t\tcfg.size = pipeConfig.size;\n>> +\t\tstatus = Adjusted;\n>> +\t}\n>> +\n>> +\tcfg.bufferCount = 3;\n>> +\n>> +\treturn status;\n>> +}\n>> +\n>> +/* -----------------------------------------------------------------------------\n>> + * Pipeline Handler\n>> + */\n>> +\n>> +SimplePipelineHandler::SimplePipelineHandler(CameraManager *manager)\n>> +\t: PipelineHandler(manager), video_(nullptr)\n>> +{\n>> +}\n>> +\n>> +SimplePipelineHandler::~SimplePipelineHandler()\n>> +{\n>> +\tdelete video_;\n>> +}\n>> +\n>> +CameraConfiguration *SimplePipelineHandler::generateConfiguration(Camera *camera,\n>> +\t\t\t\t\t\t\t\t  const StreamRoles &roles)\n>> +{\n>> +\tSimpleCameraData *data = cameraData(camera);\n>> +\tCameraConfiguration *config =\n>> +\t\tnew SimpleCameraConfiguration(camera, data);\n>> +\n>> +\tif (roles.empty())\n>> +\t\treturn config;\n>> +\n>> +\t/* Create the formats map. */\n>> +\tstd::map<PixelFormat, std::vector<SizeRange>> formats;\n>> +\tstd::transform(data->formats_.begin(), data->formats_.end(),\n>> +\t\t       std::inserter(formats, formats.end()),\n>> +\t\t       [](const auto &format) -> decltype(formats)::value_type {\n>> +\t\t\t       const PixelFormat &pixelFormat = format.first;\n>> +\t\t\t       const Size &size = format.second.size;\n>> +\t\t\t       return { pixelFormat, { size } };\n>> +\t\t       });\n>> +\n>> +\t/*\n>> +\t * Create the stream configuration. Take the first entry in the formats\n>> +\t * map as the default, for lack of a better option.\n>> +\t *\n>> +\t * \\todo Implement a better way to pick the default format\n>> +\t */\n>> +\tStreamConfiguration cfg{ StreamFormats{ formats } };\n>> +\tcfg.pixelFormat = formats.begin()->first;\n>> +\tcfg.size = formats.begin()->second[0].max;\n>> +\n>> +\tconfig->addConfiguration(cfg);\n>> +\n>> +\tconfig->validate();\n>> +\n>> +\treturn config;\n>> +}\n>> +\n>> +int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c)\n>> +{\n>> +\tSimpleCameraConfiguration *config =\n>> +\t\tstatic_cast<SimpleCameraConfiguration *>(c);\n>> +\tSimpleCameraData *data = cameraData(camera);\n>> +\tStreamConfiguration &cfg = config->at(0);\n>> +\tint ret;\n>> +\n>> +\t/*\n>> +\t * Configure links on the pipeline and propagate formats from the\n>> +\t * sensor to the video node.\n>> +\t */\n>> +\tret = data->setupLinks();\n>> +\tif (ret < 0)\n>> +\t\treturn ret;\n>> +\n>> +\tconst SimpleCameraData::Configuration &pipeConfig =\n>> +\t\tdata->formats_[cfg.pixelFormat];\n>> +\n>> +\tV4L2SubdeviceFormat format{ pipeConfig.code, data->sensor_->resolution() };\n>> +\n>> +\tret = data->setupFormats(&format, V4L2Subdevice::ActiveFormat);\n>> +\tif (ret < 0)\n>> +\t\treturn ret;\n>> +\n>> +\t/* Configure the video node. */\n>> +\tV4L2PixelFormat videoFormat = video_->toV4L2PixelFormat(cfg.pixelFormat);\n>> +\n>> +\tV4L2DeviceFormat outputFormat = {};\n>> +\toutputFormat.fourcc = videoFormat;\n>> +\toutputFormat.size = cfg.size;\n>> +\n>> +\tret = video_->setFormat(&outputFormat);\n>> +\tif (ret)\n>> +\t\treturn ret;\n>> +\n>> +\tif (outputFormat.size != cfg.size || outputFormat.fourcc != videoFormat) {\n>> +\t\tLOG(SimplePipeline, Error)\n>> +\t\t\t<< \"Unable to configure capture in \" << cfg.toString();\n>> +\t\treturn -EINVAL;\n>> +\t}\n>> +\n>> +\tcfg.setStream(&data->stream_);\n>> +\n>> +\treturn 0;\n>> +}\n>> +\n>> +int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream,\n>> +\t\t\t\t\t      std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n>> +{\n>> +\tunsigned int count = stream->configuration().bufferCount;\n>> +\n>> +\treturn video_->exportBuffers(count, buffers);\n>> +}\n>> +\n>> +int SimplePipelineHandler::start(Camera *camera)\n>> +{\n>> +\tSimpleCameraData *data = cameraData(camera);\n>> +\tunsigned int count = data->stream_.configuration().bufferCount;\n>> +\n>> +\tint ret = video_->importBuffers(count);\n>> +\tif (ret < 0)\n>> +\t\treturn ret;\n>> +\n>> +\tret = video_->streamOn();\n>> +\tif (ret < 0) {\n>> +\t\tvideo_->releaseBuffers();\n>> +\t\treturn ret;\n>> +\t}\n>> +\n>> +\tactiveCamera_ = camera;\n>> +\n>> +\treturn 0;\n>> +}\n>> +\n>> +void SimplePipelineHandler::stop(Camera *camera)\n>> +{\n>> +\tvideo_->streamOff();\n>> +\tvideo_->releaseBuffers();\n>> +\tactiveCamera_ = nullptr;\n>> +}\n>> +\n>> +int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request)\n>> +{\n>> +\tSimpleCameraData *data = cameraData(camera);\n>> +\tStream *stream = &data->stream_;\n>> +\n>> +\tFrameBuffer *buffer = request->findBuffer(stream);\n>> +\tif (!buffer) {\n>> +\t\tLOG(SimplePipeline, Error)\n>> +\t\t\t<< \"Attempt to queue request with invalid stream\";\n>> +\t\treturn -ENOENT;\n>> +\t}\n>> +\n>> +\treturn video_->queueBuffer(buffer);\n>> +}\n>> +\n>> +/* -----------------------------------------------------------------------------\n>> + * Match and Setup\n>> + */\n>> +\n>> +bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)\n>> +{\n>> +\tfor (const char *driver : drivers) {\n>> +\t\tDeviceMatch dm(driver);\n>> +\t\tmedia_ = acquireMediaDevice(enumerator, dm);\n>> +\t\tif (media_)\n>> +\t\t\tbreak;\n>> +\t}\n>> +\n>> +\tif (!media_)\n>> +\t\treturn false;\n>> +\n>> +\t/*\n>> +\t * Locate sensors and video nodes. We only support pipelines with at\n>> +\t * least one sensor and exactly one video capture node.\n>> +\t */\n>> +\tstd::vector<MediaEntity *> sensors;\n>> +\tstd::vector<MediaEntity *> videos;\n>> +\n>> +\tfor (MediaEntity *entity : media_->entities()) {\n>> +\t\tswitch (entity->function()) {\n>> +\t\tcase MEDIA_ENT_F_CAM_SENSOR:\n>> +\t\t\tsensors.push_back(entity);\n>> +\t\t\tbreak;\n>> +\n>> +\t\tcase MEDIA_ENT_F_IO_V4L:\n>> +\t\t\tif (entity->pads().size() == 1 &&\n>> +\t\t\t    (entity->pads()[0]->flags() & MEDIA_PAD_FL_SINK))\n>> +\t\t\t\tvideos.push_back(entity);\n>> +\t\t\tbreak;\n>> +\n>> +\t\tdefault:\n>> +\t\t\tbreak;\n>> +\t\t}\n>> +\t}\n>> +\n>> +\tif (sensors.empty()) {\n>> +\t\tLOG(SimplePipeline, Error) << \"No sensor found\";\n>> +\t\treturn false;\n>> +\t}\n>> +\n>> +\tif (videos.size() != 1) {\n>> +\t\tLOG(SimplePipeline, Error)\n>> +\t\t\t<< \"Pipeline with \" << videos.size()\n>> +\t\t\t<< \" video capture nodes is not supported\";\n>> +\t\treturn false;\n>> +\t}\n>> +\n>> +\t/* Locate and open the capture video node. */\n>> +\tvideo_ = new V4L2VideoDevice(videos[0]);\n>> +\tif (video_->open() < 0)\n>> +\t\treturn false;\n>> +\n>> +\tif (video_->caps().isMultiplanar()) {\n>> +\t\tLOG(SimplePipeline, Error)\n>> +\t\t\t<< \"V4L2 multiplanar devices are not supported\";\n>> +\t\treturn false;\n>> +\t}\n>> +\n>> +\tvideo_->bufferReady.connect(this, &SimplePipelineHandler::bufferReady);\n>> +\n>> +\t/*\n>> +\t * Create one camera data instance for each sensor and gather all\n>> +\t * entities in all pipelines.\n>> +\t */\n>> +\tstd::vector<std::unique_ptr<SimpleCameraData>> pipelines;\n>> +\tstd::set<MediaEntity *> entities;\n>> +\n>> +\tpipelines.reserve(sensors.size());\n>> +\n>> +\tfor (MediaEntity *sensor : sensors) {\n>> +\t\tstd::unique_ptr<SimpleCameraData> data =\n>> +\t\t\tstd::make_unique<SimpleCameraData>(this, sensor,\n>> +\t\t\t\t\t\t\t   videos[0]);\n>> +\t\tif (!data->isValid()) {\n>> +\t\t\tLOG(SimplePipeline, Error)\n>> +\t\t\t\t<< \"No valid pipeline for sensor '\"\n>> +\t\t\t\t<< sensor->name() << \"', skipping\";\n>> +\t\t\tcontinue;\n>> +\t\t}\n>> +\n>> +\t\tfor (SimpleCameraData::Entity &entity : data->entities_)\n>> +\t\t\tentities.insert(entity.entity);\n>> +\n>> +\t\tpipelines.push_back(std::move(data));\n>> +\t}\n>> +\n>> +\tif (entities.empty())\n>> +\t\treturn false;\n>> +\n>> +\t/* Create and open V4L2Subdev instances for all the entities. */\n>> +\tfor (MediaEntity *entity : entities) {\n>> +\t\tauto elem = subdevs_.emplace(std::piecewise_construct,\n>> +\t\t\t\t\t     std::forward_as_tuple(entity),\n>> +\t\t\t\t\t     std::forward_as_tuple(entity));\n>> +\t\tV4L2Subdevice *subdev = &elem.first->second;\n>> +\t\tint ret = subdev->open();\n>> +\t\tif (ret < 0) {\n>> +\t\t\tLOG(SimplePipeline, Error)\n>> +\t\t\t\t<< \"Failed to open \" << subdev->deviceNode()\n>> +\t\t\t\t<< \": \" << strerror(-ret);\n>> +\t\t\treturn false;\n>> +\t\t}\n>> +\t}\n>> +\n>> +\t/* Initialize each pipeline and register a corresponding camera. */\n>> +\tfor (std::unique_ptr<SimpleCameraData> &data : pipelines) {\n>> +\t\tint ret = data->init();\n>> +\t\tif (ret < 0)\n>> +\t\t\tcontinue;\n>> +\n>> +\t\tstd::shared_ptr<Camera> camera =\n>> +\t\t\tCamera::create(this, data->sensor_->entity()->name(),\n>> +\t\t\t\t       data->streams());\n>> +\t\tregisterCamera(std::move(camera), std::move(data));\n>> +\t}\n>> +\n>> +\treturn true;\n>> +}\n>> +\n>> +V4L2Subdevice *SimplePipelineHandler::subdev(const MediaEntity *entity)\n>> +{\n>> +\tauto iter = subdevs_.find(entity);\n>> +\tif (iter == subdevs_.end())\n>> +\t\treturn nullptr;\n>> +\n>> +\treturn &iter->second;\n>> +}\n>> +\n>> +/* -----------------------------------------------------------------------------\n>> + * Buffer Handling\n>> + */\n>> +\n>> +void SimplePipelineHandler::bufferReady(FrameBuffer *buffer)\n>> +{\n>> +\tASSERT(activeCamera_);\n>> +\tRequest *request = buffer->request();\n>> +\tcompleteBuffer(activeCamera_, request, buffer);\n>> +\tcompleteRequest(activeCamera_, request);\n>> +}\n>> +\n>> +REGISTER_PIPELINE_HANDLER(SimplePipelineHandler);\n>> +\n>> +} /* namespace libcamera */\n>> -- \n>> Regards,\n>>\n>> Laurent Pinchart\n>>\n>> _______________________________________________\n>> libcamera-devel mailing list\n>> libcamera-devel@lists.libcamera.org\n>> https://lists.libcamera.org/listinfo/libcamera-devel\n>","headers":{"Return-Path":"<andrey.konovalov@linaro.org>","Received":["from mail-lj1-x229.google.com (mail-lj1-x229.google.com\n\t[IPv6:2a00:1450:4864:20::229])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id A116E60406\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 21 Apr 2020 18:35:53 +0200 (CEST)","by mail-lj1-x229.google.com with SMTP id y4so14636833ljn.7\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 21 Apr 2020 09:35:53 -0700 (PDT)","from [192.168.118.216] (37-144-159-139.broadband.corbina.ru.\n\t[37.144.159.139]) by smtp.gmail.com with ESMTPSA id\n\tu13sm2449492lji.27.2020.04.21.09.35.51\n\t(version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128);\n\tTue, 21 Apr 2020 09:35:51 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key; \n\tunprotected) header.d=linaro.org\n\theader.i=@linaro.org header.b=\"dyaYJCvw\"; \n\tdkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google;\n\th=subject:to:cc:references:from:message-id:date:user-agent\n\t:mime-version:in-reply-to:content-language:content-transfer-encoding; \n\tbh=clt0eHPgP7EyTSEG8nX3DiGciIIA61kcnGbiV7lwy2c=;\n\tb=dyaYJCvwKovNeUXvNx8NogZsyITO9FXmEjkO1DrlgZoVSJhnhY8K1d0qvFpWqWWUEm\n\tW32foXSKJO4nMZdXnyeVL2tmWMLyMR8LCDMq8qRcMxBpUz4wEK3Xbqa36nrQkI4SqHFx\n\tfyw0yzUxLllfDO5aURTuomuf6ZBJpMSykEVpSzD82ydqfGT+mVlHT+LhxcXrih7WXREy\n\tU1v+VNTmI6Eke6eNSP5Fn5sygrxnqc87PkenT0rkCjNzlWXl87Wb0uj4apxgSFnJRzAF\n\t/jjK/t7jd47pSIYbywDj0QPNYwzGikT8B0Itsa2JOBd92iJiVz0ghzZUc+yWIwtSf0Cl\n\ttnuA==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20161025;\n\th=x-gm-message-state:subject:to:cc:references:from:message-id:date\n\t:user-agent:mime-version:in-reply-to:content-language\n\t:content-transfer-encoding;\n\tbh=clt0eHPgP7EyTSEG8nX3DiGciIIA61kcnGbiV7lwy2c=;\n\tb=YRO1w6pb7SPPn9D1wtIs/VtJP0GHen7QdVB27UqOpNqZ9CrL2KdNXz/APTehMx+MKp\n\tWqOx3ZKFRtkFETDBlFnyFIWpb73XrYZIWXtqp77/xUVgJvXytpRN/Gqlig5rCVlk+FkP\n\tkfH5BV7/PULLjWOSidQmhTRCCsRYBy39pHys8ylx9GZef6tBSi6em4MFGe//CZu+P+tx\n\tXbOQAX7eWKqwYNmuY2Ur89zxXE84LX8gMCmlsshm5CudOKmYBfyKJcq3GVy91uaARm4n\n\tP+O0hb76KkuuthA3oh0AW0KBninAmkiG7QWbUvMXf1ROoQqqcKrFzeMcGY8atslEgInl\n\tleKg==","X-Gm-Message-State":"AGi0Puau0SIinD0jFKN0Ij79hyHU2eWvLbO9uYBuMjY2w4obDg6tNKez\n\t+2csSYmuMnA/Mf2fqNkhMeJtG1mDAhg=","X-Google-Smtp-Source":"APiQypLsKJrzZkeZ9URn0PAyOJi5acA7SKSMdVnvCW8vQftcrehJpho0bkt4Zrmm+XD8MvQeGdNHNw==","X-Received":"by 2002:a2e:b8c7:: with SMTP id\n\ts7mr14060655ljp.212.1587486952671; \n\tTue, 21 Apr 2020 09:35:52 -0700 (PDT)","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"Martijn Braam <martijn@brixit.nl>, libcamera-devel@lists.libcamera.org, \n\tBenjamin GAIGNARD <benjamin.gaignard@st.com>","References":"<20200404004438.17992-1-laurent.pinchart@ideasonboard.com>\n\t<20200404004438.17992-10-laurent.pinchart@ideasonboard.com>\n\t<20200421154704.GD2600980@oden.dyn.berto.se>","From":"Andrey Konovalov <andrey.konovalov@linaro.org>","Message-ID":"<a8cd4e2d-8086-2a50-0a9b-cd4d0fc21529@linaro.org>","Date":"Tue, 21 Apr 2020 19:35:47 +0300","User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101\n\tThunderbird/68.7.0","MIME-Version":"1.0","In-Reply-To":"<20200421154704.GD2600980@oden.dyn.berto.se>","Content-Type":"text/plain; charset=utf-8; format=flowed","Content-Language":"en-US","Content-Transfer-Encoding":"8bit","Subject":"Re: [libcamera-devel] [PATCH v4 09/11] libcamera: pipeline: Add a\n\tsimple pipeline handler","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Tue, 21 Apr 2020 16:35:53 -0000"}}]