当前位置: 首页 > news >正文

音视频入门基础: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中&#xff1a; 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)

电子邮件系统&#xff1a; SMTP协议 基本概念 工作原理 连接建立&#xff1a; 命令交互 客户端发送命令&#xff1a; 服务器响应&#xff1a; 邮件传输&#xff1a; 连接关闭&#xff1a; 主要命令 邮件发送流程 SMTP的缺点: MIME&#xff1a; POP3协议 基本概念…...

【视频+图文详解】HTML基础3-html常用标签

图文教程 html常用标签 常用标签 1. 文档结构 <!DOCTYPE html>&#xff1a;声明HTML文档类型。<html>&#xff1a;定义HTML文档的根元素。<head>&#xff1a;定义文档头部&#xff0c;包含元数据。<title>&#xff1a;设置网页标题&#xff0c;浏览…...

FreeRTOS学习 --- 消息队列

队列简介 队列是任务到任务、任务到中断、中断到任务数据交流的一种机制&#xff08;消息传递&#xff09; 全局变量的弊端&#xff1a;数据无保护&#xff0c;导致数据不安全&#xff0c;当多个任务同时对该变量操作时&#xff0c;数据易受损 使用队列的情况如下&#xff1a;…...

PHP If...Else 语句详解

PHP If...Else 语句详解 引言 在PHP编程中&#xff0c;if...else语句是流程控制的重要组成部分&#xff0c;它允许程序根据条件判断执行不同的代码块。本文将详细解析PHP中的if...else语句&#xff0c;包括其基本用法、高级技巧以及注意事项。 一、基本用法 if...else语句的…...

pytorch使用SVM实现文本分类

人工智能例子汇总&#xff1a;AI常见的算法和例子-CSDN博客 完整代码&#xff1a; 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版)黑马程序员】

一、实验目的&#xff08;如果代码有错漏&#xff0c;可在代码地址查看&#xff09; 1.熟悉内容提供者(Content Provider)的概念和作用。 2.掌握内容提供者的创建和使用方法。 4.掌握内容URI的结构和用途。 二、实验条件 1.熟悉内容提供者的工作原理。 2.掌握内容提供者访问其…...

【Qt】常用的容器

Qt提供了多个基于模板的容器类&#xff0c;这些容器类可用于存储指定类型的数据项。例如常用的字符串列表类 QStringList 可用来操作一个 QList<QString>列表。 Qt的容器类比标准模板库(standard template library&#xff0c;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作为一种广泛应用的编程语言&#xff0c;提供了丰富的内置构造函数&#xff0c;帮助开发者处理不同类型的数据和操作。这些内置构造函数在创建和操作对象时非常有用。本文将详细介绍JavaScript中常用的内置构造函数及其用途。 常用内置构造函数概述 1. Object Obj…...

25寒假算法刷题 | Day1 | LeetCode 240. 搜索二维矩阵 II,148. 排序链表

目录 240. 搜索二维矩阵 II题目描述题解 148. 排序链表题目描述题解 240. 搜索二维矩阵 II 点此跳转题目链接 题目描述 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。每列的元素从上到…...

MQTT知识

MQTT协议 MQTT 是一种基于发布/订阅模式的轻量级消息传输协议&#xff0c;专门针对低带宽和不稳定网络环境的物联网应用而设计&#xff0c;可以用极少的代码为联网设备提供实时可靠的消息服务。MQTT 协议广泛应用于物联网、移动互联网、智能硬件、车联网、智慧城市、远程医疗、…...

【机器学习与数据挖掘实战】案例11:基于灰色预测和SVR的企业所得税预测分析

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈机器学习与数据挖掘实战 ⌋ ⌋ ⌋ 机器学习是人工智能的一个分支,专注于让计算机系统通过数据学习和改进。它利用统计和计算方法,使模型能够从数据中自动提取特征并做出预测或决策。数据挖掘则是从大型数据集中发现模式、关联…...

新一代搜索引擎,是 ES 的15倍?

Manticore Search介绍 Manticore Search 是一个使用 C 开发的高性能搜索引擎&#xff0c;创建于 2017 年&#xff0c;其前身是 Sphinx Search 。Manticore Search 充分利用了 Sphinx&#xff0c;显着改进了它的功能&#xff0c;修复了数百个错误&#xff0c;几乎完全重写了代码…...

