diff options
Diffstat (limited to 'plugins/rtp/src/device.vala')
-rw-r--r-- | plugins/rtp/src/device.vala | 272 |
1 files changed, 272 insertions, 0 deletions
diff --git a/plugins/rtp/src/device.vala b/plugins/rtp/src/device.vala new file mode 100644 index 00000000..e25271b1 --- /dev/null +++ b/plugins/rtp/src/device.vala @@ -0,0 +1,272 @@ +public class Dino.Plugins.Rtp.Device : MediaDevice, Object { + public Plugin plugin { get; private set; } + public Gst.Device device { get; private set; } + + private string device_name; + public string id { get { + return device_name; + }} + private string device_display_name; + public string display_name { get { + return device_display_name; + }} + public string detail_name { get { + return device.properties.get_string("alsa.card_name") ?? device.properties.get_string("alsa.id") ?? id; + }} + public Gst.Pipeline pipe { get { + return plugin.pipe; + }} + public string? media { get { + if (device.device_class.has_prefix("Audio/")) { + return "audio"; + } else if (device.device_class.has_prefix("Video/")) { + return "video"; + } else { + return null; + } + }} + public bool is_source { get { + return device.device_class.has_suffix("/Source"); + }} + public bool is_sink { get { + return device.device_class.has_suffix("/Sink"); + }} + + private Gst.Element element; + private Gst.Element tee; + 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) { + this.plugin = plugin; + update(device); + } + + public bool matches(Gst.Device device) { + if (this.device.name == device.name) return true; + return false; + } + + public void update(Gst.Device device) { + this.device = device; + this.device_name = device.name; + this.device_display_name = device.display_name; + } + + public Gst.Element? link_sink() { + if (element == null) create(); + links++; + if (mixer != null) return mixer; + if (is_sink && media == "audio") return filter; + return element; + } + + public Gst.Element? link_source() { + if (element == null) create(); + links++; + if (tee != null) return tee; + return element; + } + + public void unlink() { + if (links <= 0) { + critical("Link count below zero."); + return; + } + links--; + if (links == 0) { + destroy(); + } + } + + private Gst.Caps get_best_caps() { + if (media == "audio") { + return Gst.Caps.from_string("audio/x-raw,rate=48000,channels=1"); + } else if (media == "video" && device.caps.get_size() > 0) { + int best_index = 0; + Value? best_fraction = null; + int best_fps = 0; + int best_width = 0; + int best_height = 0; + for (int i = 0; i < device.caps.get_size(); i++) { + unowned Gst.Structure? that = device.caps.get_structure(i); + if (!that.has_name("video/x-raw")) continue; + int num = 0, den = 0, width = 0, height = 0; + 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; + if (best_fps < fps || best_fps == fps && best_width < width || best_fps == fps && best_width == width && best_height < height) { + best_fps = fps; + best_width = width; + best_height = height; + best_index = i; + } + } + Gst.Caps res = caps_copy_nth(device.caps, best_index); + unowned Gst.Structure? that = res.get_structure(0); + Value framerate = that.get_value("framerate"); + if (framerate.type() == typeof(Gst.ValueList)) { + 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 { + return new Gst.Caps.any(); + } + } + + // Backport from gst_caps_copy_nth added in GStreamer 1.16 + private static Gst.Caps caps_copy_nth(Gst.Caps source, uint index) { + Gst.Caps target = new Gst.Caps.empty(); + target.flags = source.flags; + target.append_structure_full(source.get_structure(index).copy(), source.get_features(index).copy()); + return target; + } + + private void create() { + debug("Creating device %s", id); + plugin.pause(); + element = device.create_element(id); + pipe.add(element); + if (is_source) { + 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 WITH_VOICE_PROCESSOR + if (media == "audio" && plugin.echoprobe != null) { + 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); + (dsp ?? filter).link(tee); + } + if (is_sink) { + element.@set("async", false); + element.@set("sync", false); + } + if (is_sink && media == "audio") { + filter = Gst.ElementFactory.make("capsfilter", @"caps_filter_$id"); + filter.@set("caps", get_best_caps()); + pipe.add(filter); + if (plugin.echoprobe != null) { + 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); + } + } + plugin.unpause(); + } + + private void destroy() { + if (mixer != null) { + if (is_sink && media == "audio" && plugin.echoprobe != null) { + plugin.echoprobe.unlink(mixer); + } + int linked_sink_pads = 0; + mixer.foreach_sink_pad((_, pad) => { + if (pad.is_linked()) linked_sink_pads++; + return true; + }); + if (linked_sink_pads > 0) { + warning("%s-mixer still has %i sink pads while being destroyed", id, linked_sink_pads); + } + mixer.set_locked_state(true); + mixer.set_state(Gst.State.NULL); + mixer.unlink(element); + pipe.remove(mixer); + mixer = null; + } else if (is_sink && media == "audio") { + if (filter != null) { + filter.set_locked_state(true); + filter.set_state(Gst.State.NULL); + 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); + } + } + element.set_locked_state(true); + element.set_state(Gst.State.NULL); + if (filter != null) element.unlink(filter); + else if (is_source) element.unlink(tee); + pipe.remove(element); + element = null; + if (filter != null) { + filter.set_locked_state(true); + filter.set_state(Gst.State.NULL); + filter.unlink(dsp ?? tee); + pipe.remove(filter); + filter = null; + } + if (dsp != null) { + dsp.set_locked_state(true); + dsp.set_state(Gst.State.NULL); + dsp.unlink(tee); + pipe.remove(dsp); + dsp = null; + } + if (tee != null) { + int linked_src_pads = 0; + tee.foreach_src_pad((_, pad) => { + if (pad.is_linked()) linked_src_pads++; + return true; + }); + if (linked_src_pads != 0) { + warning("%s-tee still has %d src pads while being destroyed", id, linked_src_pads); + } + tee.set_locked_state(true); + tee.set_state(Gst.State.NULL); + pipe.remove(tee); + tee = null; + } + debug("Destroyed device %s", id); + } +}
\ No newline at end of file |