Patch Detail
Show a patch.
GET /api/patches/24979/?format=api
{ "id": 24979, "url": "https://patchwork.libcamera.org/api/patches/24979/?format=api", "web_url": "https://patchwork.libcamera.org/patch/24979/", "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": "<20251106130133.130946-1-uajain@igalia.com>", "date": "2025-11-06T13:01:33", "name": "[v6] gstreamer: Plumb camera orientation configuration in libcamerasrc", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "b3ace06d4ba1d6b3c4d2e78dbecbfc760edf4058", "submitter": { "id": 232, "url": "https://patchwork.libcamera.org/api/people/232/?format=api", "name": "Umang Jain", "email": "uajain@igalia.com" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/24979/mbox/", "series": [ { "id": 5566, "url": "https://patchwork.libcamera.org/api/series/5566/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5566", "date": "2025-11-06T13:01:33", "name": "[v6] gstreamer: Plumb camera orientation configuration in libcamerasrc", "version": 6, "mbox": "https://patchwork.libcamera.org/series/5566/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/24979/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/24979/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 A8FFCC3241\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 6 Nov 2025 13:01:09 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 7DAE560A80;\n\tThu, 6 Nov 2025 14:01:08 +0100 (CET)", "from fanzine2.igalia.com (fanzine2.igalia.com [213.97.179.56])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id C034A606E6\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 6 Nov 2025 14:01:05 +0100 (CET)", "from [81.140.124.245] (helo=uajain)\n\tby fanzine2.igalia.com with esmtpsa \n\t(Cipher TLS1.3:ECDHE_X25519__RSA_PSS_RSAE_SHA256__AES_256_GCM:256)\n\t(Exim) id 1vGzbs-002zsB-KK; Thu, 06 Nov 2025 14:01:04 +0100" ], "Authentication-Results": "lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n\tunprotected) header.d=igalia.com header.i=@igalia.com\n\theader.b=\"I3w2x73D\"; dkim-atps=neutral", "DKIM-Signature": "v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=igalia.com;\n\ts=20170329;\n\th=Content-Transfer-Encoding:MIME-Version:Message-ID:Date:Subject:\n\tCc:To:From:Sender:Reply-To:Content-Type:Content-ID:Content-Description:\n\tResent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:\n\tIn-Reply-To:References:List-Id:List-Help:List-Unsubscribe:List-Subscribe:\n\tList-Post:List-Owner:List-Archive;\n\tbh=s3jNAGTYi+WKCgceguE0B3hRf/N/bwxCQxVzN+mBSNk=;\n\tb=I3w2x73DIvgeCAf2pT6aHuQrXu\n\t17KWsu2MOtcBrBT1iA+TEZTtETkyc1/Kp/ttEsA95S5RwuMkrDsjgxIaTjEv4iCcOtle4Wywu8gno\n\t2NPVuggJdP1nhTTVHQwC17R73+8zEwF3KeGK2Ty1/TWAm9pX30a9drtBEQTvX18mR+gF4EdOYNgCj\n\txb0hrYwOBuy6EPjhwsV7WaH+NJC2D9iLN9PgH0hl2Y8wSc+/itoFwn25ufC05RxXX8Mytgua+7T35\n\tJpf9uJ1Nu4iLbWZfRnjMG9UDpPze/CtE8KfGFXeZ6NY/U7aHr4wXD+XOvj1n6AlVhVmbIRTHWSuch\n\tg3Fj8w0w==;", "From": "Umang Jain <uajain@igalia.com>", "To": "libcamera-devel@lists.libcamera.org", "Cc": "Nicolas Dufresne <nicolas.dufresne@collabora.com>,\n\tUmang Jain <uajain@igalia.com>", "Subject": "[PATCH v6] gstreamer: Plumb camera orientation configuration in\n\tlibcamerasrc", "Date": "Thu, 6 Nov 2025 13:01:33 +0000", "Message-ID": "<20251106130133.130946-1-uajain@igalia.com>", "X-Mailer": "git-send-email 2.51.0", "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": "Plumb the support for CameraConfiguration::orientation in libcamerasrc.\nA new \"orientation\" property is introduced and mappings for\nlibcamera::Orientation <> GstVideoOrientationMethod are provided\nwith helpers.\n\nIf the orientation is changed after the CameraConfiguration::validate(),\na GstMessage is sent on the GstBus to report the new orientation\nproperty value.\n\nIn case the orientation that was requested cannot be satisfied,\na 'image-orientation' tag is pushed downstream for sinks to rotate\nthe stream accordingly (those implementing\nGSTGST_VIDEO_ORIENTATION_AUTO).\n\nSigned-off-by: Umang Jain <uajain@igalia.com>\n---\nChanges in v6:\n- Use GstMessage to send new property changes if any (like orientation\n after validate())\n- send 'image-orientation' tag orientation downstream if orientation\n cannot be satisfied\n- Drop 'video-orientation' and restore 'orientation' as property name\n- change authorship to myself as patch is reworked significantly\n\nChanges in v5:\n- patch scrubbing and cleanup\n- If different orientation is returned than requested, update the\n property\n- Minor string fixes, append appropriate tags\n\nLink to v4: https://patchwork.libcamera.org/patch/23965/\n\nNote: Checkstyle will complain on this patch on a hunk, but the existing format\nis quite read-able IMO, hence ignored.\n---\n src/gstreamer/gstlibcamera-utils.cpp | 60 ++++++++++++++++++++++++++++\n src/gstreamer/gstlibcamera-utils.h | 5 +++\n src/gstreamer/gstlibcamerasrc.cpp | 58 +++++++++++++++++++++++++++\n 3 files changed, 123 insertions(+)", "diff": "diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp\nindex bfb094c9..79fb606b 100644\n--- a/src/gstreamer/gstlibcamera-utils.cpp\n+++ b/src/gstreamer/gstlibcamera-utils.cpp\n@@ -357,6 +357,42 @@ control_type_to_gtype(const ControlType &type)\n \treturn G_TYPE_INVALID;\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+}\n+\n GstCaps *\n gst_libcamera_stream_formats_to_caps(const StreamFormats &formats)\n {\n@@ -898,3 +934,27 @@ int gst_libcamera_set_structure_field(GstStructure *structure, const ControlId *\n \n \treturn 0;\n }\n+\n+const gchar *\n+gst_libcamera_transform_to_tag_string(libcamera::Transform transform)\n+{\n+\tswitch (transform) {\n+\tcase Transform::Rot90:\n+\t\treturn \"rotate-90\";\n+\tcase Transform::Rot180:\n+\t\treturn \"rotate-180\";\n+\tcase Transform::Rot270:\n+\t\treturn \"rotate-270\";\n+\tcase Transform::HFlip:\n+\t\treturn \"flip-rotate-0\";\n+\tcase Transform::VFlip:\n+\t\treturn \"flip-rotate-180\";\n+\tcase Transform::Transpose:\n+\t\treturn \"flip-rotate-270\";\n+\tcase Transform::Rot180Transpose:\n+\t\treturn \"flip-rotate-90\";\n+\tcase Transform::Identity:\n+\tdefault:\n+\t\treturn \"rotate-0\";\n+\t}\n+}\ndiff --git a/src/gstreamer/gstlibcamera-utils.h b/src/gstreamer/gstlibcamera-utils.h\nindex 35df56fb..b7983264 100644\n--- a/src/gstreamer/gstlibcamera-utils.h\n+++ b/src/gstreamer/gstlibcamera-utils.h\n@@ -10,7 +10,9 @@\n \n #include <libcamera/camera_manager.h>\n #include <libcamera/controls.h>\n+#include <libcamera/orientation.h>\n #include <libcamera/stream.h>\n+#include <libcamera/transform.h>\n \n #include <gst/gst.h>\n #include <gst/video/video.h>\n@@ -32,6 +34,9 @@ libcamera::Rectangle gst_libcamera_gvalue_get_rectangle(const GValue *value);\n int gst_libcamera_set_structure_field(GstStructure *structure,\n \t\t\t\t const libcamera::ControlId *id,\n \t\t\t\t const libcamera::ControlValue &value);\n+libcamera::Orientation gst_video_orientation_to_libcamera_orientation(GstVideoOrientationMethod method);\n+GstVideoOrientationMethod libcamera_orientation_to_gst_video_orientation(libcamera::Orientation orientation);\n+const gchar *gst_libcamera_transform_to_tag_string(libcamera::Transform transform);\n \n #if !GST_CHECK_VERSION(1, 16, 0)\n static inline void gst_clear_event(GstEvent **event_ptr)\ndiff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp\nindex 391b228d..77648e6a 100644\n--- a/src/gstreamer/gstlibcamerasrc.cpp\n+++ b/src/gstreamer/gstlibcamerasrc.cpp\n@@ -141,6 +141,7 @@ struct _GstLibcameraSrc {\n \tGstTask *task;\n \n \tgchar *camera_name;\n+\tGstVideoOrientationMethod orientation;\n \n \tstd::atomic<GstEvent *> pending_eos;\n \n@@ -152,9 +153,12 @@ struct _GstLibcameraSrc {\n enum {\n \tPROP_0,\n \tPROP_CAMERA_NAME,\n+\tPROP_ORIENTATION,\n \tPROP_LAST\n };\n \n+static GParamSpec *properties[PROP_LAST];\n+\n static void gst_libcamera_src_child_proxy_init(gpointer g_iface,\n \t\t\t\t\t gpointer iface_data);\n \n@@ -606,6 +610,13 @@ gst_libcamera_src_negotiate(GstLibcameraSrc *self)\n \t\tgst_libcamera_get_framerate_from_caps(caps, element_caps);\n \t}\n \n+\t/* Set orientation in libcamera camera configuration. */\n+\tOrientation requestedLibcameraOrientation = gst_video_orientation_to_libcamera_orientation(self->orientation);\n+\t{\n+\t\tGLibLocker lock(GST_OBJECT(self));\n+\t\tstate->config_->orientation = requestedLibcameraOrientation;\n+\t}\n+\n \t/* Validate the configuration. */\n \tCameraConfiguration::Status status = state->config_->validate();\n \tif (status == CameraConfiguration::Invalid)\n@@ -627,6 +638,33 @@ gst_libcamera_src_negotiate(GstLibcameraSrc *self)\n \tgst_libcamera_clamp_and_set_frameduration(state->initControls_,\n \t\t\t\t\t\t state->cam_->controls(), element_caps);\n \n+\t/*\n+\t * If the requested orientation isn't possible, report the libcamera-provided orientation\n+\t * on GstBus and send the \"image-orientation\" tag downstream for elements to apply\n+\t * the final requested orientation.\n+\t */\n+\tif (state->config_->orientation != requestedLibcameraOrientation) {\n+\t\t{\n+\t\t\tGLibLocker lock(GST_OBJECT(self));\n+\t\t\tself->orientation = libcamera_orientation_to_gst_video_orientation(state->config_->orientation);\n+\t\t}\n+\n+\t\tGValue v = G_VALUE_INIT;\n+\t\tg_value_init(&v, properties[PROP_ORIENTATION]->value_type);\n+\t\tg_object_get_property(G_OBJECT(self), properties[PROP_ORIENTATION]->name, &v);\n+\t\tGstMessage *msg = gst_message_new_property_notify(GST_OBJECT(self), properties[PROP_ORIENTATION]->name, &v);\n+\t\tgst_element_post_message(GST_ELEMENT(self), msg);\n+\n+\t\tTransform toApply = requestedLibcameraOrientation / state->config_->orientation;\n+\t\tconst gchar *transform_tag = gst_libcamera_transform_to_tag_string(toApply);\n+\n+\t\tg_autoptr(GstEvent) tag_event =\n+\t\t\tgst_event_new_tag(gst_tag_list_new(GST_TAG_IMAGE_ORIENTATION, transform_tag, NULL));\n+\n+\t\tfor (gsize i = 0; i < state->srcpads_.size(); i++)\n+\t\t\tgst_pad_push_event(state->srcpads_[i], gst_event_ref(tag_event));\n+\t}\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@@ -934,6 +972,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 = static_cast<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@@ -953,6 +994,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, static_cast<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@@ -1162,6 +1206,20 @@ gst_libcamera_src_class_init(GstLibcameraSrcClass *klass)\n \t\t\t\t\t\t\t | G_PARAM_STATIC_STRINGS));\n \tg_object_class_install_property(object_class, PROP_CAMERA_NAME, spec);\n \n+\n+\tproperties[PROP_ORIENTATION] = g_param_spec_enum(\"orientation\", \"orientation\",\n+\t\t\t\t\t\t\t \"Property to control flipping and rotation operations of the camera. \"\n+\t\t\t\t\t\t\t \"If the orientation cannot be satisfied, libcamerasrc will send 'image-orientation' \"\n+\t\t\t\t\t\t\t \"tag downstream to assist with the orientation that was requested. Sinks that \"\n+\t\t\t\t\t\t\t \"implement the GST_VIDEO_ORIENTATION_AUTO should rotate the stream accordingly. \",\n+\t\t\t\t\t\t\t GST_TYPE_VIDEO_ORIENTATION_METHOD,\n+\t\t\t\t\t\t\t GST_VIDEO_ORIENTATION_IDENTITY,\n+\t\t\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+\tg_object_class_install_property(object_class, PROP_ORIENTATION, properties[PROP_ORIENTATION]);\n+\n \tGstCameraControls::installProperties(object_class, PROP_LAST);\n }\n \n", "prefixes": [ "v6" ] }