From dbb8abc1178720020ca872361e23b2e941b874f4 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Fri, 21 Apr 2023 17:38:15 +0200 Subject: Fix video for cameras with rotated image --- plugins/rtp/src/device.vala | 28 ++++++++++-- plugins/rtp/src/plugin.vala | 4 +- plugins/rtp/src/stream.vala | 91 +++++++++++++++++++++++++++++++++++---- plugins/rtp/src/video_widget.vala | 14 +++++- 4 files changed, 122 insertions(+), 15 deletions(-) (limited to 'plugins') diff --git a/plugins/rtp/src/device.vala b/plugins/rtp/src/device.vala index 1db8c996..7fee7307 100644 --- a/plugins/rtp/src/device.vala +++ b/plugins/rtp/src/device.vala @@ -215,7 +215,12 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object { } if (new_width == active_caps_width) return; int new_height = device_caps_height * new_width / device_caps_width; - Gst.Caps new_caps = new Gst.Caps.simple("video/x-raw", "width", typeof(int), new_width, "height", typeof(int), new_height, "framerate", typeof(Gst.Fraction), device_caps_framerate_num, device_caps_framerate_den, null); + Gst.Caps new_caps; + if (device_caps_framerate_den != 0) { + new_caps = new Gst.Caps.simple("video/x-raw", "width", typeof(int), new_width, "height", typeof(int), new_height, "framerate", typeof(Gst.Fraction), device_caps_framerate_num, device_caps_framerate_den, null); + } else { + new_caps = new Gst.Caps.simple("video/x-raw", "width", typeof(int), new_width, "height", typeof(int), new_height, null); + } double required_bitrate = get_target_bitrate(new_caps); debug("Changing resolution width from %d to %d (requires bitrate %f, current target is %u)", active_caps_width, new_width, required_bitrate, bitrate); if (bitrate < required_bitrate && new_width > active_caps_width) return; @@ -347,7 +352,7 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object { if (media == "audio") { return Gst.Caps.from_string("audio/x-raw,rate=48000,channels=1"); } else if (media == "video" && device.caps.get_size() > 0) { - int best_index = 0; + int best_index = -1; Value? best_fraction = null; int best_fps = 0; int best_width = 0; @@ -390,10 +395,25 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object { best_fraction = best_fraction_now; } } + if (best_index == -1) { + // No caps in first round, try without framerate + for (int i = 0; i < device.caps.get_size(); i++) { + unowned Gst.Structure? that = device.caps.get_structure(i); + if (!that.has_name("video/x-raw")) continue; + int width = 0, height = 0; + if (!that.has_field("width") || !that.get_int("width", out width)) continue; + if (!that.has_field("height") || !that.get_int("height", out height)) continue; + if (best_width < width || best_width == width && best_height < height) { + best_width = width; + best_height = height; + best_index = i; + } + } + } Gst.Caps res = caps_copy_nth(device.caps, best_index); unowned Gst.Structure? that = res.get_structure(0); - Value framerate = that.get_value("framerate"); - if (framerate.type() == typeof(Gst.ValueList)) { + Value? framerate = that.get_value("framerate"); + if (framerate != null && framerate.type() == typeof(Gst.ValueList) && best_fraction != null) { that.set_value("framerate", best_fraction); } debug("Selected caps %s", res.to_string()); diff --git a/plugins/rtp/src/plugin.vala b/plugins/rtp/src/plugin.vala index aefe41ff..98b9717d 100644 --- a/plugins/rtp/src/plugin.vala +++ b/plugins/rtp/src/plugin.vala @@ -426,11 +426,11 @@ public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object { if (media == "video") { // Pick best FPS - int max_fps = 0; + int max_fps = -1; Device? max_fps_device = null; foreach (Device device in devices) { int fps = get_max_fps(device); - if (fps > max_fps) { + if (fps > max_fps || max_fps_device == null) { max_fps = fps; max_fps_device = device; } diff --git a/plugins/rtp/src/stream.vala b/plugins/rtp/src/stream.vala index abdda776..94549856 100644 --- a/plugins/rtp/src/stream.vala +++ b/plugins/rtp/src/stream.vala @@ -102,6 +102,9 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { send_rtp.drop = true; send_rtp.wait_on_eos = false; send_rtp.new_sample.connect(on_new_sample); +#if GST_1_20 + send_rtp.new_serialized_event.connect(on_new_event); +#endif send_rtp.connect("signal::eos", on_eos_static, this); pipe.add(send_rtp); @@ -294,6 +297,60 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { } } + bool flip = false; + uint8 rotation = 0; +#if GST_1_20 + private bool on_new_event(Gst.App.Sink sink) { + if (sink == null || sink != send_rtp) { + return false; + } + Gst.MiniObject obj = sink.try_pull_object(0); + if (obj.type == typeof(Gst.Event)) { + unowned Gst.TagList tags; + if (((Gst.Event)obj).type == Gst.EventType.TAG) { + ((Gst.Event)obj).parse_tag(out tags); + Gst.Video.OrientationMethod orientation_method; + Gst.Video.Orientation.from_tag(tags, out orientation_method); + switch (orientation_method) { + case Gst.Video.OrientationMethod.IDENTITY: + case Gst.Video.OrientationMethod.VERT: + default: + rotation = 0; + break; + case Gst.Video.OrientationMethod.@90R: + case Gst.Video.OrientationMethod.UL_LR: + rotation = 1; + break; + case Gst.Video.OrientationMethod.@180: + case Gst.Video.OrientationMethod.HORIZ: + rotation = 2; + break; + case Gst.Video.OrientationMethod.@90L: + case Gst.Video.OrientationMethod.UR_LL: + rotation = 3; + break; + } + switch (orientation_method) { + case Gst.Video.OrientationMethod.IDENTITY: + case Gst.Video.OrientationMethod.@90R: + case Gst.Video.OrientationMethod.@180: + case Gst.Video.OrientationMethod.@90L: + default: + flip = false; + break; + case Gst.Video.OrientationMethod.VERT: + case Gst.Video.OrientationMethod.UL_LR: + case Gst.Video.OrientationMethod.HORIZ: + case Gst.Video.OrientationMethod.UR_LL: + flip = true; + break; + } + } + } + return false; + } +#endif + private Gst.FlowReturn on_new_sample(Gst.App.Sink sink) { if (sink == null) { debug("Sink is null"); @@ -323,6 +380,24 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { #endif } +#if GST_1_20 + if (sink == send_rtp) { + Xmpp.Xep.JingleRtp.HeaderExtension? ext = header_extensions.first_match((it) => it.uri == "urn:3gpp:video-orientation"); + if (ext != null) { + buffer = (Gst.Buffer) buffer.make_writable(); + Gst.RTP.Buffer rtp_buffer; + if (Gst.RTP.Buffer.map(buffer, Gst.MapFlags.WRITE, out rtp_buffer)) { + uint8[] extension_data = new uint8[1]; + bool camera = false; + extension_data[0] = extension_data[0] | (rotation & 0x3); + if (flip) extension_data[0] = extension_data[0] | 0x4; + if (camera) extension_data[0] = extension_data[0] | 0x8; + rtp_buffer.add_extension_onebyte_header(ext.id, extension_data); + } + } + } +#endif + prepare_local_crypto(); uint8[] data; @@ -489,8 +564,8 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { } } - private uint16 previous_video_orientation_degree = uint16.MAX; - public signal void video_orientation_changed(uint16 degree); + private uint16 previous_incoming_video_orientation_degree = uint16.MAX; + public signal void incoming_video_orientation_changed(uint16 degree); public override void on_recv_rtp_data(Bytes bytes) { if (rtcp_mux && bytes.length >= 2 && bytes.get(1) >= 192 && bytes.get(1) < 224) { @@ -545,9 +620,9 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { case 2: rotation_degree = 180; break; case 3: rotation_degree = 270; break; } - if (rotation_degree != previous_video_orientation_degree) { - video_orientation_changed(rotation_degree); - previous_video_orientation_degree = rotation_degree; + if (rotation_degree != previous_incoming_video_orientation_degree) { + incoming_video_orientation_changed(rotation_degree); + previous_incoming_video_orientation_degree = rotation_degree; } } } @@ -723,7 +798,7 @@ public class Dino.Plugins.Rtp.VideoStream : Stream { private Gee.List outputs = new ArrayList(); private Gst.Element output_tee; private Gst.Element rotate; - private ulong video_orientation_changed_handler; + private ulong incoming_video_orientation_changed_handler; public VideoStream(Plugin plugin, Xmpp.Xep.Jingle.Content content) { base(plugin, content); @@ -731,7 +806,7 @@ public class Dino.Plugins.Rtp.VideoStream : Stream { } public override void create() { - video_orientation_changed_handler = video_orientation_changed.connect(on_video_orientation_changed); + incoming_video_orientation_changed_handler = incoming_video_orientation_changed.connect(on_video_orientation_changed); plugin.pause(); rotate = Gst.ElementFactory.make("videoflip", @"video_rotate_$rtpid"); pipe.add(rotate); @@ -780,7 +855,7 @@ public class Dino.Plugins.Rtp.VideoStream : Stream { output_tee.set_state(Gst.State.NULL); pipe.remove(output_tee); output_tee = null; - disconnect(video_orientation_changed_handler); + disconnect(incoming_video_orientation_changed_handler); } public override void add_output(Gst.Element element, Xmpp.Jid? participant) { diff --git a/plugins/rtp/src/video_widget.vala b/plugins/rtp/src/video_widget.vala index 0d66476b..20123c68 100644 --- a/plugins/rtp/src/video_widget.vala +++ b/plugins/rtp/src/video_widget.vala @@ -227,9 +227,21 @@ public class Dino.Plugins.Rtp.VideoWidget : Gtk.Widget, Dino.Plugins.VideoCallWi if (connected_device == null) return; plugin.pause(); pipe.add(sink); +#if GST_1_20 + prepare = Gst.parse_bin_from_description(@"videoflip video-direction=auto name=video_widget_$(id)_orientation ! videoflip method=horizontal-flip name=video_widget_$(id)_flip ! videoconvert name=video_widget_$(id)_convert", true); +#else prepare = Gst.parse_bin_from_description(@"videoflip method=horizontal-flip name=video_widget_$(id)_flip ! videoconvert name=video_widget_$(id)_convert", true); +#endif prepare.name = @"video_widget_$(id)_prepare"; - prepare.get_static_pad("sink").notify["caps"].connect(input_caps_changed); +#if GST_1_20 + if (prepare is Gst.Bin) { + ((Gst.Bin) prepare).get_by_name(@"video_widget_$(id)_flip").get_static_pad("sink").notify["caps"].connect(input_caps_changed); + } else { +#endif + prepare.get_static_pad("sink").notify["caps"].connect(input_caps_changed); +#if GST_1_20 + } +#endif pipe.add(prepare); connected_device_element = connected_device.link_source(); connected_device_element.link(prepare); -- cgit v1.2.3-70-g09d2