Coverage Report

Created: 2026-05-16 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}