[{"id":4427,"web_url":"https://patchwork.libcamera.org/comment/4427/","msgid":"<696d3a07-fcdd-de9d-8723-70705a219111@linaro.org>","date":"2020-04-08T17:06:57","subject":"Re: [libcamera-devel] [PATCH v4 12/11] libcamera: pipeline: simple:\n\tSupport multiple capture video nodes","submitter":{"id":25,"url":"https://patchwork.libcamera.org/api/people/25/","name":"Andrey Konovalov","email":"andrey.konovalov@linaro.org"},"content":"Hi Laurent,\n\nThank you for the patch!\n\nOn 04.04.2020 04:16, Laurent Pinchart wrote:\n> The simple pipeline handler rejects devices that have multiple capture\n> video nodes. There's no real reason to do so, a more dynamic approach is\n> possible as the pipeline handler already locates the video device by\n> walking the media graph.\n> \n> Rework the match sequence by skipping any check on the video nodes, and\n> create the V4L2VideoDevice for the media entity at the end of the\n> pipeline when initializing the camera data. The V4L2VideoDevice\n> instances are managed by the pipeline handler itself, to avoid creating\n> separate instances in the camera data if multiple sensors are routed to\n> the same video device.\n> \n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> ---\n>   src/libcamera/pipeline/simple/simple.cpp | 136 +++++++++++++----------\n>   1 file changed, 75 insertions(+), 61 deletions(-)\n> \n> Andrey, does this fix your issue with the qcom-camss ?\n\nYes, it does. Thanks!\n\nReviewed-by: Andrey Konovalov <andrey.konovalov@linaro.org>\n\n> diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp\n> index 8e7dd091b4ab..b5f9177dd383 100644\n> --- a/src/libcamera/pipeline/simple/simple.cpp\n> +++ b/src/libcamera/pipeline/simple/simple.cpp\n> @@ -57,8 +57,7 @@ static const SimplePipelineInfo supportedDevices[] = {\n>   class SimpleCameraData : public CameraData\n>   {\n>   public:\n> -\tSimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor,\n> -\t\t\t MediaEntity *video);\n> +\tSimpleCameraData(SimplePipelineHandler *pipe, MediaEntity *sensor);\n>   \n>   \tbool isValid() const { return sensor_ != nullptr; }\n>   \tstd::set<Stream *> streams() { return { &stream_ }; }\n> @@ -82,6 +81,7 @@ public:\n>   \tStream stream_;\n>   \tstd::unique_ptr<CameraSensor> sensor_;\n>   \tstd::list<Entity> entities_;\n> +\tV4L2VideoDevice *video_;\n>   \n>   \tstd::vector<Configuration> configs_;\n>   \tstd::map<PixelFormat, Configuration> formats_;\n> @@ -129,7 +129,7 @@ public:\n>   \n>   \tbool match(DeviceEnumerator *enumerator) override;\n>   \n> -\tV4L2VideoDevice *video() { return video_; }\n> +\tV4L2VideoDevice *video(const MediaEntity *entity);\n>   \tV4L2Subdevice *subdev(const MediaEntity *entity);\n>   \tSimpleConverter *converter() { return converter_; }\n>   \n> @@ -151,7 +151,7 @@ private:\n>   \tvoid converterDone(FrameBuffer *input, FrameBuffer *output);\n>   \n>   \tMediaDevice *media_;\n> -\tV4L2VideoDevice *video_;\n> +\tstd::map<const MediaEntity *, std::unique_ptr<V4L2VideoDevice>> videos_;\n>   \tstd::map<const MediaEntity *, V4L2Subdevice> subdevs_;\n>   \n>   \tSimpleConverter *converter_;\n> @@ -166,8 +166,8 @@ private:\n>    * Camera Data\n>    */\n>   \n> -SimpleCameraData::SimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor,\n> -\t\t\t\t   MediaEntity *video)\n> +SimpleCameraData::SimpleCameraData(SimplePipelineHandler *pipe,\n> +\t\t\t\t   MediaEntity *sensor)\n>   \t: CameraData(pipe)\n>   {\n>   \tint ret;\n> @@ -179,8 +179,8 @@ SimpleCameraData::SimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor,\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/* If we have reached a video node, we're done. */\n> +\t\tif (source->function() == MEDIA_ENT_F_IO_V4L)\n>   \t\t\tbreak;\n>   \n>   \t\t/* Use the first output pad that has links. */\n> @@ -224,7 +224,14 @@ SimpleCameraData::SimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor,\n>   \t\t}\n>   \t}\n>   \n> -\t/* We have a valid pipeline, create the camera sensor. */\n> +\t/*\n> +\t * We have a valid pipeline, get the video device and create the camera\n> +\t * sensor.\n> +\t */\n> +\tvideo_ = pipe->video(source);\n> +\tif (!video_)\n> +\t\treturn;\n> +\n>   \tsensor_ = std::make_unique<CameraSensor>(sensor);\n>   \tret = sensor_->init();\n>   \tif (ret) {\n> @@ -236,7 +243,6 @@ SimpleCameraData::SimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor,\n>   int SimpleCameraData::init()\n>   {\n>   \tSimplePipelineHandler *pipe = static_cast<SimplePipelineHandler *>(pipe_);\n> -\tV4L2VideoDevice *video = pipe->video();\n>   \tSimpleConverter *converter = pipe->converter();\n>   \tint ret;\n>   \n> @@ -266,7 +272,7 @@ int SimpleCameraData::init()\n>   \t\t}\n>   \n>   \t\tstd::map<V4L2PixelFormat, std::vector<SizeRange>> videoFormats =\n> -\t\t\tvideo->formats(format.mbus_code);\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> @@ -285,7 +291,7 @@ int SimpleCameraData::init()\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\tPixelFormat pixelFormat = video_->toPixelFormat(videoFormat.first);\n>   \t\t\tif (!pixelFormat)\n>   \t\t\t\tcontinue;\n>   \n> @@ -451,13 +457,12 @@ CameraConfiguration::Status SimpleCameraConfiguration::validate()\n>    */\n>   \n>   SimplePipelineHandler::SimplePipelineHandler(CameraManager *manager)\n> -\t: PipelineHandler(manager), video_(nullptr), converter_(nullptr)\n> +\t: PipelineHandler(manager), converter_(nullptr)\n>   {\n>   }\n>   \n>   SimplePipelineHandler::~SimplePipelineHandler()\n>   {\n> -\tdelete video_;\n>   \tdelete converter_;\n>   }\n>   \n> @@ -503,6 +508,7 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c)\n>   \tSimpleCameraConfiguration *config =\n>   \t\tstatic_cast<SimpleCameraConfiguration *>(c);\n>   \tSimpleCameraData *data = cameraData(camera);\n> +\tV4L2VideoDevice *video = data->video_;\n>   \tStreamConfiguration &cfg = config->at(0);\n>   \tint ret;\n>   \n> @@ -524,13 +530,13 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c)\n>   \t\treturn ret;\n>   \n>   \t/* Configure the video node. */\n> -\tV4L2PixelFormat videoFormat = video_->toV4L2PixelFormat(pipeConfig.pixelFormat);\n> +\tV4L2PixelFormat videoFormat = video->toV4L2PixelFormat(pipeConfig.pixelFormat);\n>   \n>   \tV4L2DeviceFormat captureFormat = {};\n>   \tcaptureFormat.fourcc = videoFormat;\n>   \tcaptureFormat.size = cfg.size;\n>   \n> -\tret = video_->setFormat(&captureFormat);\n> +\tret = video->setFormat(&captureFormat);\n>   \tif (ret)\n>   \t\treturn ret;\n>   \n> @@ -563,6 +569,7 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c)\n>   int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream,\n>   \t\t\t\t\t      std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n>   {\n> +\tSimpleCameraData *data = cameraData(camera);\n>   \tunsigned int count = stream->configuration().bufferCount;\n>   \n>   \t/*\n> @@ -572,23 +579,24 @@ int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream,\n>   \tif (useConverter_)\n>   \t\treturn converter_->exportBuffers(count, buffers);\n>   \telse\n> -\t\treturn video_->exportBuffers(count, buffers);\n> +\t\treturn data->video_->exportBuffers(count, buffers);\n>   }\n>   \n>   int SimplePipelineHandler::start(Camera *camera)\n>   {\n>   \tSimpleCameraData *data = cameraData(camera);\n> +\tV4L2VideoDevice *video = data->video_;\n>   \tunsigned int count = data->stream_.configuration().bufferCount;\n>   \tint ret;\n>   \n>   \tif (useConverter_)\n> -\t\tret = video_->allocateBuffers(count, &converterBuffers_);\n> +\t\tret = video->allocateBuffers(count, &converterBuffers_);\n>   \telse\n> -\t\tret = video_->importBuffers(count);\n> +\t\tret = video->importBuffers(count);\n>   \tif (ret < 0)\n>   \t\treturn ret;\n>   \n> -\tret = video_->streamOn();\n> +\tret = video->streamOn();\n>   \tif (ret < 0) {\n>   \t\tstop(camera);\n>   \t\treturn ret;\n> @@ -603,7 +611,7 @@ int SimplePipelineHandler::start(Camera *camera)\n>   \n>   \t\t/* Queue all internal buffers for capture. */\n>   \t\tfor (std::unique_ptr<FrameBuffer> &buffer : converterBuffers_)\n> -\t\t\tvideo_->queueBuffer(buffer.get());\n> +\t\t\tvideo->queueBuffer(buffer.get());\n>   \t}\n>   \n>   \tactiveCamera_ = camera;\n> @@ -613,11 +621,14 @@ int SimplePipelineHandler::start(Camera *camera)\n>   \n>   void SimplePipelineHandler::stop(Camera *camera)\n>   {\n> +\tSimpleCameraData *data = cameraData(camera);\n> +\tV4L2VideoDevice *video = data->video_;\n> +\n>   \tif (useConverter_)\n>   \t\tconverter_->stop();\n>   \n> -\tvideo_->streamOff();\n> -\tvideo_->releaseBuffers();\n> +\tvideo->streamOff();\n> +\tvideo->releaseBuffers();\n>   \n>   \tconverterBuffers_.clear();\n>   \tactiveCamera_ = nullptr;\n> @@ -644,7 +655,7 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request)\n>   \t\treturn 0;\n>   \t}\n>   \n> -\treturn video_->queueBuffer(buffer);\n> +\treturn data->video_->queueBuffer(buffer);\n>   }\n>   \n>   /* -----------------------------------------------------------------------------\n> @@ -672,12 +683,8 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)\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> +\t/* Locate the sensors. */\n>   \tstd::vector<MediaEntity *> sensors;\n> -\tstd::vector<MediaEntity *> videos;\n>   \n>   \tfor (MediaEntity *entity : media_->entities()) {\n>   \t\tswitch (entity->function()) {\n> @@ -685,12 +692,6 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)\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> @@ -701,26 +702,6 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)\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/* Open the converter, if any. */\n>   \tif (converter) {\n>   \t\tconverter_ = new SimpleConverter(converter);\n> @@ -745,8 +726,7 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)\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\t\tstd::make_unique<SimpleCameraData>(this, sensor);\n>   \t\tif (!data->isValid()) {\n>   \t\t\tLOG(SimplePipeline, Error)\n>   \t\t\t\t<< \"No valid pipeline for sensor '\"\n> @@ -793,6 +773,35 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)\n>   \treturn true;\n>   }\n>   \n> +V4L2VideoDevice *SimplePipelineHandler::video(const MediaEntity *entity)\n> +{\n> +\t/*\n> +\t * Return the V4L2VideoDevice corresponding to the media entity, either\n> +\t * as a previously constructed device if available from the cache, or\n> +\t * by constructing a new one.\n> +\t */\n> +\n> +\tauto iter = videos_.find(entity);\n> +\tif (iter != videos_.end())\n> +\t\treturn iter->second.get();\n> +\n> +\tstd::unique_ptr<V4L2VideoDevice> video =\n> +\t\tstd::make_unique<V4L2VideoDevice>(entity);\n> +\tif (video->open() < 0)\n> +\t\treturn nullptr;\n> +\n> +\tif (video->caps().isMultiplanar()) {\n> +\t\tLOG(SimplePipeline, Error)\n> +\t\t\t<< \"V4L2 multiplanar devices are not supported\";\n> +\t\treturn nullptr;\n> +\t}\n> +\n> +\tvideo->bufferReady.connect(this, &SimplePipelineHandler::bufferReady);\n> +\n> +\tauto element = videos_.emplace(entity, std::move(video));\n> +\treturn element.first->second.get();\n> +}\n> +\n>   V4L2Subdevice *SimplePipelineHandler::subdev(const MediaEntity *entity)\n>   {\n>   \tauto iter = subdevs_.find(entity);\n> @@ -808,6 +817,9 @@ V4L2Subdevice *SimplePipelineHandler::subdev(const MediaEntity *entity)\n>   \n>   void SimplePipelineHandler::bufferReady(FrameBuffer *buffer)\n>   {\n> +\tASSERT(activeCamera_);\n> +\tSimpleCameraData *data = cameraData(activeCamera_);\n> +\n>   \t/*\n>   \t * If an error occurred during capture, or if the buffer was cancelled,\n>   \t * complete the request, even if the converter is in use as there's no\n> @@ -816,7 +828,7 @@ void SimplePipelineHandler::bufferReady(FrameBuffer *buffer)\n>   \tif (buffer->metadata().status != FrameMetadata::FrameSuccess) {\n>   \t\tif (useConverter_) {\n>   \t\t\t/* Requeue the buffer for capture. */\n> -\t\t\tvideo_->queueBuffer(buffer);\n> +\t\t\tdata->video_->queueBuffer(buffer);\n>   \n>   \t\t\t/*\n>   \t\t\t * Get the next user-facing buffer to complete the\n> @@ -842,7 +854,7 @@ void SimplePipelineHandler::bufferReady(FrameBuffer *buffer)\n>   \t */\n>   \tif (useConverter_) {\n>   \t\tif (converterQueue_.empty()) {\n> -\t\t\tvideo_->queueBuffer(buffer);\n> +\t\t\tdata->video_->queueBuffer(buffer);\n>   \t\t\treturn;\n>   \t\t}\n>   \n> @@ -862,14 +874,16 @@ void SimplePipelineHandler::bufferReady(FrameBuffer *buffer)\n>   void SimplePipelineHandler::converterDone(FrameBuffer *input,\n>   \t\t\t\t\t  FrameBuffer *output)\n>   {\n> -\t/* Complete the request. */\n>   \tASSERT(activeCamera_);\n> +\tSimpleCameraData *data = cameraData(activeCamera_);\n> +\n> +\t/* Complete the request. */\n>   \tRequest *request = output->request();\n>   \tcompleteBuffer(activeCamera_, request, output);\n>   \tcompleteRequest(activeCamera_, request);\n>   \n>   \t/* Queue the input buffer back for capture. */\n> -\tvideo_->queueBuffer(input);\n> +\tdata->video_->queueBuffer(input);\n>   }\n>   \n>   REGISTER_PIPELINE_HANDLER(SimplePipelineHandler);\n>","headers":{"Return-Path":"<andrey.konovalov@linaro.org>","Received":["from mail-lj1-x242.google.com (mail-lj1-x242.google.com\n\t[IPv6:2a00:1450:4864:20::242])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 5C8D66279B\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed,  8 Apr 2020 19:07:00 +0200 (CEST)","by mail-lj1-x242.google.com with SMTP id q19so8397548ljp.9\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 08 Apr 2020 10:07:00 -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\tm1sm10715297lfl.69.2020.04.08.10.06.58\n\t(version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128);\n\tWed, 08 Apr 2020 10:06:58 -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=\"EjsXRCjM\"; \n\tdkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google;\n\th=subject:to:references:from:message-id:date:user-agent:mime-version\n\t:in-reply-to:content-language:content-transfer-encoding;\n\tbh=+wePWj6o79TN7IzAE12LXH+mrbQKDHsl/+nE1AGXUms=;\n\tb=EjsXRCjMFrWViABYVrILitoUrosjWo7vwYFGYKpTaWGCjD6aqO2d0e222s6AxJae2N\n\tLS2xI2fgEpsVd/Czbr7XvqFwpcxC/ShPai023OBIoG7slA/wAdk2rF8f3hKhrSiyUAhv\n\t+uh50aqd65oOo/rGdG6TqhhN/zVOUAmqXn70ssARFWwHaDylzhYsBq5te+xPeCBI/R+3\n\tEy0HblpjY2KMAIPEwbobAFv9f5lzkKT/7H5iPP1UG57eziggriLslq7aSaJ55TK+MDq4\n\tp1Bq4M9FLcB0sT9pKpSITrTv6LBY4ObQ8tDVZwGI4eA/7FtF7+4t+8vlBpZLSRv3kKzJ\n\tmeZw==","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:references:from:message-id:date\n\t:user-agent:mime-version:in-reply-to:content-language\n\t:content-transfer-encoding;\n\tbh=+wePWj6o79TN7IzAE12LXH+mrbQKDHsl/+nE1AGXUms=;\n\tb=if1UsW6TO5iMfIsNJWOeGU2c+UkC7q4NQdV5H3q9rWjgt87UT1zpRT8USC/jM4Zrx2\n\tEnL5F/FjM9IR6zLfGoNs51HJCah3pV9nZtXV7inUI4zZNroDbyJuF0byp/l9+H3KPeR9\n\td4khwPe18PIGx6ecUaWyiOGBsOkjHCjXjWUSrCjIHXHJDYFQre2bM+U+HWGrqHJIBCHy\n\tpL44hidzZ7+3VtoJns4h9uqIRaZylAlFZ72YSjsU+ndJzb6bYpTviIQ0IZL0sUGNtQjz\n\t1dL2cjqpm32sCTPgebee24Pysua7xgv2mVg5v0vQ71LMceKsH8OrZ5yUcjexlIudrCEM\n\tHXfg==","X-Gm-Message-State":"AGi0PuZwaD6VkfQ30MQUE3nKKY3JvOW7MpZJJSAjV5sz0rjAkg9js76M\n\tZZkz8g8WDf/FBw/F4cxBxERCiQ==","X-Google-Smtp-Source":"APiQypJvlY00RNDhRXFR3BQ/ztxZES7QmO6M5DIRhV+NBfRDUHoc+5E6ItJTqdaFn9p5cLjVPQ9rRg==","X-Received":"by 2002:a2e:878a:: with SMTP id n10mr442500lji.130.1586365619511;\n\tWed, 08 Apr 2020 10:06:59 -0700 (PDT)","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","References":"<20200404004438.17992-1-laurent.pinchart@ideasonboard.com>\n\t<20200404011624.19728-1-laurent.pinchart@ideasonboard.com>","From":"Andrey Konovalov <andrey.konovalov@linaro.org>","Message-ID":"<696d3a07-fcdd-de9d-8723-70705a219111@linaro.org>","Date":"Wed, 8 Apr 2020 20:06:57 +0300","User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101\n\tThunderbird/68.4.1","MIME-Version":"1.0","In-Reply-To":"<20200404011624.19728-1-laurent.pinchart@ideasonboard.com>","Content-Type":"text/plain; charset=utf-8; format=flowed","Content-Language":"en-US","Content-Transfer-Encoding":"7bit","Subject":"Re: [libcamera-devel] [PATCH v4 12/11] libcamera: pipeline: simple:\n\tSupport multiple capture video nodes","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":"Wed, 08 Apr 2020 17:07:00 -0000"}},{"id":4481,"web_url":"https://patchwork.libcamera.org/comment/4481/","msgid":"<20200421204053.GG2600980@oden.dyn.berto.se>","date":"2020-04-21T20:40:53","subject":"Re: [libcamera-devel] [PATCH v4 12/11] libcamera: pipeline: simple:\n\tSupport multiple capture video nodes","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 04:16:24 +0300, Laurent Pinchart wrote:\n> The simple pipeline handler rejects devices that have multiple capture\n> video nodes. There's no real reason to do so, a more dynamic approach is\n> possible as the pipeline handler already locates the video device by\n> walking the media graph.\n> \n> Rework the match sequence by skipping any check on the video nodes, and\n> create the V4L2VideoDevice for the media entity at the end of the\n> pipeline when initializing the camera data. The V4L2VideoDevice\n> instances are managed by the pipeline handler itself, to avoid creating\n> separate instances in the camera data if multiple sensors are routed to\n> the same video device.\n> \n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n\nI think some of this could be merged with previous patches in this \nseries, but no biggie. Wether or not you keep it as or merge it,\n\nReviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>\n\n> ---\n>  src/libcamera/pipeline/simple/simple.cpp | 136 +++++++++++++----------\n>  1 file changed, 75 insertions(+), 61 deletions(-)\n> \n> Andrey, does this fix your issue with the qcom-camss ?\n> \n> diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp\n> index 8e7dd091b4ab..b5f9177dd383 100644\n> --- a/src/libcamera/pipeline/simple/simple.cpp\n> +++ b/src/libcamera/pipeline/simple/simple.cpp\n> @@ -57,8 +57,7 @@ static const SimplePipelineInfo supportedDevices[] = {\n>  class SimpleCameraData : public CameraData\n>  {\n>  public:\n> -\tSimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor,\n> -\t\t\t MediaEntity *video);\n> +\tSimpleCameraData(SimplePipelineHandler *pipe, MediaEntity *sensor);\n>  \n>  \tbool isValid() const { return sensor_ != nullptr; }\n>  \tstd::set<Stream *> streams() { return { &stream_ }; }\n> @@ -82,6 +81,7 @@ public:\n>  \tStream stream_;\n>  \tstd::unique_ptr<CameraSensor> sensor_;\n>  \tstd::list<Entity> entities_;\n> +\tV4L2VideoDevice *video_;\n>  \n>  \tstd::vector<Configuration> configs_;\n>  \tstd::map<PixelFormat, Configuration> formats_;\n> @@ -129,7 +129,7 @@ public:\n>  \n>  \tbool match(DeviceEnumerator *enumerator) override;\n>  \n> -\tV4L2VideoDevice *video() { return video_; }\n> +\tV4L2VideoDevice *video(const MediaEntity *entity);\n>  \tV4L2Subdevice *subdev(const MediaEntity *entity);\n>  \tSimpleConverter *converter() { return converter_; }\n>  \n> @@ -151,7 +151,7 @@ private:\n>  \tvoid converterDone(FrameBuffer *input, FrameBuffer *output);\n>  \n>  \tMediaDevice *media_;\n> -\tV4L2VideoDevice *video_;\n> +\tstd::map<const MediaEntity *, std::unique_ptr<V4L2VideoDevice>> videos_;\n>  \tstd::map<const MediaEntity *, V4L2Subdevice> subdevs_;\n>  \n>  \tSimpleConverter *converter_;\n> @@ -166,8 +166,8 @@ private:\n>   * Camera Data\n>   */\n>  \n> -SimpleCameraData::SimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor,\n> -\t\t\t\t   MediaEntity *video)\n> +SimpleCameraData::SimpleCameraData(SimplePipelineHandler *pipe,\n> +\t\t\t\t   MediaEntity *sensor)\n>  \t: CameraData(pipe)\n>  {\n>  \tint ret;\n> @@ -179,8 +179,8 @@ SimpleCameraData::SimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor,\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/* If we have reached a video node, we're done. */\n> +\t\tif (source->function() == MEDIA_ENT_F_IO_V4L)\n>  \t\t\tbreak;\n>  \n>  \t\t/* Use the first output pad that has links. */\n> @@ -224,7 +224,14 @@ SimpleCameraData::SimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor,\n>  \t\t}\n>  \t}\n>  \n> -\t/* We have a valid pipeline, create the camera sensor. */\n> +\t/*\n> +\t * We have a valid pipeline, get the video device and create the camera\n> +\t * sensor.\n> +\t */\n> +\tvideo_ = pipe->video(source);\n> +\tif (!video_)\n> +\t\treturn;\n> +\n>  \tsensor_ = std::make_unique<CameraSensor>(sensor);\n>  \tret = sensor_->init();\n>  \tif (ret) {\n> @@ -236,7 +243,6 @@ SimpleCameraData::SimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor,\n>  int SimpleCameraData::init()\n>  {\n>  \tSimplePipelineHandler *pipe = static_cast<SimplePipelineHandler *>(pipe_);\n> -\tV4L2VideoDevice *video = pipe->video();\n>  \tSimpleConverter *converter = pipe->converter();\n>  \tint ret;\n>  \n> @@ -266,7 +272,7 @@ int SimpleCameraData::init()\n>  \t\t}\n>  \n>  \t\tstd::map<V4L2PixelFormat, std::vector<SizeRange>> videoFormats =\n> -\t\t\tvideo->formats(format.mbus_code);\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> @@ -285,7 +291,7 @@ int SimpleCameraData::init()\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\tPixelFormat pixelFormat = video_->toPixelFormat(videoFormat.first);\n>  \t\t\tif (!pixelFormat)\n>  \t\t\t\tcontinue;\n>  \n> @@ -451,13 +457,12 @@ CameraConfiguration::Status SimpleCameraConfiguration::validate()\n>   */\n>  \n>  SimplePipelineHandler::SimplePipelineHandler(CameraManager *manager)\n> -\t: PipelineHandler(manager), video_(nullptr), converter_(nullptr)\n> +\t: PipelineHandler(manager), converter_(nullptr)\n>  {\n>  }\n>  \n>  SimplePipelineHandler::~SimplePipelineHandler()\n>  {\n> -\tdelete video_;\n>  \tdelete converter_;\n>  }\n>  \n> @@ -503,6 +508,7 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c)\n>  \tSimpleCameraConfiguration *config =\n>  \t\tstatic_cast<SimpleCameraConfiguration *>(c);\n>  \tSimpleCameraData *data = cameraData(camera);\n> +\tV4L2VideoDevice *video = data->video_;\n>  \tStreamConfiguration &cfg = config->at(0);\n>  \tint ret;\n>  \n> @@ -524,13 +530,13 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c)\n>  \t\treturn ret;\n>  \n>  \t/* Configure the video node. */\n> -\tV4L2PixelFormat videoFormat = video_->toV4L2PixelFormat(pipeConfig.pixelFormat);\n> +\tV4L2PixelFormat videoFormat = video->toV4L2PixelFormat(pipeConfig.pixelFormat);\n>  \n>  \tV4L2DeviceFormat captureFormat = {};\n>  \tcaptureFormat.fourcc = videoFormat;\n>  \tcaptureFormat.size = cfg.size;\n>  \n> -\tret = video_->setFormat(&captureFormat);\n> +\tret = video->setFormat(&captureFormat);\n>  \tif (ret)\n>  \t\treturn ret;\n>  \n> @@ -563,6 +569,7 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c)\n>  int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream,\n>  \t\t\t\t\t      std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n>  {\n> +\tSimpleCameraData *data = cameraData(camera);\n>  \tunsigned int count = stream->configuration().bufferCount;\n>  \n>  \t/*\n> @@ -572,23 +579,24 @@ int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream,\n>  \tif (useConverter_)\n>  \t\treturn converter_->exportBuffers(count, buffers);\n>  \telse\n> -\t\treturn video_->exportBuffers(count, buffers);\n> +\t\treturn data->video_->exportBuffers(count, buffers);\n>  }\n>  \n>  int SimplePipelineHandler::start(Camera *camera)\n>  {\n>  \tSimpleCameraData *data = cameraData(camera);\n> +\tV4L2VideoDevice *video = data->video_;\n>  \tunsigned int count = data->stream_.configuration().bufferCount;\n>  \tint ret;\n>  \n>  \tif (useConverter_)\n> -\t\tret = video_->allocateBuffers(count, &converterBuffers_);\n> +\t\tret = video->allocateBuffers(count, &converterBuffers_);\n>  \telse\n> -\t\tret = video_->importBuffers(count);\n> +\t\tret = video->importBuffers(count);\n>  \tif (ret < 0)\n>  \t\treturn ret;\n>  \n> -\tret = video_->streamOn();\n> +\tret = video->streamOn();\n>  \tif (ret < 0) {\n>  \t\tstop(camera);\n>  \t\treturn ret;\n> @@ -603,7 +611,7 @@ int SimplePipelineHandler::start(Camera *camera)\n>  \n>  \t\t/* Queue all internal buffers for capture. */\n>  \t\tfor (std::unique_ptr<FrameBuffer> &buffer : converterBuffers_)\n> -\t\t\tvideo_->queueBuffer(buffer.get());\n> +\t\t\tvideo->queueBuffer(buffer.get());\n>  \t}\n>  \n>  \tactiveCamera_ = camera;\n> @@ -613,11 +621,14 @@ int SimplePipelineHandler::start(Camera *camera)\n>  \n>  void SimplePipelineHandler::stop(Camera *camera)\n>  {\n> +\tSimpleCameraData *data = cameraData(camera);\n> +\tV4L2VideoDevice *video = data->video_;\n> +\n>  \tif (useConverter_)\n>  \t\tconverter_->stop();\n>  \n> -\tvideo_->streamOff();\n> -\tvideo_->releaseBuffers();\n> +\tvideo->streamOff();\n> +\tvideo->releaseBuffers();\n>  \n>  \tconverterBuffers_.clear();\n>  \tactiveCamera_ = nullptr;\n> @@ -644,7 +655,7 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request)\n>  \t\treturn 0;\n>  \t}\n>  \n> -\treturn video_->queueBuffer(buffer);\n> +\treturn data->video_->queueBuffer(buffer);\n>  }\n>  \n>  /* -----------------------------------------------------------------------------\n> @@ -672,12 +683,8 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)\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> +\t/* Locate the sensors. */\n>  \tstd::vector<MediaEntity *> sensors;\n> -\tstd::vector<MediaEntity *> videos;\n>  \n>  \tfor (MediaEntity *entity : media_->entities()) {\n>  \t\tswitch (entity->function()) {\n> @@ -685,12 +692,6 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)\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> @@ -701,26 +702,6 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)\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/* Open the converter, if any. */\n>  \tif (converter) {\n>  \t\tconverter_ = new SimpleConverter(converter);\n> @@ -745,8 +726,7 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)\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\t\tstd::make_unique<SimpleCameraData>(this, sensor);\n>  \t\tif (!data->isValid()) {\n>  \t\t\tLOG(SimplePipeline, Error)\n>  \t\t\t\t<< \"No valid pipeline for sensor '\"\n> @@ -793,6 +773,35 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)\n>  \treturn true;\n>  }\n>  \n> +V4L2VideoDevice *SimplePipelineHandler::video(const MediaEntity *entity)\n> +{\n> +\t/*\n> +\t * Return the V4L2VideoDevice corresponding to the media entity, either\n> +\t * as a previously constructed device if available from the cache, or\n> +\t * by constructing a new one.\n> +\t */\n> +\n> +\tauto iter = videos_.find(entity);\n> +\tif (iter != videos_.end())\n> +\t\treturn iter->second.get();\n> +\n> +\tstd::unique_ptr<V4L2VideoDevice> video =\n> +\t\tstd::make_unique<V4L2VideoDevice>(entity);\n> +\tif (video->open() < 0)\n> +\t\treturn nullptr;\n> +\n> +\tif (video->caps().isMultiplanar()) {\n> +\t\tLOG(SimplePipeline, Error)\n> +\t\t\t<< \"V4L2 multiplanar devices are not supported\";\n> +\t\treturn nullptr;\n> +\t}\n> +\n> +\tvideo->bufferReady.connect(this, &SimplePipelineHandler::bufferReady);\n> +\n> +\tauto element = videos_.emplace(entity, std::move(video));\n> +\treturn element.first->second.get();\n> +}\n> +\n>  V4L2Subdevice *SimplePipelineHandler::subdev(const MediaEntity *entity)\n>  {\n>  \tauto iter = subdevs_.find(entity);\n> @@ -808,6 +817,9 @@ V4L2Subdevice *SimplePipelineHandler::subdev(const MediaEntity *entity)\n>  \n>  void SimplePipelineHandler::bufferReady(FrameBuffer *buffer)\n>  {\n> +\tASSERT(activeCamera_);\n> +\tSimpleCameraData *data = cameraData(activeCamera_);\n> +\n>  \t/*\n>  \t * If an error occurred during capture, or if the buffer was cancelled,\n>  \t * complete the request, even if the converter is in use as there's no\n> @@ -816,7 +828,7 @@ void SimplePipelineHandler::bufferReady(FrameBuffer *buffer)\n>  \tif (buffer->metadata().status != FrameMetadata::FrameSuccess) {\n>  \t\tif (useConverter_) {\n>  \t\t\t/* Requeue the buffer for capture. */\n> -\t\t\tvideo_->queueBuffer(buffer);\n> +\t\t\tdata->video_->queueBuffer(buffer);\n>  \n>  \t\t\t/*\n>  \t\t\t * Get the next user-facing buffer to complete the\n> @@ -842,7 +854,7 @@ void SimplePipelineHandler::bufferReady(FrameBuffer *buffer)\n>  \t */\n>  \tif (useConverter_) {\n>  \t\tif (converterQueue_.empty()) {\n> -\t\t\tvideo_->queueBuffer(buffer);\n> +\t\t\tdata->video_->queueBuffer(buffer);\n>  \t\t\treturn;\n>  \t\t}\n>  \n> @@ -862,14 +874,16 @@ void SimplePipelineHandler::bufferReady(FrameBuffer *buffer)\n>  void SimplePipelineHandler::converterDone(FrameBuffer *input,\n>  \t\t\t\t\t  FrameBuffer *output)\n>  {\n> -\t/* Complete the request. */\n>  \tASSERT(activeCamera_);\n> +\tSimpleCameraData *data = cameraData(activeCamera_);\n> +\n> +\t/* Complete the request. */\n>  \tRequest *request = output->request();\n>  \tcompleteBuffer(activeCamera_, request, output);\n>  \tcompleteRequest(activeCamera_, request);\n>  \n>  \t/* Queue the input buffer back for capture. */\n> -\tvideo_->queueBuffer(input);\n> +\tdata->video_->queueBuffer(input);\n>  }\n>  \n>  REGISTER_PIPELINE_HANDLER(SimplePipelineHandler);\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-x230.google.com (mail-lj1-x230.google.com\n\t[IPv6:2a00:1450:4864:20::230])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 8B8B060406\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 21 Apr 2020 22:40:55 +0200 (CEST)","by mail-lj1-x230.google.com with SMTP id g4so7323226ljl.2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 21 Apr 2020 13:40:55 -0700 (PDT)","from localhost (h-209-203.A463.priv.bahnhof.se. [155.4.209.203])\n\tby smtp.gmail.com with ESMTPSA id\n\tk18sm3429189lfg.81.2020.04.21.13.40.53\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tTue, 21 Apr 2020 13:40:54 -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=\"K6EntyVz\"; \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=juPKHVVBohLgfW5rk7hrBYLL5ifpeGxwoGXveau/XpA=;\n\tb=K6EntyVzpzo5TLV81FPs8ieFHUAUW+gaRZuZAQzuM3rlLseQsgTfFiOz/DmNZEN/sV\n\tHWfaZDgKaN2ylYHntuQQ1YAabDd6ijhYYeHa6JbqLSbvEpdEkyIrjnFbBVuow5ThqzmT\n\tBYWOYwS5NSC381o3GEbzcPCwWJsGv00ApH/Kx8klN5iepJVtCmz92z1KSAzDpwCqrbUs\n\tM8iGjKSa8t4hx8phOpmIYjes0qViB/KtwHPf5dJzDNo/wsCO1lzwRGKwgNVDSrCbkLuX\n\t6y58oOvKz8wbXR2s5RAdE6G2LLd/FMcNUoBCa69e5XgtowoNX4Pre4t5EokQBcPjX1Uy\n\t3FdA==","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=juPKHVVBohLgfW5rk7hrBYLL5ifpeGxwoGXveau/XpA=;\n\tb=XAHGmvgDbVlx5pNnOmh32y7lX5R9vIZDVX8/Dq35NZNZ66roe+KcJAlQV8qUQgxZ5Y\n\trzEP+JOPKA+8w3bMI9egNkNvVgUg7kSA0/w2ZmUqgjWEjPMY2UsxAYLwQjcQ85TRROta\n\tQOjH5IGHZQB4zD1JcMzHozFsDBds22bGImz97Aa044iM1wPOh8sOytIhvveYbabZvK02\n\t74wIZulIzi0T2rK4jMalRsFvxTf8HYn/b1507ozulepqQWJYn65SoaM2qGMJXKJCBuPW\n\tO0gyMeOUczD2J48rZtDHm0cND6QJdu0zg6ZLK2vGIAgb5Jkcc7qS1FZiHcYAMGevEtQ3\n\tUxJQ==","X-Gm-Message-State":"AGi0PuaUPlOL6cv5s1sB4UuURB4vJyTdPyciENsY7QMtS0aPrxTjKIcs\n\tVb2liyrPUfWzgYBS2iY5MM0U/0N2Cug=","X-Google-Smtp-Source":"APiQypKgWTq730qFDldQnAF+8FQcy3YmcOqTTssHmPQ2JVyWlGq2xQZMR0lJ7G7u8s4E/CGXwubkNw==","X-Received":"by 2002:a2e:9b42:: with SMTP id\n\to2mr11357813ljj.255.1587501654733; \n\tTue, 21 Apr 2020 13:40:54 -0700 (PDT)","Date":"Tue, 21 Apr 2020 22:40:53 +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","Message-ID":"<20200421204053.GG2600980@oden.dyn.berto.se>","References":"<20200404004438.17992-1-laurent.pinchart@ideasonboard.com>\n\t<20200404011624.19728-1-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":"<20200404011624.19728-1-laurent.pinchart@ideasonboard.com>","Subject":"Re: [libcamera-devel] [PATCH v4 12/11] libcamera: pipeline: simple:\n\tSupport multiple capture video nodes","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 20:40:55 -0000"}}]