音视频入门基础:RTP专题(5)——FFmpeg源码中,解析SDP的实现
一、引言
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));} elseav_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);}} elseget_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 4568rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];get_word(buf1, sizeof(buf1), &p); // ignore tagget_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 wildcardget_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 occursint 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));} elseav_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);}} elseget_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 4568rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];get_word(buf1, sizeof(buf1), &p); // ignore tagget_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 wildcardget_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实现》。
相关文章:
音视频入门基础:RTP专题(5)——FFmpeg源码中,解析SDP的实现
一、引言 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…...
计算机网络 应用层 笔记 (电子邮件系统,SMTP,POP3,MIME,IMAP,万维网,HTTP,html)
电子邮件系统: SMTP协议 基本概念 工作原理 连接建立: 命令交互 客户端发送命令: 服务器响应: 邮件传输: 连接关闭: 主要命令 邮件发送流程 SMTP的缺点: MIME: POP3协议 基本概念…...
【视频+图文详解】HTML基础3-html常用标签
图文教程 html常用标签 常用标签 1. 文档结构 <!DOCTYPE html>:声明HTML文档类型。<html>:定义HTML文档的根元素。<head>:定义文档头部,包含元数据。<title>:设置网页标题,浏览…...
FreeRTOS学习 --- 消息队列
队列简介 队列是任务到任务、任务到中断、中断到任务数据交流的一种机制(消息传递) 全局变量的弊端:数据无保护,导致数据不安全,当多个任务同时对该变量操作时,数据易受损 使用队列的情况如下:…...
PHP If...Else 语句详解
PHP If...Else 语句详解 引言 在PHP编程中,if...else语句是流程控制的重要组成部分,它允许程序根据条件判断执行不同的代码块。本文将详细解析PHP中的if...else语句,包括其基本用法、高级技巧以及注意事项。 一、基本用法 if...else语句的…...
pytorch使用SVM实现文本分类
人工智能例子汇总:AI常见的算法和例子-CSDN博客 完整代码: import torch import torch.nn as nn import torch.optim as optim import jieba import numpy as np from sklearn.model_selection import train_test_split from sklearn.feature_extract…...
安卓(android)读取手机通讯录【Android移动开发基础案例教程(第2版)黑马程序员】
一、实验目的(如果代码有错漏,可在代码地址查看) 1.熟悉内容提供者(Content Provider)的概念和作用。 2.掌握内容提供者的创建和使用方法。 4.掌握内容URI的结构和用途。 二、实验条件 1.熟悉内容提供者的工作原理。 2.掌握内容提供者访问其…...
【Qt】常用的容器
Qt提供了多个基于模板的容器类,这些容器类可用于存储指定类型的数据项。例如常用的字符串列表类 QStringList 可用来操作一个 QList<QString>列表。 Qt的容器类比标准模板库(standard template library,STL)中的容器类更轻巧、使用更安全且更易于使…...
基于UKF-IMM无迹卡尔曼滤波与交互式多模型的轨迹跟踪算法matlab仿真,对比EKF-IMM和UKF
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于UKF-IMM无迹卡尔曼滤波与交互式多模型的轨迹跟踪算法matlab仿真,对比EKF-IMM和UKF。 2.测试软件版本以及运行结果展示 MATLAB2022A版本运行 3.核心程序 .…...
分布式事务组件Seata简介与使用,搭配Nacos统一管理服务端和客户端配置
文章目录 一. Seata简介二. 官方文档三. Seata分布式事务代码实现0. 环境简介1. 添加undo_log表2. 添加依赖3. 添加配置4. 开启Seata事务管理5. 启动演示 四. Seata Server配置Nacos1. 修改配置类型2. 创建Nacos配置 五. Seata Client配置Nacos1. 增加Seata关联Nacos的配置2. 在…...
JavaScript常用的内置构造函数
JavaScript作为一种广泛应用的编程语言,提供了丰富的内置构造函数,帮助开发者处理不同类型的数据和操作。这些内置构造函数在创建和操作对象时非常有用。本文将详细介绍JavaScript中常用的内置构造函数及其用途。 常用内置构造函数概述 1. Object Obj…...
25寒假算法刷题 | Day1 | LeetCode 240. 搜索二维矩阵 II,148. 排序链表
目录 240. 搜索二维矩阵 II题目描述题解 148. 排序链表题目描述题解 240. 搜索二维矩阵 II 点此跳转题目链接 题目描述 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性: 每行的元素从左到右升序排列。每列的元素从上到…...
MQTT知识
MQTT协议 MQTT 是一种基于发布/订阅模式的轻量级消息传输协议,专门针对低带宽和不稳定网络环境的物联网应用而设计,可以用极少的代码为联网设备提供实时可靠的消息服务。MQTT 协议广泛应用于物联网、移动互联网、智能硬件、车联网、智慧城市、远程医疗、…...
【机器学习与数据挖掘实战】案例11:基于灰色预测和SVR的企业所得税预测分析
【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈机器学习与数据挖掘实战 ⌋ ⌋ ⌋ 机器学习是人工智能的一个分支,专注于让计算机系统通过数据学习和改进。它利用统计和计算方法,使模型能够从数据中自动提取特征并做出预测或决策。数据挖掘则是从大型数据集中发现模式、关联…...
新一代搜索引擎,是 ES 的15倍?
Manticore Search介绍 Manticore Search 是一个使用 C 开发的高性能搜索引擎,创建于 2017 年,其前身是 Sphinx Search 。Manticore Search 充分利用了 Sphinx,显着改进了它的功能,修复了数百个错误,几乎完全重写了代码…...
使用 Context API 管理临时状态,避免 Redux/Zustand 的持久化陷阱
在开发 React Native 应用时,我们经常需要管理全局状态,比如用户信息、主题设置、网络状态等。而对于某些临时状态,例如 数据同步进行中的状态 (isSyncing),我们应该选择什么方式来管理它? 在项目开发过程中ÿ…...
PyTorch框架——基于深度学习YOLOv8神经网络学生课堂行为检测识别系统
基于YOLOv8深度学习的学生课堂行为检测识别系统,其能识别三种学生课堂行为:names: [举手, 读书, 写字] 具体图片见如下: 第一步:YOLOv8介绍 YOLOv8 是 ultralytics 公司在 2023 年 1月 10 号开源的 YOLOv5 的下一个重大更新版本…...
word2vec 实战应用介绍
Word2Vec 是一种由 Google 在 2013 年推出的重要词嵌入模型,通过将单词映射为低维向量,实现了对自然语言处理任务的高效支持。其核心思想是利用深度学习技术,通过训练大量文本数据,将单词表示为稠密的向量形式,从而捕捉单词之间的语义和语法关系。以下是关于 Word2Vec 实战…...
C# 操作符重载对象详解
.NET学习资料 .NET学习资料 .NET学习资料 一、操作符重载的概念 在 C# 中,操作符重载允许我们为自定义的类或结构体定义操作符的行为。通常,我们熟悉的操作符,如加法()、减法(-)、乘法&#…...
python学opencv|读取图像(五十四)使用cv2.blur()函数实现图像像素均值处理
【1】引言 前序学习进程中,对图像的操作均基于各个像素点上的BGR值不同而展开。 对于彩色图像,每个像素点上的BGR值为三个整数,因为是三通道图像;对于灰度图像,各个像素上的BGR值是一个整数,因为这是单通…...
Cursor实现用excel数据填充word模版的方法
cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...
VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...
蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...
