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_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";

View file

@ -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());
}

View file

@ -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;
}

View file

@ -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,36 +107,24 @@ 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) {
public static Pair<String, String> parseAttribute(final String input) {
final String[] pair = input.split(":", 2);
if (pair.length == 2) {
return new Attribute(pair[0], pair[1]);
return new Pair<>(pair[0], pair[1]);
} else {
return new Attribute(pair[0], null);
return new Pair<>(pair[0], "");
}
}
}
public static class Media {
public final String media;
public final int port;
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;

View file

@ -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;
}

View file

@ -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;
}
}