Show a patch.

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

{
    "id": 17370,
    "url": "https://patchwork.libcamera.org/api/1.1/patches/17370/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/17370/",
    "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": "<20220915114734.115572-3-rishikeshdonadkar@gmail.com>",
    "date": "2022-09-15T11:47:34",
    "name": "[libcamera-devel,v5,2/2] gstreamer: Provide framerate support for libcamerasrc.",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "d713bacc3cadb27a4e8ed650bb6e397c70b8234b",
    "submitter": {
        "id": 118,
        "url": "https://patchwork.libcamera.org/api/1.1/people/118/?format=api",
        "name": "Rishikesh Donadkar",
        "email": "rishikeshdonadkar@gmail.com"
    },
    "delegate": {
        "id": 12,
        "url": "https://patchwork.libcamera.org/api/1.1/users/12/?format=api",
        "username": "uajain",
        "first_name": "Umang",
        "last_name": "Jain",
        "email": "umang.jain@ideasonboard.com"
    },
    "mbox": "https://patchwork.libcamera.org/patch/17370/mbox/",
    "series": [
        {
            "id": 3487,
            "url": "https://patchwork.libcamera.org/api/1.1/series/3487/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=3487",
            "date": "2022-09-15T11:47:32",
            "name": "Provide framerate support for libcamerasrc",
            "version": 5,
            "mbox": "https://patchwork.libcamera.org/series/3487/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/17370/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/17370/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 B6C56C3272\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 15 Sep 2022 11:47:59 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 71A7161FB6;\n\tThu, 15 Sep 2022 13:47:59 +0200 (CEST)",
            "from mail-pg1-x532.google.com (mail-pg1-x532.google.com\n\t[IPv6:2607:f8b0:4864:20::532])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 60D7A61FB0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 15 Sep 2022 13:47:58 +0200 (CEST)",
            "by mail-pg1-x532.google.com with SMTP id 207so8685912pgc.7\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 15 Sep 2022 04:47:58 -0700 (PDT)",
            "from localhost.localdomain ([49.36.99.120])\n\tby smtp.googlemail.com with ESMTPSA id\n\tx15-20020a170902a38f00b001785dddc703sm3718994pla.120.2022.09.15.04.47.54\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tThu, 15 Sep 2022 04:47:56 -0700 (PDT)"
        ],
        "DKIM-Signature": [
            "v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1663242479;\n\tbh=2yW/T08lgkNZWJF/Wz917bnmL36SVgvJ/9PffarehGo=;\n\th=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=vLdVuKBXvmOp9YQ9uNRyqdiUul8VBX3EEnp2UaUSqiJpyIoTr3OrHOTqcyMPzuInP\n\tSkvDpRkQYSzMsGiT3GskY4frGxbY4guxZBvuO3TUeyMNpGo/VbHMql3wYdgJd64wj2\n\tN93dJ5P2LcETvymxGtX7dMQe4j30FD/kHntQwWc/vEvsuuVLvpwXJFSC178/RCM661\n\t+YTN/3B6NYkfHaIDsN3xd1AX/k5Ge36Rtq4sZ/R6u4zcQW70MdATBRsJidLBx3B7lK\n\t//MS4GdBOlyEgbvseL15wEq3/5bjzqkJwBOaqYuhr1WBY9ABVftpeykSre43Hz8FSj\n\turivNa+MONT8g==",
            "v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:from:to:cc:subject:date;\n\tbh=snGuuKLWj8mTNv76jKgQ92n9lMhXdXR/s2vS5Dmao1o=;\n\tb=NJ875NRQcRTB+Z9lr3Cr5defWU5+dF9f6dawdujJBvrztpULVXa3wYiwdGjVb3Cnxb\n\twVk67umzvCE+k+8bxS/ZqQ+2x8lFTTv+BTyWJDDo5LdhyErPjbW+s1Zxg/e9lO1Abv2L\n\tU6NMP2Uh+BezsEBxBY9gHa3swCCwxSRCDbCtWGBnx9SKjKCo1vruG18z+sIZg6qb8Zxs\n\t2yrNZnTRWp6m6K6v2PbooRITAyS1fPH4//FQncn4HOTrdX/rvUxi5VtKYs30cY1+qJDM\n\t/PR+mkCNzUFKumZBMwhbyInZxMqsyxr9pGYBh8dv7FQ+y63/JGyk3b8XH/1g0sGZORYa\n\t45hQ=="
        ],
        "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (2048-bit key; \n\tunprotected) header.d=gmail.com header.i=@gmail.com\n\theader.b=\"NJ875NRQ\"; dkim-atps=neutral",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20210112;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc\n\t:subject:date;\n\tbh=snGuuKLWj8mTNv76jKgQ92n9lMhXdXR/s2vS5Dmao1o=;\n\tb=2v2o9usczUOcU2gMGK5U0woTcsFNi36ATSciGk6IJTCeU6IbnD/eTrlCV3BNxKAv6T\n\tR+TcsEX+Dw9bgOzP5F0jp8bpXmuHrNdYHQgjPtL0BJOqGH1dC51xCSqlUKF3IksUo/GH\n\tf71gedQLEWmddP1cyn5e0KBAypUGO70+FTV7pk1dynru4D6RnO4wdk8uIglnsD/x7UW2\n\ts4A0X6MOoRKN9TA9C6G4wDeqjvUMtQ8bxoFd61RcdlKHOeS8aq/Hrxha0wMB6aB73Yvg\n\t85UqZvfnWbH4wDwKuDK2+fagkI2HzvycI+EwE9GEHVN/TQrlbWVH8FHjNtSFbdEfZhCR\n\tflOw==",
        "X-Gm-Message-State": "ACgBeo0we/s6eBQkur0xYvfdS4yB9scWDvk8RwqyQMvY+/RQ71qMPn2M\n\tfoA3wTBtbNBeNaLH6uhnVAYaYiKqTe1H8g==",
        "X-Google-Smtp-Source": "AA6agR5zO6U/jvrDUOBl//idXH226F0QfXwhyGvbVaqj4jg1fwM7zZaQk18ObSHKxQPf3AdUs61vaA==",
        "X-Received": "by 2002:a05:6a00:804:b0:544:4e98:3ff3 with SMTP id\n\tm4-20020a056a00080400b005444e983ff3mr18031048pfk.36.1663242476668; \n\tThu, 15 Sep 2022 04:47:56 -0700 (PDT)",
        "To": "libcamera-devel@lists.libcamera.org",
        "Date": "Thu, 15 Sep 2022 17:17:34 +0530",
        "Message-Id": "<20220915114734.115572-3-rishikeshdonadkar@gmail.com>",
        "X-Mailer": "git-send-email 2.25.1",
        "In-Reply-To": "<20220915114734.115572-1-rishikeshdonadkar@gmail.com>",
        "References": "<20220915114734.115572-1-rishikeshdonadkar@gmail.com>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "Subject": "[libcamera-devel] [PATCH v5 2/2] gstreamer: Provide framerate\n\tsupport for libcamerasrc.",
        "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>",
        "From": "Rishikesh Donadkar via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>",
        "Reply-To": "Rishikesh Donadkar <rishikeshdonadkar@gmail.com>",
        "Cc": "nicolas.dufresne@collabora.com, vedantparanjape160201@gmail.com",
        "Errors-To": "libcamera-devel-bounces@lists.libcamera.org",
        "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"
    },
    "content": "Control the framerate by setting the controls::FrameDurationLimits\nin an ControlList and passing the control list at camera::start(). This\nrequires converting the framerate which of the type\nGST_TYPE_FRACTION into FrameDuration which of the type int64_t. This\nconversion causes loss of precision and the precise framerate cannot be\nobtained by Inverting the control::FrameDurationLimits value.\n\nExample -\n\n* Suppose the framerate requested is 35/1. The framerate is read form\n  the caps in the from of fraction that has a numerator and\n  denominator.\n\n* Converting to FrameDuration (in microseconds)\n    (1 * 10^6) / 35 = 28571.4286\n    int64_t frame_duration = 28571\n    (the precision here is lost.)\n* To expose the framerate in caps, Inverting the frame_duration to get\n  back the framerate and converting to Seconds.\n    double framerate = 10^6 / 28571\n    and\n    10^6/28571 which is close but not equal to 35/1 will fail the negotiation.\n\nGet the framerate from the caps and convert it to FrameDuration.\n\nClamp the frame_duration between the min/max FrameDuration supported\nby the camera. Set the FrameDurationLimits control in the initControls_.\nStore the clamped/unclamped framerate in the container to be retrieved later.\n\nSet the bound checked framerate from the container into the caps.\n\nPass the ControlList initControls_ at camera->start().\n\nSigned-off-by: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>\n---\n src/gstreamer/gstlibcamera-utils.cpp | 70 ++++++++++++++++++++++++++++\n src/gstreamer/gstlibcamera-utils.h   |  6 +++\n src/gstreamer/gstlibcamerasrc.cpp    | 16 ++++++-\n 3 files changed, 91 insertions(+), 1 deletion(-)",
    "diff": "diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp\nindex 244a4a79..89f116ba 100644\n--- a/src/gstreamer/gstlibcamera-utils.cpp\n+++ b/src/gstreamer/gstlibcamera-utils.cpp\n@@ -8,6 +8,7 @@\n \n #include \"gstlibcamera-utils.h\"\n \n+#include <libcamera/control_ids.h>\n #include <libcamera/formats.h>\n \n using namespace libcamera;\n@@ -407,6 +408,75 @@ gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,\n \t}\n }\n \n+void gst_libcamera_get_framerate_from_caps([[maybe_unused]] GstCaps *caps,\n+\t\t\t\t\t   GstStructure *container)\n+{\n+\tGstStructure *s = gst_caps_get_structure(caps, 0);\n+\tgint fps_n = 0, fps_d = 1;\n+\n+\tif (gst_structure_has_field_typed(s, \"framerate\", GST_TYPE_FRACTION)) {\n+\t\tif (gst_structure_get_fraction(s, \"framerate\", &fps_n, &fps_d))\n+\t\t\tgst_structure_set(container, \"framerate\", GST_TYPE_FRACTION, fps_n, fps_d, nullptr);\n+\t\telse\n+\t\t\tGST_WARNING(\"invalid framerate in the caps.\");\n+\t}\n+}\n+\n+void gst_libcamera_clamp_and_set_frameduration(ControlList &controls, const ControlInfoMap &camera_controls,\n+\t\t\t\t\t       GstStructure *container)\n+{\n+\tgboolean in_bounds = false;\n+\tgint fps_caps_n, fps_caps_d, fps_n, fps_d;\n+\n+\tif (!gst_structure_has_field_typed(container, \"framerate\", GST_TYPE_FRACTION))\n+\t\treturn;\n+\n+\tauto iterFrameDuration = camera_controls.find(controls::FrameDurationLimits.id());\n+\tif (iterFrameDuration == camera_controls.end()) {\n+\t\tGST_WARNING(\"Valid bounds for FrameDuration not found\");\n+\t\treturn;\n+\t}\n+\n+\tconst GValue *framerate = gst_structure_get_value(container, \"framerate\");\n+\n+\tfps_caps_n = gst_value_get_fraction_numerator(framerate);\n+\tfps_caps_d = gst_value_get_fraction_denominator(framerate);\n+\n+\tint64_t frame_duration = (fps_caps_d * 1000000.0) / fps_caps_n;\n+\tint64_t min_frame_duration = iterFrameDuration->second.min().get<int64_t>();\n+\tint64_t max_frame_duration = iterFrameDuration->second.max().get<int64_t>();\n+\n+\tif (frame_duration < min_frame_duration) {\n+\t\tframe_duration = min_frame_duration;\n+\t} else if (frame_duration > max_frame_duration) {\n+\t\tframe_duration = max_frame_duration;\n+\t} else {\n+\t\tin_bounds = true;\n+\t}\n+\n+\tif (!in_bounds) {\n+\t\tdouble framerate_clamped = 1000000.0 / frame_duration;\n+\t\tgst_util_double_to_fraction(framerate_clamped, &fps_n, &fps_d);\n+\t\tgst_structure_set(container, \"framerate\", GST_TYPE_FRACTION, fps_n, fps_d, nullptr);\n+\t}\n+\n+\tcontrols.set(controls::FrameDurationLimits,\n+\t\t     Span<const int64_t, 2>({ frame_duration, frame_duration }));\n+}\n+\n+void gst_libcamera_framerate_to_caps(GstCaps *caps, const GValue *container)\n+{\n+\tif (!GST_VALUE_HOLDS_FRACTION(container))\n+\t\treturn;\n+\n+\tGstStructure *s = gst_caps_get_structure(caps, 0);\n+\tgint fps_caps_n, fps_caps_d;\n+\n+\tfps_caps_n = gst_value_get_fraction_numerator(container);\n+\tfps_caps_d = gst_value_get_fraction_denominator(container);\n+\tgst_structure_set(s, \"framerate\", GST_TYPE_FRACTION, fps_caps_n, fps_caps_d, nullptr);\n+}\n+\n #if !GST_CHECK_VERSION(1, 17, 1)\n gboolean\n gst_task_resume(GstTask *task)\ndiff --git a/src/gstreamer/gstlibcamera-utils.h b/src/gstreamer/gstlibcamera-utils.h\nindex 164189a2..3d217fcf 100644\n--- a/src/gstreamer/gstlibcamera-utils.h\n+++ b/src/gstreamer/gstlibcamera-utils.h\n@@ -9,6 +9,7 @@\n #pragma once\n \n #include <libcamera/camera_manager.h>\n+#include <libcamera/controls.h>\n #include <libcamera/stream.h>\n \n #include <gst/gst.h>\n@@ -18,6 +19,11 @@ GstCaps *gst_libcamera_stream_formats_to_caps(const libcamera::StreamFormats &fo\n GstCaps *gst_libcamera_stream_configuration_to_caps(const libcamera::StreamConfiguration &stream_cfg);\n void gst_libcamera_configure_stream_from_caps(libcamera::StreamConfiguration &stream_cfg,\n \t\t\t\t\t      GstCaps *caps);\n+void gst_libcamera_get_framerate_from_caps(GstCaps *caps, GstStructure *container);\n+void gst_libcamera_clamp_and_set_frameduration(libcamera::ControlList &controls,\n+\t\t\t\t\t       const libcamera::ControlInfoMap &camera_controls, GstStructure *container);\n+void gst_libcamera_framerate_to_caps(GstCaps *caps, const GValue *container);\n+\n #if !GST_CHECK_VERSION(1, 17, 1)\n gboolean gst_task_resume(GstTask *task);\n #endif\ndiff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp\nindex 60032236..37f07676 100644\n--- a/src/gstreamer/gstlibcamerasrc.cpp\n+++ b/src/gstreamer/gstlibcamerasrc.cpp\n@@ -131,6 +131,7 @@ struct GstLibcameraSrcState {\n \tstd::queue<std::unique_ptr<RequestWrap>> completedRequests_\n \t\tLIBCAMERA_TSA_GUARDED_BY(lock_);\n \n+\tControlList initControls_;\n \tguint group_id_;\n \n \tint queueRequest();\n@@ -462,6 +463,8 @@ gst_libcamera_src_task_enter(GstTask *task, [[maybe_unused]] GThread *thread,\n \tGstFlowReturn flow_ret = GST_FLOW_OK;\n \tgint ret;\n \n+\tGstStructure *container = gst_structure_new_empty(\"container\");\n+\n \tGST_DEBUG_OBJECT(self, \"Streaming thread has started\");\n \n \tgint stream_id_num = 0;\n@@ -496,6 +499,7 @@ gst_libcamera_src_task_enter(GstTask *task, [[maybe_unused]] GThread *thread,\n \t\t/* Retrieve the supported caps. */\n \t\tg_autoptr(GstCaps) filter = gst_libcamera_stream_formats_to_caps(stream_cfg.formats());\n \t\tg_autoptr(GstCaps) caps = gst_pad_peer_query_caps(srcpad, filter);\n+\n \t\tif (gst_caps_is_empty(caps)) {\n \t\t\tflow_ret = GST_FLOW_NOT_NEGOTIATED;\n \t\t\tbreak;\n@@ -504,6 +508,7 @@ gst_libcamera_src_task_enter(GstTask *task, [[maybe_unused]] GThread *thread,\n \t\t/* Fixate caps and configure the stream. */\n \t\tcaps = gst_caps_make_writable(caps);\n \t\tgst_libcamera_configure_stream_from_caps(stream_cfg, caps);\n+\t\tgst_libcamera_get_framerate_from_caps(caps, container);\n \t}\n \n \tif (flow_ret != GST_FLOW_OK)\n@@ -525,6 +530,10 @@ gst_libcamera_src_task_enter(GstTask *task, [[maybe_unused]] GThread *thread,\n \t\tgoto done;\n \t}\n \n+\t/* Check frame duration bounds within controls::FrameDurationLimits */\n+\tgst_libcamera_clamp_and_set_frameduration(state->initControls_,\n+\t\t\t\t\t\t  state->cam_->controls(), container);\n+\n \t/*\n \t * Regardless if it has been modified, create clean caps and push the\n \t * caps event. Downstream will decide if the caps are acceptable.\n@@ -532,8 +541,11 @@ gst_libcamera_src_task_enter(GstTask *task, [[maybe_unused]] GThread *thread,\n \tfor (gsize i = 0; i < state->srcpads_.size(); i++) {\n \t\tGstPad *srcpad = state->srcpads_[i];\n \t\tconst StreamConfiguration &stream_cfg = state->config_->at(i);\n+\t\tconst GValue *framerate = gst_structure_get_value(container, \"framerate\");\n \n \t\tg_autoptr(GstCaps) caps = gst_libcamera_stream_configuration_to_caps(stream_cfg);\n+\t\tgst_libcamera_framerate_to_caps(caps, framerate);\n+\n \t\tif (!gst_pad_push_event(srcpad, gst_event_new_caps(caps))) {\n \t\t\tflow_ret = GST_FLOW_NOT_NEGOTIATED;\n \t\t\tbreak;\n@@ -567,7 +579,7 @@ gst_libcamera_src_task_enter(GstTask *task, [[maybe_unused]] GThread *thread,\n \t\tgst_flow_combiner_add_pad(self->flow_combiner, srcpad);\n \t}\n \n-\tret = state->cam_->start();\n+\tret = state->cam_->start(&state->initControls_);\n \tif (ret) {\n \t\tGST_ELEMENT_ERROR(self, RESOURCE, SETTINGS,\n \t\t\t\t  (\"Failed to start the camera: %s\", g_strerror(-ret)),\n@@ -577,6 +589,8 @@ gst_libcamera_src_task_enter(GstTask *task, [[maybe_unused]] GThread *thread,\n \t}\n \n done:\n+\tstate->initControls_.clear();\n+\tg_free(container);\n \tswitch (flow_ret) {\n \tcase GST_FLOW_NOT_NEGOTIATED:\n \t\tGST_ELEMENT_FLOW_ERROR(self, flow_ret);\n",
    "prefixes": [
        "libcamera-devel",
        "v5",
        "2/2"
    ]
}