payload-type and rtp-hdrext sdp parsing

This commit is contained in:
Daniel Gultsch 2020-04-04 16:51:51 +02:00
parent 5b1d86d67e
commit 18059345c8
6 changed files with 195 additions and 47 deletions

View file

@ -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_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_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_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 IBB = "http://jabber.org/protocol/ibb";
public static final String PING = "urn:xmpp:ping"; public static final String PING = "urn:xmpp:ping";
public static final String PUSH = "urn:xmpp:push:0"; public static final String PUSH = "urn:xmpp:push:0";

View file

@ -252,11 +252,9 @@ public class JingleRtpConnection extends AbstractJingleConnection {
@Override @Override
public void onCreateSuccess(org.webrtc.SessionDescription description) { public void onCreateSuccess(org.webrtc.SessionDescription description) {
final SessionDescription sessionDescription = SessionDescription.parse(description.description); final SessionDescription sessionDescription = SessionDescription.parse(description.description);
Log.d(Config.LOGTAG,"description: "+description.description);
for (SessionDescription.Media media : sessionDescription.media) { for (SessionDescription.Media media : sessionDescription.media) {
Log.d(Config.LOGTAG, "media: " + media.protocol); Log.d(Config.LOGTAG, RtpDescription.of(media).toString());
for (SessionDescription.Attribute attribute : media.attributes) {
Log.d(Config.LOGTAG, "attribute key=" + attribute.key + ", value=" + attribute.value);
}
} }
Log.d(Config.LOGTAG, sessionDescription.toString()); Log.d(Config.LOGTAG, sessionDescription.toString());
} }

View file

@ -1,5 +1,7 @@
package eu.siacs.conversations.xmpp.jingle; package eu.siacs.conversations.xmpp.jingle;
import com.google.common.collect.ArrayListMultimap;
import java.util.List; import java.util.List;
public class MediaBuilder { public class MediaBuilder {
@ -8,7 +10,7 @@ public class MediaBuilder {
private String protocol; private String protocol;
private List<Integer> formats; private List<Integer> formats;
private String connectionData; private String connectionData;
private List<SessionDescription.Attribute> attributes; private ArrayListMultimap<String,String> attributes;
public MediaBuilder setMedia(String media) { public MediaBuilder setMedia(String media) {
this.media = media; this.media = media;
@ -35,7 +37,7 @@ public class MediaBuilder {
return this; return this;
} }
public MediaBuilder setAttributes(List<SessionDescription.Attribute> attributes) { public MediaBuilder setAttributes(ArrayListMultimap<String,String> attributes) {
this.attributes = attributes; this.attributes = attributes;
return this; return this;
} }

View file

@ -1,7 +1,9 @@
package eu.siacs.conversations.xmpp.jingle; package eu.siacs.conversations.xmpp.jingle;
import android.util.Log; import android.util.Log;
import android.util.Pair;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.util.List; import java.util.List;
@ -14,11 +16,11 @@ public class SessionDescription {
public final int version; public final int version;
public final String name; public final String name;
public final String connectionData; public final String connectionData;
public final List<Attribute> attributes; public final ArrayListMultimap<String, String> attributes;
public final List<Media> media; 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.version = version;
this.name = name; this.name = name;
this.connectionData = connectionData; this.connectionData = connectionData;
@ -34,10 +36,10 @@ public class SessionDescription {
public static SessionDescription parse(final String input) { public static SessionDescription parse(final String input) {
final SessionDescriptionBuilder sessionDescriptionBuilder = new SessionDescriptionBuilder(); final SessionDescriptionBuilder sessionDescriptionBuilder = new SessionDescriptionBuilder();
MediaBuilder currentMediaBuilder = null; MediaBuilder currentMediaBuilder = null;
ImmutableList.Builder<Attribute> attributeBuilder = new ImmutableList.Builder<>(); ArrayListMultimap<String, String> attributeMap = ArrayListMultimap.create();
ImmutableList.Builder<Media> mediaBuilder = new ImmutableList.Builder<>(); ImmutableList.Builder<Media> mediaBuilder = new ImmutableList.Builder<>();
for (final String line : input.split("\n")) { 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) { if (pair.length < 2 || pair[0].length() != 1) {
Log.d(Config.LOGTAG, "skipping sdp parsing on line " + line); Log.d(Config.LOGTAG, "skipping sdp parsing on line " + line);
continue; continue;
@ -59,17 +61,18 @@ public class SessionDescription {
sessionDescriptionBuilder.setName(value); sessionDescriptionBuilder.setName(value);
break; break;
case 'a': case 'a':
attributeBuilder.add(Attribute.parse(value)); final Pair<String, String> attribute = parseAttribute(value);
attributeMap.put(attribute.first, attribute.second);
break; break;
case 'm': case 'm':
if (currentMediaBuilder == null) { if (currentMediaBuilder == null) {
sessionDescriptionBuilder.setAttributes(attributeBuilder.build()); sessionDescriptionBuilder.setAttributes(attributeMap);
; ;
} else { } else {
currentMediaBuilder.setAttributes(attributeBuilder.build()); currentMediaBuilder.setAttributes(attributeMap);
mediaBuilder.add(currentMediaBuilder.createMedia()); mediaBuilder.add(currentMediaBuilder.createMedia());
} }
attributeBuilder = new ImmutableList.Builder<>(); attributeMap = ArrayListMultimap.create();
currentMediaBuilder = new MediaBuilder(); currentMediaBuilder = new MediaBuilder();
final String[] parts = value.split(" "); final String[] parts = value.split(" ");
if (parts.length >= 3) { if (parts.length >= 3) {
@ -89,14 +92,14 @@ public class SessionDescription {
} }
if (currentMediaBuilder != null) { if (currentMediaBuilder != null) {
currentMediaBuilder.setAttributes(attributeBuilder.build()); currentMediaBuilder.setAttributes(attributeMap);
mediaBuilder.add(currentMediaBuilder.createMedia()); mediaBuilder.add(currentMediaBuilder.createMedia());
} }
sessionDescriptionBuilder.setMedia(mediaBuilder.build()); sessionDescriptionBuilder.setMedia(mediaBuilder.build());
return sessionDescriptionBuilder.createSessionDescription(); return sessionDescriptionBuilder.createSessionDescription();
} }
private static int ignorantIntParser(final String input) { public static int ignorantIntParser(final String input) {
try { try {
return Integer.parseInt(input); return Integer.parseInt(input);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
@ -104,36 +107,24 @@ public class SessionDescription {
} }
} }
public static class Attribute { public static Pair<String, String> parseAttribute(final String input) {
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); final String[] pair = input.split(":", 2);
if (pair.length == 2) { if (pair.length == 2) {
return new Attribute(pair[0], pair[1]); return new Pair<>(pair[0], pair[1]);
} else { } else {
return new Attribute(pair[0], null); return new Pair<>(pair[0], "");
} }
} }
}
public static class Media { public static class Media {
public final String media; public final String media;
public final int port; public final int port;
public final String protocol; public final String protocol;
public final List<Integer> formats; public final List<Integer> formats;
public final String connectionData; 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.media = media;
this.port = port; this.port = port;
this.protocol = protocol; this.protocol = protocol;

View file

@ -1,12 +1,14 @@
package eu.siacs.conversations.xmpp.jingle; package eu.siacs.conversations.xmpp.jingle;
import com.google.common.collect.ArrayListMultimap;
import java.util.List; import java.util.List;
public class SessionDescriptionBuilder { public class SessionDescriptionBuilder {
private int version; private int version;
private String name; private String name;
private String connectionData; private String connectionData;
private List<SessionDescription.Attribute> attributes; private ArrayListMultimap<String,String> attributes;
private List<SessionDescription.Media> media; private List<SessionDescription.Media> media;
public SessionDescriptionBuilder setVersion(int version) { public SessionDescriptionBuilder setVersion(int version) {
@ -24,7 +26,7 @@ public class SessionDescriptionBuilder {
return this; return this;
} }
public SessionDescriptionBuilder setAttributes(List<SessionDescription.Attribute> attributes) { public SessionDescriptionBuilder setAttributes(ArrayListMultimap<String,String> attributes) {
this.attributes = attributes; this.attributes = attributes;
return this; return this;
} }

View file

@ -1,19 +1,23 @@
package eu.siacs.conversations.xmpp.jingle.stanzas; package eu.siacs.conversations.xmpp.jingle.stanzas;
import android.util.Log;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.jingle.SessionDescription;
public class RtpDescription extends GenericDescription { public class RtpDescription extends GenericDescription {
private RtpDescription(String name, String namespace) { private RtpDescription() {
super(name, namespace); super("description", Namespace.JINGLE_APPS_RTP);
} }
public Media getMedia() { public Media getMedia() {
@ -30,6 +34,14 @@ public class RtpDescription extends GenericDescription {
return builder.build(); 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() { public List<RtpHeaderExtension> getHeaderExtensions() {
final ImmutableList.Builder<RtpHeaderExtension> builder = new ImmutableList.Builder<>(); final ImmutableList.Builder<RtpHeaderExtension> builder = new ImmutableList.Builder<>();
for (final Element child : getChildren()) { for (final Element child : getChildren()) {
@ -43,13 +55,82 @@ public class RtpDescription extends GenericDescription {
public static RtpDescription upgrade(final Element element) { public static RtpDescription upgrade(final Element element) {
Preconditions.checkArgument("description".equals(element.getName()), "Name of provided element is not description"); 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"); 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.setAttributes(element.getAttributes());
description.setChildren(element.getChildren()); description.setChildren(element.getChildren());
return description; 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 //XEP-0294: Jingle RTP Header Extensions Negotiation
@ -60,6 +141,12 @@ public class RtpDescription extends GenericDescription {
super("rtp-hdrext", Namespace.JINGLE_RTP_HEADER_EXTENSIONS); 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() { public String getId() {
return this.getAttribute("id"); return this.getAttribute("id");
} }
@ -76,14 +163,36 @@ public class RtpDescription extends GenericDescription {
extension.setChildren(element.getChildren()); extension.setChildren(element.getChildren());
return extension; 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` //maps to `rtpmap $id $name/$clockrate/$channels`
public static class PayloadType extends Element { public static class PayloadType extends Element {
private PayloadType(String name, String xmlns) { private PayloadType() {
super(name, xmlns); 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() { public String getId() {
return this.getAttribute("id"); return this.getAttribute("id");
} }
@ -126,13 +235,41 @@ public class RtpDescription extends GenericDescription {
return builder.build(); 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) { public static PayloadType of(final Element element) {
Preconditions.checkArgument("payload-type".equals(element.getName()), "element name must be called payload-type"); 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.setAttributes(element.getAttributes());
payloadType.setChildren(element.getChildren()); payloadType.setChildren(element.getChildren());
return payloadType; 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 //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;
}
} }