RtpDescription.java

  1package eu.siacs.conversations.xmpp.jingle.stanzas;
  2
  3import android.util.Log;
  4import android.util.Pair;
  5
  6import com.google.common.base.Preconditions;
  7import com.google.common.collect.ArrayListMultimap;
  8import com.google.common.collect.ImmutableList;
  9
 10import java.util.HashMap;
 11import java.util.List;
 12import java.util.Locale;
 13import java.util.Map;
 14
 15import eu.siacs.conversations.Config;
 16import eu.siacs.conversations.xml.Element;
 17import eu.siacs.conversations.xml.Namespace;
 18import eu.siacs.conversations.xmpp.jingle.SessionDescription;
 19
 20public class RtpDescription extends GenericDescription {
 21
 22
 23    private RtpDescription() {
 24        super("description", Namespace.JINGLE_APPS_RTP);
 25    }
 26
 27    public Media getMedia() {
 28        return Media.of(this.getAttribute("media"));
 29    }
 30
 31    public List<PayloadType> getPayloadTypes() {
 32        final ImmutableList.Builder<PayloadType> builder = new ImmutableList.Builder<>();
 33        for (Element child : getChildren()) {
 34            if ("payload-type".equals(child.getName())) {
 35                builder.add(PayloadType.of(child));
 36            }
 37        }
 38        return builder.build();
 39    }
 40
 41    public List<FeedbackNegotiation> getFeedbackNegotiations() {
 42        return FeedbackNegotiation.fromChildren(this.getChildren());
 43    }
 44
 45    public List<FeedbackNegotiationTrrInt> feedbackNegotiationTrrInts() {
 46        return FeedbackNegotiationTrrInt.fromChildren(this.getChildren());
 47    }
 48
 49    public List<RtpHeaderExtension> getHeaderExtensions() {
 50        final ImmutableList.Builder<RtpHeaderExtension> builder = new ImmutableList.Builder<>();
 51        for (final Element child : getChildren()) {
 52            if ("rtp-hdrext".equals(child.getName()) && Namespace.JINGLE_RTP_HEADER_EXTENSIONS.equals(child.getNamespace())) {
 53                builder.add(RtpHeaderExtension.upgrade(child));
 54            }
 55        }
 56        return builder.build();
 57    }
 58
 59    public static RtpDescription upgrade(final Element element) {
 60        Preconditions.checkArgument("description".equals(element.getName()), "Name of provided element is not description");
 61        Preconditions.checkArgument(Namespace.JINGLE_APPS_RTP.equals(element.getNamespace()), "Element does not match the jingle rtp namespace");
 62        final RtpDescription description = new RtpDescription();
 63        description.setAttributes(element.getAttributes());
 64        description.setChildren(element.getChildren());
 65        return description;
 66    }
 67
 68    public static class FeedbackNegotiation extends Element {
 69        private FeedbackNegotiation() {
 70            super("rtcp-fb", Namespace.JINGLE_RTP_FEEDBACK_NEGOTIATION);
 71        }
 72
 73        public FeedbackNegotiation(String type, String subType) {
 74            super("rtcp-fb", Namespace.JINGLE_RTP_FEEDBACK_NEGOTIATION);
 75            this.setAttribute("type", type);
 76            if (subType != null) {
 77                this.setAttribute("subtype", subType);
 78            }
 79        }
 80
 81        public String getType() {
 82            return this.getAttribute("type");
 83        }
 84
 85        public String getSubType() {
 86            return this.getAttribute("subtype");
 87        }
 88
 89        private static FeedbackNegotiation upgrade(final Element element) {
 90            Preconditions.checkArgument("rtcp-fb".equals(element.getName()));
 91            Preconditions.checkArgument(Namespace.JINGLE_RTP_FEEDBACK_NEGOTIATION.equals(element.getNamespace()));
 92            final FeedbackNegotiation feedback = new FeedbackNegotiation();
 93            feedback.setAttributes(element.getAttributes());
 94            feedback.setChildren(element.getChildren());
 95            return feedback;
 96        }
 97
 98        public static List<FeedbackNegotiation> fromChildren(final List<Element> children) {
 99            ImmutableList.Builder<FeedbackNegotiation> builder = new ImmutableList.Builder<>();
100            for (final Element child : children) {
101                if ("rtcp-fb".equals(child.getName()) && Namespace.JINGLE_RTP_FEEDBACK_NEGOTIATION.equals(child.getNamespace())) {
102                    builder.add(upgrade(child));
103                }
104            }
105            return builder.build();
106        }
107
108    }
109
110    public static class FeedbackNegotiationTrrInt extends Element {
111
112        private FeedbackNegotiationTrrInt(int value) {
113            super("rtcp-fb-trr-int", Namespace.JINGLE_RTP_FEEDBACK_NEGOTIATION);
114            this.setAttribute("value", value);
115        }
116
117
118        private FeedbackNegotiationTrrInt() {
119            super("rtcp-fb-trr-int", Namespace.JINGLE_RTP_FEEDBACK_NEGOTIATION);
120        }
121
122        public int getValue() {
123            final String value = getAttribute("value");
124            if (value == null) {
125                return 0;
126            }
127            return SessionDescription.ignorantIntParser(value);
128
129        }
130
131        private static FeedbackNegotiationTrrInt upgrade(final Element element) {
132            Preconditions.checkArgument("rtcp-fb-trr-int".equals(element.getName()));
133            Preconditions.checkArgument(Namespace.JINGLE_RTP_FEEDBACK_NEGOTIATION.equals(element.getNamespace()));
134            final FeedbackNegotiationTrrInt trr = new FeedbackNegotiationTrrInt();
135            trr.setAttributes(element.getAttributes());
136            trr.setChildren(element.getChildren());
137            return trr;
138        }
139
140        public static List<FeedbackNegotiationTrrInt> fromChildren(final List<Element> children) {
141            ImmutableList.Builder<FeedbackNegotiationTrrInt> builder = new ImmutableList.Builder<>();
142            for (final Element child : children) {
143                if ("rtcp-fb-trr-int".equals(child.getName()) && Namespace.JINGLE_RTP_FEEDBACK_NEGOTIATION.equals(child.getNamespace())) {
144                    builder.add(upgrade(child));
145                }
146            }
147            return builder.build();
148        }
149    }
150
151
152    //XEP-0294: Jingle RTP Header Extensions Negotiation
153    //maps to `extmap:$id $uri`
154    public static class RtpHeaderExtension extends Element {
155
156        private RtpHeaderExtension() {
157            super("rtp-hdrext", Namespace.JINGLE_RTP_HEADER_EXTENSIONS);
158        }
159
160        public RtpHeaderExtension(String id, String uri) {
161            super("rtp-hdrext", Namespace.JINGLE_RTP_HEADER_EXTENSIONS);
162            this.setAttribute("id", id);
163            this.setAttribute("uri", uri);
164        }
165
166        public String getId() {
167            return this.getAttribute("id");
168        }
169
170        public String getUri() {
171            return this.getAttribute("uri");
172        }
173
174        public static RtpHeaderExtension upgrade(final Element element) {
175            Preconditions.checkArgument("rtp-hdrext".equals(element.getName()));
176            Preconditions.checkArgument(Namespace.JINGLE_RTP_HEADER_EXTENSIONS.equals(element.getNamespace()));
177            final RtpHeaderExtension extension = new RtpHeaderExtension();
178            extension.setAttributes(element.getAttributes());
179            extension.setChildren(element.getChildren());
180            return extension;
181        }
182
183        public static RtpHeaderExtension ofSdpString(final String sdp) {
184            final String[] pair = sdp.split(" ", 2);
185            if (pair.length == 2) {
186                final String id = pair[0];
187                final String uri = pair[1];
188                return new RtpHeaderExtension(id, uri);
189            } else {
190                return null;
191            }
192        }
193    }
194
195    //maps to `rtpmap $id $name/$clockrate/$channels`
196    public static class PayloadType extends Element {
197
198        private PayloadType() {
199            super("payload-type", Namespace.JINGLE_APPS_RTP);
200        }
201
202        public PayloadType(String id, String name, int clockRate, int channels) {
203            super("payload-type", Namespace.JINGLE_APPS_RTP);
204            this.setAttribute("id", id);
205            this.setAttribute("name", name);
206            this.setAttribute("clockrate", clockRate);
207            if (channels != 1) {
208                this.setAttribute("channels", channels);
209            }
210        }
211
212        public String getId() {
213            return this.getAttribute("id");
214        }
215
216        public String getPayloadTypeName() {
217            return this.getAttribute("name");
218        }
219
220        public int getClockRate() {
221            final String clockRate = this.getAttribute("clockrate");
222            if (clockRate == null) {
223                return 0;
224            }
225            try {
226                return Integer.parseInt(clockRate);
227            } catch (NumberFormatException e) {
228                return 0;
229            }
230        }
231
232        public int getChannels() {
233            final String channels = this.getAttribute("channels");
234            if (channels == null) {
235                return 1; // The number of channels; if omitted, it MUST be assumed to contain one channel
236            }
237            try {
238                return Integer.parseInt(channels);
239            } catch (NumberFormatException e) {
240                return 1;
241            }
242        }
243
244        public List<Parameter> getParameters() {
245            final ImmutableList.Builder<Parameter> builder = new ImmutableList.Builder<>();
246            for (Element child : getChildren()) {
247                if ("parameter".equals(child.getName())) {
248                    builder.add(Parameter.of(child));
249                }
250            }
251            return builder.build();
252        }
253
254        public List<FeedbackNegotiation> getFeedbackNegotiations() {
255            return FeedbackNegotiation.fromChildren(this.getChildren());
256        }
257
258        public List<FeedbackNegotiationTrrInt> feedbackNegotiationTrrInts() {
259            return FeedbackNegotiationTrrInt.fromChildren(this.getChildren());
260        }
261
262        public static PayloadType of(final Element element) {
263            Preconditions.checkArgument("payload-type".equals(element.getName()), "element name must be called payload-type");
264            PayloadType payloadType = new PayloadType();
265            payloadType.setAttributes(element.getAttributes());
266            payloadType.setChildren(element.getChildren());
267            return payloadType;
268        }
269
270        public static PayloadType ofSdpString(final String sdp) {
271            final String[] pair = sdp.split(" ", 2);
272            if (pair.length == 2) {
273                final String id = pair[0];
274                final String[] parts = pair[1].split("/");
275                if (parts.length >= 2) {
276                    final String name = parts[0];
277                    final int clockRate = SessionDescription.ignorantIntParser(parts[1]);
278                    final int channels;
279                    if (parts.length >= 3) {
280                        channels = SessionDescription.ignorantIntParser(parts[2]);
281                    } else {
282                        channels = 1;
283                    }
284                    return new PayloadType(id, name, clockRate, channels);
285                }
286            }
287            return null;
288        }
289
290        public void addChildren(final List<Element> children) {
291            if (children != null) {
292                this.children.addAll(children);
293            }
294        }
295
296        public void addParameters(List<Parameter> parameters) {
297            if (parameters != null) {
298                this.children.addAll(parameters);
299            }
300        }
301    }
302
303    //map to `fmtp $id key=value;key=value
304    //where id is the id of the parent payload-type
305    public static class Parameter extends Element {
306
307        private Parameter() {
308            super("parameter", Namespace.JINGLE_APPS_RTP);
309        }
310
311        public Parameter(String name, String value) {
312            super("parameter", Namespace.JINGLE_APPS_RTP);
313            this.setAttribute("name", name);
314            this.setAttribute("value", value);
315        }
316
317        public String getParameterName() {
318            return this.getAttribute("name");
319        }
320
321        public String getParameterValue() {
322            return this.getAttribute("value");
323        }
324
325        public static Parameter of(final Element element) {
326            Preconditions.checkArgument("parameter".equals(element.getName()), "element name must be called parameter");
327            Parameter parameter = new Parameter();
328            parameter.setAttributes(element.getAttributes());
329            parameter.setChildren(element.getChildren());
330            return parameter;
331        }
332
333        public static Pair<String, List<Parameter>> ofSdpString(final String sdp) {
334            final String[] pair = sdp.split(" ");
335            if (pair.length == 2) {
336                final String id = pair[0];
337                ImmutableList.Builder<Parameter> builder = new ImmutableList.Builder<>();
338                for (final String parameter : pair[1].split(";")) {
339                    final String[] parts = parameter.split("=", 2);
340                    if (parts.length == 2) {
341                        builder.add(new Parameter(parts[0], parts[1]));
342                    }
343                }
344                return new Pair<>(id, builder.build());
345            } else {
346                return null;
347            }
348        }
349    }
350
351    public enum Media {
352        VIDEO, AUDIO, UNKNOWN;
353
354        @Override
355        public String toString() {
356            return super.toString().toLowerCase(Locale.ROOT);
357        }
358
359        public static Media of(String value) {
360            try {
361                return value == null ? UNKNOWN : Media.valueOf(value.toUpperCase(Locale.ROOT));
362            } catch (IllegalArgumentException e) {
363                return UNKNOWN;
364            }
365        }
366    }
367
368    public static RtpDescription of(final SessionDescription.Media media) {
369        final RtpDescription rtpDescription = new RtpDescription();
370        final Map<String, List<Parameter>> parameterMap = new HashMap<>();
371        ArrayListMultimap<String, Element> feedbackNegotiationMap = ArrayListMultimap.create();
372        for (final String rtcpFb : media.attributes.get("rtcp-fb")) {
373            final String[] parts = rtcpFb.split(" ");
374            if (parts.length >= 2) {
375                final String id = parts[0];
376                final String type = parts[1];
377                final String subType = parts.length >= 3 ? parts[2] : null;
378                if ("trr-int".equals(type)) {
379                    if (subType != null) {
380                        feedbackNegotiationMap.put(id, new FeedbackNegotiationTrrInt(SessionDescription.ignorantIntParser(subType)));
381                    }
382                } else {
383                    feedbackNegotiationMap.put(id, new FeedbackNegotiation(type, subType));
384                }
385            }
386        }
387        for (final String fmtp : media.attributes.get("fmtp")) {
388            final Pair<String, List<Parameter>> pair = Parameter.ofSdpString(fmtp);
389            if (pair != null) {
390                parameterMap.put(pair.first, pair.second);
391            }
392        }
393        rtpDescription.addChildren(feedbackNegotiationMap.get("*"));
394        for (final String rtpmap : media.attributes.get("rtpmap")) {
395            final PayloadType payloadType = PayloadType.ofSdpString(rtpmap);
396            if (payloadType != null) {
397                payloadType.addParameters(parameterMap.get(payloadType.getId()));
398                payloadType.addChildren(feedbackNegotiationMap.get(payloadType.getId()));
399                rtpDescription.addChild(payloadType);
400            }
401        }
402        for (final String extmap : media.attributes.get("extmap")) {
403            final RtpHeaderExtension extension = RtpHeaderExtension.ofSdpString(extmap);
404            if (extension != null) {
405                rtpDescription.addChild(extension);
406            }
407        }
408        return rtpDescription;
409    }
410
411    private void addChildren(List<Element> elements) {
412        if (elements != null) {
413            this.children.addAll(elements);
414        }
415    }
416}