Show a patch.

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

{
    "id": 23965,
    "url": "https://patchwork.libcamera.org/api/patches/23965/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/23965/",
    "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": "<20250725115602.1477743-2-giacomo.cappellini.87@gmail.com>",
    "date": "2025-07-25T11:30:46",
    "name": "[v4,1/1] gstreamer: Add support for Orientation",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "afb9e808f6337e19b16a6a62970b39ba776d4a3f",
    "submitter": {
        "id": 231,
        "url": "https://patchwork.libcamera.org/api/people/231/?format=api",
        "name": "Giacomo Cappellini",
        "email": "giacomo.cappellini.87@gmail.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/23965/mbox/",
    "series": [
        {
            "id": 5330,
            "url": "https://patchwork.libcamera.org/api/series/5330/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5330",
            "date": "2025-07-25T11:30:45",
            "name": "gstreamer: Add support for Orientation",
            "version": 4,
            "mbox": "https://patchwork.libcamera.org/series/5330/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/23965/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/23965/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 53B5DBDCC1\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 25 Jul 2025 11:57:37 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 0EF10690CD;\n\tFri, 25 Jul 2025 13:57:37 +0200 (CEST)",
            "from mail-wm1-x32a.google.com (mail-wm1-x32a.google.com\n\t[IPv6:2a00:1450:4864:20::32a])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 040A4690A6\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 25 Jul 2025 13:57:36 +0200 (CEST)",
            "by mail-wm1-x32a.google.com with SMTP id\n\t5b1f17b1804b1-455b00339c8so14035695e9.3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 25 Jul 2025 04:57:36 -0700 (PDT)",
            "from jasus.ad.servtec.it\n\t(host-95-251-230-143.retail.telecomitalia.it. [95.251.230.143])\n\tby smtp.gmail.com with ESMTPSA id\n\t5b1f17b1804b1-45870532af4sm52130805e9.4.2025.07.25.04.57.34\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tFri, 25 Jul 2025 04:57:34 -0700 (PDT)"
        ],
        "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=gmail.com header.i=@gmail.com\n\theader.b=\"Nw78qbvj\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=gmail.com; s=20230601; t=1753444655; x=1754049455;\n\tdarn=lists.libcamera.org; \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\t:message-id:reply-to;\n\tbh=7+e4eFM5E7v0+yh0b04JNMHsjJumQN+ND2VuUhfIZiQ=;\n\tb=Nw78qbvjiHCbQ6ezmo8QgLNojukn8QJj0koYON46NXhS4nYBDgwhLVZt8nCC5011ej\n\tlYzj8nhpOWysGrEMLiH6w1HpHjqVnGaF9MNtX+CLmnlDvA5eSkI+0dkri079YTjnXVQe\n\trWqWfxUw8+PiMNvCK4mpc3NEt5h4Dqsx/FfDbxUFYvx8zU3udMbMZrYJ04bSWG8XVnE9\n\tj/acx2lPPm/TDZeqjDltYRmfXMtiVtNNlHEWN7dwwLGKygkTaiSVLFYXbcTL00jr7TZP\n\tKwJW4x8u9tQQ/p2n4iiMSX/nB5LbbaAxJgn0yHzgrRc5az0ohpyw3mt5ViWr/bZlimBb\n\t2R6g==",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1753444655; x=1754049455;\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:message-id:reply-to;\n\tbh=7+e4eFM5E7v0+yh0b04JNMHsjJumQN+ND2VuUhfIZiQ=;\n\tb=ePYCEdqpE4cwp6FWHY2eohxl/jEhLQf8T0iB2KgOEXAxrNtvVTMI7vspeejWkR7h1o\n\t+yTAsuYQUQ+ellgm794XQQcHoqGVtupdK3O08wz3Bx35Lugtm5sLp9rrEunAiUoq5txq\n\tDVlwHB5g6EhDunYXFynzqQ0WVtT1BWw2CFpXu6hFy9tGq334y+RfY5n8/wywRBdrY6Yw\n\tjeK3pf1LP60qTX43y9cXQceZhpZmr07p2v8XrnSFT4snmtarUKUAza8FS0tKOd7zlwBO\n\tP+LkND2sKYhu+UnDSFRTQxEuNlUqh9ucPctT5UqpwMk4w1YXUGwA8lFEMkRxHOzD76R6\n\tBzlQ==",
        "X-Gm-Message-State": "AOJu0Yygf15Q9tlfP32o/Kw8uLg22RZj193FGAwpImWEYNxQjNgLaJLq\n\tKYAlxmckyxwe8NROb6wKBmr4p8bFl527AtJkKtXW9SKO43lEkotbeW6a0Wj/TZnZ",
        "X-Gm-Gg": "ASbGnctyWqcVca33yRh1aLR/os0y1Xu9ZR55E3/l4djBkdgBltPiG4Mn24XZNnlOHUt\n\tkEBw0apdSgDgAF4CUETZaI9HRqKgGei8ejMnhGG2hWtGKsw3TbST0O6NC4k2hcDs6tObkfk6kLQ\n\t9yeRaKMrToAKOyy4pizwIFh2Sz1F6nflK/b8AyJJlNxMOESUY9//Wv39q7er4ZUQaOzkBBE+fXH\n\tBSsbydjIUZVZ6UBmur/yh8FGW+6fyGXZY7NKtp/iF9VI4y3FWErAaag6/354OygFa014GZjMKHR\n\ta24a2vSisPWXH4bK0JXMjRYo5u9ZqywwB8+Fwa2GPFh15ZWD/MEDv7jVIErL4WS/mNpTVF8Jdmy\n\tkki+Py0gGYm6LeyBnM14MwAvZHaTpRU9iN2nXOZkMTPiHOnSihSyRfDpkSBEANzXk5Pfs+ysjGE\n\tYbETrtOVylJqHoyvPkQ5PQRg==",
        "X-Google-Smtp-Source": "AGHT+IEJVTRmFEtA1QcnxoLdI5t/CqTko8WEIMnygsJrzg/foyExqmOK0GICgVhNAtEPu+HnneuW4Q==",
        "X-Received": "by 2002:a05:600c:45c6:b0:450:ceb2:67dd with SMTP id\n\t5b1f17b1804b1-4587665dc28mr12657325e9.33.1753444655038; \n\tFri, 25 Jul 2025 04:57:35 -0700 (PDT)",
        "From": "Giacomo Cappellini <giacomo.cappellini.87@gmail.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Cc": "Giacomo Cappellini <giacomo.cappellini.87@gmail.com>",
        "Subject": "[PATCH v4 1/1] gstreamer: Add support for Orientation",
        "Date": "Fri, 25 Jul 2025 13:30:46 +0200",
        "Message-ID": "<20250725115602.1477743-2-giacomo.cappellini.87@gmail.com>",
        "X-Mailer": "git-send-email 2.43.0",
        "In-Reply-To": "<20250725115602.1477743-1-giacomo.cappellini.87@gmail.com>",
        "References": "<20250725115602.1477743-1-giacomo.cappellini.87@gmail.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": "libcamera allows to control the images orientation through the\nCameraConfiguration::orientation field, expose a GST_PARAM_MUTABLE_READY\nparameter of type GstVideoOrientationMethod in GstLibcameraSrc to\ncontrol it. Parameter is mapped internally to libcamera::Orientation via\nnew gstlibcamera-utils functions:\n- gst_video_orientation_to_libcamera_orientation\n- libcamera_orientation_to_gst_video_orientation\n\nSigned-off-by: Giacomo Cappellini <giacomo.cappellini.87@gmail.com>\n---\n src/gstreamer/gstlibcamera-utils.cpp | 58 ++++++++++++++++++++++------\n src/gstreamer/gstlibcamera-utils.h   |  4 ++\n src/gstreamer/gstlibcamerasrc.cpp    | 46 +++++++++++++++++-----\n 3 files changed, 86 insertions(+), 22 deletions(-)",
    "diff": "diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp\nindex a548b0c1..15069f98 100644\n--- a/src/gstreamer/gstlibcamera-utils.cpp\n+++ b/src/gstreamer/gstlibcamera-utils.cpp\n@@ -10,6 +10,9 @@\n \n #include <libcamera/control_ids.h>\n #include <libcamera/formats.h>\n+#include <libcamera/orientation.h>\n+\n+#include <gst/video/video.h>\n \n using namespace libcamera;\n \n@@ -20,7 +23,7 @@ static struct {\n \t/* Compressed */\n \t{ GST_VIDEO_FORMAT_ENCODED, formats::MJPEG },\n \n-\t/* Bayer formats, gstreamer only supports 8-bit */\n+\t/* Bayer formats */\n \t{ GST_VIDEO_FORMAT_ENCODED, formats::SBGGR8 },\n \t{ GST_VIDEO_FORMAT_ENCODED, formats::SGBRG8 },\n \t{ GST_VIDEO_FORMAT_ENCODED, formats::SGRBG8 },\n@@ -317,20 +320,15 @@ bare_structure_from_format(const PixelFormat &format)\n \t\treturn gst_structure_new(\"video/x-raw\", \"format\", G_TYPE_STRING,\n \t\t\t\t\t gst_video_format_to_string(gst_format), nullptr);\n \n-\tswitch (format) {\n-\tcase formats::MJPEG:\n+\tif (format == formats::MJPEG)\n \t\treturn gst_structure_new_empty(\"image/jpeg\");\n \n-\tcase formats::SBGGR8:\n-\tcase formats::SGBRG8:\n-\tcase formats::SGRBG8:\n-\tcase formats::SRGGB8:\n-\t\treturn gst_structure_new(\"video/x-bayer\", \"format\", G_TYPE_STRING,\n-\t\t\t\t\t bayer_format_to_string(format), nullptr);\n-\n-\tdefault:\n+\tconst gchar *s = bayer_format_to_string(format);\n+\tif (s)\n+\t\treturn gst_structure_new(\"video/x-bayer\", \"format\",\n+\t\t\t\t\t G_TYPE_STRING, s, nullptr);\n+\telse\n \t\treturn nullptr;\n-\t}\n }\n \n GstCaps *\n@@ -659,3 +657,39 @@ gst_libcamera_get_camera_manager(int &ret)\n \n \treturn cm;\n }\n+\n+static const struct {\n+\tOrientation orientation;\n+\tGstVideoOrientationMethod method;\n+} orientation_map[]{\n+\t{ Orientation::Rotate0, GST_VIDEO_ORIENTATION_IDENTITY },\n+\t{ Orientation::Rotate90, GST_VIDEO_ORIENTATION_90R },\n+\t{ Orientation::Rotate180, GST_VIDEO_ORIENTATION_180 },\n+\t{ Orientation::Rotate270, GST_VIDEO_ORIENTATION_90L },\n+\t{ Orientation::Rotate0Mirror, GST_VIDEO_ORIENTATION_HORIZ },\n+\t{ Orientation::Rotate180Mirror, GST_VIDEO_ORIENTATION_VERT },\n+\t{ Orientation::Rotate90Mirror, GST_VIDEO_ORIENTATION_UL_LR },\n+\t{ Orientation::Rotate270Mirror, GST_VIDEO_ORIENTATION_UR_LL },\n+};\n+\n+Orientation\n+gst_video_orientation_to_libcamera_orientation(GstVideoOrientationMethod method)\n+{\n+\tfor (auto &b : orientation_map) {\n+\t\tif (b.method == method)\n+\t\t\treturn b.orientation;\n+\t}\n+\n+\treturn Orientation::Rotate0;\n+}\n+\n+GstVideoOrientationMethod\n+libcamera_orientation_to_gst_video_orientation(Orientation orientation)\n+{\n+\tfor (auto &a : orientation_map) {\n+\t\tif (a.orientation == orientation)\n+\t\t\treturn a.method;\n+\t}\n+\n+\treturn GST_VIDEO_ORIENTATION_IDENTITY;\n+}\ndiff --git a/src/gstreamer/gstlibcamera-utils.h b/src/gstreamer/gstlibcamera-utils.h\nindex 5f4e8a0f..bbbd33db 100644\n--- a/src/gstreamer/gstlibcamera-utils.h\n+++ b/src/gstreamer/gstlibcamera-utils.h\n@@ -10,6 +10,7 @@\n \n #include <libcamera/camera_manager.h>\n #include <libcamera/controls.h>\n+#include <libcamera/orientation.h>\n #include <libcamera/stream.h>\n \n #include <gst/gst.h>\n@@ -92,3 +93,6 @@ public:\n private:\n \tGRecMutex *mutex_;\n };\n+\n+libcamera::Orientation gst_video_orientation_to_libcamera_orientation(GstVideoOrientationMethod method);\n+GstVideoOrientationMethod libcamera_orientation_to_gst_video_orientation(libcamera::Orientation orientation);\ndiff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp\nindex 3aca4eed..85add936 100644\n--- a/src/gstreamer/gstlibcamerasrc.cpp\n+++ b/src/gstreamer/gstlibcamerasrc.cpp\n@@ -29,6 +29,7 @@\n \n #include <atomic>\n #include <queue>\n+#include <sstream>\n #include <tuple>\n #include <utility>\n #include <vector>\n@@ -38,6 +39,7 @@\n #include <libcamera/control_ids.h>\n \n #include <gst/base/base.h>\n+#include <gst/video/video.h>\n \n #include \"gstlibcamera-controls.h\"\n #include \"gstlibcamera-utils.h\"\n@@ -146,6 +148,7 @@ struct _GstLibcameraSrc {\n \tGstTask *task;\n \n \tgchar *camera_name;\n+\tGstVideoOrientationMethod orientation;\n \n \tstd::atomic<GstEvent *> pending_eos;\n \n@@ -157,6 +160,7 @@ struct _GstLibcameraSrc {\n enum {\n \tPROP_0,\n \tPROP_CAMERA_NAME,\n+\tPROP_ORIENTATION,\n \tPROP_LAST\n };\n \n@@ -166,8 +170,8 @@ static void gst_libcamera_src_child_proxy_init(gpointer g_iface,\n G_DEFINE_TYPE_WITH_CODE(GstLibcameraSrc, gst_libcamera_src, GST_TYPE_ELEMENT,\n \t\t\tG_IMPLEMENT_INTERFACE(GST_TYPE_CHILD_PROXY,\n \t\t\t\t\t      gst_libcamera_src_child_proxy_init)\n-\t\t\tGST_DEBUG_CATEGORY_INIT(source_debug, \"libcamerasrc\", 0,\n-\t\t\t\t\t\t\"libcamera Source\"))\n+\t\t\t\tGST_DEBUG_CATEGORY_INIT(source_debug, \"libcamerasrc\", 0,\n+\t\t\t\t\t\t\t\"libcamera Source\"))\n \n #define TEMPLATE_CAPS GST_STATIC_CAPS(\"video/x-raw; image/jpeg; video/x-bayer\")\n \n@@ -225,8 +229,7 @@ int GstLibcameraSrcState::queueRequest()\n \treturn 0;\n }\n \n-void\n-GstLibcameraSrcState::requestCompleted(Request *request)\n+void GstLibcameraSrcState::requestCompleted(Request *request)\n {\n \tGST_DEBUG_OBJECT(src_, \"buffers are ready\");\n \n@@ -616,9 +619,17 @@ gst_libcamera_src_negotiate(GstLibcameraSrc *self)\n \t\tgst_libcamera_get_framerate_from_caps(caps, element_caps);\n \t}\n \n+\t/* Set orientation control. */\n+\tstate->config_->orientation = gst_video_orientation_to_libcamera_orientation(self->orientation);\n+\n \t/* Validate the configuration. */\n-\tif (state->config_->validate() == CameraConfiguration::Invalid)\n+\tCameraConfiguration::Status status = state->config_->validate();\n+\tif (status == CameraConfiguration::Invalid)\n \t\treturn false;\n+\telse if (status == CameraConfiguration::Adjusted)\n+\t\tGST_ELEMENT_INFO(self, RESOURCE, SETTINGS,\n+\t\t\t\t (\"Configuration was adjusted\"),\n+\t\t\t\t (\"CameraConfiguration::validate() returned CameraConfiguration::Adjusted\"));\n \n \tint ret = state->cam_->configure(state->config_.get());\n \tif (ret) {\n@@ -643,6 +654,10 @@ gst_libcamera_src_negotiate(GstLibcameraSrc *self)\n \t\tg_autoptr(GstCaps) caps = gst_libcamera_stream_configuration_to_caps(stream_cfg, transfer[i]);\n \t\tgst_libcamera_framerate_to_caps(caps, element_caps);\n \n+\t\tif (status == CameraConfiguration::Adjusted &&\n+\t\t    !gst_pad_peer_query_accept_caps(srcpad, caps))\n+\t\t\treturn false;\n+\n \t\tif (!gst_pad_push_event(srcpad, gst_event_new_caps(caps)))\n \t\t\treturn false;\n \t}\n@@ -730,7 +745,8 @@ gst_libcamera_src_task_run(gpointer user_data)\n \t\tif (gst_pad_check_reconfigure(srcpad)) {\n \t\t\t/* Check if the caps even need changing. */\n \t\t\tg_autoptr(GstCaps) caps = gst_pad_get_current_caps(srcpad);\n-\t\t\tif (!gst_pad_peer_query_accept_caps(srcpad, caps)) {\n+\t\t\tg_autoptr(GstCaps) peercaps = gst_pad_peer_query_caps(srcpad, caps);\n+\t\t\tif (gst_caps_is_empty(peercaps)) {\n \t\t\t\treconfigure = true;\n \t\t\t\tbreak;\n \t\t\t}\n@@ -926,6 +942,9 @@ gst_libcamera_src_set_property(GObject *object, guint prop_id,\n \t\tg_free(self->camera_name);\n \t\tself->camera_name = g_value_dup_string(value);\n \t\tbreak;\n+\tcase PROP_ORIENTATION:\n+\t\tself->orientation = (GstVideoOrientationMethod)g_value_get_enum(value);\n+\t\tbreak;\n \tdefault:\n \t\tif (!state->controls_.setProperty(prop_id - PROP_LAST, value, pspec))\n \t\t\tG_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);\n@@ -945,6 +964,9 @@ gst_libcamera_src_get_property(GObject *object, guint prop_id, GValue *value,\n \tcase PROP_CAMERA_NAME:\n \t\tg_value_set_string(value, self->camera_name);\n \t\tbreak;\n+\tcase PROP_ORIENTATION:\n+\t\tg_value_set_enum(value, (gint)self->orientation);\n+\t\tbreak;\n \tdefault:\n \t\tif (!state->controls_.getProperty(prop_id - PROP_LAST, value, pspec))\n \t\t\tG_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);\n@@ -1148,12 +1170,16 @@ gst_libcamera_src_class_init(GstLibcameraSrcClass *klass)\n \n \tGParamSpec *spec = g_param_spec_string(\"camera-name\", \"Camera Name\",\n \t\t\t\t\t       \"Select by name which camera to use.\", nullptr,\n-\t\t\t\t\t       (GParamFlags)(GST_PARAM_MUTABLE_READY\n-\t\t\t\t\t\t\t     | G_PARAM_CONSTRUCT\n-\t\t\t\t\t\t\t     | G_PARAM_READWRITE\n-\t\t\t\t\t\t\t     | G_PARAM_STATIC_STRINGS));\n+\t\t\t\t\t       (GParamFlags)(GST_PARAM_MUTABLE_READY | G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));\n \tg_object_class_install_property(object_class, PROP_CAMERA_NAME, spec);\n \n+\t/* Register the orientation enum type. */\n+\tspec = g_param_spec_enum(\"orientation\", \"Orientation\",\n+\t\t\t\t \"Select the orientation of the camera.\",\n+\t\t\t\t GST_TYPE_VIDEO_ORIENTATION_METHOD, GST_VIDEO_ORIENTATION_IDENTITY,\n+\t\t\t\t (GParamFlags)(GST_PARAM_MUTABLE_READY | G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));\n+\tg_object_class_install_property(object_class, PROP_ORIENTATION, spec);\n+\n \tGstCameraControls::installProperties(object_class, PROP_LAST);\n }\n \n",
    "prefixes": [
        "v4",
        "1/1"
    ]
}