Show a patch.

GET /api/patches/2565/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 2565,
    "url": "https://patchwork.libcamera.org/api/patches/2565/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/2565/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/projects/1/?format=api",
        "name": "libcamera",
        "link_name": "libcamera",
        "list_id": "libcamera_core",
        "list_email": "libcamera-devel@lists.libcamera.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": ""
    },
    "msgid": "<20200110193808.2266294-19-niklas.soderlund@ragnatech.se>",
    "date": "2020-01-10T19:37:53",
    "name": "[libcamera-devel,v3,18/33] libcamera: v4l2_videodevice: Add FrameBuffer interface",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "122b78a2f3104ad55820c7064523d2a04e7a7119",
    "submitter": {
        "id": 5,
        "url": "https://patchwork.libcamera.org/api/people/5/?format=api",
        "name": "Niklas Söderlund",
        "email": "niklas.soderlund@ragnatech.se"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/2565/mbox/",
    "series": [
        {
            "id": 615,
            "url": "https://patchwork.libcamera.org/api/series/615/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=615",
            "date": "2020-01-10T19:37:35",
            "name": "libcamera: Rework buffer API",
            "version": 3,
            "mbox": "https://patchwork.libcamera.org/series/615/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/2565/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/2565/checks/",
    "tags": {},
    "headers": {
        "Return-Path": "<niklas.soderlund@ragnatech.se>",
        "Received": [
            "from bin-mail-out-06.binero.net (bin-mail-out-06.binero.net\n\t[195.74.38.229])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 4435C606BB\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 10 Jan 2020 20:38:52 +0100 (CET)",
            "from bismarck.berto.se (p54ac5d7b.dip0.t-ipconnect.de\n\t[84.172.93.123]) by bin-vsp-out-02.atm.binero.net (Halon) with ESMTPA\n\tid d21a601f-33e0-11ea-b6d8-005056917f90;\n\tFri, 10 Jan 2020 20:38:48 +0100 (CET)"
        ],
        "X-Halon-ID": "d21a601f-33e0-11ea-b6d8-005056917f90",
        "Authorized-sender": "niklas@soderlund.pp.se",
        "From": "=?utf-8?q?Niklas_S=C3=B6derlund?= <niklas.soderlund@ragnatech.se>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Date": "Fri, 10 Jan 2020 20:37:53 +0100",
        "Message-Id": "<20200110193808.2266294-19-niklas.soderlund@ragnatech.se>",
        "X-Mailer": "git-send-email 2.24.1",
        "In-Reply-To": "<20200110193808.2266294-1-niklas.soderlund@ragnatech.se>",
        "References": "<20200110193808.2266294-1-niklas.soderlund@ragnatech.se>",
        "MIME-Version": "1.0",
        "Content-Type": "text/plain; charset=UTF-8",
        "Content-Transfer-Encoding": "8bit",
        "Subject": "[libcamera-devel] [PATCH v3 18/33] libcamera: v4l2_videodevice: Add\n\tFrameBuffer 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": "Fri, 10 Jan 2020 19:38:53 -0000"
    },
    "content": "Add a new interface in parallel with the existing Buffer implementation\nto also support FrameBuffer. The reason it's added in parallel is to aid\nin the migration from Buffer to FrameBuffer throughout libcamera. With\nthis change discrete parts of libcamera can be migrated and tested\nindependently.\n\nAs the new interface is added in parallel there are some oddities in\nthis change which will be undone in a follow up patch once libcamera\nhave 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\nSigned-off-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>\nReviewed-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       | 315 ++++++++++++++++++++++-\n 2 files changed, 317 insertions(+), 10 deletions(-)",
    "diff": "diff --git a/src/libcamera/include/v4l2_videodevice.h b/src/libcamera/include/v4l2_videodevice.h\nindex 820b0cb297744828..68617bb2f2b42dcf 100644\n--- a/src/libcamera/include/v4l2_videodevice.h\n+++ b/src/libcamera/include/v4l2_videodevice.h\n@@ -183,11 +183,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@@ -218,10 +224,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 \tint 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@@ -229,7 +238,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 };\ndiff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp\nindex 474849846bddca8b..0099eb6c325dfbbb 100644\n--- a/src/libcamera/v4l2_videodevice.cpp\n+++ b/src/libcamera/v4l2_videodevice.cpp\n@@ -409,7 +409,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@@ -1077,6 +1078,96 @@ 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\tint fd = exportDmabufFd(buf.index, nplane);\n+\t\tif (fd < 0)\n+\t\t\treturn nullptr;\n+\n+\t\tFrameBuffer::Plane plane;\n+\t\tplane.fd = FileDescriptor(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+\n+\t\t::close(fd);\n+\t}\n+\n+\treturn utils::make_unique<FrameBuffer>(std::move(planes));\n+}\n+\n int V4L2VideoDevice::exportDmabufFd(unsigned int index, unsigned int plane)\n {\n \tstruct v4l2_exportbuffer expbuf = {};\n@@ -1097,14 +1188,41 @@ int V4L2VideoDevice::exportDmabufFd(unsigned int index, unsigned int plane)\n \treturn 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@@ -1223,6 +1341,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@@ -1282,17 +1484,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@@ -1300,6 +1582,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@@ -1322,7 +1609,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@@ -1349,7 +1636,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;\n",
    "prefixes": [
        "libcamera-devel",
        "v3",
        "18/33"
    ]
}