一、引言
FFmpeg源码中通过ff_sdp_parse函数解析SDP。该函数定义在libavformat/rtsp.c中:
int ff_sdp_parse(AVFormatContext *s, const char *content)
{
const char *p;
int letter, i;
char buf[SDP_MAX_SIZE], *q;
SDPParseState sdp_parse_state = { { 0 } }, *s1 = &sdp_parse_state;
p = content;
for (;;) {
p += strspn(p, SPACE_CHARS);
letter = *p;
if (letter == '\0')
break;
p++;
if (*p != '=')
goto next_line;
p++;
/* get the content */
q = buf;
while (*p != '\n' && *p != '\r' && *p != '\0') {
if ((q - buf) < sizeof(buf) - 1)
*q++ = *p;
p++;
}
*q = '\0';
sdp_parse_line(s, s1, letter, buf);
next_line:
while (*p != '\n' && *p != '\0')
p++;
if (*p == '\n')
p++;
}
for (i = 0; i < s1->nb_default_include_source_addrs; i++)
av_freep(&s1->default_include_source_addrs[i]);
av_freep(&s1->default_include_source_addrs);
for (i = 0; i < s1->nb_default_exclude_source_addrs; i++)
av_freep(&s1->default_exclude_source_addrs[i]);
av_freep(&s1->default_exclude_source_addrs);
return 0;
}
而ff_sdp_parse函数中又会通过sdp_parse_line函数解析SDP中的一行数据:
int ff_sdp_parse(AVFormatContext *s, const char *content)
{
//...
for (;;) {
//...
sdp_parse_line(s, s1, letter, buf);
//...
}
//...
return 0;
}
二、sdp_parse_line函数的定义
sdp_parse_line函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavformat/rtsp.c中:
static void sdp_parse_line(AVFormatContext *s, SDPParseState *s1,
int letter, const char *buf)
{
RTSPState *rt = s->priv_data;
char buf1[64], st_type[64];
const char *p;
enum AVMediaType codec_type;
int payload_type;
AVStream *st;
RTSPStream *rtsp_st;
RTSPSource *rtsp_src;
struct sockaddr_storage sdp_ip;
int ttl;
av_log(s, AV_LOG_TRACE, "sdp: %c='%s'\n", letter, buf);
p = buf;
if (s1->skip_media && letter != 'm')
return;
switch (letter) {
case 'c':
get_word(buf1, sizeof(buf1), &p);
if (strcmp(buf1, "IN") != 0)
return;
get_word(buf1, sizeof(buf1), &p);
if (strcmp(buf1, "IP4") && strcmp(buf1, "IP6"))
return;
get_word_sep(buf1, sizeof(buf1), "/", &p);
if (get_sockaddr(s, buf1, &sdp_ip))
return;
ttl = 16;
if (*p == '/') {
p++;
get_word_sep(buf1, sizeof(buf1), "/", &p);
ttl = atoi(buf1);
}
if (s->nb_streams == 0) {
s1->default_ip = sdp_ip;
s1->default_ttl = ttl;
} else {
rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];
rtsp_st->sdp_ip = sdp_ip;
rtsp_st->sdp_ttl = ttl;
}
break;
case 's':
av_dict_set(&s->metadata, "title", p, 0);
break;
case 'i':
if (s->nb_streams == 0) {
av_dict_set(&s->metadata, "comment", p, 0);
break;
}
break;
case 'm':
/* new stream */
s1->skip_media = 0;
s1->seen_fmtp = 0;
s1->seen_rtpmap = 0;
codec_type = AVMEDIA_TYPE_UNKNOWN;
get_word(st_type, sizeof(st_type), &p);
if (!strcmp(st_type, "audio")) {
codec_type = AVMEDIA_TYPE_AUDIO;
} else if (!strcmp(st_type, "video")) {
codec_type = AVMEDIA_TYPE_VIDEO;
} else if (!strcmp(st_type, "application")) {
codec_type = AVMEDIA_TYPE_DATA;
} else if (!strcmp(st_type, "text")) {
codec_type = AVMEDIA_TYPE_SUBTITLE;
}
if (codec_type == AVMEDIA_TYPE_UNKNOWN ||
!(rt->media_type_mask & (1 << codec_type)) ||
rt->nb_rtsp_streams >= s->max_streams
) {
s1->skip_media = 1;
return;
}
rtsp_st = av_mallocz(sizeof(RTSPStream));
if (!rtsp_st)
return;
rtsp_st->stream_index = -1;
dynarray_add(&rt->rtsp_streams, &rt->nb_rtsp_streams, rtsp_st);
rtsp_st->sdp_ip = s1->default_ip;
rtsp_st->sdp_ttl = s1->default_ttl;
copy_default_source_addrs(s1->default_include_source_addrs,
s1->nb_default_include_source_addrs,
&rtsp_st->include_source_addrs,
&rtsp_st->nb_include_source_addrs);
copy_default_source_addrs(s1->default_exclude_source_addrs,
s1->nb_default_exclude_source_addrs,
&rtsp_st->exclude_source_addrs,
&rtsp_st->nb_exclude_source_addrs);
get_word(buf1, sizeof(buf1), &p); /* port */
rtsp_st->sdp_port = atoi(buf1);
get_word(buf1, sizeof(buf1), &p); /* protocol */
if (!strcmp(buf1, "udp"))
rt->transport = RTSP_TRANSPORT_RAW;
else if (strstr(buf1, "/AVPF") || strstr(buf1, "/SAVPF"))
rtsp_st->feedback = 1;
/* XXX: handle list of formats */
get_word(buf1, sizeof(buf1), &p); /* format list */
rtsp_st->sdp_payload_type = atoi(buf1);
if (!strcmp(ff_rtp_enc_name(rtsp_st->sdp_payload_type), "MP2T")) {
/* no corresponding stream */
if (rt->transport == RTSP_TRANSPORT_RAW) {
if (CONFIG_RTPDEC && !rt->ts)
rt->ts = avpriv_mpegts_parse_open(s);
} else {
const RTPDynamicProtocolHandler *handler;
handler = ff_rtp_handler_find_by_id(
rtsp_st->sdp_payload_type, AVMEDIA_TYPE_DATA);
init_rtp_handler(handler, rtsp_st, NULL);
finalize_rtp_handler_init(s, rtsp_st, NULL);
}
} else if (rt->server_type == RTSP_SERVER_WMS &&
codec_type == AVMEDIA_TYPE_DATA) {
/* RTX stream, a stream that carries all the other actual
* audio/video streams. Don't expose this to the callers. */
} else {
st = avformat_new_stream(s, NULL);
if (!st)
return;
st->id = rt->nb_rtsp_streams - 1;
rtsp_st->stream_index = st->index;
st->codecpar->codec_type = codec_type;
if (rtsp_st->sdp_payload_type < RTP_PT_PRIVATE) {
const RTPDynamicProtocolHandler *handler;
/* if standard payload type, we can find the codec right now */
ff_rtp_get_codec_info(st->codecpar, rtsp_st->sdp_payload_type);
if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO &&
st->codecpar->sample_rate > 0)
avpriv_set_pts_info(st, 32, 1, st->codecpar->sample_rate);
/* Even static payload types may need a custom depacketizer */
handler = ff_rtp_handler_find_by_id(
rtsp_st->sdp_payload_type, st->codecpar->codec_type);
init_rtp_handler(handler, rtsp_st, st);
finalize_rtp_handler_init(s, rtsp_st, st);
}
if (rt->default_lang[0])
av_dict_set(&st->metadata, "language", rt->default_lang, 0);
}
/* put a default control url */
av_strlcpy(rtsp_st->control_url, rt->control_uri,
sizeof(rtsp_st->control_url));
break;
case 'a':
if (av_strstart(p, "control:", &p)) {
if (rt->nb_rtsp_streams == 0) {
if (!strncmp(p, "rtsp://", 7))
av_strlcpy(rt->control_uri, p,
sizeof(rt->control_uri));
} else {
char proto[32];
/* get the control url */
rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];
/* XXX: may need to add full url resolution */
av_url_split(proto, sizeof(proto), NULL, 0, NULL, 0,
NULL, NULL, 0, p);
if (proto[0] == '\0') {
/* relative control URL */
if (rtsp_st->control_url[strlen(rtsp_st->control_url)-1]!='/')
av_strlcat(rtsp_st->control_url, "/",
sizeof(rtsp_st->control_url));
av_strlcat(rtsp_st->control_url, p,
sizeof(rtsp_st->control_url));
} else
av_strlcpy(rtsp_st->control_url, p,
sizeof(rtsp_st->control_url));
}
} else if (av_strstart(p, "rtpmap:", &p) && s->nb_streams > 0) {
/* NOTE: rtpmap is only supported AFTER the 'm=' tag */
get_word(buf1, sizeof(buf1), &p);
payload_type = atoi(buf1);
rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];
if (rtsp_st->stream_index >= 0) {
st = s->streams[rtsp_st->stream_index];
sdp_parse_rtpmap(s, st, rtsp_st, payload_type, p);
}
s1->seen_rtpmap = 1;
if (s1->seen_fmtp) {
parse_fmtp(s, rt, payload_type, s1->delayed_fmtp);
}
} else if (av_strstart(p, "fmtp:", &p) ||
av_strstart(p, "framesize:", &p)) {
// let dynamic protocol handlers have a stab at the line.
get_word(buf1, sizeof(buf1), &p);
payload_type = atoi(buf1);
if (s1->seen_rtpmap) {
parse_fmtp(s, rt, payload_type, buf);
} else {
s1->seen_fmtp = 1;
av_strlcpy(s1->delayed_fmtp, buf, sizeof(s1->delayed_fmtp));
}
} else if (av_strstart(p, "ssrc:", &p) && s->nb_streams > 0) {
rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];
get_word(buf1, sizeof(buf1), &p);
rtsp_st->ssrc = strtoll(buf1, NULL, 10);
} else if (av_strstart(p, "range:", &p)) {
int64_t start, end;
// this is so that seeking on a streamed file can work.
rtsp_parse_range_npt(p, &start, &end);
s->start_time = start;
/* AV_NOPTS_VALUE means live broadcast (and can't seek) */
s->duration = (end == AV_NOPTS_VALUE) ?
AV_NOPTS_VALUE : end - start;
} else if (av_strstart(p, "lang:", &p)) {
if (s->nb_streams > 0) {
get_word(buf1, sizeof(buf1), &p);
rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];
if (rtsp_st->stream_index >= 0) {
st = s->streams[rtsp_st->stream_index];
av_dict_set(&st->metadata, "language", buf1, 0);
}
} else
get_word(rt->default_lang, sizeof(rt->default_lang), &p);
} else if (av_strstart(p, "IsRealDataType:integer;",&p)) {
if (atoi(p) == 1)
rt->transport = RTSP_TRANSPORT_RDT;
} else if (av_strstart(p, "SampleRate:integer;", &p) &&
s->nb_streams > 0) {
st = s->streams[s->nb_streams - 1];
st->codecpar->sample_rate = atoi(p);
} else if (av_strstart(p, "crypto:", &p) && s->nb_streams > 0) {
// RFC 4568
rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];
get_word(buf1, sizeof(buf1), &p); // ignore tag
get_word(rtsp_st->crypto_suite, sizeof(rtsp_st->crypto_suite), &p);
p += strspn(p, SPACE_CHARS);
if (av_strstart(p, "inline:", &p))
get_word(rtsp_st->crypto_params, sizeof(rtsp_st->crypto_params), &p);
} else if (av_strstart(p, "source-filter:", &p)) {
int exclude = 0;
get_word(buf1, sizeof(buf1), &p);
if (strcmp(buf1, "incl") && strcmp(buf1, "excl"))
return;
exclude = !strcmp(buf1, "excl");
get_word(buf1, sizeof(buf1), &p);
if (strcmp(buf1, "IN") != 0)
return;
get_word(buf1, sizeof(buf1), &p);
if (strcmp(buf1, "IP4") && strcmp(buf1, "IP6") && strcmp(buf1, "*"))
return;
// not checking that the destination address actually matches or is wildcard
get_word(buf1, sizeof(buf1), &p);
while (*p != '\0') {
rtsp_src = av_mallocz(sizeof(*rtsp_src));
if (!rtsp_src)
return;
get_word(rtsp_src->addr, sizeof(rtsp_src->addr), &p);
if (exclude) {
if (s->nb_streams == 0) {
dynarray_add(&s1->default_exclude_source_addrs, &s1->nb_default_exclude_source_addrs, rtsp_src);
} else {
rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];
dynarray_add(&rtsp_st->exclude_source_addrs, &rtsp_st->nb_exclude_source_addrs, rtsp_src);
}
} else {
if (s->nb_streams == 0) {
dynarray_add(&s1->default_include_source_addrs, &s1->nb_default_include_source_addrs, rtsp_src);
} else {
rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];
dynarray_add(&rtsp_st->include_source_addrs, &rtsp_st->nb_include_source_addrs, rtsp_src);
}
}
}
} else {
if (rt->server_type == RTSP_SERVER_WMS)
ff_wms_parse_sdp_a_line(s, p);
if (s->nb_streams > 0) {
rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];
if (rt->server_type == RTSP_SERVER_REAL)
ff_real_parse_sdp_a_line(s, rtsp_st->stream_index, p);
if (rtsp_st->dynamic_handler &&
rtsp_st->dynamic_handler->parse_sdp_a_line)
rtsp_st->dynamic_handler->parse_sdp_a_line(s,
rtsp_st->stream_index,
rtsp_st->dynamic_protocol_context, buf);
}
}
break;
}
}
该函数的作用就是解析SDP中的一行数据。由《音视频入门基础:RTP专题(3)——SDP简介》可以知道:一个SDP会话描述由若干行文本组成,每一行文本的格式如下:<type>=<value>,其中,<type> 必须恰好是一个区分大小写的字符,而 <value> 是结构化文本,其格式取决于 <type>。
形参s:既是输入型参数也是输出型参数,指向一个AVFormatContext类型变量。s->pb存放整个SDP的文本数据。
形参s1:既是输入型参数也是输出型参数,指向一个SDPParseState类型变量。SDPParseState结构体定义如下,用于记录SDP解析的状态:
typedef struct SDPParseState {
/* SDP only */
struct sockaddr_storage default_ip;
int default_ttl;
int skip_media; ///< set if an unknown m= line occurs
int nb_default_include_source_addrs; /**< Number of source-specific multicast include source IP address (from SDP content) */
struct RTSPSource **default_include_source_addrs; /**< Source-specific multicast include source IP address (from SDP content) */
int nb_default_exclude_source_addrs; /**< Number of source-specific multicast exclude source IP address (from SDP content) */
struct RTSPSource **default_exclude_source_addrs; /**< Source-specific multicast exclude source IP address (from SDP content) */
int seen_rtpmap;
int seen_fmtp;
char delayed_fmtp[2048];
} SDPParseState;
形参letter:输入型参数,为该行的<type>值。
形参buf:输入型参数,指向该行的<value>文本数据。
三、sdp_parse_line函数的内部实现分析
sdp_parse_line函数中会通过witch-case语句,通过判断形参letter的值,即该行的<type>值,执行不同的解析:
static void sdp_parse_line(AVFormatContext *s, SDPParseState *s1,
int letter, const char *buf)
{
RTSPState *rt = s->priv_data;
char buf1[64], st_type[64];
const char *p;
enum AVMediaType codec_type;
int payload_type;
AVStream *st;
RTSPStream *rtsp_st;
RTSPSource *rtsp_src;
struct sockaddr_storage sdp_ip;
int ttl;
av_log(s, AV_LOG_TRACE, "sdp: %c='%s'\n", letter, buf);
p = buf;
if (s1->skip_media && letter != 'm')
return;
switch (letter) {
//...
}
}
(一)情况一:<type>的值为'c'
<type>的值为'c'时,<value>会包含连接数据信息,此时该行SDP格式为:c=<nettype> <addrtype> <connection-address>,sdp_parse_line函数中会执行下面代码块:
case 'c':
get_word(buf1, sizeof(buf1), &p);
if (strcmp(buf1, "IN") != 0)
return;
get_word(buf1, sizeof(buf1), &p);
if (strcmp(buf1, "IP4") && strcmp(buf1, "IP6"))
return;
get_word_sep(buf1, sizeof(buf1), "/", &p);
if (get_sockaddr(s, buf1, &sdp_ip))
return;
ttl = 16;
if (*p == '/') {
p++;
get_word_sep(buf1, sizeof(buf1), "/", &p);
ttl = atoi(buf1);
}
if (s->nb_streams == 0) {
s1->default_ip = sdp_ip;
s1->default_ttl = ttl;
} else {
rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];
rtsp_st->sdp_ip = sdp_ip;
rtsp_st->sdp_ttl = ttl;
}
break;
上述代码块中,首先判断<nettype>的值是否为“IN”(表示“Internet”),如果不为“IN”,sdp_parse_line函数直接返回,终止该行解析:
get_word(buf1, sizeof(buf1), &p);
if (strcmp(buf1, "IN") != 0)
return;
判断<addrtype>的值是否为IP4或IP6,如果不为IP4或IP6,sdp_parse_line函数直接返回,终止该行解析:
get_word(buf1, sizeof(buf1), &p);
if (strcmp(buf1, "IP4") && strcmp(buf1, "IP6"))
return;
获取<connection-address>(连接地址),通过get_sockaddr函数得到对应的struct addrinfo结构链表:
get_word_sep(buf1, sizeof(buf1), "/", &p);
if (get_sockaddr(s, buf1, &sdp_ip))
return;
将<connection-address>相关的信息赋值给rtsp_st->sdp_ip:
ttl = 16;
if (*p == '/') {
p++;
get_word_sep(buf1, sizeof(buf1), "/", &p);
ttl = atoi(buf1);
}
if (s->nb_streams == 0) {
s1->default_ip = sdp_ip;
s1->default_ttl = ttl;
} else {
rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];
rtsp_st->sdp_ip = sdp_ip;
rtsp_st->sdp_ttl = ttl;
}
break;
(二)情况二:<type>的值为's'
<type>的值为's'时,<value>会包含文本会话名称,sdp_parse_line函数中会执行下面代码块将会话名称存入s->metadata的成员变量中:
case 's':
av_dict_set(&s->metadata, "title", p, 0);
break;
(三)情况三:<type>的值为'm'
<type>的值为'm'时,<value>会包含媒体描述信息,此时该行SDP格式为:m=<media> <port> <proto> <fmt> ...,sdp_parse_line函数中会执行下面代码块:
case 'm':
/* new stream */
s1->skip_media = 0;
s1->seen_fmtp = 0;
s1->seen_rtpmap = 0;
codec_type = AVMEDIA_TYPE_UNKNOWN;
get_word(st_type, sizeof(st_type), &p);
if (!strcmp(st_type, "audio")) {
codec_type = AVMEDIA_TYPE_AUDIO;
} else if (!strcmp(st_type, "video")) {
codec_type = AVMEDIA_TYPE_VIDEO;
} else if (!strcmp(st_type, "application")) {
codec_type = AVMEDIA_TYPE_DATA;
} else if (!strcmp(st_type, "text")) {
codec_type = AVMEDIA_TYPE_SUBTITLE;
}
if (codec_type == AVMEDIA_TYPE_UNKNOWN ||
!(rt->media_type_mask & (1 << codec_type)) ||
rt->nb_rtsp_streams >= s->max_streams
) {
s1->skip_media = 1;
return;
}
rtsp_st = av_mallocz(sizeof(RTSPStream));
if (!rtsp_st)
return;
rtsp_st->stream_index = -1;
dynarray_add(&rt->rtsp_streams, &rt->nb_rtsp_streams, rtsp_st);
rtsp_st->sdp_ip = s1->default_ip;
rtsp_st->sdp_ttl = s1->default_ttl;
copy_default_source_addrs(s1->default_include_source_addrs,
s1->nb_default_include_source_addrs,
&rtsp_st->include_source_addrs,
&rtsp_st->nb_include_source_addrs);
copy_default_source_addrs(s1->default_exclude_source_addrs,
s1->nb_default_exclude_source_addrs,
&rtsp_st->exclude_source_addrs,
&rtsp_st->nb_exclude_source_addrs);
get_word(buf1, sizeof(buf1), &p); /* port */
rtsp_st->sdp_port = atoi(buf1);
get_word(buf1, sizeof(buf1), &p); /* protocol */
if (!strcmp(buf1, "udp"))
rt->transport = RTSP_TRANSPORT_RAW;
else if (strstr(buf1, "/AVPF") || strstr(buf1, "/SAVPF"))
rtsp_st->feedback = 1;
/* XXX: handle list of formats */
get_word(buf1, sizeof(buf1), &p); /* format list */
rtsp_st->sdp_payload_type = atoi(buf1);
if (!strcmp(ff_rtp_enc_name(rtsp_st->sdp_payload_type), "MP2T")) {
/* no corresponding stream */
if (rt->transport == RTSP_TRANSPORT_RAW) {
if (CONFIG_RTPDEC && !rt->ts)
rt->ts = avpriv_mpegts_parse_open(s);
} else {
const RTPDynamicProtocolHandler *handler;
handler = ff_rtp_handler_find_by_id(
rtsp_st->sdp_payload_type, AVMEDIA_TYPE_DATA);
init_rtp_handler(handler, rtsp_st, NULL);
finalize_rtp_handler_init(s, rtsp_st, NULL);
}
} else if (rt->server_type == RTSP_SERVER_WMS &&
codec_type == AVMEDIA_TYPE_DATA) {
/* RTX stream, a stream that carries all the other actual
* audio/video streams. Don't expose this to the callers. */
} else {
st = avformat_new_stream(s, NULL);
if (!st)
return;
st->id = rt->nb_rtsp_streams - 1;
rtsp_st->stream_index = st->index;
st->codecpar->codec_type = codec_type;
if (rtsp_st->sdp_payload_type < RTP_PT_PRIVATE) {
const RTPDynamicProtocolHandler *handler;
/* if standard payload type, we can find the codec right now */
ff_rtp_get_codec_info(st->codecpar, rtsp_st->sdp_payload_type);
if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO &&
st->codecpar->sample_rate > 0)
avpriv_set_pts_info(st, 32, 1, st->codecpar->sample_rate);
/* Even static payload types may need a custom depacketizer */
handler = ff_rtp_handler_find_by_id(
rtsp_st->sdp_payload_type, st->codecpar->codec_type);
init_rtp_handler(handler, rtsp_st, st);
finalize_rtp_handler_init(s, rtsp_st, st);
}
if (rt->default_lang[0])
av_dict_set(&st->metadata, "language", rt->default_lang, 0);
}
/* put a default control url */
av_strlcpy(rtsp_st->control_url, rt->control_uri,
sizeof(rtsp_st->control_url));
break;
上述代码块中,首先读取出<media>的值,让变量codec_type赋值为对应的媒体类型:
/* new stream */
s1->skip_media = 0;
s1->seen_fmtp = 0;
s1->seen_rtpmap = 0;
codec_type = AVMEDIA_TYPE_UNKNOWN;
get_word(st_type, sizeof(st_type), &p);
if (!strcmp(st_type, "audio")) {
codec_type = AVMEDIA_TYPE_AUDIO;
} else if (!strcmp(st_type, "video")) {
codec_type = AVMEDIA_TYPE_VIDEO;
} else if (!strcmp(st_type, "application")) {
codec_type = AVMEDIA_TYPE_DATA;
} else if (!strcmp(st_type, "text")) {
codec_type = AVMEDIA_TYPE_SUBTITLE;
}
分配一个RTSPStream结构,RTSPStream结构体用于存贮RTSP流的信息:
rtsp_st = av_mallocz(sizeof(RTSPStream));
if (!rtsp_st)
return;
rtsp_st->stream_index = -1;
dynarray_add(&rt->rtsp_streams, &rt->nb_rtsp_streams, rtsp_st);
rtsp_st->sdp_ip = s1->default_ip;
rtsp_st->sdp_ttl = s1->default_ttl;
copy_default_source_addrs(s1->default_include_source_addrs,
s1->nb_default_include_source_addrs,
&rtsp_st->include_source_addrs,
&rtsp_st->nb_include_source_addrs);
copy_default_source_addrs(s1->default_exclude_source_addrs,
s1->nb_default_exclude_source_addrs,
&rtsp_st->exclude_source_addrs,
&rtsp_st->nb_exclude_source_addrs);
读取出<port>,即发送媒体流的传输端口,赋值给rtsp_st->sdp_port:
get_word(buf1, sizeof(buf1), &p); /* port */
rtsp_st->sdp_port = atoi(buf1);
读取出<proto>,即传输协议:
get_word(buf1, sizeof(buf1), &p); /* protocol */
if (!strcmp(buf1, "udp"))
rt->transport = RTSP_TRANSPORT_RAW;
else if (strstr(buf1, "/AVPF") || strstr(buf1, "/SAVPF"))
rtsp_st->feedback = 1;
读取出<fmt>,如果 <proto> 子字段为 “RTP/AVP ”或 “RTP/SAVP”,则 <fmt> 子字段包含 RTP 有效载荷类型编号,将其赋值给rtsp_st->sdp_payload_type:
/* XXX: handle list of formats */
get_word(buf1, sizeof(buf1), &p); /* format list */
rtsp_st->sdp_payload_type = atoi(buf1);
(四)情况四:<type>的值为'a'
<type>的值为'a'时,<value>会包含附加信息,sdp_parse_line函数中会执行下面代码块:
case 'a':
if (av_strstart(p, "control:", &p)) {
if (rt->nb_rtsp_streams == 0) {
if (!strncmp(p, "rtsp://", 7))
av_strlcpy(rt->control_uri, p,
sizeof(rt->control_uri));
} else {
char proto[32];
/* get the control url */
rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];
/* XXX: may need to add full url resolution */
av_url_split(proto, sizeof(proto), NULL, 0, NULL, 0,
NULL, NULL, 0, p);
if (proto[0] == '\0') {
/* relative control URL */
if (rtsp_st->control_url[strlen(rtsp_st->control_url)-1]!='/')
av_strlcat(rtsp_st->control_url, "/",
sizeof(rtsp_st->control_url));
av_strlcat(rtsp_st->control_url, p,
sizeof(rtsp_st->control_url));
} else
av_strlcpy(rtsp_st->control_url, p,
sizeof(rtsp_st->control_url));
}
} else if (av_strstart(p, "rtpmap:", &p) && s->nb_streams > 0) {
/* NOTE: rtpmap is only supported AFTER the 'm=' tag */
get_word(buf1, sizeof(buf1), &p);
payload_type = atoi(buf1);
rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];
if (rtsp_st->stream_index >= 0) {
st = s->streams[rtsp_st->stream_index];
sdp_parse_rtpmap(s, st, rtsp_st, payload_type, p);
}
s1->seen_rtpmap = 1;
if (s1->seen_fmtp) {
parse_fmtp(s, rt, payload_type, s1->delayed_fmtp);
}
} else if (av_strstart(p, "fmtp:", &p) ||
av_strstart(p, "framesize:", &p)) {
// let dynamic protocol handlers have a stab at the line.
get_word(buf1, sizeof(buf1), &p);
payload_type = atoi(buf1);
if (s1->seen_rtpmap) {
parse_fmtp(s, rt, payload_type, buf);
} else {
s1->seen_fmtp = 1;
av_strlcpy(s1->delayed_fmtp, buf, sizeof(s1->delayed_fmtp));
}
} else if (av_strstart(p, "ssrc:", &p) && s->nb_streams > 0) {
rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];
get_word(buf1, sizeof(buf1), &p);
rtsp_st->ssrc = strtoll(buf1, NULL, 10);
} else if (av_strstart(p, "range:", &p)) {
int64_t start, end;
// this is so that seeking on a streamed file can work.
rtsp_parse_range_npt(p, &start, &end);
s->start_time = start;
/* AV_NOPTS_VALUE means live broadcast (and can't seek) */
s->duration = (end == AV_NOPTS_VALUE) ?
AV_NOPTS_VALUE : end - start;
} else if (av_strstart(p, "lang:", &p)) {
if (s->nb_streams > 0) {
get_word(buf1, sizeof(buf1), &p);
rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];
if (rtsp_st->stream_index >= 0) {
st = s->streams[rtsp_st->stream_index];
av_dict_set(&st->metadata, "language", buf1, 0);
}
} else
get_word(rt->default_lang, sizeof(rt->default_lang), &p);
} else if (av_strstart(p, "IsRealDataType:integer;",&p)) {
if (atoi(p) == 1)
rt->transport = RTSP_TRANSPORT_RDT;
} else if (av_strstart(p, "SampleRate:integer;", &p) &&
s->nb_streams > 0) {
st = s->streams[s->nb_streams - 1];
st->codecpar->sample_rate = atoi(p);
} else if (av_strstart(p, "crypto:", &p) && s->nb_streams > 0) {
// RFC 4568
rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];
get_word(buf1, sizeof(buf1), &p); // ignore tag
get_word(rtsp_st->crypto_suite, sizeof(rtsp_st->crypto_suite), &p);
p += strspn(p, SPACE_CHARS);
if (av_strstart(p, "inline:", &p))
get_word(rtsp_st->crypto_params, sizeof(rtsp_st->crypto_params), &p);
} else if (av_strstart(p, "source-filter:", &p)) {
int exclude = 0;
get_word(buf1, sizeof(buf1), &p);
if (strcmp(buf1, "incl") && strcmp(buf1, "excl"))
return;
exclude = !strcmp(buf1, "excl");
get_word(buf1, sizeof(buf1), &p);
if (strcmp(buf1, "IN") != 0)
return;
get_word(buf1, sizeof(buf1), &p);
if (strcmp(buf1, "IP4") && strcmp(buf1, "IP6") && strcmp(buf1, "*"))
return;
// not checking that the destination address actually matches or is wildcard
get_word(buf1, sizeof(buf1), &p);
while (*p != '\0') {
rtsp_src = av_mallocz(sizeof(*rtsp_src));
if (!rtsp_src)
return;
get_word(rtsp_src->addr, sizeof(rtsp_src->addr), &p);
if (exclude) {
if (s->nb_streams == 0) {
dynarray_add(&s1->default_exclude_source_addrs, &s1->nb_default_exclude_source_addrs, rtsp_src);
} else {
rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];
dynarray_add(&rtsp_st->exclude_source_addrs, &rtsp_st->nb_exclude_source_addrs, rtsp_src);
}
} else {
if (s->nb_streams == 0) {
dynarray_add(&s1->default_include_source_addrs, &s1->nb_default_include_source_addrs, rtsp_src);
} else {
rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];
dynarray_add(&rtsp_st->include_source_addrs, &rtsp_st->nb_include_source_addrs, rtsp_src);
}
}
}
} else {
if (rt->server_type == RTSP_SERVER_WMS)
ff_wms_parse_sdp_a_line(s, p);
if (s->nb_streams > 0) {
rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];
if (rt->server_type == RTSP_SERVER_REAL)
ff_real_parse_sdp_a_line(s, rtsp_st->stream_index, p);
if (rtsp_st->dynamic_handler &&
rtsp_st->dynamic_handler->parse_sdp_a_line)
rtsp_st->dynamic_handler->parse_sdp_a_line(s,
rtsp_st->stream_index,
rtsp_st->dynamic_protocol_context, buf);
}
}
break;
1.a=rtpmap
a=rtpmap时,SDP的该行格式为:
a=rtpmap:<payload type> <encoding name>/<clock rate> [/<encoding parameters>],sdp_parse_line函数中会执行下面代码块把音视频压缩编码格式赋值给st->codecpar->codec_id,
else if (av_strstart(p, "rtpmap:", &p) && s->nb_streams > 0) {
/* NOTE: rtpmap is only supported AFTER the 'm=' tag */
get_word(buf1, sizeof(buf1), &p);
payload_type = atoi(buf1);
rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];
if (rtsp_st->stream_index >= 0) {
st = s->streams[rtsp_st->stream_index];
sdp_parse_rtpmap(s, st, rtsp_st, payload_type, p);
}
s1->seen_rtpmap = 1;
if (s1->seen_fmtp) {
parse_fmtp(s, rt, payload_type, s1->delayed_fmtp);
}
}
2.a=fmtp
a=fmtp时,SDP的该行信息的格式为:a=fmtp:<format> <format specific parameters>,sdp_parse_line函数中会执行下面代码块进行解析:
else if (av_strstart(p, "fmtp:", &p) ||
av_strstart(p, "framesize:", &p)) {
// let dynamic protocol handlers have a stab at the line.
get_word(buf1, sizeof(buf1), &p);
payload_type = atoi(buf1);
if (s1->seen_rtpmap) {
parse_fmtp(s, rt, payload_type, buf);
} else {
s1->seen_fmtp = 1;
av_strlcpy(s1->delayed_fmtp, buf, sizeof(s1->delayed_fmtp));
}
}
对于H.264视频,该行格式一般为:a=fmtp:XX packetization-mode=X; sprop-parameter-sets=XXX,XXX; profile-level-id=XXX。其解析流程可以参考:《音视频入门基础:RTP专题(6)——FFmpeg源码中,解析SDP中的packetization-mode、profile-level-id和sprop-parameter-sets实现》。