From patchwork Wed Jul 23 09:39:37 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Giacomo Cappellini X-Patchwork-Id: 23901 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 054FBBDCC1 for ; Wed, 23 Jul 2025 09:40:08 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 17C416904D; Wed, 23 Jul 2025 11:40:07 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="Jflei3E8"; dkim-atps=neutral Received: from mail-ej1-x62f.google.com (mail-ej1-x62f.google.com [IPv6:2a00:1450:4864:20::62f]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id B18CC68FB6 for ; Wed, 23 Jul 2025 11:40:05 +0200 (CEST) Received: by mail-ej1-x62f.google.com with SMTP id a640c23a62f3a-ae0c571f137so1223657566b.0 for ; Wed, 23 Jul 2025 02:40:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1753263605; x=1753868405; darn=lists.libcamera.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=frtNiTtbW4je1h3Op+wmlTSSukxlBLL4xo8u1I2THCc=; b=Jflei3E8upOMtcCqn+O0tb2yFb1VkAfM0zEBU9RG8jAWCWYpUAvrlK2Ue0ck2kqcAW JjmMFPUx/ugJPRhqDaYs3bASJkOaOKbcgeNGx53gJRCQhpGjrPd47FBIgEQdoIq7W/6j 3T2f2CF0kawHz8IEadc/XUO91GZd2CK+ouMF6p+hyxH9EdPH2r5xM8RRZjm+4qI5nzhP dmM4UKBF8t5wiY88alzZefZmhQvnF14F9Rqc4H/EosQGng8Y41N5qpZpHIeMA+EgoMut YYA909HAzSsmxn71n0s4bosTSX4Vyj80KE6ATjBOVIZUvYrZNeZkXdDTvKFNgCwMpouL selA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1753263605; x=1753868405; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=frtNiTtbW4je1h3Op+wmlTSSukxlBLL4xo8u1I2THCc=; b=fqDnQqdL2cwDCkeVPo/yZRPIq3sV3H5dZ2RyQoNg72uWYeA3zk7n58TOCt2X6aY79P 2AxJLMR/mJ6cfvqalkLWB3yUGKMwbg2do2NRDN/LcK65Kz7fIZGq/q61N5e4tmqJPPOV PHabbzgZ3okBKnB3y46wXVVK+EdoJRpk/A4Spz7n6NfCwx6EEsRRjZ3BQoUgJ2Un5dqn a0vARpI9KaQDhwAcVhFkteGAk0Uy6RsyDeMMS/UW5jrD5vjEjuVkNgVtJjMQbzlfww9O PbfoCanqXFZ6O6EKo/gnHcyGRD6gCyTBsLnnmTRTsesaMWGa4Sof2KF7tyN4gQz9ROzG 0EnA== X-Gm-Message-State: AOJu0YxUu3gXAdh+PJXKX1GW+vNnboz6LWurfhXE0inLTHMO6A+FqWLy vmHDuOUMxClhyOKOfCQ2QUe4gnhbr3MoroqWPa9hxDqilcdwp1MvxNhFdQBfazE+4Lw= X-Gm-Gg: ASbGncsMP/zgdn41hU5Ir/ZySwvVgE+PeRSwoMLsmsypq1zXaAsuWBniG0m27hICqrX Dfupgt5fZEjaCNOe/hG9zR3f06UM29Qhzy0l7DvofzcsGTB7L8YEiIwTiHsbH3RE+3f/8i63/j/ ghvdGq50cIjan2sq4PV6A8tcYrj8Ay2Oy7GB/s4R0HA6IwsVStrV1RdR9tVCsbpTLuTLtrHz/6/ hCRI+FiuDnjeLwMoVNnZsbnjnAOt2YNtZqUnYAnEPidsT3cfWUuYfZzCq5friPT6XSd7an4m8Ox S3+eg+584EPsDE7LYxY4BYz3YlpwOYVQJMEyhvQKTnqGIQ+E/FMcfj15wET8bcWHWVJeisz+EeI X3L+9qJv1fnDt4xCKCo2NbI4U3uq0OMuKmvhdn0U7dVPz8TqM3BijGyJdrdel6KHdakqLc3tSgp 0JpsAO1/NPm+CbGWqMx3kaKg== X-Google-Smtp-Source: AGHT+IGtB3qzr13XzkXeHj6anXyc2sb2nk6l2O/HjrP5Df30iTPZt5TQ0TkfvVET+1Lq9rPuv0KMFw== X-Received: by 2002:a17:907:d643:b0:ae3:d021:9b05 with SMTP id a640c23a62f3a-af2f6bfd8a8mr241351066b.15.1753263604648; Wed, 23 Jul 2025 02:40:04 -0700 (PDT) Received: from jasus.ad.servtec.it (host-95-251-230-143.retail.telecomitalia.it. [95.251.230.143]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-aec6c7d7783sm1023304566b.63.2025.07.23.02.40.03 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 23 Jul 2025 02:40:03 -0700 (PDT) From: Giacomo Cappellini To: libcamera-devel@lists.libcamera.org Cc: Giacomo Cappellini Subject: [PATCH v2] gstreamer: Add support for Orientation Date: Wed, 23 Jul 2025 11:39:37 +0200 Message-ID: <20250723093954.599102-1-giacomo.cappellini.87@gmail.com> X-Mailer: git-send-email 2.43.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" libcamera allows to control the images orientation through the CameraConfiguration::orientation field, expose a GST_PARAM_MUTABLE_READY parameter of type GstVideoOrientationMethod in GstLibcameraSrc to control it. Parameter is mapped internally to libcamera::Orientation via new gstlibcamera-utils functions: - gst_video_orientation_to_libcamera_orientation - libcamera_orientation_to_gst_video_orientation Update CameraConfiguration::Adjusted case in negotiation to warn about changes in StreamConfiguration and SensorConfiguration, as well as the new Orientation parameter. signed-off-by: Giacomo Cappellini --- src/gstreamer/gstlibcamera-utils.cpp | 32 +++++++ src/gstreamer/gstlibcamera-utils.h | 4 + src/gstreamer/gstlibcamerasrc.cpp | 124 ++++++++++++++++++++++++++- 3 files changed, 159 insertions(+), 1 deletion(-) diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp index a548b0c1..fc29d19d 100644 --- a/src/gstreamer/gstlibcamera-utils.cpp +++ b/src/gstreamer/gstlibcamera-utils.cpp @@ -10,6 +10,8 @@ #include #include +#include +#include using namespace libcamera; @@ -659,3 +661,33 @@ gst_libcamera_get_camera_manager(int &ret) return cm; } + +Orientation gst_video_orientation_to_libcamera_orientation(GstVideoOrientationMethod method) +{ + switch (method) { + case GST_VIDEO_ORIENTATION_IDENTITY: return Orientation::Rotate0; + case GST_VIDEO_ORIENTATION_90R: return Orientation::Rotate90; + case GST_VIDEO_ORIENTATION_180: return Orientation::Rotate180; + case GST_VIDEO_ORIENTATION_90L: return Orientation::Rotate270; + case GST_VIDEO_ORIENTATION_HORIZ: return Orientation::Rotate0Mirror; + case GST_VIDEO_ORIENTATION_VERT: return Orientation::Rotate180Mirror; + case GST_VIDEO_ORIENTATION_UL_LR: return Orientation::Rotate90Mirror; + case GST_VIDEO_ORIENTATION_UR_LL: return Orientation::Rotate270Mirror; + default: return Orientation::Rotate0; + } +} + +GstVideoOrientationMethod libcamera_orientation_to_gst_video_orientation(Orientation orientation) +{ + switch (orientation) { + case Orientation::Rotate0: return GST_VIDEO_ORIENTATION_IDENTITY; + case Orientation::Rotate90: return GST_VIDEO_ORIENTATION_90R; + case Orientation::Rotate180: return GST_VIDEO_ORIENTATION_180; + case Orientation::Rotate270: return GST_VIDEO_ORIENTATION_90L; + case Orientation::Rotate0Mirror: return GST_VIDEO_ORIENTATION_HORIZ; + case Orientation::Rotate180Mirror: return GST_VIDEO_ORIENTATION_VERT; + case Orientation::Rotate90Mirror: return GST_VIDEO_ORIENTATION_UL_LR; + case Orientation::Rotate270Mirror: return GST_VIDEO_ORIENTATION_UR_LL; + default: return GST_VIDEO_ORIENTATION_IDENTITY; + } +} \ No newline at end of file diff --git a/src/gstreamer/gstlibcamera-utils.h b/src/gstreamer/gstlibcamera-utils.h index 5f4e8a0f..3d4b049f 100644 --- a/src/gstreamer/gstlibcamera-utils.h +++ b/src/gstreamer/gstlibcamera-utils.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -92,3 +93,6 @@ public: private: GRecMutex *mutex_; }; + +libcamera::Orientation gst_video_orientation_to_libcamera_orientation(GstVideoOrientationMethod method); +GstVideoOrientationMethod libcamera_orientation_to_gst_video_orientation(libcamera::Orientation orientation); diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp index 3aca4eed..03d389aa 100644 --- a/src/gstreamer/gstlibcamerasrc.cpp +++ b/src/gstreamer/gstlibcamerasrc.cpp @@ -32,12 +32,14 @@ #include #include #include +#include #include #include #include #include +#include #include "gstlibcamera-controls.h" #include "gstlibcamera-utils.h" @@ -146,6 +148,7 @@ struct _GstLibcameraSrc { GstTask *task; gchar *camera_name; + GstVideoOrientationMethod orientation; std::atomic pending_eos; @@ -157,6 +160,7 @@ struct _GstLibcameraSrc { enum { PROP_0, PROP_CAMERA_NAME, + PROP_ORIENTATION, PROP_LAST }; @@ -616,9 +620,110 @@ gst_libcamera_src_negotiate(GstLibcameraSrc *self) gst_libcamera_get_framerate_from_caps(caps, element_caps); } + /* Set orientation control. */ + state->config_->orientation = gst_video_orientation_to_libcamera_orientation(self->orientation); + + /* Save original configuration for comparison after validation */ + std::vector orig_stream_cfgs; + for (gsize i = 0; i < state->config_->size(); i++) + orig_stream_cfgs.push_back(state->config_->at(i)); + std::optional orig_sensor_cfg = state->config_->sensorConfig; + Orientation orig_orientation = state->config_->orientation; + /* Validate the configuration. */ - if (state->config_->validate() == CameraConfiguration::Invalid) + switch(state->config_->validate()) { + case CameraConfiguration::Valid: + GST_DEBUG_OBJECT(self, "Camera configuration is valid"); + break; + case CameraConfiguration::Adjusted: + { + bool warned = false; + // Warn if number of StreamConfigurations changed + if (orig_stream_cfgs.size() != state->config_->size()) { + GST_WARNING_OBJECT(self, "Number of StreamConfiguration elements changed: requested=%zu, actual=%zu", + orig_stream_cfgs.size(), state->config_->size()); + warned = true; + } + // Warn about changes in each StreamConfiguration + // TODO implement diffing in StreamConfiguration + for (gsize i = 0; i < std::min(orig_stream_cfgs.size(), state->config_->size()); i++) { + if (orig_stream_cfgs[i].toString() != state->config_->at(i).toString()) { + GST_WARNING_OBJECT(self, "StreamConfiguration %zu changed: %s -> %s", + i, orig_stream_cfgs[i].toString().c_str(), + state->config_->at(i).toString().c_str()); + warned = true; + } + } + // Warn about SensorConfiguration changes + // TODO implement diffing in SensorConfiguration + if (orig_sensor_cfg.has_value() || state->config_->sensorConfig.has_value()) { + const SensorConfiguration *orig = orig_sensor_cfg.has_value() ? &orig_sensor_cfg.value() : nullptr; + const SensorConfiguration *curr = state->config_->sensorConfig.has_value() ? &state->config_->sensorConfig.value() : nullptr; + bool sensor_changed = false; + std::ostringstream diff; + if ((orig == nullptr) != (curr == nullptr)) { + diff << "SensorConfiguration presence changed: " + << (orig ? "was present" : "was absent") + << " -> " + << (curr ? "present" : "absent"); + sensor_changed = true; + } else if (orig && curr) { + if (orig->bitDepth != curr->bitDepth) { + diff << "bitDepth: " << orig->bitDepth << " -> " << curr->bitDepth << "; "; + sensor_changed = true; + } + if (orig->analogCrop != curr->analogCrop) { + diff << "analogCrop: " << orig->analogCrop.toString() << " -> " << curr->analogCrop.toString() << "; "; + sensor_changed = true; + } + if (orig->binning.binX != curr->binning.binX || + orig->binning.binY != curr->binning.binY) { + diff << "binning: (" << orig->binning.binX << "," << orig->binning.binY << ") -> (" + << curr->binning.binX << "," << curr->binning.binY << "); "; + sensor_changed = true; + } + if (orig->skipping.xOddInc != curr->skipping.xOddInc || + orig->skipping.xEvenInc != curr->skipping.xEvenInc || + orig->skipping.yOddInc != curr->skipping.yOddInc || + orig->skipping.yEvenInc != curr->skipping.yEvenInc) { + diff << "skipping: (" + << orig->skipping.xOddInc << "," << orig->skipping.xEvenInc << "," + << orig->skipping.yOddInc << "," << orig->skipping.yEvenInc << ") -> (" + << curr->skipping.xOddInc << "," << curr->skipping.xEvenInc << "," + << curr->skipping.yOddInc << "," << curr->skipping.yEvenInc << "); "; + sensor_changed = true; + } + if (orig->outputSize != curr->outputSize) { + diff << "outputSize: " << orig->outputSize.toString() << " -> " << curr->outputSize.toString() << "; "; + sensor_changed = true; + } + } + if (sensor_changed) { + GST_WARNING_OBJECT(self, "SensorConfiguration changed: %s", diff.str().c_str()); + warned = true; + } + } + // Warn about orientation change + if (orig_orientation != state->config_->orientation) { + GEnumClass *enum_class = (GEnumClass *)g_type_class_ref(GST_TYPE_VIDEO_ORIENTATION_METHOD); + const char *orig_orientation_str = g_enum_get_value(enum_class, libcamera_orientation_to_gst_video_orientation(orig_orientation))->value_nick; + const char *new_orientation_str = g_enum_get_value(enum_class, libcamera_orientation_to_gst_video_orientation(state->config_->orientation))->value_nick; + GST_WARNING_OBJECT(self, "Orientation changed: %s -> %s", orig_orientation_str, new_orientation_str); + warned = true; + } + if (!warned) { + GST_DEBUG_OBJECT(self, "Camera configuration adjusted, but no significant changes detected."); + } + // Update Gst orientation property to match adjusted config + self->orientation = libcamera_orientation_to_gst_video_orientation(state->config_->orientation); + break; + } + case CameraConfiguration::Invalid: + GST_ELEMENT_ERROR(self, RESOURCE, SETTINGS, + ("Camera configuration is not supported"), + ("CameraConfiguration::validate() returned Invalid")); return false; + } int ret = state->cam_->configure(state->config_.get()); if (ret) { @@ -926,6 +1031,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 = (GstVideoOrientationMethod)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); @@ -945,6 +1053,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, (gint)self->orientation); + break; default: if (!state->controls_.getProperty(prop_id - PROP_LAST, value, pspec)) G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); @@ -1154,6 +1265,17 @@ gst_libcamera_src_class_init(GstLibcameraSrcClass *klass) | G_PARAM_STATIC_STRINGS)); g_object_class_install_property(object_class, PROP_CAMERA_NAME, spec); + /* Register the orientation enum type. */ + spec = g_param_spec_enum("orientation", "Orientation", + "Select the orientation of the camera.", + 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, spec); + GstCameraControls::installProperties(object_class, PROP_LAST); }