/src/janus-gateway/src/sdp-utils.c
Line | Count | Source |
1 | | /*! \file sdp-utils.c |
2 | | * \author Lorenzo Miniero <lorenzo@meetecho.com> |
3 | | * \copyright GNU General Public License v3 |
4 | | * \brief SDP utilities |
5 | | * \details Implementation of an internal SDP representation. Allows |
6 | | * to parse SDP strings to an internal janus_sdp object, the manipulation |
7 | | * of such object by playing with its properties, and a serialization |
8 | | * to an SDP string that can be passed around. Since they don't have any |
9 | | * core dependencies, these utilities can be used by plugins as well. |
10 | | * |
11 | | * \ingroup core |
12 | | * \ref core |
13 | | */ |
14 | | |
15 | | #include <string.h> |
16 | | |
17 | | #include "sdp-utils.h" |
18 | | #include "rtp.h" |
19 | | #include "utils.h" |
20 | | #include "debug.h" |
21 | | |
22 | | /* Preferred codecs when negotiating audio/video, and number of supported codecs */ |
23 | | const char *janus_preferred_audio_codecs[] = { |
24 | | "opus", "multiopus", "pcmu", "pcma", "g722", "l16-48", "l16", "isac16", "isac32" |
25 | | }; |
26 | | uint janus_audio_codecs = sizeof(janus_preferred_audio_codecs)/sizeof(*janus_preferred_audio_codecs); |
27 | | const char *janus_preferred_video_codecs[] = { |
28 | | "vp8", "vp9", "h264", "av1", "h265" |
29 | | }; |
30 | | uint janus_video_codecs = sizeof(janus_preferred_video_codecs)/sizeof(*janus_preferred_video_codecs); |
31 | | |
32 | | /* Reference counters management */ |
33 | 1.48k | void janus_sdp_destroy(janus_sdp *sdp) { |
34 | 1.48k | if(!sdp || !g_atomic_int_compare_and_exchange(&sdp->destroyed, 0, 1)) |
35 | 0 | return; |
36 | 1.48k | janus_refcount_decrease(&sdp->ref); |
37 | 1.48k | } |
38 | | |
39 | 271k | void janus_sdp_mline_destroy(janus_sdp_mline *m) { |
40 | 271k | if(!m || !g_atomic_int_compare_and_exchange(&m->destroyed, 0, 1)) |
41 | 0 | return; |
42 | 271k | janus_refcount_decrease(&m->ref); |
43 | 271k | } |
44 | | |
45 | 407k | void janus_sdp_attribute_destroy(janus_sdp_attribute *a) { |
46 | 407k | if(!a || !g_atomic_int_compare_and_exchange(&a->destroyed, 0, 1)) |
47 | 0 | return; |
48 | 407k | janus_refcount_decrease(&a->ref); |
49 | 407k | } |
50 | | |
51 | | /* Internal frees */ |
52 | 1.48k | static void janus_sdp_free(const janus_refcount *sdp_ref) { |
53 | 1.48k | janus_sdp *sdp = janus_refcount_containerof(sdp_ref, janus_sdp, ref); |
54 | | /* This SDP instance can be destroyed, free all the resources */ |
55 | 1.48k | g_free(sdp->o_name); |
56 | 1.48k | g_free(sdp->o_addr); |
57 | 1.48k | g_free(sdp->s_name); |
58 | 1.48k | g_free(sdp->c_addr); |
59 | 1.48k | GList *temp = sdp->attributes; |
60 | 10.0k | while(temp) { |
61 | 8.60k | janus_sdp_attribute *a = (janus_sdp_attribute *)temp->data; |
62 | 8.60k | janus_sdp_attribute_destroy(a); |
63 | 8.60k | temp = temp->next; |
64 | 8.60k | } |
65 | 1.48k | g_list_free(sdp->attributes); |
66 | 1.48k | sdp->attributes = NULL; |
67 | 1.48k | temp = sdp->m_lines; |
68 | 272k | while(temp) { |
69 | 271k | janus_sdp_mline *m = (janus_sdp_mline *)temp->data; |
70 | 271k | janus_sdp_mline_destroy(m); |
71 | 271k | temp = temp->next; |
72 | 271k | } |
73 | 1.48k | g_list_free(sdp->m_lines); |
74 | 1.48k | sdp->m_lines = NULL; |
75 | 1.48k | g_free(sdp); |
76 | 1.48k | } |
77 | | |
78 | 271k | static void janus_sdp_mline_free(const janus_refcount *mline_ref) { |
79 | 271k | janus_sdp_mline *mline = janus_refcount_containerof(mline_ref, janus_sdp_mline, ref); |
80 | | /* This SDP m-line instance can be destroyed, free all the resources */ |
81 | 271k | g_free(mline->type_str); |
82 | 271k | g_free(mline->proto); |
83 | 271k | g_free(mline->c_addr); |
84 | 271k | g_free(mline->b_name); |
85 | 271k | g_list_free_full(mline->fmts, (GDestroyNotify)g_free); |
86 | 271k | mline->fmts = NULL; |
87 | 271k | g_list_free(mline->ptypes); |
88 | 271k | mline->ptypes = NULL; |
89 | 271k | GList *temp = mline->attributes; |
90 | 663k | while(temp) { |
91 | 391k | janus_sdp_attribute *a = (janus_sdp_attribute *)temp->data; |
92 | 391k | janus_sdp_attribute_destroy(a); |
93 | 391k | temp = temp->next; |
94 | 391k | } |
95 | 271k | g_list_free(mline->attributes); |
96 | 271k | g_free(mline); |
97 | 271k | } |
98 | | |
99 | 407k | static void janus_sdp_attribute_free(const janus_refcount *attr_ref) { |
100 | 407k | janus_sdp_attribute *attr = janus_refcount_containerof(attr_ref, janus_sdp_attribute, ref); |
101 | | /* This SDP attribute instance can be destroyed, free all the resources */ |
102 | 407k | g_free(attr->name); |
103 | 407k | g_free(attr->value); |
104 | 407k | g_free(attr); |
105 | 407k | } |
106 | | |
107 | | |
108 | | /* SDP and m-lines/attributes code */ |
109 | 0 | janus_sdp_mline *janus_sdp_mline_create(janus_sdp_mtype type, guint16 port, const char *proto, janus_sdp_mdirection direction) { |
110 | 0 | janus_sdp_mline *m = g_malloc0(sizeof(janus_sdp_mline)); |
111 | 0 | g_atomic_int_set(&m->destroyed, 0); |
112 | 0 | janus_refcount_init(&m->ref, janus_sdp_mline_free); |
113 | 0 | m->type = type; |
114 | 0 | const char *type_str = janus_sdp_mtype_str(type); |
115 | 0 | if(type_str == NULL) { |
116 | 0 | JANUS_LOG(LOG_WARN, "Unknown media type, type_str will have to be set manually\n"); |
117 | 0 | } else { |
118 | 0 | m->type_str = g_strdup(type_str); |
119 | 0 | } |
120 | 0 | m->port = port; |
121 | 0 | m->proto = proto ? g_strdup(proto) : NULL; |
122 | 0 | m->direction = direction; |
123 | 0 | return m; |
124 | 0 | } |
125 | | |
126 | 0 | janus_sdp_mline *janus_sdp_mline_find(janus_sdp *sdp, janus_sdp_mtype type) { |
127 | 0 | if(sdp == NULL) |
128 | 0 | return NULL; |
129 | 0 | GList *ml = sdp->m_lines; |
130 | 0 | while(ml) { |
131 | 0 | janus_sdp_mline *m = (janus_sdp_mline *)ml->data; |
132 | 0 | if(m->type == type) |
133 | 0 | return m; |
134 | 0 | ml = ml->next; |
135 | 0 | } |
136 | 0 | return NULL; |
137 | 0 | } |
138 | | |
139 | 0 | janus_sdp_mline *janus_sdp_mline_find_by_index(janus_sdp *sdp, int index) { |
140 | 0 | if(sdp == NULL || index < 0) |
141 | 0 | return NULL; |
142 | 0 | GList *ml = sdp->m_lines; |
143 | 0 | while(ml) { |
144 | 0 | janus_sdp_mline *m = (janus_sdp_mline *)ml->data; |
145 | 0 | if(m->index == index) |
146 | 0 | return m; |
147 | 0 | ml = ml->next; |
148 | 0 | } |
149 | 0 | return NULL; |
150 | 0 | } |
151 | | |
152 | 0 | int janus_sdp_mline_remove(janus_sdp *sdp, janus_sdp_mtype type) { |
153 | 0 | if(sdp == NULL) |
154 | 0 | return -1; |
155 | 0 | GList *ml = sdp->m_lines; |
156 | 0 | while(ml) { |
157 | 0 | janus_sdp_mline *m = (janus_sdp_mline *)ml->data; |
158 | 0 | if(m->type == type) { |
159 | | /* Found! */ |
160 | 0 | sdp->m_lines = g_list_remove(sdp->m_lines, m); |
161 | 0 | janus_sdp_mline_destroy(m); |
162 | 0 | return 0; |
163 | 0 | } |
164 | 0 | ml = ml->next; |
165 | 0 | } |
166 | | /* If we got here, we couldn't the m-line */ |
167 | 0 | return -2; |
168 | 0 | } |
169 | | |
170 | 0 | janus_sdp_attribute *janus_sdp_attribute_create(const char *name, const char *value, ...) { |
171 | 0 | if(!name) |
172 | 0 | return NULL; |
173 | 0 | janus_sdp_attribute *a = g_malloc(sizeof(janus_sdp_attribute)); |
174 | 0 | g_atomic_int_set(&a->destroyed, 0); |
175 | 0 | janus_refcount_init(&a->ref, janus_sdp_attribute_free); |
176 | 0 | a->name = g_strdup(name); |
177 | 0 | a->direction = JANUS_SDP_DEFAULT; |
178 | 0 | a->value = NULL; |
179 | 0 | if(value) { |
180 | 0 | char buffer[2048]; |
181 | 0 | va_list ap; |
182 | 0 | va_start(ap, value); |
183 | 0 | g_vsnprintf(buffer, sizeof(buffer), value, ap); |
184 | 0 | va_end(ap); |
185 | 0 | a->value = g_strdup(buffer); |
186 | 0 | } |
187 | 0 | return a; |
188 | 0 | } |
189 | | |
190 | 0 | int janus_sdp_attribute_add_to_mline(janus_sdp_mline *mline, janus_sdp_attribute *attr) { |
191 | 0 | if(!mline || !attr) |
192 | 0 | return -1; |
193 | 0 | mline->attributes = g_list_append(mline->attributes, attr); |
194 | 0 | return 0; |
195 | 0 | } |
196 | | |
197 | 271k | janus_sdp_mtype janus_sdp_parse_mtype(const char *type) { |
198 | 271k | if(type == NULL) |
199 | 0 | return JANUS_SDP_OTHER; |
200 | 271k | if(!strcasecmp(type, "audio")) |
201 | 4.55k | return JANUS_SDP_AUDIO; |
202 | 266k | if(!strcasecmp(type, "video")) |
203 | 264k | return JANUS_SDP_VIDEO; |
204 | 2.56k | if(!strcasecmp(type, "application")) |
205 | 2.37k | return JANUS_SDP_APPLICATION; |
206 | 199 | return JANUS_SDP_OTHER; |
207 | 2.56k | } |
208 | | |
209 | 0 | const char *janus_sdp_mtype_str(janus_sdp_mtype type) { |
210 | 0 | switch(type) { |
211 | 0 | case JANUS_SDP_AUDIO: |
212 | 0 | return "audio"; |
213 | 0 | case JANUS_SDP_VIDEO: |
214 | 0 | return "video"; |
215 | 0 | case JANUS_SDP_APPLICATION: |
216 | 0 | return "application"; |
217 | 0 | case JANUS_SDP_OTHER: |
218 | 0 | default: |
219 | 0 | break; |
220 | 0 | } |
221 | 0 | return NULL; |
222 | 0 | } |
223 | | |
224 | 380k | janus_sdp_mdirection janus_sdp_parse_mdirection(const char *direction) { |
225 | 380k | if(direction == NULL) |
226 | 0 | return JANUS_SDP_INVALID; |
227 | 380k | if(!strcasecmp(direction, "sendrecv")) |
228 | 862 | return JANUS_SDP_SENDRECV; |
229 | 379k | if(!strcasecmp(direction, "sendonly")) |
230 | 1.98k | return JANUS_SDP_SENDONLY; |
231 | 377k | if(!strcasecmp(direction, "recvonly")) |
232 | 2.06k | return JANUS_SDP_RECVONLY; |
233 | 375k | if(!strcasecmp(direction, "inactive")) |
234 | 2.23k | return JANUS_SDP_INACTIVE; |
235 | 373k | return JANUS_SDP_INVALID; |
236 | 375k | } |
237 | | |
238 | 260k | const char *janus_sdp_mdirection_str(janus_sdp_mdirection direction) { |
239 | 260k | switch(direction) { |
240 | 0 | case JANUS_SDP_DEFAULT: |
241 | 258k | case JANUS_SDP_SENDRECV: |
242 | 258k | return "sendrecv"; |
243 | 669 | case JANUS_SDP_SENDONLY: |
244 | 669 | return "sendonly"; |
245 | 430 | case JANUS_SDP_RECVONLY: |
246 | 430 | return "recvonly"; |
247 | 794 | case JANUS_SDP_INACTIVE: |
248 | 794 | return "inactive"; |
249 | 0 | case JANUS_SDP_INVALID: |
250 | 0 | default: |
251 | 0 | break; |
252 | 260k | } |
253 | 0 | return NULL; |
254 | 260k | } |
255 | | |
256 | 0 | const char *janus_sdp_oa_type_str(janus_sdp_oa_type type) { |
257 | 0 | switch(type) { |
258 | 0 | case JANUS_SDP_OA_MLINE: |
259 | 0 | return "JANUS_SDP_OA_MLINE"; |
260 | 0 | case JANUS_SDP_OA_ENABLED: |
261 | 0 | return "JANUS_SDP_OA_ENABLED"; |
262 | 0 | case JANUS_SDP_OA_MID: |
263 | 0 | return "JANUS_SDP_OA_MID"; |
264 | 0 | case JANUS_SDP_OA_MSID: |
265 | 0 | return "JANUS_SDP_OA_MSID"; |
266 | 0 | case JANUS_SDP_OA_DIRECTION: |
267 | 0 | return "JANUS_SDP_OA_DIRECTION"; |
268 | 0 | case JANUS_SDP_OA_CODEC: |
269 | 0 | return "JANUS_SDP_OA_CODEC"; |
270 | 0 | case JANUS_SDP_OA_EXTENSION: |
271 | 0 | return "JANUS_SDP_OA_EXTENSION"; |
272 | 0 | case JANUS_SDP_OA_EXTENSIONS: |
273 | 0 | return "JANUS_SDP_OA_EXTENSIONS"; |
274 | 0 | case JANUS_SDP_OA_ACCEPT_EXTMAP: |
275 | 0 | return "JANUS_SDP_OA_ACCEPT_EXTMAP"; |
276 | 0 | case JANUS_SDP_OA_PT: |
277 | 0 | return "JANUS_SDP_OA_PT"; |
278 | 0 | case JANUS_SDP_OA_FMTP: |
279 | 0 | return "JANUS_SDP_OA_FMTP"; |
280 | 0 | case JANUS_SDP_OA_AUDIO_DTMF: |
281 | 0 | return "JANUS_SDP_OA_AUDIO_DTMF"; |
282 | 0 | case JANUS_SDP_OA_VP9_PROFILE: |
283 | 0 | return "JANUS_SDP_OA_VP9_PROFILE"; |
284 | 0 | case JANUS_SDP_OA_H264_PROFILE: |
285 | 0 | return "JANUS_SDP_OA_H264_PROFILE"; |
286 | 0 | case JANUS_SDP_OA_VIDEO_RTCPFB_DEFAULTS: |
287 | 0 | return "JANUS_SDP_OA_VIDEO_RTCPFB_DEFAULTS"; |
288 | 0 | case JANUS_SDP_OA_DATA_LEGACY: |
289 | 0 | return "JANUS_SDP_OA_DATA_LEGACY"; |
290 | 0 | case JANUS_SDP_OA_DONE: |
291 | 0 | return "JANUS_SDP_OA_DONE"; |
292 | 0 | default: |
293 | 0 | break; |
294 | 0 | } |
295 | 0 | return NULL; |
296 | 0 | } |
297 | | |
298 | 1.51k | janus_sdp *janus_sdp_parse(const char *sdp, char *error, size_t errlen) { |
299 | 1.51k | if(!sdp) |
300 | 0 | return NULL; |
301 | 1.51k | if(strstr(sdp, "v=") != sdp) { |
302 | 30 | if(error) |
303 | 30 | g_snprintf(error, errlen, "Invalid SDP (doesn't start with v=)"); |
304 | 30 | return NULL; |
305 | 30 | } |
306 | 1.48k | janus_sdp *imported = g_malloc0(sizeof(janus_sdp)); |
307 | 1.48k | g_atomic_int_set(&imported->destroyed, 0); |
308 | 1.48k | janus_refcount_init(&imported->ref, janus_sdp_free); |
309 | 1.48k | imported->o_ipv4 = TRUE; |
310 | 1.48k | imported->c_ipv4 = TRUE; |
311 | | |
312 | 1.48k | gboolean success = TRUE; |
313 | 1.48k | janus_sdp_mline *mline = NULL; |
314 | 1.48k | int mlines = 0; |
315 | 1.48k | char *line = NULL, *cr = NULL, *rest = NULL; |
316 | 1.48k | char *sdp_copy = g_strdup(sdp); |
317 | 1.48k | gboolean first = TRUE, mline_ended = FALSE; |
318 | | /* When a m-line has been detected we re-use the previous SDP line */ |
319 | 976k | while(success && (mline_ended || (line = strtok_r(!first ? NULL: sdp_copy, "\n", &rest)) != NULL)) { |
320 | 975k | first = FALSE; |
321 | 975k | mline_ended = FALSE; |
322 | 975k | cr = strchr(line, '\r'); |
323 | 975k | if(cr != NULL) |
324 | 3.76k | *cr = '\0'; |
325 | 975k | if(*line == '\0') { |
326 | 753 | if(cr != NULL) |
327 | 753 | *cr = '\r'; |
328 | 753 | continue; |
329 | 753 | } |
330 | 974k | if(strnlen(line, 3) < 3) { |
331 | 26 | if(error) |
332 | 26 | g_snprintf(error, errlen, "Invalid line (%zu bytes): %s", strlen(line), line); |
333 | 26 | success = FALSE; |
334 | 26 | break; |
335 | 26 | } |
336 | 974k | if(*(line+1) != '=') { |
337 | 28 | if(error) |
338 | 28 | g_snprintf(error, errlen, "Invalid line (2nd char is not '='): %s", line); |
339 | 28 | success = FALSE; |
340 | 28 | break; |
341 | 28 | } |
342 | 974k | char c = *line; |
343 | 974k | if(mline == NULL) { |
344 | | /* Global stuff */ |
345 | 283k | switch(c) { |
346 | 1.83k | case 'v': { |
347 | 1.83k | if(sscanf(line, "v=%d", &imported->version) != 1) { |
348 | 5 | if(error) |
349 | 5 | g_snprintf(error, errlen, "Invalid v= line: %s", line); |
350 | 5 | success = FALSE; |
351 | 5 | break; |
352 | 5 | } |
353 | 1.82k | break; |
354 | 1.83k | } |
355 | 1.82k | case 'o': { |
356 | 484 | if(imported->o_name || imported->o_addr) { |
357 | 1 | if(error) |
358 | 1 | g_snprintf(error, errlen, "Multiple o= lines: %s", line); |
359 | 1 | success = FALSE; |
360 | 1 | break; |
361 | 1 | } |
362 | 483 | char name[256], addrtype[6], addr[256]; |
363 | 483 | if(sscanf(line, "o=%255s %"SCNu64" %"SCNu64" IN %5s %255s", |
364 | 483 | name, &imported->o_sessid, &imported->o_version, addrtype, addr) != 5) { |
365 | 5 | if(error) |
366 | 5 | g_snprintf(error, errlen, "Invalid o= line: %s", line); |
367 | 5 | success = FALSE; |
368 | 5 | break; |
369 | 5 | } |
370 | 478 | if(!strcasecmp(addrtype, "IP4")) |
371 | 328 | imported->o_ipv4 = TRUE; |
372 | 150 | else if(!strcasecmp(addrtype, "IP6")) |
373 | 102 | imported->o_ipv4 = FALSE; |
374 | 48 | else { |
375 | 48 | if(error) |
376 | 48 | g_snprintf(error, errlen, "Invalid o= line (unsupported protocol %s): %s", addrtype, line); |
377 | 48 | success = FALSE; |
378 | 48 | break; |
379 | 48 | } |
380 | 430 | imported->o_name = g_strdup(name); |
381 | 430 | imported->o_addr = g_strdup(addr); |
382 | 430 | break; |
383 | 478 | } |
384 | 436 | case 's': { |
385 | 436 | if(imported->s_name) { |
386 | 1 | if(error) |
387 | 1 | g_snprintf(error, errlen, "Multiple s= lines: %s", line); |
388 | 1 | success = FALSE; |
389 | 1 | break; |
390 | 1 | } |
391 | 435 | imported->s_name = g_strdup(line+2); |
392 | 435 | break; |
393 | 436 | } |
394 | 209 | case 't': { |
395 | 209 | if(sscanf(line, "t=%"SCNu64" %"SCNu64, &imported->t_start, &imported->t_stop) != 2) { |
396 | 4 | if(error) |
397 | 4 | g_snprintf(error, errlen, "Invalid t= line: %s", line); |
398 | 4 | success = FALSE; |
399 | 4 | break; |
400 | 4 | } |
401 | 205 | break; |
402 | 209 | } |
403 | 205 | case 'c': { |
404 | 61 | if(imported->c_addr) { |
405 | 1 | if(error) |
406 | 1 | g_snprintf(error, errlen, "Multiple global c= lines: %s", line); |
407 | 1 | success = FALSE; |
408 | 1 | break; |
409 | 1 | } |
410 | 60 | char addrtype[6], addr[256]; |
411 | 60 | if(sscanf(line, "c=IN %5s %255s", addrtype, addr) != 2) { |
412 | 3 | if(error) |
413 | 3 | g_snprintf(error, errlen, "Invalid c= line: %s", line); |
414 | 3 | success = FALSE; |
415 | 3 | break; |
416 | 3 | } |
417 | 57 | if(!strcasecmp(addrtype, "IP4")) |
418 | 7 | imported->c_ipv4 = TRUE; |
419 | 50 | else if(!strcasecmp(addrtype, "IP6")) |
420 | 3 | imported->c_ipv4 = FALSE; |
421 | 47 | else { |
422 | 47 | if(error) |
423 | 47 | g_snprintf(error, errlen, "Invalid c= line (unsupported protocol %s): %s", addrtype, line); |
424 | 47 | success = FALSE; |
425 | 47 | break; |
426 | 47 | } |
427 | 10 | imported->c_addr = g_strdup(addr); |
428 | 10 | break; |
429 | 57 | } |
430 | 8.60k | case 'a': { |
431 | 8.60k | janus_sdp_attribute *a = g_malloc0(sizeof(janus_sdp_attribute)); |
432 | 8.60k | janus_refcount_init(&a->ref, janus_sdp_attribute_free); |
433 | 8.60k | line += 2; |
434 | 8.60k | char *semicolon = strchr(line, ':'); |
435 | 8.60k | if(semicolon == NULL) { |
436 | 6.49k | a->name = g_strdup(line); |
437 | 6.49k | a->value = NULL; |
438 | 6.49k | } else { |
439 | 2.11k | if(*(semicolon+1) == '\0') { |
440 | 1 | janus_sdp_attribute_destroy(a); |
441 | 1 | if(error) |
442 | 1 | g_snprintf(error, errlen, "Invalid a= line: %s", line); |
443 | 1 | success = FALSE; |
444 | 1 | break; |
445 | 1 | } |
446 | 2.11k | *semicolon = '\0'; |
447 | 2.11k | a->name = g_strdup(line); |
448 | 2.11k | a->value = g_strdup(semicolon+1); |
449 | 2.11k | a->direction = JANUS_SDP_DEFAULT; |
450 | 2.11k | *semicolon = ':'; |
451 | 2.11k | if(strstr(line, "/sendonly")) |
452 | 281 | a->direction = JANUS_SDP_SENDONLY; |
453 | 1.83k | else if(strstr(line, "/recvonly")) |
454 | 225 | a->direction = JANUS_SDP_RECVONLY; |
455 | 2.11k | if(strstr(line, "/inactive")) |
456 | 395 | a->direction = JANUS_SDP_INACTIVE; |
457 | 2.11k | } |
458 | 8.60k | imported->attributes = g_list_prepend(imported->attributes, a); |
459 | 8.60k | break; |
460 | 8.60k | } |
461 | 271k | case 'm': { |
462 | 271k | janus_sdp_mline *m = g_malloc0(sizeof(janus_sdp_mline)); |
463 | 271k | g_atomic_int_set(&m->destroyed, 0); |
464 | 271k | janus_refcount_init(&m->ref, janus_sdp_mline_free); |
465 | | /* Start with media type, port and protocol */ |
466 | 271k | char type[32]; |
467 | 271k | char proto[64]; |
468 | 271k | if(strnlen(line, 200 + 1) > 200) { |
469 | 2 | janus_sdp_mline_destroy(m); |
470 | 2 | if(error) |
471 | 2 | g_snprintf(error, errlen, "Invalid m= line (too long): %zu", strlen(line)); |
472 | 2 | success = FALSE; |
473 | 2 | break; |
474 | 2 | } |
475 | 271k | if(sscanf(line, "m=%31s %"SCNu16" %63s %*s", type, &m->port, proto) != 3) { |
476 | 49 | janus_sdp_mline_destroy(m); |
477 | 49 | if(error) |
478 | 49 | g_snprintf(error, errlen, "Invalid m= line: %s", line); |
479 | 49 | success = FALSE; |
480 | 49 | break; |
481 | 49 | } |
482 | 271k | m->index = mlines; |
483 | 271k | mlines++; |
484 | 271k | m->type = janus_sdp_parse_mtype(type); |
485 | 271k | if(m->type == JANUS_SDP_OTHER) { |
486 | 199 | janus_sdp_mline_destroy(m); |
487 | 199 | if(error) |
488 | 199 | g_snprintf(error, errlen, "Invalid m= line: %s", line); |
489 | 199 | success = FALSE; |
490 | 199 | break; |
491 | 199 | } |
492 | 271k | m->type_str = g_strdup(type); |
493 | 271k | m->proto = g_strdup(proto); |
494 | 271k | m->direction = JANUS_SDP_SENDRECV; |
495 | 271k | m->c_ipv4 = TRUE; |
496 | | /* Now let's check the payload types/formats */ |
497 | 271k | gchar **mline_parts = g_strsplit(line+2, " ", -1); |
498 | 271k | if(!mline_parts && (m->port > 0 || m->type == JANUS_SDP_APPLICATION)) { |
499 | 0 | janus_sdp_mline_destroy(m); |
500 | 0 | if(error) |
501 | 0 | g_snprintf(error, errlen, "Invalid m= line (no payload types/formats): %s", line); |
502 | 0 | success = FALSE; |
503 | 0 | break; |
504 | 271k | } else { |
505 | 271k | int mindex = 0; |
506 | 1.40M | while(mline_parts[mindex]) { |
507 | 1.13M | if(mindex < 3) { |
508 | | /* We've parsed these before */ |
509 | 813k | mindex++; |
510 | 813k | continue; |
511 | 813k | } |
512 | | /* Add string fmt */ |
513 | 316k | m->fmts = g_list_prepend(m->fmts, g_strdup(mline_parts[mindex])); |
514 | | /* Add numeric payload type */ |
515 | 316k | int ptype = atoi(mline_parts[mindex]); |
516 | 316k | if(ptype < 0) { |
517 | 22.0k | JANUS_LOG(LOG_ERR, "Invalid payload type (%s)\n", mline_parts[mindex]); |
518 | 294k | } else { |
519 | 294k | m->ptypes = g_list_prepend(m->ptypes, GINT_TO_POINTER(ptype)); |
520 | 294k | } |
521 | 316k | mindex++; |
522 | 316k | } |
523 | 271k | g_strfreev(mline_parts); |
524 | 271k | if(m->fmts == NULL || m->ptypes == NULL) { |
525 | 44 | janus_sdp_mline_destroy(m); |
526 | 44 | if(error) |
527 | 44 | g_snprintf(error, errlen, "Invalid m= line (no payload types/formats): %s", line); |
528 | 44 | success = FALSE; |
529 | 44 | break; |
530 | 44 | } |
531 | 271k | m->fmts = g_list_reverse(m->fmts); |
532 | 271k | m->ptypes = g_list_reverse(m->ptypes); |
533 | 271k | } |
534 | | /* Append to the list of m-lines */ |
535 | 271k | imported->m_lines = g_list_prepend(imported->m_lines, m); |
536 | | /* From now on, we parse this m-line */ |
537 | 271k | mline = m; |
538 | 271k | break; |
539 | 271k | } |
540 | 642 | default: |
541 | 642 | JANUS_LOG(LOG_WARN, "Ignoring '%c' property\n", c); |
542 | 642 | break; |
543 | 283k | } |
544 | 690k | } else { |
545 | | /* m-line stuff */ |
546 | 690k | switch(c) { |
547 | 3.61k | case 'c': { |
548 | 3.61k | if(mline->c_addr) { |
549 | 6 | if(error) |
550 | 6 | g_snprintf(error, errlen, "Multiple m-line c= lines: %s", line); |
551 | 6 | success = FALSE; |
552 | 6 | break; |
553 | 6 | } |
554 | 3.60k | char addrtype[6], addr[256]; |
555 | 3.60k | if(sscanf(line, "c=IN %5s %255s", addrtype, addr) != 2) { |
556 | 6 | if(error) |
557 | 6 | g_snprintf(error, errlen, "Invalid c= line: %s", line); |
558 | 6 | success = FALSE; |
559 | 6 | break; |
560 | 6 | } |
561 | 3.59k | if(!strcasecmp(addrtype, "IP4")) |
562 | 2.62k | mline->c_ipv4 = TRUE; |
563 | 969 | else if(!strcasecmp(addrtype, "IP6")) |
564 | 923 | mline->c_ipv4 = FALSE; |
565 | 46 | else { |
566 | 46 | if(error) |
567 | 46 | g_snprintf(error, errlen, "Invalid c= line (unsupported protocol %s): %s", addrtype, line); |
568 | 46 | success = FALSE; |
569 | 46 | break; |
570 | 46 | } |
571 | 3.55k | mline->c_addr = g_strdup(addr); |
572 | 3.55k | break; |
573 | 3.59k | } |
574 | 6.94k | case 'b': { |
575 | 6.94k | if(mline->b_name) { |
576 | 2.18k | JANUS_LOG(LOG_WARN, "Ignoring extra m-line b= line: %s\n", line); |
577 | 2.18k | if(cr != NULL) |
578 | 813 | *cr = '\r'; |
579 | 2.18k | continue; |
580 | 2.18k | } |
581 | 4.75k | line += 2; |
582 | 4.75k | char *semicolon = strchr(line, ':'); |
583 | 4.75k | if(semicolon == NULL || (*(semicolon+1) == '\0')) { |
584 | 7 | if(error) |
585 | 7 | g_snprintf(error, errlen, "Invalid b= line: %s", line); |
586 | 7 | success = FALSE; |
587 | 7 | break; |
588 | 7 | } |
589 | 4.74k | *semicolon = '\0'; |
590 | 4.74k | if(strcmp(line, "AS") && strcmp(line, "TIAS")) { |
591 | | /* We only support b=AS and b=TIAS, skip */ |
592 | 1.65k | break; |
593 | 1.65k | } |
594 | 3.09k | mline->b_name = g_strdup(line); |
595 | 3.09k | mline->b_value = atol(semicolon+1); |
596 | 3.09k | *semicolon = ':'; |
597 | 3.09k | break; |
598 | 4.74k | } |
599 | 398k | case 'a': { |
600 | 398k | janus_sdp_attribute *a = g_malloc0(sizeof(janus_sdp_attribute)); |
601 | 398k | janus_refcount_init(&a->ref, janus_sdp_attribute_free); |
602 | 398k | line += 2; |
603 | 398k | char *semicolon = strchr(line, ':'); |
604 | 398k | if(semicolon == NULL) { |
605 | | /* Is this a media direction attribute? */ |
606 | 380k | janus_sdp_mdirection direction = janus_sdp_parse_mdirection(line); |
607 | 380k | if(direction != JANUS_SDP_INVALID) { |
608 | 7.14k | janus_sdp_attribute_destroy(a); |
609 | 7.14k | mline->direction = direction; |
610 | 7.14k | break; |
611 | 7.14k | } |
612 | 373k | a->name = g_strdup(line); |
613 | 373k | a->value = NULL; |
614 | 373k | } else { |
615 | 18.5k | if(*(semicolon+1) == '\0') { |
616 | 2 | janus_sdp_attribute_destroy(a); |
617 | 2 | if(error) |
618 | 2 | g_snprintf(error, errlen, "Invalid a= line: %s", line); |
619 | 2 | success = FALSE; |
620 | 2 | break; |
621 | 2 | } |
622 | 18.5k | *semicolon = '\0'; |
623 | 18.5k | a->name = g_strdup(line); |
624 | 18.5k | a->value = g_strdup(semicolon+1); |
625 | 18.5k | a->direction = JANUS_SDP_DEFAULT; |
626 | 18.5k | *semicolon = ':'; |
627 | 18.5k | if(strstr(line, "/sendonly")) |
628 | 1.54k | a->direction = JANUS_SDP_SENDONLY; |
629 | 16.9k | else if(strstr(line, "/recvonly")) |
630 | 1.21k | a->direction = JANUS_SDP_RECVONLY; |
631 | 18.5k | if(strstr(line, "/inactive")) |
632 | 2.37k | a->direction = JANUS_SDP_INACTIVE; |
633 | 18.5k | } |
634 | 391k | mline->attributes = g_list_prepend(mline->attributes, a); |
635 | 391k | break; |
636 | 398k | } |
637 | 270k | case 'm': { |
638 | | /* Current m-line ended, back to global parsing */ |
639 | 270k | if(mline && mline->attributes) |
640 | 6.51k | mline->attributes = g_list_reverse(mline->attributes); |
641 | 270k | mline = NULL; |
642 | 270k | mline_ended = TRUE; |
643 | 270k | continue; |
644 | 398k | } |
645 | 10.7k | default: |
646 | 10.7k | JANUS_LOG(LOG_WARN, "Ignoring '%c' property (m-line)\n", c); |
647 | 10.7k | break; |
648 | 690k | } |
649 | 690k | } |
650 | 701k | if(cr != NULL) |
651 | 2.18k | *cr = '\r'; |
652 | 701k | } |
653 | 1.48k | if(cr != NULL) |
654 | 42 | *cr = '\r'; |
655 | 1.48k | g_free(sdp_copy); |
656 | | /* FIXME Do a last check: is all the stuff that's supposed to be there available? */ |
657 | 1.48k | if(success && (imported->o_name == NULL || imported->o_addr == NULL || imported->s_name == NULL || imported->m_lines == NULL)) { |
658 | 599 | success = FALSE; |
659 | 599 | if(error) |
660 | 599 | g_snprintf(error, errlen, "Missing mandatory lines (o=, s= or m=)"); |
661 | 599 | } |
662 | | /* If something wrong happened, free and return a failure */ |
663 | 1.48k | if(!success) { |
664 | 1.13k | if(error) |
665 | 1.13k | JANUS_LOG(LOG_ERR, "%s\n", error); |
666 | 1.13k | janus_sdp_destroy(imported); |
667 | 1.13k | imported = NULL; |
668 | 1.13k | } else { |
669 | | /* Reverse lists for efficiency */ |
670 | 359 | if(mline && mline->attributes) |
671 | 150 | mline->attributes = g_list_reverse(mline->attributes); |
672 | 359 | if(imported->attributes) |
673 | 41 | imported->attributes = g_list_reverse(imported->attributes); |
674 | 359 | if(imported->m_lines) |
675 | 359 | imported->m_lines = g_list_reverse(imported->m_lines); |
676 | 359 | } |
677 | 1.48k | return imported; |
678 | 1.48k | } |
679 | | |
680 | 0 | int janus_sdp_remove_payload_type(janus_sdp *sdp, int index, int pt) { |
681 | 0 | if(!sdp || pt < 0) |
682 | 0 | return -1; |
683 | 0 | GList *ml = sdp->m_lines; |
684 | 0 | while(ml) { |
685 | 0 | janus_sdp_mline *m = (janus_sdp_mline *)ml->data; |
686 | 0 | if(index != -1 && index != m->index) { |
687 | 0 | ml = ml->next; |
688 | 0 | continue; |
689 | 0 | } |
690 | | /* Remove any reference from the m-line */ |
691 | 0 | m->ptypes = g_list_remove(m->ptypes, GINT_TO_POINTER(pt)); |
692 | | /* Also remove all attributes that reference the same payload type */ |
693 | 0 | GList *ma = m->attributes; |
694 | 0 | while(ma) { |
695 | 0 | janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data; |
696 | 0 | if(a->value && atoi(a->value) == pt) { |
697 | 0 | m->attributes = g_list_remove(m->attributes, a); |
698 | 0 | ma = m->attributes; |
699 | 0 | janus_sdp_attribute_destroy(a); |
700 | 0 | continue; |
701 | 0 | } |
702 | 0 | ma = ma->next; |
703 | 0 | } |
704 | 0 | if(index != -1) |
705 | 0 | break; |
706 | 0 | ml = ml->next; |
707 | 0 | } |
708 | 0 | return 0; |
709 | 0 | } |
710 | | |
711 | 0 | int janus_sdp_get_codec_pt(janus_sdp *sdp, int index, const char *codec) { |
712 | 0 | return janus_sdp_get_codec_pt_full(sdp, index, codec, NULL); |
713 | 0 | } |
714 | | |
715 | 0 | int janus_sdp_get_codec_pt_full(janus_sdp *sdp, int index, const char *codec, const char *profile) { |
716 | 0 | if(sdp == NULL || codec == NULL) |
717 | 0 | return -1; |
718 | | /* Check the format string (note that we only parse what browsers can negotiate) */ |
719 | 0 | gboolean video = FALSE, vp9 = FALSE, h264 = FALSE; |
720 | 0 | const char *format = NULL, *format2 = NULL; |
721 | 0 | if(!strcasecmp(codec, "opus")) { |
722 | 0 | format = "opus/48000/2"; |
723 | 0 | format2 = "OPUS/48000/2"; |
724 | 0 | } else if(!strcasecmp(codec, "multiopus")) { |
725 | | /* FIXME We're hardcoding to 6 channels, for now */ |
726 | 0 | format = "multiopus/48000/6"; |
727 | 0 | format2 = "MULTIOPUS/48000/6"; |
728 | 0 | } else if(!strcasecmp(codec, "pcmu")) { |
729 | | /* We know the payload type is 0: we just need to make sure it's there */ |
730 | 0 | format = "pcmu/8000"; |
731 | 0 | format2 = "PCMU/8000"; |
732 | 0 | } else if(!strcasecmp(codec, "pcma")) { |
733 | | /* We know the payload type is 8: we just need to make sure it's there */ |
734 | 0 | format = "pcma/8000"; |
735 | 0 | format2 = "PCMA/8000"; |
736 | 0 | } else if(!strcasecmp(codec, "g722")) { |
737 | | /* We know the payload type is 9: we just need to make sure it's there */ |
738 | 0 | format = "g722/8000"; |
739 | 0 | format2 = "G722/8000"; |
740 | 0 | } else if(!strcasecmp(codec, "isac16")) { |
741 | 0 | format = "isac/16000"; |
742 | 0 | format2 = "ISAC/16000"; |
743 | 0 | } else if(!strcasecmp(codec, "isac32")) { |
744 | 0 | format = "isac/32000"; |
745 | 0 | format2 = "ISAC/32000"; |
746 | 0 | } else if(!strcasecmp(codec, "l16-48")) { |
747 | 0 | format = "l16/48000"; |
748 | 0 | format2 = "L16/48000"; |
749 | 0 | } else if(!strcasecmp(codec, "l16")) { |
750 | 0 | format = "l16/16000"; |
751 | 0 | format2 = "L16/16000"; |
752 | 0 | } else if(!strcasecmp(codec, "dtmf")) { |
753 | 0 | format = "telephone-event/8000"; |
754 | 0 | format2 = "TELEPHONE-EVENT/8000"; |
755 | 0 | } else if(!strcasecmp(codec, "vp8")) { |
756 | 0 | video = TRUE; |
757 | 0 | format = "vp8/90000"; |
758 | 0 | format2 = "VP8/90000"; |
759 | 0 | } else if(!strcasecmp(codec, "vp9")) { |
760 | 0 | video = TRUE; |
761 | 0 | vp9 = TRUE; /* We may need to filter on profiles */ |
762 | 0 | format = "vp9/90000"; |
763 | 0 | format2 = "VP9/90000"; |
764 | 0 | } else if(!strcasecmp(codec, "h264")) { |
765 | 0 | video = TRUE; |
766 | 0 | h264 = TRUE; /* We may need to filter on profiles */ |
767 | 0 | format = "h264/90000"; |
768 | 0 | format2 = "H264/90000"; |
769 | 0 | } else if(!strcasecmp(codec, "av1")) { |
770 | 0 | video = TRUE; |
771 | 0 | format = "av1/90000"; |
772 | 0 | format2 = "AV1/90000"; |
773 | 0 | } else if(!strcasecmp(codec, "h265")) { |
774 | 0 | video = TRUE; |
775 | 0 | format = "h265/90000"; |
776 | 0 | format2 = "H265/90000"; |
777 | 0 | } else { |
778 | 0 | JANUS_LOG(LOG_ERR, "Unsupported codec '%s'\n", codec); |
779 | 0 | return -1; |
780 | 0 | } |
781 | | /* Check all m->lines */ |
782 | 0 | GList *ml = sdp->m_lines; |
783 | 0 | while(ml) { |
784 | 0 | janus_sdp_mline *m = (janus_sdp_mline *)ml->data; |
785 | 0 | if((!video && m->type != JANUS_SDP_AUDIO) || (video && m->type != JANUS_SDP_VIDEO)) { |
786 | 0 | ml = ml->next; |
787 | 0 | continue; |
788 | 0 | } |
789 | 0 | if(index != -1 && index != m->index) { |
790 | 0 | ml = ml->next; |
791 | 0 | continue; |
792 | 0 | } |
793 | | /* Look in all rtpmap attributes first */ |
794 | 0 | GList *ma = m->attributes; |
795 | 0 | int pt = -1; |
796 | 0 | GList *pts = NULL; |
797 | 0 | while(ma) { |
798 | 0 | janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data; |
799 | 0 | if(a->name != NULL && a->value != NULL && !strcasecmp(a->name, "rtpmap")) { |
800 | 0 | pt = atoi(a->value); |
801 | 0 | if(pt < 0) { |
802 | 0 | JANUS_LOG(LOG_ERR, "Invalid payload type (%s)\n", a->value); |
803 | 0 | } else if(strstr(a->value, format) || strstr(a->value, format2)) { |
804 | 0 | if(profile != NULL && (vp9 || h264)) { |
805 | | /* Let's keep track of this payload type */ |
806 | 0 | pts = g_list_append(pts, GINT_TO_POINTER(pt)); |
807 | 0 | } else { |
808 | | /* Payload type for codec found */ |
809 | 0 | g_list_free(pts); |
810 | 0 | return pt; |
811 | 0 | } |
812 | 0 | } |
813 | 0 | } |
814 | 0 | ma = ma->next; |
815 | 0 | } |
816 | 0 | if(profile != NULL) { |
817 | | /* Now look for the profile in the fmtp attributes */ |
818 | 0 | ma = m->attributes; |
819 | 0 | while(ma) { |
820 | 0 | janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data; |
821 | 0 | if(profile != NULL && a->name != NULL && a->value != NULL && !strcasecmp(a->name, "fmtp")) { |
822 | | /* Does this match the payload types we're looking for? */ |
823 | 0 | pt = atoi(a->value); |
824 | 0 | if(g_list_find(pts, GINT_TO_POINTER(pt)) == NULL) { |
825 | | /* Not what we're looking for */ |
826 | 0 | ma = ma->next; |
827 | 0 | continue; |
828 | 0 | } |
829 | 0 | if(vp9) { |
830 | 0 | char profile_id[20]; |
831 | 0 | g_snprintf(profile_id, sizeof(profile_id), "profile-id=%s", profile); |
832 | 0 | if(strstr(a->value, profile_id) != NULL) { |
833 | | /* Found */ |
834 | 0 | JANUS_LOG(LOG_VERB, "VP9 profile %s found --> %d\n", profile, pt); |
835 | 0 | g_list_free(pts); |
836 | 0 | return pt; |
837 | 0 | } |
838 | 0 | } else if(h264 && strstr(a->value, "packetization-mode=0") == NULL) { |
839 | | /* We only support packetization-mode=1, no matter the profile */ |
840 | 0 | char profile_level_id[30]; |
841 | 0 | char *profile_lower = g_ascii_strdown(profile, -1); |
842 | 0 | g_snprintf(profile_level_id, sizeof(profile_level_id), "profile-level-id=%s", profile_lower); |
843 | 0 | g_free(profile_lower); |
844 | 0 | if(strstr(a->value, profile_level_id) != NULL) { |
845 | | /* Found */ |
846 | 0 | JANUS_LOG(LOG_VERB, "H.264 profile %s found --> %d\n", profile, pt); |
847 | 0 | g_list_free(pts); |
848 | 0 | return pt; |
849 | 0 | } |
850 | | /* Not found, try converting the profile to upper case */ |
851 | 0 | char *profile_upper = g_ascii_strup(profile, -1); |
852 | 0 | g_snprintf(profile_level_id, sizeof(profile_level_id), "profile-level-id=%s", profile_upper); |
853 | 0 | g_free(profile_upper); |
854 | 0 | if(strstr(a->value, profile_level_id) != NULL) { |
855 | | /* Found */ |
856 | 0 | JANUS_LOG(LOG_VERB, "H.264 profile %s found --> %d\n", profile, pt); |
857 | 0 | g_list_free(pts); |
858 | 0 | return pt; |
859 | 0 | } |
860 | 0 | } |
861 | 0 | } |
862 | 0 | ma = ma->next; |
863 | 0 | } |
864 | 0 | } |
865 | 0 | g_list_free(pts); |
866 | 0 | if(index != -1) |
867 | 0 | break; |
868 | 0 | ml = ml->next; |
869 | 0 | } |
870 | 0 | return -1; |
871 | 0 | } |
872 | | |
873 | 0 | const char *janus_sdp_get_codec_name(janus_sdp *sdp, int index, int pt) { |
874 | 0 | if(sdp == NULL || pt < 0) |
875 | 0 | return NULL; |
876 | 0 | if(pt == 0) |
877 | 0 | return "pcmu"; |
878 | 0 | if(pt == 8) |
879 | 0 | return "pcma"; |
880 | 0 | if(pt == 9) |
881 | 0 | return "g722"; |
882 | 0 | GList *ml = sdp->m_lines; |
883 | 0 | while(ml) { |
884 | 0 | janus_sdp_mline *m = (janus_sdp_mline *)ml->data; |
885 | 0 | if(index != -1 && index != m->index) { |
886 | 0 | ml = ml->next; |
887 | 0 | continue; |
888 | 0 | } |
889 | | /* Look in all rtpmap attributes */ |
890 | 0 | GList *ma = m->attributes; |
891 | 0 | while(ma) { |
892 | 0 | janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data; |
893 | 0 | if(a->name != NULL && a->value != NULL && !strcasecmp(a->name, "rtpmap")) { |
894 | 0 | int a_pt = atoi(a->value); |
895 | 0 | if(a_pt == pt) { |
896 | | /* Found! */ |
897 | 0 | if(strstr(a->value, "vp8") || strstr(a->value, "VP8")) |
898 | 0 | return "vp8"; |
899 | 0 | if(strstr(a->value, "vp9") || strstr(a->value, "VP9")) |
900 | 0 | return "vp9"; |
901 | 0 | if(strstr(a->value, "h264") || strstr(a->value, "H264")) |
902 | 0 | return "h264"; |
903 | 0 | if(strstr(a->value, "av1") || strstr(a->value, "AV1")) |
904 | 0 | return "av1"; |
905 | 0 | if(strstr(a->value, "h265") || strstr(a->value, "H265")) |
906 | 0 | return "h265"; |
907 | 0 | if(strstr(a->value, "multiopus") || strstr(a->value, "MULTIOPUS")) |
908 | 0 | return "multiopus"; |
909 | 0 | if(strstr(a->value, "opus") || strstr(a->value, "OPUS")) |
910 | 0 | return "opus"; |
911 | 0 | if(strstr(a->value, "pcmu") || strstr(a->value, "PCMU")) |
912 | 0 | return "pcmu"; |
913 | 0 | if(strstr(a->value, "pcma") || strstr(a->value, "PCMA")) |
914 | 0 | return "pcma"; |
915 | 0 | if(strstr(a->value, "g722") || strstr(a->value, "G722")) |
916 | 0 | return "g722"; |
917 | 0 | if(strstr(a->value, "isac/16") || strstr(a->value, "ISAC/16")) |
918 | 0 | return "isac16"; |
919 | 0 | if(strstr(a->value, "isac/32") || strstr(a->value, "ISAC/32")) |
920 | 0 | return "isac32"; |
921 | 0 | if(strstr(a->value, "l16/48") || strstr(a->value, "L16/48")) |
922 | 0 | return "l16-48"; |
923 | 0 | if(strstr(a->value, "l16/16") || strstr(a->value, "L16/16")) |
924 | 0 | return "l16"; |
925 | 0 | if(strstr(a->value, "telephone-event/8000") || strstr(a->value, "telephone-event/8000")) |
926 | 0 | return "dtmf"; |
927 | | /* RED is not really a codec, but we need to detect it anyway */ |
928 | 0 | if(strstr(a->value, "red") || strstr(a->value, "RED")) |
929 | 0 | return "red"; |
930 | 0 | JANUS_LOG(LOG_ERR, "Unsupported codec '%s'\n", a->value); |
931 | 0 | return NULL; |
932 | 0 | } |
933 | 0 | } |
934 | 0 | ma = ma->next; |
935 | 0 | } |
936 | 0 | if(index != -1) |
937 | 0 | break; |
938 | 0 | ml = ml->next; |
939 | 0 | } |
940 | 0 | return NULL; |
941 | 0 | } |
942 | | |
943 | 0 | const char *janus_sdp_get_rtpmap_codec(const char *rtpmap) { |
944 | 0 | if(rtpmap == NULL) |
945 | 0 | return NULL; |
946 | 0 | const char *codec = NULL; |
947 | 0 | char *rtpmap_val = g_ascii_strdown(rtpmap, -1); |
948 | 0 | if(strstr(rtpmap_val, "opus/") == rtpmap_val) |
949 | 0 | codec = "opus"; |
950 | 0 | else if(strstr(rtpmap_val, "multiopus/") == rtpmap_val) |
951 | 0 | codec = "multiopus"; |
952 | 0 | else if(strstr(rtpmap_val, "pcmu/") == rtpmap_val) |
953 | 0 | codec = "pcmu"; |
954 | 0 | else if(strstr(rtpmap_val, "pcma/") == rtpmap_val) |
955 | 0 | codec = "pcma"; |
956 | 0 | else if(strstr(rtpmap_val, "g722/") == rtpmap_val) |
957 | 0 | codec = "g722"; |
958 | 0 | else if(strstr(rtpmap_val, "isac/16") == rtpmap_val) |
959 | 0 | codec = "isac16"; |
960 | 0 | else if(strstr(rtpmap_val, "isac/32") == rtpmap_val) |
961 | 0 | codec = "isac32"; |
962 | 0 | else if(strstr(rtpmap_val, "l16/48") == rtpmap_val) |
963 | 0 | codec = "l16-48"; |
964 | 0 | else if(strstr(rtpmap_val, "l16/16") == rtpmap_val) |
965 | 0 | codec = "l16"; |
966 | 0 | else if(strstr(rtpmap_val, "telephone-event/") == rtpmap_val) |
967 | 0 | codec = "dtmf"; |
968 | 0 | else if(strstr(rtpmap_val, "vp8/") == rtpmap_val) |
969 | 0 | codec = "vp8"; |
970 | 0 | else if(strstr(rtpmap_val, "vp9/") == rtpmap_val) |
971 | 0 | codec = "vp9"; |
972 | 0 | else if(strstr(rtpmap_val, "h264/") == rtpmap_val) |
973 | 0 | codec = "h264"; |
974 | 0 | else if(strstr(rtpmap_val, "av1/") == rtpmap_val) |
975 | 0 | codec = "av1"; |
976 | 0 | else if(strstr(rtpmap_val, "h265/") == rtpmap_val) |
977 | 0 | codec = "h265"; |
978 | 0 | if(codec == NULL) |
979 | 0 | JANUS_LOG(LOG_ERR, "Unsupported rtpmap '%s'\n", rtpmap); |
980 | 0 | g_free(rtpmap_val); |
981 | 0 | return codec; |
982 | 0 | } |
983 | | |
984 | 0 | const char *janus_sdp_get_codec_rtpmap(const char *codec) { |
985 | 0 | if(codec == NULL) |
986 | 0 | return NULL; |
987 | 0 | if(!strcasecmp(codec, "opus")) |
988 | 0 | return "opus/48000/2"; |
989 | 0 | if(!strcasecmp(codec, "multiopus")) |
990 | | /* FIXME We're hardcoding to 6 channels, for now */ |
991 | 0 | return "multiopus/48000/6"; |
992 | 0 | if(!strcasecmp(codec, "pcmu")) |
993 | 0 | return "PCMU/8000"; |
994 | 0 | if(!strcasecmp(codec, "pcma")) |
995 | 0 | return "PCMA/8000"; |
996 | 0 | if(!strcasecmp(codec, "g722")) |
997 | 0 | return "G722/8000"; |
998 | 0 | if(!strcasecmp(codec, "isac16")) |
999 | 0 | return "ISAC/16000"; |
1000 | 0 | if(!strcasecmp(codec, "isac32")) |
1001 | 0 | return "ISAC/32000"; |
1002 | 0 | if(!strcasecmp(codec, "l16-48")) |
1003 | 0 | return "L16/48000"; |
1004 | 0 | if(!strcasecmp(codec, "l16")) |
1005 | 0 | return "L16/16000"; |
1006 | 0 | if(!strcasecmp(codec, "dtmf")) |
1007 | 0 | return "telephone-event/8000"; |
1008 | 0 | if(!strcasecmp(codec, "vp8")) |
1009 | 0 | return "VP8/90000"; |
1010 | 0 | if(!strcasecmp(codec, "vp9")) |
1011 | 0 | return "VP9/90000"; |
1012 | 0 | if(!strcasecmp(codec, "h264")) |
1013 | 0 | return "H264/90000"; |
1014 | 0 | if(!strcasecmp(codec, "av1")) |
1015 | 0 | return "AV1/90000"; |
1016 | 0 | if(!strcasecmp(codec, "h265")) |
1017 | 0 | return "H265/90000"; |
1018 | 0 | JANUS_LOG(LOG_ERR, "Unsupported codec '%s'\n", codec); |
1019 | 0 | return NULL; |
1020 | 0 | } |
1021 | | |
1022 | 0 | const char *janus_sdp_get_fmtp(janus_sdp *sdp, int index, int pt) { |
1023 | 0 | if(sdp == NULL || pt < 0) |
1024 | 0 | return NULL; |
1025 | 0 | GList *ml = sdp->m_lines; |
1026 | 0 | while(ml) { |
1027 | 0 | janus_sdp_mline *m = (janus_sdp_mline *)ml->data; |
1028 | 0 | if(index != -1 && index != m->index) { |
1029 | 0 | ml = ml->next; |
1030 | 0 | continue; |
1031 | 0 | } |
1032 | | /* Look in all fmtp attributes */ |
1033 | 0 | GList *ma = m->attributes; |
1034 | 0 | while(ma) { |
1035 | 0 | janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data; |
1036 | 0 | if(a->name != NULL && a->value != NULL && !strcasecmp(a->name, "fmtp")) { |
1037 | 0 | int a_pt = atoi(a->value); |
1038 | 0 | if(a_pt == pt) { |
1039 | | /* Found! */ |
1040 | 0 | char needle[10]; |
1041 | 0 | g_snprintf(needle, sizeof(needle), "%d ", pt); |
1042 | 0 | if(strstr(a->value, needle) == a->value) |
1043 | 0 | return a->value + strlen(needle); |
1044 | 0 | } |
1045 | 0 | } |
1046 | 0 | ma = ma->next; |
1047 | 0 | } |
1048 | 0 | if(index != -1) |
1049 | 0 | break; |
1050 | 0 | ml = ml->next; |
1051 | 0 | } |
1052 | 0 | return NULL; |
1053 | 0 | } |
1054 | | |
1055 | 0 | char *janus_sdp_get_video_profile(janus_videocodec codec, const char *fmtp) { |
1056 | 0 | if(fmtp == NULL) |
1057 | 0 | return NULL; |
1058 | 0 | const char *needle = NULL; |
1059 | 0 | if(codec == JANUS_VIDEOCODEC_H264) { |
1060 | 0 | needle = "profile-level-id="; |
1061 | 0 | } else if(codec == JANUS_VIDEOCODEC_VP9) { |
1062 | 0 | needle = "profile-id="; |
1063 | 0 | } else { |
1064 | 0 | return NULL; |
1065 | 0 | } |
1066 | 0 | gchar **list = g_strsplit(fmtp, ";", -1); |
1067 | 0 | int i=0; |
1068 | 0 | gchar *index = list[0]; |
1069 | 0 | char *profile = NULL, *temp = NULL; |
1070 | 0 | while(index != NULL) { |
1071 | 0 | if((temp = strstr(index, needle)) != NULL) { |
1072 | 0 | profile = temp + strlen(needle); |
1073 | 0 | if(strlen(profile) > 0) |
1074 | 0 | profile = g_strdup(profile); |
1075 | 0 | else |
1076 | 0 | profile = NULL; |
1077 | 0 | break; |
1078 | 0 | } |
1079 | 0 | i++; |
1080 | 0 | index = list[i]; |
1081 | 0 | } |
1082 | 0 | g_clear_pointer(&list, g_strfreev); |
1083 | 0 | return profile; |
1084 | 0 | } |
1085 | | |
1086 | 0 | int janus_sdp_get_opusred_pt(janus_sdp *sdp, int index) { |
1087 | 0 | if(sdp == NULL) |
1088 | 0 | return -1; |
1089 | | /* Check all m->lines */ |
1090 | 0 | GList *ml = sdp->m_lines; |
1091 | 0 | while(ml) { |
1092 | 0 | janus_sdp_mline *m = (janus_sdp_mline *)ml->data; |
1093 | 0 | if(m->type != JANUS_SDP_AUDIO) { |
1094 | 0 | ml = ml->next; |
1095 | 0 | continue; |
1096 | 0 | } |
1097 | 0 | if(index != -1 && index != m->index) { |
1098 | 0 | ml = ml->next; |
1099 | 0 | continue; |
1100 | 0 | } |
1101 | | /* Look in all rtpmap attributes */ |
1102 | 0 | GList *ma = m->attributes; |
1103 | 0 | while(ma) { |
1104 | 0 | janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data; |
1105 | 0 | if(a->name != NULL && a->value != NULL && !strcasecmp(a->name, "rtpmap")) { |
1106 | 0 | int pt = atoi(a->value); |
1107 | 0 | if(strstr(a->value, "red/48000/2")) |
1108 | 0 | return pt; |
1109 | 0 | } |
1110 | 0 | ma = ma->next; |
1111 | 0 | } |
1112 | 0 | if(index != -1) |
1113 | 0 | break; |
1114 | 0 | ml = ml->next; |
1115 | 0 | } |
1116 | 0 | return -1; |
1117 | 0 | } |
1118 | | |
1119 | 359 | char *janus_sdp_write(janus_sdp *imported) { |
1120 | 359 | if(!imported) |
1121 | 0 | return NULL; |
1122 | 359 | janus_refcount_increase(&imported->ref); |
1123 | 359 | char *sdp = g_malloc(2560), mline[8192], buffer[2048]; |
1124 | 359 | *sdp = '\0'; |
1125 | 359 | size_t sdplen = 2560, mlen = sizeof(mline), offset = 0, moffset = 0; |
1126 | | /* v= */ |
1127 | 359 | g_snprintf(buffer, sizeof(buffer), "v=%d\r\n", imported->version); |
1128 | 359 | janus_strlcat_fast(sdp, buffer, sdplen, &offset); |
1129 | | /* o= */ |
1130 | 359 | g_snprintf(buffer, sizeof(buffer), "o=%s %"SCNu64" %"SCNu64" IN %s %s\r\n", |
1131 | 359 | imported->o_name, imported->o_sessid, imported->o_version, |
1132 | 359 | imported->o_ipv4 ? "IP4" : "IP6", imported->o_addr); |
1133 | 359 | janus_strlcat_fast(sdp, buffer, sdplen, &offset); |
1134 | | /* s= */ |
1135 | 359 | g_snprintf(buffer, sizeof(buffer), "s=%s\r\n", imported->s_name); |
1136 | 359 | janus_strlcat_fast(sdp, buffer, sdplen, &offset); |
1137 | | /* t= */ |
1138 | 359 | g_snprintf(buffer, sizeof(buffer), "t=%"SCNu64" %"SCNu64"\r\n", imported->t_start, imported->t_stop); |
1139 | 359 | janus_strlcat_fast(sdp, buffer, sdplen, &offset); |
1140 | | /* c= */ |
1141 | 359 | if(imported->c_addr != NULL) { |
1142 | 5 | if(imported->c_ipv4 && imported->c_addr && strstr(imported->c_addr, ":")) |
1143 | 2 | imported->c_ipv4 = FALSE; |
1144 | 5 | g_snprintf(buffer, sizeof(buffer), "c=IN %s %s\r\n", |
1145 | 5 | imported->c_ipv4 ? "IP4" : "IP6", imported->c_addr); |
1146 | 5 | janus_strlcat_fast(sdp, buffer, sdplen, &offset); |
1147 | 5 | } |
1148 | | /* a= */ |
1149 | 359 | GList *temp = imported->attributes; |
1150 | 6.69k | while(temp) { |
1151 | 6.33k | janus_sdp_attribute *a = (janus_sdp_attribute *)temp->data; |
1152 | 6.33k | if(a->value != NULL) { |
1153 | 394 | g_snprintf(buffer, sizeof(buffer), "a=%s:%s\r\n", a->name, a->value); |
1154 | 5.93k | } else { |
1155 | 5.93k | g_snprintf(buffer, sizeof(buffer), "a=%s\r\n", a->name); |
1156 | 5.93k | } |
1157 | 6.33k | janus_strlcat_fast(sdp, buffer, sdplen, &offset); |
1158 | 6.33k | temp = temp->next; |
1159 | 6.33k | } |
1160 | | /* m= */ |
1161 | 359 | temp = imported->m_lines; |
1162 | 260k | while(temp) { |
1163 | 260k | mline[0] = '\0'; |
1164 | 260k | moffset = 0; |
1165 | 260k | janus_sdp_mline *m = (janus_sdp_mline *)temp->data; |
1166 | 260k | g_snprintf(buffer, sizeof(buffer), "m=%s %d %s", m->type_str, m->port, m->proto); |
1167 | 260k | janus_strlcat_fast(mline, buffer, mlen, &moffset); |
1168 | 260k | if(m->port == 0 && m->type != JANUS_SDP_APPLICATION) { |
1169 | | /* Remove all payload types/formats if we're rejecting the media */ |
1170 | 3.41k | g_list_free_full(m->fmts, (GDestroyNotify)g_free); |
1171 | 3.41k | m->fmts = NULL; |
1172 | 3.41k | g_list_free(m->ptypes); |
1173 | 3.41k | m->ptypes = NULL; |
1174 | 3.41k | m->ptypes = g_list_append(m->ptypes, GINT_TO_POINTER(0)); |
1175 | 3.41k | janus_strlcat_fast(mline, " 0", mlen, &moffset); |
1176 | 256k | } else { |
1177 | 256k | if(m->proto != NULL && strstr(m->proto, "RTP") != NULL) { |
1178 | | /* RTP profile, use payload types */ |
1179 | 521 | GList *ptypes = m->ptypes; |
1180 | 11.1k | while(ptypes) { |
1181 | 10.6k | g_snprintf(buffer, sizeof(buffer), " %d", GPOINTER_TO_INT(ptypes->data)); |
1182 | 10.6k | janus_strlcat_fast(mline, buffer, mlen, &moffset); |
1183 | 10.6k | ptypes = ptypes->next; |
1184 | 10.6k | } |
1185 | 256k | } else { |
1186 | | /* Something else, use formats */ |
1187 | 256k | GList *fmts = m->fmts; |
1188 | 542k | while(fmts) { |
1189 | 286k | g_snprintf(buffer, sizeof(buffer), " %s", (char *)(fmts->data)); |
1190 | 286k | janus_strlcat_fast(mline, buffer, mlen, &moffset); |
1191 | 286k | fmts = fmts->next; |
1192 | 286k | } |
1193 | 256k | } |
1194 | 256k | } |
1195 | 260k | janus_strlcat_fast(mline, "\r\n", mlen, &moffset); |
1196 | | /* c= */ |
1197 | 260k | if(m->c_addr != NULL) { |
1198 | 1.41k | g_snprintf(buffer, sizeof(buffer), "c=IN %s %s\r\n", |
1199 | 1.41k | m->c_ipv4 ? "IP4" : "IP6", m->c_addr); |
1200 | 1.41k | janus_strlcat_fast(mline, buffer, mlen, &moffset); |
1201 | 1.41k | } |
1202 | 260k | if(m->port > 0) { |
1203 | | /* b= */ |
1204 | 255k | if(m->b_name != NULL) { |
1205 | 1.12k | g_snprintf(buffer, sizeof(buffer), "b=%s:%"SCNu32"\r\n", m->b_name, m->b_value); |
1206 | 1.12k | janus_strlcat_fast(mline, buffer, mlen, &moffset); |
1207 | 1.12k | } |
1208 | 255k | } |
1209 | | /* a= (note that we don't format the direction if it's JANUS_SDP_DEFAULT) */ |
1210 | 260k | const char *direction = m->direction != JANUS_SDP_DEFAULT ? janus_sdp_mdirection_str(m->direction) : NULL; |
1211 | 260k | if(direction != NULL) { |
1212 | 260k | g_snprintf(buffer, sizeof(buffer), "a=%s\r\n", direction); |
1213 | 260k | janus_strlcat_fast(mline, buffer, mlen, &moffset); |
1214 | 260k | } |
1215 | 260k | GList *temp2 = m->attributes; |
1216 | 549k | while(temp2) { |
1217 | 288k | janus_sdp_attribute *a = (janus_sdp_attribute *)temp2->data; |
1218 | 288k | if(m->port == 0 && strcasecmp(a->name, "mid")) { |
1219 | | /* This media has been rejected or disabled: we only add the mid attribute, if available */ |
1220 | 3.46k | temp2 = temp2->next; |
1221 | 3.46k | continue; |
1222 | 3.46k | } |
1223 | 285k | if(a->value != NULL) { |
1224 | 6.73k | g_snprintf(buffer, sizeof(buffer), "a=%s:%s\r\n", a->name, a->value); |
1225 | 278k | } else { |
1226 | 278k | g_snprintf(buffer, sizeof(buffer), "a=%s\r\n", a->name); |
1227 | 278k | } |
1228 | 285k | janus_strlcat_fast(mline, buffer, mlen, &moffset); |
1229 | 285k | temp2 = temp2->next; |
1230 | 285k | } |
1231 | | /* Append the generated m-line to the SDP */ |
1232 | 260k | size_t cur_sdplen = strlen(sdp); |
1233 | 260k | size_t mlinelen = strlen(mline); |
1234 | 260k | if(cur_sdplen + mlinelen + 1 > sdplen) { |
1235 | | /* Increase the SDP buffer first */ |
1236 | 310 | if(sdplen < (mlinelen+1)) |
1237 | 42 | sdplen = cur_sdplen + mlinelen + 1; |
1238 | 268 | else |
1239 | 268 | sdplen = sdplen*2; |
1240 | 310 | sdp = g_realloc(sdp, sdplen); |
1241 | 310 | } |
1242 | 260k | janus_strlcat_fast(sdp, mline, sdplen, &offset); |
1243 | | /* Move on */ |
1244 | 260k | temp = temp->next; |
1245 | 260k | } |
1246 | 359 | janus_refcount_decrease(&imported->ref); |
1247 | 359 | return sdp; |
1248 | 359 | } |
1249 | | |
1250 | 0 | void janus_sdp_find_preferred_codec(janus_sdp *sdp, janus_sdp_mtype type, int index, const char **codec) { |
1251 | 0 | if(sdp == NULL) |
1252 | 0 | return; |
1253 | 0 | janus_refcount_increase(&sdp->ref); |
1254 | 0 | gboolean found = FALSE; |
1255 | 0 | GList *temp = sdp->m_lines; |
1256 | 0 | while(temp) { |
1257 | | /* Which media are available? */ |
1258 | 0 | janus_sdp_mline *m = (janus_sdp_mline *)temp->data; |
1259 | 0 | if(index != -1 && index != m->index) { |
1260 | 0 | temp = temp->next; |
1261 | 0 | continue; |
1262 | 0 | } |
1263 | 0 | if(m->type == type && m->port > 0 && m->direction != JANUS_SDP_INACTIVE) { |
1264 | 0 | uint i=0; |
1265 | 0 | for(i=0; i<(type == JANUS_SDP_AUDIO ? janus_audio_codecs : janus_video_codecs); i++) { |
1266 | 0 | if(janus_sdp_get_codec_pt(sdp, m->index, |
1267 | 0 | type == JANUS_SDP_AUDIO ? janus_preferred_audio_codecs[i] : janus_preferred_video_codecs[i]) > 0) { |
1268 | 0 | found = TRUE; |
1269 | 0 | if(codec) |
1270 | 0 | *codec = (type == JANUS_SDP_AUDIO ? janus_preferred_audio_codecs[i] : janus_preferred_video_codecs[i]); |
1271 | 0 | break; |
1272 | 0 | } |
1273 | 0 | } |
1274 | 0 | } |
1275 | 0 | if(found || index != -1) |
1276 | 0 | break; |
1277 | 0 | temp = temp->next; |
1278 | 0 | } |
1279 | 0 | janus_refcount_decrease(&sdp->ref); |
1280 | 0 | } |
1281 | | |
1282 | 0 | void janus_sdp_find_first_codec(janus_sdp *sdp, janus_sdp_mtype type, int index, const char **codec) { |
1283 | 0 | if(sdp == NULL) |
1284 | 0 | return; |
1285 | 0 | janus_refcount_increase(&sdp->ref); |
1286 | 0 | gboolean found = FALSE; |
1287 | 0 | GList *temp = sdp->m_lines; |
1288 | 0 | while(temp) { |
1289 | | /* Which media are available? */ |
1290 | 0 | janus_sdp_mline *m = (janus_sdp_mline *)temp->data; |
1291 | 0 | if(index != -1 && index != m->index) { |
1292 | 0 | temp = temp->next; |
1293 | 0 | continue; |
1294 | 0 | } |
1295 | 0 | if(m->type == type && m->port > 0 && m->direction != JANUS_SDP_INACTIVE && m->ptypes) { |
1296 | 0 | int pt = GPOINTER_TO_INT(m->ptypes->data); |
1297 | 0 | const char *c = janus_sdp_get_codec_name(sdp, m->index, pt); |
1298 | 0 | if(c && !strcasecmp(c, "red")) { |
1299 | | /* We're using RED, so check the second payload type for the actual codec */ |
1300 | 0 | pt = m->ptypes->next ? GPOINTER_TO_INT(m->ptypes->next->data) : -1; |
1301 | 0 | c = janus_sdp_get_codec_name(sdp, m->index, pt); |
1302 | 0 | } |
1303 | 0 | c = janus_sdp_match_preferred_codec(m->type, (char *)c); |
1304 | 0 | if(c) { |
1305 | 0 | found = TRUE; |
1306 | 0 | if(codec) |
1307 | 0 | *codec = c; |
1308 | 0 | } |
1309 | 0 | } |
1310 | 0 | if(found || index != -1) |
1311 | 0 | break; |
1312 | 0 | temp = temp->next; |
1313 | 0 | } |
1314 | 0 | janus_refcount_decrease(&sdp->ref); |
1315 | 0 | } |
1316 | | |
1317 | 0 | const char *janus_sdp_match_preferred_codec(janus_sdp_mtype type, char *codec) { |
1318 | 0 | if(codec == NULL) |
1319 | 0 | return NULL; |
1320 | 0 | if(type != JANUS_SDP_AUDIO && type != JANUS_SDP_VIDEO) |
1321 | 0 | return NULL; |
1322 | 0 | gboolean video = (type == JANUS_SDP_VIDEO); |
1323 | 0 | uint i=0; |
1324 | 0 | for(i=0; i<(video ? janus_video_codecs : janus_audio_codecs); i++) { |
1325 | 0 | if(!strcasecmp(codec, (video ? janus_preferred_video_codecs[i] : janus_preferred_audio_codecs[i]))) { |
1326 | | /* Found! */ |
1327 | 0 | return video ? janus_preferred_video_codecs[i] : janus_preferred_audio_codecs[i]; |
1328 | 0 | } |
1329 | 0 | } |
1330 | 0 | return NULL; |
1331 | 0 | } |
1332 | | |
1333 | 0 | janus_sdp *janus_sdp_new(const char *name, const char *address) { |
1334 | 0 | janus_sdp *sdp = g_malloc(sizeof(janus_sdp)); |
1335 | 0 | g_atomic_int_set(&sdp->destroyed, 0); |
1336 | 0 | janus_refcount_init(&sdp->ref, janus_sdp_free); |
1337 | | /* Fill in some predefined stuff */ |
1338 | 0 | sdp->version = 0; |
1339 | 0 | sdp->o_name = g_strdup("-"); |
1340 | 0 | sdp->o_sessid = janus_get_real_time(); |
1341 | 0 | sdp->o_version = 1; |
1342 | 0 | sdp->o_ipv4 = TRUE; |
1343 | 0 | sdp->o_addr = g_strdup(address ? address : "127.0.0.1"); |
1344 | 0 | sdp->s_name = g_strdup(name ? name : "Janus session"); |
1345 | 0 | sdp->t_start = 0; |
1346 | 0 | sdp->t_stop = 0; |
1347 | 0 | sdp->c_ipv4 = TRUE; |
1348 | 0 | sdp->c_addr = g_strdup(address ? address : "127.0.0.1"); |
1349 | 0 | sdp->attributes = NULL; |
1350 | 0 | sdp->m_lines = NULL; |
1351 | | /* Done */ |
1352 | 0 | return sdp; |
1353 | 0 | } |
1354 | | |
1355 | 0 | static int janus_sdp_id_compare(gconstpointer a, gconstpointer b) { |
1356 | 0 | return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b); |
1357 | 0 | } |
1358 | 0 | janus_sdp *janus_sdp_generate_offer(const char *name, const char *address, ...) { |
1359 | | /* This method has a variable list of arguments, telling us what we should offer */ |
1360 | 0 | int property = -1; |
1361 | 0 | va_list args; |
1362 | 0 | va_start(args, address); |
1363 | | |
1364 | | /* Create a new janus_sdp object */ |
1365 | 0 | janus_sdp *offer = janus_sdp_new(name, address); |
1366 | |
|
1367 | 0 | gboolean new_mline = FALSE, mline_enabled = FALSE; |
1368 | 0 | janus_sdp_mtype type = JANUS_SDP_OTHER; |
1369 | 0 | gboolean audio_dtmf = FALSE, video_rtcpfb = TRUE, data_legacy = FALSE; |
1370 | 0 | int pt = -1, opusred_pt = -1; |
1371 | 0 | const char *codec = NULL, *mid = NULL, *msid = NULL, *mstid = NULL, |
1372 | 0 | *fmtp = NULL, *vp9_profile = NULL, *h264_profile = NULL; |
1373 | 0 | janus_sdp_mdirection mdir = JANUS_SDP_DEFAULT; |
1374 | 0 | GHashTable *extmaps = NULL, *extids = NULL, *m_extids = NULL; |
1375 | |
|
1376 | 0 | while(property != JANUS_SDP_OA_DONE) { |
1377 | 0 | property = va_arg(args, int); |
1378 | 0 | if(!new_mline && property != JANUS_SDP_OA_MLINE && property != JANUS_SDP_OA_DONE) { |
1379 | | /* The first attribute MUST be JANUS_SDP_OA_MLINE or JANUS_SDP_OA_DONE */ |
1380 | 0 | JANUS_LOG(LOG_ERR, "First attribute is not JANUS_SDP_OA_MLINE or JANUS_SDP_OA_DONE\n"); |
1381 | 0 | janus_sdp_destroy(offer); |
1382 | 0 | if(extmaps != NULL) |
1383 | 0 | g_hash_table_destroy(extmaps); |
1384 | 0 | if(extids != NULL) |
1385 | 0 | g_hash_table_destroy(extids); |
1386 | 0 | if(m_extids != NULL) |
1387 | 0 | g_hash_table_destroy(m_extids); |
1388 | 0 | va_end(args); |
1389 | 0 | return NULL; |
1390 | 0 | } |
1391 | 0 | if(property == JANUS_SDP_OA_MLINE || property == JANUS_SDP_OA_DONE) { |
1392 | | /* A new m-line is starting or we're done, should we wrap the previous one? */ |
1393 | 0 | new_mline = TRUE; |
1394 | 0 | if(mline_enabled) { |
1395 | | /* Create a new m-line with the data collected so far */ |
1396 | 0 | if(type == JANUS_SDP_AUDIO) { |
1397 | 0 | if(janus_sdp_generate_offer_mline(offer, |
1398 | 0 | JANUS_SDP_OA_MLINE, JANUS_SDP_AUDIO, |
1399 | 0 | JANUS_SDP_OA_MID, mid, |
1400 | 0 | JANUS_SDP_OA_MSID, msid, mstid, |
1401 | 0 | JANUS_SDP_OA_OPUSRED_PT, opusred_pt, |
1402 | 0 | JANUS_SDP_OA_CODEC, codec, |
1403 | 0 | JANUS_SDP_OA_DIRECTION, mdir, |
1404 | 0 | JANUS_SDP_OA_FMTP, fmtp, |
1405 | 0 | JANUS_SDP_OA_EXTENSIONS, m_extids, |
1406 | 0 | JANUS_SDP_OA_AUDIO_DTMF, audio_dtmf, |
1407 | 0 | JANUS_SDP_OA_DONE |
1408 | 0 | ) < 0) { |
1409 | 0 | janus_sdp_destroy(offer); |
1410 | 0 | if(extmaps != NULL) |
1411 | 0 | g_hash_table_destroy(extmaps); |
1412 | 0 | if(extids != NULL) |
1413 | 0 | g_hash_table_destroy(extids); |
1414 | 0 | if(m_extids != NULL) |
1415 | 0 | g_hash_table_destroy(m_extids); |
1416 | 0 | va_end(args); |
1417 | 0 | return NULL; |
1418 | 0 | } |
1419 | 0 | } else if(type == JANUS_SDP_VIDEO) { |
1420 | 0 | if(janus_sdp_generate_offer_mline(offer, |
1421 | 0 | JANUS_SDP_OA_MLINE, JANUS_SDP_VIDEO, |
1422 | 0 | JANUS_SDP_OA_MID, mid, |
1423 | 0 | JANUS_SDP_OA_MSID, msid, mstid, |
1424 | 0 | JANUS_SDP_OA_PT, pt, |
1425 | 0 | JANUS_SDP_OA_CODEC, codec, |
1426 | 0 | JANUS_SDP_OA_DIRECTION, mdir, |
1427 | 0 | JANUS_SDP_OA_FMTP, fmtp, |
1428 | 0 | JANUS_SDP_OA_EXTENSIONS, m_extids, |
1429 | 0 | JANUS_SDP_OA_VIDEO_RTCPFB_DEFAULTS, video_rtcpfb, |
1430 | 0 | JANUS_SDP_OA_VP9_PROFILE, vp9_profile, |
1431 | 0 | JANUS_SDP_OA_H264_PROFILE, h264_profile, |
1432 | 0 | JANUS_SDP_OA_DONE |
1433 | 0 | ) < 0) { |
1434 | 0 | janus_sdp_destroy(offer); |
1435 | 0 | if(extmaps != NULL) |
1436 | 0 | g_hash_table_destroy(extmaps); |
1437 | 0 | if(extids != NULL) |
1438 | 0 | g_hash_table_destroy(extids); |
1439 | 0 | if(m_extids != NULL) |
1440 | 0 | g_hash_table_destroy(m_extids); |
1441 | 0 | va_end(args); |
1442 | 0 | return NULL; |
1443 | 0 | } |
1444 | 0 | } else if(type == JANUS_SDP_APPLICATION) { |
1445 | 0 | if(janus_sdp_generate_offer_mline(offer, |
1446 | 0 | JANUS_SDP_OA_MLINE, JANUS_SDP_APPLICATION, |
1447 | 0 | JANUS_SDP_OA_MID, mid, |
1448 | 0 | JANUS_SDP_OA_DATA_LEGACY, data_legacy, |
1449 | 0 | JANUS_SDP_OA_DONE |
1450 | 0 | ) < 0) { |
1451 | 0 | janus_sdp_destroy(offer); |
1452 | 0 | if(extmaps != NULL) |
1453 | 0 | g_hash_table_destroy(extmaps); |
1454 | 0 | if(extids != NULL) |
1455 | 0 | g_hash_table_destroy(extids); |
1456 | 0 | if(m_extids != NULL) |
1457 | 0 | g_hash_table_destroy(m_extids); |
1458 | 0 | va_end(args); |
1459 | 0 | return NULL; |
1460 | 0 | } |
1461 | 0 | } |
1462 | 0 | } |
1463 | 0 | if(property != JANUS_SDP_OA_MLINE) |
1464 | 0 | continue; |
1465 | | /* Now reset the properties */ |
1466 | 0 | audio_dtmf = FALSE; |
1467 | 0 | video_rtcpfb = TRUE; |
1468 | 0 | data_legacy = FALSE; |
1469 | 0 | pt = -1; |
1470 | 0 | opusred_pt = -1; |
1471 | 0 | mid = NULL; |
1472 | 0 | msid = NULL; |
1473 | 0 | mstid = NULL; |
1474 | 0 | codec = NULL; |
1475 | 0 | fmtp = NULL; |
1476 | 0 | vp9_profile = NULL; |
1477 | 0 | h264_profile = NULL; |
1478 | 0 | if(m_extids != NULL) |
1479 | 0 | g_hash_table_destroy(m_extids); |
1480 | 0 | m_extids = NULL; |
1481 | 0 | mdir = JANUS_SDP_DEFAULT; |
1482 | 0 | mline_enabled = TRUE; |
1483 | | /* The value of JANUS_SDP_OA_MLINE MUST be the media we want to add */ |
1484 | 0 | type = va_arg(args, int); |
1485 | 0 | if(type == JANUS_SDP_AUDIO) { |
1486 | | /* Audio, let's set some defaults */ |
1487 | 0 | pt = 111; |
1488 | 0 | codec = "opus"; |
1489 | 0 | } else if(type == JANUS_SDP_VIDEO) { |
1490 | | /* Video, let's set some defaults */ |
1491 | 0 | pt = 96; |
1492 | 0 | codec = "vp8"; |
1493 | 0 | } else if(type == JANUS_SDP_APPLICATION) { |
1494 | | /* Data */ |
1495 | 0 | } else { |
1496 | | /* Unsupported m-line type */ |
1497 | 0 | JANUS_LOG(LOG_ERR, "Invalid m-line type\n"); |
1498 | 0 | janus_sdp_destroy(offer); |
1499 | 0 | if(extmaps != NULL) |
1500 | 0 | g_hash_table_destroy(extmaps); |
1501 | 0 | if(extids != NULL) |
1502 | 0 | g_hash_table_destroy(extids); |
1503 | 0 | if(m_extids != NULL) |
1504 | 0 | g_hash_table_destroy(m_extids); |
1505 | 0 | va_end(args); |
1506 | 0 | return NULL; |
1507 | 0 | } |
1508 | | /* Let's assume the m-line is enabled, by default */ |
1509 | 0 | mline_enabled = TRUE; |
1510 | 0 | } else if(property == JANUS_SDP_OA_ENABLED) { |
1511 | 0 | mline_enabled = va_arg(args, gboolean); |
1512 | 0 | } else if(property == JANUS_SDP_OA_MID) { |
1513 | 0 | mid = va_arg(args, char *); |
1514 | 0 | } else if(property == JANUS_SDP_OA_MSID) { |
1515 | 0 | msid = va_arg(args, char *); |
1516 | 0 | mstid = va_arg(args, char *); |
1517 | 0 | } else if(property == JANUS_SDP_OA_DIRECTION) { |
1518 | 0 | mdir = va_arg(args, janus_sdp_mdirection); |
1519 | 0 | } else if(property == JANUS_SDP_OA_CODEC) { |
1520 | 0 | codec = va_arg(args, char *); |
1521 | 0 | } else if(property == JANUS_SDP_OA_PT) { |
1522 | 0 | pt = va_arg(args, int); |
1523 | 0 | } else if(property == JANUS_SDP_OA_OPUSRED_PT) { |
1524 | 0 | opusred_pt = va_arg(args, int); |
1525 | 0 | } else if(property == JANUS_SDP_OA_FMTP) { |
1526 | 0 | fmtp = va_arg(args, char *); |
1527 | 0 | } else if(property == JANUS_SDP_OA_VP9_PROFILE) { |
1528 | 0 | vp9_profile = va_arg(args, char *); |
1529 | 0 | } else if(property == JANUS_SDP_OA_H264_PROFILE) { |
1530 | 0 | h264_profile = va_arg(args, char *); |
1531 | 0 | } else if(property == JANUS_SDP_OA_AUDIO_DTMF) { |
1532 | 0 | audio_dtmf = va_arg(args, gboolean); |
1533 | 0 | } else if(property == JANUS_SDP_OA_VIDEO_RTCPFB_DEFAULTS) { |
1534 | 0 | video_rtcpfb = va_arg(args, gboolean); |
1535 | 0 | } else if(property == JANUS_SDP_OA_DATA_LEGACY) { |
1536 | 0 | data_legacy = va_arg(args, gboolean); |
1537 | 0 | } else if(property == JANUS_SDP_OA_EXTENSION) { |
1538 | 0 | char *extmap = va_arg(args, char *); |
1539 | 0 | int id = va_arg(args, int); |
1540 | 0 | if(extmap != NULL && id > 0 && id < 15) { |
1541 | 0 | if(extmaps == NULL) |
1542 | 0 | extmaps = g_hash_table_new(g_str_hash, g_str_equal); |
1543 | 0 | if(extids == NULL) |
1544 | 0 | extids = g_hash_table_new(NULL, NULL); |
1545 | | /* Make sure the extmap and ID have not been added already */ |
1546 | 0 | if(g_hash_table_lookup(extids, GINT_TO_POINTER(id)) == NULL && |
1547 | 0 | g_hash_table_lookup(extmaps, extmap) == NULL) { |
1548 | 0 | g_hash_table_insert(extmaps, extmap, GINT_TO_POINTER(id)); |
1549 | 0 | g_hash_table_insert(extids, GINT_TO_POINTER(id), extmap); |
1550 | 0 | } |
1551 | 0 | if(g_hash_table_lookup(extmaps, extmap) == GINT_TO_POINTER(id)) { |
1552 | 0 | if(m_extids == NULL) |
1553 | 0 | m_extids = g_hash_table_new(NULL, NULL); |
1554 | 0 | g_hash_table_insert(m_extids, GINT_TO_POINTER(id), extmap); |
1555 | 0 | } |
1556 | 0 | } |
1557 | 0 | } else { |
1558 | 0 | JANUS_LOG(LOG_WARN, "Unknown property %d for preparing SDP offer, ignoring...\n", property); |
1559 | 0 | } |
1560 | 0 | } |
1561 | 0 | if(extmaps != NULL) |
1562 | 0 | g_hash_table_destroy(extmaps); |
1563 | 0 | if(extids != NULL) |
1564 | 0 | g_hash_table_destroy(extids); |
1565 | | |
1566 | | /* Done */ |
1567 | 0 | va_end(args); |
1568 | |
|
1569 | 0 | return offer; |
1570 | 0 | } |
1571 | | |
1572 | 0 | int janus_sdp_generate_offer_mline(janus_sdp *offer, ...) { |
1573 | 0 | if(offer == NULL) |
1574 | 0 | return -1; |
1575 | | |
1576 | | /* This method has a variable list of arguments, telling us what we should offer */ |
1577 | 0 | va_list args; |
1578 | 0 | va_start(args, offer); |
1579 | | |
1580 | | /* First of all, let's see what we should add */ |
1581 | 0 | janus_sdp_mtype type = JANUS_SDP_OTHER; |
1582 | 0 | gboolean audio_dtmf = FALSE, video_rtcpfb = TRUE, data_legacy = FALSE; |
1583 | 0 | int pt = -1, opusred_pt = -1; |
1584 | 0 | const char *codec = NULL, *mid = NULL, *msid = NULL, *mstid = NULL, |
1585 | 0 | *rtpmap = NULL, *fmtp = NULL, *vp9_profile = NULL, *h264_profile = NULL; |
1586 | 0 | janus_sdp_mdirection mdir = JANUS_SDP_DEFAULT; |
1587 | 0 | GHashTable *extmaps = NULL, *extids = NULL; |
1588 | 0 | gboolean extids_allocated = FALSE; |
1589 | 0 | gboolean twcc = FALSE; |
1590 | |
|
1591 | 0 | int property = va_arg(args, int); |
1592 | 0 | if(property != JANUS_SDP_OA_MLINE) { |
1593 | | /* The first attribute MUST be JANUS_SDP_OA_MLINE */ |
1594 | 0 | JANUS_LOG(LOG_ERR, "First attribute is not JANUS_SDP_OA_MLINE\n"); |
1595 | 0 | va_end(args); |
1596 | 0 | return -2; |
1597 | 0 | } |
1598 | 0 | type = va_arg(args, int); |
1599 | 0 | if(type == JANUS_SDP_AUDIO) { |
1600 | | /* Audio */ |
1601 | 0 | pt = 111; |
1602 | 0 | codec = "opus"; |
1603 | 0 | } else if(type == JANUS_SDP_VIDEO) { |
1604 | | /* Video */ |
1605 | 0 | pt = 96; |
1606 | 0 | codec = "vp8"; |
1607 | 0 | } else if(type == JANUS_SDP_APPLICATION) { |
1608 | | /* Data */ |
1609 | 0 | #ifndef HAVE_SCTP |
1610 | 0 | va_end(args); |
1611 | 0 | return -3; |
1612 | 0 | #endif |
1613 | 0 | } else { |
1614 | | /* Unsupported m-line type */ |
1615 | 0 | JANUS_LOG(LOG_ERR, "Invalid m-line type\n"); |
1616 | 0 | janus_sdp_destroy(offer); |
1617 | 0 | va_end(args); |
1618 | 0 | return -4; |
1619 | 0 | } |
1620 | | |
1621 | | /* Let's see what we should do with the media to add */ |
1622 | 0 | property = va_arg(args, int); |
1623 | 0 | while(property != JANUS_SDP_OA_DONE) { |
1624 | 0 | if(property == JANUS_SDP_OA_DIRECTION) { |
1625 | 0 | mdir = va_arg(args, janus_sdp_mdirection); |
1626 | 0 | } else if(property == JANUS_SDP_OA_CODEC) { |
1627 | 0 | codec = va_arg(args, char *); |
1628 | 0 | } else if(property == JANUS_SDP_OA_MID) { |
1629 | 0 | mid = va_arg(args, char *); |
1630 | 0 | } else if(property == JANUS_SDP_OA_MSID) { |
1631 | 0 | msid = va_arg(args, char *); |
1632 | 0 | mstid = va_arg(args, char *); |
1633 | 0 | } else if(property == JANUS_SDP_OA_PT) { |
1634 | 0 | pt = va_arg(args, int); |
1635 | 0 | } else if(property == JANUS_SDP_OA_OPUSRED_PT) { |
1636 | 0 | opusred_pt = va_arg(args, int); |
1637 | 0 | } else if(property == JANUS_SDP_OA_FMTP) { |
1638 | 0 | fmtp = va_arg(args, char *); |
1639 | 0 | } else if(property == JANUS_SDP_OA_AUDIO_DTMF) { |
1640 | 0 | audio_dtmf = va_arg(args, gboolean); |
1641 | 0 | } else if(property == JANUS_SDP_OA_VIDEO_RTCPFB_DEFAULTS) { |
1642 | 0 | video_rtcpfb = va_arg(args, gboolean); |
1643 | 0 | } else if(property == JANUS_SDP_OA_VP9_PROFILE) { |
1644 | 0 | vp9_profile = va_arg(args, char *); |
1645 | 0 | } else if(property == JANUS_SDP_OA_H264_PROFILE) { |
1646 | 0 | h264_profile = va_arg(args, char *); |
1647 | 0 | } else if(property == JANUS_SDP_OA_DATA_LEGACY) { |
1648 | 0 | data_legacy = va_arg(args, gboolean); |
1649 | 0 | } else if(property == JANUS_SDP_OA_EXTENSION) { |
1650 | 0 | if((extmaps != NULL || extids != NULL) && !extids_allocated) { |
1651 | 0 | JANUS_LOG(LOG_ERR, "Conflicting extensions settings (can't use both JANUS_SDP_OA_EXTENSION and JANUS_SDP_OA_EXTENSIONS)\n"); |
1652 | 0 | if(extmaps != NULL) |
1653 | 0 | g_hash_table_destroy(extmaps); |
1654 | 0 | if(extids_allocated) { |
1655 | 0 | if(extids != NULL) |
1656 | 0 | g_hash_table_destroy(extids); |
1657 | 0 | } |
1658 | 0 | va_end(args); |
1659 | 0 | return -5; |
1660 | 0 | } |
1661 | 0 | char *extmap = va_arg(args, char *); |
1662 | 0 | int id = va_arg(args, int); |
1663 | 0 | if(extmap != NULL && id > 0 && id < 15) { |
1664 | 0 | if(extmaps == NULL) |
1665 | 0 | extmaps = g_hash_table_new(g_str_hash, g_str_equal); |
1666 | 0 | if(extids == NULL) |
1667 | 0 | extids = g_hash_table_new(NULL, NULL); |
1668 | 0 | extids_allocated = TRUE; |
1669 | | /* Make sure the extmap and ID have not been added already */ |
1670 | 0 | char *check_extmap = g_hash_table_lookup(extids, GINT_TO_POINTER(id)); |
1671 | 0 | if(check_extmap != NULL) { |
1672 | 0 | JANUS_LOG(LOG_WARN, "Ignoring duplicate extension %d (already added: %s)\n", id, check_extmap); |
1673 | 0 | } else { |
1674 | 0 | if(g_hash_table_lookup(extmaps, extmap) != NULL) { |
1675 | 0 | JANUS_LOG(LOG_WARN, "Ignoring duplicate extension %s (already added: %d)\n", |
1676 | 0 | extmap, GPOINTER_TO_INT(g_hash_table_lookup(extmaps, extmap))); |
1677 | 0 | } else { |
1678 | 0 | g_hash_table_insert(extmaps, extmap, GINT_TO_POINTER(id)); |
1679 | 0 | g_hash_table_insert(extids, GINT_TO_POINTER(id), extmap); |
1680 | 0 | if(!strcasecmp(extmap, JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC)) { |
1681 | | /* Take note of the fact we may negotiate TWCC for this m-line */ |
1682 | 0 | twcc = TRUE; |
1683 | 0 | } |
1684 | 0 | } |
1685 | 0 | } |
1686 | 0 | } |
1687 | 0 | } else if(property == JANUS_SDP_OA_EXTENSIONS) { |
1688 | 0 | if(extmaps != NULL || extids != NULL) { |
1689 | 0 | JANUS_LOG(LOG_ERR, "Conflicting extensions settings (can't use both JANUS_SDP_OA_EXTENSION and JANUS_SDP_OA_EXTENSIONS)\n"); |
1690 | 0 | if(extmaps != NULL) |
1691 | 0 | g_hash_table_destroy(extmaps); |
1692 | 0 | if(extids_allocated) { |
1693 | 0 | if(extids != NULL) |
1694 | 0 | g_hash_table_destroy(extids); |
1695 | 0 | } |
1696 | 0 | va_end(args); |
1697 | 0 | return -5; |
1698 | 0 | } |
1699 | 0 | extids = va_arg(args, GHashTable *); |
1700 | 0 | extids_allocated = FALSE; |
1701 | 0 | } else { |
1702 | 0 | JANUS_LOG(LOG_WARN, "Unknown property %d for preparing SDP answer, ignoring...\n", property); |
1703 | 0 | } |
1704 | 0 | property = va_arg(args, int); |
1705 | 0 | } |
1706 | | /* Configure some defaults, if values weren't specified */ |
1707 | 0 | if(type == JANUS_SDP_AUDIO) { |
1708 | 0 | if(codec == NULL) |
1709 | 0 | codec = "opus"; |
1710 | 0 | rtpmap = janus_sdp_get_codec_rtpmap(codec); |
1711 | 0 | if(rtpmap == NULL) { |
1712 | 0 | JANUS_LOG(LOG_ERR, "Unsupported audio codec '%s', can't prepare an offer\n", codec); |
1713 | 0 | if(extmaps != NULL) |
1714 | 0 | g_hash_table_destroy(extmaps); |
1715 | 0 | if(extids_allocated) { |
1716 | 0 | if(extids != NULL) |
1717 | 0 | g_hash_table_destroy(extids); |
1718 | 0 | } |
1719 | 0 | va_end(args); |
1720 | 0 | return -3; |
1721 | 0 | } |
1722 | 0 | } else if(type == JANUS_SDP_VIDEO) { |
1723 | 0 | if(codec == NULL) |
1724 | 0 | codec = "vp8"; |
1725 | 0 | rtpmap = janus_sdp_get_codec_rtpmap(codec); |
1726 | 0 | if(rtpmap == NULL) { |
1727 | 0 | JANUS_LOG(LOG_ERR, "Unsupported video codec '%s', can't prepare an offer\n", codec); |
1728 | 0 | if(extmaps != NULL) |
1729 | 0 | g_hash_table_destroy(extmaps); |
1730 | 0 | if(extids_allocated) { |
1731 | 0 | if(extids != NULL) |
1732 | 0 | g_hash_table_destroy(extids); |
1733 | 0 | } |
1734 | 0 | va_end(args); |
1735 | 0 | return -4; |
1736 | 0 | } |
1737 | 0 | } |
1738 | | |
1739 | | /* Create the m-line */ |
1740 | 0 | const char *transport = "UDP/TLS/RTP/SAVPF"; |
1741 | 0 | if(type == JANUS_SDP_APPLICATION) |
1742 | 0 | transport = (data_legacy ? "DTLS/SCTP" : "UDP/DTLS/SCTP"); |
1743 | 0 | janus_sdp_mline *m = janus_sdp_mline_create(type, 9, transport, mdir); |
1744 | 0 | m->index = g_list_length(offer->m_lines); |
1745 | 0 | m->c_ipv4 = TRUE; |
1746 | 0 | m->c_addr = g_strdup(offer->c_addr); |
1747 | 0 | janus_sdp_attribute *a = NULL; |
1748 | | /* Any mid we should set? */ |
1749 | 0 | if(mid != NULL) { |
1750 | 0 | a = janus_sdp_attribute_create("mid", "%s", mid); |
1751 | 0 | m->attributes = g_list_append(m->attributes, a); |
1752 | 0 | } |
1753 | | /* Any msid we should set? */ |
1754 | 0 | if(type != JANUS_SDP_APPLICATION && msid != NULL && mstid != NULL) { |
1755 | 0 | a = janus_sdp_attribute_create("msid", "%s %s", msid, mstid); |
1756 | 0 | m->attributes = g_list_append(m->attributes, a); |
1757 | 0 | } |
1758 | 0 | if(type == JANUS_SDP_AUDIO || type == JANUS_SDP_VIDEO) { |
1759 | | /* Add the selected codec */ |
1760 | 0 | if(type == JANUS_SDP_AUDIO && opusred_pt > 0) { |
1761 | | /* ... but add RED first */ |
1762 | 0 | m->ptypes = g_list_append(m->ptypes, GINT_TO_POINTER(opusred_pt)); |
1763 | 0 | a = janus_sdp_attribute_create("rtpmap", "%d red/48000/2", opusred_pt); |
1764 | 0 | m->attributes = g_list_append(m->attributes, a); |
1765 | 0 | } |
1766 | 0 | m->ptypes = g_list_append(m->ptypes, GINT_TO_POINTER(pt)); |
1767 | 0 | a = janus_sdp_attribute_create("rtpmap", "%d %s", pt, rtpmap); |
1768 | 0 | m->attributes = g_list_append(m->attributes, a); |
1769 | 0 | if(type == JANUS_SDP_AUDIO) { |
1770 | | /* Check if we need to add a payload type for DTMF tones (telephone-event/8000) */ |
1771 | 0 | if(audio_dtmf) { |
1772 | | /* We do */ |
1773 | 0 | int dtmf_pt = 126; |
1774 | 0 | m->ptypes = g_list_append(m->ptypes, GINT_TO_POINTER(dtmf_pt)); |
1775 | 0 | a = janus_sdp_attribute_create("rtpmap", "%d %s", dtmf_pt, janus_sdp_get_codec_rtpmap("dtmf")); |
1776 | 0 | m->attributes = g_list_append(m->attributes, a); |
1777 | 0 | } |
1778 | 0 | } |
1779 | 0 | if(type == JANUS_SDP_VIDEO && video_rtcpfb) { |
1780 | | /* Add rtcp-fb attributes */ |
1781 | 0 | a = janus_sdp_attribute_create("rtcp-fb", "%d ccm fir", pt); |
1782 | 0 | m->attributes = g_list_append(m->attributes, a); |
1783 | 0 | a = janus_sdp_attribute_create("rtcp-fb", "%d nack", pt); |
1784 | 0 | m->attributes = g_list_append(m->attributes, a); |
1785 | 0 | a = janus_sdp_attribute_create("rtcp-fb", "%d nack pli", pt); |
1786 | 0 | m->attributes = g_list_append(m->attributes, a); |
1787 | 0 | a = janus_sdp_attribute_create("rtcp-fb", "%d goog-remb", pt); |
1788 | 0 | m->attributes = g_list_append(m->attributes, a); |
1789 | 0 | } |
1790 | 0 | if(twcc) { |
1791 | | /* Add the transport-wide RTCP feedback message here, since the header extension was negotiated */ |
1792 | 0 | a = janus_sdp_attribute_create("rtcp-fb", "%d transport-cc", pt); |
1793 | 0 | m->attributes = g_list_append(m->attributes, a); |
1794 | 0 | } |
1795 | | /* Check if we need to add extensions to the SDP */ |
1796 | 0 | if(extids != NULL) { |
1797 | 0 | GList *ids = g_list_sort(g_hash_table_get_keys(extids), janus_sdp_id_compare), *iter = ids; |
1798 | 0 | while(iter) { |
1799 | 0 | char *extmap = g_hash_table_lookup(extids, iter->data); |
1800 | 0 | if(extmap != NULL) { |
1801 | 0 | a = janus_sdp_attribute_create("extmap", |
1802 | 0 | "%d %s", GPOINTER_TO_INT(iter->data), extmap); |
1803 | 0 | janus_sdp_attribute_add_to_mline(m, a); |
1804 | 0 | } |
1805 | 0 | iter = iter->next; |
1806 | 0 | } |
1807 | 0 | g_list_free(ids); |
1808 | 0 | } |
1809 | | /* If RED is being offered, add an fmtp line for that */ |
1810 | 0 | if(type == JANUS_SDP_AUDIO && opusred_pt > 0) { |
1811 | 0 | a = janus_sdp_attribute_create("fmtp", "%d %d/%d", opusred_pt, pt, pt); |
1812 | 0 | m->attributes = g_list_append(m->attributes, a); |
1813 | 0 | } |
1814 | | /* Check if there's a custom fmtp line to add */ |
1815 | 0 | if(type == JANUS_SDP_AUDIO && fmtp != NULL) { |
1816 | 0 | a = janus_sdp_attribute_create("fmtp", "%d %s", pt, fmtp); |
1817 | 0 | m->attributes = g_list_append(m->attributes, a); |
1818 | 0 | } else if(type == JANUS_SDP_VIDEO) { |
1819 | | /* For video we can configure an fmtp in different ways */ |
1820 | 0 | if(!strcasecmp(codec, "vp9") && vp9_profile) { |
1821 | | /* Add a profile-id fmtp attribute */ |
1822 | 0 | a = janus_sdp_attribute_create("fmtp", "%d profile-id=%s", pt, vp9_profile); |
1823 | 0 | m->attributes = g_list_append(m->attributes, a); |
1824 | 0 | } else if(!strcasecmp(codec, "h264") && h264_profile) { |
1825 | | /* Add a profile-level-id fmtp attribute */ |
1826 | 0 | a = janus_sdp_attribute_create("fmtp", "%d profile-level-id=%s;packetization-mode=1", |
1827 | 0 | pt, h264_profile); |
1828 | 0 | m->attributes = g_list_append(m->attributes, a); |
1829 | 0 | } else if(fmtp) { |
1830 | | /* There's a custom fmtp line to add for video */ |
1831 | 0 | a = janus_sdp_attribute_create("fmtp", "%d %s", pt, fmtp); |
1832 | 0 | m->attributes = g_list_append(m->attributes, a); |
1833 | 0 | } |
1834 | 0 | } |
1835 | 0 | } else { |
1836 | 0 | m->fmts = g_list_append(m->fmts, g_strdup(data_legacy ? "5000" : "webrtc-datachannel")); |
1837 | | /* Add an sctpmap attribute */ |
1838 | 0 | if(data_legacy) { |
1839 | 0 | a = janus_sdp_attribute_create("sctpmap", "5000 webrtc-datachannel 16"); |
1840 | 0 | m->attributes = g_list_append(m->attributes, a); |
1841 | 0 | } else { |
1842 | 0 | a = janus_sdp_attribute_create("sctp-port", "5000"); |
1843 | 0 | m->attributes = g_list_append(m->attributes, a); |
1844 | 0 | } |
1845 | 0 | } |
1846 | 0 | offer->m_lines = g_list_append(offer->m_lines, m); |
1847 | |
|
1848 | 0 | if(extmaps != NULL) |
1849 | 0 | g_hash_table_destroy(extmaps); |
1850 | 0 | if(extids_allocated) { |
1851 | 0 | if(extids != NULL) |
1852 | 0 | g_hash_table_destroy(extids); |
1853 | 0 | } |
1854 | | |
1855 | | /* Done */ |
1856 | 0 | va_end(args); |
1857 | |
|
1858 | 0 | return 0; |
1859 | 0 | } |
1860 | | |
1861 | 0 | janus_sdp *janus_sdp_generate_answer(janus_sdp *offer) { |
1862 | 0 | if(offer == NULL) |
1863 | 0 | return NULL; |
1864 | | |
1865 | 0 | janus_refcount_increase(&offer->ref); |
1866 | | /* Create an SDP answer, and start by copying some of the headers */ |
1867 | 0 | janus_sdp *answer = g_malloc(sizeof(janus_sdp)); |
1868 | 0 | g_atomic_int_set(&answer->destroyed, 0); |
1869 | 0 | janus_refcount_init(&answer->ref, janus_sdp_free); |
1870 | 0 | answer->version = offer->version; |
1871 | 0 | answer->o_name = g_strdup(offer->o_name ? offer->o_name : "-"); |
1872 | 0 | answer->o_sessid = offer->o_sessid; |
1873 | 0 | answer->o_version = offer->o_version; |
1874 | 0 | answer->o_ipv4 = offer->o_ipv4; |
1875 | 0 | answer->o_addr = g_strdup(offer->o_addr ? offer->o_addr : "127.0.0.1"); |
1876 | 0 | answer->s_name = g_strdup(offer->s_name ? offer->s_name : "Janus session"); |
1877 | 0 | answer->t_start = 0; |
1878 | 0 | answer->t_stop = 0; |
1879 | 0 | answer->c_ipv4 = offer->c_ipv4; |
1880 | 0 | answer->c_addr = g_strdup(offer->c_addr ? offer->c_addr : "127.0.0.1"); |
1881 | 0 | answer->attributes = NULL; |
1882 | 0 | answer->m_lines = NULL; |
1883 | | |
1884 | | /* Iterate on all m-lines to add, if any */ |
1885 | 0 | GList *temp = offer->m_lines; |
1886 | 0 | while(temp) { |
1887 | 0 | janus_sdp_mline *m = (janus_sdp_mline *)temp->data; |
1888 | | /* For each m-line we parse, we'll need a corresponding one in the answer */ |
1889 | 0 | janus_sdp_mline *am = g_malloc0(sizeof(janus_sdp_mline)); |
1890 | 0 | janus_refcount_init(&am->ref, janus_sdp_mline_free); |
1891 | 0 | am->index = m->index; |
1892 | 0 | am->type = m->type; |
1893 | 0 | am->type_str = m->type_str ? g_strdup(m->type_str) : NULL; |
1894 | 0 | am->proto = g_strdup(m->proto ? m->proto : "UDP/TLS/RTP/SAVPF"); |
1895 | 0 | am->c_ipv4 = m->c_ipv4; |
1896 | 0 | am->c_addr = g_strdup(am->c_addr ? am->c_addr : "127.0.0.1"); |
1897 | | /* We reject the media line by default, but this can be changed later */ |
1898 | 0 | am->port = 0; |
1899 | 0 | am->direction = JANUS_SDP_INACTIVE; |
1900 | 0 | am->ptypes = g_list_append(am->ptypes, GINT_TO_POINTER(0)); |
1901 | 0 | if(am->type == JANUS_SDP_APPLICATION) { |
1902 | 0 | GList *fmt = m->fmts; |
1903 | 0 | while(fmt) { |
1904 | 0 | char *fmt_str = (char *)fmt->data; |
1905 | 0 | if(fmt_str) |
1906 | 0 | am->fmts = g_list_append(am->fmts, g_strdup(fmt_str)); |
1907 | 0 | fmt = fmt->next; |
1908 | 0 | } |
1909 | 0 | } |
1910 | | /* Append to the list of m-lines in the answer */ |
1911 | 0 | answer->m_lines = g_list_append(answer->m_lines, am); |
1912 | 0 | temp = temp->next; |
1913 | 0 | } |
1914 | 0 | janus_refcount_decrease(&offer->ref); |
1915 | | |
1916 | | /* Done*/ |
1917 | 0 | return answer; |
1918 | 0 | } |
1919 | | |
1920 | 0 | int janus_sdp_generate_answer_mline(janus_sdp *offer, janus_sdp *answer, janus_sdp_mline *offered, ...) { |
1921 | 0 | if(answer == NULL || offered == NULL) |
1922 | 0 | return -1; |
1923 | | |
1924 | 0 | janus_refcount_increase(&offer->ref); |
1925 | 0 | janus_refcount_increase(&answer->ref); |
1926 | | /* This method has a variable list of arguments, telling us how we should respond */ |
1927 | 0 | va_list args; |
1928 | 0 | va_start(args, offered); |
1929 | | |
1930 | | /* Let's see what we should do with the media */ |
1931 | 0 | gboolean mline_enabled = TRUE; |
1932 | 0 | janus_sdp_mtype type = JANUS_SDP_OTHER; |
1933 | 0 | gboolean audio_dtmf = FALSE, audio_opusred = FALSE, video_rtcpfb = TRUE; |
1934 | 0 | const char *codec = NULL, *msid = NULL, *mstid = NULL, |
1935 | 0 | *fmtp = NULL, *vp9_profile = NULL, *h264_profile = NULL; |
1936 | 0 | char *custom_audio_fmtp = NULL; |
1937 | 0 | GList *extmaps = NULL; |
1938 | 0 | gboolean twcc = FALSE; |
1939 | 0 | janus_sdp_mdirection mdir = JANUS_SDP_DEFAULT; |
1940 | 0 | int property = va_arg(args, int); |
1941 | 0 | if(property != JANUS_SDP_OA_MLINE) { |
1942 | | /* The first attribute MUST be JANUS_SDP_OA_MLINE */ |
1943 | 0 | JANUS_LOG(LOG_ERR, "First attribute is not JANUS_SDP_OA_MLINE\n"); |
1944 | 0 | va_end(args); |
1945 | 0 | janus_refcount_decrease(&offer->ref); |
1946 | 0 | janus_refcount_decrease(&answer->ref); |
1947 | 0 | return -2; |
1948 | 0 | } |
1949 | 0 | type = va_arg(args, int); |
1950 | 0 | if(type != JANUS_SDP_AUDIO && type != JANUS_SDP_VIDEO && type != JANUS_SDP_APPLICATION) { |
1951 | | /* Unsupported m-line type */ |
1952 | 0 | JANUS_LOG(LOG_ERR, "Invalid m-line type\n"); |
1953 | 0 | va_end(args); |
1954 | 0 | janus_refcount_decrease(&offer->ref); |
1955 | 0 | janus_refcount_decrease(&answer->ref); |
1956 | 0 | return -3; |
1957 | 0 | } |
1958 | | |
1959 | | /* Let's see what we should do with the media to add */ |
1960 | 0 | property = va_arg(args, int); |
1961 | 0 | while(property != JANUS_SDP_OA_DONE) { |
1962 | 0 | if(property == JANUS_SDP_OA_ENABLED) { |
1963 | 0 | mline_enabled = va_arg(args, gboolean); |
1964 | 0 | } else if(property == JANUS_SDP_OA_DIRECTION) { |
1965 | 0 | mdir = va_arg(args, janus_sdp_mdirection); |
1966 | 0 | } else if(property == JANUS_SDP_OA_CODEC) { |
1967 | 0 | codec = va_arg(args, char *); |
1968 | 0 | } else if(property == JANUS_SDP_OA_MSID) { |
1969 | 0 | msid = va_arg(args, char *); |
1970 | 0 | mstid = va_arg(args, char *); |
1971 | 0 | } else if(property == JANUS_SDP_OA_FMTP) { |
1972 | 0 | fmtp = va_arg(args, char *); |
1973 | 0 | } else if(property == JANUS_SDP_OA_VP9_PROFILE) { |
1974 | 0 | vp9_profile = va_arg(args, char *); |
1975 | 0 | } else if(property == JANUS_SDP_OA_H264_PROFILE) { |
1976 | 0 | h264_profile = va_arg(args, char *); |
1977 | 0 | } else if(property == JANUS_SDP_OA_AUDIO_DTMF) { |
1978 | 0 | audio_dtmf = va_arg(args, gboolean); |
1979 | 0 | } else if(property == JANUS_SDP_OA_VIDEO_RTCPFB_DEFAULTS) { |
1980 | 0 | video_rtcpfb = va_arg(args, gboolean); |
1981 | 0 | } else if(property == JANUS_SDP_OA_ACCEPT_EXTMAP) { |
1982 | 0 | const char *extension = va_arg(args, char *); |
1983 | 0 | if(extension != NULL) { |
1984 | 0 | extmaps = g_list_append(extmaps, (char *)extension); |
1985 | 0 | if(!strcasecmp(extension, JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC)) { |
1986 | | /* Take note of the fact we may negotiate TWCC for this m-line */ |
1987 | 0 | twcc = TRUE; |
1988 | 0 | } |
1989 | 0 | } |
1990 | 0 | } else if(property == JANUS_SDP_OA_ACCEPT_OPUSRED) { |
1991 | 0 | audio_opusred = va_arg(args, gboolean); |
1992 | 0 | } else { |
1993 | 0 | JANUS_LOG(LOG_WARN, "Unknown property %d for preparing SDP answer, ignoring...\n", property); |
1994 | 0 | } |
1995 | 0 | property = va_arg(args, int); |
1996 | 0 | } |
1997 | | |
1998 | | /* Iterate on all m-lines to add, if any, to find the one with the same index */ |
1999 | 0 | GList *temp = answer->m_lines; |
2000 | 0 | while(temp) { |
2001 | 0 | janus_sdp_mline *am = (janus_sdp_mline *)temp->data; |
2002 | 0 | if(am->index != offered->index) { |
2003 | 0 | temp = temp->next; |
2004 | 0 | continue; |
2005 | 0 | } |
2006 | | /* When answering, m-lines are disabled by default */ |
2007 | 0 | am->direction = JANUS_SDP_INACTIVE; |
2008 | 0 | g_list_free(am->ptypes); |
2009 | 0 | am->ptypes = NULL; |
2010 | 0 | g_list_free_full(am->fmts, (GDestroyNotify)g_free); |
2011 | 0 | am->fmts = NULL; |
2012 | 0 | g_list_free_full(am->attributes, (GDestroyNotify)janus_sdp_attribute_destroy); |
2013 | 0 | am->attributes = NULL; |
2014 | 0 | if(!mline_enabled) { |
2015 | 0 | am->ptypes = g_list_append(am->ptypes, GINT_TO_POINTER(0)); |
2016 | 0 | if(am->type == JANUS_SDP_APPLICATION) { |
2017 | 0 | GList *fmt = offered->fmts; |
2018 | 0 | while(fmt) { |
2019 | 0 | char *fmt_str = (char *)fmt->data; |
2020 | 0 | if(fmt_str) |
2021 | 0 | am->fmts = g_list_append(am->fmts, g_strdup(fmt_str)); |
2022 | 0 | fmt = fmt->next; |
2023 | 0 | } |
2024 | 0 | } |
2025 | 0 | break; |
2026 | 0 | } |
2027 | 0 | am->port = 9; |
2028 | 0 | if(am->type == JANUS_SDP_AUDIO || am->type == JANUS_SDP_VIDEO) { |
2029 | | /* What is the direction we were offered? And how were we asked to react? |
2030 | | * Adapt the direction in our answer accordingly */ |
2031 | 0 | switch(offered->direction) { |
2032 | 0 | case JANUS_SDP_RECVONLY: |
2033 | 0 | if(mdir == JANUS_SDP_SENDRECV || mdir == JANUS_SDP_DEFAULT || mdir == JANUS_SDP_SENDONLY) { |
2034 | | /* Peer is recvonly, we'll only send */ |
2035 | 0 | am->direction = JANUS_SDP_SENDONLY; |
2036 | 0 | } else { |
2037 | | /* Peer is recvonly, but we're not ok to send, so reply with inactive */ |
2038 | 0 | JANUS_LOG(LOG_WARN, "%s offered as '%s', but we need '%s' for us: using 'inactive'\n", |
2039 | 0 | am->type == JANUS_SDP_AUDIO ? "Audio" : "Video", |
2040 | 0 | janus_sdp_mdirection_str(offered->direction), janus_sdp_mdirection_str(mdir)); |
2041 | 0 | am->direction = JANUS_SDP_INACTIVE; |
2042 | 0 | } |
2043 | 0 | break; |
2044 | 0 | case JANUS_SDP_SENDONLY: |
2045 | 0 | if(mdir == JANUS_SDP_SENDRECV || mdir == JANUS_SDP_DEFAULT || mdir == JANUS_SDP_RECVONLY) { |
2046 | | /* Peer is sendonly, we'll only receive */ |
2047 | 0 | am->direction = JANUS_SDP_RECVONLY; |
2048 | 0 | } else { |
2049 | | /* Peer is sendonly, but we're not ok to receive, so reply with inactive */ |
2050 | 0 | JANUS_LOG(LOG_WARN, "%s offered as '%s', but we need '%s' for us: using 'inactive'\n", |
2051 | 0 | am->type == JANUS_SDP_AUDIO ? "Audio" : "Video", |
2052 | 0 | janus_sdp_mdirection_str(offered->direction), janus_sdp_mdirection_str(mdir)); |
2053 | 0 | am->direction = JANUS_SDP_INACTIVE; |
2054 | 0 | } |
2055 | 0 | break; |
2056 | 0 | case JANUS_SDP_INACTIVE: |
2057 | | /* Peer inactive, set inactive in the answer to */ |
2058 | 0 | am->direction = JANUS_SDP_INACTIVE; |
2059 | 0 | break; |
2060 | 0 | case JANUS_SDP_SENDRECV: |
2061 | 0 | default: |
2062 | | /* The peer is fine with everything, so use our constraint */ |
2063 | 0 | am->direction = mdir; |
2064 | 0 | break; |
2065 | 0 | } |
2066 | | /* Look for the right codec and stick to that */ |
2067 | 0 | if(codec == NULL) { |
2068 | | /* FIXME User didn't provide a codec to accept? Let's see if Opus (for audio) |
2069 | | * of VP8 (for video) were negotiated: if so, use them, otherwise let's |
2070 | | * pick some other codec we know about among the ones that were offered. |
2071 | | * Notice that if it's not a codec we understand, we reject the medium, |
2072 | | * as browsers would reject it anyway. If you need more flexibility you'll |
2073 | | * have to generate an answer yourself, rather than automatically... */ |
2074 | 0 | codec = am->type == JANUS_SDP_AUDIO ? "opus" : "vp8"; |
2075 | 0 | if(janus_sdp_get_codec_pt(offer, offered->index, codec) < 0) { |
2076 | | /* We couldn't find our preferred codec, let's try something else */ |
2077 | 0 | if(am->type == JANUS_SDP_AUDIO) { |
2078 | | /* Opus not found, maybe mu-law? */ |
2079 | 0 | codec = "pcmu"; |
2080 | 0 | if(janus_sdp_get_codec_pt(offer, offered->index, codec) < 0) { |
2081 | | /* mu-law not found, maybe a-law? */ |
2082 | 0 | codec = "pcma"; |
2083 | 0 | if(janus_sdp_get_codec_pt(offer, offered->index, codec) < 0) { |
2084 | | /* a-law not found, maybe G.722? */ |
2085 | 0 | codec = "g722"; |
2086 | 0 | if(janus_sdp_get_codec_pt(offer, offered->index, codec) < 0) { |
2087 | | /* G.722 not found, maybe isac32? */ |
2088 | 0 | codec = "isac32"; |
2089 | 0 | if(janus_sdp_get_codec_pt(offer, offered->index, codec) < 0) { |
2090 | | /* isac32 not found, maybe isac16? */ |
2091 | 0 | codec = "isac16"; |
2092 | 0 | if(janus_sdp_get_codec_pt(offer, offered->index, codec) < 0) { |
2093 | | /* isac16 not found, maybe multiopus? */ |
2094 | 0 | codec = "multiopus"; |
2095 | 0 | if(janus_sdp_get_codec_pt(offer, offered->index, codec) < 0) { |
2096 | | /* multiopus not found, maybe L16/48000? */ |
2097 | 0 | codec = "l16-48"; |
2098 | 0 | if(janus_sdp_get_codec_pt(offer, offered->index, codec) < 0) { |
2099 | | /* L16/48000 not found, maybe L16/16000? */ |
2100 | 0 | codec = "l16"; |
2101 | 0 | } |
2102 | 0 | } |
2103 | 0 | } |
2104 | 0 | } |
2105 | 0 | } |
2106 | 0 | } |
2107 | 0 | } |
2108 | 0 | } else { |
2109 | | /* VP8 not found, maybe VP9? */ |
2110 | 0 | codec = "vp9"; |
2111 | 0 | if(janus_sdp_get_codec_pt(offer, offered->index, codec) < 0) { |
2112 | | /* VP9 not found either, maybe H.264? */ |
2113 | 0 | codec = "h264"; |
2114 | 0 | if(janus_sdp_get_codec_pt(offer, offered->index, codec) < 0) { |
2115 | | /* H.264 not found either, maybe AV1? */ |
2116 | 0 | codec = "av1"; |
2117 | 0 | if(janus_sdp_get_codec_pt(offer, offered->index, codec) < 0) { |
2118 | | /* AV1 not found either, maybe H.265? */ |
2119 | 0 | codec = "h265"; |
2120 | 0 | } |
2121 | 0 | } |
2122 | 0 | } |
2123 | 0 | } |
2124 | 0 | } |
2125 | 0 | } |
2126 | 0 | const char *video_profile = NULL; |
2127 | 0 | if(codec && !strcasecmp(codec, "vp9")) |
2128 | 0 | video_profile = vp9_profile; |
2129 | 0 | else if(codec && !strcasecmp(codec, "h264")) |
2130 | 0 | video_profile = h264_profile; |
2131 | 0 | int pt = janus_sdp_get_codec_pt_full(offer, offered->index, codec, video_profile); |
2132 | 0 | if(pt < 0) { |
2133 | | /* Reject */ |
2134 | 0 | JANUS_LOG(LOG_WARN, "Couldn't find codec we needed (%s) in the offer, rejecting %s\n", |
2135 | 0 | codec, am->type == JANUS_SDP_AUDIO ? "audio" : "video"); |
2136 | 0 | am->port = 0; |
2137 | 0 | am->direction = JANUS_SDP_INACTIVE; |
2138 | 0 | am->ptypes = g_list_append(am->ptypes, GINT_TO_POINTER(0)); |
2139 | 0 | break; |
2140 | 0 | } |
2141 | 0 | if(am->type == JANUS_SDP_AUDIO && !strcasecmp(codec, "multiopus") && |
2142 | 0 | (fmtp == NULL || strstr(fmtp, "channel_mapping") == NULL)) { |
2143 | | /* Missing channel mapping for the multiopus m-line, check the offer */ |
2144 | 0 | GList *mo = offered->attributes; |
2145 | 0 | while(mo) { |
2146 | 0 | janus_sdp_attribute *a = (janus_sdp_attribute *)mo->data; |
2147 | 0 | if(a->name && strstr(a->name, "fmtp") && a->value) { |
2148 | 0 | char *tmp = strchr(a->value, ' '); |
2149 | 0 | if(tmp && strlen(tmp) > 1 && custom_audio_fmtp == NULL) { |
2150 | 0 | tmp++; |
2151 | 0 | custom_audio_fmtp = g_strdup(tmp); |
2152 | | /* FIXME We should integrate the existing audio_fmtp */ |
2153 | 0 | } |
2154 | 0 | break; |
2155 | 0 | } |
2156 | 0 | mo = mo->next; |
2157 | 0 | } |
2158 | 0 | } |
2159 | 0 | am->ptypes = g_list_append(am->ptypes, GINT_TO_POINTER(pt)); |
2160 | | /* Any msid we should set? */ |
2161 | 0 | if(msid != NULL && mstid != NULL) { |
2162 | 0 | janus_sdp_attribute *a = janus_sdp_attribute_create("msid", "%s %s", msid, mstid); |
2163 | 0 | am->attributes = g_list_append(am->attributes, a); |
2164 | 0 | } |
2165 | | /* Before moving to the attributes, check if the Trasport |
2166 | | * Wide CC RTP extension was negotiated */ |
2167 | 0 | if(extmaps != NULL && twcc) { |
2168 | | /* We're trying to accept TWCC, but make sure it was offered */ |
2169 | 0 | twcc = FALSE; |
2170 | 0 | GList *ma = offered->attributes; |
2171 | 0 | while(ma) { |
2172 | 0 | janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data; |
2173 | 0 | if(a->name && strstr(a->name, "extmap") && a->value && |
2174 | 0 | strstr(a->value, JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC) != NULL) { |
2175 | | /* Take note of the fact we'll have to negotiate TWCC for this m-line */ |
2176 | 0 | twcc = TRUE; |
2177 | 0 | break; |
2178 | 0 | } |
2179 | 0 | ma = ma->next; |
2180 | 0 | } |
2181 | 0 | } |
2182 | | /* Add the related attributes */ |
2183 | 0 | if(am->type == JANUS_SDP_AUDIO) { |
2184 | | /* Add rtpmap attribute */ |
2185 | 0 | int opusred_pt = -1; |
2186 | 0 | const char *codec_rtpmap = janus_sdp_get_codec_rtpmap(codec); |
2187 | 0 | janus_sdp_attribute *a = NULL; |
2188 | 0 | if(codec_rtpmap) { |
2189 | | /* If we're supposed to negotiate opus/red as well, check if it's there */ |
2190 | 0 | if(!strcasecmp(codec, "opus") && audio_opusred) { |
2191 | 0 | opusred_pt = janus_sdp_get_opusred_pt(offer, am->index); |
2192 | 0 | if(opusred_pt > 0) { |
2193 | | /* Add rtpmap attribute for opus/red too */ |
2194 | 0 | am->ptypes = g_list_prepend(am->ptypes, GINT_TO_POINTER(opusred_pt)); |
2195 | 0 | a = janus_sdp_attribute_create("rtpmap", "%d red/48000/2", opusred_pt); |
2196 | 0 | am->attributes = g_list_append(am->attributes, a); |
2197 | 0 | } |
2198 | 0 | } |
2199 | 0 | a = janus_sdp_attribute_create("rtpmap", "%d %s", pt, codec_rtpmap); |
2200 | 0 | am->attributes = g_list_append(am->attributes, a); |
2201 | | /* Check if we need to add a payload type for DTMF tones (telephone-event/8000) */ |
2202 | 0 | if(audio_dtmf) { |
2203 | 0 | int dtmf_pt = janus_sdp_get_codec_pt(offer, am->index, "dtmf"); |
2204 | 0 | if(dtmf_pt >= 0) { |
2205 | | /* We do */ |
2206 | 0 | am->ptypes = g_list_append(am->ptypes, GINT_TO_POINTER(dtmf_pt)); |
2207 | 0 | a = janus_sdp_attribute_create("rtpmap", "%d %s", dtmf_pt, janus_sdp_get_codec_rtpmap("dtmf")); |
2208 | 0 | am->attributes = g_list_append(am->attributes, a); |
2209 | 0 | } |
2210 | 0 | } |
2211 | | /* If we're negotiating opus/red, add an fmtp line for that */ |
2212 | 0 | if(audio_opusred && opusred_pt > 0) { |
2213 | 0 | a = janus_sdp_attribute_create("fmtp", "%d %d/%d", opusred_pt, pt, pt); |
2214 | 0 | am->attributes = g_list_append(am->attributes, a); |
2215 | 0 | } |
2216 | | /* Check if there's a custom fmtp line to add for audio |
2217 | | * FIXME We should actually check if it matches the offer */ |
2218 | 0 | if(fmtp || custom_audio_fmtp) { |
2219 | 0 | a = janus_sdp_attribute_create("fmtp", "%d %s", |
2220 | 0 | pt, custom_audio_fmtp ? custom_audio_fmtp : fmtp); |
2221 | 0 | am->attributes = g_list_append(am->attributes, a); |
2222 | 0 | } |
2223 | 0 | if(twcc) { |
2224 | | /* Add the transport-wide RTCP feedback message here, since the header extension was negotiated */ |
2225 | 0 | a = janus_sdp_attribute_create("rtcp-fb", "%d transport-cc", pt); |
2226 | 0 | am->attributes = g_list_append(am->attributes, a); |
2227 | 0 | } |
2228 | 0 | } |
2229 | 0 | } else { |
2230 | | /* Add rtpmap attribute */ |
2231 | 0 | const char *codec_rtpmap = janus_sdp_get_codec_rtpmap(codec); |
2232 | 0 | janus_sdp_attribute *a = NULL; |
2233 | 0 | if(codec_rtpmap) { |
2234 | 0 | a = janus_sdp_attribute_create("rtpmap", "%d %s", pt, codec_rtpmap); |
2235 | 0 | am->attributes = g_list_append(am->attributes, a); |
2236 | 0 | if(video_rtcpfb) { |
2237 | | /* Add rtcp-fb attributes */ |
2238 | 0 | a = janus_sdp_attribute_create("rtcp-fb", "%d ccm fir", pt); |
2239 | 0 | am->attributes = g_list_append(am->attributes, a); |
2240 | 0 | a = janus_sdp_attribute_create("rtcp-fb", "%d nack", pt); |
2241 | 0 | am->attributes = g_list_append(am->attributes, a); |
2242 | 0 | a = janus_sdp_attribute_create("rtcp-fb", "%d nack pli", pt); |
2243 | 0 | am->attributes = g_list_append(am->attributes, a); |
2244 | 0 | a = janus_sdp_attribute_create("rtcp-fb", "%d goog-remb", pt); |
2245 | 0 | am->attributes = g_list_append(am->attributes, a); |
2246 | 0 | } |
2247 | 0 | if(twcc) { |
2248 | | /* Add the transport-wide RTCP feedback message here, since the header extension was negotiated */ |
2249 | 0 | a = janus_sdp_attribute_create("rtcp-fb", "%d transport-cc", pt); |
2250 | 0 | am->attributes = g_list_append(am->attributes, a); |
2251 | 0 | } |
2252 | 0 | } |
2253 | 0 | if(!strcasecmp(codec, "vp9") && vp9_profile) { |
2254 | | /* Add a profile-id fmtp attribute */ |
2255 | 0 | a = janus_sdp_attribute_create("fmtp", "%d profile-id=%s", pt, vp9_profile); |
2256 | 0 | am->attributes = g_list_append(am->attributes, a); |
2257 | 0 | } else if(!strcasecmp(codec, "h264") && h264_profile) { |
2258 | | /* Add a profile-level-id fmtp attribute */ |
2259 | 0 | a = janus_sdp_attribute_create("fmtp", "%d profile-level-id=%s;packetization-mode=1", pt, h264_profile); |
2260 | 0 | am->attributes = g_list_append(am->attributes, a); |
2261 | 0 | } else if(fmtp) { |
2262 | | /* There's a custom fmtp line to add for video |
2263 | | * FIXME We should actually check if it matches the offer */ |
2264 | 0 | a = janus_sdp_attribute_create("fmtp", "%d %s", pt, fmtp); |
2265 | 0 | am->attributes = g_list_append(am->attributes, a); |
2266 | 0 | } |
2267 | 0 | } |
2268 | | /* Add the extmap attributes, if needed */ |
2269 | 0 | if(extmaps != NULL) { |
2270 | 0 | GList *ma = offered->attributes; |
2271 | 0 | while(ma) { |
2272 | | /* Iterate on all attributes, to see if there's an extension to accept */ |
2273 | 0 | janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data; |
2274 | 0 | if(a->name && strstr(a->name, "extmap") && a->value) { |
2275 | 0 | GList *emtemp = extmaps; |
2276 | 0 | while(emtemp != NULL) { |
2277 | 0 | char *extension = (char *)emtemp->data; |
2278 | 0 | if(strstr(a->value, extension)) { |
2279 | | /* Accept the extension */ |
2280 | 0 | int id = atoi(a->value); |
2281 | 0 | if(id < 0) { |
2282 | 0 | JANUS_LOG(LOG_ERR, "Invalid extension ID (%d)\n", id); |
2283 | 0 | emtemp = emtemp->next; |
2284 | 0 | continue; |
2285 | 0 | } |
2286 | | |
2287 | 0 | if(strstr(a->value, JANUS_RTP_EXTMAP_DEPENDENCY_DESC) && |
2288 | 0 | strcasecmp(codec, "av1") && strcasecmp(codec, "vp9")) { |
2289 | | /* Don't negotiate the Dependency Descriptor extension, |
2290 | | * unless we're doing AV1 or VP9 for SVC. See for ref: |
2291 | | * https://issues.webrtc.org/issues/42226269 */ |
2292 | 0 | emtemp = emtemp->next; |
2293 | 0 | continue; |
2294 | 0 | } |
2295 | 0 | const char *direction = NULL; |
2296 | 0 | switch(a->direction) { |
2297 | 0 | case JANUS_SDP_SENDONLY: |
2298 | 0 | direction = "/recvonly"; |
2299 | 0 | break; |
2300 | 0 | case JANUS_SDP_RECVONLY: |
2301 | 0 | direction = "/sendonly"; |
2302 | 0 | break; |
2303 | 0 | case JANUS_SDP_INACTIVE: |
2304 | 0 | direction = "/inactive"; |
2305 | 0 | break; |
2306 | 0 | default: |
2307 | 0 | direction = ""; |
2308 | 0 | break; |
2309 | 0 | } |
2310 | 0 | a = janus_sdp_attribute_create("extmap", |
2311 | 0 | "%d%s %s", id, direction, extension); |
2312 | 0 | janus_sdp_attribute_add_to_mline(am, a); |
2313 | 0 | } |
2314 | 0 | emtemp = emtemp->next; |
2315 | 0 | } |
2316 | 0 | } else if(am->type == JANUS_SDP_VIDEO && a->name && strstr(a->name, "fmtp") && |
2317 | 0 | a->value && atoi(a->value) == pt) { |
2318 | | /* Check if we need to copy the fmtp attribute too */ |
2319 | 0 | if(((!strcasecmp(codec, "vp8") && fmtp == NULL)) || |
2320 | 0 | ((!strcasecmp(codec, "vp9") && vp9_profile == NULL && fmtp == NULL)) || |
2321 | 0 | ((!strcasecmp(codec, "h264") && h264_profile == NULL && fmtp == NULL))) { |
2322 | | /* FIXME Copy the fmtp attribute (we should check if we support it) */ |
2323 | 0 | a = janus_sdp_attribute_create("fmtp", "%s", a->value); |
2324 | 0 | janus_sdp_attribute_add_to_mline(am, a); |
2325 | 0 | } |
2326 | 0 | } |
2327 | 0 | ma = ma->next; |
2328 | 0 | } |
2329 | 0 | } |
2330 | 0 | } else { |
2331 | | /* This is for data, add formats and an sctpmap attribute */ |
2332 | 0 | am->direction = JANUS_SDP_DEFAULT; |
2333 | 0 | GList *fmt = offered->fmts; |
2334 | 0 | while(fmt) { |
2335 | 0 | char *fmt_str = (char *)fmt->data; |
2336 | 0 | if(fmt_str) |
2337 | 0 | am->fmts = g_list_append(am->fmts, g_strdup(fmt_str)); |
2338 | 0 | fmt = fmt->next; |
2339 | 0 | } |
2340 | 0 | } |
2341 | | /* Nothing else we need to do */ |
2342 | 0 | break; |
2343 | 0 | } |
2344 | 0 | janus_refcount_decrease(&offer->ref); |
2345 | 0 | janus_refcount_decrease(&answer->ref); |
2346 | | |
2347 | | /* Done */ |
2348 | 0 | g_list_free(extmaps); |
2349 | 0 | g_free(custom_audio_fmtp); |
2350 | 0 | va_end(args); |
2351 | |
|
2352 | 0 | return 0; |
2353 | 0 | } |