使用 Context API 管理临时状态,避免 Redux/Zustand 的持久化陷阱

在开发 React Native 应用时&#xff0c;我们经常需要管理全局状态&#xff0c;比如用户信息、主题设置、网络状态等。而对于某些临时状态&#xff0c;例如 数据同步进行中的状态 (isSyncing)&#xff0c;我们应该选择什么方式来管理它&#xff1f; 在项目开发过程中&#xff…...

PyTorch框架——基于深度学习YOLOv8神经网络学生课堂行为检测识别系统

基于YOLOv8深度学习的学生课堂行为检测识别系统&#xff0c;其能识别三种学生课堂行为&#xff1a;names: [举手, 读书, 写字] 具体图片见如下&#xff1a; 第一步&#xff1a;YOLOv8介绍 YOLOv8 是 ultralytics 公司在 2023 年 1月 10 号开源的 YOLOv5 的下一个重大更新版本…...

word2vec 实战应用介绍

Word2Vec 是一种由 Google 在 2013 年推出的重要词嵌入模型,通过将单词映射为低维向量,实现了对自然语言处理任务的高效支持。其核心思想是利用深度学习技术,通过训练大量文本数据,将单词表示为稠密的向量形式,从而捕捉单词之间的语义和语法关系。以下是关于 Word2Vec 实战…...

C# 操作符重载对象详解

.NET学习资料 .NET学习资料 .NET学习资料 一、操作符重载的概念 在 C# 中&#xff0c;操作符重载允许我们为自定义的类或结构体定义操作符的行为。通常&#xff0c;我们熟悉的操作符&#xff0c;如加法&#xff08;&#xff09;、减法&#xff08;-&#xff09;、乘法&#…...

python学opencv|读取图像(五十四)使用cv2.blur()函数实现图像像素均值处理

【1】引言 前序学习进程中&#xff0c;对图像的操作均基于各个像素点上的BGR值不同而展开。 对于彩色图像&#xff0c;每个像素点上的BGR值为三个整数&#xff0c;因为是三通道图像&#xff1b;对于灰度图像&#xff0c;各个像素上的BGR值是一个整数&#xff0c;因为这是单通…...

Java 语言特性(面试系列2)

一、SQL 基础 1. 复杂查询 &#xff08;1&#xff09;连接查询&#xff08;JOIN&#xff09; 内连接&#xff08;INNER JOIN&#xff09;&#xff1a;返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

label-studio的使用教程(导入本地路径)

文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...

练习(含atoi的模拟实现,自定义类型等练习)

一、结构体大小的计算及位段 &#xff08;结构体大小计算及位段 详解请看&#xff1a;自定义类型&#xff1a;结构体进阶-CSDN博客&#xff09; 1.在32位系统环境&#xff0c;编译选项为4字节对齐&#xff0c;那么sizeof(A)和sizeof(B)是多少&#xff1f; #pragma pack(4)st…...

java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别

UnsatisfiedLinkError 在对接硬件设备中&#xff0c;我们会遇到使用 java 调用 dll文件 的情况&#xff0c;此时大概率出现UnsatisfiedLinkError链接错误&#xff0c;原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用&#xff0c;结果 dll 未实现 JNI 协…...

学校招生小程序源码介绍

基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码&#xff0c;专为学校招生场景量身打造&#xff0c;功能实用且操作便捷。 从技术架构来看&#xff0c;ThinkPHP提供稳定可靠的后台服务&#xff0c;FastAdmin加速开发流程&#xff0c;UniApp则保障小程序在多端有良好的兼…...

工程地质软件市场:发展现状、趋势与策略建议

一、引言 在工程建设领域&#xff0c;准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具&#xff0c;正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...

【git】把本地更改提交远程新分支feature_g

创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

以光量子为例,详解量子获取方式

光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学&#xff08;silicon photonics&#xff09;的光波导&#xff08;optical waveguide&#xff09;芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中&#xff0c;光既是波又是粒子。光子本…...

网站指纹识别

网站指纹识别 网站的最基本组成&#xff1a;服务器&#xff08;操作系统&#xff09;、中间件&#xff08;web容器&#xff09;、脚本语言、数据厍 为什么要了解这些&#xff1f;举个例子&#xff1a;发现了一个文件读取漏洞&#xff0c;我们需要读/etc/passwd&#xff0c;如…...