diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 2d0e953ad..b872541a9 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -156,6 +156,7 @@ public class JingleRtpConnection extends AbstractJingleConnection { this.initialRtpContentMap = rtpContentMap; final JinglePacket sessionInitiate = rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId); Log.d(Config.LOGTAG, sessionInitiate.toString()); + Log.d(Config.LOGTAG,"here is what we think the sdp looks like"+SessionDescription.of(rtpContentMap).toString()); send(sessionInitiate); } @@ -235,8 +236,6 @@ public class JingleRtpConnection extends AbstractJingleConnection { public void onIceCandidate(IceCandidate iceCandidate) { IceUdpTransportInfo.Candidate candidate = IceUdpTransportInfo.Candidate.fromSdpAttribute(iceCandidate.sdp); Log.d(Config.LOGTAG, "onIceCandidate: " + iceCandidate.sdp); - Log.d(Config.LOGTAG, "xml: " + candidate.toString()); - Log.d(Config.LOGTAG, "mid: " + iceCandidate.sdpMid); sendTransportInfo(iceCandidate.sdpMid, candidate); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java index 3d028a439..e9a6bf854 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java @@ -3,16 +3,28 @@ package eu.siacs.conversations.xmpp.jingle; import android.util.Log; import android.util.Pair; +import com.google.common.base.Joiner; +import com.google.common.base.Strings; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import java.util.List; +import java.util.Locale; import java.util.Map; import eu.siacs.conversations.Config; +import eu.siacs.conversations.xmpp.jingle.stanzas.Group; +import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo; +import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; public class SessionDescription { + private final static String LINE_DIVIDER = "\r\n"; + private final static String HARDCODED_MEDIA_PROTOCOL = "UDP/TLS/RTP/SAVPF"; //probably only true for DTLS-SRTP aka when we have a fingerprint + private final static int HARDCODED_MEDIA_PORT = 1; + private final static String HARDCODED_ICE_OPTIONS = "trickle renomination"; + private final static String HARDCODED_CONNECTION = "IN IP4 0.0.0.0"; + public final int version; public final String name; public final String connectionData; @@ -28,6 +40,18 @@ public class SessionDescription { this.media = media; } + private static void appendAttributes(StringBuilder s, ArrayListMultimap attributes) { + for (Map.Entry attribute : attributes.entries()) { + final String key = attribute.getKey(); + final String value = attribute.getValue(); + s.append("a=").append(key); + if (!Strings.isNullOrEmpty(value)) { + s.append(':').append(value); + } + s.append(LINE_DIVIDER); + } + } + public static SessionDescription parse(final Map contents) { final SessionDescriptionBuilder sessionDescriptionBuilder = new SessionDescriptionBuilder(); return sessionDescriptionBuilder.createSessionDescription(); @@ -38,7 +62,7 @@ public class SessionDescription { MediaBuilder currentMediaBuilder = null; ArrayListMultimap attributeMap = ArrayListMultimap.create(); ImmutableList.Builder mediaBuilder = new ImmutableList.Builder<>(); - for (final String line : input.split("\n")) { + for (final String line : input.split(LINE_DIVIDER)) { 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); @@ -99,6 +123,86 @@ public class SessionDescription { return sessionDescriptionBuilder.createSessionDescription(); } + public static SessionDescription of(final RtpContentMap contentMap) { + final SessionDescriptionBuilder sessionDescriptionBuilder = new SessionDescriptionBuilder(); + final ArrayListMultimap attributeMap = ArrayListMultimap.create(); + final ImmutableList.Builder mediaListBuilder = new ImmutableList.Builder<>(); + final Group group = contentMap.group; + if (group != null) { + attributeMap.put("group", group.getSemantics() + " " + Joiner.on(' ').join(group.getIdentificationTags())); + } + + //random additional attributes + + + for (Map.Entry entry : contentMap.contents.entrySet()) { + final String name = entry.getKey(); + RtpContentMap.DescriptionTransport descriptionTransport = entry.getValue(); + RtpDescription description = descriptionTransport.description; + IceUdpTransportInfo transport = descriptionTransport.transport; + final ArrayListMultimap mediaAttributes = ArrayListMultimap.create(); + final String ufrag = transport.getAttribute("ufrag"); + final String pwd = transport.getAttribute("pwd"); + if (!Strings.isNullOrEmpty(ufrag)) { + mediaAttributes.put("ice-ufrag", ufrag); + } + if (!Strings.isNullOrEmpty(pwd)) { + mediaAttributes.put("ice-pwd", pwd); + } + mediaAttributes.put("ice-options", HARDCODED_ICE_OPTIONS); + final IceUdpTransportInfo.Fingerprint fingerprint = transport.getFingerprint(); + if (fingerprint != null) { + mediaAttributes.put("fingerprint", fingerprint.getHash() + " " + fingerprint.getContent()); + mediaAttributes.put("setup", fingerprint.getSetup()); + } + final ImmutableList.Builder formatBuilder = new ImmutableList.Builder<>(); + for (RtpDescription.PayloadType payloadType : description.getPayloadTypes()) { + formatBuilder.add(payloadType.getIntId()); + mediaAttributes.put("rtpmap", payloadType.toSdpAttribute()); + List parameters = payloadType.getParameters(); + if (parameters.size() > 0) { + mediaAttributes.put("fmtp", RtpDescription.Parameter.toSdpString(payloadType.getId(), parameters)); + } + for (RtpDescription.FeedbackNegotiation feedbackNegotiation : payloadType.getFeedbackNegotiations()) { + mediaAttributes.put("rtcp-fb", payloadType.getId() + " " + feedbackNegotiation.getType() + (Strings.isNullOrEmpty(feedbackNegotiation.getSubType()) ? "" : " " + feedbackNegotiation.getSubType())); + } + for (RtpDescription.FeedbackNegotiationTrrInt feedbackNegotiationTrrInt : payloadType.feedbackNegotiationTrrInts()) { + mediaAttributes.put("rtcp-fb", payloadType.getId() + " trr-int " + feedbackNegotiationTrrInt.getValue()); + } + } + for (RtpDescription.FeedbackNegotiation feedbackNegotiation : description.getFeedbackNegotiations()) { + mediaAttributes.put("rtcp-fb", "* " + feedbackNegotiation.getType() + (Strings.isNullOrEmpty(feedbackNegotiation.getSubType()) ? "" : " " + feedbackNegotiation.getSubType())); + } + for (RtpDescription.FeedbackNegotiationTrrInt feedbackNegotiationTrrInt : description.feedbackNegotiationTrrInts()) { + mediaAttributes.put("rtcp-fb", "* trr-int " + feedbackNegotiationTrrInt.getValue()); + } + for (RtpDescription.RtpHeaderExtension extension : description.getHeaderExtensions()) { + mediaAttributes.put("extmap", extension.getId() + " " + extension.getUri()); + } + mediaAttributes.put("mid", name); + + //random additional attributes + mediaAttributes.put("sendrecv",""); + mediaAttributes.put("rtcp-mux",""); + + final MediaBuilder mediaBuilder = new MediaBuilder(); + mediaBuilder.setMedia(description.getMedia().toString().toLowerCase(Locale.ROOT)); + mediaBuilder.setConnectionData(HARDCODED_CONNECTION); + mediaBuilder.setPort(HARDCODED_MEDIA_PORT); + mediaBuilder.setProtocol(HARDCODED_MEDIA_PROTOCOL); + mediaBuilder.setAttributes(mediaAttributes); + mediaBuilder.setFormats(formatBuilder.build()); + mediaListBuilder.add(mediaBuilder.createMedia()); + + } + sessionDescriptionBuilder.setVersion(0); + sessionDescriptionBuilder.setName(" "); + sessionDescriptionBuilder.setMedia(mediaListBuilder.build()); + sessionDescriptionBuilder.setAttributes(attributeMap); + + return sessionDescriptionBuilder.createSessionDescription(); + } + public static int ignorantIntParser(final String input) { try { return Integer.parseInt(input); @@ -116,6 +220,20 @@ public class SessionDescription { } } + @Override + public String toString() { + final StringBuilder s = new StringBuilder() + .append("v=").append(version).append(LINE_DIVIDER) + .append("s=").append(name).append(LINE_DIVIDER); + appendAttributes(s, attributes); + for (Media media : this.media) { + s.append("m=").append(media.media).append(' ').append(media.port).append(' ').append(media.protocol).append(' ').append(Joiner.on(' ').join(media.formats)).append(LINE_DIVIDER); + s.append("c=").append(media.connectionData).append(LINE_DIVIDER); + appendAttributes(s, media.attributes); + } + return s.toString(); + } + public static class Media { public final String media; public final int port; diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/RtpDescription.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/RtpDescription.java index 8b9296993..53621a6d8 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/RtpDescription.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/RtpDescription.java @@ -206,7 +206,7 @@ public class RtpDescription extends GenericDescription { } } - //maps to `rtpmap $id $name/$clockrate/$channels` + //maps to `rtpmap:$id $name/$clockrate/$channels` public static class PayloadType extends Element { private PayloadType() { @@ -223,10 +223,21 @@ public class RtpDescription extends GenericDescription { } } + public String toSdpAttribute() { + final int channels = getChannels(); + return getId()+" "+getPayloadTypeName()+"/"+getClockRate()+(channels == 1 ? "" : "/"+channels); + } + + public int getIntId() { + final String id = this.getAttribute("id"); + return id == null ? 0 : SessionDescription.ignorantIntParser(id); + } + public String getId() { return this.getAttribute("id"); } + public String getPayloadTypeName() { return this.getAttribute("name"); } @@ -344,6 +355,19 @@ public class RtpDescription extends GenericDescription { return parameter; } + public static String toSdpString(final String id, List parameters) { + final StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(id).append(' '); + for(int i = 0; i < parameters.size(); ++i) { + Parameter p = parameters.get(i); + stringBuilder.append(p.getParameterName()).append('=').append(p.getParameterValue()); + if (i != parameters.size() - 1) { + stringBuilder.append(';'); + } + } + return stringBuilder.toString(); + } + public static Pair> ofSdpString(final String sdp) { final String[] pair = sdp.split(" "); if (pair.length == 2) {