{"id":26366,"url":"https://patchwork.libcamera.org/api/1.1/patches/26366/?format=json","web_url":"https://patchwork.libcamera.org/patch/26366/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/1.1/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20260325151416.2114564-25-stefan.klug@ideasonboard.com>","date":"2026-03-25T15:13:56","name":"[v2,24/32] libcamera: internal: Add a BufferQueue class to handle buffer queues","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"d2ed44d01cf5370fc51d7d0c7c2dbbc5b9f4293e","submitter":{"id":184,"url":"https://patchwork.libcamera.org/api/1.1/people/184/?format=json","name":"Stefan Klug","email":"stefan.klug@ideasonboard.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/26366/mbox/","series":[{"id":5849,"url":"https://patchwork.libcamera.org/api/1.1/series/5849/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=5849","date":"2026-03-25T15:13:32","name":"rkisp1: pipeline rework for PFC","version":2,"mbox":"https://patchwork.libcamera.org/series/5849/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/26366/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/26366/checks/","tags":{},"headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 9D1DBC3306\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 25 Mar 2026 15:15:51 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 118BE62CC5;\n\tWed, 25 Mar 2026 16:15:51 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 15F3462CB1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 25 Mar 2026 16:15:49 +0100 (CET)","from ideasonboard.com (unknown\n\t[IPv6:2a00:6020:448c:6c00:b16a:5ed9:4ada:a95a])\n\tby perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id EB8301862; \n\tWed, 25 Mar 2026 16:14:30 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"bpPzCe/a\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1774451671;\n\tbh=UqvxXSxT4S9Qzmc+IYgd7Sf4uCo3mJ5q/3Ivvy/ssgo=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=bpPzCe/ar7MAnKLVC6HcrdDm2jW8wIokJY7eSV8HWGzlow6/8oqWJD2ckYzdQFb/O\n\tYxZMC4MGVpyaww+mtgSXGHaKMQH/7EoDRHvjqH12JD9yvurC0OQmP6P93Cygif2qkD\n\tIaAHvU1GDhWU2WY5nXG+8C6tr1Ssy0iEVzq9uYj0=","From":"Stefan Klug <stefan.klug@ideasonboard.com>","To":"libcamera-devel@lists.libcamera.org","Cc":"Stefan Klug <stefan.klug@ideasonboard.com>","Subject":"[PATCH v2 24/32] libcamera: internal: Add a BufferQueue class to\n\thandle buffer queues","Date":"Wed, 25 Mar 2026 16:13:56 +0100","Message-ID":"<20260325151416.2114564-25-stefan.klug@ideasonboard.com>","X-Mailer":"git-send-email 2.51.0","In-Reply-To":"<20260325151416.2114564-1-stefan.klug@ideasonboard.com>","References":"<20260325151416.2114564-1-stefan.klug@ideasonboard.com>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","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>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"},"content":"Add a class that encapsulates a queue of v4l2 buffers and the typical\nuse-cases. This simplifies manual queue management and helps in cases\nwhere a pre or postprocessing stage is needed.\n\nSigned-off-by: Stefan Klug <stefan.klug@ideasonboard.com>\n\n---\n\nChanges in v2:\n- Added this patch\n---\n include/libcamera/internal/buffer_queue.h | 131 +++++++\n include/libcamera/internal/meson.build    |   1 +\n src/libcamera/buffer_queue.cpp            | 449 ++++++++++++++++++++++\n src/libcamera/meson.build                 |   1 +\n 4 files changed, 582 insertions(+)\n create mode 100644 include/libcamera/internal/buffer_queue.h\n create mode 100644 src/libcamera/buffer_queue.cpp","diff":"diff --git a/include/libcamera/internal/buffer_queue.h b/include/libcamera/internal/buffer_queue.h\nnew file mode 100644\nindex 000000000000..d5904baeed90\n--- /dev/null\n+++ b/include/libcamera/internal/buffer_queue.h\n@@ -0,0 +1,131 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2025, Ideas on Board\n+ *\n+ * Sequence sync helper\n+ */\n+\n+#pragma once\n+\n+#include <map>\n+#include <memory>\n+\n+#include <libcamera/base/log.h>\n+#include <libcamera/base/signal.h>\n+\n+#include <libcamera/framebuffer.h>\n+\n+#include \"sequence_sync_helper.h\"\n+\n+namespace libcamera {\n+\n+LOG_DECLARE_CATEGORY(RkISP1Schedule)\n+\n+struct BufferQueueDelegateBase {\n+\tvirtual ~BufferQueueDelegateBase() = default;\n+\tvirtual int allocateBuffers(unsigned int count,\n+\t\t\t\t    std::vector<std::unique_ptr<FrameBuffer>> *buffers) = 0;\n+\tvirtual int importBuffers(unsigned int count) = 0;\n+\tvirtual int releaseBuffers() = 0;\n+\n+\tvirtual int queueBuffer(FrameBuffer *buffer) = 0;\n+\n+\tSignal<FrameBuffer *> bufferReady;\n+};\n+\n+template<typename T>\n+struct BufferQueueDelegate : public BufferQueueDelegateBase {\n+\tBufferQueueDelegate(T *video) : video_(video)\n+\t{\n+\t\tvideo_->bufferReady.connect(this, [this](FrameBuffer *buffer) {\n+\t\t\tthis->bufferReady.emit(buffer);\n+\t\t});\n+\t}\n+\n+\tint allocateBuffers(unsigned int count,\n+\t\t\t    std::vector<std::unique_ptr<FrameBuffer>> *buffers) override\n+\t{\n+\t\treturn video_->allocateBuffers(count, buffers);\n+\t}\n+\n+\tint importBuffers(unsigned int count) override\n+\t{\n+\t\treturn video_->importBuffers(count);\n+\t}\n+\n+\tint queueBuffer(FrameBuffer *buffer) override\n+\t{\n+\t\treturn video_->queueBuffer(buffer);\n+\t}\n+\n+\tint releaseBuffers() override\n+\t{\n+\t\treturn video_->releaseBuffers();\n+\t}\n+\n+private:\n+\tT *video_;\n+};\n+\n+class BufferQueue\n+{\n+public:\n+\tenum State {\n+\t\tIdle = 0,\n+\t\tPreparing,\n+\t\tCapturing,\n+\t\tPostprocessing\n+\t};\n+\n+\tenum Flags {\n+\t\tPrepareStage = 1,\n+\t\tPostprocessStage = 2\n+\t};\n+\n+\tBufferQueue(std::unique_ptr<BufferQueueDelegateBase> &&delegate, int flags = 0, std::string name = {});\n+\n+\tint allocateBuffers(unsigned int count);\n+\tint importBuffers(unsigned int count);\n+\tint releaseBuffers();\n+\n+\tint sequenceCorrection();\n+\tuint32_t nextSequence();\n+\n+\tint prepareBuffer(uint32_t *sequence = nullptr);\n+\tint prepareBuffer(FrameBuffer *buffer, uint32_t *sequence = nullptr);\n+\tint preparedBuffer();\n+\n+\tint queueBuffer(uint32_t *sequence = nullptr);\n+\tint queueBuffer(FrameBuffer *buffer, uint32_t *sequence = nullptr);\n+\n+\tvoid postprocessedBuffer();\n+\n+\tbool empty(State state);\n+\n+\tFrameBuffer *front(State state);\n+\n+\tunsigned int expectedSequence(FrameBuffer *buffer) const;\n+\tconst std::vector<std::unique_ptr<FrameBuffer>> &buffers() const;\n+\n+\tSignal<FrameBuffer *> bufferReady;\n+\n+protected:\n+\tvoid onBufferReady(FrameBuffer *buffer);\n+\n+\tint internalPrepareBuffer(FrameBuffer *buffer, uint32_t *sequence = nullptr);\n+\tint internalPreparedBuffer();\n+\tvoid internalPostprocessedBuffer();\n+\n+\tstd::map<State, std::list<FrameBuffer *>> bufferState_;\n+\tstd::map<FrameBuffer *, unsigned int> expectedSequence_;\n+\tstd::vector<std::unique_ptr<FrameBuffer>> buffers_;\n+\tSequenceSyncHelper syncHelper_;\n+\tuint32_t nextSequence_;\n+\tstd::string name_;\n+\tbool ownsBuffers_;\n+\tbool hasBuffers_;\n+\tint flags_;\n+\tstd::unique_ptr<BufferQueueDelegateBase> delegate_;\n+};\n+\n+} /* namespace libcamera */\ndiff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build\nindex 80c425e13ccd..8d25629daa24 100644\n--- a/include/libcamera/internal/meson.build\n+++ b/include/libcamera/internal/meson.build\n@@ -4,6 +4,7 @@ subdir('tracepoints')\n \n libcamera_internal_headers = files([\n     'bayer_format.h',\n+    'buffer_queue.h',\n     'byte_stream_buffer.h',\n     'camera.h',\n     'camera_controls.h',\ndiff --git a/src/libcamera/buffer_queue.cpp b/src/libcamera/buffer_queue.cpp\nnew file mode 100644\nindex 000000000000..46dbdd809d08\n--- /dev/null\n+++ b/src/libcamera/buffer_queue.cpp\n@@ -0,0 +1,449 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2025, Ideas on Board\n+ *\n+ * BufferQueue implementation\n+ */\n+\n+#include \"libcamera/internal/buffer_queue.h\"\n+\n+#include <libcamera/base/log.h>\n+\n+namespace libcamera {\n+\n+LOG_DEFINE_CATEGORY(BufferQueue)\n+\n+/**\n+ * \\class BufferQueue\n+ * \\brief Helper class to handle buffer queues\n+ *\n+ * Handling buffer queues is a common task when dealing with V4L2 video device.\n+ * Depending on the specific use case, additional functionalities are required:\n+ * - Supporting internally allocated and imported buffers\n+ * - Estimate the sequence number that a buffer will have after dequeuing\n+ * - Correct for errors in that sequence scheme due to frames beeing dropped in\n+ *   the kernel.\n+ * - Optionally adding a preparation stage for a buffer before it get's queued\n+ *   to the device\n+ * - Optionally adding a postprocessing stage after dequeueing\n+ *\n+ * This class encapsulates these functionalities in one component. To support\n+ * arbitrary V4L2VideoDevice like classes, the actual access to the buffer\n+ * related functions is handled through the BufferQueueDelegateBase interface\n+ * with BufferQueueDelegate providing a default implementation that works with\n+ * the V4L2VideDevice class.\n+ *\n+ * On construction time, it must be specified if a preparation stage and/or a\n+ * prostprocessing stage shall be added.\n+ *\n+ * Internally there are 4 queues, a buffer can be in:\n+ * Idle, Preparing, Capturing, Postprocessing.\n+ *\n+ * After creation of the BufferQueue it is important to route all buffer related\n+ * actions through the queue. That is:\n+ * 1. Allocation/import of buffers\n+ * 2. Queueing buffers\n+ * 3. Releasing buffers\n+ * 4. Handling the bufferReady signal.\n+ *\n+ * The callable functions depend on the flags passed to the constructor and are\n+ * shown on the following state diagram.\n+ *\n+ *  *------*\n+ *  | Idle |\n+ *  *------*\n+ *      |                        +----------------+\n+ *      +-- prepareBuffer() ---->|  Preparing     |\n+ *      |                        +----------------+\n+ *      |                                 |\n+ *      |                         preparedBuffer()\n+ *\t|\t\t                  |\n+ *\t|\t\t         +----------------+\n+ *      \\-- queueBuffer() ------>| Capturing      |\n+ *                               +----------------+\n+ *                                        |\n+ *                               +----------------+\n+ *                               | Postprocessing | --> bufferReady signal\n+ *                               +----------------+\n+ *                                      / |\n+ *     /-------------------------------/  | postprocessedBuffer()\n+ *     | /--------------------------------/\n+ *  *------*\n+ *  | Idle |\n+ *  *------*\n+ *\n+ * Notes:\n+ * - If buffers are not allocated by the queue but imported they never end up in\n+ *   the idle queue but are passed in by prepareBuffer()/queueBuffer() and leave\n+ *   the Queue after postprocessing.\n+ * - If a preparing stage is used, queueBuffer can not be called.\n+ * - If the postprocessing stage is disabled, it will still be used while\n+ *   emitting the bufferReady signal but the transition to idle happens\n+ *   automatically afterwards.\n+ */\n+\n+/**\n+ * \\brief Construct a BufferQueue\n+ * \\param[in] delegate The delegate\n+ * \\param[in] flags Optional flags\n+ * \\param[in] name Optional name\n+ *\n+ * Construct a buffer queue using the given delegate to forward the buffer\n+ * handling to. The default queue has only Idle and queued states. This can be\n+ * changed using the \\a flags parameter, to either add a prepare stage, a\n+ * postprocess stage or both.\n+ */\n+BufferQueue::BufferQueue(std::unique_ptr<BufferQueueDelegateBase> &&delegate,\n+\t\t\t int flags, std::string name)\n+\t: nextSequence_(0), name_(std::move(name)), ownsBuffers_(false),\n+\t  hasBuffers_(false), flags_(flags), delegate_(std::move(delegate))\n+{\n+\tdelegate_->bufferReady.connect(this, &BufferQueue::onBufferReady);\n+}\n+\n+/**\n+ * \\brief Allocate buffers\n+ * \\param[in] count The number of buffers to allocate\n+ *\n+ * This function allocates \\a count buffers by calling allocateBuffers() on the\n+ * delegate and forwarding the return value. A non negative return code is\n+ * treated as success. The buffers are owned by the BufferQueue.\n+ *\n+ * \\return The value returned by the BufferQueueDelegateBase::allocateBuffers()\n+ */\n+int BufferQueue::allocateBuffers(unsigned int count)\n+{\n+\tASSERT(!hasBuffers_);\n+\tbuffers_.clear();\n+\tint ret = delegate_->allocateBuffers(count, &buffers_);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tfor (const auto &buffer : buffers_)\n+\t\tbufferState_[Idle].push_back(buffer.get());\n+\n+\thasBuffers_ = true;\n+\townsBuffers_ = true;\n+\treturn ret;\n+}\n+\n+/**\n+ * \\brief Import buffers\n+ * \\param[in] count The number of buffers to import\n+ *\n+ * This function imports \\a count buffers by calling importBuffers() on the\n+ * delegate and forwarding the return value. A non negative return code is\n+ * treated as success.\n+ *\n+ * \\return The value returned by the BufferQueueDelegateBase::importBuffers()\n+ */\n+int BufferQueue::importBuffers(unsigned int count)\n+{\n+\tint ret = delegate_->importBuffers(count);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\thasBuffers_ = true;\n+\townsBuffers_ = false;\n+\treturn 0;\n+}\n+\n+int BufferQueue::sequenceCorrection()\n+{\n+\treturn syncHelper_.correction();\n+}\n+\n+uint32_t BufferQueue::nextSequence()\n+{\n+\treturn nextSequence_ + syncHelper_.correction();\n+}\n+\n+/**\n+ * \\brief Move the next buffer to prepare state\n+ * \\param[out] sequence The expected sequence of the buffer\n+ *\n+ * This function moves the front buffer from the idle queue to prepare queue. If\n+ * \\a sequence is provided it is set to the expected sequence number of that\n+ * buffer.\n+ *\n+ * \\note This function must only be called if the queue has a prepare state and\n+ * owns the buffers.\n+ *\n+ * \\return 0 on success, a negative error code otherwise\n+ */\n+int BufferQueue::prepareBuffer(uint32_t *sequence)\n+{\n+\tASSERT(hasBuffers_);\n+\tASSERT(ownsBuffers_);\n+\tASSERT(flags_ & PrepareStage);\n+\tASSERT(!bufferState_[Idle].empty());\n+\n+\tFrameBuffer *buffer = bufferState_[Idle].front();\n+\treturn prepareBuffer(buffer, sequence);\n+}\n+\n+/**\n+ * \\brief Move a buffer to prepare state\n+ * \\param[in] buffer The buffer\n+ * \\param[out] sequence The expected sequence of the buffer\n+ *\n+ * This function moves \\a buffer to prepare queue. If\n+ * \\a sequence is provided it is set to the expected sequence number of that\n+ * buffer.\n+ *\n+ * \\note This function must only be called if the queue has a prepare state. If\n+ * the queue owns the buffer, \\a buffer must point to the front buffer of the\n+ * idle queue.\n+ *\n+ * \\return 0 on success, a negative error code otherwise\n+ */\n+int BufferQueue::prepareBuffer(FrameBuffer *buffer, uint32_t *sequence)\n+{\n+\tASSERT(flags_ & PrepareStage);\n+\n+\treturn internalPrepareBuffer(buffer, sequence);\n+}\n+\n+/**\n+ * \\brief Exit prepare state\n+ *\n+ * This function pops the frontmost buffer from the prepare queue and queues it\n+ * on the underlying device by calling queueBuffer() on the delegate.\n+ *\n+ * \\note This function must only be called if the queue has a prepare state.\n+ *\n+ * \\return 0 on success, a negative error code otherwise\n+ */\n+int BufferQueue::preparedBuffer()\n+{\n+\tASSERT(flags_ & PrepareStage);\n+\n+\treturn internalPreparedBuffer();\n+}\n+\n+/**\n+ * \\brief Queue the next buffer\n+ * \\param[out] sequence The expected sequence of the buffer\n+ *\n+ * This function queues the front buffer from the idle queue to the underlying\n+ * device ba calling queueBuffer() on the delegate. If \\a sequence is provided\n+ * it is set to the expected sequence number of that buffer.\n+ *\n+ * \\note This function must only be called if the queue does not have a prepare\n+ * state and owns the buffers.\n+ *\n+ * \\return 0 on success, a negative error code otherwise\n+ */\n+int BufferQueue::queueBuffer(uint32_t *sequence)\n+{\n+\tASSERT(hasBuffers_);\n+\tASSERT(ownsBuffers_);\n+\tASSERT(!bufferState_[Idle].empty());\n+\n+\tFrameBuffer *buffer = bufferState_[Idle].front();\n+\treturn queueBuffer(buffer, sequence);\n+}\n+\n+/**\n+ * \\brief Queue a buffer\n+ * \\param[in] buffer The buffer\n+ * \\param[out] sequence The expected sequence of the buffer\n+ *\n+ * This function queues \\a buffer to the underlying device ba calling\n+ * queueBuffer() on the delegate. If \\a sequence is provided it is set to the\n+ * expected sequence number of that buffer.\n+ *\n+ * \\note This function must only be called if the queue does not have a prepare\n+ * state. If the queue owns the buffers, \\a buffer must point to the front\n+ * buffer of the idle queue.\n+ *\n+ * \\return 0 on success, a negative error code otherwise\n+ */\n+int BufferQueue::queueBuffer(FrameBuffer *buffer, uint32_t *sequence)\n+{\n+\tASSERT(!(flags_ & PrepareStage));\n+\treturn internalPrepareBuffer(buffer, sequence);\n+}\n+\n+/**\n+ * \\brief Exit postprocessed state\n+ *\n+ * This function pops the frontmost buffer from the postprocess queue and puts it\n+ * back to the idle queue in case the buffers are owned by the queue\n+ *\n+ * \\note This function must only be called if the queue has a prepare state.\n+ */\n+void BufferQueue::postprocessedBuffer()\n+{\n+\tASSERT(hasBuffers_);\n+\tASSERT(flags_ & PostprocessStage);\n+\treturn internalPostprocessedBuffer();\n+}\n+\n+/**\n+ * \\brief Release buffers\n+ *\n+ * This function releases the allocated or imported buffers by calling\n+ * releaseBuffers() on the delegate.\n+ *\n+ * \\return 0 on success, a negative error code otherwise\n+ */\n+int BufferQueue::releaseBuffers()\n+{\n+\tASSERT(bufferState_[BufferQueue::Idle].size() == buffers_.size());\n+\n+\tbufferState_[BufferQueue::Idle] = {};\n+\tbuffers_.clear();\n+\thasBuffers_ = false;\n+\n+\treturn delegate_->releaseBuffers();\n+}\n+\n+/**\n+ * \\brief Check if queue is empty\n+ * \\param[in] state The state\n+ *\n+ * \\return True if the queue for the given state is empty, false otherwise\n+ */\n+bool BufferQueue::empty(BufferQueue::State state)\n+{\n+\treturn bufferState_[state].empty();\n+}\n+\n+/**\n+ * \\brief Get the front buffer of a queue\n+ * \\param[in] state The state\n+ *\n+ * \\return The front buffer of the queue, or null otherwise\n+ */\n+FrameBuffer *BufferQueue::front(BufferQueue::State state)\n+{\n+\tif (empty(state))\n+\t\treturn nullptr;\n+\treturn bufferState_[state].front();\n+}\n+\n+/**\n+ * \\brief Get the expected sequence for a buffer\n+ * \\param[in] buffer The buffer\n+ *\n+ * \\return The front buffer of the queue, or null otherwise\n+ */\n+unsigned int BufferQueue::expectedSequence(FrameBuffer *buffer) const\n+{\n+\tauto it = expectedSequence_.find(buffer);\n+\tASSERT(it != expectedSequence_.end());\n+\treturn it->second;\n+}\n+\n+/**\n+ * \\brief Get the allocated buffers\n+ *\n+ * \\return The buffers owned by the queue\n+ */\n+const std::vector<std::unique_ptr<FrameBuffer>> &BufferQueue::buffers() const\n+{\n+\treturn buffers_;\n+}\n+\n+void BufferQueue::onBufferReady(FrameBuffer *buffer)\n+{\n+\tASSERT(!empty(Capturing));\n+\n+\tauto &meta = buffer->metadata();\n+\tauto &queue = bufferState_[Capturing];\n+\n+\t/*\n+         * V4L2 does not guarantee that buffers are dequeued in order. We expect\n+         * drivers to usually do so, and therefore warn, if a buffer is returned\n+         * out of order. After streamoff V4L2VideoDevice returns the buffers in\n+         * arbitrary order so there is no warning needed in that case.\n+         */\n+\tauto it = std::find(queue.begin(), queue.end(), buffer);\n+\tASSERT(it != queue.end());\n+\n+\tif (it != queue.begin() &&\n+\t    meta.status != FrameMetadata::FrameCancelled)\n+\t\tLOG(BufferQueue, Warning) << name_ << \": Dequeued buffer out of order \" << buffer;\n+\n+\tqueue.erase(it);\n+\tif (meta.status == FrameMetadata::FrameCancelled) {\n+\t\tsyncHelper_.cancelFrame();\n+\t\tif (ownsBuffers_)\n+\t\t\tbufferState_[Idle].push_back(buffer);\n+\t} else {\n+\t\tsyncHelper_.gotFrame(expectedSequence_[buffer], meta.sequence);\n+\t\tbufferState_[Postprocessing].push_back(buffer);\n+\t}\n+\n+\tbufferReady.emit(buffer);\n+\n+\tif (!(flags_ & PostprocessStage) &&\n+\t    meta.status != FrameMetadata::FrameCancelled)\n+\t\tinternalPostprocessedBuffer();\n+}\n+\n+int BufferQueue::internalPrepareBuffer(FrameBuffer *buffer, uint32_t *sequence)\n+{\n+\tASSERT(hasBuffers_);\n+\n+\tif (ownsBuffers_) {\n+\t\tASSERT(!bufferState_[Idle].empty());\n+\t\tASSERT(bufferState_[Idle].front() == buffer);\n+\t}\n+\n+\tLOG(BufferQueue, Debug) << name_ << \":Buffer prepare: \"\n+\t\t\t\t<< buffer;\n+\tint correction = syncHelper_.correction();\n+\tnextSequence_ += correction;\n+\texpectedSequence_[buffer] = nextSequence_;\n+\tif (ownsBuffers_)\n+\t\tbufferState_[Idle].pop_front();\n+\tbufferState_[Preparing].push_back(buffer);\n+\tsyncHelper_.pushCorrection(correction);\n+\n+\tif (sequence)\n+\t\t*sequence = nextSequence_;\n+\n+\tnextSequence_++;\n+\n+\tif (!(flags_ & PrepareStage))\n+\t\treturn internalPreparedBuffer();\n+\n+\treturn 0;\n+}\n+\n+int BufferQueue::internalPreparedBuffer()\n+{\n+\tASSERT(!bufferState_[Preparing].empty());\n+\n+\tauto &srcQueue = bufferState_[Preparing];\n+\tFrameBuffer *buffer = srcQueue.front();\n+\n+\tsrcQueue.pop_front();\n+\tint ret = delegate_->queueBuffer(buffer);\n+\tif (ret < 0) {\n+\t\tLOG(BufferQueue, Error) << \"Failed to queue buffer: \"\n+\t\t\t\t\t<< strerror(-ret);\n+\t\tif (ownsBuffers_)\n+\t\t\tbufferState_[Idle].push_back(buffer);\n+\t\treturn ret;\n+\t}\n+\n+\tLOG(BufferQueue, Debug) << name_ << \" Queued buffer: \" << buffer;\n+\n+\tbufferState_[Capturing].push_back(buffer);\n+\treturn 0;\n+}\n+\n+void BufferQueue::internalPostprocessedBuffer()\n+{\n+\tASSERT(!empty(Postprocessing));\n+\n+\tFrameBuffer *buffer = bufferState_[Postprocessing].front();\n+\tbufferState_[Postprocessing].pop_front();\n+\tif (ownsBuffers_)\n+\t\tbufferState_[Idle].push_back(buffer);\n+}\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\nindex 8b82555a5e81..55bdb895f6b4 100644\n--- a/src/libcamera/meson.build\n+++ b/src/libcamera/meson.build\n@@ -18,6 +18,7 @@ libcamera_public_sources = files([\n \n libcamera_internal_sources = files([\n     'bayer_format.cpp',\n+    'buffer_queue.cpp',\n     'byte_stream_buffer.cpp',\n     'camera_controls.cpp',\n     'camera_lens.cpp',\n","prefixes":["v2","24/32"]}