[{"id":3428,"web_url":"https://patchwork.libcamera.org/comment/3428/","msgid":"<20200112134154.GD25899@pendragon.ideasonboard.com>","date":"2020-01-12T13:41:54","subject":"Re: [libcamera-devel] [PATCH v4 18/32] libcamera: v4l2_videodevice:\n\tAdd FrameBuffer interface","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Niklas,\n\nThank you for the patch.\n\nOn Sun, Jan 12, 2020 at 02:01:58AM +0100, Niklas Söderlund wrote:\n> Add a new interface in parallel with the existing Buffer implementation\n> to also support FrameBuffer. The reason it's added in parallel is to aid\n> in the migration from Buffer to FrameBuffer throughout libcamera. With\n> this change discrete parts of libcamera can be migrated and tested\n> independently.\n> \n> As the new interface is added in parallel there are some oddities in\n> this change which will be undone in a follow up patch once libcamera\n> have migrated away from the Buffer interface.\n> \n> - There is a nasty hack in V4L2VideoDevice::bufferAvailable(). It is\n>   needed to allow both interfaces to exist and function at the same\n>   time. The idea is if buffers are allocated using the FrameBuffer\n>   interface V4L2VideoDevice::cache_ is set and we know to call the\n>   FrameBuffer 'buffer ready' signal, and likewise if it's not to call\n>   the Buffer variant.\n> \n> - There is some code duplication between the two interfaces as they aim\n>   to solve the same thing in slightly different ways. As all Buffer\n>   related code is soon to be removed no effort to create code sharing\n>   between them have been made.\n> \n> - Some function and variables which can't be distinguished by their\n>   argument types have been given a frameBuffer prefix instead of a\n>   buffer prefix. They are clearly documented in the code and will be\n>   renamed to the correct buffer prefix when the Buffer interface is\n>   removed.\n> \n> Signed-off-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>\n> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> ---\n> * Changes since v2\n> - s/todo:/todo/\n> - Style updates\n> - Update documentation\n> - Remove buf.length > check in V4L2VideoDevice::dequeueFrameBuffer()\n> - Set length for multi planer output buffers in queueBuffer()\n> \n> * Changes since v1\n> - Rename allocateBuffers() to exportBuffers()\n> - Rename externalBuffers() to importBuffers()\n> - Rename createBuffer() to createFrameBuffer()\n> - Reworked createFrameBuffer() to take advantage of the FileDescriptor()\n> - Fix up a lot of documentation and small code beautification fixes.\n> - Merged all FrameBuffer interface additions into this patch, some where\n>   done in the really huge 27/30 patch.\n> ---\n>  src/libcamera/include/v4l2_videodevice.h |  12 +\n>  src/libcamera/v4l2_videodevice.cpp       | 313 ++++++++++++++++++++++-\n>  2 files changed, 315 insertions(+), 10 deletions(-)\n> \n> diff --git a/src/libcamera/include/v4l2_videodevice.h b/src/libcamera/include/v4l2_videodevice.h\n> index 9d22754a39a75621..09967d3c6ae59044 100644\n> --- a/src/libcamera/include/v4l2_videodevice.h\n> +++ b/src/libcamera/include/v4l2_videodevice.h\n> @@ -184,11 +184,17 @@ public:\n>  \n>  \tint exportBuffers(BufferPool *pool);\n>  \tint importBuffers(BufferPool *pool);\n> +\tint exportBuffers(unsigned int count,\n> +\t\t\t  std::vector<std::unique_ptr<FrameBuffer>> *buffers);\n> +\tint importBuffers(unsigned int count);\n>  \tint releaseBuffers();\n>  \n>  \tint queueBuffer(Buffer *buffer);\n>  \tstd::vector<std::unique_ptr<Buffer>> queueAllBuffers();\n>  \tSignal<Buffer *> bufferReady;\n> +\tint queueBuffer(FrameBuffer *buffer);\n> +\t/* todo Rename to bufferReady when the Buffer version is removed */\n> +\tSignal<FrameBuffer *> frameBufferReady;\n>  \n>  \tint streamOn();\n>  \tint streamOff();\n> @@ -219,10 +225,13 @@ private:\n>  \tint requestBuffers(unsigned int count);\n>  \tint createPlane(BufferMemory *buffer, unsigned int index,\n>  \t\t\tunsigned int plane, unsigned int length);\n> +\tstd::unique_ptr<FrameBuffer> createFrameBuffer(const struct v4l2_buffer &buf);\n>  \tFileDescriptor exportDmabufFd(unsigned int index, unsigned int plane);\n>  \n>  \tBuffer *dequeueBuffer();\n>  \tvoid bufferAvailable(EventNotifier *notifier);\n> +\t/* todo Rename to dequeueBuffer() when the Buffer version is removed */\n> +\tFrameBuffer *dequeueFrameBuffer();\n>  \n>  \tV4L2Capability caps_;\n>  \n> @@ -230,7 +239,10 @@ private:\n>  \tenum v4l2_memory memoryType_;\n>  \n>  \tBufferPool *bufferPool_;\n> +\tV4L2BufferCache *cache_;\n>  \tstd::map<unsigned int, Buffer *> queuedBuffers_;\n> +\t/* todo Rename to queuedBuffers_ when the Buffer version is removed */\n> +\tstd::map<unsigned int, FrameBuffer *> queuedFrameBuffers_;\n>  \n>  \tEventNotifier *fdEvent_;\n>  };\n> diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp\n> index 84c45dbcb85c8638..ed5b2377b806f19c 100644\n> --- a/src/libcamera/v4l2_videodevice.cpp\n> +++ b/src/libcamera/v4l2_videodevice.cpp\n> @@ -410,7 +410,8 @@ const std::string V4L2DeviceFormat::toString() const\n>   * \\param[in] deviceNode The file-system path to the video device node\n>   */\n>  V4L2VideoDevice::V4L2VideoDevice(const std::string &deviceNode)\n> -\t: V4L2Device(deviceNode), bufferPool_(nullptr), fdEvent_(nullptr)\n> +\t: V4L2Device(deviceNode), bufferPool_(nullptr), cache_(nullptr),\n> +\t  fdEvent_(nullptr)\n>  {\n>  \t/*\n>  \t * We default to an MMAP based CAPTURE video device, however this will\n> @@ -1075,6 +1076,94 @@ int V4L2VideoDevice::importBuffers(BufferPool *pool)\n>  \treturn 0;\n>  }\n>  \n> +/**\n> + * \\brief Allocate buffers from the video device\n> + * \\param[in] count Number of buffers to allocate\n> + * \\param[out] buffers Vector to store allocated buffers\n> + * \\return 0 on success or a negative error code otherwise\n> + */\n> +int V4L2VideoDevice::exportBuffers(unsigned int count,\n> +\t\t\t\t   std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n> +{\n> +\tif (cache_) {\n> +\t\tLOG(V4L2, Error) << \"Buffers already allocated\";\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\tmemoryType_ = V4L2_MEMORY_MMAP;\n> +\n> +\tint ret = requestBuffers(count);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tfor (unsigned i = 0; i < count; ++i) {\n> +\t\tstruct v4l2_buffer buf = {};\n> +\t\tstruct v4l2_plane planes[VIDEO_MAX_PLANES] = {};\n> +\n> +\t\tbuf.index = i;\n> +\t\tbuf.type = bufferType_;\n> +\t\tbuf.memory = memoryType_;\n> +\t\tbuf.length = ARRAY_SIZE(planes);\n> +\t\tbuf.m.planes = planes;\n> +\n> +\t\tret = ioctl(VIDIOC_QUERYBUF, &buf);\n> +\t\tif (ret < 0) {\n> +\t\t\tLOG(V4L2, Error)\n> +\t\t\t\t<< \"Unable to query buffer \" << i << \": \"\n> +\t\t\t\t<< strerror(-ret);\n> +\t\t\tgoto err_buf;\n> +\t\t}\n> +\n> +\t\tstd::unique_ptr<FrameBuffer> buffer = createFrameBuffer(buf);\n> +\t\tif (!buffer) {\n> +\t\t\tLOG(V4L2, Error) << \"Unable to create buffer\";\n> +\t\t\tret = -EINVAL;\n> +\t\t\tgoto err_buf;\n> +\t\t}\n> +\n> +\t\tbuffers->push_back(std::move(buffer));\n> +\t}\n> +\n> +\tcache_ = new V4L2BufferCache(*buffers);\n> +\n> +\treturn count;\n> +\n> +err_buf:\n> +\trequestBuffers(0);\n> +\n> +\tbuffers->clear();\n> +\n> +\treturn ret;\n> +}\n> +\n> +std::unique_ptr<FrameBuffer>\n> +V4L2VideoDevice::createFrameBuffer(const struct v4l2_buffer &buf)\n> +{\n> +\tconst bool multiPlanar = V4L2_TYPE_IS_MULTIPLANAR(buf.type);\n> +\tconst unsigned int numPlanes = multiPlanar ? buf.length : 1;\n> +\n> +\tif (numPlanes == 0 || numPlanes > VIDEO_MAX_PLANES) {\n> +\t\tLOG(V4L2, Error) << \"Invalid number of planes\";\n> +\t\treturn nullptr;\n> +\t}\n> +\n> +\tstd::vector<FrameBuffer::Plane> planes;\n> +\tfor (unsigned int nplane = 0; nplane < numPlanes; nplane++) {\n> +\t\tFileDescriptor fd = exportDmabufFd(buf.index, nplane);\n> +\t\tif (fd.fd() < 0)\n\nHow about\n\n\t\tif (!fd.isValid())\n\nThe rest looks good, please keep my R-b.\n\n> +\t\t\treturn nullptr;\n> +\n> +\t\tFrameBuffer::Plane plane;\n> +\t\tplane.fd = std::move(fd);\n> +\t\tplane.length = multiPlanar ?\n> +\t\t\tbuf.m.planes[nplane].length : buf.length;\n> +\n> +\t\tplanes.push_back(std::move(plane));\n> +\t}\n> +\n> +\treturn utils::make_unique<FrameBuffer>(std::move(planes));\n> +}\n> +\n>  FileDescriptor V4L2VideoDevice::exportDmabufFd(unsigned int index,\n>  \t\t\t\t\t       unsigned int plane)\n>  {\n> @@ -1096,14 +1185,41 @@ FileDescriptor V4L2VideoDevice::exportDmabufFd(unsigned int index,\n>  \treturn FileDescriptor(expbuf.fd);\n>  }\n>  \n> +/**\n> + * \\brief Prepare the device to import \\a count buffers\n> + * \\param[in] count Number of buffers to prepare to import\n> + * \\return 0 on success or a negative error code otherwise\n> + */\n> +int V4L2VideoDevice::importBuffers(unsigned int count)\n> +{\n> +\tif (cache_) {\n> +\t\tLOG(V4L2, Error) << \"Buffers already allocated\";\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\tmemoryType_ = V4L2_MEMORY_DMABUF;\n> +\n> +\tint ret = requestBuffers(count);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tcache_ = new V4L2BufferCache(count);\n> +\n> +\tLOG(V4L2, Debug) << \"Prepared to import \" << count << \" buffers\";\n> +\n> +\treturn 0;\n> +}\n> +\n>  /**\n>   * \\brief Release all internally allocated buffers\n>   */\n>  int V4L2VideoDevice::releaseBuffers()\n>  {\n> -\tLOG(V4L2, Debug) << \"Releasing bufferPool\";\n> +\tLOG(V4L2, Debug) << \"Releasing buffers\";\n>  \n>  \tbufferPool_ = nullptr;\n> +\tdelete cache_;\n> +\tcache_ = nullptr;\n>  \n>  \treturn requestBuffers(0);\n>  }\n> @@ -1222,6 +1338,90 @@ std::vector<std::unique_ptr<Buffer>> V4L2VideoDevice::queueAllBuffers()\n>  \treturn buffers;\n>  }\n>  \n> +/**\n> + * \\brief Queue a buffer to the video device\n> + * \\param[in] buffer The buffer to be queued\n> + *\n> + * For capture video devices the \\a buffer will be filled with data by the\n> + * device. For output video devices the \\a buffer shall contain valid data and\n> + * will be processed by the device. Once the device has finished processing the\n> + * buffer, it will be available for dequeue.\n> + *\n> + * The best available V4L2 buffer is picked for \\a buffer using the V4L2 buffer\n> + * cache.\n> + *\n> + * \\return 0 on success or a negative error code otherwise\n> + */\n> +int V4L2VideoDevice::queueBuffer(FrameBuffer *buffer)\n> +{\n> +\tstruct v4l2_plane v4l2Planes[VIDEO_MAX_PLANES] = {};\n> +\tstruct v4l2_buffer buf = {};\n> +\tint ret;\n> +\n> +\tret = cache_->get(*buffer);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tbuf.index = ret;\n> +\tbuf.type = bufferType_;\n> +\tbuf.memory = memoryType_;\n> +\tbuf.field = V4L2_FIELD_NONE;\n> +\n> +\tbool multiPlanar = V4L2_TYPE_IS_MULTIPLANAR(buf.type);\n> +\tconst std::vector<FrameBuffer::Plane> &planes = buffer->planes();\n> +\n> +\tif (buf.memory == V4L2_MEMORY_DMABUF) {\n> +\t\tif (multiPlanar) {\n> +\t\t\tfor (unsigned int p = 0; p < planes.size(); ++p)\n> +\t\t\t\tv4l2Planes[p].m.fd = planes[p].fd.fd();\n> +\t\t} else {\n> +\t\t\tbuf.m.fd = planes[0].fd.fd();\n> +\t\t}\n> +\t}\n> +\n> +\tif (multiPlanar) {\n> +\t\tbuf.length = planes.size();\n> +\t\tbuf.m.planes = v4l2Planes;\n> +\t}\n> +\n> +\tif (V4L2_TYPE_IS_OUTPUT(buf.type)) {\n> +\t\tconst FrameMetadata &metadata = buffer->metadata();\n> +\n> +\t\tif (multiPlanar) {\n> +\t\t\tunsigned int nplane = 0;\n> +\t\t\tfor (const FrameMetadata::Plane &plane : metadata.planes) {\n> +\t\t\t\tv4l2Planes[nplane].bytesused = plane.bytesused;\n> +\t\t\t\tv4l2Planes[nplane].length = buffer->planes()[nplane].length;\n> +\t\t\t\tnplane++;\n> +\t\t\t}\n> +\t\t} else {\n> +\t\t\tif (metadata.planes.size())\n> +\t\t\t\tbuf.bytesused = metadata.planes[0].bytesused;\n> +\t\t}\n> +\n> +\t\tbuf.sequence = metadata.sequence;\n> +\t\tbuf.timestamp.tv_sec = metadata.timestamp / 1000000000;\n> +\t\tbuf.timestamp.tv_usec = (metadata.timestamp / 1000) % 1000000;\n> +\t}\n> +\n> +\tLOG(V4L2, Debug) << \"Queueing buffer \" << buf.index;\n> +\n> +\tret = ioctl(VIDIOC_QBUF, &buf);\n> +\tif (ret < 0) {\n> +\t\tLOG(V4L2, Error)\n> +\t\t\t<< \"Failed to queue buffer \" << buf.index << \": \"\n> +\t\t\t<< strerror(-ret);\n> +\t\treturn ret;\n> +\t}\n> +\n> +\tif (queuedFrameBuffers_.empty())\n> +\t\tfdEvent_->setEnabled(true);\n> +\n> +\tqueuedFrameBuffers_[buf.index] = buffer;\n> +\n> +\treturn 0;\n> +}\n> +\n>  /**\n>   * \\brief Dequeue the next available buffer from the video device\n>   *\n> @@ -1281,17 +1481,97 @@ Buffer *V4L2VideoDevice::dequeueBuffer()\n>   * When this slot is called, a Buffer has become available from the device, and\n>   * will be emitted through the bufferReady Signal.\n>   *\n> - * For Capture video devices the Buffer will contain valid data.\n> - * For Output video devices the Buffer can be considered empty.\n> + * For Capture video devices the FrameBuffer will contain valid data.\n> + * For Output video devices the FrameBuffer can be considered empty.\n>   */\n>  void V4L2VideoDevice::bufferAvailable(EventNotifier *notifier)\n>  {\n> -\tBuffer *buffer = dequeueBuffer();\n> -\tif (!buffer)\n> -\t\treturn;\n> +\t/*\n> +\t * This is a hack which allows both Buffer and FrameBuffer interfaces\n> +\t * to work with the same code base. This allows different parts of\n> +\t * libcamera to migrate to FrameBuffer in different patches.\n> +\t *\n> +\t * If we have a cache_ we know FrameBuffer is in use.\n> +\t *\n> +\t * \\todo Remove this hack when the Buffer interface is removed.\n> +\t */\n> +\tif (cache_) {\n> +\t\tFrameBuffer *buffer = dequeueFrameBuffer();\n> +\t\tif (!buffer)\n> +\t\t\treturn;\n>  \n> -\t/* Notify anyone listening to the device. */\n> -\tbufferReady.emit(buffer);\n> +\t\t/* Notify anyone listening to the device. */\n> +\t\tframeBufferReady.emit(buffer);\n> +\t} else {\n> +\t\tBuffer *buffer = dequeueBuffer();\n> +\t\tif (!buffer)\n> +\t\t\treturn;\n> +\n> +\t\t/* Notify anyone listening to the device. */\n> +\t\tbufferReady.emit(buffer);\n> +\t}\n> +}\n> +\n> +/**\n> + * \\brief Dequeue the next available buffer from the video device\n> + *\n> + * This method dequeues the next available buffer from the device. If no buffer\n> + * is available to be dequeued it will return nullptr immediately.\n> + *\n> + * \\todo Rename to dequeueBuffer() once the FrameBuffer transition is complete\n> + *\n> + * \\return A pointer to the dequeued buffer on success, or nullptr otherwise\n> + */\n> +FrameBuffer *V4L2VideoDevice::dequeueFrameBuffer()\n> +{\n> +\tstruct v4l2_buffer buf = {};\n> +\tstruct v4l2_plane planes[VIDEO_MAX_PLANES] = {};\n> +\tint ret;\n> +\n> +\tbuf.type = bufferType_;\n> +\tbuf.memory = memoryType_;\n> +\n> +\tbool multiPlanar = V4L2_TYPE_IS_MULTIPLANAR(buf.type);\n> +\n> +\tif (multiPlanar) {\n> +\t\tbuf.length = VIDEO_MAX_PLANES;\n> +\t\tbuf.m.planes = planes;\n> +\t}\n> +\n> +\tret = ioctl(VIDIOC_DQBUF, &buf);\n> +\tif (ret < 0) {\n> +\t\tLOG(V4L2, Error)\n> +\t\t\t<< \"Failed to dequeue buffer: \" << strerror(-ret);\n> +\t\treturn nullptr;\n> +\t}\n> +\n> +\tLOG(V4L2, Debug) << \"Dequeuing buffer \" << buf.index;\n> +\n> +\tcache_->put(buf.index);\n> +\n> +\tauto it = queuedFrameBuffers_.find(buf.index);\n> +\tFrameBuffer *buffer = it->second;\n> +\tqueuedFrameBuffers_.erase(it);\n> +\n> +\tif (queuedFrameBuffers_.empty())\n> +\t\tfdEvent_->setEnabled(false);\n> +\n> +\tbuffer->metadata_.status = buf.flags & V4L2_BUF_FLAG_ERROR\n> +\t\t\t\t ? FrameMetadata::FrameError\n> +\t\t\t\t : FrameMetadata::FrameSuccess;\n> +\tbuffer->metadata_.sequence = buf.sequence;\n> +\tbuffer->metadata_.timestamp = buf.timestamp.tv_sec * 1000000000ULL\n> +\t\t\t\t    + buf.timestamp.tv_usec * 1000ULL;\n> +\n> +\tbuffer->metadata_.planes.clear();\n> +\tif (multiPlanar) {\n> +\t\tfor (unsigned int nplane = 0; nplane < buf.length; nplane++)\n> +\t\t\tbuffer->metadata_.planes.push_back({ planes[nplane].bytesused });\n> +\t} else {\n> +\t\tbuffer->metadata_.planes.push_back({ buf.bytesused });\n> +\t}\n> +\n> +\treturn buffer;\n>  }\n>  \n>  /**\n> @@ -1299,6 +1579,11 @@ void V4L2VideoDevice::bufferAvailable(EventNotifier *notifier)\n>   * \\brief A Signal emitted when a buffer completes\n>   */\n>  \n> +/**\n> + * \\var V4L2VideoDevice::frameBufferReady\n> + * \\brief A Signal emitted when a framebuffer completes\n> + */\n> +\n>  /**\n>   * \\brief Start the video stream\n>   * \\return 0 on success or a negative error code otherwise\n> @@ -1321,7 +1606,7 @@ int V4L2VideoDevice::streamOn()\n>   * \\brief Stop the video stream\n>   *\n>   * Buffers that are still queued when the video stream is stopped are\n> - * immediately dequeued with their status set to Buffer::BufferError,\n> + * immediately dequeued with their status set to FrameMetadata::FrameCancelled,\n>   * and the bufferReady signal is emitted for them. The order in which those\n>   * buffers are dequeued is not specified.\n>   *\n> @@ -1348,7 +1633,15 @@ int V4L2VideoDevice::streamOff()\n>  \t\tbufferReady.emit(buffer);\n>  \t}\n>  \n> +\tfor (auto it : queuedFrameBuffers_) {\n> +\t\tFrameBuffer *buffer = it.second;\n> +\n> +\t\tbuffer->metadata_.status = FrameMetadata::FrameCancelled;\n> +\t\tframeBufferReady.emit(buffer);\n> +\t}\n> +\n>  \tqueuedBuffers_.clear();\n> +\tqueuedFrameBuffers_.clear();\n>  \tfdEvent_->setEnabled(false);\n>  \n>  \treturn 0;","headers":{"Return-Path":"<laurent.pinchart@ideasonboard.com>","Received":["from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 1ED8F6045E\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSun, 12 Jan 2020 14:42:18 +0100 (CET)","from pendragon.ideasonboard.com (85-76-9-232-nat.elisa-mobile.fi\n\t[85.76.9.232])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 3A52430F;\n\tSun, 12 Jan 2020 14:42:16 +0100 (CET)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1578836537;\n\tbh=VU4ha7YYwPezHr6XQxu6ZBkv+vbcl/fhjKPAUp62K0M=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=HsEQcyaHsiAbxuNTQIu9u8qvuTdQrczWCwdPJ3hm/3j9Mk9Src6IqaFqJDxJCDGXf\n\truy/OIZOQvmOs1tN1ADp1dB19+SsjKLNXUdn6KSrqaRZ0FXSYsAF4dtQ4Iy36Huxgs\n\tKRVn1eOb9Hy5Gxp2iCC5JE6M0El/gsYOCxAksb5o=","Date":"Sun, 12 Jan 2020 15:41:54 +0200","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Niklas =?utf-8?q?S=C3=B6derlund?= <niklas.soderlund@ragnatech.se>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20200112134154.GD25899@pendragon.ideasonboard.com>","References":"<20200112010212.2609025-1-niklas.soderlund@ragnatech.se>\n\t<20200112010212.2609025-19-niklas.soderlund@ragnatech.se>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<20200112010212.2609025-19-niklas.soderlund@ragnatech.se>","User-Agent":"Mutt/1.10.1 (2018-07-13)","Subject":"Re: [libcamera-devel] [PATCH v4 18/32] libcamera: v4l2_videodevice:\n\tAdd FrameBuffer interface","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":"Sun, 12 Jan 2020 13:42:18 -0000"}}]