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/module.vala | 264 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 plugins/rtp/src/module.vala (limited to 'plugins/rtp/src/module.vala') diff --git a/plugins/rtp/src/module.vala b/plugins/rtp/src/module.vala new file mode 100644 index 00000000..577e9f53 --- /dev/null +++ b/plugins/rtp/src/module.vala @@ -0,0 +1,264 @@ +using Gee; +using Xmpp; +using Xmpp.Xep; + +public class Dino.Plugins.Rtp.Module : JingleRtp.Module { + private Set supported_codecs = new HashSet(); + private Set unsupported_codecs = new HashSet(); + public Plugin plugin { get; private set; } + public CodecUtil codec_util { get { + return plugin.codec_util; + }} + + public Module(Plugin plugin) { + base(); + this.plugin = plugin; + } + + private async bool pipeline_works(string media, string element_desc) { + var supported = false; + string pipeline_desc = @"$(media)testsrc is-live=true ! $element_desc ! appsink name=output"; + try { + var pipeline = Gst.parse_launch(pipeline_desc); + var output = (pipeline as Gst.Bin).get_by_name("output") as Gst.App.Sink; + SourceFunc callback = pipeline_works.callback; + var finished = false; + output.emit_signals = true; + output.new_sample.connect(() => { + if (!finished) { + finished = true; + supported = true; + Idle.add(() => { + callback(); + return Source.REMOVE; + }); + } + return Gst.FlowReturn.EOS; + }); + pipeline.bus.add_watch(Priority.DEFAULT, (_, message) => { + if (message.type == Gst.MessageType.ERROR && !finished) { + Error e; + string d; + message.parse_error(out e, out d); + debug("pipeline [%s] failed: %s", pipeline_desc, e.message); + debug(d); + finished = true; + callback(); + } + return true; + }); + Timeout.add(2000, () => { + if (!finished) { + finished = true; + callback(); + } + return Source.REMOVE; + }); + pipeline.set_state(Gst.State.PLAYING); + yield; + pipeline.set_state(Gst.State.NULL); + } catch (Error e) { + debug("pipeline [%s] failed: %s", pipeline_desc, e.message); + } + return supported; + } + + private async bool supports(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; + 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); + if (encode_element == null || decode_element == null) { + debug("No suitable encoder or decoder found for %s", codec); + unsupported_codecs.add(codec); + return false; + } + + string encode_bin = codec_util.get_encode_bin_description(media, codec, 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); + encode_element = codec_util.get_encode_element_name(media, codec); + if (encode_element == null) { + debug("No suitable encoder found for %s", codec); + unsupported_codecs.add(codec); + return false; + } + encode_bin = codec_util.get_encode_bin_description(media, codec, encode_element); + } + debug("using %s to encode %s", encode_element, codec); + + string decode_bin = codec_util.get_decode_bin_description(media, codec, 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); + decode_element = codec_util.get_decode_element_name(media, codec); + if (decode_element == null) { + debug("No suitable decoder found for %s", codec); + unsupported_codecs.add(codec); + return false; + } + decode_bin = codec_util.get_decode_bin_description(media, codec, decode_element); + } + debug("using %s to decode %s", decode_element, codec); + + supported_codecs.add(codec); + return true; + } + + public async void add_if_supported(Gee.List list, string media, JingleRtp.PayloadType payload_type) { + if (yield supports(media, payload_type)) { + list.add(payload_type); + } + } + + 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 = 96 + }); + yield add_if_supported(list, media, new JingleRtp.PayloadType() { + channels = 1, + clockrate = 32000, + name = "speex", + id = 97 + }); + yield add_if_supported(list, media, new JingleRtp.PayloadType() { + channels = 1, + clockrate = 16000, + name = "speex", + id = 98 + }); + yield add_if_supported(list, media, new JingleRtp.PayloadType() { + channels = 1, + clockrate = 8000, + name = "speex", + id = 99 + }); + 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 + }); + } 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 + }); + } else { + warning("Unsupported media type: %s", media); + } + return list; + } + + 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; + } + } else if (media == "video") { + foreach (JingleRtp.PayloadType type in payloads) { + if (yield supports(media, type)) return type; + } + } else { + warning("Unsupported media type: %s", media); + } + return null; + } + + public override JingleRtp.Stream create_stream(Jingle.Content content) { + return plugin.open_stream(content); + } + + public override void close_stream(JingleRtp.Stream stream) { + var rtp_stream = stream as Rtp.Stream; + plugin.close_stream(rtp_stream); + } + +// public uint32 get_session_id(string id) { +// return (uint32) id.split("-")[0].to_int(); +// } +// +// public string create_feed(string media, bool incoming) { +// init(); +// string id = random_uuid(); +// if (media == "audio") { +// id = "0-" + id; +// } else { +// id = "1-" + id; +// } +// MediaDevice? device = plugin.get_preferred_device(media, incoming); +// Feed feed; +// if (incoming) { +// if (media == "audio") { +// feed = new IncomingAudioFeed(id, this, device); +// } else if (media == "video") { +// feed = new IncomingVideoFeed(id, this, device); +// } else { +// critical("Incoming feed of media '%s' not supported", media); +// return id; +// } +// } else { +// if (media == "audio") { +// string? matching_incoming_feed_id = null; +// foreach (Feed match in plugin.feeds.values) { +// if (match is IncomingAudioFeed) { +// matching_incoming_feed_id = match.id; +// } +// } +// feed = new OutgoingAudioFeed(id, this, device); +// } else if (media == "video") { +// feed = new OutgoingVideoFeed(id, this, device); +// } else { +// critical("Outgoing feed of media '%s' not supported", media); +// return id; +// } +// } +// plugin.add_feed(id, feed); +// return id; +// } +// +// public void connect_feed(string id, JingleRtp.PayloadType payload, Jingle.DatagramConnection connection) { +// if (!plugin.feeds.has_key(id)) { +// critical("Tried to connect feed with id %s, but no such feed found", id); +// return; +// } +// Feed feed = plugin.feeds[id]; +// feed.connect(payload, connection); +// } +// +// public void destroy_feed(string id) { +// if (!plugin.feeds.has_key(id)) { +// critical("Tried to destroy feed with id %s, but no such feed found", id); +// return; +// } +// Feed feed = plugin.feeds[id]; +// feed.destroy(); +// plugin.feeds.remove(id); +// } +} \ No newline at end of file -- cgit v1.2.3-54-g00ecf