From patchwork Thu Nov 6 13:01:33 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Umang Jain X-Patchwork-Id: 24979 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id A8FFCC3241 for ; Thu, 6 Nov 2025 13:01:09 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7DAE560A80; Thu, 6 Nov 2025 14:01:08 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=igalia.com header.i=@igalia.com header.b="I3w2x73D"; dkim-atps=neutral Received: from fanzine2.igalia.com (fanzine2.igalia.com [213.97.179.56]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C034A606E6 for ; Thu, 6 Nov 2025 14:01:05 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=igalia.com; s=20170329; h=Content-Transfer-Encoding:MIME-Version:Message-ID:Date:Subject: Cc:To:From:Sender:Reply-To:Content-Type:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=s3jNAGTYi+WKCgceguE0B3hRf/N/bwxCQxVzN+mBSNk=; b=I3w2x73DIvgeCAf2pT6aHuQrXu 17KWsu2MOtcBrBT1iA+TEZTtETkyc1/Kp/ttEsA95S5RwuMkrDsjgxIaTjEv4iCcOtle4Wywu8gno 2NPVuggJdP1nhTTVHQwC17R73+8zEwF3KeGK2Ty1/TWAm9pX30a9drtBEQTvX18mR+gF4EdOYNgCj xb0hrYwOBuy6EPjhwsV7WaH+NJC2D9iLN9PgH0hl2Y8wSc+/itoFwn25ufC05RxXX8Mytgua+7T35 Jpf9uJ1Nu4iLbWZfRnjMG9UDpPze/CtE8KfGFXeZ6NY/U7aHr4wXD+XOvj1n6AlVhVmbIRTHWSuch g3Fj8w0w==; Received: from [81.140.124.245] (helo=uajain) by fanzine2.igalia.com with esmtpsa (Cipher TLS1.3:ECDHE_X25519__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1vGzbs-002zsB-KK; Thu, 06 Nov 2025 14:01:04 +0100 From: Umang Jain To: libcamera-devel@lists.libcamera.org Cc: Nicolas Dufresne , Umang Jain Subject: [PATCH v6] gstreamer: Plumb camera orientation configuration in libcamerasrc 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 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Plumb the support for CameraConfiguration::orientation in libcamerasrc. A new "orientation" property is introduced and mappings for libcamera::Orientation <> GstVideoOrientationMethod are provided with helpers. If the orientation is changed after the CameraConfiguration::validate(), a GstMessage is sent on the GstBus to report the new orientation property value. In case the orientation that was requested cannot be satisfied, a 'image-orientation' tag is pushed downstream for sinks to rotate the stream accordingly (those implementing GSTGST_VIDEO_ORIENTATION_AUTO). Signed-off-by: Umang Jain --- Changes in v6: - Use GstMessage to send new property changes if any (like orientation after validate()) - send 'image-orientation' tag orientation downstream if orientation cannot be satisfied - Drop 'video-orientation' and restore 'orientation' as property name - change authorship to myself as patch is reworked significantly Changes in v5: - patch scrubbing and cleanup - If different orientation is returned than requested, update the property - Minor string fixes, append appropriate tags Link to v4: https://patchwork.libcamera.org/patch/23965/ Note: Checkstyle will complain on this patch on a hunk, but the existing format is quite read-able IMO, hence ignored. --- src/gstreamer/gstlibcamera-utils.cpp | 60 ++++++++++++++++++++++++++++ src/gstreamer/gstlibcamera-utils.h | 5 +++ src/gstreamer/gstlibcamerasrc.cpp | 58 +++++++++++++++++++++++++++ 3 files changed, 123 insertions(+) diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp index bfb094c9..79fb606b 100644 --- a/src/gstreamer/gstlibcamera-utils.cpp +++ b/src/gstreamer/gstlibcamera-utils.cpp @@ -357,6 +357,42 @@ control_type_to_gtype(const ControlType &type) return G_TYPE_INVALID; } +static const struct { + Orientation orientation; + GstVideoOrientationMethod method; +} orientation_map[]{ + { Orientation::Rotate0, GST_VIDEO_ORIENTATION_IDENTITY }, + { Orientation::Rotate90, GST_VIDEO_ORIENTATION_90R }, + { Orientation::Rotate180, GST_VIDEO_ORIENTATION_180 }, + { Orientation::Rotate270, GST_VIDEO_ORIENTATION_90L }, + { Orientation::Rotate0Mirror, GST_VIDEO_ORIENTATION_HORIZ }, + { Orientation::Rotate180Mirror, GST_VIDEO_ORIENTATION_VERT }, + { Orientation::Rotate90Mirror, GST_VIDEO_ORIENTATION_UL_LR }, + { Orientation::Rotate270Mirror, GST_VIDEO_ORIENTATION_UR_LL }, +}; + +Orientation +gst_video_orientation_to_libcamera_orientation(GstVideoOrientationMethod method) +{ + for (auto &b : orientation_map) { + if (b.method == method) + return b.orientation; + } + + return Orientation::Rotate0; +} + +GstVideoOrientationMethod +libcamera_orientation_to_gst_video_orientation(Orientation orientation) +{ + for (auto &a : orientation_map) { + if (a.orientation == orientation) + return a.method; + } + + return GST_VIDEO_ORIENTATION_IDENTITY; +} + GstCaps * gst_libcamera_stream_formats_to_caps(const StreamFormats &formats) { @@ -898,3 +934,27 @@ int gst_libcamera_set_structure_field(GstStructure *structure, const ControlId * return 0; } + +const gchar * +gst_libcamera_transform_to_tag_string(libcamera::Transform transform) +{ + switch (transform) { + case Transform::Rot90: + return "rotate-90"; + case Transform::Rot180: + return "rotate-180"; + case Transform::Rot270: + return "rotate-270"; + case Transform::HFlip: + return "flip-rotate-0"; + case Transform::VFlip: + return "flip-rotate-180"; + case Transform::Transpose: + return "flip-rotate-270"; + case Transform::Rot180Transpose: + return "flip-rotate-90"; + case Transform::Identity: + default: + return "rotate-0"; + } +} diff --git a/src/gstreamer/gstlibcamera-utils.h b/src/gstreamer/gstlibcamera-utils.h index 35df56fb..b7983264 100644 --- a/src/gstreamer/gstlibcamera-utils.h +++ b/src/gstreamer/gstlibcamera-utils.h @@ -10,7 +10,9 @@ #include #include +#include #include +#include #include #include @@ -32,6 +34,9 @@ libcamera::Rectangle gst_libcamera_gvalue_get_rectangle(const GValue *value); int gst_libcamera_set_structure_field(GstStructure *structure, const libcamera::ControlId *id, const libcamera::ControlValue &value); +libcamera::Orientation gst_video_orientation_to_libcamera_orientation(GstVideoOrientationMethod method); +GstVideoOrientationMethod libcamera_orientation_to_gst_video_orientation(libcamera::Orientation orientation); +const gchar *gst_libcamera_transform_to_tag_string(libcamera::Transform transform); #if !GST_CHECK_VERSION(1, 16, 0) static inline void gst_clear_event(GstEvent **event_ptr) diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp index 391b228d..77648e6a 100644 --- a/src/gstreamer/gstlibcamerasrc.cpp +++ b/src/gstreamer/gstlibcamerasrc.cpp @@ -141,6 +141,7 @@ struct _GstLibcameraSrc { GstTask *task; gchar *camera_name; + GstVideoOrientationMethod orientation; std::atomic pending_eos; @@ -152,9 +153,12 @@ struct _GstLibcameraSrc { enum { PROP_0, PROP_CAMERA_NAME, + PROP_ORIENTATION, PROP_LAST }; +static GParamSpec *properties[PROP_LAST]; + static void gst_libcamera_src_child_proxy_init(gpointer g_iface, gpointer iface_data); @@ -606,6 +610,13 @@ gst_libcamera_src_negotiate(GstLibcameraSrc *self) gst_libcamera_get_framerate_from_caps(caps, element_caps); } + /* Set orientation in libcamera camera configuration. */ + Orientation requestedLibcameraOrientation = gst_video_orientation_to_libcamera_orientation(self->orientation); + { + GLibLocker lock(GST_OBJECT(self)); + state->config_->orientation = requestedLibcameraOrientation; + } + /* Validate the configuration. */ CameraConfiguration::Status status = state->config_->validate(); if (status == CameraConfiguration::Invalid) @@ -627,6 +638,33 @@ gst_libcamera_src_negotiate(GstLibcameraSrc *self) gst_libcamera_clamp_and_set_frameduration(state->initControls_, state->cam_->controls(), element_caps); + /* + * If the requested orientation isn't possible, report the libcamera-provided orientation + * on GstBus and send the "image-orientation" tag downstream for elements to apply + * the final requested orientation. + */ + if (state->config_->orientation != requestedLibcameraOrientation) { + { + GLibLocker lock(GST_OBJECT(self)); + self->orientation = libcamera_orientation_to_gst_video_orientation(state->config_->orientation); + } + + GValue v = G_VALUE_INIT; + g_value_init(&v, properties[PROP_ORIENTATION]->value_type); + g_object_get_property(G_OBJECT(self), properties[PROP_ORIENTATION]->name, &v); + GstMessage *msg = gst_message_new_property_notify(GST_OBJECT(self), properties[PROP_ORIENTATION]->name, &v); + gst_element_post_message(GST_ELEMENT(self), msg); + + Transform toApply = requestedLibcameraOrientation / state->config_->orientation; + const gchar *transform_tag = gst_libcamera_transform_to_tag_string(toApply); + + g_autoptr(GstEvent) tag_event = + gst_event_new_tag(gst_tag_list_new(GST_TAG_IMAGE_ORIENTATION, transform_tag, NULL)); + + for (gsize i = 0; i < state->srcpads_.size(); i++) + gst_pad_push_event(state->srcpads_[i], gst_event_ref(tag_event)); + } + /* * Regardless if it has been modified, create clean caps and push the * caps event. Downstream will decide if the caps are acceptable. @@ -934,6 +972,9 @@ gst_libcamera_src_set_property(GObject *object, guint prop_id, g_free(self->camera_name); self->camera_name = g_value_dup_string(value); break; + case PROP_ORIENTATION: + self->orientation = static_cast(g_value_get_enum(value)); + break; default: if (!state->controls_.setProperty(prop_id - PROP_LAST, value, pspec)) G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); @@ -953,6 +994,9 @@ gst_libcamera_src_get_property(GObject *object, guint prop_id, GValue *value, case PROP_CAMERA_NAME: g_value_set_string(value, self->camera_name); break; + case PROP_ORIENTATION: + g_value_set_enum(value, static_cast(self->orientation)); + break; default: if (!state->controls_.getProperty(prop_id - PROP_LAST, value, pspec)) G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); @@ -1162,6 +1206,20 @@ gst_libcamera_src_class_init(GstLibcameraSrcClass *klass) | G_PARAM_STATIC_STRINGS)); g_object_class_install_property(object_class, PROP_CAMERA_NAME, spec); + + properties[PROP_ORIENTATION] = g_param_spec_enum("orientation", "orientation", + "Property to control flipping and rotation operations of the camera. " + "If the orientation cannot be satisfied, libcamerasrc will send 'image-orientation' " + "tag downstream to assist with the orientation that was requested. Sinks that " + "implement the GST_VIDEO_ORIENTATION_AUTO should rotate the stream accordingly. ", + GST_TYPE_VIDEO_ORIENTATION_METHOD, + GST_VIDEO_ORIENTATION_IDENTITY, + (GParamFlags)(GST_PARAM_MUTABLE_READY + | G_PARAM_CONSTRUCT + | G_PARAM_READWRITE + | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property(object_class, PROP_ORIENTATION, properties[PROP_ORIENTATION]); + GstCameraControls::installProperties(object_class, PROP_LAST); }