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

音视频入门基础: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…...

Android开发工作经历整理

一.无人机应用软件开发 集成大疆官网的DJIMobileSDK到AS中编写软件&#xff0c;操控无人机执行多个航点任务。集成OpenCV库进行图像识别&#xff0c;通过获取参数&#xff0c;根据算法执行sdk&#xff0c;使无人机降落到机库&#xff0c;并执行后续的换电操作。待无人机就绪后…...

C++中常用的十大排序方法之4——希尔排序

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【&#x1f60a;///计算机爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于C中常用的排序方法之4——希尔排序的相…...

解决注入线程池的栈溢出问题

文章目录 1.问题产生2.问题解决 1.问题产生 在使用sleuth的时候&#xff0c;需要注入线程池&#xff0c;他才会自动包装&#xff0c;实现traceId的传递&#xff0c;但是突然启动时出现了栈溢出的问题 2.问题解决 根据报错&#xff0c;发现是Gson序列化相关的问题&#xff0c…...

自动驾驶---两轮自行车的自主导航

1 背景 无人驾驶汽车最早出现在DARPA的比赛中&#xff0c;从那个时刻开始&#xff0c;逐渐引起全球学者的注意&#xff0c;于是从上个世纪开始各大高校院所开始了无人汽车的研发。直到这两年&#xff0c;无人驾驶汽车才开始走进寻常百姓家&#xff0c;虽然目前市面上的乘用车还…...

哈夫曼树并查集

&#xff08;1&#xff09;哈夫曼树 特殊概念&#xff1a; 1.结点的权&#xff1a;表示结点树的重要性 2.带权路径长度&#xff1a;从树的根到该节点的路径长度&#xff08;经过的边数&#xff09;与该节点上权值的乘积 2.树的带权路径长度&#xff1a;该树的所有叶子节点的…...

PyTorch数据建模

回归分析 import torch import numpy as np import pandas as pd from torch.utils.data import DataLoader,TensorDataset import time strat = time.perf_counter()...

在 Ubuntu 上安装 Node.js 23.x

在 Ubuntu 上安装 Node.js 23.x 前提条件安装步骤1. 下载设置脚本2. 运行设置脚本3. 安装 Node.js4. 验证安装 参考链接总结 在现代 web 开发中&#xff0c;Node.js 是一个不可或缺的工具。它提供了一个强大的 JavaScript 运行时环境&#xff0c;使得开发人员可以在服务器端使用…...

SQL范式与反范式_优化数据库性能

1. 引言 什么是SQL范式 SQL范式是指数据库设计中的一系列规则和标准,旨在减少数据冗余、提高数据完整性和一致性。常见的范式包括第一范式(1NF)、第二范式(2NF)、第三范式(3NF)和BCNF(Boyce-Codd范式)。 什么是SQL反范式 SQL反范式是指在满足范式要求的基础上,有…...

hunyuan 混元学习

使用了5个subset,也是用了text-image和text-video进行训练的 也是进行了复杂的视频选择。同movie gen. 也进行了模型切断&#xff0c;用拉普拉斯算子找到最清晰的一帧作为训练的起始 训练了不同的模型去选择数据&#xff0c;比如用Dover去选择美观度比较好的数据&#xff0c…...

四、GPIO中断实现按键功能

4.1 GPIO简介 输入输出&#xff08;I/O&#xff09;是一个非常重要的概念。I/O泛指所有类型的输入输出端口&#xff0c;包括单向的端口如逻辑门电路的输入输出管脚和双向的GPIO端口。而GPIO&#xff08;General-Purpose Input/Output&#xff09;则是一个常见的术语&#xff0c…...

.Net / C# 繁体中文 与 简体中文 互相转换, 支持地方特色词汇

版本号 Nuget 搜索 “OpenCCNET”, 注意别找错, 好多库的名字都差不多 支持 “繁,简” 的互相转换, 支持多个地区常用词汇的转换, 还支持 日文的新旧转换. OpenCC 在 .Net 中的实现 https://github.com/CosineG/OpenCC.NET <PackageReference Include"OpenCCNET"…...

一元函数微积分的几何应用:二维平面光滑曲线的曲率公式

文章目录 前言曲率和曲率半径的定义曲率计算公式参数方程形式直角坐标显式方程形式极坐标形式向量形式 前言 本文将介绍二维平面光滑曲线的曲率定义以及不同形式的曲率及曲率半径公式的推导。 曲率和曲率半径的定义 &#xff08;关于二维平面光滑曲线的定义以及弧长公式请参…...

数据结构与算法之异步: LeetCode 1114. 按序打印 (Ts版)

按序打印 https://leetcode.cn/problems/print-in-order/description/ 描述 给你一个类&#xff1a; public class Foo {public void first() { print("first"); }public void second() { print("second"); }public void third() { print("third&qu…...

python:求解爱因斯坦场方程

在物理学中&#xff0c;爱因斯坦的广义相对论&#xff08;General Relativity&#xff09;是描述引力如何作用于时空的理论。广义相对论由爱因斯坦在1915年提出&#xff0c;并被阿尔伯特爱因斯坦、纳森罗森和纳尔逊曼德尔斯塔姆共同发展。广义相对论的核心方程是爱因斯坦场方程…...

PostgreSQL 数据备份与恢复:掌握 pg_dump 和 pg_restore 的最佳实践

title: PostgreSQL 数据备份与恢复:掌握 pg_dump 和 pg_restore 的最佳实践 date: 2025/1/28 updated: 2025/1/28 author: cmdragon excerpt: 在数据库管理中,备份与恢复是确保数据安全和业务连续性的关键措施。PostgreSQL 提供了一系列工具,以便于数据库管理员对数据进行…...

