From ef2e3c774cab82a94a5e34399f2013d64c3cf03b Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sun, 21 Mar 2021 12:41:38 +0100 Subject: Add RTP implementation as plugin --- plugins/rtp/src/plugin.vala | 413 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 413 insertions(+) create mode 100644 plugins/rtp/src/plugin.vala (limited to 'plugins/rtp/src/plugin.vala') diff --git a/plugins/rtp/src/plugin.vala b/plugins/rtp/src/plugin.vala new file mode 100644 index 00000000..69b0f37a --- /dev/null +++ b/plugins/rtp/src/plugin.vala @@ -0,0 +1,413 @@ +using Gee; +using Xmpp; +using Xmpp.Xep; + +public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object { + public Dino.Application app { get; private set; } + public CodecUtil codec_util { get; private set; } + public Gst.DeviceMonitor device_monitor { get; private set; } + public Gst.Pipeline pipe { get; private set; } + public Gst.Bin rtpbin { get; private set; } + public Gst.Element echoprobe { get; private set; } + + private Gee.List streams = new ArrayList(); + private Gee.List devices = new ArrayList(); + // private Gee.List participants = new ArrayList(); + + public void registered(Dino.Application app) { + this.app = app; + this.codec_util = new CodecUtil(); + app.startup.connect(startup); + app.add_option_group(Gst.init_get_option_group()); + app.stream_interactor.module_manager.initialize_account_modules.connect((account, list) => { + list.add(new Module(this)); + }); + app.plugin_registry.video_call_plugin = this; + } + + private int pause_count = 0; + public void pause() { +// if (pause_count == 0) { +// debug("Pausing pipe for modifications"); +// pipe.set_state(Gst.State.PAUSED); +// } + pause_count++; + } + public void unpause() { + pause_count--; + if (pause_count == 0) { + debug("Continue pipe after modifications"); + pipe.set_state(Gst.State.PLAYING); + } + if (pause_count < 0) warning("Pause count below zero!"); + } + + public void startup() { + device_monitor = new Gst.DeviceMonitor(); + device_monitor.show_all = true; + device_monitor.get_bus().add_watch(Priority.DEFAULT, on_device_monitor_message); + device_monitor.start(); + + pipe = new Gst.Pipeline(null); + + // RTP + rtpbin = Gst.ElementFactory.make("rtpbin", null) as Gst.Bin; + if (rtpbin == null) { + warning("RTP not supported"); + pipe = null; + return; + } + rtpbin.pad_added.connect(on_rtp_pad_added); + rtpbin.@set("latency", 100); + rtpbin.connect("signal::request-pt-map", request_pt_map, this); + pipe.add(rtpbin); + + // Audio echo probe + echoprobe = Gst.ElementFactory.make("webrtcechoprobe", "echo-probe"); + pipe.add(echoprobe); + + // Pipeline + pipe.auto_flush_bus = true; + pipe.bus.add_watch(GLib.Priority.DEFAULT, (_, message) => { + on_pipe_bus_message(message); + return true; + }); + pipe.set_state(Gst.State.PLAYING); + } + + private static Gst.Caps? request_pt_map(Gst.Element rtpbin, uint session, uint pt, Plugin plugin) { + debug("request-pt-map"); + return null; + } + + private void on_rtp_pad_added(Gst.Pad pad) { + debug("pad added: %s", pad.name); + if (pad.name.has_prefix("recv_rtp_src_")) { + string[] split = pad.name.split("_"); + uint8 rtpid = (uint8)int.parse(split[3]); + foreach (Stream stream in streams) { + if (stream.rtpid == rtpid) { + stream.on_ssrc_pad_added(split[4], pad); + } + } + } + if (pad.name.has_prefix("send_rtp_src_")) { + string[] split = pad.name.split("_"); + uint8 rtpid = (uint8)int.parse(split[3]); + debug("pad %s for stream %hhu", pad.name, rtpid); + foreach (Stream stream in streams) { + if (stream.rtpid == rtpid) { + stream.on_send_rtp_src_added(pad); + } + } + } + } + + private void on_pipe_bus_message(Gst.Message message) { + switch (message.type) { + case Gst.MessageType.ERROR: + Error error; + string str; + message.parse_error(out error, out str); + warning("Error in pipeline: %s", error.message); + debug(str); + break; + case Gst.MessageType.WARNING: + Error error; + string str; + message.parse_warning(out error, out str); + warning("Warning in pipeline: %s", error.message); + debug(str); + break; + case Gst.MessageType.CLOCK_LOST: + debug("Clock lost. Restarting"); + pipe.set_state(Gst.State.READY); + pipe.set_state(Gst.State.PLAYING); + break; + case Gst.MessageType.STATE_CHANGED: + Gst.State new_state; + message.parse_state_changed(null, out new_state, null); + if (message.src is Gst.Element) { + debug("%s changed state to %s", ((Gst.Element)message.src).name, new_state.to_string()); + } + break; + case Gst.MessageType.STREAM_STATUS: + Gst.StreamStatusType status; + Gst.Element owner; + message.parse_stream_status(out status, out owner); + if (owner != null) { + debug("%s stream changed status to %s", owner.name, status.to_string()); + } + break; + case Gst.MessageType.ELEMENT: + unowned Gst.Structure struc = message.get_structure(); + if (struc != null && message.src is Gst.Element) { + debug("Message from %s in pipeline: %s", ((Gst.Element)message.src).name, struc.to_string()); + } + break; + case Gst.MessageType.NEW_CLOCK: + debug("New clock."); + break; + case Gst.MessageType.TAG: + // Ignore + break; + case Gst.MessageType.QOS: + // Ignore + break; + default: + debug("Pipe bus message: %s", message.type.to_string()); + break; + } + } + + private bool on_device_monitor_message(Gst.Bus bus, Gst.Message message) { + Gst.Device old_device = null; + Gst.Device device = null; + switch (message.type) { + case Gst.MessageType.DEVICE_ADDED: + message.parse_device_added(out device); + if (device.properties.has_name("pipewire-proplist") && device.device_class.has_prefix("Audio/")) return Source.CONTINUE; + if (device.properties.get_string("device.class") == "monitor") return Source.CONTINUE; + devices.add(new Device(this, device)); + break; + case Gst.MessageType.DEVICE_CHANGED: + message.parse_device_changed(out device, out old_device); + if (device.properties.has_name("pipewire-proplist") && device.device_class.has_prefix("Audio/")) return Source.CONTINUE; + if (device.properties.get_string("device.class") == "monitor") return Source.CONTINUE; + devices.first_match((it) => it.matches(old_device)).update(device); + break; + case Gst.MessageType.DEVICE_REMOVED: + message.parse_device_removed(out device); + if (device.properties.has_name("pipewire-proplist") && device.device_class.has_prefix("Audio/")) return Source.CONTINUE; + if (device.properties.get_string("device.class") == "monitor") return Source.CONTINUE; + devices.remove(devices.first_match((it) => it.matches(device))); + break; + } + if (device != null) { + switch (device.device_class) { + case "Audio/Source": + devices_changed("audio", false); + break; + case "Audio/Sink": + devices_changed("audio", true); + break; + case "Video/Source": + devices_changed("video", false); + break; + case "Video/Sink": + devices_changed("video", true); + break; + } + } + return Source.CONTINUE; + } + + public uint8 next_free_id() { + uint8 rtpid = 0; + while (streams.size < 100 && streams.any_match((stream) => stream.rtpid == rtpid)) { + rtpid++; + } + return rtpid; + } + + // public Participant get_participant(Jid full_jid, bool self) { +// foreach (Participant participant in participants) { +// if (participant.full_jid.equals(full_jid)) { +// return participant; +// } +// } +// Participant participant; +// if (self) { +// participant = new SelfParticipant(pipe, full_jid); +// } else { +// participant = new Participant(pipe, full_jid); +// } +// participants.add(participant); +// return participant; +// } + + public Stream open_stream(Xmpp.Xep.Jingle.Content content) { + var content_params = content.content_params as Xmpp.Xep.JingleRtp.Parameters; + if (content_params == null) return null; + Stream stream; + if (content_params.media == "video") { + stream = new VideoStream(this, content); + } else { + stream = new Stream(this, content); + } + streams.add(stream); + return stream; + } + + public void close_stream(Stream stream) { + streams.remove(stream); + stream.destroy(); + } + + public void shutdown() { + device_monitor.stop(); + pipe.set_state(Gst.State.NULL); + rtpbin = null; + pipe = null; + Gst.deinit(); + } + + public VideoCallWidget? create_widget(WidgetType type) { + if (type == WidgetType.GTK) { + return new VideoWidget(this); + } + return null; + } + + public Gee.List get_devices(string media, bool incoming) { + if (media == "video" && !incoming) { + return get_video_sources(); + } + + ArrayList result = new ArrayList(); + foreach (Device device in devices) { + if (device.media == media && (incoming && device.is_sink || !incoming && device.is_source)) { + result.add(device); + } + } + if (media == "audio") { + // Reorder sources + result.sort((media_left, media_right) => { + Device left = media_left as Device; + Device right = media_right as Device; + if (left == null) return 1; + if (right == null) return -1; + + bool left_is_pipewire = left.device.properties.has_name("pipewire-proplist"); + bool right_is_pipewire = right.device.properties.has_name("pipewire-proplist"); + + bool left_is_default = false; + left.device.properties.get_boolean("is-default", out left_is_default); + bool right_is_default = false; + right.device.properties.get_boolean("is-default", out right_is_default); + + // Prefer pipewire + if (left_is_pipewire && !right_is_pipewire) return -1; + if (right_is_pipewire && !left_is_pipewire) return 1; + + // Prefer pulse audio default device + if (left_is_default && !right_is_default) return -1; + if (right_is_default && !left_is_default) return 1; + + + return 0; + }); + } + return result; + } + + public Gee.List get_video_sources() { + ArrayList pipewire_devices = new ArrayList(); + ArrayList other_devices = new ArrayList(); + + foreach (Device device in devices) { + if (device.media != "video") continue; + if (device.is_sink) continue; + + bool is_color = false; + for (int i = 0; i < device.device.caps.get_size(); i++) { + unowned Gst.Structure structure = device.device.caps.get_structure(i); + if (structure.has_field("format") && !structure.get_string("format").has_prefix("GRAY")) { + is_color = true; + } + } + + // Don't allow grey-scale devices + if (!is_color) continue; + + if (device.device.properties.has_name("pipewire-proplist")) { + pipewire_devices.add(device); + } else { + other_devices.add(device); + } + } + + // If we have any pipewire devices, present only those. Don't want duplicated devices from pipewire and video for linux. + ArrayList devices = pipewire_devices.size > 0 ? pipewire_devices : other_devices; + + // Reorder sources + devices.sort((media_left, media_right) => { + Device left = media_left as Device; + Device right = media_right as Device; + if (left == null) return 1; + if (right == null) return -1; + + int left_fps = 0; + for (int i = 0; i < left.device.caps.get_size(); i++) { + unowned Gst.Structure structure = left.device.caps.get_structure(i); + int num = 0, den = 0; + if (structure.has_field("framerate") && structure.get_fraction("framerate", out num, out den)) left_fps = int.max(left_fps, num / den); + } + + int right_fps = 0; + for (int i = 0; i < left.device.caps.get_size(); i++) { + unowned Gst.Structure structure = left.device.caps.get_structure(i); + int num = 0, den = 0; + if (structure.has_field("framerate") && structure.get_fraction("framerate", out num, out den)) right_fps = int.max(right_fps, num / den); + } + + // More FPS is better + if (left_fps > right_fps) return -1; + if (right_fps > left_fps) return 1; + + return 0; + }); + + return devices; + } + + public Device? get_preferred_device(string media, bool incoming) { + foreach (MediaDevice media_device in get_devices(media, incoming)) { + Device? device = media_device as Device; + if (device != null) return device; + } + warning("No preferred device for %s %s. Media will not be processed.", incoming ? "incoming" : "outgoing", media); + return null; + } + + public MediaDevice? get_device(Xmpp.Xep.JingleRtp.Stream stream, bool incoming) { + Stream plugin_stream = stream as Stream; + if (plugin_stream == null) return null; + if (incoming) { + return plugin_stream.output_device ?? get_preferred_device(stream.media, incoming); + } else { + return plugin_stream.input_device ?? get_preferred_device(stream.media, incoming); + } + } + + private void dump_dot() { + string name = @"pipe-$(pipe.clock.get_time())-$(pipe.current_state)"; + Gst.Debug.bin_to_dot_file(pipe, Gst.DebugGraphDetails.ALL, name); + debug("Stored pipe details as %s", name); + } + + public void set_pause(Xmpp.Xep.JingleRtp.Stream stream, bool pause) { + Stream plugin_stream = stream as Stream; + if (plugin_stream == null) return; + if (pause) { + plugin_stream.pause(); + } else { + plugin_stream.unpause(); + Timeout.add_seconds(3, () => { + dump_dot(); + return false; + }); + } + } + + public void set_device(Xmpp.Xep.JingleRtp.Stream stream, MediaDevice? device) { + Device real_device = device as Device; + Stream plugin_stream = stream as Stream; + if (real_device == null || plugin_stream == null) return; + if (real_device.is_source) { + plugin_stream.input_device = real_device; + } else if (real_device.is_sink) { + plugin_stream.output_device = real_device; + } + } +} -- cgit v1.2.3-70-g09d2 From fc3263d49e5a5c737742eb7e591498ade830b685 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Thu, 25 Mar 2021 13:06:41 +0100 Subject: Fix device manager usage for GStreamer 1.16 --- plugins/rtp/src/plugin.vala | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'plugins/rtp/src/plugin.vala') diff --git a/plugins/rtp/src/plugin.vala b/plugins/rtp/src/plugin.vala index 69b0f37a..0f3cb10d 100644 --- a/plugins/rtp/src/plugin.vala +++ b/plugins/rtp/src/plugin.vala @@ -47,6 +47,12 @@ public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object { device_monitor.show_all = true; device_monitor.get_bus().add_watch(Priority.DEFAULT, on_device_monitor_message); device_monitor.start(); + foreach (Gst.Device device in device_monitor.get_devices()) { + if (device.properties.has_name("pipewire-proplist") && device.device_class.has_prefix("Audio/")) continue; + if (device.properties.get_string("device.class") == "monitor") continue; + if (devices.any_match((it) => it.matches(device))) continue; + devices.add(new Device(this, device)); + } pipe = new Gst.Pipeline(null); @@ -163,24 +169,28 @@ public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object { private bool on_device_monitor_message(Gst.Bus bus, Gst.Message message) { Gst.Device old_device = null; Gst.Device device = null; + Device old = null; switch (message.type) { case Gst.MessageType.DEVICE_ADDED: message.parse_device_added(out device); if (device.properties.has_name("pipewire-proplist") && device.device_class.has_prefix("Audio/")) return Source.CONTINUE; if (device.properties.get_string("device.class") == "monitor") return Source.CONTINUE; + if (devices.any_match((it) => it.matches(device))) return Source.CONTINUE; devices.add(new Device(this, device)); break; case Gst.MessageType.DEVICE_CHANGED: message.parse_device_changed(out device, out old_device); if (device.properties.has_name("pipewire-proplist") && device.device_class.has_prefix("Audio/")) return Source.CONTINUE; if (device.properties.get_string("device.class") == "monitor") return Source.CONTINUE; - devices.first_match((it) => it.matches(old_device)).update(device); + old = devices.first_match((it) => it.matches(old_device)); + if (old != null) old.update(device); break; case Gst.MessageType.DEVICE_REMOVED: message.parse_device_removed(out device); if (device.properties.has_name("pipewire-proplist") && device.device_class.has_prefix("Audio/")) return Source.CONTINUE; if (device.properties.get_string("device.class") == "monitor") return Source.CONTINUE; - devices.remove(devices.first_match((it) => it.matches(device))); + old = devices.first_match((it) => it.matches(device)); + if (old != null) devices.remove(old); break; } if (device != null) { -- cgit v1.2.3-70-g09d2 From c5cb43350af15e99d7304935ccf5fe84c2acdfc9 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Thu, 1 Apr 2021 11:51:12 +0200 Subject: Remove unnecessary debug code --- plugins/rtp/src/plugin.vala | 4 ---- 1 file changed, 4 deletions(-) (limited to 'plugins/rtp/src/plugin.vala') diff --git a/plugins/rtp/src/plugin.vala b/plugins/rtp/src/plugin.vala index 0f3cb10d..62e0d411 100644 --- a/plugins/rtp/src/plugin.vala +++ b/plugins/rtp/src/plugin.vala @@ -403,10 +403,6 @@ public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object { plugin_stream.pause(); } else { plugin_stream.unpause(); - Timeout.add_seconds(3, () => { - dump_dot(); - return false; - }); } } -- cgit v1.2.3-70-g09d2 From 6ebdec1d78a7ad1b8668a2ba6eceb34515c75384 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sun, 11 Apr 2021 12:31:03 +0200 Subject: GStreamer compat --- plugins/rtp/CMakeLists.txt | 6 ++++++ plugins/rtp/src/device.vala | 30 +++++++++++++++++++++--------- plugins/rtp/src/plugin.vala | 4 +++- 3 files changed, 30 insertions(+), 10 deletions(-) (limited to 'plugins/rtp/src/plugin.vala') diff --git a/plugins/rtp/CMakeLists.txt b/plugins/rtp/CMakeLists.txt index c6888459..0925ff0c 100644 --- a/plugins/rtp/CMakeLists.txt +++ b/plugins/rtp/CMakeLists.txt @@ -9,6 +9,10 @@ find_packages(RTP_PACKAGES REQUIRED GstApp ) +if(Gst_VERSION VERSION_GREATER "1.16") + set(RTP_DEFINITIONS GST_1_16) +endif() + vala_precompile(RTP_VALA_C SOURCES src/codec_util.vala @@ -25,6 +29,8 @@ CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/qlite.vapi PACKAGES ${RTP_PACKAGES} +DEFINITIONS + ${RTP_DEFINITIONS} OPTIONS --vapidir=${CMAKE_CURRENT_SOURCE_DIR}/vapi ) diff --git a/plugins/rtp/src/device.vala b/plugins/rtp/src/device.vala index 20762f77..3c9a38d2 100644 --- a/plugins/rtp/src/device.vala +++ b/plugins/rtp/src/device.vala @@ -130,11 +130,13 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object { filter.@set("caps", get_best_caps()); pipe.add(filter); element.link(filter); - if (media == "audio") { + if (media == "audio" && plugin.echoprobe != null) { dsp = Gst.ElementFactory.make("webrtcdsp", @"$id-dsp"); - dsp.@set("probe", plugin.echoprobe.name); - pipe.add(dsp); - filter.link(dsp); + if (dsp != null) { + dsp.@set("probe", plugin.echoprobe.name); + pipe.add(dsp); + filter.link(dsp); + } } tee = Gst.ElementFactory.make("tee", @"$id-tee"); tee.@set("allow-not-linked", true); @@ -149,15 +151,19 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object { filter = Gst.ElementFactory.make("capsfilter", @"$id-caps-filter"); filter.@set("caps", get_best_caps()); pipe.add(filter); - filter.link(plugin.echoprobe); - plugin.echoprobe.link(element); + if (plugin.echoprobe != null) { + filter.link(plugin.echoprobe); + plugin.echoprobe.link(element); + } else { + filter.link(element); + } } plugin.unpause(); } private void destroy() { if (mixer != null) { - if (is_sink && media == "audio") { + if (is_sink && media == "audio" && plugin.echoprobe != null) { plugin.echoprobe.unlink(mixer); } int linked_sink_pads = 0; @@ -177,11 +183,17 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object { if (filter != null) { filter.set_locked_state(true); filter.set_state(Gst.State.NULL); - filter.unlink(plugin.echoprobe); + if (plugin.echoprobe != null) { + filter.unlink(plugin.echoprobe); + } else { + filter.unlink(element); + } pipe.remove(filter); filter = null; } - plugin.echoprobe.unlink(element); + if (plugin.echoprobe != null) { + plugin.echoprobe.unlink(element); + } } element.set_locked_state(true); element.set_state(Gst.State.NULL); diff --git a/plugins/rtp/src/plugin.vala b/plugins/rtp/src/plugin.vala index 62e0d411..40ad1e0f 100644 --- a/plugins/rtp/src/plugin.vala +++ b/plugins/rtp/src/plugin.vala @@ -70,7 +70,7 @@ public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object { // Audio echo probe echoprobe = Gst.ElementFactory.make("webrtcechoprobe", "echo-probe"); - pipe.add(echoprobe); + if (echoprobe != null) pipe.add(echoprobe); // Pipeline pipe.auto_flush_bus = true; @@ -178,6 +178,7 @@ public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object { if (devices.any_match((it) => it.matches(device))) return Source.CONTINUE; devices.add(new Device(this, device)); break; +#if GST_1_16 case Gst.MessageType.DEVICE_CHANGED: message.parse_device_changed(out device, out old_device); if (device.properties.has_name("pipewire-proplist") && device.device_class.has_prefix("Audio/")) return Source.CONTINUE; @@ -185,6 +186,7 @@ public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object { old = devices.first_match((it) => it.matches(old_device)); if (old != null) old.update(device); break; +#endif case Gst.MessageType.DEVICE_REMOVED: message.parse_device_removed(out device); if (device.properties.has_name("pipewire-proplist") && device.device_class.has_prefix("Audio/")) return Source.CONTINUE; -- cgit v1.2.3-70-g09d2 From 3880628de4785db4c0a03a79a0c486507fe9b1a8 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Thu, 29 Apr 2021 15:46:06 +0200 Subject: Video optimizations --- cmake/FindGstRtp.cmake | 14 + plugins/rtp/CMakeLists.txt | 4 +- plugins/rtp/src/codec_util.vala | 115 +++- plugins/rtp/src/device.vala | 9 +- plugins/rtp/src/module.vala | 133 +++-- plugins/rtp/src/plugin.vala | 14 + plugins/rtp/src/stream.vala | 220 +++++++- plugins/rtp/src/video_widget.vala | 10 +- plugins/rtp/vapi/gstreamer-rtp-1.0.vapi | 625 +++++++++++++++++++++ .../xep/0167_jingle_rtp/content_parameters.vala | 40 +- .../xep/0167_jingle_rtp/jingle_rtp_module.vala | 5 + .../module/xep/0167_jingle_rtp/payload_type.vala | 49 +- .../src/module/xep/0167_jingle_rtp/stream.vala | 7 + 13 files changed, 1126 insertions(+), 119 deletions(-) create mode 100644 cmake/FindGstRtp.cmake create mode 100644 plugins/rtp/vapi/gstreamer-rtp-1.0.vapi (limited to 'plugins/rtp/src/plugin.vala') diff --git a/cmake/FindGstRtp.cmake b/cmake/FindGstRtp.cmake new file mode 100644 index 00000000..0756a985 --- /dev/null +++ b/cmake/FindGstRtp.cmake @@ -0,0 +1,14 @@ +include(PkgConfigWithFallback) +find_pkg_config_with_fallback(GstRtp + PKG_CONFIG_NAME gstreamer-rtp-1.0 + LIB_NAMES gstrtp + LIB_DIR_HINTS gstreamer-1.0 + INCLUDE_NAMES gst/rtp/rtp.h + INCLUDE_DIR_SUFFIXES gstreamer-1.0 gstreamer-1.0/include gstreamer-rtp-1.0 gstreamer-rtp-1.0/include + DEPENDS Gst +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(GstRtp + REQUIRED_VARS GstRtp_LIBRARY + VERSION_VAR GstRtp_VERSION) diff --git a/plugins/rtp/CMakeLists.txt b/plugins/rtp/CMakeLists.txt index 76d6e66d..92ec1b97 100644 --- a/plugins/rtp/CMakeLists.txt +++ b/plugins/rtp/CMakeLists.txt @@ -1,3 +1,4 @@ +find_package(GstRtp REQUIRED) find_packages(RTP_PACKAGES REQUIRED Gee GLib @@ -27,6 +28,7 @@ CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/dino.vapi ${CMAKE_BINARY_DIR}/exports/qlite.vapi + ${CMAKE_CURRENT_SOURCE_DIR}/vapi/gstreamer-rtp-1.0.vapi PACKAGES ${RTP_PACKAGES} DEFINITIONS @@ -35,7 +37,7 @@ DEFINITIONS add_definitions(${VALA_CFLAGS} -DG_LOG_DOMAIN="rtp" -I${CMAKE_CURRENT_SOURCE_DIR}/src) add_library(rtp SHARED ${RTP_VALA_C}) -target_link_libraries(rtp libdino crypto-vala ${RTP_PACKAGES}) +target_link_libraries(rtp libdino crypto-vala ${RTP_PACKAGES} gstreamer-rtp-1.0) set_target_properties(rtp PROPERTIES PREFIX "") set_target_properties(rtp PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins/) diff --git a/plugins/rtp/src/codec_util.vala b/plugins/rtp/src/codec_util.vala index 6bd465c1..7537c11d 100644 --- a/plugins/rtp/src/codec_util.vala +++ b/plugins/rtp/src/codec_util.vala @@ -6,7 +6,7 @@ public class Dino.Plugins.Rtp.CodecUtil { private Set supported_elements = new HashSet(); private Set unsupported_elements = new HashSet(); - public static Gst.Caps get_caps(string media, JingleRtp.PayloadType payload_type) { + public static Gst.Caps get_caps(string media, JingleRtp.PayloadType payload_type, bool incoming) { Gst.Caps caps = new Gst.Caps.simple("application/x-rtp", "media", typeof(string), media, "payload", typeof(int), payload_type.id); @@ -19,6 +19,15 @@ public class Dino.Plugins.Rtp.CodecUtil { if (payload_type.name != null) { s.set("encoding-name", typeof(string), payload_type.name.up()); } + if (incoming) { + foreach (JingleRtp.RtcpFeedback rtcp_fb in payload_type.rtcp_fbs) { + if (rtcp_fb.subtype == null) { + s.set(@"rtcp-fb-$(rtcp_fb.type_)", typeof(bool), true); + } else { + s.set(@"rtcp-fb-$(rtcp_fb.type_)-$(rtcp_fb.subtype)", typeof(bool), true); + } + } + } return caps; } @@ -122,32 +131,82 @@ public class Dino.Plugins.Rtp.CodecUtil { return new string[0]; } - public static string? get_encode_prefix(string media, string codec, string encode) { + public static string? get_encode_prefix(string media, string codec, string encode, JingleRtp.PayloadType? payload_type) { if (encode == "msdkh264enc") return "video/x-raw,format=NV12 ! "; if (encode == "vaapih264enc") return "video/x-raw,format=NV12 ! "; return null; } - public static string? get_encode_suffix(string media, string codec, string encode) { + public static string? get_encode_args(string media, string codec, string encode, JingleRtp.PayloadType? payload_type) { // H264 - const string h264_suffix = " ! video/x-h264,profile=constrained-baseline ! h264parse"; - if (encode == "msdkh264enc") return @" bitrate=256 rate-control=vbr target-usage=7$h264_suffix"; - if (encode == "vaapih264enc") return @" bitrate=256 quality-level=7 tune=low-power$h264_suffix"; - if (encode == "x264enc") return @" byte-stream=1 bitrate=256 profile=baseline speed-preset=ultrafast tune=zerolatency$h264_suffix"; - if (media == "video" && codec == "h264") return h264_suffix; + if (encode == "msdkh264enc") return @" rate-control=vbr"; + if (encode == "vaapih264enc") return @" tune=low-power"; + if (encode == "x264enc") return @" byte-stream=1 profile=baseline speed-preset=ultrafast tune=zerolatency"; // VP8 - if (encode == "msdkvp8enc") return " bitrate=256 rate-control=vbr target-usage=7"; - if (encode == "vaapivp8enc") return " bitrate=256 rate-control=vbr quality-level=7"; - if (encode == "vp8enc") return " target-bitrate=256000 deadline=1 error-resilient=1"; + if (encode == "msdkvp8enc") return " rate-control=vbr"; + if (encode == "vaapivp8enc") return " rate-control=vbr"; + if (encode == "vp8enc") return " deadline=1 error-resilient=1"; // OPUS - if (encode == "opusenc") return " audio-type=voice"; + if (encode == "opusenc") { + if (payload_type != null && payload_type.parameters.has("useinbandfec", "1")) return " audio-type=voice inband-fec=true"; + return " audio-type=voice"; + } return null; } - public static string? get_decode_prefix(string media, string codec, string decode) { + public static string? get_encode_suffix(string media, string codec, string encode, JingleRtp.PayloadType? payload_type) { + // H264 + if (media == "video" && codec == "h264") return " ! video/x-h264,profile=constrained-baseline ! h264parse"; + return null; + } + + public uint update_bitrate(string media, JingleRtp.PayloadType payload_type, Gst.Element encode_element, uint bitrate) { + Gst.Bin? encode_bin = encode_element as Gst.Bin; + if (encode_bin == null) return 0; + string? codec = get_codec_from_payload(media, payload_type); + string? encode_name = get_encode_element_name(media, codec); + if (encode_name == null) return 0; + Gst.Element encode = encode_bin.get_by_name(@"$(encode_bin.name)_encode"); + + bitrate = uint.min(2048000, bitrate); + + switch (encode_name) { + case "msdkh264enc": + case "vaapih264enc": + case "x264enc": + case "msdkvp8enc": + case "vaapivp8enc": + bitrate = uint.min(2048000, bitrate); + encode.set("bitrate", bitrate); + return bitrate; + case "vp8enc": + bitrate = uint.min(2147483, bitrate); + encode.set("target-bitrate", bitrate * 1000); + return bitrate; + } + + return 0; + } + + public static string? get_decode_prefix(string media, string codec, string decode, JingleRtp.PayloadType? payload_type) { + return null; + } + + public static string? get_decode_args(string media, string codec, string decode, JingleRtp.PayloadType? payload_type) { + if (decode == "opusdec" && payload_type != null && payload_type.parameters.has("useinbandfec", "1")) return " use-inband-fec=true"; + if (decode == "vaapivp9dec" || decode == "vaapivp8dec" || decode == "vaapih264dec") return " max-errors=100"; + return null; + } + + public static string? get_decode_suffix(string media, string codec, string encode, JingleRtp.PayloadType? payload_type) { + return null; + } + + public static string? get_depay_args(string media, string codec, string encode, JingleRtp.PayloadType? payload_type) { + if (codec == "vp8") return " wait-for-keyframe=true"; return null; } @@ -195,21 +254,24 @@ public class Dino.Plugins.Rtp.CodecUtil { unsupported_elements.add(element_name); } - public string? get_decode_bin_description(string media, string? codec, string? element_name = null, string? name = null) { + public string? get_decode_bin_description(string media, string? codec, JingleRtp.PayloadType? payload_type, string? element_name = null, string? name = null) { if (codec == null) return null; string base_name = name ?? @"encode-$codec-$(Random.next_int())"; string depay = get_depay_element_name(media, codec); string decode = element_name ?? get_decode_element_name(media, codec); if (depay == null || decode == null) return null; - string decode_prefix = get_decode_prefix(media, codec, decode) ?? ""; - string resample = media == "audio" ? @" ! audioresample name=$base_name-resample" : ""; - return @"$depay name=$base_name-rtp-depay ! $decode_prefix$decode name=$base_name-decode ! $(media)convert name=$base_name-convert$resample"; + string decode_prefix = get_decode_prefix(media, codec, decode, payload_type) ?? ""; + string decode_args = get_decode_args(media, codec, decode, payload_type) ?? ""; + string decode_suffix = get_decode_suffix(media, codec, decode, payload_type) ?? ""; + string depay_args = get_depay_args(media, codec, decode, payload_type) ?? ""; + string resample = media == "audio" ? @" ! audioresample name=$(base_name)_resample" : ""; + return @"$depay$depay_args name=$(base_name)_rtp_depay ! $decode_prefix$decode$decode_args name=$(base_name)_$(codec)_decode$decode_suffix ! $(media)convert name=$(base_name)_convert$resample"; } public Gst.Element? get_decode_bin(string media, JingleRtp.PayloadType payload_type, string? name = null) { string? codec = get_codec_from_payload(media, payload_type); string base_name = name ?? @"encode-$codec-$(Random.next_int())"; - string? desc = get_decode_bin_description(media, codec, null, base_name); + string? desc = get_decode_bin_description(media, codec, payload_type, null, base_name); if (desc == null) return null; debug("Pipeline to decode %s %s: %s", media, codec, desc); Gst.Element bin = Gst.parse_bin_from_description(desc, true); @@ -217,22 +279,23 @@ public class Dino.Plugins.Rtp.CodecUtil { return bin; } - public string? get_encode_bin_description(string media, string? codec, string? element_name = null, uint pt = 96, string? name = null) { + public string? get_encode_bin_description(string media, string? codec, JingleRtp.PayloadType? payload_type, string? element_name = null, string? name = null) { if (codec == null) return null; - string base_name = name ?? @"encode-$codec-$(Random.next_int())"; + string base_name = name ?? @"encode_$(codec)_$(Random.next_int())"; string pay = get_pay_element_name(media, codec); string encode = element_name ?? get_encode_element_name(media, codec); if (pay == null || encode == null) return null; - string encode_prefix = get_encode_prefix(media, codec, encode) ?? ""; - string encode_suffix = get_encode_suffix(media, codec, encode) ?? ""; - string resample = media == "audio" ? @" ! audioresample name=$base_name-resample" : ""; - return @"$(media)convert name=$base_name-convert$resample ! $encode_prefix$encode$encode_suffix ! $pay pt=$pt name=$base_name-rtp-pay"; + string encode_prefix = get_encode_prefix(media, codec, encode, payload_type) ?? ""; + string encode_args = get_encode_args(media, codec, encode, payload_type) ?? ""; + string encode_suffix = get_encode_suffix(media, codec, encode, payload_type) ?? ""; + string resample = media == "audio" ? @" ! audioresample name=$(base_name)_resample" : ""; + return @"$(media)convert name=$(base_name)_convert$resample ! $encode_prefix$encode$encode_args name=$(base_name)_encode$encode_suffix ! $pay pt=$(payload_type != null ? payload_type.id : 96) name=$(base_name)_rtp_pay"; } public Gst.Element? get_encode_bin(string media, JingleRtp.PayloadType payload_type, string? name = null) { string? codec = get_codec_from_payload(media, payload_type); - string base_name = name ?? @"encode-$codec-$(Random.next_int())"; - string? desc = get_encode_bin_description(media, codec, null, payload_type.id, base_name); + string base_name = name ?? @"encode_$(codec)_$(Random.next_int())"; + string? desc = get_encode_bin_description(media, codec, payload_type, null, base_name); if (desc == null) return null; debug("Pipeline to encode %s %s: %s", media, codec, desc); Gst.Element bin = Gst.parse_bin_from_description(desc, true); diff --git a/plugins/rtp/src/device.vala b/plugins/rtp/src/device.vala index 3c9a38d2..785f853a 100644 --- a/plugins/rtp/src/device.vala +++ b/plugins/rtp/src/device.vala @@ -126,19 +126,20 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object { element = device.create_element(id); pipe.add(element); if (is_source) { - filter = Gst.ElementFactory.make("capsfilter", @"$id-caps-filter"); + element.@set("do-timestamp", true); + filter = Gst.ElementFactory.make("capsfilter", @"caps_filter_$id"); filter.@set("caps", get_best_caps()); pipe.add(filter); element.link(filter); if (media == "audio" && plugin.echoprobe != null) { - dsp = Gst.ElementFactory.make("webrtcdsp", @"$id-dsp"); + dsp = Gst.ElementFactory.make("webrtcdsp", @"dsp_$id"); if (dsp != null) { dsp.@set("probe", plugin.echoprobe.name); pipe.add(dsp); filter.link(dsp); } } - tee = Gst.ElementFactory.make("tee", @"$id-tee"); + tee = Gst.ElementFactory.make("tee", @"tee_$id"); tee.@set("allow-not-linked", true); pipe.add(tee); (dsp ?? filter).link(tee); @@ -148,7 +149,7 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object { element.@set("sync", false); } if (is_sink && media == "audio") { - filter = Gst.ElementFactory.make("capsfilter", @"$id-caps-filter"); + filter = Gst.ElementFactory.make("capsfilter", @"caps_filter_$id"); filter.@set("caps", get_best_caps()); pipe.add(filter); if (plugin.echoprobe != null) { diff --git a/plugins/rtp/src/module.vala b/plugins/rtp/src/module.vala index 231a9dde..52cc1880 100644 --- a/plugins/rtp/src/module.vala +++ b/plugins/rtp/src/module.vala @@ -63,7 +63,7 @@ public class Dino.Plugins.Rtp.Module : JingleRtp.Module { return supported; } - private async bool supports(string media, JingleRtp.PayloadType payload_type) { + private async bool is_payload_supported(string media, JingleRtp.PayloadType payload_type) { string codec = CodecUtil.get_codec_from_payload(media, payload_type); if (codec == null) return false; if (unsupported_codecs.contains(codec)) return false; @@ -77,7 +77,7 @@ public class Dino.Plugins.Rtp.Module : JingleRtp.Module { return false; } - string encode_bin = codec_util.get_encode_bin_description(media, codec, encode_element); + string encode_bin = codec_util.get_encode_bin_description(media, codec, null, encode_element); while (!(yield pipeline_works(media, encode_bin))) { debug("%s not suited for encoding %s", encode_element, codec); codec_util.mark_element_unsupported(encode_element); @@ -87,11 +87,11 @@ public class Dino.Plugins.Rtp.Module : JingleRtp.Module { unsupported_codecs.add(codec); return false; } - encode_bin = codec_util.get_encode_bin_description(media, codec, encode_element); + encode_bin = codec_util.get_encode_bin_description(media, codec, null, encode_element); } debug("using %s to encode %s", encode_element, codec); - string decode_bin = codec_util.get_decode_bin_description(media, codec, decode_element); + string decode_bin = codec_util.get_decode_bin_description(media, codec, null, decode_element); while (!(yield pipeline_works(media, @"$encode_bin ! $decode_bin"))) { debug("%s not suited for decoding %s", decode_element, codec); codec_util.mark_element_unsupported(decode_element); @@ -101,7 +101,7 @@ public class Dino.Plugins.Rtp.Module : JingleRtp.Module { unsupported_codecs.add(codec); return false; } - decode_bin = codec_util.get_decode_bin_description(media, codec, decode_element); + decode_bin = codec_util.get_decode_bin_description(media, codec, null, decode_element); } debug("using %s to decode %s", decode_element, codec); @@ -109,8 +109,21 @@ public class Dino.Plugins.Rtp.Module : JingleRtp.Module { return true; } + public override bool is_header_extension_supported(string media, JingleRtp.HeaderExtension ext) { + if (media == "video" && ext.uri == "urn:3gpp:video-orientation") return true; + return false; + } + + public override Gee.List get_suggested_header_extensions(string media) { + Gee.List exts = new ArrayList(); + if (media == "video") { + exts.add(new JingleRtp.HeaderExtension(1, "urn:3gpp:video-orientation")); + } + return exts; + } + public async void add_if_supported(Gee.List list, string media, JingleRtp.PayloadType payload_type) { - if (yield supports(media, payload_type)) { + if (yield is_payload_supported(media, payload_type)) { list.add(payload_type); } } @@ -118,58 +131,34 @@ public class Dino.Plugins.Rtp.Module : JingleRtp.Module { public override async Gee.List get_supported_payloads(string media) { Gee.List list = new ArrayList(JingleRtp.PayloadType.equals_func); if (media == "audio") { - yield add_if_supported(list, media, new JingleRtp.PayloadType() { - channels = 2, - clockrate = 48000, - name = "opus", - id = 99 - }); - yield add_if_supported(list, media, new JingleRtp.PayloadType() { - channels = 1, - clockrate = 32000, - name = "speex", - id = 100 - }); - yield add_if_supported(list, media, new JingleRtp.PayloadType() { - channels = 1, - clockrate = 16000, - name = "speex", - id = 101 - }); - yield add_if_supported(list, media, new JingleRtp.PayloadType() { - channels = 1, - clockrate = 8000, - name = "speex", - id = 102 - }); - yield add_if_supported(list, media, new JingleRtp.PayloadType() { - channels = 1, - clockrate = 8000, - name = "PCMU", - id = 0 - }); - yield add_if_supported(list, media, new JingleRtp.PayloadType() { - channels = 1, - clockrate = 8000, - name = "PCMA", - id = 8 - }); + var opus = new JingleRtp.PayloadType() { channels = 2, clockrate = 48000, name = "opus", id = 99 }; + opus.parameters["useinbandfec"] = "1"; + var speex32 = new JingleRtp.PayloadType() { channels = 1, clockrate = 32000, name = "speex", id = 100 }; + var speex16 = new JingleRtp.PayloadType() { channels = 1, clockrate = 16000, name = "speex", id = 101 }; + var speex8 = new JingleRtp.PayloadType() { channels = 1, clockrate = 8000, name = "speex", id = 102 }; + var pcmu = new JingleRtp.PayloadType() { channels = 1, clockrate = 8000, name = "PCMU", id = 0 }; + var pcma = new JingleRtp.PayloadType() { channels = 1, clockrate = 8000, name = "PCMA", id = 8 }; + yield add_if_supported(list, media, opus); + yield add_if_supported(list, media, speex32); + yield add_if_supported(list, media, speex16); + yield add_if_supported(list, media, speex8); + yield add_if_supported(list, media, pcmu); + yield add_if_supported(list, media, pcma); } else if (media == "video") { - yield add_if_supported(list, media, new JingleRtp.PayloadType() { - clockrate = 90000, - name = "H264", - id = 96 - }); - yield add_if_supported(list, media, new JingleRtp.PayloadType() { - clockrate = 90000, - name = "VP9", - id = 97 - }); - yield add_if_supported(list, media, new JingleRtp.PayloadType() { - clockrate = 90000, - name = "VP8", - id = 98 - }); + var h264 = new JingleRtp.PayloadType() { clockrate = 90000, name = "H264", id = 96 }; + var vp9 = new JingleRtp.PayloadType() { clockrate = 90000, name = "VP9", id = 97 }; + var vp8 = new JingleRtp.PayloadType() { clockrate = 90000, name = "VP8", id = 98 }; + var rtcp_fbs = new ArrayList(); + rtcp_fbs.add(new JingleRtp.RtcpFeedback("goog-remb")); + rtcp_fbs.add(new JingleRtp.RtcpFeedback("ccm", "fir")); + rtcp_fbs.add(new JingleRtp.RtcpFeedback("nack")); + rtcp_fbs.add(new JingleRtp.RtcpFeedback("nack", "pli")); + h264.rtcp_fbs.add_all(rtcp_fbs); + vp9.rtcp_fbs.add_all(rtcp_fbs); + vp8.rtcp_fbs.add_all(rtcp_fbs); + yield add_if_supported(list, media, h264); + yield add_if_supported(list, media, vp9); + yield add_if_supported(list, media, vp8); } else { warning("Unsupported media type: %s", media); } @@ -179,11 +168,15 @@ public class Dino.Plugins.Rtp.Module : JingleRtp.Module { public override async JingleRtp.PayloadType? pick_payload_type(string media, Gee.List payloads) { if (media == "audio") { foreach (JingleRtp.PayloadType type in payloads) { - if (yield supports(media, type)) return type; + if (yield is_payload_supported(media, type)) return adjust_payload_type(media, type.clone()); } } else if (media == "video") { + // We prefer H.264 (best support for hardware acceleration and good overall codec quality) + JingleRtp.PayloadType? h264 = payloads.first_match((it) => it.name.up() == "H264"); + if (h264 != null && yield is_payload_supported(media, h264)) return adjust_payload_type(media, h264.clone()); + // Take first of the list that we do support otherwise foreach (JingleRtp.PayloadType type in payloads) { - if (yield supports(media, type)) return type; + if (yield is_payload_supported(media, type)) return adjust_payload_type(media, type.clone()); } } else { warning("Unsupported media type: %s", media); @@ -191,6 +184,28 @@ public class Dino.Plugins.Rtp.Module : JingleRtp.Module { return null; } + public JingleRtp.PayloadType adjust_payload_type(string media, JingleRtp.PayloadType type) { + var iter = type.rtcp_fbs.iterator(); + while (iter.next()) { + var fb = iter.@get(); + switch (fb.type_) { + case "goog-remb": + if (fb.subtype != null) iter.remove(); + break; + case "ccm": + if (fb.subtype != "fir") iter.remove(); + break; + case "nack": + if (fb.subtype != null && fb.subtype != "pli") iter.remove(); + break; + default: + iter.remove(); + break; + } + } + return type; + } + public override JingleRtp.Stream create_stream(Jingle.Content content) { return plugin.open_stream(content); } diff --git a/plugins/rtp/src/plugin.vala b/plugins/rtp/src/plugin.vala index 40ad1e0f..f0ad7db2 100644 --- a/plugins/rtp/src/plugin.vala +++ b/plugins/rtp/src/plugin.vala @@ -65,6 +65,9 @@ public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object { } rtpbin.pad_added.connect(on_rtp_pad_added); rtpbin.@set("latency", 100); + rtpbin.@set("do-lost", true); + rtpbin.@set("do-sync-event", true); + rtpbin.@set("drop-on-latency", true); rtpbin.connect("signal::request-pt-map", request_pt_map, this); pipe.add(rtpbin); @@ -160,6 +163,17 @@ public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object { case Gst.MessageType.QOS: // Ignore break; + case Gst.MessageType.LATENCY: + if (message.src != null && message.src.name != null && message.src is Gst.Element) { + Gst.Query latency_query = new Gst.Query.latency(); + if (((Gst.Element)message.src).query(latency_query)) { + bool live; + Gst.ClockTime min_latency, max_latency; + latency_query.parse_latency(out live, out min_latency, out max_latency); + debug("Latency message from %s: live=%s, min_latency=%s, max_latency=%s", message.src.name, live.to_string(), min_latency.to_string(), max_latency.to_string()); + } + } + break; default: debug("Pipe bus message: %s", message.type.to_string()); break; diff --git a/plugins/rtp/src/stream.vala b/plugins/rtp/src/stream.vala index 3a63f3fa..23634aa3 100644 --- a/plugins/rtp/src/stream.vala +++ b/plugins/rtp/src/stream.vala @@ -19,9 +19,12 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { private Gst.App.Src recv_rtp; private Gst.App.Src recv_rtcp; private Gst.Element encode; + private Gst.RTP.BasePayload encode_pay; private Gst.Element decode; + private Gst.RTP.BaseDepayload decode_depay; private Gst.Element input; private Gst.Element output; + private Gst.Element session; private Device _input_device; public Device input_device { get { return _input_device; } set { @@ -85,15 +88,15 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { } // Create app elements - send_rtp = Gst.ElementFactory.make("appsink", @"rtp-sink-$rtpid") as Gst.App.Sink; + send_rtp = Gst.ElementFactory.make("appsink", @"rtp_sink_$rtpid") as Gst.App.Sink; send_rtp.async = false; - send_rtp.caps = CodecUtil.get_caps(media, payload_type); + send_rtp.caps = CodecUtil.get_caps(media, payload_type, false); send_rtp.emit_signals = true; send_rtp.sync = false; send_rtp.new_sample.connect(on_new_sample); pipe.add(send_rtp); - send_rtcp = Gst.ElementFactory.make("appsink", @"rtcp-sink-$rtpid") as Gst.App.Sink; + send_rtcp = Gst.ElementFactory.make("appsink", @"rtcp_sink_$rtpid") as Gst.App.Sink; send_rtcp.async = false; send_rtcp.caps = new Gst.Caps.empty_simple("application/x-rtcp"); send_rtcp.emit_signals = true; @@ -101,14 +104,14 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { send_rtcp.new_sample.connect(on_new_sample); pipe.add(send_rtcp); - recv_rtp = Gst.ElementFactory.make("appsrc", @"rtp-src-$rtpid") as Gst.App.Src; - recv_rtp.caps = CodecUtil.get_caps(media, payload_type); + recv_rtp = Gst.ElementFactory.make("appsrc", @"rtp_src_$rtpid") as Gst.App.Src; + recv_rtp.caps = CodecUtil.get_caps(media, payload_type, true); recv_rtp.do_timestamp = true; recv_rtp.format = Gst.Format.TIME; recv_rtp.is_live = true; pipe.add(recv_rtp); - recv_rtcp = Gst.ElementFactory.make("appsrc", @"rtcp-src-$rtpid") as Gst.App.Src; + recv_rtcp = Gst.ElementFactory.make("appsrc", @"rtcp_src_$rtpid") as Gst.App.Src; recv_rtcp.caps = new Gst.Caps.empty_simple("application/x-rtcp"); recv_rtcp.do_timestamp = true; recv_rtcp.format = Gst.Format.TIME; @@ -122,7 +125,8 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { recv_rtcp.get_static_pad("src").link(recv_rtcp_sink_pad); // Connect input - encode = codec_util.get_encode_bin(media, payload_type, @"encode-$rtpid"); + encode = codec_util.get_encode_bin(media, payload_type, @"encode_$rtpid"); + encode_pay = (Gst.RTP.BasePayload)((Gst.Bin)encode).get_by_name(@"encode_$(rtpid)_rtp_pay"); pipe.add(encode); send_rtp_sink_pad = rtpbin.get_request_pad(@"send_rtp_sink_$rtpid"); encode.get_static_pad("src").link(send_rtp_sink_pad); @@ -131,7 +135,8 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { } // Connect output - decode = codec_util.get_decode_bin(media, payload_type, @"decode-$rtpid"); + decode = codec_util.get_decode_bin(media, payload_type, @"decode_$rtpid"); + decode_depay = (Gst.RTP.BaseDepayload)((Gst.Bin)encode).get_by_name(@"decode_$(rtpid)_rtp_depay"); pipe.add(decode); if (output != null) { decode.link(output); @@ -144,6 +149,110 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { created = true; push_recv_data = true; plugin.unpause(); + + GLib.Signal.emit_by_name(rtpbin, "get-session", rtpid, out session); + if (session != null && payload_type.rtcp_fbs.any_match((it) => it.type_ == "goog-remb")) { + Object internal_session; + session.@get("internal-session", out internal_session); + if (internal_session != null) { + internal_session.connect("signal::on-feedback-rtcp", on_feedback_rtcp, this); + } + Timeout.add(1000, () => remb_adjust()); + } + if (media == "video") { + codec_util.update_bitrate(media, payload_type, encode, 256); + } + } + + private uint remb = 256; + private int last_packets_lost = -1; + private uint64 last_packets_received; + private uint64 last_octets_received; + private bool remb_adjust() { + unowned Gst.Structure? stats; + if (session == null) { + debug("Session for %u finished, turning off remb adjustment", rtpid); + return Source.REMOVE; + } + session.get("stats", out stats); + if (stats == null) { + warning("No stats for session %u", rtpid); + return Source.REMOVE; + } + unowned ValueArray? source_stats; + stats.get("source-stats", typeof(ValueArray), out source_stats); + if (source_stats == null) { + warning("No source-stats for session %u", rtpid); + return Source.REMOVE; + } + foreach (Value value in source_stats.values) { + unowned Gst.Structure source_stat = (Gst.Structure) value.get_boxed(); + uint ssrc; + if (!source_stat.get_uint("ssrc", out ssrc)) continue; + if (ssrc.to_string() == participant_ssrc) { + int packets_lost; + uint64 packets_received, octets_received; + source_stat.get_int("packets-lost", out packets_lost); + source_stat.get_uint64("packets-received", out packets_received); + source_stat.get_uint64("octets-received", out octets_received); + int new_lost = packets_lost - last_packets_lost; + uint64 new_received = packets_received - last_packets_received; + uint64 new_octets = octets_received - last_octets_received; + if (new_received == 0) continue; + last_packets_lost = packets_lost; + last_packets_received = packets_received; + last_octets_received = octets_received; + double loss_rate = (double)new_lost / (double)(new_lost + new_received); + if (new_lost <= 0 || loss_rate < 0.02) { + remb = (uint)(1.08 * (double)remb); + } else if (loss_rate > 0.1) { + remb = (uint)((1.0 - 0.5 * loss_rate) * (double)remb); + } + remb = uint.max(remb, (uint)((new_octets * 8) / 1000)); + remb = uint.max(16, remb); // Never go below 16 + uint8[] data = new uint8[] { + 143, 206, 0, 5, + 0, 0, 0, 0, + 0, 0, 0, 0, + 'R', 'E', 'M', 'B', + 1, 0, 0, 0, + 0, 0, 0, 0 + }; + data[4] = (uint8)((encode_pay.ssrc >> 24) & 0xff); + data[5] = (uint8)((encode_pay.ssrc >> 16) & 0xff); + data[6] = (uint8)((encode_pay.ssrc >> 8) & 0xff); + data[7] = (uint8)(encode_pay.ssrc & 0xff); + uint8 br_exp = 0; + uint32 br_mant = remb * 1000; + uint8 bits = (uint8)Math.log2(br_mant); + if (bits > 16) { + br_exp = (uint8)bits - 16; + br_mant = br_mant >> br_exp; + } + data[17] = (uint8)((br_exp << 2) | ((br_mant >> 16) & 0x3)); + data[18] = (uint8)((br_mant >> 8) & 0xff); + data[19] = (uint8)(br_mant & 0xff); + data[20] = (uint8)((ssrc >> 24) & 0xff); + data[21] = (uint8)((ssrc >> 16) & 0xff); + data[22] = (uint8)((ssrc >> 8) & 0xff); + data[23] = (uint8)(ssrc & 0xff); + encrypt_and_send_rtcp(data); + } + } + return Source.CONTINUE; + } + + private static void on_feedback_rtcp(Gst.Element session, uint type, uint fbtype, uint sender_ssrc, uint media_ssrc, Gst.Buffer? fci, Stream self) { + if (type == 206 && fbtype == 15 && fci != null && sender_ssrc.to_string() == self.participant_ssrc) { + // https://tools.ietf.org/html/draft-alvestrand-rmcat-remb-03 + uint8[] data; + fci.extract_dup(0, fci.get_size(), out data); + if (data[0] != 'R' || data[1] != 'E' || data[2] != 'M' || data[3] != 'B') return; + uint8 br_exp = data[5] >> 2; + uint32 br_mant = (((uint32)data[5] & 0x3) << 16) + ((uint32)data[6] << 8) + (uint32)data[7]; + uint bitrate = (br_mant << br_exp) / 1000; + self.codec_util.update_bitrate(self.media, self.payload_type, self.encode, bitrate * 8); + } } private void prepare_local_crypto() { @@ -167,22 +276,26 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { if (crypto_session.has_encrypt) { data = crypto_session.encrypt_rtp(data); } - on_send_rtp_data(new Bytes.take(data)); + on_send_rtp_data(new Bytes.take((owned) data)); } else if (sink == send_rtcp) { - if (crypto_session.has_encrypt) { - data = crypto_session.encrypt_rtcp(data); - } - if (rtcp_mux) { - on_send_rtp_data(new Bytes.take(data)); - } else { - on_send_rtcp_data(new Bytes.take(data)); - } + encrypt_and_send_rtcp((owned) data); } else { warning("unknown sample"); } return Gst.FlowReturn.OK; } + private void encrypt_and_send_rtcp(owned uint8[] data) { + if (crypto_session.has_encrypt) { + data = crypto_session.encrypt_rtcp(data); + } + if (rtcp_mux) { + on_send_rtp_data(new Bytes.take((owned) data)); + } else { + on_send_rtcp_data(new Bytes.take((owned) data)); + } + } + private static Gst.PadProbeReturn drop_probe() { return Gst.PadProbeReturn.DROP; } @@ -211,6 +324,7 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { encode.get_static_pad("src").unlink(send_rtp_sink_pad); pipe.remove(encode); encode = null; + encode_pay = null; // Disconnect RTP sending if (send_rtp_src_pad != null) { @@ -243,6 +357,7 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { decode.set_state(Gst.State.NULL); pipe.remove(decode); decode = null; + decode_depay = null; output = null; // Disconnect output device @@ -276,6 +391,8 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { send_rtcp_src_pad = null; send_rtp_src_pad = null; recv_rtp_src_pad = null; + + session = null; } private void prepare_remote_crypto() { @@ -285,6 +402,9 @@ 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); + public override void on_recv_rtp_data(Bytes bytes) { if (rtcp_mux && bytes.length >= 2 && bytes.get(1) >= 192 && bytes.get(1) < 224) { on_recv_rtcp_data(bytes); @@ -301,6 +421,33 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { } if (push_recv_data) { Gst.Buffer buffer = new Gst.Buffer.wrapped((owned) data); + Gst.RTP.Buffer rtp_buffer; + if (Gst.RTP.Buffer.map(buffer, Gst.MapFlags.READ, out rtp_buffer)) { + if (rtp_buffer.get_extension()) { + Xmpp.Xep.JingleRtp.HeaderExtension? ext = header_extensions.first_match((it) => it.uri == "urn:3gpp:video-orientation"); + if (ext != null) { + unowned uint8[] extension_data; + if (rtp_buffer.get_extension_onebyte_header(ext.id, 0, out extension_data) && extension_data.length == 1) { + bool camera = (extension_data[0] & 0x8) > 0; + bool flip = (extension_data[0] & 0x4) > 0; + uint8 rotation = extension_data[0] & 0x3; + uint16 rotation_degree = uint16.MAX; + switch(rotation) { + case 0: rotation_degree = 0; break; + case 1: rotation_degree = 90; break; + 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; + } + } + } + } + rtp_buffer.unmap(); + } + // FIXME: VAPI file in Vala < 0.49.1 has a bug that results in broken ownership of buffer in push_buffer() // We workaround by using the plain signal. The signal unfortunately will cause an unnecessary copy of // the underlying buffer, so and some point we should move over to the new version (once we require @@ -449,6 +596,8 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { 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; public VideoStream(Plugin plugin, Xmpp.Xep.Jingle.Content content) { base(plugin, content); @@ -456,11 +605,15 @@ public class Dino.Plugins.Rtp.VideoStream : Stream { } public override void create() { + video_orientation_changed_handler = video_orientation_changed.connect(on_video_orientation_changed); plugin.pause(); - output_tee = Gst.ElementFactory.make("tee", null); + rotate = Gst.ElementFactory.make("videoflip", @"video_rotate_$rtpid"); + pipe.add(rotate); + output_tee = Gst.ElementFactory.make("tee", @"video_tee_$rtpid"); output_tee.@set("allow-not-linked", true); pipe.add(output_tee); - add_output(output_tee); + rotate.link(output_tee); + add_output(rotate); base.create(); foreach (Gst.Element output in outputs) { output_tee.link(output); @@ -468,19 +621,44 @@ public class Dino.Plugins.Rtp.VideoStream : Stream { plugin.unpause(); } + private void on_video_orientation_changed(uint16 degree) { + if (rotate != null) { + switch (degree) { + case 0: + rotate.@set("method", 0); + break; + case 90: + rotate.@set("method", 1); + break; + case 180: + rotate.@set("method", 2); + break; + case 270: + rotate.@set("method", 3); + break; + } + } + } + public override void destroy() { foreach (Gst.Element output in outputs) { output_tee.unlink(output); } base.destroy(); + rotate.set_locked_state(true); + rotate.set_state(Gst.State.NULL); + rotate.unlink(output_tee); + pipe.remove(rotate); + rotate = null; output_tee.set_locked_state(true); output_tee.set_state(Gst.State.NULL); pipe.remove(output_tee); output_tee = null; + disconnect(video_orientation_changed_handler); } public override void add_output(Gst.Element element) { - if (element == output_tee) { + if (element == output_tee || element == rotate) { base.add_output(element); return; } @@ -491,7 +669,7 @@ public class Dino.Plugins.Rtp.VideoStream : Stream { } public override void remove_output(Gst.Element element) { - if (element == output_tee) { + if (element == output_tee || element == rotate) { base.remove_output(element); return; } diff --git a/plugins/rtp/src/video_widget.vala b/plugins/rtp/src/video_widget.vala index fa5ba138..351069a7 100644 --- a/plugins/rtp/src/video_widget.vala +++ b/plugins/rtp/src/video_widget.vala @@ -19,7 +19,7 @@ public class Dino.Plugins.Rtp.VideoWidget : Gtk.Bin, Dino.Plugins.VideoCallWidge this.plugin = plugin; id = last_id++; - element = Gst.ElementFactory.make("gtksink", @"video-widget-$id"); + element = Gst.ElementFactory.make("gtksink", @"video_widget_$id"); if (element != null) { Gtk.Widget widget; element.@get("widget", out widget); @@ -51,8 +51,8 @@ public class Dino.Plugins.Rtp.VideoWidget : Gtk.Bin, Dino.Plugins.VideoCallWidge if (connected_stream == null) return; plugin.pause(); pipe.add(element); - convert = Gst.parse_bin_from_description(@"videoconvert name=video-widget-$id-convert", true); - convert.name = @"video-widget-$id-prepare"; + convert = Gst.parse_bin_from_description(@"videoconvert name=video_widget_$(id)_convert", true); + convert.name = @"video_widget_$(id)_prepare"; pipe.add(convert); convert.link(element); connected_stream.add_output(convert); @@ -68,8 +68,8 @@ public class Dino.Plugins.Rtp.VideoWidget : Gtk.Bin, Dino.Plugins.VideoCallWidge if (connected_device == null) return; plugin.pause(); pipe.add(element); - convert = Gst.parse_bin_from_description(@"videoflip method=horizontal-flip name=video-widget-$id-flip ! videoconvert name=video-widget-$id-convert", true); - convert.name = @"video-widget-$id-prepare"; + convert = Gst.parse_bin_from_description(@"videoflip method=horizontal-flip name=video_widget_$(id)_flip ! videoconvert name=video_widget_$(id)_convert", true); + convert.name = @"video_widget_$(id)_prepare"; pipe.add(convert); convert.link(element); connected_device.link_source().link(convert); diff --git a/plugins/rtp/vapi/gstreamer-rtp-1.0.vapi b/plugins/rtp/vapi/gstreamer-rtp-1.0.vapi new file mode 100644 index 00000000..30490896 --- /dev/null +++ b/plugins/rtp/vapi/gstreamer-rtp-1.0.vapi @@ -0,0 +1,625 @@ +// Fixme: This is fetched from development code of Vala upstream which fixed a few bugs. +/* gstreamer-rtp-1.0.vapi generated by vapigen, do not modify. */ + +[CCode (cprefix = "Gst", gir_namespace = "GstRtp", gir_version = "1.0", lower_case_cprefix = "gst_")] +namespace Gst { + namespace RTCP { + [CCode (cheader_filename = "gst/rtp/rtp.h", has_type_id = false)] + [GIR (name = "RTCPBuffer")] + public struct Buffer { + public weak Gst.Buffer buffer; + public bool add_packet (Gst.RTCP.Type type, Gst.RTCP.Packet packet); + public bool get_first_packet (Gst.RTCP.Packet packet); + public uint get_packet_count (); + public static bool map (Gst.Buffer buffer, Gst.MapFlags flags, out Gst.RTCP.Buffer rtcp); + public static Gst.Buffer @new (uint mtu); + public static Gst.Buffer new_copy_data ([CCode (array_length_cname = "len", array_length_pos = 1.1, array_length_type = "guint")] uint8[] data); + public static Gst.Buffer new_take_data ([CCode (array_length_cname = "len", array_length_pos = 1.1, array_length_type = "guint")] owned uint8[] data); + public bool unmap (); + public static bool validate (Gst.Buffer buffer); + public static bool validate_data ([CCode (array_length_cname = "len", array_length_pos = 1.1, array_length_type = "guint")] uint8[] data); + [Version (since = "1.6")] + public static bool validate_data_reduced ([CCode (array_length_cname = "len", array_length_pos = 1.1, array_length_type = "guint")] uint8[] data); + [Version (since = "1.6")] + public static bool validate_reduced (Gst.Buffer buffer); + } + [CCode (cheader_filename = "gst/rtp/rtp.h", has_type_id = false)] + [GIR (name = "RTCPPacket")] + public struct Packet { + public weak Gst.RTCP.Buffer? rtcp; + public uint offset; + [Version (since = "1.10")] + public bool add_profile_specific_ext ([CCode (array_length_cname = "len", array_length_pos = 1.1, array_length_type = "guint")] uint8[] data); + public bool add_rb (uint32 ssrc, uint8 fractionlost, int32 packetslost, uint32 exthighestseq, uint32 jitter, uint32 lsr, uint32 dlsr); + [Version (since = "1.10")] + public uint8 app_get_data (); + [Version (since = "1.10")] + public uint16 app_get_data_length (); + [Version (since = "1.10")] + public unowned string app_get_name (); + [Version (since = "1.10")] + public uint32 app_get_ssrc (); + [Version (since = "1.10")] + public uint8 app_get_subtype (); + [Version (since = "1.10")] + public bool app_set_data_length (uint16 wordlen); + [Version (since = "1.10")] + public void app_set_name (string name); + [Version (since = "1.10")] + public void app_set_ssrc (uint32 ssrc); + [Version (since = "1.10")] + public void app_set_subtype (uint8 subtype); + public bool bye_add_ssrc (uint32 ssrc); + public bool bye_add_ssrcs ([CCode (array_length_cname = "len", array_length_pos = 1.1, array_length_type = "guint")] uint32[] ssrc); + public uint32 bye_get_nth_ssrc (uint nth); + public string bye_get_reason (); + public uint8 bye_get_reason_len (); + public uint bye_get_ssrc_count (); + public bool bye_set_reason (string reason); + [Version (since = "1.10")] + public bool copy_profile_specific_ext ([CCode (array_length_cname = "len", array_length_pos = 1.1, array_length_type = "guint")] out uint8[] data); + public uint8 fb_get_fci (); + public uint16 fb_get_fci_length (); + public uint32 fb_get_media_ssrc (); + public uint32 fb_get_sender_ssrc (); + public Gst.RTCP.FBType fb_get_type (); + public bool fb_set_fci_length (uint16 wordlen); + public void fb_set_media_ssrc (uint32 ssrc); + public void fb_set_sender_ssrc (uint32 ssrc); + public void fb_set_type (Gst.RTCP.FBType type); + public uint8 get_count (); + public uint16 get_length (); + public bool get_padding (); + [Version (since = "1.10")] + public bool get_profile_specific_ext ([CCode (array_length_cname = "len", array_length_pos = 1.1, array_length_type = "guint")] out unowned uint8[] data); + [Version (since = "1.10")] + public uint16 get_profile_specific_ext_length (); + public void get_rb (uint nth, out uint32 ssrc, out uint8 fractionlost, out int32 packetslost, out uint32 exthighestseq, out uint32 jitter, out uint32 lsr, out uint32 dlsr); + public uint get_rb_count (); + public Gst.RTCP.Type get_type (); + public bool move_to_next (); + public bool remove (); + public uint32 rr_get_ssrc (); + public void rr_set_ssrc (uint32 ssrc); + public bool sdes_add_entry (Gst.RTCP.SDESType type, [CCode (array_length_cname = "len", array_length_pos = 1.5, array_length_type = "guint8")] uint8[] data); + public bool sdes_add_item (uint32 ssrc); + public bool sdes_copy_entry (out Gst.RTCP.SDESType type, [CCode (array_length_cname = "len", array_length_pos = 1.5, array_length_type = "guint8")] out uint8[] data); + public bool sdes_first_entry (); + public bool sdes_first_item (); + public bool sdes_get_entry (out Gst.RTCP.SDESType type, [CCode (array_length_cname = "len", array_length_pos = 1.5, array_length_type = "guint8")] out unowned uint8[] data); + public uint sdes_get_item_count (); + public uint32 sdes_get_ssrc (); + public bool sdes_next_entry (); + public bool sdes_next_item (); + public void set_rb (uint nth, uint32 ssrc, uint8 fractionlost, int32 packetslost, uint32 exthighestseq, uint32 jitter, uint32 lsr, uint32 dlsr); + public void sr_get_sender_info (out uint32 ssrc, out uint64 ntptime, out uint32 rtptime, out uint32 packet_count, out uint32 octet_count); + public void sr_set_sender_info (uint32 ssrc, uint64 ntptime, uint32 rtptime, uint32 packet_count, uint32 octet_count); + [Version (since = "1.16")] + public bool xr_first_rb (); + [Version (since = "1.16")] + public uint16 xr_get_block_length (); + [Version (since = "1.16")] + public Gst.RTCP.XRType xr_get_block_type (); + [Version (since = "1.16")] + public bool xr_get_dlrr_block (uint nth, out uint32 ssrc, out uint32 last_rr, out uint32 delay); + [Version (since = "1.16")] + public bool xr_get_prt_by_seq (uint16 seq, out uint32 receipt_time); + [Version (since = "1.16")] + public bool xr_get_prt_info (out uint32 ssrc, out uint8 thinning, out uint16 begin_seq, out uint16 end_seq); + [Version (since = "1.16")] + public bool xr_get_rle_info (out uint32 ssrc, out uint8 thinning, out uint16 begin_seq, out uint16 end_seq, out uint32 chunk_count); + [Version (since = "1.16")] + public bool xr_get_rle_nth_chunk (uint nth, out uint16 chunk); + [Version (since = "1.16")] + public bool xr_get_rrt (out uint64 timestamp); + [Version (since = "1.16")] + public uint32 xr_get_ssrc (); + [Version (since = "1.16")] + public bool xr_get_summary_info (out uint32 ssrc, out uint16 begin_seq, out uint16 end_seq); + [Version (since = "1.16")] + public bool xr_get_summary_jitter (out uint32 min_jitter, out uint32 max_jitter, out uint32 mean_jitter, out uint32 dev_jitter); + [Version (since = "1.16")] + public bool xr_get_summary_pkt (out uint32 lost_packets, out uint32 dup_packets); + [Version (since = "1.16")] + public bool xr_get_summary_ttl (out bool is_ipv4, out uint8 min_ttl, out uint8 max_ttl, out uint8 mean_ttl, out uint8 dev_ttl); + [Version (since = "1.16")] + public bool xr_get_voip_burst_metrics (out uint8 burst_density, out uint8 gap_density, out uint16 burst_duration, out uint16 gap_duration); + [Version (since = "1.16")] + public bool xr_get_voip_configuration_params (out uint8 gmin, out uint8 rx_config); + [Version (since = "1.16")] + public bool xr_get_voip_delay_metrics (out uint16 roundtrip_delay, out uint16 end_system_delay); + [Version (since = "1.16")] + public bool xr_get_voip_jitter_buffer_params (out uint16 jb_nominal, out uint16 jb_maximum, out uint16 jb_abs_max); + [Version (since = "1.16")] + public bool xr_get_voip_metrics_ssrc (out uint32 ssrc); + [Version (since = "1.16")] + public bool xr_get_voip_packet_metrics (out uint8 loss_rate, out uint8 discard_rate); + [Version (since = "1.16")] + public bool xr_get_voip_quality_metrics (out uint8 r_factor, out uint8 ext_r_factor, out uint8 mos_lq, out uint8 mos_cq); + [Version (since = "1.16")] + public bool xr_get_voip_signal_metrics (out uint8 signal_level, out uint8 noise_level, out uint8 rerl, out uint8 gmin); + [Version (since = "1.16")] + public bool xr_next_rb (); + } + [CCode (cheader_filename = "gst/rtp/rtp.h", cprefix = "GST_RTCP_", type_id = "gst_rtcpfb_type_get_type ()")] + [GIR (name = "RTCPFBType")] + public enum FBType { + FB_TYPE_INVALID, + RTPFB_TYPE_NACK, + RTPFB_TYPE_TMMBR, + RTPFB_TYPE_TMMBN, + RTPFB_TYPE_RTCP_SR_REQ, + RTPFB_TYPE_TWCC, + PSFB_TYPE_PLI, + PSFB_TYPE_SLI, + PSFB_TYPE_RPSI, + PSFB_TYPE_AFB, + PSFB_TYPE_FIR, + PSFB_TYPE_TSTR, + PSFB_TYPE_TSTN, + PSFB_TYPE_VBCN + } + [CCode (cheader_filename = "gst/rtp/rtp.h", cprefix = "GST_RTCP_SDES_", type_id = "gst_rtcpsdes_type_get_type ()")] + [GIR (name = "RTCPSDESType")] + public enum SDESType { + INVALID, + END, + CNAME, + NAME, + EMAIL, + PHONE, + LOC, + TOOL, + NOTE, + PRIV; + [CCode (cname = "gst_rtcp_sdes_name_to_type")] + public static Gst.RTCP.SDESType from_string (string name); + [CCode (cname = "gst_rtcp_sdes_type_to_name")] + public unowned string to_string (); + } + [CCode (cheader_filename = "gst/rtp/rtp.h", cprefix = "GST_RTCP_TYPE_", type_id = "gst_rtcp_type_get_type ()")] + [GIR (name = "RTCPType")] + public enum Type { + INVALID, + SR, + RR, + SDES, + BYE, + APP, + RTPFB, + PSFB, + XR + } + [CCode (cheader_filename = "gst/rtp/rtp.h", cprefix = "GST_RTCP_XR_TYPE_", type_id = "gst_rtcpxr_type_get_type ()")] + [GIR (name = "RTCPXRType")] + [Version (since = "1.16")] + public enum XRType { + INVALID, + LRLE, + DRLE, + PRT, + RRT, + DLRR, + SSUMM, + VOIP_METRICS + } + [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTCP_MAX_BYE_SSRC_COUNT")] + public const int MAX_BYE_SSRC_COUNT; + [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTCP_MAX_RB_COUNT")] + public const int MAX_RB_COUNT; + [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTCP_MAX_SDES")] + public const int MAX_SDES; + [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTCP_MAX_SDES_ITEM_COUNT")] + public const int MAX_SDES_ITEM_COUNT; + [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTCP_REDUCED_SIZE_VALID_MASK")] + public const int REDUCED_SIZE_VALID_MASK; + [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTCP_VALID_MASK")] + public const int VALID_MASK; + [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTCP_VALID_VALUE")] + public const int VALID_VALUE; + [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTCP_VERSION")] + public const int VERSION; + [CCode (cheader_filename = "gst/rtp/rtp.h")] + public static uint64 ntp_to_unix (uint64 ntptime); + [CCode (cheader_filename = "gst/rtp/rtp.h")] + public static uint64 unix_to_ntp (uint64 unixtime); + } + namespace RTP { + [CCode (cheader_filename = "gst/rtp/rtp.h", type_id = "gst_rtp_base_audio_payload_get_type ()")] + [GIR (name = "RTPBaseAudioPayload")] + public class BaseAudioPayload : Gst.RTP.BasePayload { + public Gst.ClockTime base_ts; + public int frame_duration; + public int frame_size; + public int sample_size; + [CCode (has_construct_function = false)] + protected BaseAudioPayload (); + public Gst.FlowReturn flush (uint payload_len, Gst.ClockTime timestamp); + public Gst.Base.Adapter get_adapter (); + public Gst.FlowReturn push ([CCode (array_length_cname = "payload_len", array_length_pos = 1.5, array_length_type = "guint")] uint8[] data, Gst.ClockTime timestamp); + public void set_frame_based (); + public void set_frame_options (int frame_duration, int frame_size); + public void set_sample_based (); + public void set_sample_options (int sample_size); + public void set_samplebits_options (int sample_size); + [NoAccessorMethod] + public bool buffer_list { get; set; } + } + [CCode (cheader_filename = "gst/rtp/rtp.h", type_id = "gst_rtp_base_depayload_get_type ()")] + [GIR (name = "RTPBaseDepayload")] + public abstract class BaseDepayload : Gst.Element { + public uint clock_rate; + public bool need_newsegment; + public weak Gst.Segment segment; + public weak Gst.Pad sinkpad; + public weak Gst.Pad srcpad; + [CCode (has_construct_function = false)] + protected BaseDepayload (); + [NoWrapper] + public virtual bool handle_event (Gst.Event event); + [Version (since = "1.16")] + public bool is_source_info_enabled (); + [NoWrapper] + public virtual bool packet_lost (Gst.Event event); + [NoWrapper] + public virtual Gst.Buffer process (Gst.Buffer @in); + [NoWrapper] + public virtual Gst.Buffer process_rtp_packet (Gst.RTP.Buffer rtp_buffer); + public Gst.FlowReturn push (Gst.Buffer out_buf); + public Gst.FlowReturn push_list (Gst.BufferList out_list); + [NoWrapper] + public virtual bool set_caps (Gst.Caps caps); + [Version (since = "1.16")] + public void set_source_info_enabled (bool enable); + [NoAccessorMethod] + [Version (since = "1.20")] + public bool auto_header_extension { get; set; } + [NoAccessorMethod] + [Version (since = "1.18")] + public int max_reorder { get; set; } + [NoAccessorMethod] + [Version (since = "1.16")] + public bool source_info { get; set; } + [NoAccessorMethod] + public Gst.Structure stats { owned get; } + [Version (since = "1.20")] + public signal void add_extension (owned Gst.RTP.HeaderExtension ext); + [Version (since = "1.20")] + public signal void clear_extensions (); + [Version (since = "1.20")] + public signal Gst.RTP.HeaderExtension request_extension (uint ext_id, string? ext_uri); + } + [CCode (cheader_filename = "gst/rtp/rtp.h", type_id = "gst_rtp_base_payload_get_type ()")] + [GIR (name = "RTPBasePayload")] + public abstract class BasePayload : Gst.Element { + [CCode (has_construct_function = false)] + protected BasePayload (); + [Version (since = "1.16")] + public Gst.Buffer allocate_output_buffer (uint payload_len, uint8 pad_len, uint8 csrc_count); + [NoWrapper] + public virtual Gst.Caps get_caps (Gst.Pad pad, Gst.Caps filter); + [Version (since = "1.16")] + public uint get_source_count (Gst.Buffer buffer); + [NoWrapper] + public virtual Gst.FlowReturn handle_buffer (Gst.Buffer buffer); + public bool is_filled (uint size, Gst.ClockTime duration); + [Version (since = "1.16")] + public bool is_source_info_enabled (); + public Gst.FlowReturn push (Gst.Buffer buffer); + public Gst.FlowReturn push_list (Gst.BufferList list); + [NoWrapper] + public virtual bool query (Gst.Pad pad, Gst.Query query); + [NoWrapper] + public virtual bool set_caps (Gst.Caps caps); + public void set_options (string media, bool @dynamic, string encoding_name, uint32 clock_rate); + [Version (since = "1.20")] + public bool set_outcaps_structure (Gst.Structure? s); + [Version (since = "1.16")] + public void set_source_info_enabled (bool enable); + [NoWrapper] + public virtual bool sink_event (Gst.Event event); + [NoWrapper] + public virtual bool src_event (Gst.Event event); + [NoAccessorMethod] + [Version (since = "1.20")] + public bool auto_header_extension { get; set; } + [NoAccessorMethod] + public int64 max_ptime { get; set; } + [NoAccessorMethod] + public int64 min_ptime { get; set; } + [NoAccessorMethod] + public uint mtu { get; set; } + [NoAccessorMethod] + [Version (since = "1.16")] + public bool onvif_no_rate_control { get; set; } + [NoAccessorMethod] + public bool perfect_rtptime { get; set; } + [NoAccessorMethod] + public uint pt { get; set; } + [NoAccessorMethod] + public int64 ptime_multiple { get; set; } + [NoAccessorMethod] + [Version (since = "1.18")] + public bool scale_rtptime { get; set; } + [NoAccessorMethod] + public uint seqnum { get; } + [NoAccessorMethod] + public int seqnum_offset { get; set; } + [NoAccessorMethod] + [Version (since = "1.16")] + public bool source_info { get; set; } + [NoAccessorMethod] + public uint ssrc { get; set; } + [NoAccessorMethod] + public Gst.Structure stats { owned get; } + [NoAccessorMethod] + public uint timestamp { get; } + [NoAccessorMethod] + public uint timestamp_offset { get; set; } + [Version (since = "1.20")] + public signal void add_extension (owned Gst.RTP.HeaderExtension ext); + [Version (since = "1.20")] + public signal void clear_extensions (); + [Version (since = "1.20")] + public signal Gst.RTP.HeaderExtension request_extension (uint ext_id, string ext_uri); + } + [CCode (cheader_filename = "gst/rtp/rtp.h", type_id = "gst_rtp_header_extension_get_type ()")] + [GIR (name = "RTPHeaderExtension")] + [Version (since = "1.20")] + public abstract class HeaderExtension : Gst.Element { + public uint ext_id; + [CCode (has_construct_function = false)] + protected HeaderExtension (); + public static Gst.RTP.HeaderExtension? create_from_uri (string uri); + public uint get_id (); + public virtual size_t get_max_size (Gst.Buffer input_meta); + public string get_sdp_caps_field_name (); + public virtual Gst.RTP.HeaderExtensionFlags get_supported_flags (); + public unowned string get_uri (); + public virtual bool read (Gst.RTP.HeaderExtensionFlags read_flags, [CCode (array_length_cname = "size", array_length_pos = 2.5, array_length_type = "gsize", type = "const guint8*")] uint8[] data, Gst.Buffer buffer); + public virtual bool set_attributes_from_caps (Gst.Caps caps); + public bool set_attributes_from_caps_simple_sdp (Gst.Caps caps); + public virtual bool set_caps_from_attributes (Gst.Caps caps); + public bool set_caps_from_attributes_simple_sdp (Gst.Caps caps); + public void set_id (uint ext_id); + public virtual bool set_non_rtp_sink_caps (Gst.Caps caps); + [CCode (cname = "gst_rtp_header_extension_class_set_uri")] + public class void set_uri (string uri); + public void set_wants_update_non_rtp_src_caps (bool state); + public virtual bool update_non_rtp_src_caps (Gst.Caps caps); + public virtual size_t write (Gst.Buffer input_meta, Gst.RTP.HeaderExtensionFlags write_flags, Gst.Buffer output, [CCode (array_length_cname = "size", array_length_pos = 4.1, array_length_type = "gsize", type = "guint8*")] uint8[] data); + } + [CCode (cheader_filename = "gst/rtp/rtp.h", has_type_id = false)] + [GIR (name = "RTPBuffer")] + public struct Buffer { + public weak Gst.Buffer buffer; + public uint state; + [CCode (array_length = false)] + public weak void* data[4]; + [CCode (array_length = false)] + public weak size_t size[4]; + public bool add_extension_onebyte_header (uint8 id, [CCode (array_length_cname = "size", array_length_pos = 2.1, array_length_type = "guint")] uint8[] data); + public bool add_extension_twobytes_header (uint8 appbits, uint8 id, [CCode (array_length_cname = "size", array_length_pos = 3.1, array_length_type = "guint")] uint8[] data); + [CCode (cname = "gst_buffer_add_rtp_source_meta")] + [Version (since = "1.16")] + public static unowned Gst.RTP.SourceMeta? add_rtp_source_meta (Gst.Buffer buffer, uint32? ssrc, uint32? csrc, uint csrc_count); + public static void allocate_data (Gst.Buffer buffer, uint payload_len, uint8 pad_len, uint8 csrc_count); + public static uint calc_header_len (uint8 csrc_count); + public static uint calc_packet_len (uint payload_len, uint8 pad_len, uint8 csrc_count); + public static uint calc_payload_len (uint packet_len, uint8 pad_len, uint8 csrc_count); + public static int compare_seqnum (uint16 seqnum1, uint16 seqnum2); + public static uint32 default_clock_rate (uint8 payload_type); + public static uint64 ext_timestamp (ref uint64 exttimestamp, uint32 timestamp); + public uint32 get_csrc (uint8 idx); + public uint8 get_csrc_count (); + public bool get_extension (); + [Version (since = "1.2")] + public GLib.Bytes get_extension_bytes (out uint16 bits); + public bool get_extension_data (out uint16 bits, [CCode (array_length = false)] out unowned uint8[] data, out uint wordlen); + public bool get_extension_onebyte_header (uint8 id, uint nth, [CCode (array_length_cname = "size", array_length_pos = 3.1, array_length_type = "guint")] out unowned uint8[] data); + [Version (since = "1.18")] + public static bool get_extension_onebyte_header_from_bytes (GLib.Bytes bytes, uint16 bit_pattern, uint8 id, uint nth, [CCode (array_length_cname = "size", array_length_pos = 5.1, array_length_type = "guint")] out unowned uint8[] data); + public bool get_extension_twobytes_header (out uint8 appbits, uint8 id, uint nth, [CCode (array_length_cname = "size", array_length_pos = 4.1, array_length_type = "guint")] out unowned uint8[] data); + public uint get_header_len (); + public bool get_marker (); + public uint get_packet_len (); + public bool get_padding (); + [CCode (array_length = false)] + public unowned uint8[] get_payload (); + public Gst.Buffer get_payload_buffer (); + [Version (since = "1.2")] + public GLib.Bytes get_payload_bytes (); + public uint get_payload_len (); + public Gst.Buffer get_payload_subbuffer (uint offset, uint len); + public uint8 get_payload_type (); + [CCode (cname = "gst_buffer_get_rtp_source_meta")] + [Version (since = "1.16")] + public static unowned Gst.RTP.SourceMeta? get_rtp_source_meta (Gst.Buffer buffer); + public uint16 get_seq (); + public uint32 get_ssrc (); + public uint32 get_timestamp (); + public uint8 get_version (); + public static bool map (Gst.Buffer buffer, Gst.MapFlags flags, out Gst.RTP.Buffer rtp); + public static Gst.Buffer new_allocate (uint payload_len, uint8 pad_len, uint8 csrc_count); + public static Gst.Buffer new_allocate_len (uint packet_len, uint8 pad_len, uint8 csrc_count); + public static Gst.Buffer new_copy_data ([CCode (array_length_cname = "len", array_length_pos = 1.1, array_length_type = "gsize")] uint8[] data); + public static Gst.Buffer new_take_data ([CCode (array_length_cname = "len", array_length_pos = 1.1, array_length_type = "gsize")] owned uint8[] data); + public void pad_to (uint len); + public void set_csrc (uint8 idx, uint32 csrc); + public void set_extension (bool extension); + public bool set_extension_data (uint16 bits, uint16 length); + public void set_marker (bool marker); + public void set_packet_len (uint len); + public void set_padding (bool padding); + public void set_payload_type (uint8 payload_type); + public void set_seq (uint16 seq); + public void set_ssrc (uint32 ssrc); + public void set_timestamp (uint32 timestamp); + public void set_version (uint8 version); + public void unmap (); + } + [CCode (cheader_filename = "gst/rtp/rtp.h", has_type_id = false)] + [GIR (name = "RTPPayloadInfo")] + public struct PayloadInfo { + public uint8 payload_type; + public weak string media; + public weak string encoding_name; + public uint clock_rate; + public weak string encoding_parameters; + public uint bitrate; + } + [CCode (cheader_filename = "gst/rtp/rtp.h", has_type_id = false)] + [GIR (name = "RTPSourceMeta")] + [Version (since = "1.16")] + public struct SourceMeta { + public Gst.Meta meta; + public uint32 ssrc; + public bool ssrc_valid; + [CCode (array_length = false)] + public weak uint32 csrc[15]; + public uint csrc_count; + public bool append_csrc ([CCode (array_length_cname = "csrc_count", array_length_pos = 1.1, array_length_type = "guint", type = "const guint32*")] uint32[] csrc); + public uint get_source_count (); + public bool set_ssrc (uint32? ssrc); + } + [CCode (cheader_filename = "gst/rtp/rtp.h", cprefix = "GST_RTP_BUFFER_FLAG_", type_id = "gst_rtp_buffer_flags_get_type ()")] + [Flags] + [GIR (name = "RTPBufferFlags")] + [Version (since = "1.10")] + public enum BufferFlags { + RETRANSMISSION, + REDUNDANT, + LAST + } + [CCode (cheader_filename = "gst/rtp/rtp.h", cprefix = "GST_RTP_BUFFER_MAP_FLAG_", type_id = "gst_rtp_buffer_map_flags_get_type ()")] + [Flags] + [GIR (name = "RTPBufferMapFlags")] + [Version (since = "1.6.1")] + public enum BufferMapFlags { + SKIP_PADDING, + LAST + } + [CCode (cheader_filename = "gst/rtp/rtp.h", cprefix = "GST_RTP_HEADER_EXTENSION_", type_id = "gst_rtp_header_extension_flags_get_type ()")] + [Flags] + [GIR (name = "RTPHeaderExtensionFlags")] + [Version (since = "1.20")] + public enum HeaderExtensionFlags { + ONE_BYTE, + TWO_BYTE + } + [CCode (cheader_filename = "gst/rtp/rtp.h", cprefix = "GST_RTP_PAYLOAD_", type_id = "gst_rtp_payload_get_type ()")] + [GIR (name = "RTPPayload")] + public enum Payload { + PCMU, + @1016, + G721, + GSM, + G723, + DVI4_8000, + DVI4_16000, + LPC, + PCMA, + G722, + L16_STEREO, + L16_MONO, + QCELP, + CN, + MPA, + G728, + DVI4_11025, + DVI4_22050, + G729, + CELLB, + JPEG, + NV, + H261, + MPV, + MP2T, + H263; + public const string @1016_STRING; + public const string CELLB_STRING; + public const string CN_STRING; + public const string DVI4_11025_STRING; + public const string DVI4_16000_STRING; + public const string DVI4_22050_STRING; + public const string DVI4_8000_STRING; + public const string DYNAMIC_STRING; + public const string G721_STRING; + public const string G722_STRING; + public const int G723_53; + public const string G723_53_STRING; + public const int G723_63; + public const string G723_63_STRING; + public const string G723_STRING; + public const string G728_STRING; + public const string G729_STRING; + public const string GSM_STRING; + public const string H261_STRING; + public const string H263_STRING; + public const string JPEG_STRING; + public const string L16_MONO_STRING; + public const string L16_STEREO_STRING; + public const string LPC_STRING; + public const string MP2T_STRING; + public const string MPA_STRING; + public const string MPV_STRING; + public const string NV_STRING; + public const string PCMA_STRING; + public const string PCMU_STRING; + public const string QCELP_STRING; + public const int TS41; + public const string TS41_STRING; + public const int TS48; + public const string TS48_STRING; + } + [CCode (cheader_filename = "gst/rtp/rtp.h", cprefix = "GST_RTP_PROFILE_", type_id = "gst_rtp_profile_get_type ()")] + [GIR (name = "RTPProfile")] + [Version (since = "1.6")] + public enum Profile { + UNKNOWN, + AVP, + SAVP, + AVPF, + SAVPF + } + [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTP_HDREXT_BASE")] + public const string HDREXT_BASE; + [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTP_HDREXT_ELEMENT_CLASS")] + [Version (since = "1.20")] + public const string HDREXT_ELEMENT_CLASS; + [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTP_HDREXT_NTP_56")] + public const string HDREXT_NTP_56; + [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTP_HDREXT_NTP_56_SIZE")] + public const int HDREXT_NTP_56_SIZE; + [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTP_HDREXT_NTP_64")] + public const string HDREXT_NTP_64; + [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTP_HDREXT_NTP_64_SIZE")] + public const int HDREXT_NTP_64_SIZE; + [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTP_HEADER_EXTENSION_URI_METADATA_KEY")] + [Version (since = "1.20")] + public const string HEADER_EXTENSION_URI_METADATA_KEY; + [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTP_SOURCE_META_MAX_CSRC_COUNT")] + public const int SOURCE_META_MAX_CSRC_COUNT; + [CCode (cheader_filename = "gst/rtp/rtp.h", cname = "GST_RTP_VERSION")] + public const int VERSION; + [CCode (cheader_filename = "gst/rtp/rtp.h")] + [Version (since = "1.20")] + public static GLib.List get_header_extension_list (); + [CCode (cheader_filename = "gst/rtp/rtp.h")] + public static bool hdrext_get_ntp_56 ([CCode (array_length_cname = "size", array_length_pos = 1.5, array_length_type = "guint")] uint8[] data, out uint64 ntptime); + [CCode (cheader_filename = "gst/rtp/rtp.h")] + public static bool hdrext_get_ntp_64 ([CCode (array_length_cname = "size", array_length_pos = 1.5, array_length_type = "guint")] uint8[] data, out uint64 ntptime); + [CCode (cheader_filename = "gst/rtp/rtp.h")] + public static bool hdrext_set_ntp_56 (void* data, uint size, uint64 ntptime); + [CCode (cheader_filename = "gst/rtp/rtp.h")] + public static bool hdrext_set_ntp_64 (void* data, uint size, uint64 ntptime); + [CCode (cheader_filename = "gst/rtp/rtp.h")] + public static unowned Gst.RTP.PayloadInfo? payload_info_for_name (string media, string encoding_name); + [CCode (cheader_filename = "gst/rtp/rtp.h")] + public static unowned Gst.RTP.PayloadInfo? payload_info_for_pt (uint8 payload_type); + [CCode (cheader_filename = "gst/rtp/rtp.h")] + public static GLib.Type source_meta_api_get_type (); + [CCode (cheader_filename = "gst/rtp/rtp.h")] + public static unowned Gst.MetaInfo? source_meta_get_info (); + } +} diff --git a/xmpp-vala/src/module/xep/0167_jingle_rtp/content_parameters.vala b/xmpp-vala/src/module/xep/0167_jingle_rtp/content_parameters.vala index d6f1acd2..d4440169 100644 --- a/xmpp-vala/src/module/xep/0167_jingle_rtp/content_parameters.vala +++ b/xmpp-vala/src/module/xep/0167_jingle_rtp/content_parameters.vala @@ -17,6 +17,7 @@ public class Xmpp.Xep.JingleRtp.Parameters : Jingle.ContentParameters, Object { public bool encryption_required { get; private set; default = false; } public PayloadType? agreed_payload_type { get; private set; } public Gee.List payload_types = new ArrayList(PayloadType.equals_func); + public Gee.List header_extensions = new ArrayList(); public Gee.List remote_cryptos = new ArrayList(); public Crypto? local_crypto = null; public Crypto? remote_crypto = null; @@ -54,9 +55,12 @@ public class Xmpp.Xep.JingleRtp.Parameters : Jingle.ContentParameters, Object { this.remote_cryptos.add(Crypto.parse(crypto)); } } - foreach (StanzaNode payloadType in node.get_subnodes("payload-type")) { + foreach (StanzaNode payloadType in node.get_subnodes(PayloadType.NAME)) { this.payload_types.add(PayloadType.parse(payloadType)); } + foreach (StanzaNode subnode in node.get_subnodes(HeaderExtension.NAME, HeaderExtension.NS_URI)) { + this.header_extensions.add(HeaderExtension.parse(subnode)); + } } public async void handle_proposed_content(XmppStream stream, Jingle.Session session, Jingle.Content content) { @@ -66,6 +70,11 @@ public class Xmpp.Xep.JingleRtp.Parameters : Jingle.ContentParameters, Object { content.reject(); return; } + // Drop unsupported header extensions + var iter = header_extensions.iterator(); + while(iter.next()) { + if (!parent.is_header_extension_supported(media, iter.@get())) iter.remove(); + } remote_crypto = parent.pick_remote_crypto(remote_cryptos); if (local_crypto == null && remote_crypto != null) { local_crypto = parent.pick_local_crypto(remote_crypto); @@ -151,7 +160,7 @@ public class Xmpp.Xep.JingleRtp.Parameters : Jingle.ContentParameters, Object { Gee.List crypto_nodes = description_node.get_deep_subnodes("encryption", "crypto"); if (crypto_nodes.size == 0) { - warning("Counterpart didn't include any cryptos"); + debug("Counterpart didn't include any cryptos"); if (encryption_required) { return; } @@ -182,6 +191,9 @@ public class Xmpp.Xep.JingleRtp.Parameters : Jingle.ContentParameters, Object { ret.put_node(payload_type.to_xml()); } } + foreach (HeaderExtension ext in header_extensions) { + ret.put_node(ext.to_xml()); + } if (local_crypto != null) { ret.put_node(new StanzaNode.build("encryption", NS_URI) .put_node(local_crypto.to_xml())); @@ -191,4 +203,28 @@ public class Xmpp.Xep.JingleRtp.Parameters : Jingle.ContentParameters, Object { } return ret; } +} + +public class Xmpp.Xep.JingleRtp.HeaderExtension { + public const string NS_URI = "urn:xmpp:jingle:apps:rtp:rtp-hdrext:0"; + public const string NAME = "rtp-hdrext"; + + public uint8 id { get; private set; } + public string uri { get; private set; } + + public HeaderExtension(uint8 id, string uri) { + this.id = id; + this.uri = uri; + } + + public static HeaderExtension parse(StanzaNode node) { + return new HeaderExtension((uint8) node.get_attribute_int("id"), node.get_attribute("uri")); + } + + public StanzaNode to_xml() { + return new StanzaNode.build(NAME, NS_URI) + .add_self_xmlns() + .put_attribute("id", id.to_string()) + .put_attribute("uri", uri); + } } \ No newline at end of file diff --git a/xmpp-vala/src/module/xep/0167_jingle_rtp/jingle_rtp_module.vala b/xmpp-vala/src/module/xep/0167_jingle_rtp/jingle_rtp_module.vala index 3adad114..6eb6289b 100644 --- a/xmpp-vala/src/module/xep/0167_jingle_rtp/jingle_rtp_module.vala +++ b/xmpp-vala/src/module/xep/0167_jingle_rtp/jingle_rtp_module.vala @@ -24,6 +24,8 @@ public abstract class Module : XmppStreamModule { public abstract Crypto? pick_remote_crypto(Gee.List cryptos); public abstract Crypto? pick_local_crypto(Crypto? remote); public abstract Stream create_stream(Jingle.Content content); + public abstract bool is_header_extension_supported(string media, HeaderExtension ext); + public abstract Gee.List get_suggested_header_extensions(string media); public abstract void close_stream(Stream stream); public async Jingle.Session start_call(XmppStream stream, Jid receiver_full_jid, bool video, string? sid = null) throws Jingle.Error { @@ -40,6 +42,7 @@ public abstract class Module : XmppStreamModule { // Create audio content Parameters audio_content_parameters = new Parameters(this, "audio", yield get_supported_payloads("audio")); audio_content_parameters.local_crypto = generate_local_crypto(); + audio_content_parameters.header_extensions.add_all(get_suggested_header_extensions("audio")); Jingle.Transport? audio_transport = yield jingle_module.select_transport(stream, content_type.required_transport_type, content_type.required_components, receiver_full_jid, Set.empty()); if (audio_transport == null) { throw new Jingle.Error.NO_SHARED_PROTOCOLS("No suitable audio transports"); @@ -57,6 +60,7 @@ public abstract class Module : XmppStreamModule { // Create video content Parameters video_content_parameters = new Parameters(this, "video", yield get_supported_payloads("video")); video_content_parameters.local_crypto = generate_local_crypto(); + video_content_parameters.header_extensions.add_all(get_suggested_header_extensions("video")); Jingle.Transport? video_transport = yield stream.get_module(Jingle.Module.IDENTITY).select_transport(stream, content_type.required_transport_type, content_type.required_components, receiver_full_jid, Set.empty()); if (video_transport == null) { throw new Jingle.Error.NO_SHARED_PROTOCOLS("No suitable video transports"); @@ -98,6 +102,7 @@ public abstract class Module : XmppStreamModule { // Content for video does not yet exist -> create it Parameters video_content_parameters = new Parameters(this, "video", yield get_supported_payloads("video")); video_content_parameters.local_crypto = generate_local_crypto(); + video_content_parameters.header_extensions.add_all(get_suggested_header_extensions("video")); Jingle.Transport? video_transport = yield stream.get_module(Jingle.Module.IDENTITY).select_transport(stream, content_type.required_transport_type, content_type.required_components, receiver_full_jid, Set.empty()); if (video_transport == null) { throw new Jingle.Error.NO_SHARED_PROTOCOLS("No suitable video transports"); diff --git a/xmpp-vala/src/module/xep/0167_jingle_rtp/payload_type.vala b/xmpp-vala/src/module/xep/0167_jingle_rtp/payload_type.vala index 452f1d65..faba38c9 100644 --- a/xmpp-vala/src/module/xep/0167_jingle_rtp/payload_type.vala +++ b/xmpp-vala/src/module/xep/0167_jingle_rtp/payload_type.vala @@ -3,6 +3,8 @@ using Xmpp; using Xmpp.Xep; public class Xmpp.Xep.JingleRtp.PayloadType { + public const string NAME = "payload-type"; + public uint8 id { get; set; } public string? name { get; set; } public uint8 channels { get; set; default = 1; } @@ -10,6 +12,7 @@ public class Xmpp.Xep.JingleRtp.PayloadType { public uint32 maxptime { get; set; } public uint32 ptime { get; set; } public Map parameters = new HashMap(); + public Gee.List rtcp_fbs = new ArrayList(); public static PayloadType parse(StanzaNode node) { PayloadType payloadType = new PayloadType(); @@ -22,11 +25,14 @@ public class Xmpp.Xep.JingleRtp.PayloadType { foreach (StanzaNode parameter in node.get_subnodes("parameter")) { payloadType.parameters[parameter.get_attribute("name")] = parameter.get_attribute("value"); } + foreach (StanzaNode subnode in node.get_subnodes(RtcpFeedback.NAME, RtcpFeedback.NS_URI)) { + payloadType.rtcp_fbs.add(RtcpFeedback.parse(subnode)); + } return payloadType; } public StanzaNode to_xml() { - StanzaNode node = new StanzaNode.build("payload-type", NS_URI) + StanzaNode node = new StanzaNode.build(NAME, NS_URI) .put_attribute("id", id.to_string()); if (channels != 1) node.put_attribute("channels", channels.to_string()); if (clockrate != 0) node.put_attribute("clockrate", clockrate.to_string()); @@ -38,9 +44,25 @@ public class Xmpp.Xep.JingleRtp.PayloadType { .put_attribute("name", parameter) .put_attribute("value", parameters[parameter])); } + foreach (RtcpFeedback rtcp_fb in rtcp_fbs) { + node.put_node(rtcp_fb.to_xml()); + } return node; } + public PayloadType clone() { + PayloadType clone = new PayloadType(); + clone.id = id; + clone.name = name; + clone.channels = channels; + clone.clockrate = clockrate; + clone.maxptime = maxptime; + clone.ptime = ptime; + clone.parameters.set_all(parameters); + clone.rtcp_fbs.add_all(rtcp_fbs); + return clone; + } + public static bool equals_func(PayloadType a, PayloadType b) { return a.id == b.id && a.name == b.name && @@ -49,4 +71,29 @@ public class Xmpp.Xep.JingleRtp.PayloadType { a.maxptime == b.maxptime && a.ptime == b.ptime; } +} + +public class Xmpp.Xep.JingleRtp.RtcpFeedback { + public const string NS_URI = "urn:xmpp:jingle:apps:rtp:rtcp-fb:0"; + public const string NAME = "rtcp-fb"; + + public string type_ { get; private set; } + public string? subtype { get; private set; } + + public RtcpFeedback(string type, string? subtype = null) { + this.type_ = type; + this.subtype = subtype; + } + + public static RtcpFeedback parse(StanzaNode node) { + return new RtcpFeedback(node.get_attribute("type"), node.get_attribute("subtype")); + } + + public StanzaNode to_xml() { + StanzaNode node = new StanzaNode.build(NAME, NS_URI) + .add_self_xmlns() + .put_attribute("type", type_); + if (subtype != null) node.put_attribute("subtype", subtype); + return node; + } } \ No newline at end of file diff --git a/xmpp-vala/src/module/xep/0167_jingle_rtp/stream.vala b/xmpp-vala/src/module/xep/0167_jingle_rtp/stream.vala index adae11f5..65be8a0a 100644 --- a/xmpp-vala/src/module/xep/0167_jingle_rtp/stream.vala +++ b/xmpp-vala/src/module/xep/0167_jingle_rtp/stream.vala @@ -33,6 +33,13 @@ public abstract class Xmpp.Xep.JingleRtp.Stream : Object { } return null; }} + public Gee.List? header_extensions { get { + var content_params = content.content_params; + if (content_params is Parameters) { + return ((Parameters)content_params).header_extensions; + } + return null; + }} public bool sending { get { return content.session.senders_include_us(content.senders); }} -- cgit v1.2.3-70-g09d2 From 5d85b6cdb0165d863aadd25d9a73707b8f5cc83e Mon Sep 17 00:00:00 2001 From: fiaxh Date: Sat, 17 Apr 2021 14:50:31 +0200 Subject: Handle non-existant call support --- libdino/src/plugin/interfaces.vala | 2 ++ libdino/src/service/calls.vala | 38 ++++++++++++++++++++--- main/src/ui/conversation_titlebar/call_entry.vala | 13 +++++--- plugins/rtp/src/codec_util.vala | 1 + plugins/rtp/src/module.vala | 6 ++-- plugins/rtp/src/plugin.vala | 16 ++++++++++ 6 files changed, 65 insertions(+), 11 deletions(-) (limited to 'plugins/rtp/src/plugin.vala') diff --git a/libdino/src/plugin/interfaces.vala b/libdino/src/plugin/interfaces.vala index 8be77895..97951850 100644 --- a/libdino/src/plugin/interfaces.vala +++ b/libdino/src/plugin/interfaces.vala @@ -85,6 +85,8 @@ public abstract interface ConversationAdditionPopulator : ConversationItemPopula } public abstract interface VideoCallPlugin : Object { + + public abstract bool supports(string media); // Video widget public abstract VideoCallWidget? create_widget(WidgetType type); diff --git a/libdino/src/service/calls.vala b/libdino/src/service/calls.vala index b7374607..1d47823d 100644 --- a/libdino/src/service/calls.vala +++ b/libdino/src/service/calls.vala @@ -83,7 +83,7 @@ namespace Dino { we_should_send_video[call] = video; we_should_send_audio[call] = true; - if (yield has_jmi_resources(conversation)) { + if (has_jmi_resources(conversation)) { XmppStream? stream = stream_interactor.get_stream(conversation.account); jmi_call[conversation.account] = call; jmi_video[conversation.account] = video; @@ -245,8 +245,28 @@ namespace Dino { // If video_feed == null && !mute we're trying to mute a non-existant feed. It will be muted as soon as it is created. } - public async bool can_do_calls(Conversation conversation) { - return (yield get_call_resources(conversation)).size > 0 || yield has_jmi_resources(conversation); + public async bool can_do_audio_calls_async(Conversation conversation) { + if (!can_do_audio_calls()) return false; + return (yield get_call_resources(conversation)).size > 0 || has_jmi_resources(conversation); + } + + private bool can_do_audio_calls() { + Plugins.VideoCallPlugin? plugin = Application.get_default().plugin_registry.video_call_plugin; + if (plugin == null) return false; + + return plugin.supports("audio"); + } + + public async bool can_do_video_calls_async(Conversation conversation) { + if (!can_do_video_calls()) return false; + return (yield get_call_resources(conversation)).size > 0 || has_jmi_resources(conversation); + } + + private bool can_do_video_calls() { + Plugins.VideoCallPlugin? plugin = Application.get_default().plugin_registry.video_call_plugin; + if (plugin == null) return false; + + return plugin.supports("video"); } private async Gee.List get_call_resources(Conversation conversation) { @@ -266,7 +286,7 @@ namespace Dino { return ret; } - private async bool has_jmi_resources(Conversation conversation) { + private bool has_jmi_resources(Conversation conversation) { int64 jmi_resources = db.entity.select() .with(db.entity.jid_id, "=", db.get_jid_id(conversation.counterpart)) .join_with(db.entity_feature, db.entity.caps_hash, db.entity_feature.entity) @@ -289,6 +309,11 @@ namespace Dino { } private void on_incoming_call(Account account, Xep.Jingle.Session session) { + if (!can_do_audio_calls()) { + warning("Incoming call but no call support detected. Ignoring."); + return; + } + bool counterpart_wants_video = false; foreach (Xep.Jingle.Content content in session.contents) { Xep.JingleRtp.Parameters? rtp_content_parameter = content.content_params as Xep.JingleRtp.Parameters; @@ -550,6 +575,11 @@ namespace Dino { Xep.JingleMessageInitiation.Module mi_module = stream_interactor.module_manager.get_module(account, Xep.JingleMessageInitiation.Module.IDENTITY); mi_module.session_proposed.connect((from, to, sid, descriptions) => { + if (!can_do_audio_calls()) { + warning("Incoming call but no call support detected. Ignoring."); + return; + } + bool audio_requested = descriptions.any_match((description) => description.ns_uri == Xep.JingleRtp.NS_URI && description.get_attribute("media") == "audio"); bool video_requested = descriptions.any_match((description) => description.ns_uri == Xep.JingleRtp.NS_URI && description.get_attribute("media") == "video"); if (!audio_requested && !video_requested) return; diff --git a/main/src/ui/conversation_titlebar/call_entry.vala b/main/src/ui/conversation_titlebar/call_entry.vala index 5e28ecbe..e1d10e5c 100644 --- a/main/src/ui/conversation_titlebar/call_entry.vala +++ b/main/src/ui/conversation_titlebar/call_entry.vala @@ -34,6 +34,9 @@ namespace Dino.Ui { private StreamInteractor stream_interactor; private Conversation conversation; + private ModelButton audio_button = new ModelButton() { text="Audio call", visible=true }; + private ModelButton video_button = new ModelButton() { text="Video call", visible=true }; + public CallButton(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; @@ -42,7 +45,6 @@ namespace Dino.Ui { Gtk.PopoverMenu popover_menu = new Gtk.PopoverMenu(); Box box = new Box(Orientation.VERTICAL, 0) { margin=10, visible=true }; - ModelButton audio_button = new ModelButton() { text="Audio call", visible=true }; audio_button.clicked.connect(() => { stream_interactor.get_module(Calls.IDENTITY).initiate_call.begin(conversation, false, (_, res) => { Call call = stream_interactor.get_module(Calls.IDENTITY).initiate_call.end(res); @@ -50,7 +52,7 @@ namespace Dino.Ui { }); }); box.add(audio_button); - ModelButton video_button = new ModelButton() { text="Video call", visible=true }; + video_button.clicked.connect(() => { stream_interactor.get_module(Calls.IDENTITY).initiate_call.begin(conversation, true, (_, res) => { Call call = stream_interactor.get_module(Calls.IDENTITY).initiate_call.end(res); @@ -116,9 +118,12 @@ namespace Dino.Ui { private async void update_visibility() { if (conversation.type_ == Conversation.Type.CHAT) { Conversation conv_bak = conversation; - bool can_do_calls = yield stream_interactor.get_module(Calls.IDENTITY).can_do_calls(conversation); + bool audio_works = yield stream_interactor.get_module(Calls.IDENTITY).can_do_audio_calls_async(conversation); + bool video_works = yield stream_interactor.get_module(Calls.IDENTITY).can_do_audio_calls_async(conversation); if (conv_bak != conversation) return; - visible = can_do_calls; + + visible = audio_works; + video_button.visible = video_works; } else { visible = false; } diff --git a/plugins/rtp/src/codec_util.vala b/plugins/rtp/src/codec_util.vala index 7537c11d..6a2438f1 100644 --- a/plugins/rtp/src/codec_util.vala +++ b/plugins/rtp/src/codec_util.vala @@ -225,6 +225,7 @@ public class Dino.Plugins.Rtp.CodecUtil { } public string? get_encode_element_name(string media, string? codec) { + if (!is_element_supported(get_pay_element_name(media, codec))) return null; foreach (string candidate in get_encode_candidates(media, codec)) { if (is_element_supported(candidate)) return candidate; } diff --git a/plugins/rtp/src/module.vala b/plugins/rtp/src/module.vala index 52cc1880..13a21cd8 100644 --- a/plugins/rtp/src/module.vala +++ b/plugins/rtp/src/module.vala @@ -64,13 +64,13 @@ public class Dino.Plugins.Rtp.Module : JingleRtp.Module { } private async bool is_payload_supported(string media, JingleRtp.PayloadType payload_type) { - string codec = CodecUtil.get_codec_from_payload(media, payload_type); + string? codec = CodecUtil.get_codec_from_payload(media, payload_type); if (codec == null) return false; if (unsupported_codecs.contains(codec)) return false; if (supported_codecs.contains(codec)) return true; - string encode_element = codec_util.get_encode_element_name(media, codec); - string decode_element = codec_util.get_decode_element_name(media, codec); + string? encode_element = codec_util.get_encode_element_name(media, codec); + string? decode_element = codec_util.get_decode_element_name(media, codec); if (encode_element == null || decode_element == null) { debug("No suitable encoder or decoder found for %s", codec); unsupported_codecs.add(codec); diff --git a/plugins/rtp/src/plugin.vala b/plugins/rtp/src/plugin.vala index f0ad7db2..d43588b4 100644 --- a/plugins/rtp/src/plugin.vala +++ b/plugins/rtp/src/plugin.vala @@ -278,6 +278,22 @@ public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object { Gst.deinit(); } + public bool supports(string media) { + if (rtpbin == null) return false; + + if (media == "audio") { + if (get_devices("audio", false).is_empty) return false; + if (get_devices("audio", true).is_empty) return false; + } + + if (media == "video") { + if (Gst.ElementFactory.make("gtksink", null) == null) return false; + if (get_devices("video", false).is_empty) return false; + } + + return true; + } + public VideoCallWidget? create_widget(WidgetType type) { if (type == WidgetType.GTK) { return new VideoWidget(this); -- cgit v1.2.3-70-g09d2 From 23ffd37dded3bf872e42d7a00727ab3c4d105a97 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sat, 1 May 2021 15:19:05 +0200 Subject: Echo Cancellation --- CMakeLists.txt | 6 +- cmake/FindGstAudio.cmake | 14 +++ cmake/FindWebRTCAudioProcessing.cmake | 12 ++ plugins/rtp/CMakeLists.txt | 20 +++- plugins/rtp/src/device.vala | 30 +++-- plugins/rtp/src/plugin.vala | 5 +- plugins/rtp/src/voice_processor.vala | 176 +++++++++++++++++++++++++++++ plugins/rtp/src/voice_processor_native.cpp | 141 +++++++++++++++++++++++ 8 files changed, 385 insertions(+), 19 deletions(-) create mode 100644 cmake/FindGstAudio.cmake create mode 100644 cmake/FindWebRTCAudioProcessing.cmake create mode 100644 plugins/rtp/src/voice_processor.vala create mode 100644 plugins/rtp/src/voice_processor_native.cpp (limited to 'plugins/rtp/src/plugin.vala') diff --git a/CMakeLists.txt b/CMakeLists.txt index f480b0b2..b3bd35cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,11 @@ cmake_minimum_required(VERSION 3.3) list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) include(ComputeVersion) if (NOT VERSION_FOUND) - project(Dino LANGUAGES C) + project(Dino LANGUAGES C CXX) elseif (VERSION_IS_RELEASE) - project(Dino VERSION ${VERSION_FULL} LANGUAGES C) + project(Dino VERSION ${VERSION_FULL} LANGUAGES C CXX) else () - project(Dino LANGUAGES C) + project(Dino LANGUAGES C CXX) set(PROJECT_VERSION ${VERSION_FULL}) endif () diff --git a/cmake/FindGstAudio.cmake b/cmake/FindGstAudio.cmake new file mode 100644 index 00000000..d5fc5dfb --- /dev/null +++ b/cmake/FindGstAudio.cmake @@ -0,0 +1,14 @@ +include(PkgConfigWithFallback) +find_pkg_config_with_fallback(GstAudio + PKG_CONFIG_NAME gstreamer-audio-1.0 + LIB_NAMES gstaudio + LIB_DIR_HINTS gstreamer-1.0 + INCLUDE_NAMES gst/audio/audio.h + INCLUDE_DIR_SUFFIXES gstreamer-1.0 gstreamer-1.0/include gstreamer-audio-1.0 gstreamer-audio-1.0/include + DEPENDS Gst +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(GstAudio + REQUIRED_VARS GstAudio_LIBRARY + VERSION_VAR GstAudio_VERSION) diff --git a/cmake/FindWebRTCAudioProcessing.cmake b/cmake/FindWebRTCAudioProcessing.cmake new file mode 100644 index 00000000..5f17805d --- /dev/null +++ b/cmake/FindWebRTCAudioProcessing.cmake @@ -0,0 +1,12 @@ +include(PkgConfigWithFallback) +find_pkg_config_with_fallback(WebRTCAudioProcessing + PKG_CONFIG_NAME webrtc-audio-processing + LIB_NAMES webrtc_audio_processing + INCLUDE_NAMES webrtc/modules/audio_processing/include/audio_processing.h + INCLUDE_DIR_SUFFIXES webrtc-audio-processing webrtc_audio_processing +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(WebRTCAudioProcessing + REQUIRED_VARS WebRTCAudioProcessing_LIBRARY + VERSION_VAR WebRTCAudioProcessing_VERSION) diff --git a/plugins/rtp/CMakeLists.txt b/plugins/rtp/CMakeLists.txt index 92ec1b97..b19c8a8f 100644 --- a/plugins/rtp/CMakeLists.txt +++ b/plugins/rtp/CMakeLists.txt @@ -1,4 +1,5 @@ find_package(GstRtp REQUIRED) +find_package(WebRTCAudioProcessing 0.2) find_packages(RTP_PACKAGES REQUIRED Gee GLib @@ -8,12 +9,26 @@ find_packages(RTP_PACKAGES REQUIRED GTK3 Gst GstApp + GstAudio ) if(Gst_VERSION VERSION_GREATER "1.16") set(RTP_DEFINITIONS GST_1_16) endif() +if(WebRTCAudioProcessing_VERSION GREATER "0.4") + message(WARNING "Ignoring WebRTCAudioProcessing, only versions < 0.4 supported so far") + unset(WebRTCAudioProcessing_FOUND) +endif() + +if(WebRTCAudioProcessing_FOUND) + set(RTP_DEFINITIONS ${RTP_DEFINITIONS} WITH_VOICE_PROCESSOR) + set(RTP_VOICE_PROCESSOR_VALA src/voice_processor.vala) + set(RTP_VOICE_PROCESSOR_CXX src/voice_processor_native.cpp) +else() + message(WARNING "WebRTCAudioProcessing not found, build without voice pre-processing!") +endif() + vala_precompile(RTP_VALA_C SOURCES src/codec_util.vala @@ -23,6 +38,7 @@ SOURCES src/stream.vala src/video_widget.vala src/register_plugin.vala + ${RTP_VOICE_PROCESSOR_VALA} CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/crypto-vala.vapi ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi @@ -36,8 +52,8 @@ DEFINITIONS ) add_definitions(${VALA_CFLAGS} -DG_LOG_DOMAIN="rtp" -I${CMAKE_CURRENT_SOURCE_DIR}/src) -add_library(rtp SHARED ${RTP_VALA_C}) -target_link_libraries(rtp libdino crypto-vala ${RTP_PACKAGES} gstreamer-rtp-1.0) +add_library(rtp SHARED ${RTP_VALA_C} ${RTP_VOICE_PROCESSOR_CXX}) +target_link_libraries(rtp libdino crypto-vala ${RTP_PACKAGES} gstreamer-rtp-1.0 webrtc-audio-processing) set_target_properties(rtp PROPERTIES PREFIX "") set_target_properties(rtp PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins/) diff --git a/plugins/rtp/src/device.vala b/plugins/rtp/src/device.vala index 785f853a..f8894502 100644 --- a/plugins/rtp/src/device.vala +++ b/plugins/rtp/src/device.vala @@ -37,6 +37,7 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object { private Gst.Element dsp; private Gst.Element mixer; private Gst.Element filter; + private Gst.Element rate; private int links = 0; public Device(Plugin plugin, Gst.Device device) { @@ -132,12 +133,10 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object { pipe.add(filter); element.link(filter); if (media == "audio" && plugin.echoprobe != null) { - dsp = Gst.ElementFactory.make("webrtcdsp", @"dsp_$id"); - if (dsp != null) { - dsp.@set("probe", plugin.echoprobe.name); - pipe.add(dsp); - filter.link(dsp); - } + dsp = new VoiceProcessor(plugin.echoprobe, element as Gst.Audio.StreamVolume); + dsp.name = @"dsp_$id"; + pipe.add(dsp); + filter.link(dsp); } tee = Gst.ElementFactory.make("tee", @"tee_$id"); tee.@set("allow-not-linked", true); @@ -153,7 +152,11 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object { filter.@set("caps", get_best_caps()); pipe.add(filter); if (plugin.echoprobe != null) { - filter.link(plugin.echoprobe); + rate = Gst.ElementFactory.make("audiorate", @"rate_$id"); + rate.@set("tolerance", 100000000); + pipe.add(rate); + filter.link(rate); + rate.link(plugin.echoprobe); plugin.echoprobe.link(element); } else { filter.link(element); @@ -184,14 +187,17 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object { if (filter != null) { filter.set_locked_state(true); filter.set_state(Gst.State.NULL); - if (plugin.echoprobe != null) { - filter.unlink(plugin.echoprobe); - } else { - filter.unlink(element); - } + filter.unlink(rate ?? ((Gst.Element)plugin.echoprobe) ?? element); pipe.remove(filter); filter = null; } + if (rate != null) { + rate.set_locked_state(true); + rate.set_state(Gst.State.NULL); + rate.unlink(plugin.echoprobe); + pipe.remove(rate); + rate = null; + } if (plugin.echoprobe != null) { plugin.echoprobe.unlink(element); } diff --git a/plugins/rtp/src/plugin.vala b/plugins/rtp/src/plugin.vala index d43588b4..e3d5ee41 100644 --- a/plugins/rtp/src/plugin.vala +++ b/plugins/rtp/src/plugin.vala @@ -8,7 +8,7 @@ public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object { public Gst.DeviceMonitor device_monitor { get; private set; } public Gst.Pipeline pipe { get; private set; } public Gst.Bin rtpbin { get; private set; } - public Gst.Element echoprobe { get; private set; } + public EchoProbe echoprobe { get; private set; } private Gee.List streams = new ArrayList(); private Gee.List devices = new ArrayList(); @@ -72,7 +72,8 @@ public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object { pipe.add(rtpbin); // Audio echo probe - echoprobe = Gst.ElementFactory.make("webrtcechoprobe", "echo-probe"); +// echoprobe = Gst.ElementFactory.make("webrtcechoprobe", "echo-probe"); + echoprobe = new EchoProbe(); if (echoprobe != null) pipe.add(echoprobe); // Pipeline diff --git a/plugins/rtp/src/voice_processor.vala b/plugins/rtp/src/voice_processor.vala new file mode 100644 index 00000000..e6dc7e8f --- /dev/null +++ b/plugins/rtp/src/voice_processor.vala @@ -0,0 +1,176 @@ +using Gst; + +namespace Dino.Plugins.Rtp { +public static extern Buffer adjust_to_running_time(Base.Transform transform, Buffer buf); +} + +public class Dino.Plugins.Rtp.EchoProbe : Audio.Filter { + private static StaticPadTemplate sink_template = {"sink", PadDirection.SINK, PadPresence.ALWAYS, {null, "audio/x-raw,rate=48000,channels=1,layout=interleaved,format=S16LE"}}; + private static StaticPadTemplate src_template = {"src", PadDirection.SRC, PadPresence.ALWAYS, {null, "audio/x-raw,rate=48000,channels=1,layout=interleaved,format=S16LE"}}; + public Audio.Info audio_info { get; private set; } + public signal void on_new_buffer(Buffer buffer); + private uint period_samples; + private uint period_size; + private Base.Adapter adapter = new Base.Adapter(); + + static construct { + add_static_pad_template(sink_template); + add_static_pad_template(src_template); + set_static_metadata("Acoustic Echo Canceller probe", "Generic/Audio", "Gathers playback buffers for echo cancellation", "Dino Team "); + } + + construct { + set_passthrough(true); + } + + public override bool setup(Audio.Info info) { + audio_info = info; + period_samples = info.rate / 100; // 10ms buffers + period_size = period_samples * info.bpf; + return true; + } + + + public override FlowReturn transform_ip(Buffer buf) { + lock (adapter) { + adapter.push(adjust_to_running_time(this, buf)); + while (adapter.available() > period_size) { + on_new_buffer(adapter.take_buffer(period_size)); + } + } + return FlowReturn.OK; + } + + public override bool stop() { + adapter.clear(); + return true; + } +} + +public class Dino.Plugins.Rtp.VoiceProcessor : Audio.Filter { + private static StaticPadTemplate sink_template = {"sink", PadDirection.SINK, PadPresence.ALWAYS, {null, "audio/x-raw,rate=48000,channels=1,layout=interleaved,format=S16LE"}}; + private static StaticPadTemplate src_template = {"src", PadDirection.SRC, PadPresence.ALWAYS, {null, "audio/x-raw,rate=48000,channels=1,layout=interleaved,format=S16LE"}}; + public Audio.Info audio_info { get; private set; } + private ulong process_outgoing_buffer_handler_id; + private uint adjust_delay_timeout_id; + private uint period_samples; + private uint period_size; + private Base.Adapter adapter = new Base.Adapter(); + private EchoProbe? echo_probe; + private Audio.StreamVolume? stream_volume; + private ClockTime last_reverse; + private void* native; + + static construct { + add_static_pad_template(sink_template); + add_static_pad_template(src_template); + set_static_metadata("Voice Processor (AGC, AEC, filters, etc.)", "Generic/Audio", "Pre-processes voice with WebRTC Audio Processing Library", "Dino Team "); + } + + construct { + set_passthrough(false); + } + + public VoiceProcessor(EchoProbe? echo_probe = null, Audio.StreamVolume? stream_volume = null) { + this.echo_probe = echo_probe; + this.stream_volume = stream_volume; + } + + private static extern void* init_native(int stream_delay); + private static extern void setup_native(void* native); + private static extern void destroy_native(void* native); + private static extern void analyze_reverse_stream(void* native, Audio.Info info, Buffer buffer); + private static extern void process_stream(void* native, Audio.Info info, Buffer buffer); + private static extern void adjust_stream_delay(void* native); + private static extern void notify_gain_level(void* native, int gain_level); + private static extern int get_suggested_gain_level(void* native); + private static extern bool get_stream_has_voice(void* native); + + public override bool setup(Audio.Info info) { + debug("VoiceProcessor.setup(%s)", info.to_caps().to_string()); + audio_info = info; + period_samples = info.rate / 100; // 10ms buffers + period_size = period_samples * info.bpf; + adapter.clear(); + setup_native(native); + return true; + } + + public override bool start() { + native = init_native(150); + if (process_outgoing_buffer_handler_id == 0 && echo_probe != null) { + process_outgoing_buffer_handler_id = echo_probe.on_new_buffer.connect(process_outgoing_buffer); + } + if (stream_volume == null && sinkpad.get_peer() != null && sinkpad.get_peer().get_parent_element() is Audio.StreamVolume) { + stream_volume = sinkpad.get_peer().get_parent_element() as Audio.StreamVolume; + } + return true; + } + + private bool adjust_delay() { + if (native != null) { + adjust_stream_delay(native); + return Source.CONTINUE; + } else { + adjust_delay_timeout_id = 0; + return Source.REMOVE; + } + } + + private void process_outgoing_buffer(Buffer buffer) { + if (buffer.pts != uint64.MAX) { + last_reverse = buffer.pts; + } + analyze_reverse_stream(native, echo_probe.audio_info, buffer); + if (adjust_delay_timeout_id == 0 && echo_probe != null) { + adjust_delay_timeout_id = Timeout.add(5000, adjust_delay); + } + } + + public override FlowReturn submit_input_buffer(bool is_discont, Buffer input) { + lock (adapter) { + if (is_discont) { + adapter.clear(); + } + adapter.push(adjust_to_running_time(this, input)); + } + return FlowReturn.OK; + } + + public override FlowReturn generate_output(out Buffer output_buffer) { + lock (adapter) { + if (adapter.available() >= period_size) { + output_buffer = (Gst.Buffer) adapter.take_buffer(period_size).make_writable(); + int old_gain_level = 0; + if (stream_volume != null) { + old_gain_level = (int) (stream_volume.get_volume(Audio.StreamVolumeFormat.LINEAR) * 255.0); + notify_gain_level(native, old_gain_level); + } + process_stream(native, audio_info, output_buffer); + if (stream_volume != null) { + int new_gain_level = get_suggested_gain_level(native); + if (old_gain_level != new_gain_level) { + debug("Gain: %i -> %i", old_gain_level, new_gain_level); + stream_volume.set_volume(Audio.StreamVolumeFormat.LINEAR, ((double)new_gain_level) / 255.0); + } + } + } + } + return FlowReturn.OK; + } + + public override bool stop() { + if (process_outgoing_buffer_handler_id != 0) { + echo_probe.disconnect(process_outgoing_buffer_handler_id); + process_outgoing_buffer_handler_id = 0; + } + if (adjust_delay_timeout_id != 0) { + Source.remove(adjust_delay_timeout_id); + adjust_delay_timeout_id = 0; + } + adapter.clear(); + destroy_native(native); + native = null; + return true; + } +} \ No newline at end of file diff --git a/plugins/rtp/src/voice_processor_native.cpp b/plugins/rtp/src/voice_processor_native.cpp new file mode 100644 index 00000000..9b3292b8 --- /dev/null +++ b/plugins/rtp/src/voice_processor_native.cpp @@ -0,0 +1,141 @@ +#include +#include +#include +#include +#include +#include + +#define SAMPLE_RATE 48000 +#define SAMPLE_CHANNELS 1 + +struct _DinoPluginsRtpVoiceProcessorNative { + webrtc::AudioProcessing *apm; + gint stream_delay; +}; + +extern "C" void *dino_plugins_rtp_adjust_to_running_time(GstBaseTransform *transform, GstBuffer *buffer) { + GstBuffer *copy = gst_buffer_copy(buffer); + GST_BUFFER_PTS(copy) = gst_segment_to_running_time(&transform->segment, GST_FORMAT_TIME, GST_BUFFER_PTS(buffer)); + return copy; +} + +extern "C" void *dino_plugins_rtp_voice_processor_init_native(gint stream_delay) { + _DinoPluginsRtpVoiceProcessorNative *native = new _DinoPluginsRtpVoiceProcessorNative(); + webrtc::Config config; + config.Set(new webrtc::ExtendedFilter(true)); + config.Set(new webrtc::ExperimentalAgc(true, 85)); + native->apm = webrtc::AudioProcessing::Create(config); + native->stream_delay = stream_delay; + return native; +} + +extern "C" void dino_plugins_rtp_voice_processor_setup_native(void *native_ptr) { + _DinoPluginsRtpVoiceProcessorNative *native = (_DinoPluginsRtpVoiceProcessorNative *) native_ptr; + webrtc::AudioProcessing *apm = native->apm; + webrtc::ProcessingConfig pconfig; + pconfig.streams[webrtc::ProcessingConfig::kInputStream] = + webrtc::StreamConfig(SAMPLE_RATE, SAMPLE_CHANNELS, false); + pconfig.streams[webrtc::ProcessingConfig::kOutputStream] = + webrtc::StreamConfig(SAMPLE_RATE, SAMPLE_CHANNELS, false); + pconfig.streams[webrtc::ProcessingConfig::kReverseInputStream] = + webrtc::StreamConfig(SAMPLE_RATE, SAMPLE_CHANNELS, false); + pconfig.streams[webrtc::ProcessingConfig::kReverseOutputStream] = + webrtc::StreamConfig(SAMPLE_RATE, SAMPLE_CHANNELS, false); + apm->Initialize(pconfig); + apm->high_pass_filter()->Enable(true); + apm->echo_cancellation()->enable_drift_compensation(false); + apm->echo_cancellation()->set_suppression_level(webrtc::EchoCancellation::kModerateSuppression); + apm->echo_cancellation()->enable_delay_logging(true); + apm->echo_cancellation()->Enable(true); + apm->noise_suppression()->set_level(webrtc::NoiseSuppression::kModerate); + apm->noise_suppression()->Enable(true); + apm->gain_control()->set_analog_level_limits(0, 255); + apm->gain_control()->set_mode(webrtc::GainControl::kAdaptiveAnalog); + apm->gain_control()->set_target_level_dbfs(3); + apm->gain_control()->set_compression_gain_db(9); + apm->gain_control()->enable_limiter(true); + apm->gain_control()->Enable(true); + apm->voice_detection()->set_likelihood(webrtc::VoiceDetection::Likelihood::kLowLikelihood); + apm->voice_detection()->Enable(true); +} + +extern "C" void +dino_plugins_rtp_voice_processor_analyze_reverse_stream(void *native_ptr, GstAudioInfo *info, GstBuffer *buffer) { + _DinoPluginsRtpVoiceProcessorNative *native = (_DinoPluginsRtpVoiceProcessorNative *) native_ptr; + webrtc::StreamConfig config(SAMPLE_RATE, SAMPLE_CHANNELS, false); + webrtc::AudioProcessing *apm = native->apm; + + GstAudioBuffer audio_buffer; + gst_audio_buffer_map(&audio_buffer, info, buffer, GST_MAP_READ); + + webrtc::AudioFrame frame; + frame.num_channels_ = info->channels; + frame.sample_rate_hz_ = info->rate; + frame.samples_per_channel_ = gst_buffer_get_size(buffer) / info->bpf; + memcpy(frame.data_, audio_buffer.planes[0], frame.samples_per_channel_ * info->bpf); + + int err = apm->AnalyzeReverseStream(&frame); + if (err < 0) g_warning("ProcessReverseStream %i", err); + + gst_audio_buffer_unmap(&audio_buffer); +} + +extern "C" void dino_plugins_rtp_voice_processor_notify_gain_level(void *native_ptr, gint gain_level) { + _DinoPluginsRtpVoiceProcessorNative *native = (_DinoPluginsRtpVoiceProcessorNative *) native_ptr; + webrtc::AudioProcessing *apm = native->apm; + apm->gain_control()->set_stream_analog_level(gain_level); +} + +extern "C" gint dino_plugins_rtp_voice_processor_get_suggested_gain_level(void *native_ptr) { + _DinoPluginsRtpVoiceProcessorNative *native = (_DinoPluginsRtpVoiceProcessorNative *) native_ptr; + webrtc::AudioProcessing *apm = native->apm; + return apm->gain_control()->stream_analog_level(); +} + +extern "C" bool dino_plugins_rtp_voice_processor_get_stream_has_voice(void *native_ptr) { + _DinoPluginsRtpVoiceProcessorNative *native = (_DinoPluginsRtpVoiceProcessorNative *) native_ptr; + webrtc::AudioProcessing *apm = native->apm; + return apm->voice_detection()->stream_has_voice(); +} + +extern "C" void dino_plugins_rtp_voice_processor_adjust_stream_delay(void *native_ptr) { + _DinoPluginsRtpVoiceProcessorNative *native = (_DinoPluginsRtpVoiceProcessorNative *) native_ptr; + webrtc::AudioProcessing *apm = native->apm; + int median, std; + float fraction_poor_delays; + apm->echo_cancellation()->GetDelayMetrics(&median, &std, &fraction_poor_delays); + if (fraction_poor_delays < 0) return; + g_debug("voice_processor_native.cpp: Stream delay metrics: %i %i %f", median, std, fraction_poor_delays); + if (fraction_poor_delays > 0.5) { + native->stream_delay = std::max(0, native->stream_delay + std::min(-10, std::max(median, 10))); + g_debug("Adjusted stream delay %i", native->stream_delay); + } +} + +extern "C" void +dino_plugins_rtp_voice_processor_process_stream(void *native_ptr, GstAudioInfo *info, GstBuffer *buffer) { + _DinoPluginsRtpVoiceProcessorNative *native = (_DinoPluginsRtpVoiceProcessorNative *) native_ptr; + webrtc::StreamConfig config(SAMPLE_RATE, SAMPLE_CHANNELS, false); + webrtc::AudioProcessing *apm = native->apm; + + GstAudioBuffer audio_buffer; + gst_audio_buffer_map(&audio_buffer, info, buffer, GST_MAP_READWRITE); + + webrtc::AudioFrame frame; + frame.num_channels_ = info->channels; + frame.sample_rate_hz_ = info->rate; + frame.samples_per_channel_ = info->rate / 100; + memcpy(frame.data_, audio_buffer.planes[0], frame.samples_per_channel_ * info->bpf); + + apm->set_stream_delay_ms(native->stream_delay); + int err = apm->ProcessStream(&frame); + if (err >= 0) memcpy(audio_buffer.planes[0], frame.data_, frame.samples_per_channel_ * info->bpf); + if (err < 0) g_warning("ProcessStream %i", err); + + gst_audio_buffer_unmap(&audio_buffer); +} + +extern "C" void dino_plugins_rtp_voice_processor_destroy_native(void *native_ptr) { + _DinoPluginsRtpVoiceProcessorNative *native = (_DinoPluginsRtpVoiceProcessorNative *) native_ptr; + delete native; +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From d388525fc69ab688a90f19d2d2499e0f6f10f573 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sat, 1 May 2021 16:00:37 +0200 Subject: Correctly handle missing webrtc-audio-processing --- .github/workflows/build.yml | 2 +- plugins/rtp/src/device.vala | 4 +++- plugins/rtp/src/plugin.vala | 5 +++-- 3 files changed, 7 insertions(+), 4 deletions(-) (limited to 'plugins/rtp/src/plugin.vala') diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ce12d441..60d587e9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,7 +6,7 @@ jobs: steps: - uses: actions/checkout@v2 - run: sudo apt-get update - - run: sudo apt-get install -y build-essential gettext cmake valac libgee-0.8-dev libsqlite3-dev libgtk-3-dev libnotify-dev libgpgme-dev libsoup2.4-dev libgcrypt20-dev libqrencode-dev libgspell-1-dev libnice-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libsrtp2-dev + - run: sudo apt-get install -y build-essential gettext cmake valac libgee-0.8-dev libsqlite3-dev libgtk-3-dev libnotify-dev libgpgme-dev libsoup2.4-dev libgcrypt20-dev libqrencode-dev libgspell-1-dev libnice-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libsrtp2-dev libwebrtc-audio-processing-dev - run: ./configure --with-tests --with-libsignal-in-tree - run: make - run: build/xmpp-vala-test diff --git a/plugins/rtp/src/device.vala b/plugins/rtp/src/device.vala index f8894502..3c650ad6 100644 --- a/plugins/rtp/src/device.vala +++ b/plugins/rtp/src/device.vala @@ -132,12 +132,14 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object { filter.@set("caps", get_best_caps()); pipe.add(filter); element.link(filter); +#if WITH_VOICE_PROCESSOR if (media == "audio" && plugin.echoprobe != null) { - dsp = new VoiceProcessor(plugin.echoprobe, element as Gst.Audio.StreamVolume); + dsp = new VoiceProcessor(plugin.echoprobe as EchoProbe, element as Gst.Audio.StreamVolume); dsp.name = @"dsp_$id"; pipe.add(dsp); filter.link(dsp); } +#endif tee = Gst.ElementFactory.make("tee", @"tee_$id"); tee.@set("allow-not-linked", true); pipe.add(tee); diff --git a/plugins/rtp/src/plugin.vala b/plugins/rtp/src/plugin.vala index e3d5ee41..f575a7d0 100644 --- a/plugins/rtp/src/plugin.vala +++ b/plugins/rtp/src/plugin.vala @@ -8,7 +8,7 @@ public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object { public Gst.DeviceMonitor device_monitor { get; private set; } public Gst.Pipeline pipe { get; private set; } public Gst.Bin rtpbin { get; private set; } - public EchoProbe echoprobe { get; private set; } + public Gst.Element echoprobe { get; private set; } private Gee.List streams = new ArrayList(); private Gee.List devices = new ArrayList(); @@ -71,10 +71,11 @@ public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object { rtpbin.connect("signal::request-pt-map", request_pt_map, this); pipe.add(rtpbin); +#if WITH_VOICE_PROCESSOR // Audio echo probe -// echoprobe = Gst.ElementFactory.make("webrtcechoprobe", "echo-probe"); echoprobe = new EchoProbe(); if (echoprobe != null) pipe.add(echoprobe); +#endif // Pipeline pipe.auto_flush_bus = true; -- cgit v1.2.3-70-g09d2 From 0409f554268c0e8f24e23e471a94de4d3a035ff1 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sat, 1 May 2021 17:27:55 +0200 Subject: Fix webcam framerate selection --- plugins/rtp/src/device.vala | 33 ++++++++++++++++++++++++++++-- plugins/rtp/src/plugin.vala | 6 +----- plugins/rtp/src/voice_processor_native.cpp | 6 +++--- 3 files changed, 35 insertions(+), 10 deletions(-) (limited to 'plugins/rtp/src/plugin.vala') diff --git a/plugins/rtp/src/device.vala b/plugins/rtp/src/device.vala index 3c650ad6..e25271b1 100644 --- a/plugins/rtp/src/device.vala +++ b/plugins/rtp/src/device.vala @@ -87,6 +87,7 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object { 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; + Value? best_fraction = null; int best_fps = 0; int best_width = 0; int best_height = 0; @@ -94,7 +95,28 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object { unowned Gst.Structure? that = device.caps.get_structure(i); if (!that.has_name("video/x-raw")) continue; int num = 0, den = 0, width = 0, height = 0; - if (!that.has_field("framerate") || !that.get_fraction("framerate", out num, out den)) continue; + if (!that.has_field("framerate")) continue; + Value framerate = that.get_value("framerate"); + if (framerate.type() == typeof(Gst.Fraction)) { + num = Gst.Value.get_fraction_numerator(framerate); + den = Gst.Value.get_fraction_denominator(framerate); + } else if (framerate.type() == typeof(Gst.ValueList)) { + for(uint j = 0; j < Gst.ValueList.get_size(framerate); j++) { + Value fraction = Gst.ValueList.get_value(framerate, j); + int in_num = Gst.Value.get_fraction_numerator(fraction); + int in_den = Gst.Value.get_fraction_denominator(fraction); + int fps = den > 0 ? (num/den) : 0; + int in_fps = in_den > 0 ? (in_num/in_den) : 0; + if (in_fps > fps) { + best_fraction = fraction; + num = in_num; + den = in_den; + } + } + } else { + debug("Unknown type for framerate: %s", framerate.type_name()); + } + if (den == 0) continue; if (!that.has_field("width") || !that.get_int("width", out width)) continue; if (!that.has_field("height") || !that.get_int("height", out height)) continue; int fps = num/den; @@ -105,7 +127,14 @@ public class Dino.Plugins.Rtp.Device : MediaDevice, Object { best_index = i; } } - return caps_copy_nth(device.caps, best_index); + 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)) { + that.set_value("framerate", best_fraction); + } + debug("Selected caps %s", res.to_string()); + return res; } else if (device.caps.get_size() > 0) { return caps_copy_nth(device.caps, 0); } else { diff --git a/plugins/rtp/src/plugin.vala b/plugins/rtp/src/plugin.vala index f575a7d0..19a266b1 100644 --- a/plugins/rtp/src/plugin.vala +++ b/plugins/rtp/src/plugin.vala @@ -136,11 +136,7 @@ public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object { pipe.set_state(Gst.State.PLAYING); break; case Gst.MessageType.STATE_CHANGED: - Gst.State new_state; - message.parse_state_changed(null, out new_state, null); - if (message.src is Gst.Element) { - debug("%s changed state to %s", ((Gst.Element)message.src).name, new_state.to_string()); - } + // Ignore break; case Gst.MessageType.STREAM_STATUS: Gst.StreamStatusType status; diff --git a/plugins/rtp/src/voice_processor_native.cpp b/plugins/rtp/src/voice_processor_native.cpp index 9b3292b8..00f719e1 100644 --- a/plugins/rtp/src/voice_processor_native.cpp +++ b/plugins/rtp/src/voice_processor_native.cpp @@ -75,7 +75,7 @@ dino_plugins_rtp_voice_processor_analyze_reverse_stream(void *native_ptr, GstAud memcpy(frame.data_, audio_buffer.planes[0], frame.samples_per_channel_ * info->bpf); int err = apm->AnalyzeReverseStream(&frame); - if (err < 0) g_warning("ProcessReverseStream %i", err); + if (err < 0) g_warning("voice_processor_native.cpp: ProcessReverseStream %i", err); gst_audio_buffer_unmap(&audio_buffer); } @@ -108,7 +108,7 @@ extern "C" void dino_plugins_rtp_voice_processor_adjust_stream_delay(void *nativ g_debug("voice_processor_native.cpp: Stream delay metrics: %i %i %f", median, std, fraction_poor_delays); if (fraction_poor_delays > 0.5) { native->stream_delay = std::max(0, native->stream_delay + std::min(-10, std::max(median, 10))); - g_debug("Adjusted stream delay %i", native->stream_delay); + g_debug("voice_processor_native.cpp: Adjusted stream delay %i", native->stream_delay); } } @@ -130,7 +130,7 @@ dino_plugins_rtp_voice_processor_process_stream(void *native_ptr, GstAudioInfo * apm->set_stream_delay_ms(native->stream_delay); int err = apm->ProcessStream(&frame); if (err >= 0) memcpy(audio_buffer.planes[0], frame.data_, frame.samples_per_channel_ * info->bpf); - if (err < 0) g_warning("ProcessStream %i", err); + if (err < 0) g_warning("voice_processor_native.cpp: ProcessStream %i", err); gst_audio_buffer_unmap(&audio_buffer); } -- cgit v1.2.3-70-g09d2