Detailed changes
@@ -39,6 +39,7 @@ public final class Namespace {
public static final String JINGLE_FEATURE_AUDIO = "urn:xmpp:jingle:apps:rtp:audio";
public static final String JINGLE_FEATURE_VIDEO = "urn:xmpp:jingle:apps:rtp:video";
public static final String JINGLE_RTP_HEADER_EXTENSIONS = "urn:xmpp:jingle:apps:rtp:rtp-hdrext:0";
+ public static final String JINGLE_RTP_FEEDBACK_NEGOTIATION = "urn:xmpp:jingle:apps:rtp:rtcp-fb:0";
public static final String IBB = "http://jabber.org/protocol/ibb";
public static final String PING = "urn:xmpp:ping";
public static final String PUSH = "urn:xmpp:push:0";
@@ -252,11 +252,9 @@ public class JingleRtpConnection extends AbstractJingleConnection {
@Override
public void onCreateSuccess(org.webrtc.SessionDescription description) {
final SessionDescription sessionDescription = SessionDescription.parse(description.description);
+ Log.d(Config.LOGTAG,"description: "+description.description);
for (SessionDescription.Media media : sessionDescription.media) {
- Log.d(Config.LOGTAG, "media: " + media.protocol);
- for (SessionDescription.Attribute attribute : media.attributes) {
- Log.d(Config.LOGTAG, "attribute key=" + attribute.key + ", value=" + attribute.value);
- }
+ Log.d(Config.LOGTAG, RtpDescription.of(media).toString());
}
Log.d(Config.LOGTAG, sessionDescription.toString());
}
@@ -1,5 +1,7 @@
package eu.siacs.conversations.xmpp.jingle;
+import com.google.common.collect.ArrayListMultimap;
+
import java.util.List;
public class MediaBuilder {
@@ -8,7 +10,7 @@ public class MediaBuilder {
private String protocol;
private List<Integer> formats;
private String connectionData;
- private List<SessionDescription.Attribute> attributes;
+ private ArrayListMultimap<String,String> attributes;
public MediaBuilder setMedia(String media) {
this.media = media;
@@ -35,7 +37,7 @@ public class MediaBuilder {
return this;
}
- public MediaBuilder setAttributes(List<SessionDescription.Attribute> attributes) {
+ public MediaBuilder setAttributes(ArrayListMultimap<String,String> attributes) {
this.attributes = attributes;
return this;
}
@@ -1,7 +1,9 @@
package eu.siacs.conversations.xmpp.jingle;
import android.util.Log;
+import android.util.Pair;
+import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import java.util.List;
@@ -14,11 +16,11 @@ public class SessionDescription {
public final int version;
public final String name;
public final String connectionData;
- public final List<Attribute> attributes;
+ public final ArrayListMultimap<String, String> attributes;
public final List<Media> media;
- public SessionDescription(int version, String name, String connectionData, List<Attribute> attributes, List<Media> media) {
+ public SessionDescription(int version, String name, String connectionData, ArrayListMultimap<String, String> attributes, List<Media> media) {
this.version = version;
this.name = name;
this.connectionData = connectionData;
@@ -34,10 +36,10 @@ public class SessionDescription {
public static SessionDescription parse(final String input) {
final SessionDescriptionBuilder sessionDescriptionBuilder = new SessionDescriptionBuilder();
MediaBuilder currentMediaBuilder = null;
- ImmutableList.Builder<Attribute> attributeBuilder = new ImmutableList.Builder<>();
+ ArrayListMultimap<String, String> attributeMap = ArrayListMultimap.create();
ImmutableList.Builder<Media> mediaBuilder = new ImmutableList.Builder<>();
for (final String line : input.split("\n")) {
- final String[] pair = line.split("=", 2);
+ final String[] pair = line.trim().split("=", 2);
if (pair.length < 2 || pair[0].length() != 1) {
Log.d(Config.LOGTAG, "skipping sdp parsing on line " + line);
continue;
@@ -59,17 +61,18 @@ public class SessionDescription {
sessionDescriptionBuilder.setName(value);
break;
case 'a':
- attributeBuilder.add(Attribute.parse(value));
+ final Pair<String, String> attribute = parseAttribute(value);
+ attributeMap.put(attribute.first, attribute.second);
break;
case 'm':
if (currentMediaBuilder == null) {
- sessionDescriptionBuilder.setAttributes(attributeBuilder.build());
+ sessionDescriptionBuilder.setAttributes(attributeMap);
;
} else {
- currentMediaBuilder.setAttributes(attributeBuilder.build());
+ currentMediaBuilder.setAttributes(attributeMap);
mediaBuilder.add(currentMediaBuilder.createMedia());
}
- attributeBuilder = new ImmutableList.Builder<>();
+ attributeMap = ArrayListMultimap.create();
currentMediaBuilder = new MediaBuilder();
final String[] parts = value.split(" ");
if (parts.length >= 3) {
@@ -89,14 +92,14 @@ public class SessionDescription {
}
if (currentMediaBuilder != null) {
- currentMediaBuilder.setAttributes(attributeBuilder.build());
+ currentMediaBuilder.setAttributes(attributeMap);
mediaBuilder.add(currentMediaBuilder.createMedia());
}
sessionDescriptionBuilder.setMedia(mediaBuilder.build());
return sessionDescriptionBuilder.createSessionDescription();
}
- private static int ignorantIntParser(final String input) {
+ public static int ignorantIntParser(final String input) {
try {
return Integer.parseInt(input);
} catch (NumberFormatException e) {
@@ -104,25 +107,13 @@ public class SessionDescription {
}
}
- public static class Attribute {
- public final String key;
- public final String value;
-
- public Attribute(String key, String value) {
- this.key = key;
- this.value = value;
- }
-
- public static Attribute parse(String input) {
- final String[] pair = input.split(":", 2);
- if (pair.length == 2) {
- return new Attribute(pair[0], pair[1]);
- } else {
- return new Attribute(pair[0], null);
- }
+ public static Pair<String, String> parseAttribute(final String input) {
+ final String[] pair = input.split(":", 2);
+ if (pair.length == 2) {
+ return new Pair<>(pair[0], pair[1]);
+ } else {
+ return new Pair<>(pair[0], "");
}
-
-
}
public static class Media {
@@ -131,9 +122,9 @@ public class SessionDescription {
public final String protocol;
public final List<Integer> formats;
public final String connectionData;
- public final List<Attribute> attributes;
+ public final ArrayListMultimap<String, String> attributes;
- public Media(String media, int port, String protocol, List<Integer> formats, String connectionData, List<Attribute> attributes) {
+ public Media(String media, int port, String protocol, List<Integer> formats, String connectionData, ArrayListMultimap<String, String> attributes) {
this.media = media;
this.port = port;
this.protocol = protocol;
@@ -1,12 +1,14 @@
package eu.siacs.conversations.xmpp.jingle;
+import com.google.common.collect.ArrayListMultimap;
+
import java.util.List;
public class SessionDescriptionBuilder {
private int version;
private String name;
private String connectionData;
- private List<SessionDescription.Attribute> attributes;
+ private ArrayListMultimap<String,String> attributes;
private List<SessionDescription.Media> media;
public SessionDescriptionBuilder setVersion(int version) {
@@ -24,7 +26,7 @@ public class SessionDescriptionBuilder {
return this;
}
- public SessionDescriptionBuilder setAttributes(List<SessionDescription.Attribute> attributes) {
+ public SessionDescriptionBuilder setAttributes(ArrayListMultimap<String,String> attributes) {
this.attributes = attributes;
return this;
}
@@ -1,19 +1,23 @@
package eu.siacs.conversations.xmpp.jingle.stanzas;
+import android.util.Log;
+
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.Locale;
+import eu.siacs.conversations.Config;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
+import eu.siacs.conversations.xmpp.jingle.SessionDescription;
public class RtpDescription extends GenericDescription {
- private RtpDescription(String name, String namespace) {
- super(name, namespace);
+ private RtpDescription() {
+ super("description", Namespace.JINGLE_APPS_RTP);
}
public Media getMedia() {
@@ -22,7 +26,7 @@ public class RtpDescription extends GenericDescription {
public List<PayloadType> getPayloadTypes() {
final ImmutableList.Builder<PayloadType> builder = new ImmutableList.Builder<>();
- for(Element child : getChildren()) {
+ for (Element child : getChildren()) {
if ("payload-type".equals(child.getName())) {
builder.add(PayloadType.of(child));
}
@@ -30,9 +34,17 @@ public class RtpDescription extends GenericDescription {
return builder.build();
}
+ public List<FeedbackNegotiation> getFeedbackNegotiations() {
+ return FeedbackNegotiation.fromChildren(this.getChildren());
+ }
+
+ public List<FeedbackNegotiationTrrInt> feedbackNegotiationTrrInts() {
+ return FeedbackNegotiationTrrInt.fromChildren(this.getChildren());
+ }
+
public List<RtpHeaderExtension> getHeaderExtensions() {
final ImmutableList.Builder<RtpHeaderExtension> builder = new ImmutableList.Builder<>();
- for(final Element child : getChildren()) {
+ for (final Element child : getChildren()) {
if ("rtp-hdrext".equals(child.getName()) && Namespace.JINGLE_RTP_HEADER_EXTENSIONS.equals(child.getNamespace())) {
builder.add(RtpHeaderExtension.upgrade(child));
}
@@ -43,13 +55,82 @@ public class RtpDescription extends GenericDescription {
public static RtpDescription upgrade(final Element element) {
Preconditions.checkArgument("description".equals(element.getName()), "Name of provided element is not description");
Preconditions.checkArgument(Namespace.JINGLE_APPS_RTP.equals(element.getNamespace()), "Element does not match the jingle rtp namespace");
- final RtpDescription description = new RtpDescription("description", Namespace.JINGLE_APPS_RTP);
+ final RtpDescription description = new RtpDescription();
description.setAttributes(element.getAttributes());
description.setChildren(element.getChildren());
return description;
}
- //TODO: support for https://xmpp.org/extensions/xep-0293.html
+ public static class FeedbackNegotiation extends Element {
+ private FeedbackNegotiation() {
+ super("rtcp-fb", Namespace.JINGLE_RTP_FEEDBACK_NEGOTIATION);
+ }
+
+ public String getType() {
+ return this.getAttribute("type");
+ }
+
+ public String getSubType() {
+ return this.getAttribute("subtype");
+ }
+
+ private static FeedbackNegotiation upgrade(final Element element) {
+ Preconditions.checkArgument("rtcp-fb".equals(element.getName()));
+ Preconditions.checkArgument(Namespace.JINGLE_RTP_FEEDBACK_NEGOTIATION.equals(element.getNamespace()));
+ final FeedbackNegotiation feedback = new FeedbackNegotiation();
+ feedback.setAttributes(element.getAttributes());
+ feedback.setChildren(element.getChildren());
+ return feedback;
+ }
+
+ public static List<FeedbackNegotiation> fromChildren(final List<Element> children) {
+ ImmutableList.Builder<FeedbackNegotiation> builder = new ImmutableList.Builder<>();
+ for (final Element child : children) {
+ if ("rtcp-fb".equals(child.getName()) && Namespace.JINGLE_RTP_FEEDBACK_NEGOTIATION.equals(child.getNamespace())) {
+ builder.add(upgrade(child));
+ }
+ }
+ return builder.build();
+ }
+
+ }
+
+ public static class FeedbackNegotiationTrrInt extends Element {
+ private FeedbackNegotiationTrrInt() {
+ super("rtcp-fb-trr-int", Namespace.JINGLE_RTP_FEEDBACK_NEGOTIATION);
+ }
+
+ public int getValue() {
+ final String value = getAttribute("value");
+ if (value == null) {
+ return 0;
+ }
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+
+ private static FeedbackNegotiationTrrInt upgrade(final Element element) {
+ Preconditions.checkArgument("rtcp-fb-trr-int".equals(element.getName()));
+ Preconditions.checkArgument(Namespace.JINGLE_RTP_FEEDBACK_NEGOTIATION.equals(element.getNamespace()));
+ final FeedbackNegotiationTrrInt trr = new FeedbackNegotiationTrrInt();
+ trr.setAttributes(element.getAttributes());
+ trr.setChildren(element.getChildren());
+ return trr;
+ }
+
+ public static List<FeedbackNegotiationTrrInt> fromChildren(final List<Element> children) {
+ ImmutableList.Builder<FeedbackNegotiationTrrInt> builder = new ImmutableList.Builder<>();
+ for (final Element child : children) {
+ if ("rtcp-fb-trr-int".equals(child.getName()) && Namespace.JINGLE_RTP_FEEDBACK_NEGOTIATION.equals(child.getNamespace())) {
+ builder.add(upgrade(child));
+ }
+ }
+ return builder.build();
+ }
+ }
//XEP-0294: Jingle RTP Header Extensions Negotiation
@@ -60,6 +141,12 @@ public class RtpDescription extends GenericDescription {
super("rtp-hdrext", Namespace.JINGLE_RTP_HEADER_EXTENSIONS);
}
+ public RtpHeaderExtension(String id, String uri) {
+ super("rtp-hdrext", Namespace.JINGLE_RTP_HEADER_EXTENSIONS);
+ this.setAttribute("id", id);
+ this.setAttribute("uri", uri);
+ }
+
public String getId() {
return this.getAttribute("id");
}
@@ -76,14 +163,36 @@ public class RtpDescription extends GenericDescription {
extension.setChildren(element.getChildren());
return extension;
}
+
+ public static RtpHeaderExtension ofSdpString(final String sdp) {
+ final String[] pair = sdp.split(" ", 2);
+ if (pair.length == 2) {
+ final String id = pair[0];
+ final String uri = pair[1];
+ return new RtpHeaderExtension(id,uri);
+ } else {
+ return null;
+ }
+ }
}
//maps to `rtpmap $id $name/$clockrate/$channels`
public static class PayloadType extends Element {
- private PayloadType(String name, String xmlns) {
- super(name, xmlns);
+ private PayloadType() {
+ super("payload-type", Namespace.JINGLE_APPS_RTP);
+ }
+
+ public PayloadType(String id, String name, int clockRate, int channels) {
+ super("payload-type", Namespace.JINGLE_APPS_RTP);
+ this.setAttribute("id",id);
+ this.setAttribute("name", name);
+ this.setAttribute("clockrate", clockRate);
+ if (channels != 1) {
+ this.setAttribute("channels", channels);
+ }
}
+
public String getId() {
return this.getAttribute("id");
}
@@ -126,13 +235,41 @@ public class RtpDescription extends GenericDescription {
return builder.build();
}
+ public List<FeedbackNegotiation> getFeedbackNegotiations() {
+ return FeedbackNegotiation.fromChildren(this.getChildren());
+ }
+
+ public List<FeedbackNegotiationTrrInt> feedbackNegotiationTrrInts() {
+ return FeedbackNegotiationTrrInt.fromChildren(this.getChildren());
+ }
+
public static PayloadType of(final Element element) {
Preconditions.checkArgument("payload-type".equals(element.getName()), "element name must be called payload-type");
- PayloadType payloadType = new PayloadType("payload-type", Namespace.JINGLE_APPS_RTP);
+ PayloadType payloadType = new PayloadType();
payloadType.setAttributes(element.getAttributes());
payloadType.setChildren(element.getChildren());
return payloadType;
}
+
+ public static PayloadType ofSdpString(final String sdp) {
+ final String[] pair = sdp.split(" ",2);
+ if (pair.length == 2) {
+ final String id = pair[0];
+ final String[] parts = pair[1].split("/");
+ if (parts.length >= 2) {
+ final String name = parts[0];
+ final int clockRate = SessionDescription.ignorantIntParser(parts[1]);
+ final int channels;
+ if (parts.length >= 3) {
+ channels = SessionDescription.ignorantIntParser(parts[2]);
+ } else {
+ channels =1;
+ }
+ return new PayloadType(id,name,clockRate,channels);
+ }
+ }
+ return null;
+ }
}
//map to `fmtp $id key=value;key=value
@@ -182,4 +319,21 @@ public class RtpDescription extends GenericDescription {
}
}
}
+
+ public static RtpDescription of(final SessionDescription.Media media) {
+ final RtpDescription rtpDescription = new RtpDescription();
+ for(final String rtpmap : media.attributes.get("rtpmap")) {
+ final PayloadType payloadType = PayloadType.ofSdpString(rtpmap);
+ if (payloadType != null) {
+ rtpDescription.addChild(payloadType);
+ }
+ }
+ for(final String extmap : media.attributes.get("extmap")) {
+ final RtpHeaderExtension extension = RtpHeaderExtension.ofSdpString(extmap);
+ if (extension != null) {
+ rtpDescription.addChild(extension);
+ }
+ }
+ return rtpDescription;
+ }
}