Show a patch.

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

{
    "id": 10626,
    "url": "https://patchwork.libcamera.org/api/patches/10626/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/10626/",
    "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": "<20201209102630.5562-3-naush@raspberrypi.com>",
    "date": "2020-12-09T10:26:29",
    "name": "[libcamera-devel,v4,2/3] libcamera: raspberrypi: Add control of sensor vblanking",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "5fb0a367c2d65300eff0779af78e878ebacd7918",
    "submitter": {
        "id": 34,
        "url": "https://patchwork.libcamera.org/api/people/34/?format=api",
        "name": "Naushir Patuck",
        "email": "naush@raspberrypi.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/10626/mbox/",
    "series": [
        {
            "id": 1519,
            "url": "https://patchwork.libcamera.org/api/series/1519/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=1519",
            "date": "2020-12-09T10:26:27",
            "name": "raspberrypi: FPS control",
            "version": 4,
            "mbox": "https://patchwork.libcamera.org/series/1519/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/10626/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/10626/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 DDFD5BD80A\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed,  9 Dec 2020 10:26:41 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id A7BD6635D3;\n\tWed,  9 Dec 2020 11:26:41 +0100 (CET)",
            "from mail-wr1-x42b.google.com (mail-wr1-x42b.google.com\n\t[IPv6:2a00:1450:4864:20::42b])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id DB495635C4\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed,  9 Dec 2020 11:26:39 +0100 (CET)",
            "by mail-wr1-x42b.google.com with SMTP id t16so1146441wra.3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 09 Dec 2020 02:26:39 -0800 (PST)",
            "from naushir-VirtualBox.pitowers.org\n\t([2a00:1098:3142:14:a00:27ff:fe4d:f6a2])\n\tby smtp.gmail.com with ESMTPSA id\n\tq73sm2636035wme.44.2020.12.09.02.26.38\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tWed, 09 Dec 2020 02:26:38 -0800 (PST)"
        ],
        "Authentication-Results": "lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n\tunprotected) header.d=raspberrypi.com header.i=@raspberrypi.com\n\theader.b=\"VB/vU+G0\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google;\n\th=from:to:cc:subject:date:message-id:in-reply-to:references\n\t:mime-version:content-transfer-encoding;\n\tbh=HXzp59A82QAIvyf2nqqzumqzCvKFLHky6q7smUIK3Iw=;\n\tb=VB/vU+G0UF5FfUtSWZe+pe1CeliZNxc7mAwhlCn8StaYVkR548zab4FEhMDrXwZdqh\n\tE7NRXbd56VVL/v+iJtAYEKkI/0oZjMqR0BHTjD5VVJ8ddJDzCWJ1KqIAJhlKmt8tj1kz\n\twL8VoBXVucBq2J6wuL8349mMEcCoziBZLin/jwLMtcm1YvwUtcEaCDSdhFA2lwTrElK3\n\tMdbIX1SPqatjIXiGG7vRK+dvHkSnY5bMLPWWe2uNKoouPamleIgPEVyPc9kdXuM0X+FY\n\tJ1SihNAi7506veETVVqFigeoN4faAZLU3PlLitLKSyfBRB4UiokEofal8hGUyCBSUhv1\n\tUg9A==",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20161025;\n\th=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to\n\t:references:mime-version:content-transfer-encoding;\n\tbh=HXzp59A82QAIvyf2nqqzumqzCvKFLHky6q7smUIK3Iw=;\n\tb=STJuQi/9qCE/MvY0HEIkZ6LYzQ9H+U28Pnbzsn2KBTi7yPtKue/fclRjeJju+GhtbK\n\ttgRu1MxRhzEIVf0xHUuTrskfoltJccBKY/oXlpVZ4+/6UYG8Vwdm56/Phu2D1UOK0BB2\n\tZqyKZrXWxBeVNcLJHmRJtQt9F2KEYjgtkuLfRRwPUbXvPvibJ8mkRsOSMeWbn3ziZMmf\n\ttzs5jz8aq2+KpwDzq27rH1Ip8KNnHDY6Dg7xucP4ZXpIhqWT6uKAa3nNZZjnsojzwfLY\n\tL9+v97eMTQPJ2VfM4yY4SCqCFmAQbEFu7w6RX+732GdQ1GSiNAfUhrDXPH+eG81Ox7h1\n\thn6g==",
        "X-Gm-Message-State": "AOAM531uaLL6yaxrcLA1vMjdkmi2Qixs/ysRc34xsCCY5okH8m2srSQH\n\tTWhTWI7E2NU06ny4R+Y0mZ0y1bFFBLISYA==",
        "X-Google-Smtp-Source": "ABdhPJx0cSqKG6+t7PZTfiJxU/xzh6vKUvghu9R99W4XUPQHLpRRR/Ny2rtKDNjQfW/pXyXkjnTqZQ==",
        "X-Received": "by 2002:adf:fd41:: with SMTP id h1mr710775wrs.284.1607509599073; \n\tWed, 09 Dec 2020 02:26:39 -0800 (PST)",
        "From": "Naushir Patuck <naush@raspberrypi.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Date": "Wed,  9 Dec 2020 10:26:29 +0000",
        "Message-Id": "<20201209102630.5562-3-naush@raspberrypi.com>",
        "X-Mailer": "git-send-email 2.25.1",
        "In-Reply-To": "<20201209102630.5562-1-naush@raspberrypi.com>",
        "References": "<20201209102630.5562-1-naush@raspberrypi.com>",
        "MIME-Version": "1.0",
        "Subject": "[libcamera-devel] [PATCH v4 2/3] libcamera: raspberrypi: Add\n\tcontrol of sensor vblanking",
        "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>",
        "Content-Type": "text/plain; charset=\"us-ascii\"",
        "Content-Transfer-Encoding": "7bit",
        "Errors-To": "libcamera-devel-bounces@lists.libcamera.org",
        "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"
    },
    "content": "Add support for setting V4L2_CID_VBLANK appropriately when setting\nV4L2_CID_EXPOSURE. This will allow adaptive framerates during\nviewfinder use cases (e.g. when the exposure time goes above 33ms, we\ncan reduce the framerate to lower than 30fps).\n\nThe minimum and maximum frame durations are provided via libcamera\ncontrols, and will prioritise exposure time limits over any AGC request.\n\nV4L2_CID_VBLANK is controlled through the staggered writer, just like\nthe exposure and gain controls.\n\nSigned-off-by: Naushir Patuck <naush@raspberrypi.com>\n---\n include/libcamera/ipa/raspberrypi.h           |  1 +\n src/ipa/raspberrypi/cam_helper.cpp            | 37 ++++++++++++++++-\n src/ipa/raspberrypi/cam_helper.hpp            | 15 ++++++-\n src/ipa/raspberrypi/cam_helper_imx219.cpp     | 13 +++++-\n src/ipa/raspberrypi/cam_helper_imx477.cpp     | 11 ++++-\n src/ipa/raspberrypi/cam_helper_ov5647.cpp     | 11 ++++-\n src/ipa/raspberrypi/raspberrypi.cpp           | 41 ++++++++++++++++---\n .../pipeline/raspberrypi/raspberrypi.cpp      |  3 +-\n 8 files changed, 119 insertions(+), 13 deletions(-)",
    "diff": "diff --git a/include/libcamera/ipa/raspberrypi.h b/include/libcamera/ipa/raspberrypi.h\nindex 01fe5abc..160ca681 100644\n--- a/include/libcamera/ipa/raspberrypi.h\n+++ b/include/libcamera/ipa/raspberrypi.h\n@@ -65,6 +65,7 @@ static const ControlInfoMap Controls = {\n \t{ &controls::Sharpness, ControlInfo(0.0f, 16.0f, 1.0f) },\n \t{ &controls::ColourCorrectionMatrix, ControlInfo(-16.0f, 16.0f) },\n \t{ &controls::ScalerCrop, ControlInfo(Rectangle{}, Rectangle(65535, 65535, 65535, 65535), Rectangle{}) },\n+\t{ &controls::FrameDurations, ControlInfo(1.0e3f, 1.0e9f) },\n };\n \n } /* namespace RPi */\ndiff --git a/src/ipa/raspberrypi/cam_helper.cpp b/src/ipa/raspberrypi/cam_helper.cpp\nindex c8ac3232..03da127f 100644\n--- a/src/ipa/raspberrypi/cam_helper.cpp\n+++ b/src/ipa/raspberrypi/cam_helper.cpp\n@@ -34,8 +34,10 @@ CamHelper *CamHelper::Create(std::string const &cam_name)\n \treturn nullptr;\n }\n \n-CamHelper::CamHelper(MdParser *parser)\n-\t: parser_(parser), initialized_(false)\n+CamHelper::CamHelper(MdParser *parser, unsigned int maxFrameLength,\n+\t\t     unsigned int frameIntegrationDiff)\n+\t: parser_(parser), initialized_(false), maxFrameLength_(maxFrameLength),\n+\t  frameIntegrationDiff_(frameIntegrationDiff)\n {\n }\n \n@@ -56,6 +58,37 @@ double CamHelper::Exposure(uint32_t exposure_lines) const\n \treturn exposure_lines * mode_.line_length / 1000.0;\n }\n \n+uint32_t CamHelper::GetVBlanking(double &exposure, double minFrameDuration,\n+\t\t\t\t double maxFrameDuration) const\n+{\n+\tuint32_t frameLengthMin, frameLengthMax, vblank;\n+\tuint32_t exposureLines = ExposureLines(exposure);\n+\n+\tassert(initialized_);\n+\n+\t/*\n+\t * Clip frame length by the frame duration range and the maximum allowable\n+\t * value in the sensor, given by maxFrameLength_.\n+\t */\n+\tframeLengthMin = std::clamp<uint32_t>(1e3 * minFrameDuration / mode_.line_length,\n+\t\t\t\t\t      mode_.height, maxFrameLength_);\n+\tframeLengthMax = std::clamp<uint32_t>(1e3 * maxFrameDuration / mode_.line_length,\n+\t\t\t\t\t      mode_.height, maxFrameLength_);\n+\t/*\n+\t * Limit the exposure to the maximum frame duration requested, and\n+\t * re-calculate if it has been clipped.\n+\t */\n+\texposureLines = std::min(frameLengthMax - frameIntegrationDiff_, exposureLines);\n+\texposure = Exposure(exposureLines);\n+\n+\t/* Limit the vblank to the range allowed by the frame length limits. */\n+\tvblank = std::max<uint32_t>(exposureLines + frameIntegrationDiff_, mode_.height);\n+\tvblank = std::clamp(vblank - mode_.height,\n+\t\t\t    frameLengthMin - mode_.height, frameLengthMax - mode_.height);\n+\n+\treturn vblank;\n+}\n+\n void CamHelper::SetCameraMode(const CameraMode &mode)\n {\n \tmode_ = mode;\ndiff --git a/src/ipa/raspberrypi/cam_helper.hpp b/src/ipa/raspberrypi/cam_helper.hpp\nindex 044c2866..b1739a57 100644\n--- a/src/ipa/raspberrypi/cam_helper.hpp\n+++ b/src/ipa/raspberrypi/cam_helper.hpp\n@@ -62,12 +62,15 @@ class CamHelper\n {\n public:\n \tstatic CamHelper *Create(std::string const &cam_name);\n-\tCamHelper(MdParser *parser);\n+\tCamHelper(MdParser *parser, unsigned int maxFrameLength,\n+\t\t  unsigned int frameIntegrationDiff);\n \tvirtual ~CamHelper();\n \tvoid SetCameraMode(const CameraMode &mode);\n \tMdParser &Parser() const { return *parser_; }\n \tuint32_t ExposureLines(double exposure_us) const;\n \tdouble Exposure(uint32_t exposure_lines) const; // in us\n+\tvirtual uint32_t GetVBlanking(double &exposure_us, double minFrameDuration,\n+\t\t\t\t      double maxFrameDuration) const;\n \tvirtual uint32_t GainCode(double gain) const = 0;\n \tvirtual double Gain(uint32_t gain_code) const = 0;\n \tvirtual void GetDelays(int &exposure_delay, int &gain_delay) const;\n@@ -76,10 +79,20 @@ public:\n \tvirtual unsigned int HideFramesModeSwitch() const;\n \tvirtual unsigned int MistrustFramesStartup() const;\n \tvirtual unsigned int MistrustFramesModeSwitch() const;\n+\n protected:\n \tMdParser *parser_;\n \tCameraMode mode_;\n+\n+private:\n \tbool initialized_;\n+\t/* Largest possible frame length, in units of lines. */\n+\tunsigned int maxFrameLength_;\n+\t/*\n+\t * Smallest difference between the frame length and integration time,\n+\t * in units of lines.\n+\t */\n+\tunsigned int frameIntegrationDiff_;\n };\n \n // This is for registering camera helpers with the system, so that the\ndiff --git a/src/ipa/raspberrypi/cam_helper_imx219.cpp b/src/ipa/raspberrypi/cam_helper_imx219.cpp\nindex db8ab879..8688ec09 100644\n--- a/src/ipa/raspberrypi/cam_helper_imx219.cpp\n+++ b/src/ipa/raspberrypi/cam_helper_imx219.cpp\n@@ -49,13 +49,22 @@ public:\n \tdouble Gain(uint32_t gain_code) const override;\n \tunsigned int MistrustFramesModeSwitch() const override;\n \tbool SensorEmbeddedDataPresent() const override;\n+\n+private:\n+\t/*\n+\t * Smallest difference between the frame length and integration time,\n+\t * in units of lines.\n+\t */\n+\tstatic constexpr int frameIntegrationDiff = 4;\n+\t/* Largest possible frame length, in units of lines. */\n+\tstatic constexpr int maxFrameLength = 0xffff;\n };\n \n CamHelperImx219::CamHelperImx219()\n #if ENABLE_EMBEDDED_DATA\n-\t: CamHelper(new MdParserImx219())\n+\t: CamHelper(new MdParserImx219(), maxFrameLength, frameIntegrationDiff)\n #else\n-\t: CamHelper(new MdParserRPi())\n+\t: CamHelper(new MdParserRPi(), maxFrameLength, frameIntegrationDiff)\n #endif\n {\n }\ndiff --git a/src/ipa/raspberrypi/cam_helper_imx477.cpp b/src/ipa/raspberrypi/cam_helper_imx477.cpp\nindex 0e896ac7..53961310 100644\n--- a/src/ipa/raspberrypi/cam_helper_imx477.cpp\n+++ b/src/ipa/raspberrypi/cam_helper_imx477.cpp\n@@ -38,10 +38,19 @@ public:\n \tuint32_t GainCode(double gain) const override;\n \tdouble Gain(uint32_t gain_code) const override;\n \tbool SensorEmbeddedDataPresent() const override;\n+\n+private:\n+\t/*\n+\t * Smallest difference between the frame length and integration time,\n+\t * in units of lines.\n+\t */\n+\tstatic constexpr int frameIntegrationDiff = 22;\n+\t/* Largest possible frame length, in units of lines. */\n+\tstatic constexpr int maxFrameLength = 0xffdc;\n };\n \n CamHelperImx477::CamHelperImx477()\n-\t: CamHelper(new MdParserImx477())\n+\t: CamHelper(new MdParserImx477(), maxFrameLength, frameIntegrationDiff)\n {\n }\n \ndiff --git a/src/ipa/raspberrypi/cam_helper_ov5647.cpp b/src/ipa/raspberrypi/cam_helper_ov5647.cpp\nindex dc5d8275..7630c127 100644\n--- a/src/ipa/raspberrypi/cam_helper_ov5647.cpp\n+++ b/src/ipa/raspberrypi/cam_helper_ov5647.cpp\n@@ -22,6 +22,15 @@ public:\n \tunsigned int HideFramesModeSwitch() const override;\n \tunsigned int MistrustFramesStartup() const override;\n \tunsigned int MistrustFramesModeSwitch() const override;\n+\n+private:\n+\t/*\n+\t * Smallest difference between the frame length and integration time,\n+\t * in units of lines.\n+\t */\n+\tstatic constexpr int frameIntegrationDiff = 4;\n+\t/* Largest possible frame length, in units of lines. */\n+\tstatic constexpr int maxFrameLength = 0xffff;\n };\n \n /*\n@@ -30,7 +39,7 @@ public:\n  */\n \n CamHelperOv5647::CamHelperOv5647()\n-\t: CamHelper(new MdParserRPi())\n+\t: CamHelper(new MdParserRPi(), maxFrameLength, frameIntegrationDiff)\n {\n }\n \ndiff --git a/src/ipa/raspberrypi/raspberrypi.cpp b/src/ipa/raspberrypi/raspberrypi.cpp\nindex 60cfdc27..4e7c2b94 100644\n--- a/src/ipa/raspberrypi/raspberrypi.cpp\n+++ b/src/ipa/raspberrypi/raspberrypi.cpp\n@@ -58,6 +58,8 @@ namespace libcamera {\n /* Configure the sensor with these values initially. */\n constexpr double DefaultAnalogueGain = 1.0;\n constexpr unsigned int DefaultExposureTime = 20000;\n+constexpr double defaultMinFrameDuration = 1e6 / 30.0;\n+constexpr double defaultMaxFrameDuration = 1e6 / 0.01;\n \n LOG_DEFINE_CATEGORY(IPARPI)\n \n@@ -145,6 +147,9 @@ private:\n \t/* LS table allocation passed in from the pipeline handler. */\n \tFileDescriptor lsTableHandle_;\n \tvoid *lsTable_;\n+\n+\t/* Frame duration (1/fps) given in microseconds. */\n+\tdouble minFrameDuration_, maxFrameDuration_;\n };\n \n int IPARPi::init(const IPASettings &settings)\n@@ -266,7 +271,8 @@ void IPARPi::configure(const CameraSensorInfo &sensorInfo,\n \t\tsensorMetadata = helper_->SensorEmbeddedDataPresent();\n \n \t\tresult->data.push_back(gainDelay);\n-\t\tresult->data.push_back(exposureDelay);\n+\t\tresult->data.push_back(exposureDelay); /* FOR EXPOSURE ctrl */\n+\t\tresult->data.push_back(exposureDelay); /* For VBLANK ctrl */\n \t\tresult->data.push_back(sensorMetadata);\n \n \t\tresult->operation |= RPi::IPA_CONFIG_STAGGERED_WRITE;\n@@ -335,6 +341,8 @@ void IPARPi::configure(const CameraSensorInfo &sensorInfo,\n \t\tAgcStatus agcStatus;\n \t\tagcStatus.shutter_time = DefaultExposureTime;\n \t\tagcStatus.analogue_gain = DefaultAnalogueGain;\n+\t\tminFrameDuration_ = defaultMinFrameDuration;\n+\t\tmaxFrameDuration_ = defaultMaxFrameDuration;\n \t\tapplyAGC(&agcStatus, ctrls);\n \n \t\tresult->controls.emplace_back(ctrls);\n@@ -712,6 +720,17 @@ void IPARPi::queueRequest(const ControlList &controls)\n \t\t\tbreak;\n \t\t}\n \n+\t\tcase controls::FRAME_DURATIONS: {\n+\t\t\tauto frameDurations = ctrl.second.get<Span<const float>>();\n+\n+\t\t\t/* This will be applied once AGC recalculations occur. */\n+\t\t\tminFrameDuration_ = frameDurations[0];\n+\t\t\tmaxFrameDuration_ = frameDurations[1];\n+\t\t\tlibcameraMetadata_.set(controls::FrameDurations,\n+\t\t\t\t\t       { frameDurations[0], frameDurations[1] });\n+\t\t\tbreak;\n+\t\t}\n+\n \t\tdefault:\n \t\t\tLOG(IPARPI, Warning)\n \t\t\t\t<< \"Ctrl \" << controls::controls.at(ctrl.first)->name()\n@@ -882,7 +901,12 @@ void IPARPi::applyAWB(const struct AwbStatus *awbStatus, ControlList &ctrls)\n void IPARPi::applyAGC(const struct AgcStatus *agcStatus, ControlList &ctrls)\n {\n \tint32_t gainCode = helper_->GainCode(agcStatus->analogue_gain);\n-\tint32_t exposureLines = helper_->ExposureLines(agcStatus->shutter_time);\n+\n+\t/* GetVBlanking might clip exposure time to the fps limits. */\n+\tdouble exposure = agcStatus->shutter_time;\n+\tint32_t vblanking = helper_->GetVBlanking(exposure, minFrameDuration_,\n+\t\t\t\t\t\t  maxFrameDuration_);\n+\tint32_t exposureLines = helper_->ExposureLines(exposure);\n \n \tif (unicamCtrls_.find(V4L2_CID_ANALOGUE_GAIN) == unicamCtrls_.end()) {\n \t\tLOG(IPARPI, Error) << \"Can't find analogue gain control\";\n@@ -894,13 +918,20 @@ void IPARPi::applyAGC(const struct AgcStatus *agcStatus, ControlList &ctrls)\n \t\treturn;\n \t}\n \n-\tLOG(IPARPI, Debug) << \"Applying AGC Exposure: \" << agcStatus->shutter_time\n-\t\t\t   << \" (Shutter lines: \" << exposureLines << \") Gain: \"\n+\tLOG(IPARPI, Debug) << \"Applying AGC Exposure: \" << exposure\n+\t\t\t   << \" (Shutter lines: \" << exposureLines << \", AGC requested \"\n+\t\t\t   << agcStatus->shutter_time << \") Gain: \"\n \t\t\t   << agcStatus->analogue_gain << \" (Gain Code: \"\n \t\t\t   << gainCode << \")\";\n \n-\tctrls.set(V4L2_CID_ANALOGUE_GAIN, gainCode);\n+\t/*\n+\t * Due to the behavior of V4L2, the current value of VBLANK could clip the\n+\t * exposure time without us knowing. The next time though this function should\n+\t * clip exposure correctly.\n+\t */\n+\tctrls.set(V4L2_CID_VBLANK, vblanking);\n \tctrls.set(V4L2_CID_EXPOSURE, exposureLines);\n+\tctrls.set(V4L2_CID_ANALOGUE_GAIN, gainCode);\n }\n \n void IPARPi::applyDG(const struct AgcStatus *dgStatus, ControlList &ctrls)\ndiff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp\nindex 2ec1f6e7..13349f31 100644\n--- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp\n+++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp\n@@ -1221,7 +1221,8 @@ int RPiCameraData::configureIPA(const CameraConfiguration *config)\n \t\tif (!staggeredCtrl_) {\n \t\t\tstaggeredCtrl_.init(unicam_[Unicam::Image].dev(),\n \t\t\t\t\t    { { V4L2_CID_ANALOGUE_GAIN, result.data[resultIdx++] },\n-\t\t\t\t\t      { V4L2_CID_EXPOSURE, result.data[resultIdx++] } });\n+\t\t\t\t\t      { V4L2_CID_EXPOSURE, result.data[resultIdx++] },\n+\t\t\t\t\t      { V4L2_CID_VBLANK, result.data[resultIdx++] } });\n \t\t\tsensorMetadata_ = result.data[resultIdx++];\n \t\t}\n \t}\n",
    "prefixes": [
        "libcamera-devel",
        "v4",
        "2/3"
    ]
}