stream-deploy/ZLM/3rdpart/media-server/librtsp/source/sdp.c

1823 lines
36 KiB
C++

#include "sdp.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#if defined(OS_WINDOWS)
#define strdup _strdup
#define strcasecmp _stricmp
#endif
#define N_EMAIL 1
#define N_PHONE 1
#define N_CONNECTION 1
#define N_BANDWIDTH 1
#define N_TIMING 1
#define N_REPEAT 1
#define N_TIMEZONE 1
#define N_REPEAT_OFFSET 1
#define N_ATTRIBUTE 5
#define N_MEDIA 3 // audio/video/whiteboard
#define N_MEDIA_FORMAT 5
struct sdp_connection
{
char* network;
char* addrtype;
char* address;
};
struct sdp_origin
{
char* username;
char* session;
char* session_version;
struct sdp_connection c;
};
struct sdp_email
{
char* email;
};
struct sdp_phone
{
char* phone;
};
struct sdp_bandwidth
{
char* bwtype;
char* bandwidth;
};
struct bandwidths
{
int count;
int capacity;
struct sdp_bandwidth bandwidths[N_BANDWIDTH];
struct sdp_bandwidth *ptr;
};
struct sdp_repeat
{
char* interval;
char* duration;
struct offset
{
int count;
int capacity;
char *offsets[N_REPEAT_OFFSET];
char **ptr;
} offsets;
};
struct sdp_timezone
{
char* time;
char* offset;
};
struct sdp_timing
{
char* start;
char* stop;
struct repeat
{
int count;
int capacity;
struct sdp_repeat repeats[N_REPEAT];
struct sdp_repeat *ptr;
} r;
struct timezone_t
{
int count;
int capacity;
struct sdp_timezone timezones[N_TIMEZONE];
struct sdp_timezone *ptr;
} z;
};
struct sdp_encryption
{
char* method;
char* key;
};
struct sdp_attribute
{
char* name;
char* value;
};
struct attributes
{
int count;
int capacity;
struct sdp_attribute attrs[N_ATTRIBUTE];
struct sdp_attribute *ptr;
};
struct sdp_media
{
char* media; //audio, video, text, application, message
char* port;
char* proto; // udp, RTP/AVP, RTP/SAVP
struct format
{
int count;
int capacity;
char *formats[N_MEDIA_FORMAT];
char **ptr;
} fmt;
char* i;
struct connection
{
int count;
int capacity;
struct sdp_connection connections[N_EMAIL];
struct sdp_connection *ptr;
} c;
struct attributes a;
struct bandwidths b;
struct sdp_encryption k;
};
struct sdp_t
{
char *raw; // raw source string
int offset; // parse offset
int v;
struct sdp_origin o;
char* s;
char* i;
char* u;
struct email
{
int count;
int capacity;
struct sdp_email emails[N_EMAIL];
struct sdp_email *ptr;
} e;
struct phone
{
int count;
int capacity;
struct sdp_phone phones[N_PHONE];
struct sdp_phone *ptr;
} p;
struct sdp_connection c;
struct bandwidths b;
struct timing
{
int count;
int capacity;
struct sdp_timing times[N_TIMING];
struct sdp_timing *ptr;
} t;
struct sdp_encryption k;
struct attributes a;
struct media
{
int count;
int capacity;
struct sdp_media medias[N_MEDIA];
struct sdp_media *ptr;
} m;
};
static inline void sdp_skip_space(struct sdp_t* sdp)
{
char c = sdp->raw[sdp->offset];
while(' ' == c || '\t' == c)
c = sdp->raw[++sdp->offset];
}
static inline int sdp_token_word(struct sdp_t* sdp, const char* escape)
{
int n = sdp->offset;
sdp->offset += strcspn(sdp->raw + sdp->offset, escape);
return sdp->offset - n;
}
static inline int sdp_token_crlf(struct sdp_t* sdp)
{
int i;
sdp_skip_space(sdp);
for(i = 0; '\r' == sdp->raw[sdp->offset] || '\n' == sdp->raw[sdp->offset]; i++)
++sdp->offset;
// sdp end line
if('\0' == sdp->raw[sdp->offset])
return 0;
return i > 0 ? 0 : -1;
}
static inline void trim_right(const char* s, int *len)
{
//// left trim
//while(*len > 0 && isspace(s[*pos]))
//{
// --*len;
// ++*pos;
//}
// right trim
while(*len > 0 && ' '==(s[*len - 1]))
{
--*len;
}
}
static inline struct sdp_timing* sdp_get_timing(struct sdp_t* sdp, int idx)
{
if(idx >= sdp->t.count)
return NULL;
return idx >= N_TIMING ? &sdp->t.ptr[idx - N_TIMING] : &sdp->t.times[idx];
}
static inline struct sdp_media* sdp_get_media(struct sdp_t* sdp, int idx)
{
if(idx >= sdp->m.count)
return NULL;
return idx >= N_MEDIA ? &sdp->m.ptr[idx - N_MEDIA] : &sdp->m.medias[idx];
}
// RFC4566 5.1
static int sdp_parse_version(struct sdp_t* sdp)
{
char c;
assert(sdp);
if(!sdp) return -1;
sdp->v = 0;
sdp_skip_space(sdp);
c = sdp->raw[sdp->offset];
while('0' <= c && c <= '9')
{
sdp->v = sdp->v * 10 + (c - '0');
c = sdp->raw[++sdp->offset];
}
return sdp_token_crlf(sdp);
}
// RFC4566 5.2
// o=<username> <sess-id> <sess-version> <nettype> <addrtype> <unicast-address>
// <username> "-" if the originating host does not support the concept of user IDs.
// <sess-id> is a numeric string
// <sess-version> is a version number for this session description
// <nettype> IN
// <addrtype> IP4/IP6
static int sdp_parse_origin(struct sdp_t* sdp)
{
char* v[6];
char** vv[6];
int i, j, n[6];
struct sdp_origin *o;
o = &sdp->o;
memset(o, 0, sizeof(struct sdp_origin));
memset(n, 0, sizeof(n));
o->username = (char*)"-"; // default value
o->c.network = (char*)"IN";
o->c.addrtype = (char*)"IP4";
sdp_skip_space(sdp);
for (i = 0; i < 6 && sdp->raw[sdp->offset] && !strchr("\r\n", sdp->raw[sdp->offset]); i++)
{
v[i] = sdp->raw + sdp->offset;
n[i] = sdp_token_word(sdp, " \t\r\n");
sdp_skip_space(sdp);
}
sdp_token_crlf(sdp);
//if (i < 5)
// return 0;
vv[0] = &o->username;
vv[1] = &o->session;
vv[2] = &o->session_version;
vv[3] = &o->c.network;
vv[4] = &o->c.addrtype;
vv[5] = &o->c.address;
for (j = 0; j < i; j++)
{
v[j][n[j]] = '\0';
*vv[j] = v[j];
}
return 0;
}
// RFC4566 5.3
// s=<session name>
// There MUST be one and only one "s=" field per session description. can be empty
static int sdp_parse_session(struct sdp_t* sdp)
{
int n = 0;
sdp->s = sdp->raw + sdp->offset;
n = sdp_token_word(sdp, "\r\n");
if(0 != sdp_token_crlf(sdp))
return -1;
sdp->s[n] = '\0';
return 0;
}
// RFC4566 5.4
// i=<session description>
// There MUST be at most one session-level "i=" field per session description,
// and at most one "i=" field per media.
// default UTF-8
static int sdp_parse_information(struct sdp_t* sdp)
{
int n = 0;
char **i;
if(sdp->m.count > 0)
{
i = &sdp_get_media(sdp, sdp->m.count-1)->i;
}
else
{
i = &sdp->i;
}
*i = sdp->raw + sdp->offset;
n = sdp_token_word(sdp, "\r\n");
if(0 != sdp_token_crlf(sdp))
return -1;
(*i)[n] = '\0';
return 0;
}
// RFC4566 5.5
// u=<uri>
// This field is OPTIONAL, but if it is present it MUST be
// specified before the first media field.
static int sdp_parse_uri(struct sdp_t* sdp)
{
int n = 0;
// No more than one URI field is allowed per session description.
assert(!sdp->u);
sdp->u = sdp->raw + sdp->offset;
n = sdp_token_word(sdp, "\r\n");
if(0 != sdp_token_crlf(sdp))
return -1;
sdp->u[n] = '\0';
return 0;
}
// RFC4566 5.6
// e=<email-address>
// p=<phone-number>
// OPTIONAL, If an email address or phone number is present, it MUST be specified
// before the first media field. More than one email or phone field can
// be given for a session description.
// p=+1 617 555-6011
// e=j.doe@example.com (Jane Doe)
// e=Jane Doe <j.doe@example.com>
static int sdp_parse_email(struct sdp_t* sdp)
{
int n = 0;
struct sdp_email *e;
if(sdp->e.count >= N_EMAIL)
{
if(sdp->e.count - N_EMAIL >= sdp->e.capacity)
{
void* ptr;
ptr = (struct sdp_email*)realloc(sdp->e.ptr, (sdp->e.capacity+8)*sizeof(struct sdp_email));
if(!ptr)
return -ENOMEM;
sdp->e.ptr = ptr;
sdp->e.capacity += 8;
}
e = &sdp->e.ptr[sdp->e.count - N_EMAIL];
}
else
{
e = &sdp->e.emails[sdp->e.count];
}
memset(e, 0, sizeof(struct sdp_email));
e->email = sdp->raw + sdp->offset;
n = sdp_token_word(sdp, "\r\n");
if(0 != sdp_token_crlf(sdp))
return -1;
e->email[n] = '\0';
++sdp->e.count;
return 0;
}
static int sdp_parse_phone(struct sdp_t* sdp)
{
int n = 0;
struct sdp_phone *p;
if(sdp->p.count >= N_PHONE)
{
if(sdp->p.count - N_PHONE >= sdp->p.capacity)
{
void* ptr;
ptr = (struct sdp_phone*)realloc(sdp->p.ptr, (sdp->p.capacity+8)*sizeof(struct sdp_phone));
if(!ptr)
return -ENOMEM;
sdp->p.ptr = ptr;
sdp->p.capacity += 8;
}
p = &sdp->p.ptr[sdp->p.count - N_PHONE];
}
else
{
p = &sdp->p.phones[sdp->p.count];
}
memset(p, 0, sizeof(struct sdp_phone));
p->phone = sdp->raw + sdp->offset;
n = sdp_token_word(sdp, "\r\n");
if(0 != sdp_token_crlf(sdp))
return -1;
trim_right(p->phone, &n);
p->phone[n] = '\0';
++sdp->p.count;
return 0;
}
// RFC4566 5.7
// c=<nettype> <addrtype> <connection-address>
// A session description MUST contain either at least one "c=" field in
// each media description or a single "c=" field at the session level.
// c=IN IP4 224.2.36.42/127
// c=IN IP4 224.2.1.1/127/3
// c=IN IP6 FF15::101/3
// The slash notation for multiple addresses described above MUST NOT be
// used for IP unicast addresses
static int sdp_parse_connection(struct sdp_t* sdp)
{
int n[3];
struct sdp_media *m;
struct sdp_connection *c;
m = NULL;
if(sdp->m.count > 0)
{
m = sdp_get_media(sdp, sdp->m.count-1);
if(m->c.count >= N_CONNECTION)
{
if(m->c.count - N_CONNECTION >= m->c.capacity)
{
void* ptr;
ptr = (struct sdp_connection*)realloc(m->c.ptr, (m->c.capacity+8)*sizeof(struct sdp_connection));
if(!ptr)
return -ENOMEM;
m->c.ptr = ptr;
m->c.capacity += 8;
}
c = &m->c.ptr[m->c.count - N_CONNECTION];
}
else
{
c = &m->c.connections[m->c.count];
}
}
else
{
c = &sdp->c;
}
memset(c, 0, sizeof(struct sdp_connection));
memset(n, 0, sizeof(n));
sdp_skip_space(sdp);
c->network = sdp->raw + sdp->offset;
n[0] = sdp_token_word(sdp, " \t\r\n");
sdp_skip_space(sdp);
c->addrtype = sdp->raw + sdp->offset;
n[1] = sdp_token_word(sdp, " \t\r\n");
sdp_skip_space(sdp);
c->address = sdp->raw + sdp->offset;
n[2] = sdp_token_word(sdp, "\r\n");
trim_right(c->address, &n[2]);
// check before assign '\0'
if(0==sdp_token_crlf(sdp) /* && n[0]>0 && n[1]>0 && n[2]>0*/ )
{
c->network[n[0]] = '\0';
c->addrtype[n[1]] = '\0';
c->address[n[2]] = '\0';
if(m) ++m->c.count; // add media connection
return 0;
}
return -1;
}
// RFC4566 5.8
// b=<bwtype>:<bandwidth>
// bwtype: CT/AS
// bandwidth: kilobits per second by default
// A prefix "X-" is defined for <bwtype> names.
// b=X-YZ:128
static int sdp_parse_bandwidth(struct sdp_t* sdp)
{
int n[2];
struct bandwidths *bs;
struct sdp_bandwidth *b;
if(sdp->m.count > 0)
{
bs = &sdp_get_media(sdp, sdp->m.count-1)->b;
}
else
{
bs = &sdp->b;
}
if(bs->count >= N_BANDWIDTH)
{
if(bs->count - N_BANDWIDTH >= bs->capacity)
{
void* ptr;
ptr = (struct sdp_bandwidth*)realloc(bs->ptr, (bs->capacity+8)*sizeof(struct sdp_bandwidth));
if(!ptr)
return -ENOMEM;
bs->ptr = ptr;
bs->capacity += 8;
}
b = &bs->ptr[bs->count - N_BANDWIDTH];
}
else
{
b = &bs->bandwidths[bs->count];
}
memset(n, 0, sizeof(n));
memset(b, 0, sizeof(struct sdp_bandwidth));
sdp_skip_space(sdp);
b->bwtype = sdp->raw + sdp->offset;
n[0] = sdp_token_word(sdp, ":\r\n");
trim_right(b->bwtype, &n[0]);
if(':' == sdp->raw[sdp->offset])
++sdp->offset;
sdp_skip_space(sdp);
b->bandwidth = sdp->raw + sdp->offset;
n[1] = sdp_token_word(sdp, "\r\n");
trim_right(b->bandwidth, &n[1]);
if(0 == sdp_token_crlf(sdp))
{
++bs->count;
b->bwtype[n[0]] = '\0';
b->bandwidth[n[1]] = '\0';
return 0;
}
return -1;
}
// RFC4566 5.9 (p17)
// t=<start-time> <stop-time>
// If the <stop-time> is set to zero, then the session is not bounded,
// though it will not become active until after the <start-time>. If
// the <start-time> is also zero, the session is regarded as permanent.
//
// 1. These values are the decimal representation of Network Time Protocol (NTP) time values in seconds
// since 1900 [13]. To convert these values to UNIX time, subtract decimal 2208988800.
// 2. If the <stop-time> is set to zero, then the session is not bounded, though it will not become active
// until after the <start-time>. If the <start-time> is also zero, the session is regarded as permanent.
static int sdp_parse_timing(struct sdp_t* sdp)
{
int n[2];
struct sdp_timing *t;
if(sdp->t.count >= N_TIMING)
{
if(sdp->t.count - N_TIMING >= sdp->t.capacity)
{
void* ptr;
ptr = (struct sdp_timing*)realloc(sdp->t.ptr, (sdp->t.capacity+8)*sizeof(struct sdp_timing));
if(!ptr)
return -ENOMEM;
sdp->t.ptr = ptr;
sdp->t.capacity += 8;
}
t = &sdp->t.ptr[sdp->t.count - N_TIMING];
}
else
{
t = &sdp->t.times[sdp->t.count];
}
memset(n, 0, sizeof(n));
memset(t, 0, sizeof(struct sdp_timing));
sdp_skip_space(sdp);
t->start = sdp->raw + sdp->offset;
n[0] = sdp_token_word(sdp, " \t\r\n");
sdp_skip_space(sdp);
t->stop = sdp->raw + sdp->offset;
n[1] = sdp_token_word(sdp, "\r\n");
trim_right(t->stop, &n[1]);
if(0 == sdp_token_crlf(sdp))
{
t->start[n[0]] = '\0';
t->stop[n[1]] = '\0';
++sdp->t.count;
return 0;
}
return -1;
}
static int sdp_append_timing_repeat_offset(struct sdp_repeat *r, char* offset)
{
if(r->offsets.count >= N_REPEAT_OFFSET)
{
if(r->offsets.count - N_REPEAT_OFFSET >= r->offsets.capacity)
{
void* ptr;
ptr = (char**)realloc(r->offsets.ptr, (r->offsets.capacity+8)*sizeof(char*));
if(!ptr)
return -ENOMEM;
r->offsets.ptr = ptr;
r->offsets.capacity += 8;
}
r->offsets.ptr[r->offsets.count - N_REPEAT_OFFSET] = offset;
}
else
{
r->offsets.offsets[r->offsets.count] = offset;
}
++r->offsets.count;
return 0;
}
// RFC4566 5.10
// r=<repeat interval> <active duration> <offsets from start-time>
// t=3034423619 3042462419
// r=604800 3600 0 90000
// r=7d 1h 0 25h
static int sdp_parse_repeat(struct sdp_t* sdp)
{
int ret;
int n[3];
char *offset;
struct sdp_timing *t;
struct sdp_repeat *r;
assert(sdp->t.count > 0);
if(sdp->t.count < 1)
return -1; // repeat must after timing
t = sdp_get_timing(sdp, sdp->t.count-1);
if(t->r.count >= N_REPEAT)
{
if(t->r.count - N_REPEAT >= t->r.capacity)
{
void* ptr;
ptr = (struct sdp_repeat*)realloc(t->r.ptr, (t->r.capacity+8)*sizeof(struct sdp_repeat));
if(!ptr)
return -ENOMEM;
t->r.ptr = ptr;
t->r.capacity += 8;
}
r = &t->r.ptr[t->r.count - N_REPEAT];
}
else
{
r = &t->r.repeats[t->r.count];
}
offset = NULL;
memset(n, 0, sizeof(n));
memset(r, 0, sizeof(struct sdp_repeat));
sdp_skip_space(sdp);
r->interval = sdp->raw + sdp->offset;
n[0] = sdp_token_word(sdp, " \t\r\n");
sdp_skip_space(sdp);
r->duration = sdp->raw + sdp->offset;
n[1] = sdp_token_word(sdp, " \t\r\n");
while(sdp->raw[sdp->offset] && strchr(" \t", sdp->raw[sdp->offset]))
{
if(n[2] > 0 && offset)
{
offset[n[2]] = '\0';
ret = sdp_append_timing_repeat_offset(r, offset);
if(0 != ret)
return ret;
}
sdp_skip_space(sdp);
offset = sdp->raw + sdp->offset;
n[2] = sdp_token_word(sdp, " \t\r\n");
}
if(0 == sdp_token_crlf(sdp))
{
r->interval[n[0]] = '\0';
r->duration[n[1]] = '\0';
if(n[2] > 0 && offset)
{
offset[n[2]] = '\0';
ret = sdp_append_timing_repeat_offset(r, offset);
if(0 != ret)
return ret;
}
++t->r.count;
return 0;
}
return -1;
}
// RFC4566 5.11
// z=<adjustment time> <offset> <adjustment time> <offset> ....
// z=2882844526 -1h 2898848070 0
static int sdp_parse_timezone(struct sdp_t* sdp)
{
int n[2];
char *time, *offset;
struct sdp_timing *t;
struct sdp_timezone *z;
assert(sdp->t.count > 0);
if(sdp->t.count < 1)
return -1; // timezone must after timing
t = sdp_get_timing(sdp, sdp->t.count-1);
do
{
sdp_skip_space(sdp);
time = sdp->raw + sdp->offset;
n[0] = sdp_token_word(sdp, " \t\r\n");
sdp_skip_space(sdp);
offset = sdp->raw + sdp->offset;
n[1] = sdp_token_word(sdp, " \t\r\n");
if(n[0] < 1 || n[1] < 1)
break;
if(t->z.count >= N_TIMEZONE)
{
if(t->z.count - N_TIMEZONE >= t->z.capacity)
{
void* ptr;
ptr = (struct sdp_timezone*)realloc(t->z.ptr, (t->z.capacity+8)*sizeof(struct sdp_timezone));
if(!ptr)
return -ENOMEM;
t->z.ptr = ptr;
t->z.capacity += 8;
}
z = &t->z.ptr[t->z.count - N_TIMEZONE];
}
else
{
z = &t->z.timezones[t->z.count];
}
z->time = time;
z->offset = offset;
++t->z.count;
} while(n[0] > 0 && n[1] > 0);
return sdp_token_crlf(sdp);
}
// RFC4566 5.12
// k=<method>
// k=<method>:<encryption key>
// k=clear:<encryption key>
// k=base64:<encoded encryption key>
// k=uri:<URI to obtain key>
// k=prompt
// A key field is permitted before the first media entry (in which case
// it applies to all media in the session), or for each media entry as required.
static int sdp_parse_encryption(struct sdp_t* sdp)
{
int n[2];
struct sdp_encryption *k;
if(sdp->m.count > 0)
{
k = &sdp_get_media(sdp, sdp->m.count-1)->k;
}
else
{
k = &sdp->k;
}
memset(k, 0, sizeof(struct sdp_encryption));
sdp_skip_space(sdp);
k->method = sdp->raw + sdp->offset;
n[0] = sdp_token_word(sdp, ":\r\n");
trim_right(k->method, &n[0]);
if(':' == sdp->raw[sdp->offset])
++sdp->offset; // skip ':'
sdp_skip_space(sdp);
k->key = sdp->raw + sdp->offset;
n[1] = sdp_token_word(sdp, "\r\n");
trim_right(k->key, &n[1]);
if(0 == sdp_token_crlf(sdp))
{
k->method[n[0]] = '\0';
k->key[n[1]] = '\0';
return 0;
}
return -1;
}
// RFC4566 5.13
// a=<attribute>
// a=<attribute>:<value>
// a=cat:<category>
// a=keywds:<keywords>
// a=tool:<name and version of tool>
// a=ptime:<packet time>
// a=maxptime:<maximum packet time>
// a=rtpmap:<payload type> <encoding name>/<clock rate> [/<encoding parameters>]
// a=recvonly
// a=sendrecv
// a=sendonly
// a=inactive
// a=orient:<orientation>
// a=type:<conference type>
// a=charset:<character set>
// a=sdplang:<language tag>
// a=lang:<language tag>
// a=framerate:<frame rate>
// a=quality:<quality>
// a=fmtp:<format> <format specific parameters>
static int sdp_parse_attribute(struct sdp_t* sdp)
{
int n[2];
struct attributes *as;
struct sdp_attribute *a;
if(sdp->m.count > 0)
{
as = &sdp_get_media(sdp, sdp->m.count-1)->a;
}
else
{
as = &sdp->a;
}
if(as->count >= N_ATTRIBUTE)
{
if(as->count - N_ATTRIBUTE >= as->capacity)
{
void* ptr;
ptr = (struct sdp_attribute*)realloc(as->ptr, (as->capacity+8)*sizeof(struct sdp_attribute));
if(!ptr)
return -ENOMEM;
as->ptr = ptr;
as->capacity += 8;
}
a = &as->ptr[as->count - N_ATTRIBUTE];
}
else
{
a = &as->attrs[as->count];
}
memset(a, 0, sizeof(struct sdp_attribute));
sdp_skip_space(sdp);
a->name = sdp->raw + sdp->offset;
n[0] = sdp_token_word(sdp, ":\r\n");
trim_right(a->name, &n[0]);
if(':' == sdp->raw[sdp->offset])
++sdp->offset; // skip ':'
sdp_skip_space(sdp);
a->value = sdp->raw + sdp->offset;
n[1] = sdp_token_word(sdp, "\r\n");
trim_right(a->value, &n[1]);
if(0 == sdp_token_crlf(sdp))
{
++as->count;
a->name[n[0]] = '\0';
a->value[n[1]] = '\0';
return 0;
}
return -1;
}
static int sdp_append_media_format(struct sdp_media *m, char* fmt)
{
if(m->fmt.count >= N_MEDIA_FORMAT)
{
if(m->fmt.count - N_MEDIA_FORMAT >= m->fmt.capacity)
{
void* ptr;
ptr = (char**)realloc(m->fmt.ptr, (m->fmt.capacity+8)*sizeof(char*));
if(!ptr)
return -ENOMEM;
m->fmt.ptr = ptr;
m->fmt.capacity += 8;
}
m->fmt.ptr[m->fmt.count - N_MEDIA_FORMAT] = fmt;
}
else
{
m->fmt.formats[m->fmt.count] = fmt;
}
++m->fmt.count;
return 0;
}
// RFC4566 5.14
// m=<media> <port> <proto> <fmt> ...
// media: audio/video/text/application/message
// proto: udp, RTP/AVP, RTP/SAVP
// m=<media> <port>/<number of ports> <proto> <fmt> ...
// m=video 49170/2 RTP/AVP 31
// c=IN IP4 224.2.1.1/127/2
// m=video 49170/2 RTP/AVP 31
// m=application 9 UDP/DTLS/SCTP webrtc-datachannel
static int sdp_parse_media(struct sdp_t* sdp)
{
int ret;
int n[4];
char *fmt;
struct sdp_media *m;
if(sdp->m.count >= N_MEDIA)
{
if(sdp->m.count - N_MEDIA >= sdp->m.capacity)
{
void* ptr;
ptr = (struct sdp_media*)realloc(sdp->m.ptr, (sdp->m.capacity+8)*sizeof(struct sdp_media));
if(!ptr)
return -ENOMEM;
sdp->m.ptr = ptr;
sdp->m.capacity += 8;
}
m = &sdp->m.ptr[sdp->m.count - N_MEDIA];
}
else
{
m = &sdp->m.medias[sdp->m.count];
}
memset(m, 0, sizeof(struct sdp_media));
sdp_skip_space(sdp);
m->media = sdp->raw + sdp->offset;
n[0] = sdp_token_word(sdp, " \t\r\n");
sdp_skip_space(sdp);
m->port = sdp->raw + sdp->offset;
n[1] = sdp_token_word(sdp, " \t\r\n");
sdp_skip_space(sdp);
m->proto = sdp->raw + sdp->offset;
n[2] = sdp_token_word(sdp, " \t\r\n");
sdp_skip_space(sdp);
fmt = sdp->raw + sdp->offset;
n[3] = sdp_token_word(sdp, " \t\r\n");
while (' ' == fmt[n[3]] || '\t' == fmt[n[3]])
{
fmt[n[3]] = '\0';
ret = sdp_append_media_format(m, fmt);
if(0 != ret)
return ret;
sdp->offset += 1; // skip '\0'
sdp_skip_space(sdp);
fmt = sdp->raw + sdp->offset;
n[3] = sdp_token_word(sdp, " \t\r\n");
}
if(0 == sdp_token_crlf(sdp))
{
m->media[n[0]] = '\0';
m->port[n[1]] = '\0';
m->proto[n[2]] = '\0';
if(n[3] > 0)
{
fmt[n[3]] = '\0';
ret = sdp_append_media_format(m, fmt);
if(0 != ret)
return ret;
}
++sdp->m.count;
return 0;
}
return -1;
}
// gb28181 extension
static int sdp_parse_gb28181_ssrc(struct sdp_t* sdp)
{
int n;
struct attributes* as;
struct sdp_attribute* a;
if (sdp->m.count > 0)
{
as = &sdp_get_media(sdp, sdp->m.count - 1)->a;
}
else
{
as = &sdp->a;
}
if (as->count >= N_ATTRIBUTE)
{
if (as->count - N_ATTRIBUTE >= as->capacity)
{
void* ptr;
ptr = (struct sdp_attribute*)realloc(as->ptr, (as->capacity + 8) * sizeof(struct sdp_attribute));
if (!ptr)
return -ENOMEM;
as->ptr = ptr;
as->capacity += 8;
}
a = &as->ptr[as->count - N_ATTRIBUTE];
}
else
{
a = &as->attrs[as->count];
}
memset(a, 0, sizeof(struct sdp_attribute));
a->name = "ssrc";
a->value = sdp->raw + sdp->offset;
n = sdp_token_word(sdp, "\r\n");
if (0 != sdp_token_crlf(sdp))
return -1;
a->value[n] = '\0';
++as->count;
return 0;
}
static void* sdp_create(const char* s, int len)
{
struct sdp_t *sdp;
sdp = (struct sdp_t*)malloc(sizeof(struct sdp_t) + len + 1);
if( !sdp )
return NULL;
memset(sdp, 0, sizeof(struct sdp_t));
sdp->raw = (char*)(sdp + 1);
memcpy(sdp->raw, s, len);
sdp->raw[len] = 0;
return sdp;
}
void sdp_destroy(struct sdp_t* sdp)
{
int i;
struct sdp_media *m;
struct sdp_timing *t;
if(sdp->e.count > N_EMAIL)
free(sdp->e.ptr);
if(sdp->p.count > N_PHONE)
free(sdp->p.ptr);
if(sdp->b.count > N_BANDWIDTH)
free(sdp->b.ptr);
for(i = 0; i < sdp->t.count; i++)
{
t = sdp_get_timing(sdp, i);
if(t->r.count > N_REPEAT)
free(t->r.ptr);
if(t->z.count > N_TIMEZONE)
free(t->z.ptr);
}
if(sdp->t.count > N_TIMING)
free(sdp->t.ptr);
if(sdp->a.count > N_ATTRIBUTE)
free(sdp->a.ptr);
for(i = 0; i < sdp->m.count; i++)
{
m = sdp_get_media(sdp, i);
if(m->fmt.count > N_MEDIA_FORMAT)
free(m->fmt.ptr);
if(m->a.count > N_ATTRIBUTE)
free(m->a.ptr);
if(m->c.count > N_CONNECTION)
free(m->c.ptr);
if (m->b.count > N_BANDWIDTH)
free(m->b.ptr);
}
if(sdp->m.count > N_MEDIA)
free(sdp->m.ptr);
free(sdp);
}
struct sdp_t* sdp_parse(const char* s, int len)
{
int r;
char c;
struct sdp_t *sdp;
assert(s);
sdp = (struct sdp_t*)sdp_create(s, len);
if(!sdp)
return NULL;
while(sdp->raw[sdp->offset] && !strchr("\r\n", sdp->raw[sdp->offset]))
{
sdp_skip_space(sdp);
c = sdp->raw[sdp->offset++];
sdp_skip_space(sdp);
if('=' != sdp->raw[sdp->offset++])
goto parse_failed;
sdp_skip_space(sdp);
switch(c)
{
case 'v':
r = sdp_parse_version(sdp);
break;
case 'o':
r = sdp_parse_origin(sdp);
break;
case 's':
r = sdp_parse_session(sdp);
break;
case 'i':
r = sdp_parse_information(sdp);
break;
case 'u':
r = sdp_parse_uri(sdp);
break;
case 'e':
r = sdp_parse_email(sdp);
break;
case 'p':
r = sdp_parse_phone(sdp);
break;
case 'c':
r = sdp_parse_connection(sdp);
break;
case 'b':
r = sdp_parse_bandwidth(sdp);
break;
case 't':
r = sdp_parse_timing(sdp);
break;
case 'r':
r = sdp_parse_repeat(sdp);
break;
case 'z':
r = sdp_parse_timezone(sdp);
break;
case 'k':
r = sdp_parse_encryption(sdp);
break;
case 'a':
r = sdp_parse_attribute(sdp);
break;
case 'm':
r = sdp_parse_media(sdp);
break;
case 'y':
r = sdp_parse_gb28181_ssrc(sdp);
break;
default:
// unknown sdp
sdp_token_word(sdp, "\r\n");
r = sdp_token_crlf(sdp);
break;
}
if(0 != r)
goto parse_failed;
}
return sdp;
parse_failed:
sdp_destroy(sdp);
return NULL;
}
int sdp_version_get(struct sdp_t* sdp)
{
return sdp->v;
}
//void sdp_version_set(struct sdp_t* sdp, int version)
//{
// struct sdp_t *sdp;
// sdp = (struct sdp_t*)sdp;
// sdp->v = version;
//}
int sdp_origin_get(struct sdp_t* sdp, const char **username, const char** session, const char** version, const char** network, const char** addrtype, const char** address)
{
if(sdp->o.username && sdp->o.session && sdp->o.session_version
&& sdp->o.c.network && sdp->o.c.addrtype && sdp->o.c.address)
{
*username = sdp->o.username;
*session = sdp->o.session;
*version = sdp->o.session_version;
*network = sdp->o.c.network;
*addrtype = sdp->o.c.addrtype;
*address = sdp->o.c.address;
return 0;
}
return -1;
}
int sdp_origin_get_network(struct sdp_t* sdp)
{
if(0 == strcasecmp("IN", sdp->o.c.network))
return SDP_C_NETWORK_IN;
return SDP_C_NETWORK_UNKNOWN;
}
int sdp_origin_get_addrtype(struct sdp_t* sdp)
{
if(0 == strcasecmp("IP4", sdp->o.c.addrtype))
return SDP_C_ADDRESS_IP4;
if(0 == strcasecmp("IP6", sdp->o.c.addrtype))
return SDP_C_ADDRESS_IP6;
return SDP_C_ADDRESS_UNKNOWN;
}
const char* sdp_session_get_name(struct sdp_t* sdp)
{
return sdp->s;
}
const char* sdp_session_get_information(struct sdp_t* sdp)
{
return sdp->i;
}
const char* sdp_uri_get(struct sdp_t* sdp)
{
return sdp->u;
}
int sdp_email_count(struct sdp_t* sdp)
{
return sdp->e.count;
}
const char* sdp_email_get(struct sdp_t* sdp, int idx)
{
if(idx >= sdp->e.count || idx < 0)
return NULL;
return idx < N_EMAIL ? sdp->e.emails[idx].email : sdp->e.ptr[idx - N_EMAIL].email;
}
int sdp_phone_count(struct sdp_t* sdp)
{
return sdp->p.count;
}
const char* sdp_phone_get(struct sdp_t* sdp, int idx)
{
if(idx >= sdp->p.count || idx < 0)
return NULL;
return idx < N_PHONE ? sdp->p.phones[idx].phone : sdp->p.ptr[idx - N_PHONE].phone;
}
int sdp_connection_get(struct sdp_t* sdp, const char** network, const char** addrtype, const char** address)
{
if(sdp->c.network && sdp->c.addrtype && sdp->c.address)
{
*network = sdp->c.network;
*addrtype = sdp->c.addrtype;
*address = sdp->c.address;
return 0;
}
return -1;
}
int sdp_connection_get_address(struct sdp_t* sdp, char* ip, int bytes)
{
const char* p;
if(sdp->c.address && bytes > 0)
{
p = sdp->c.address;
while(*p && '/' != *p && bytes > 1)
{
*ip++ = *p++;
--bytes;
}
if(0 == *p || '/' == *p)
{
*ip = '\0';
return 0;
}
}
return -1;
}
int sdp_connection_get_network(struct sdp_t* sdp)
{
if(0 == strcasecmp("IN", sdp->c.network))
return SDP_C_NETWORK_IN;
return SDP_C_NETWORK_UNKNOWN;
}
int sdp_connection_get_addrtype(struct sdp_t* sdp)
{
if(0 == strcasecmp("IP4", sdp->c.addrtype))
return SDP_C_ADDRESS_IP4;
if(0 == strcasecmp("IP6", sdp->c.addrtype))
return SDP_C_ADDRESS_IP6;
return SDP_C_ADDRESS_UNKNOWN;
}
int sdp_bandwidth_count(struct sdp_t* sdp)
{
return sdp->b.count;
}
const char* sdp_bandwidth_get_type(struct sdp_t* sdp, int idx)
{
if(idx >= (int)sdp->b.count || idx < 0)
return NULL;
return idx < N_BANDWIDTH ? sdp->b.bandwidths[idx].bwtype : sdp->b.ptr[idx - N_BANDWIDTH].bwtype;
}
int sdp_bandwidth_get_value(struct sdp_t* sdp, int idx)
{
const char* b;
if(idx >= (int)sdp->b.count || idx < 0)
return -1;
b = idx < N_BANDWIDTH ? sdp->b.bandwidths[idx].bandwidth : sdp->b.ptr[idx - N_BANDWIDTH].bandwidth;
return atoi(b);
}
int sdp_timing_count(struct sdp_t* sdp)
{
return sdp->t.count;
}
int sdp_timing_get(sdp_t* sdp, int idx, const char** start, const char** stop)
{
struct sdp_timing* t;
t = sdp_get_timing(sdp, idx);
if (NULL == t)
return -1;
*start = t->start;
*stop = t->stop;
return 0;
}
int sdp_timing_repeat_count(sdp_t* sdp, int idx)
{
struct sdp_timing* t;
t = sdp_get_timing(sdp, idx);
return t ? t->r.count : -1;
}
int sdp_timing_timezone_count(sdp_t* sdp, int idx)
{
struct sdp_timing* t;
t = sdp_get_timing(sdp, idx);
return t ? t->z.count : -1;
}
int sdp_media_count(struct sdp_t* sdp)
{
return sdp->m.count;
}
const char* sdp_media_type(struct sdp_t* sdp, int media)
{
struct sdp_media *m;
m = sdp_get_media(sdp, media);
return m ? m->media : NULL;
}
int sdp_media_port(struct sdp_t* sdp, int media, int port[], int num)
{
int i, n;
const char* p;
struct sdp_media *m;
m = sdp_get_media(sdp, media);
if (!m || !port)
return -1;
p = strchr(m->port, '/');
port[0] = atoi(m->port);
n = atoi(p ? p + 1 : "1");
for (i = 1; i < num && i < n; i++)
port[i] = port[0] + i;
return i;
}
const char* sdp_media_proto(struct sdp_t* sdp, int media)
{
struct sdp_media *m;
m = sdp_get_media(sdp, media);
return m ? m->proto : NULL;
}
// rfc 4566 5.14. Media Descriptions ("m=")
// (p24) If the <proto> sub-field is "udp" the <fmt> sub-fields MUST
// reference a media type describing the format under the "audio",
// "video", "text", "application", or "message" top-level media types.
static inline int sdp_media_format_value(const char* format)
{
switch(format[0])
{
case 'a': return ('u' == format[1]) ? SDP_M_MEDIA_AUDIO : SDP_M_MEDIA_APPLICATION;
case 'v': return SDP_M_MEDIA_VIDEO;
case 't': return SDP_M_MEDIA_TEXT;
case 'm': return SDP_M_MEDIA_MESSAGE;
case 'w': return 0 == strcmp("webrtc-datachannel", format) ? SDP_M_MEDIA_APPLICATION : -1;
default: return atoi(format);
}
//if(0 == strcasecmp("video", format))
// return SDP_M_MEDIA_VIDEO;
//else if(0 == strcasecmp("audio", format))
// return SDP_M_MEDIA_AUDIO;
//else if(0 == strcasecmp("text", format))
// return SDP_M_MEDIA_TEXT;
//else if(0 == strcasecmp("application", format))
// return SDP_M_MEDIA_APPLICATION;
//else if(0 == strcasecmp("message", format))
// return SDP_M_MEDIA_MESSAGE;
//else
// return atoi(format);
}
int sdp_media_formats(struct sdp_t* sdp, int media, int *formats, int count)
{
int i;
struct sdp_media *m;
m = sdp_get_media(sdp, media);
if(!m)
return -1;
for(i = 0; i < count && i < m->fmt.count; i++)
{
if(i < N_MEDIA_FORMAT)
formats[i] = sdp_media_format_value(m->fmt.formats[i]);
else
formats[i] = sdp_media_format_value(m->fmt.ptr[i-N_MEDIA_FORMAT]);
}
return (int)m->fmt.count;
}
int sdp_media_get_connection(sdp_t* sdp, int media, const char** network, const char** addrtype, const char** address)
{
struct sdp_media *m;
struct sdp_connection *conn;
m = sdp_get_media(sdp, media);
if(m && m->c.count > 0)
conn = &m->c.connections[0];
else
conn = &sdp->c;
if(conn && conn->network && conn->addrtype && conn->address)
{
*network = conn->network;
*addrtype = conn->addrtype;
*address = conn->address;
return 0;
}
return -1;
}
int sdp_media_get_connection_address(struct sdp_t* sdp, int media, char* ip, int bytes)
{
const char* p;
struct sdp_media *m;
struct sdp_connection *conn;
m = sdp_get_media(sdp, media);
if(m && m->c.count > 0)
conn = &m->c.connections[0];
else
conn = &sdp->c;
if(conn->address && bytes > 0)
{
p = conn->address;
while(*p && '/' != *p && bytes > 1)
{
*ip++ = *p++;
--bytes;
}
if(0 == *p || '/' == *p)
{
*ip = '\0';
return 0;
}
}
return -1;
}
int sdp_media_get_connection_network(struct sdp_t* sdp, int media)
{
struct sdp_media *m;
struct sdp_connection *conn;
m = sdp_get_media(sdp, media);
if(m && m->c.count > 0)
conn = &m->c.connections[0];
else
conn = &sdp->c;
if(conn->network)
{
if(0 == strcasecmp("IN", conn->network))
return SDP_C_NETWORK_IN;
}
return SDP_C_NETWORK_UNKNOWN;
}
int sdp_media_get_connection_addrtype(struct sdp_t* sdp, int media)
{
struct sdp_media *m;
struct sdp_connection *conn;
m = sdp_get_media(sdp, media);
if(m && m->c.count > 0)
conn = &m->c.connections[0];
else
conn = &sdp->c;
if(conn->addrtype)
{
if(0 == strcasecmp("IP4", conn->addrtype))
return SDP_C_ADDRESS_IP4;
if(0 == strcasecmp("IP6", conn->addrtype))
return SDP_C_ADDRESS_IP6;
}
return SDP_C_ADDRESS_UNKNOWN;
}
int sdp_media_bandwidth_count(struct sdp_t* sdp, int media)
{
struct sdp_media *m;
m = sdp_get_media(sdp, media);
if(!m)
return 0;
return m->b.count;
}
const char* sdp_media_bandwidth_get_type(struct sdp_t* sdp, int media, int idx)
{
struct sdp_media *m;
m = sdp_get_media(sdp, media);
if(!m)
return NULL;
if(idx >= (int)m->b.count || idx < 0)
return NULL;
return idx < N_BANDWIDTH ? m->b.bandwidths[idx].bwtype : m->b.ptr[idx - N_BANDWIDTH].bwtype;
}
int sdp_media_bandwidth_get_value(struct sdp_t* sdp, int media, int idx)
{
const char* b;
struct sdp_media *m;
m = sdp_get_media(sdp, media);
if(!m)
return -1;
if(idx >= (int)m->b.count || idx < 0)
return -1;
b = idx < N_BANDWIDTH ? m->b.bandwidths[idx].bandwidth : m->b.ptr[idx - N_BANDWIDTH].bandwidth;
return atoi(b);
}
int sdp_attribute_count(struct sdp_t* sdp)
{
return sdp->a.count;
}
int sdp_attribute_get(struct sdp_t* sdp, int idx, const char** name, const char** value)
{
struct sdp_attribute *attr;
if(idx < 0 || idx > sdp->a.count)
return -1; // not found
if(idx < N_ATTRIBUTE)
attr = sdp->a.attrs + idx;
else
attr = sdp->a.ptr + idx - N_ATTRIBUTE;
*name = attr->name;
*value = attr->value;
return 0;
}
static const char* sdp_attribute_find_impl(const struct attributes* a, const char* name)
{
int i;
const struct sdp_attribute* attr;
for (i = 0; name && i < a->count; i++)
{
if (i < N_ATTRIBUTE)
attr = a->attrs + i;
else
attr = a->ptr + i - N_ATTRIBUTE;
if (attr->name && 0 == strcmp(attr->name, name))
return attr->value;
}
return NULL;
}
static int sdp_attribute_list_impl(const struct attributes* a, const char* name, void (*onattr)(void* param, const char* name, const char* value), void* param)
{
int i;
const struct sdp_attribute* attr;
for (i = 0; i < a->count; i++)
{
if (i < N_ATTRIBUTE)
attr = a->attrs + i;
else
attr = a->ptr + i - N_ATTRIBUTE;
if (!name || (attr->name && 0 == strcmp(attr->name, name)))
onattr(param, attr->name, attr->value);
}
return 0;
}
const char* sdp_media_attribute_find(struct sdp_t* sdp, int media, const char* name)
{
struct sdp_media* m;
m = sdp_get_media(sdp, media);
return m ? sdp_attribute_find_impl(&m->a, name) : NULL;
}
int sdp_media_attribute_list(struct sdp_t* sdp, int media, const char* name, void (*onattr)(void* param, const char* name, const char* value), void* param)
{
struct sdp_media* m;
m = sdp_get_media(sdp, media);
return m ? sdp_attribute_list_impl(&m->a, name, onattr, param) : -1;
}
const char* sdp_attribute_find(struct sdp_t* sdp, const char* name)
{
return sdp_attribute_find_impl(&sdp->a, name);
}
int sdp_attribute_list(struct sdp_t* sdp, const char* name, void (*onattr)(void* param, const char* name, const char* value), void* param)
{
return sdp_attribute_list_impl(&sdp->a, name, onattr, param);
}
static int sdp_attribute_mode(struct attributes* a)
{
static const int values[] = { SDP_A_SENDRECV, SDP_A_SENDONLY, SDP_A_RECVONLY, SDP_A_INACTIVE };
static const char* strings[] = { "sendrecv", "sendonly", "recvonly", "inactive" };
int i, j;
const struct sdp_attribute *attr;
for (i = 0; i < a->count; i++)
{
if (i < N_ATTRIBUTE)
attr = a->attrs + i;
else
attr = a->ptr + i - N_ATTRIBUTE;
for (j = 0; j < sizeof(values) / sizeof(values[0]); j++)
{
if (attr->name && 0 == strcmp(strings[j], attr->name))
return values[j];
}
}
return -1;
}
int sdp_media_mode(struct sdp_t* sdp, int media)
{
int mode;
struct sdp_media *m;
m = sdp_get_media(sdp, media);
mode = m ? sdp_attribute_mode(&m->a) : -1;
mode = -1 == mode ? sdp_attribute_mode(&sdp->a) : mode;
return -1 == mode ? SDP_A_SENDRECV : mode; // default SDP_A_SENDRECV
}