Message ID | 20250723093954.599102-1-giacomo.cappellini.87@gmail.com |
---|---|
State | Superseded |
Headers | show |
Series |
|
Related | show |
Hello Giacomo On Wed, Jul 23, 2025 at 11:39:37AM +0200, Giacomo Cappellini wrote: > 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 <giacomo.cappellini.87@gmail.com> > --- > src/gstreamer/gstlibcamera-utils.cpp | 32 +++++++ > src/gstreamer/gstlibcamera-utils.h | 4 + > src/gstreamer/gstlibcamerasrc.cpp | 124 ++++++++++++++++++++++++++- > 3 files changed, 159 insertions(+), 1 deletion(-) > Please run checkstyle for your patch before sending. There are many issues with this patch ($) ./utils/checkstyle.py HEAD > 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 <libcamera/control_ids.h> > #include <libcamera/formats.h> > +#include <libcamera/orientation.h> > +#include <gst/video/video.h> > > 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; > + } > +} We could something similar to buffer_map[] and format_map[] in the same file. This way the mappings are stated only once and would be easier to update later just in case. 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; } > \ 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 <libcamera/camera_manager.h> > #include <libcamera/controls.h> > #include <libcamera/stream.h> > +#include <libcamera/orientation.h> > > #include <gst/gst.h> > #include <gst/video/video.h> > @@ -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 <tuple> > #include <utility> > #include <vector> > +#include <sstream> > > #include <libcamera/camera.h> > #include <libcamera/camera_manager.h> > #include <libcamera/control_ids.h> > > #include <gst/base/base.h> > +#include <gst/video/video.h> > > #include "gstlibcamera-controls.h" > #include "gstlibcamera-utils.h" > @@ -146,6 +148,7 @@ struct _GstLibcameraSrc { > GstTask *task; > > gchar *camera_name; > + GstVideoOrientationMethod orientation; > > std::atomic<GstEvent *> 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<StreamConfiguration> orig_stream_cfgs; > + for (gsize i = 0; i < state->config_->size(); i++) > + orig_stream_cfgs.push_back(state->config_->at(i)); > + std::optional<SensorConfiguration> 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: Did you happen to see: https://patchwork.libcamera.org/patch/23898/ ? > + { > + 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); > } > > -- > 2.43.0 >
Thanks for the review. I was not aware of https://patchwork.libcamera.org/patch/23898/, we made changes to gst_libcamera_src_negotiate at same time/day. I see my contribution brings a more compete diffing for the CameraConfiguration::Adjusted case, but my code lacks the check if downstream can accept this new stream configuration and if not return a not-negotiated error. In v3 I've fixed the style errors and replaced old maps with orientation_map as you suggested. G.C. Il giorno mer 23 lug 2025 alle ore 13:37 Umang Jain <uajain@igalia.com> ha scritto: > > Hello Giacomo > > On Wed, Jul 23, 2025 at 11:39:37AM +0200, Giacomo Cappellini wrote: > > 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 <giacomo.cappellini.87@gmail.com> > > --- > > src/gstreamer/gstlibcamera-utils.cpp | 32 +++++++ > > src/gstreamer/gstlibcamera-utils.h | 4 + > > src/gstreamer/gstlibcamerasrc.cpp | 124 ++++++++++++++++++++++++++- > > 3 files changed, 159 insertions(+), 1 deletion(-) > > > > Please run checkstyle for your patch before sending. There are many > issues with this patch > ($) ./utils/checkstyle.py HEAD > > > 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 <libcamera/control_ids.h> > > #include <libcamera/formats.h> > > +#include <libcamera/orientation.h> > > +#include <gst/video/video.h> > > > > 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; > > + } > > +} > > We could something similar to buffer_map[] and format_map[] in the same > file. This way the mappings are stated only once and would be easier > to update later just in case. > > 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; > } > > > \ 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 <libcamera/camera_manager.h> > > #include <libcamera/controls.h> > > #include <libcamera/stream.h> > > +#include <libcamera/orientation.h> > > > > #include <gst/gst.h> > > #include <gst/video/video.h> > > @@ -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 <tuple> > > #include <utility> > > #include <vector> > > +#include <sstream> > > > > #include <libcamera/camera.h> > > #include <libcamera/camera_manager.h> > > #include <libcamera/control_ids.h> > > > > #include <gst/base/base.h> > > +#include <gst/video/video.h> > > > > #include "gstlibcamera-controls.h" > > #include "gstlibcamera-utils.h" > > @@ -146,6 +148,7 @@ struct _GstLibcameraSrc { > > GstTask *task; > > > > gchar *camera_name; > > + GstVideoOrientationMethod orientation; > > > > std::atomic<GstEvent *> 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<StreamConfiguration> orig_stream_cfgs; > > + for (gsize i = 0; i < state->config_->size(); i++) > > + orig_stream_cfgs.push_back(state->config_->at(i)); > > + std::optional<SensorConfiguration> 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: > > Did you happen to see: https://patchwork.libcamera.org/patch/23898/ ? > > > + { > > + 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); > > } > > > > -- > > 2.43.0 > >
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 <libcamera/control_ids.h> #include <libcamera/formats.h> +#include <libcamera/orientation.h> +#include <gst/video/video.h> 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 <libcamera/camera_manager.h> #include <libcamera/controls.h> #include <libcamera/stream.h> +#include <libcamera/orientation.h> #include <gst/gst.h> #include <gst/video/video.h> @@ -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 <tuple> #include <utility> #include <vector> +#include <sstream> #include <libcamera/camera.h> #include <libcamera/camera_manager.h> #include <libcamera/control_ids.h> #include <gst/base/base.h> +#include <gst/video/video.h> #include "gstlibcamera-controls.h" #include "gstlibcamera-utils.h" @@ -146,6 +148,7 @@ struct _GstLibcameraSrc { GstTask *task; gchar *camera_name; + GstVideoOrientationMethod orientation; std::atomic<GstEvent *> 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<StreamConfiguration> orig_stream_cfgs; + for (gsize i = 0; i < state->config_->size(); i++) + orig_stream_cfgs.push_back(state->config_->at(i)); + std::optional<SensorConfiguration> 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); }
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 <giacomo.cappellini.87@gmail.com> --- src/gstreamer/gstlibcamera-utils.cpp | 32 +++++++ src/gstreamer/gstlibcamera-utils.h | 4 + src/gstreamer/gstlibcamerasrc.cpp | 124 ++++++++++++++++++++++++++- 3 files changed, 159 insertions(+), 1 deletion(-)