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