rudimentary rtpmap to session converter

This commit is contained in:
Daniel Gultsch 2020-04-05 15:43:28 +02:00
parent 2591a96945
commit b1c0e93b34
3 changed files with 145 additions and 4 deletions

View file

@ -156,6 +156,7 @@ public class JingleRtpConnection extends AbstractJingleConnection {
this.initialRtpContentMap = rtpContentMap; this.initialRtpContentMap = rtpContentMap;
final JinglePacket sessionInitiate = rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId); final JinglePacket sessionInitiate = rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId);
Log.d(Config.LOGTAG, sessionInitiate.toString()); 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); send(sessionInitiate);
} }
@ -235,8 +236,6 @@ public class JingleRtpConnection extends AbstractJingleConnection {
public void onIceCandidate(IceCandidate iceCandidate) { public void onIceCandidate(IceCandidate iceCandidate) {
IceUdpTransportInfo.Candidate candidate = IceUdpTransportInfo.Candidate.fromSdpAttribute(iceCandidate.sdp); IceUdpTransportInfo.Candidate candidate = IceUdpTransportInfo.Candidate.fromSdpAttribute(iceCandidate.sdp);
Log.d(Config.LOGTAG, "onIceCandidate: " + 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); sendTransportInfo(iceCandidate.sdpMid, candidate);
} }

View file

@ -3,16 +3,28 @@ package eu.siacs.conversations.xmpp.jingle;
import android.util.Log; import android.util.Log;
import android.util.Pair; 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.ArrayListMultimap;
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.Map; import java.util.Map;
import eu.siacs.conversations.Config; 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 { 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 int version;
public final String name; public final String name;
public final String connectionData; public final String connectionData;
@ -28,6 +40,18 @@ public class SessionDescription {
this.media = media; this.media = media;
} }
private static void appendAttributes(StringBuilder s, ArrayListMultimap<String, String> attributes) {
for (Map.Entry<String, String> 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<String, RtpContentMap.DescriptionTransport> contents) { public static SessionDescription parse(final Map<String, RtpContentMap.DescriptionTransport> contents) {
final SessionDescriptionBuilder sessionDescriptionBuilder = new SessionDescriptionBuilder(); final SessionDescriptionBuilder sessionDescriptionBuilder = new SessionDescriptionBuilder();
return sessionDescriptionBuilder.createSessionDescription(); return sessionDescriptionBuilder.createSessionDescription();
@ -38,7 +62,7 @@ public class SessionDescription {
MediaBuilder currentMediaBuilder = null; MediaBuilder currentMediaBuilder = null;
ArrayListMultimap<String, String> attributeMap = ArrayListMultimap.create(); 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(LINE_DIVIDER)) {
final String[] pair = line.trim().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);
@ -99,6 +123,86 @@ public class SessionDescription {
return sessionDescriptionBuilder.createSessionDescription(); return sessionDescriptionBuilder.createSessionDescription();
} }
public static SessionDescription of(final RtpContentMap contentMap) {
final SessionDescriptionBuilder sessionDescriptionBuilder = new SessionDescriptionBuilder();
final ArrayListMultimap<String, String> attributeMap = ArrayListMultimap.create();
final ImmutableList.Builder<Media> 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<String, RtpContentMap.DescriptionTransport> entry : contentMap.contents.entrySet()) {
final String name = entry.getKey();
RtpContentMap.DescriptionTransport descriptionTransport = entry.getValue();
RtpDescription description = descriptionTransport.description;
IceUdpTransportInfo transport = descriptionTransport.transport;
final ArrayListMultimap<String, String> 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<Integer> formatBuilder = new ImmutableList.Builder<>();
for (RtpDescription.PayloadType payloadType : description.getPayloadTypes()) {
formatBuilder.add(payloadType.getIntId());
mediaAttributes.put("rtpmap", payloadType.toSdpAttribute());
List<RtpDescription.Parameter> 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) { public static int ignorantIntParser(final String input) {
try { try {
return Integer.parseInt(input); 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 static class Media {
public final String media; public final String media;
public final int port; public final int port;

View file

@ -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 { public static class PayloadType extends Element {
private PayloadType() { 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() { public String getId() {
return this.getAttribute("id"); return this.getAttribute("id");
} }
public String getPayloadTypeName() { public String getPayloadTypeName() {
return this.getAttribute("name"); return this.getAttribute("name");
} }
@ -344,6 +355,19 @@ public class RtpDescription extends GenericDescription {
return parameter; return parameter;
} }
public static String toSdpString(final String id, List<Parameter> 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<String, List<Parameter>> ofSdpString(final String sdp) { public static Pair<String, List<Parameter>> ofSdpString(final String sdp) {
final String[] pair = sdp.split(" "); final String[] pair = sdp.split(" ");
if (pair.length == 2) { if (pair.length == 2) {