From: Robert Mader <robert.mader@posteo.de>
Date: Mon, 19 Dec 2022 00:01:05 +0100
Subject: camera: Add videoflip for rotation support

Cameras on mobile devices are often rotated by 90 degrees. Sometimes the
sensor or the ISP can handle this in libcamera, but if not, it's left to
clients to do so.

Pipewires libcamera plugin and gstreamer source recently got support to
the relevant APIs to communicate rotations/transforms to clients/sinks,
only requiring clients to honor the `image-orientation` tag event, see
https://gitlab.freedesktop.org/pipewire/pipewire/-/merge_requests/1455

Implement support for the later by chaining the `videoflip` element to
camera source elements. Doing it there ensures that the rotation is
applied both for the preview as well as for capturing.
Note: if no rotatition is required, `videoflip` will go into `passthrough`
mode, avoiding unnecessary additional copies.

Once the rest of the pipeline is fully hardware accelerated, i.e. after
a GTK4 port, `videoflip` can be replaced with faster alternatives where
appropriate.

Origin: https://gitlab.gnome.org/jwestman/libaperture/-/merge_requests/22
---
 src/aperture-camera.c       | 22 +++++++++++++++++++++-
 tests/test-device-manager.c |  8 +++++++-
 2 files changed, 28 insertions(+), 2 deletions(-)

diff --git a/src/aperture-camera.c b/src/aperture-camera.c
index 9207faf..3bd648a 100644
--- a/src/aperture-camera.c
+++ b/src/aperture-camera.c
@@ -91,6 +91,11 @@ static GstElement *
 aperture_camera_get_source_element_impl (ApertureCamera *self, GstElement *previous)
 {
   ApertureCameraPrivate *priv;
+  GstElement *bin;
+  GstElement *device_element;
+  GstElement *videoflip;
+  g_autoptr(GstPad) pad = NULL;
+  GstPad *ghost_pad;
 
   g_return_val_if_fail (APERTURE_IS_CAMERA (self), NULL);
   g_return_val_if_fail (previous == NULL || GST_IS_ELEMENT (previous), NULL);
@@ -101,7 +106,22 @@ aperture_camera_get_source_element_impl (ApertureCamera *self, GstElement *previ
     return NULL;
   }
 
-  return gst_device_create_element (priv->gst_device, NULL);
+  bin = gst_bin_new (NULL);
+
+  device_element = gst_device_create_element (priv->gst_device, NULL);
+
+  videoflip = gst_element_factory_make ("videoflip", NULL);
+  g_object_set (videoflip, "video-direction", 8 /* GST_VIDEO_ORIENTATION_AUTO */, NULL);
+
+  gst_bin_add_many (GST_BIN (bin), device_element, videoflip, NULL);
+  gst_element_link (device_element, videoflip);
+
+  pad = gst_element_get_static_pad (videoflip, "src");
+  ghost_pad = gst_ghost_pad_new ("src", pad);
+  gst_pad_set_active (ghost_pad, TRUE);
+  gst_element_add_pad (bin, ghost_pad);
+
+  return bin;
 }
 
 
diff --git a/tests/test-device-manager.c b/tests/test-device-manager.c
index bb7ffd6..3d417bf 100644
--- a/tests/test-device-manager.c
+++ b/tests/test-device-manager.c
@@ -78,7 +78,13 @@ manager_contains_test_device (ApertureDeviceManager *manager)
     g_set_object (&camera, aperture_device_manager_get_camera (manager, i));
     if (APERTURE_IS_CAMERA (camera)) {
       g_autoptr(GstElement) element = aperture_camera_get_source_element (camera, NULL);
-      if (g_str_has_prefix (gst_object_get_name (GST_OBJECT (element)), "videotestsrc")) {
+      GstElement *device_element;
+      GList *children;
+
+      children = GST_BIN_CHILDREN (element);
+      device_element = g_list_last (children)->data;
+
+      if (g_str_has_prefix (gst_object_get_name (GST_OBJECT (device_element)), "videotestsrc")) {
         num_dummy_cameras ++;
       }
     }
