payload-type and rtp-hdrext sdp parsing
This commit is contained in:
parent
5b1d86d67e
commit
18059345c8
|
@ -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,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;
|
||||
|
|
|
@ -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() {
|
||||
|
@ -30,6 +34,14 @@ 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()) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue