Patch Detail
Show a patch.
GET /api/1.1/patches/26367/?format=api
{ "id": 26367, "url": "https://patchwork.libcamera.org/api/1.1/patches/26367/?format=api", "web_url": "https://patchwork.libcamera.org/patch/26367/", "project": { "id": 1, "url": "https://patchwork.libcamera.org/api/1.1/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": "<20260325151416.2114564-26-stefan.klug@ideasonboard.com>", "date": "2026-03-25T15:13:57", "name": "[v2,25/32] pipeline: rkisp1: Decouple image, stats and param buffers", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "39994601a99776f2719c79856a47b562c420e8ce", "submitter": { "id": 184, "url": "https://patchwork.libcamera.org/api/1.1/people/184/?format=api", "name": "Stefan Klug", "email": "stefan.klug@ideasonboard.com" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/26367/mbox/", "series": [ { "id": 5849, "url": "https://patchwork.libcamera.org/api/1.1/series/5849/?format=api", "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/26367/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/26367/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 E04DBC32F8\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 25 Mar 2026 15:15:53 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 95E8062CC9;\n\tWed, 25 Mar 2026 16:15:53 +0100 (CET)", "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 2063762CB1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 25 Mar 2026 16:15:52 +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 B211E1862; \n\tWed, 25 Mar 2026 16:14:33 +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=\"sckeQkMX\"; dkim-atps=neutral", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1774451674;\n\tbh=MSXQQJi8LE2cnkAR6yhx1iug0DEiN/oipTl0Lozh39U=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=sckeQkMX1NIGXiuwvimpijbWkRB9YJRbBqq13TZqp/tM85vtiBdVJCaAQ1WV5QLA8\n\tHDLOcPsGJHIcLrAhNm/0K76NtLLWYx3b4Rcr6NuarIorUinMnXa0d7Lb+3r7pOzi7L\n\tvuIaEjkuM1FaZvliJ+hJhZtl/3W5tRP+y5Zoc+uQ=", "From": "Stefan Klug <stefan.klug@ideasonboard.com>", "To": "libcamera-devel@lists.libcamera.org", "Cc": "Stefan Klug <stefan.klug@ideasonboard.com>", "Subject": "[PATCH v2 25/32] pipeline: rkisp1: Decouple image,\n\tstats and param buffers", "Date": "Wed, 25 Mar 2026 16:13:57 +0100", "Message-ID": "<20260325151416.2114564-26-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": "The current code creates FrameInfo objects that tie together params,\nstats and image buffers and expect that these always stay in these\ngroupings. However there are cases where these sequences get out of sync\n(e.g. a scratch buffer is used for an image or a params buffer was too\nlate and therefore gets applied one frame later). As these situations\nare timing related and cpu dependent it is impossible to guarantee the\ninitial grouping of buffers.\n\nSplit the buffers into separate queues for stats, images and params.\nResynchronize on image buffers as soon as they are dequeued from V4L2 as\nthese are the only buffers tied to the libcamera requests coming from\nthe user application.\n\nNow handle the other buffer types according to their specific properties:\n\nStats buffers only need to be tracked after dequing and can be tied to\nthe corresponding request after the corresponding image buffer was\ndequeued.\n\nIf params buffers get out of sync we can either inject the same set of\nparameters twice or skip one set of params.\n\nIf image buffers get out of sync we need to update the expected sensor\nsequence, so that the next request is assigned the correct sensor\nsequence.\n\nSigned-off-by: Stefan Klug <stefan.klug@ideasonboard.com>\n\n---\n\nChanges in v2:\n- Mostly cosmetic changes\n\nChanges in v1:\n- Moved variables resets in start() further to the top\n\nChanges in v0.6:\n- Fixed multiple assertions on start/stop and a few corner cases\n- Fixed crash in dewarpRequestReady on stop\n\nChanges in v0.5\n- Fixed possible use-after-free in RequestInfo\n---\n src/libcamera/pipeline/rkisp1/rkisp1.cpp | 749 +++++++++++++++--------\n 1 file changed, 482 insertions(+), 267 deletions(-)", "diff": "diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\nindex b70c551fc775..f3e0ee5d3028 100644\n--- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n+++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n@@ -6,6 +6,8 @@\n */\n \n #include <algorithm>\n+#include <deque>\n+#include <iterator>\n #include <map>\n #include <memory>\n #include <numeric>\n@@ -45,6 +47,7 @@\n #include \"libcamera/internal/media_pipeline.h\"\n #include \"libcamera/internal/pipeline_handler.h\"\n #include \"libcamera/internal/request.h\"\n+#include \"libcamera/internal/sequence_sync_helper.h\"\n #include \"libcamera/internal/v4l2_subdevice.h\"\n #include \"libcamera/internal/v4l2_videodevice.h\"\n #include \"libcamera/internal/yaml_parser.h\"\n@@ -54,48 +57,19 @@\n namespace libcamera {\n \n LOG_DEFINE_CATEGORY(RkISP1)\n+LOG_DEFINE_CATEGORY(RkISP1Schedule)\n \n class PipelineHandlerRkISP1;\n class RkISP1CameraData;\n \n-struct RkISP1FrameInfo {\n-\tunsigned int frame;\n-\tRequest *request;\n \n-\tFrameBuffer *paramBuffer;\n-\tFrameBuffer *statBuffer;\n-\tFrameBuffer *mainPathBuffer;\n-\tFrameBuffer *selfPathBuffer;\n-\n-\tbool paramDequeued;\n-\tbool metadataProcessed;\n-};\n-\n-class RkISP1Frames\n-{\n-public:\n-\tRkISP1Frames(PipelineHandler *pipe);\n-\n-\tRkISP1FrameInfo *create(const RkISP1CameraData *data, Request *request,\n-\t\t\t\tbool isRaw);\n-\tint destroy(unsigned int frame);\n-\tvoid clear();\n-\n-\tRkISP1FrameInfo *find(unsigned int frame);\n-\tRkISP1FrameInfo *find(FrameBuffer *buffer);\n-\tRkISP1FrameInfo *find(Request *request);\n-\n-private:\n-\tPipelineHandlerRkISP1 *pipe_;\n-\tstd::map<unsigned int, RkISP1FrameInfo> frameInfo_;\n-};\n \n class RkISP1CameraData : public Camera::Private\n {\n public:\n \tRkISP1CameraData(PipelineHandler *pipe, RkISP1MainPath *mainPath,\n \t\t\t RkISP1SelfPath *selfPath)\n-\t\t: Camera::Private(pipe), frame_(0), frameInfo_(pipe),\n+\t\t: Camera::Private(pipe), frame_(0),\n \t\t mainPath_(mainPath), selfPath_(selfPath),\n \t\t canUseDewarper_(false), usesDewarper_(false)\n \t{\n@@ -109,9 +83,12 @@ public:\n \tStream selfPathStream_;\n \tstd::unique_ptr<CameraSensor> sensor_;\n \tstd::unique_ptr<DelayedControls> delayedCtrls_;\n+\t/*\n+\t * The sensor frame sequence of the last request queued to the pipeline\n+\t * handler.\n+\t */\n \tunsigned int frame_;\n \tstd::vector<IPABuffer> ipaBuffers_;\n-\tRkISP1Frames frameInfo_;\n \n \tRkISP1MainPath *mainPath_;\n \tRkISP1SelfPath *selfPath_;\n@@ -162,21 +139,54 @@ private:\n \tTransform combinedTransform_;\n };\n \n+struct SensorFrameInfo {\n+\tRequest *request = nullptr;\n+\tFrameBuffer *statsBuffer = nullptr;\n+\tControlList metadata;\n+\tbool metadataProcessed = false;\n+};\n+\n+struct RequestInfo {\n+\tRequest *request = nullptr;\n+\t/*\n+\t * The estimated sensor sequence for this request. Only reliable when\n+\t * sequenceValid is true\n+\t */\n+\tsize_t sequence = 0;\n+\tbool sequenceValid = false;\n+};\n+\n+struct ParamBufferInfo {\n+\tFrameBuffer *buffer = nullptr;\n+\tsize_t expectedSequence = 0;\n+};\n+\n+struct DewarpBufferInfo {\n+\tFrameBuffer *inputBuffer;\n+\tFrameBuffer *outputBuffer;\n+};\n+\n namespace {\n \n /*\n- * Maximum number of requests that shall be queued into the pipeline to keep\n- * the regulation fast.\n- * \\todo This needs revisiting as soon as buffers got decoupled from requests\n- * and/or a fast path for controls was implemented.\n+ * This many buffers ensures that the pipeline runs smoothly, without frame\n+ * drops.\n */\n-static constexpr unsigned int kRkISP1MaxQueuedRequests = 4;\n+static constexpr unsigned int kRkISP1MinBufferCount = 6;\n \n /*\n- * This many internal buffers (or rather parameter and statistics buffer\n- * pairs) ensures that the pipeline runs smoothly, without frame drops.\n+ * This many internal buffers (params and stats) are needed for smooth operation\n+ * \\todo In high framerate or high cpu load situations it might be necessary to\n+ * increase this number. \\todo: This also relates to max sensor delay and must\n+ * always be >= maxSensor delay\n */\n-static constexpr unsigned int kRkISP1MinBufferCount = 4;\n+static constexpr unsigned int kRkISP1InternalBufferCount = 4;\n+\n+/*\n+ * This many internal image buffers between ISP and dewarper are needed for\n+ * smooth operation.\n+ */\n+static constexpr unsigned int kRkISP1DewarpImageBufferCount = 4;\n \n } /* namespace */\n \n@@ -209,18 +219,20 @@ private:\n \n \tfriend RkISP1CameraData;\n \tfriend RkISP1CameraConfiguration;\n-\tfriend RkISP1Frames;\n \n \tint initLinks(Camera *camera, const RkISP1CameraConfiguration &config);\n \tint createCamera(MediaEntity *sensor);\n-\tvoid tryCompleteRequest(RkISP1FrameInfo *info);\n-\tvoid cancelDewarpRequest(RkISP1FrameInfo *info);\n+\tvoid tryCompleteRequests();\n+\tvoid cancelDewarpRequest(Request *request);\n \tvoid imageBufferReady(FrameBuffer *buffer);\n \tvoid paramBufferReady(FrameBuffer *buffer);\n \tvoid statBufferReady(FrameBuffer *buffer);\n \tvoid dewarpBufferReady(FrameBuffer *buffer);\n \tvoid frameStart(uint32_t sequence);\n \n+\tvoid queueInternalBuffers();\n+\tvoid computeParamBuffers(uint32_t maxSequence);\n+\n \tint allocateBuffers(Camera *camera);\n \tint freeBuffers(Camera *camera);\n \n@@ -243,140 +255,30 @@ private:\n \tstd::vector<std::unique_ptr<FrameBuffer>> mainPathBuffers_;\n \tstd::queue<FrameBuffer *> availableMainPathBuffers_;\n \n+\tbool running_ = false;\n+\n \tstd::vector<std::unique_ptr<FrameBuffer>> paramBuffers_;\n \tstd::vector<std::unique_ptr<FrameBuffer>> statBuffers_;\n \tstd::queue<FrameBuffer *> availableParamBuffers_;\n \tstd::queue<FrameBuffer *> availableStatBuffers_;\n \n+\tstd::deque<RequestInfo> queuedRequests_;\n+\n+\tstd::map<unsigned int, SensorFrameInfo> sensorFrameInfos_;\n+\n+\tstd::deque<DewarpBufferInfo> queuedDewarpBuffers_;\n+\tSequenceSyncHelper paramsSyncHelper_;\n+\tSequenceSyncHelper imageSyncHelper_;\n+\n+\tstd::queue<ParamBufferInfo> computingParamBuffers_;\n+\tstd::queue<ParamBufferInfo> queuedParamBuffers_;\n+\n+\tuint32_t nextParamsSequence_;\n+\tuint32_t nextStatsToProcess_;\n+\n \tCamera *activeCamera_;\n };\n \n-RkISP1Frames::RkISP1Frames(PipelineHandler *pipe)\n-\t: pipe_(static_cast<PipelineHandlerRkISP1 *>(pipe))\n-{\n-}\n-\n-RkISP1FrameInfo *RkISP1Frames::create(const RkISP1CameraData *data, Request *request,\n-\t\t\t\t bool isRaw)\n-{\n-\tunsigned int frame = data->frame_;\n-\n-\tFrameBuffer *paramBuffer = nullptr;\n-\tFrameBuffer *statBuffer = nullptr;\n-\tFrameBuffer *mainPathBuffer = nullptr;\n-\tFrameBuffer *selfPathBuffer = nullptr;\n-\n-\tif (!isRaw) {\n-\t\tif (pipe_->availableParamBuffers_.empty()) {\n-\t\t\tLOG(RkISP1, Error) << \"Parameters buffer underrun\";\n-\t\t\treturn nullptr;\n-\t\t}\n-\n-\t\tif (pipe_->availableStatBuffers_.empty()) {\n-\t\t\tLOG(RkISP1, Error) << \"Statistic buffer underrun\";\n-\t\t\treturn nullptr;\n-\t\t}\n-\n-\t\tparamBuffer = pipe_->availableParamBuffers_.front();\n-\t\tpipe_->availableParamBuffers_.pop();\n-\n-\t\tstatBuffer = pipe_->availableStatBuffers_.front();\n-\t\tpipe_->availableStatBuffers_.pop();\n-\n-\t\tif (data->usesDewarper_) {\n-\t\t\tmainPathBuffer = pipe_->availableMainPathBuffers_.front();\n-\t\t\tpipe_->availableMainPathBuffers_.pop();\n-\t\t}\n-\t}\n-\n-\tif (!mainPathBuffer)\n-\t\tmainPathBuffer = request->findBuffer(&data->mainPathStream_);\n-\tselfPathBuffer = request->findBuffer(&data->selfPathStream_);\n-\n-\tauto [it, inserted] = frameInfo_.try_emplace(frame);\n-\tASSERT(inserted);\n-\n-\tauto &info = it->second;\n-\n-\tinfo.frame = frame;\n-\tinfo.request = request;\n-\tinfo.paramBuffer = paramBuffer;\n-\tinfo.mainPathBuffer = mainPathBuffer;\n-\tinfo.selfPathBuffer = selfPathBuffer;\n-\tinfo.statBuffer = statBuffer;\n-\tinfo.paramDequeued = false;\n-\tinfo.metadataProcessed = false;\n-\n-\treturn &info;\n-}\n-\n-int RkISP1Frames::destroy(unsigned int frame)\n-{\n-\tauto it = frameInfo_.find(frame);\n-\tif (it == frameInfo_.end())\n-\t\treturn -ENOENT;\n-\n-\tauto &info = it->second;\n-\n-\tpipe_->availableParamBuffers_.push(info.paramBuffer);\n-\tpipe_->availableStatBuffers_.push(info.statBuffer);\n-\tpipe_->availableMainPathBuffers_.push(info.mainPathBuffer);\n-\n-\tframeInfo_.erase(it);\n-\n-\treturn 0;\n-}\n-\n-void RkISP1Frames::clear()\n-{\n-\tfor (const auto &[frame, info] : frameInfo_) {\n-\t\tpipe_->availableParamBuffers_.push(info.paramBuffer);\n-\t\tpipe_->availableStatBuffers_.push(info.statBuffer);\n-\t\tpipe_->availableMainPathBuffers_.push(info.mainPathBuffer);\n-\t}\n-\n-\tframeInfo_.clear();\n-}\n-\n-RkISP1FrameInfo *RkISP1Frames::find(unsigned int frame)\n-{\n-\tauto itInfo = frameInfo_.find(frame);\n-\n-\tif (itInfo != frameInfo_.end())\n-\t\treturn &itInfo->second;\n-\n-\tLOG(RkISP1, Fatal) << \"Can't locate info from frame\";\n-\n-\treturn nullptr;\n-}\n-\n-RkISP1FrameInfo *RkISP1Frames::find(FrameBuffer *buffer)\n-{\n-\tfor (auto &[frame, info] : frameInfo_) {\n-\t\tif (info.paramBuffer == buffer ||\n-\t\t info.statBuffer == buffer ||\n-\t\t info.mainPathBuffer == buffer ||\n-\t\t info.selfPathBuffer == buffer)\n-\t\t\treturn &info;\n-\t}\n-\n-\tLOG(RkISP1, Fatal) << \"Can't locate info from buffer\";\n-\n-\treturn nullptr;\n-}\n-\n-RkISP1FrameInfo *RkISP1Frames::find(Request *request)\n-{\n-\tfor (auto &[frame, info] : frameInfo_) {\n-\t\tif (info.request == request)\n-\t\t\treturn &info;\n-\t}\n-\n-\tLOG(RkISP1, Fatal) << \"Can't locate info from request\";\n-\n-\treturn nullptr;\n-}\n-\n PipelineHandlerRkISP1 *RkISP1CameraData::pipe()\n {\n \treturn static_cast<PipelineHandlerRkISP1 *>(Camera::Private::pipe());\n@@ -473,44 +375,95 @@ int RkISP1CameraData::loadTuningFile(const std::string &path)\n void RkISP1CameraData::paramsComputed(unsigned int frame, unsigned int bytesused)\n {\n \tPipelineHandlerRkISP1 *pipe = RkISP1CameraData::pipe();\n-\tRkISP1FrameInfo *info = frameInfo_.find(frame);\n-\tif (!info)\n-\t\treturn;\n+\tParamBufferInfo &pInfo = pipe->computingParamBuffers_.front();\n+\tpipe->computingParamBuffers_.pop();\n \n-\tinfo->paramBuffer->_d()->metadata().planes()[0].bytesused = bytesused;\n+\tASSERT(pInfo.expectedSequence == frame);\n+\tFrameBuffer *buffer = pInfo.buffer;\n \n-\tint ret = pipe->param_->queueBuffer(info->paramBuffer);\n+\tLOG(RkISP1Schedule, Debug) << \"Queue params for \" << frame << \" \" << buffer;\n+\n+\tbuffer->_d()->metadata().planes()[0].bytesused = bytesused;\n+\tint ret = pipe->param_->queueBuffer(buffer);\n \tif (ret < 0) {\n \t\tLOG(RkISP1, Error) << \"Failed to queue parameter buffer: \"\n \t\t\t\t << strerror(-ret);\n+\t\tpipe->availableParamBuffers_.push(buffer);\n \t\treturn;\n \t}\n \n-\tpipe->stat_->queueBuffer(info->statBuffer);\n-\n-\tif (info->mainPathBuffer)\n-\t\tmainPath_->queueBuffer(info->mainPathBuffer);\n-\n-\tif (selfPath_ && info->selfPathBuffer)\n-\t\tselfPath_->queueBuffer(info->selfPathBuffer);\n+\tpipe->queuedParamBuffers_.push({ buffer, frame });\n }\n \n void RkISP1CameraData::setSensorControls(unsigned int frame,\n \t\t\t\t\t const ControlList &sensorControls)\n {\n+\t/* We know delayed controls is prewarmed for frame 0 */\n+\tif (frame == 0)\n+\t\treturn;\n+\n+\tLOG(RkISP1Schedule, Debug) << \"DelayedControls push \" << frame;\n \tdelayedCtrls_->push(frame, sensorControls);\n }\n \n void RkISP1CameraData::metadataReady(unsigned int frame, const ControlList &metadata)\n {\n-\tRkISP1FrameInfo *info = frameInfo_.find(frame);\n-\tif (!info)\n-\t\treturn;\n+\tPipelineHandlerRkISP1 *pipe = RkISP1CameraData::pipe();\n \n-\tinfo->request->_d()->metadata().merge(metadata);\n-\tinfo->metadataProcessed = true;\n+\tLOG(RkISP1Schedule, Debug) << \" metadataReady \" << frame;\n \n-\tpipe()->tryCompleteRequest(info);\n+\tauto &info = pipe->sensorFrameInfos_[frame];\n+\n+\t/*\n+\t * We don't necessarily know the request for that sequence number,\n+\t * as the dequeue of the image buffer might not have happened yet.\n+\t * So we check all known requests and store the metadata otherwise.\n+\t */\n+\tfor (auto &reqInfo : pipe->queuedRequests_) {\n+\t\tif (!reqInfo.sequenceValid) {\n+\t\t\tLOG(RkISP1Schedule, Debug)\n+\t\t\t\t<< \"Need to store metadata for later \" << frame;\n+\t\t\tinfo.metadata = metadata;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (frame > reqInfo.sequence) {\n+\t\t\t/*\n+\t\t\t * We will never get stats for that request. Log an\n+\t\t\t * error and return it.\n+\t\t\t */\n+\t\t\tLOG(RkISP1, Warning)\n+\t\t\t\t<< \"Stats for frame \" << reqInfo.sequence\n+\t\t\t\t<< \" got lost\";\n+\t\t\tauto &info2 = pipe->sensorFrameInfos_[reqInfo.sequence];\n+\t\t\tinfo2.metadataProcessed = true;\n+\t\t\tASSERT(info2.statsBuffer == nullptr);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (frame == reqInfo.sequence) {\n+\t\t\treqInfo.request->_d()->metadata().merge(metadata);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\t/* We should never end up here */\n+\t\tLOG(RkISP1, Error) << \"Request for sequence \" << frame\n+\t\t\t\t << \" is already handled. Metadata was too late\";\n+\n+\t\tbreak;\n+\t}\n+\n+\tinfo.metadataProcessed = true;\n+\t/*\n+\t * info.statsBuffer can be null, if ipa->processStats() was called\n+\t * without a buffer to just fill the metadata.\n+\t */\n+\tif (info.statsBuffer)\n+\t\tpipe->availableStatBuffers_.push(info.statsBuffer);\n+\tinfo.statsBuffer = nullptr;\n+\n+\tpipe->tryCompleteRequests();\n+\tpipe->queueInternalBuffers();\n }\n \n /* -----------------------------------------------------------------------------\n@@ -777,7 +730,7 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()\n */\n \n PipelineHandlerRkISP1::PipelineHandlerRkISP1(CameraManager *manager)\n-\t: PipelineHandler(manager, kRkISP1MaxQueuedRequests), hasSelfPath_(true)\n+\t: PipelineHandler(manager), hasSelfPath_(true)\n {\n }\n \n@@ -1152,18 +1105,19 @@ int PipelineHandlerRkISP1::allocateBuffers(Camera *camera)\n \t} };\n \n \tif (!isRaw_) {\n-\t\tret = param_->allocateBuffers(kRkISP1MinBufferCount, ¶mBuffers_);\n+\t\tret = param_->allocateBuffers(kRkISP1InternalBufferCount, ¶mBuffers_);\n \t\tif (ret < 0)\n \t\t\treturn ret;\n \n-\t\tret = stat_->allocateBuffers(kRkISP1MinBufferCount, &statBuffers_);\n+\t\tret = stat_->allocateBuffers(kRkISP1InternalBufferCount, &statBuffers_);\n \t\tif (ret < 0)\n \t\t\treturn ret;\n \t}\n \n \t/* If the dewarper is being used, allocate internal buffers for ISP. */\n \tif (data->usesDewarper_) {\n-\t\tret = mainPath_.exportBuffers(kRkISP1MinBufferCount, &mainPathBuffers_);\n+\t\tret = mainPath_.exportBuffers(kRkISP1DewarpImageBufferCount,\n+\t\t\t\t\t &mainPathBuffers_);\n \t\tif (ret < 0)\n \t\t\treturn ret;\n \n@@ -1242,6 +1196,12 @@ int PipelineHandlerRkISP1::start(Camera *camera, [[maybe_unused]] const ControlL\n \tif (!!controls)\n \t\tctrls = *controls;\n \n+\tparamsSyncHelper_.reset();\n+\timageSyncHelper_.reset();\n+\tnextParamsSequence_ = 0;\n+\tnextStatsToProcess_ = 0;\n+\tdata->frame_ = 0;\n+\n \tipa::rkisp1::StartResult res;\n \tdata->ipa_->start(ctrls, &res);\n \tif (res.code) {\n@@ -1253,8 +1213,6 @@ int PipelineHandlerRkISP1::start(Camera *camera, [[maybe_unused]] const ControlL\n \tdata->sensor_->setControls(&res.controls);\n \tdata->delayedCtrls_->reset();\n \n-\tdata->frame_ = 0;\n-\n \tif (!isRaw_) {\n \t\tret = param_->streamOn();\n \t\tif (ret) {\n@@ -1301,6 +1259,9 @@ int PipelineHandlerRkISP1::start(Camera *camera, [[maybe_unused]] const ControlL\n \tisp_->setFrameStartEnabled(true);\n \n \tactiveCamera_ = camera;\n+\trunning_ = true;\n+\n+\tqueueInternalBuffers();\n \n \tactions.release();\n \treturn 0;\n@@ -1310,6 +1271,9 @@ void PipelineHandlerRkISP1::stopDevice(Camera *camera)\n {\n \tRkISP1CameraData *data = cameraData(camera);\n \tint ret;\n+\trunning_ = false;\n+\n+\tLOG(RkISP1Schedule, Debug) << \"Stop device\";\n \n \tisp_->setFrameStartEnabled(false);\n \n@@ -1330,46 +1294,154 @@ void PipelineHandlerRkISP1::stopDevice(Camera *camera)\n \t\t\tLOG(RkISP1, Warning)\n \t\t\t\t<< \"Failed to stop parameters for \" << camera->id();\n \n+\t\t/*\n+\t\t * The param buffers are not returned in order, so the queue\n+\t\t * becomes useless.\n+\t\t */\n+\t\tqueuedParamBuffers_ = {};\n+\n \t\tif (data->usesDewarper_)\n \t\t\tdewarper_->stop();\n \t}\n \n-\tASSERT(data->queuedRequests_.empty());\n-\tdata->frameInfo_.clear();\n+\ttryCompleteRequests();\n+\n+\t/* There can still be requests that are either waiting for metadata\n+\t or that contain buffers which were not yet queued at all. */\n+\twhile (!queuedRequests_.empty()) {\n+\t\tRequestInfo &reqInfo = queuedRequests_.front();\n+\t\tcancelRequest(reqInfo.request);\n+\t\tqueuedRequests_.pop_front();\n+\t}\n+\tsensorFrameInfos_.clear();\n+\n+\tASSERT(queuedDewarpBuffers_.empty());\n+\tASSERT(queuedParamBuffers_.empty());\n+\tASSERT(computingParamBuffers_.empty());\n \n \tfreeBuffers(camera);\n \n \tactiveCamera_ = nullptr;\n }\n \n-int PipelineHandlerRkISP1::queueRequestDevice(Camera *camera, Request *request)\n+void PipelineHandlerRkISP1::queueInternalBuffers()\n {\n-\tRkISP1CameraData *data = cameraData(camera);\n+\tif (!running_)\n+\t\treturn;\n \n-\tRkISP1FrameInfo *info = data->frameInfo_.create(data, request, isRaw_);\n-\tif (!info)\n-\t\treturn -ENOENT;\n+\tRkISP1CameraData *data = cameraData(activeCamera_);\n+\n+\twhile (!availableStatBuffers_.empty()) {\n+\t\tFrameBuffer *buf = availableStatBuffers_.front();\n+\t\tavailableStatBuffers_.pop();\n+\t\tdata->pipe()->stat_->queueBuffer(buf);\n+\t}\n+\n+\t/*\n+\t * In case of the dewarper, there is a seperate buffer loop for the main\n+\t * path\n+\t */\n+\twhile (!availableMainPathBuffers_.empty()) {\n+\t\tFrameBuffer *buf = availableMainPathBuffers_.front();\n+\t\tavailableMainPathBuffers_.pop();\n+\n+\t\tLOG(RkISP1Schedule, Debug) << \"Queue mainPath \" << buf;\n+\t\tdata->mainPath_->queueBuffer(buf);\n+\t}\n+}\n+\n+void PipelineHandlerRkISP1::computeParamBuffers(uint32_t maxSequence)\n+{\n+\tRkISP1CameraData *data = cameraData(activeCamera_);\n \n-\tdata->ipa_->queueRequest(data->frame_, request->controls());\n \tif (isRaw_) {\n-\t\tif (info->mainPathBuffer)\n-\t\t\tdata->mainPath_->queueBuffer(info->mainPathBuffer);\n-\n-\t\tif (data->selfPath_ && info->selfPathBuffer)\n-\t\t\tdata->selfPath_->queueBuffer(info->selfPathBuffer);\n-\n \t\t/*\n \t\t * Call computeParams with an empty param buffer to trigger the\n \t\t * setSensorControls signal.\n \t\t */\n-\t\tdata->ipa_->computeParams(data->frame_, 0);\n-\t} else {\n-\t\tdata->ipa_->computeParams(data->frame_,\n-\t\t\t\t\t info->paramBuffer->cookie());\n+\t\tdata->ipa_->computeParams(maxSequence, 0);\n+\t\treturn;\n \t}\n \n+\twhile (nextParamsSequence_ <= maxSequence) {\n+\t\tif (availableParamBuffers_.empty()) {\n+\t\t\tLOG(RkISP1Schedule, Warning)\n+\t\t\t\t<< \"Ran out of parameter buffers\";\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tint correction = paramsSyncHelper_.correction();\n+\t\tif (correction != 0)\n+\t\t\tLOG(RkISP1Schedule, Warning)\n+\t\t\t\t<< \"Correcting params sequence \"\n+\t\t\t\t<< correction;\n+\n+\t\tuint32_t paramsSequence;\n+\t\tif (correction >= 0) {\n+\t\t\tnextParamsSequence_ += correction;\n+\t\t\tparamsSyncHelper_.pushCorrection(correction);\n+\t\t\tparamsSequence = nextParamsSequence_++;\n+\t\t} else {\n+\t\t\t/*\n+\t\t\t * Inject the same sequence multiple times, to correct\n+\t\t\t * for the offset.\n+\t\t\t */\n+\t\t\tparamsSyncHelper_.pushCorrection(-1);\n+\t\t\tparamsSequence = nextParamsSequence_;\n+\t\t}\n+\n+\t\tFrameBuffer *buf = availableParamBuffers_.front();\n+\t\tavailableParamBuffers_.pop();\n+\t\tcomputingParamBuffers_.push({ buf, paramsSequence });\n+\t\tLOG(RkISP1Schedule, Debug) << \"Request params for \" << paramsSequence;\n+\t\tdata->ipa_->computeParams(paramsSequence, buf->cookie());\n+\t}\n+}\n+\n+int PipelineHandlerRkISP1::queueRequestDevice(Camera *camera, Request *request)\n+{\n+\tRkISP1CameraData *data = cameraData(camera);\n+\n+\tRequestInfo info;\n+\tinfo.request = request;\n+\n+\tint correction = imageSyncHelper_.correction();\n+\tif (correction != 0)\n+\t\tLOG(RkISP1Schedule, Debug)\n+\t\t\t<< \"Correcting image sequence \"\n+\t\t\t<< data->frame_ << \" to \" << data->frame_ + correction;\n+\tdata->frame_ += correction;\n+\timageSyncHelper_.pushCorrection(correction);\n+\tinfo.sequence = data->frame_;\n \tdata->frame_++;\n \n+\tLOG(RkISP1Schedule, Debug) << \"Queue request. Request sequence: \"\n+\t\t\t\t << request->sequence()\n+\t\t\t\t << \" estimated sensor frame sequence: \" << info.sequence\n+\t\t\t\t << \" queue size: \" << (queuedRequests_.size() + 1);\n+\n+\tdata->ipa_->queueRequest(info.sequence, request->controls());\n+\n+\t/*\n+\t * When the dewarper is used, the request buffers will be queued in\n+\t * imageBufferReady()\n+\t */\n+\tif (!data->usesDewarper_) {\n+\t\tFrameBuffer *mainPathBuffer = request->findBuffer(&data->mainPathStream_);\n+\t\tFrameBuffer *selfPathBuffer = request->findBuffer(&data->selfPathStream_);\n+\t\tif (mainPathBuffer)\n+\t\t\tdata->mainPath_->queueBuffer(mainPathBuffer);\n+\n+\t\tif (data->selfPath_ && selfPathBuffer)\n+\t\t\tdata->selfPath_->queueBuffer(selfPathBuffer);\n+\t}\n+\n+\tqueuedRequests_.push_back(info);\n+\n+\t/* Kickstart computation of parameters. */\n+\tif (info.sequence < kRkISP1InternalBufferCount)\n+\t\tcomputeParamBuffers(info.sequence);\n+\n \treturn 0;\n }\n \n@@ -1523,8 +1595,11 @@ void PipelineHandlerRkISP1::frameStart(uint32_t sequence)\n \t\treturn;\n \n \tRkISP1CameraData *data = cameraData(activeCamera_);\n+\tLOG(RkISP1Schedule, Debug) << \"frameStart \" << sequence;\n \tuint32_t sequenceToApply = sequence + data->delayedCtrls_->maxDelay();\n \tdata->delayedCtrls_->applyControls(sequenceToApply);\n+\n+\tcomputeParamBuffers(sequenceToApply + 1);\n }\n \n bool PipelineHandlerRkISP1::match(DeviceEnumerator *enumerator)\n@@ -1602,29 +1677,51 @@ bool PipelineHandlerRkISP1::match(DeviceEnumerator *enumerator)\n * Buffer Handling\n */\n \n-void PipelineHandlerRkISP1::tryCompleteRequest(RkISP1FrameInfo *info)\n+void PipelineHandlerRkISP1::tryCompleteRequests()\n {\n-\tRkISP1CameraData *data = cameraData(activeCamera_);\n-\tRequest *request = info->request;\n+\tstd::optional<size_t> lastDeletedSequence;\n \n-\tif (request->hasPendingBuffers())\n+\t/* Complete finished requests */\n+\twhile (!queuedRequests_.empty()) {\n+\t\tRequestInfo info = queuedRequests_.front();\n+\n+\t\tif (info.request->hasPendingBuffers())\n+\t\t\tbreak;\n+\n+\t\tif (!info.sequenceValid)\n+\t\t\tbreak;\n+\n+\t\tif (!sensorFrameInfos_[info.sequence].metadataProcessed)\n+\t\t\tbreak;\n+\n+\t\tqueuedRequests_.pop_front();\n+\n+\t\tLOG(RkISP1Schedule, Debug) << \"Complete request \" << info.sequence;\n+\t\tcompleteRequest(info.request);\n+\n+\t\tsensorFrameInfos_[info.sequence].request = nullptr;\n+\t\tlastDeletedSequence = info.sequence;\n+\t}\n+\n+\tif (!lastDeletedSequence.has_value())\n \t\treturn;\n \n-\tif (!info->metadataProcessed)\n-\t\treturn;\n+\t/* Drop all outdated sensor frame infos. */\n+\twhile (!sensorFrameInfos_.empty()) {\n+\t\tauto iter = sensorFrameInfos_.begin();\n+\t\tif (iter->first > lastDeletedSequence.value())\n+\t\t\tbreak;\n \n-\tif (!isRaw_ && !info->paramDequeued)\n-\t\treturn;\n+\t\tASSERT(iter->second.request == nullptr);\n+\t\tASSERT(iter->second.statsBuffer == nullptr);\n \n-\tdata->frameInfo_.destroy(info->frame);\n-\n-\tcompleteRequest(request);\n+\t\tsensorFrameInfos_.erase(iter);\n+\t}\n }\n \n-void PipelineHandlerRkISP1::cancelDewarpRequest(RkISP1FrameInfo *info)\n+void PipelineHandlerRkISP1::cancelDewarpRequest(Request *request)\n {\n \tRkISP1CameraData *data = cameraData(activeCamera_);\n-\tRequest *request = info->request;\n \t/*\n \t * i.MX8MP is the only known platform with dewarper. It has\n \t * no self path. Hence, only main path buffer completion is\n@@ -1643,67 +1740,129 @@ void PipelineHandlerRkISP1::cancelDewarpRequest(RkISP1FrameInfo *info)\n \t\t}\n \t}\n \n-\ttryCompleteRequest(info);\n+\ttryCompleteRequests();\n }\n \n void PipelineHandlerRkISP1::imageBufferReady(FrameBuffer *buffer)\n {\n \tASSERT(activeCamera_);\n \tRkISP1CameraData *data = cameraData(activeCamera_);\n-\n-\tRkISP1FrameInfo *info = data->frameInfo_.find(buffer);\n-\tif (!info)\n-\t\treturn;\n-\n \tconst FrameMetadata &metadata = buffer->metadata();\n-\tRequest *request = info->request;\n+\tRequestInfo *reqInfo = nullptr;\n+\n+\t/*\n+\t * When the dewarper is used, the buffer is not yet tied to a request,\n+\t * so find the first request without a valid sequence. Otherwise find\n+\t * the request for that buffer. This is not necessarily the same,\n+\t * because after streamoff the buffers are returned in arbitrary order.\n+\t */\n+\tfor (auto &info : queuedRequests_) {\n+\t\tif (data->usesDewarper_) {\n+\t\t\tif (!info.sequenceValid) {\n+\t\t\t\treqInfo = &info;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t} else {\n+\t\t\tif (info.request == buffer->request()) {\n+\t\t\t\treqInfo = &info;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (!reqInfo) {\n+\t\tif (data->usesDewarper_) {\n+\t\t\tLOG(RkISP1Schedule, Info)\n+\t\t\t\t<< \"Image buffer ready, but no corresponding request\";\n+\t\t\tavailableMainPathBuffers_.push(buffer);\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tLOG(RkISP1Schedule, Fatal)\n+\t\t\t<< \"Image buffer ready, but no corresponding request\";\n+\t}\n+\n+\tRequest *request = reqInfo->request;\n+\n+\tLOG(RkISP1Schedule, Debug) << \"Image buffer ready: \" << buffer\n+\t\t\t\t << \" Expected sequence: \" << reqInfo->sequence\n+\t\t\t\t << \" got: \" << metadata.sequence;\n+\n+\tuint32_t sequence = metadata.sequence;\n+\n+\t/*\n+\t * If the frame was cancelled, the metadata sequnce is usually wrong and\n+\t * we assume that our guess was right.\n+\t */\n+\tif (metadata.status == FrameMetadata::FrameCancelled)\n+\t\tsequence = reqInfo->sequence;\n+\n+\t/* We now know the buffer sequence that belongs to this request */\n+\tint droppedFrames = imageSyncHelper_.gotFrame(reqInfo->sequence, sequence);\n+\tif (droppedFrames != 0)\n+\t\tLOG(RkISP1Schedule, Warning)\n+\t\t\t<< \"Frame \" << reqInfo->sequence << \": Dropped \"\n+\t\t\t<< droppedFrames << \" frames\";\n+\n+\treqInfo->sequence = sequence;\n+\treqInfo->sequenceValid = true;\n+\n+\tif (sensorFrameInfos_[sequence].metadataProcessed) {\n+\t\tLOG(RkISP1Schedule, Debug)\n+\t\t\t<< \"Apply stored metadata \" << sequence;\n+\t\trequest->_d()->metadata().merge(sensorFrameInfos_[sequence].metadata);\n+\t}\n \n \tif (metadata.status != FrameMetadata::FrameCancelled) {\n \t\t/*\n \t\t * Record the sensor's timestamp in the request metadata.\n \t\t *\n-\t\t * \\todo The sensor timestamp should be better estimated by connecting\n-\t\t * to the V4L2Device::frameStart signal.\n+\t\t * \\todo The sensor timestamp should be better estimated by\n+\t\t * connecting to the V4L2Device::frameStart signal.\n \t\t */\n \t\trequest->_d()->metadata().set(controls::SensorTimestamp,\n \t\t\t\t\t metadata.timestamp);\n \n+\t\t/* In raw mode call processStats() to fill the metadata */\n \t\tif (isRaw_) {\n \t\t\tconst ControlList &ctrls =\n-\t\t\t\tdata->delayedCtrls_->get(metadata.sequence);\n-\t\t\tdata->ipa_->processStats(info->frame, 0, ctrls);\n+\t\t\t\tdata->delayedCtrls_->get(sequence);\n+\t\t\tdata->ipa_->processStats(sequence, 0, ctrls);\n \t\t}\n \t} else {\n-\t\tif (isRaw_)\n-\t\t\tinfo->metadataProcessed = true;\n+\t\t/* No need to block waiting for metedata on that frame. */\n+\t\tsensorFrameInfos_[sequence].metadataProcessed = true;\n \t}\n \n \tif (!data->usesDewarper_) {\n-\t\tcompleteBuffer(request, buffer);\n-\t\ttryCompleteRequest(info);\n+\t\tcompleteBuffer(reqInfo->request, buffer);\n+\t\ttryCompleteRequests();\n \n \t\treturn;\n \t}\n \n-\t/* Do not queue cancelled frames to dewarper. */\n+\t/* Do not queue cancelled frames to the dewarper. */\n \tif (metadata.status == FrameMetadata::FrameCancelled) {\n-\t\tcancelDewarpRequest(info);\n+\t\tcancelDewarpRequest(reqInfo->request);\n \t\treturn;\n \t}\n \n \tdewarper_->setControls(&data->mainPathStream_, request->controls());\n \n \t/*\n-\t * Queue input and output buffers to the dewarper. The output\n-\t * buffers for the dewarper are the buffers of the request, supplied\n-\t * by the application.\n+\t * Queue input and output buffers to the dewarper. The output buffers\n+\t * for the dewarper are the buffers of the request, supplied by the\n+\t * application.\n \t */\n+\tDewarpBufferInfo dewarpInfo{ buffer, reqInfo->request->findBuffer(&data->mainPathStream_) };\n+\tqueuedDewarpBuffers_.push_back(dewarpInfo);\n+\tLOG(RkISP1Schedule, Debug) << \"Queue dewarper \" << dewarpInfo.inputBuffer\n+\t\t\t\t << \" \" << dewarpInfo.outputBuffer;\n \tint ret = dewarper_->queueBuffers(buffer, request->buffers());\n \tif (ret < 0) {\n \t\tLOG(RkISP1, Error) << \"Failed to queue buffers to dewarper: -\"\n \t\t\t\t << strerror(-ret);\n \n-\t\tcancelDewarpRequest(info);\n+\t\tcancelDewarpRequest(reqInfo->request);\n \n \t\treturn;\n \t}\n@@ -1713,29 +1872,59 @@ void PipelineHandlerRkISP1::imageBufferReady(FrameBuffer *buffer)\n \n void PipelineHandlerRkISP1::dewarpBufferReady(FrameBuffer *buffer)\n {\n-\tASSERT(activeCamera_);\n-\tRkISP1CameraData *data = cameraData(activeCamera_);\n \tRequest *request = buffer->request();\n+\tconst FrameMetadata &metadata = buffer->metadata();\n \n-\tRkISP1FrameInfo *info = data->frameInfo_.find(buffer->request());\n-\tif (!info)\n-\t\treturn;\n+\t/*\n+\t * After stopping the dewarper, the buffers are returned out of order.\n+\t * Search the list for the corresponding info and handle it. In regular\n+\t * operation it will always be the first entry.\n+\t */\n+\tfor (DewarpBufferInfo &dwInfo : queuedDewarpBuffers_) {\n+\t\tif (dwInfo.outputBuffer != buffer)\n+\t\t\tcontinue;\n \n-\tcompleteBuffer(request, buffer);\n-\ttryCompleteRequest(info);\n+\t\tavailableMainPathBuffers_.push(dwInfo.inputBuffer);\n+\t\tdwInfo.inputBuffer = nullptr;\n+\t\tdwInfo.outputBuffer = nullptr;\n+\n+\t\tif (metadata.status == FrameMetadata::FrameCancelled)\n+\t\t\tbuffer->_d()->cancel();\n+\n+\t\tcompleteBuffer(request, buffer);\n+\t}\n+\n+\twhile (!queuedDewarpBuffers_.empty() &&\n+\t queuedDewarpBuffers_.front().inputBuffer == nullptr)\n+\t\tqueuedDewarpBuffers_.pop_front();\n+\n+\ttryCompleteRequests();\n+\tqueueInternalBuffers();\n }\n \n void PipelineHandlerRkISP1::paramBufferReady(FrameBuffer *buffer)\n {\n-\tASSERT(activeCamera_);\n-\tRkISP1CameraData *data = cameraData(activeCamera_);\n+\tLOG(RkISP1Schedule, Debug) << \"Param buffer ready \" << buffer;\n \n-\tRkISP1FrameInfo *info = data->frameInfo_.find(buffer);\n-\tif (!info)\n+\t/* After stream off, the buffers are returned out of order, so\n+\t * we don't care about the rest.\n+\t */\n+\tif (!running_) {\n+\t\tavailableParamBuffers_.push(buffer);\n \t\treturn;\n+\t}\n \n-\tinfo->paramDequeued = true;\n-\ttryCompleteRequest(info);\n+\tParamBufferInfo pInfo = queuedParamBuffers_.front();\n+\tqueuedParamBuffers_.pop();\n+\n+\tASSERT(pInfo.buffer == buffer);\n+\n+\tsize_t metaSequence = buffer->metadata().sequence;\n+\tLOG(RkISP1Schedule, Debug) << \"Params buffer ready \"\n+\t\t\t\t << \" Expected: \" << pInfo.expectedSequence\n+\t\t\t\t << \" got: \" << metaSequence;\n+\tparamsSyncHelper_.gotFrame(pInfo.expectedSequence, metaSequence);\n+\tavailableParamBuffers_.push(buffer);\n }\n \n void PipelineHandlerRkISP1::statBufferReady(FrameBuffer *buffer)\n@@ -1743,21 +1932,47 @@ void PipelineHandlerRkISP1::statBufferReady(FrameBuffer *buffer)\n \tASSERT(activeCamera_);\n \tRkISP1CameraData *data = cameraData(activeCamera_);\n \n-\tRkISP1FrameInfo *info = data->frameInfo_.find(buffer);\n-\tif (!info)\n-\t\treturn;\n+\tsize_t sequence = buffer->metadata().sequence;\n \n \tif (buffer->metadata().status == FrameMetadata::FrameCancelled) {\n-\t\tinfo->metadataProcessed = true;\n-\t\ttryCompleteRequest(info);\n+\t\tLOG(RkISP1Schedule, Warning) << \"Stats cancelled \" << sequence;\n+\t\t/*\n+\t\t * We can't assume that the sequence of the stat buffer is valid,\n+\t\t * so there is nothing left to do.\n+\t\t */\n+\t\tavailableStatBuffers_.push(buffer);\n \t\treturn;\n \t}\n \n-\tif (data->frame_ <= buffer->metadata().sequence)\n-\t\tdata->frame_ = buffer->metadata().sequence + 1;\n+\tLOG(RkISP1Schedule, Debug) << \"Stats ready \" << sequence;\n \n-\tdata->ipa_->processStats(info->frame, info->statBuffer->cookie(),\n-\t\t\t\t data->delayedCtrls_->get(buffer->metadata().sequence));\n+\tif (nextStatsToProcess_ != sequence)\n+\t\tLOG(RkISP1Schedule, Warning) << \"Stats sequence out of sync.\"\n+\t\t\t\t\t << \" Expected: \" << nextStatsToProcess_\n+\t\t\t\t\t << \" got: \" << sequence;\n+\n+\tif (nextStatsToProcess_ > sequence) {\n+\t\tLOG(RkISP1Schedule, Warning) << \"Stats were too late. Ignored\";\n+\t\tavailableStatBuffers_.push(buffer);\n+\t\treturn;\n+\t}\n+\n+\t/* Send empty stats to ensure metadata gets created*/\n+\twhile (nextStatsToProcess_ < sequence) {\n+\t\tLOG(RkISP1Schedule, Warning) << \"Send empty stats to fill metadata\";\n+\t\tdata->ipa_->processStats(nextStatsToProcess_, 0,\n+\t\t\t\t\t data->delayedCtrls_->get(nextStatsToProcess_));\n+\n+\t\tnextStatsToProcess_++;\n+\t}\n+\n+\tnextStatsToProcess_++;\n+\n+\tsensorFrameInfos_[sequence].statsBuffer = buffer;\n+\n+\tLOG(RkISP1Schedule, Debug) << \"Process stats \" << sequence;\n+\tdata->ipa_->processStats(sequence, buffer->cookie(),\n+\t\t\t\t data->delayedCtrls_->get(sequence));\n }\n \n REGISTER_PIPELINE_HANDLER(PipelineHandlerRkISP1, \"rkisp1\")\n", "prefixes": [ "v2", "25/32" ] }