位运算的概念

文章目录 整数在计算机中的表示二进制表示有符号类型和无符号类型机器数和真值原码、反码和补码原码、反码和补码的表示方法计算机中的表示 位运算与、或、异或和取反移位运算移位运算与乘除法的关系位运算的性质 目录 整数在计算机中的表示 二进制表示 程序中的所有数在计算…...

自主Shell命令行解释器

什么是命令行 我们一直使用的"ls","cd","pwd","mkdir"等命令&#xff0c;都是在命令行上输入的&#xff0c;我们之前对于命令行的理解&#xff1a; 命令行是干啥的&#xff1f;是为我们做命令行解释的。 命令行这个东西实际上是我们…...

Vue.js 的介绍与组件开发初步

Vue.js 的介绍与组件开发初步 Vue.js 的介绍与组件开发初步引言第一部分&#xff1a;Vue.js 基础入门1.1 什么是 Vue.js&#xff1f;1.2 搭建 Vue.js 开发环境安装 Node.js 和 npm安装 Vue CLI创建新项目运行示例 1.3 第一个 Vue.js 示例 第二部分&#xff1a;Vue.js 组件开发基…...

XCCL、NCCL、HCCL通信库

XCCL提供的基本能力 XCCL提供的基本能力 不同的XCCL 针对不同的网络拓扑&#xff0c;实现的是不同的优化算法的&#xff08;不同CCL库最大的区别就是这&#xff09; 不同CCL库还会根据自己的硬件、系统&#xff0c;在底层上面对一些相对应的改动&#xff1b; 但是对上的API接口…...

Python教学:文档处理及箱线图等

代码1&#xff1a; import os import pandas as pd import numpy as py import os.path from os import listdir import openpyxl from openpyxl import Workbook import re import matplotlib.pyplot as plt # 导入matplotlib的绘图模块&#xff0c;用于可视化 cwdos.getcwd…...

【Redis】安装配置Redis超详细教程 / Linux版

Linux安装配置Redis超详细教程 安装redis依赖安装redis启动redis停止redisredis.conf常见配置设置redis为后台启动修改redis监听地址设置工作目录修改密码监听的端口号数据库数量设置redis最大内存设置日志文件设置redis开机自动启动 学习视频&#xff1a;黑马程序员Redis入门到…...

【大数据技术】教程05:本机DataGrip远程连接虚拟机MySQL/Hive

本机DataGrip远程连接虚拟机MySQL/Hive datagrip-2024.3.4VMware Workstation Pro 16CentOS-Stream-10-latest-x86_64-dvd1.iso写在前面 本文主要介绍如何使用本机的DataGrip连接虚拟机的MySQL数据库和Hive数据库,提高编程效率。 安装DataGrip 请按照以下步骤安装DataGrip软…...

springboot 启动原理

目标&#xff1a; SpringBootApplication注解认识了解SpringBoot的启动流程 了解SpringFactoriesLoader对META-INF/spring.factories的反射加载认识AutoConfigurationImportSelector这个ImportSelector starter的认识和使用 目录 SpringBoot 启动原理SpringBootApplication 注…...

llama.cpp GGUF 模型格式

llama.cpp GGUF 模型格式 1. Specification1.1. GGUF Naming Convention (命名规则)1.1.1. Validating Above Naming Convention 1.2. File Structure 2. Standardized key-value pairs2.1. General2.1.1. Required2.1.2. General metadata2.1.3. Source metadata 2.2. LLM2.2.…...

使用Pytorch训练一个图像分类器

一、准备数据集 一般来说&#xff0c;当你不得不与图像、文本或者视频资料打交道时&#xff0c;会选择使用python的标准库将原始数据加载转化成numpy数组&#xff0c;甚至可以继续转换成torch.*Tensor。 对图片而言&#xff0c;可以使用Pillow库和OpenCV库对视频而言&#xf…...

PythonStyle MVC 开发框架

在 Python 中&#xff0c;MVC&#xff08;Model - View - Controller&#xff0c;模型 - 视图 - 控制器&#xff09;是一种常见的软件设计模式&#xff0c;它将应用程序分为三个主要部分&#xff0c;各自承担不同的职责&#xff0c;以提高代码的可维护性、可扩展性和可测试性。…...

HTTP协议的无状态和无连接

无连接 ①无连接的含义 这里所说的无连接并不是指不连接&#xff0c;客户与服务器之间的HTTP连接是一种一次性连接&#xff0c;它限制每次连接只处理一个请求&#xff0c;当服务器返回本次请求的应答后便立即关闭连接&#xff0c;下次请求再重新建立连接。这种一次性连接主要考…...

毫秒级响应的VoIP中的系统组合推荐

在高并发、低延迟、毫秒级响应的 VoIP 场景中&#xff0c;选择合适的操作系统组合至关重要。以下是针对 Ubuntu linux-lowlatency、CentOS Stream kernel-rt 和 Debian 自定义 PREEMPT_RT 的详细对比及推荐&#xff1a; 1. 系统组合对比 特性Ubuntu linux-lowlatencyCentO…...

PWN--格式化字符串

简介 ‌格式化字符串‌是指在编程过程中&#xff0c;通过特殊的占位符将相关对应的信息整合或提取的规则字符串。格式化字符串包括格式化输入和格式化输出&#xff0c;其本质是程序员调用相关格式化字符串的操作协议规定。错误的或不当的信息配置可能导致程序运行失效或产生未…...