aboutsummaryrefslogtreecommitdiff
path: root/plugins/rtp/src
diff options
context:
space:
mode:
authorMarvin W <git@larma.de>2021-04-29 15:46:06 +0200
committerMarvin W <git@larma.de>2021-04-29 15:53:59 +0200
commit3880628de4785db4c0a03a79a0c486507fe9b1a8 (patch)
tree488180882a2a60576c68e688b743acfaff8d32f7 /plugins/rtp/src
parent328c3cf37f296f4519829afd03d21f94ea4153e5 (diff)
downloaddino-3880628de4785db4c0a03a79a0c486507fe9b1a8.tar.gz
dino-3880628de4785db4c0a03a79a0c486507fe9b1a8.zip
Video optimizations
Diffstat (limited to 'plugins/rtp/src')
-rw-r--r--plugins/rtp/src/codec_util.vala115
-rw-r--r--plugins/rtp/src/device.vala9
-rw-r--r--plugins/rtp/src/module.vala133
-rw-r--r--plugins/rtp/src/plugin.vala14
-rw-r--r--plugins/rtp/src/stream.vala220
-rw-r--r--plugins/rtp/src/video_widget.vala10
6 files changed, 386 insertions, 115 deletions
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<string> supported_elements = new HashSet<string>();
private Set<string> unsupported_elements = new HashSet<string>();
- 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<JingleRtp.HeaderExtension> get_suggested_header_extensions(string media) {
+ Gee.List<JingleRtp.HeaderExtension> exts = new ArrayList<JingleRtp.HeaderExtension>();
+ if (media == "video") {
+ exts.add(new JingleRtp.HeaderExtension(1, "urn:3gpp:video-orientation"));
+ }
+ return exts;
+ }
+
public async void add_if_supported(Gee.List<JingleRtp.PayloadType> 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<JingleRtp.PayloadType> get_supported_payloads(string media) {
Gee.List<JingleRtp.PayloadType> list = new ArrayList<JingleRtp.PayloadType>(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<JingleRtp.RtcpFeedback>();
+ 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<JingleRtp.PayloadType> 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<Gst.Element> outputs = new ArrayList<Gst.Element>();
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);