音视频入门基础: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实现》。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/11743.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python算法和数据结构刷题[1]:数组、矩阵、字符串

一画图二伪代码三写代码 LeetCode必刷100题&#xff1a;一份来自面试官的算法地图&#xff08;题解持续更新中&#xff09;-CSDN博客 算法通关手册&#xff08;LeetCode&#xff09; | 算法通关手册&#xff08;LeetCode&#xff09; (itcharge.cn) 面试经典 150 题 - 学习计…

【PyTorch】7.自动微分模块:开启神经网络 “进化之门” 的魔法钥匙

目录 1. 梯度基本计算 2. 控制梯度计算 3. 梯度计算注意 4. 小节 个人主页&#xff1a;Icomi 专栏地址&#xff1a;PyTorch入门 在深度学习蓬勃发展的当下&#xff0c;PyTorch 是不可或缺的工具。它作为强大的深度学习框架&#xff0c;为构建和训练神经网络提供了高效且灵活…

C++基础day1

前言&#xff1a;谢谢阿秀&#xff0c;指路阿秀的学习笔记 一、基础语法 1.构造和析构: 类的构造函数是一种特殊的函数&#xff0c;在创建一个新的对象时调用。类的析构函数也是一种特殊的函数&#xff0c;在删除所创建的对象时调用。 构造顺序&#xff1a;父类->子类 析…

深入理解linux中的文件(上)

1.前置知识&#xff1a; &#xff08;1&#xff09;文章 内容 属性 &#xff08;2&#xff09;访问文件之前&#xff0c;都必须打开它&#xff08;打开文件&#xff0c;等价于把文件加载到内存中&#xff09; 如果不打开文件&#xff0c;文件就在磁盘中 &#xff08;3&am…

Spring Boot Web项目全解析:从前端请求到后端处理

第一章&#xff1a;对静态资源的整合 ConfigurationProperties(prefix"spring.resources", ignoreUnknownFieldsfalse)public class ResourceProperties implements ResourceLoaderAware {//可以设置和静态资源有关的参数&#xff0c;缓存时间等WebMvcAuotConfigura…

Java创建对象有几种方式?

大家好&#xff0c;我是锋哥。今天分享关于【Java创建对象有几种方式?】面试题。希望对大家有帮助&#xff1b; Java创建对象有几种方式? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在 Java 中&#xff0c;创建对象有几种常见的方式&#xff0c;具体如下&…

基于Spring Security 6的OAuth2 系列之八 - 授权服务器--Spring Authrization Server的基本原理

之所以想写这一系列&#xff0c;是因为之前工作过程中使用Spring Security OAuth2搭建了网关和授权服务器&#xff0c;但当时基于spring-boot 2.3.x&#xff0c;其默认的Spring Security是5.3.x。之后新项目升级到了spring-boot 3.3.0&#xff0c;结果一看Spring Security也升级…

深入浅出并查集(不相交集合实现思路)

引言 并查集&#xff08;Disjoint Set Union&#xff0c;简称DSU&#xff09;是一种用于处理一些不交集的合并及查询问题。它主要支持两种操作&#xff1a;查找&#xff08;Find&#xff09;和合并&#xff08;Union&#xff09;。 查找&#xff1a;确定某个元素属于哪一个子…

如何运行Composer安装PHP包 安装JWT库

1. 使用Composer Composer是PHP的依赖管理工具&#xff0c;它允许你轻松地安装和管理PHP包。对于JWT&#xff0c;你可以使用firebase/php-jwt这个库&#xff0c;这是由Firebase提供的官方库。 安装Composer&#xff08;如果你还没有安装的话&#xff09;&#xff1a; 访问Co…

享元模式——C++实现

目录 1. 享元模式简介 2. 代码示例 1. 享元模式简介 享元模式是一种结构型模式。 享元模式用于缓存共享对象&#xff0c;降低内存消耗。共享对象相同的部分&#xff0c;避免创建大量相同的对象&#xff0c;减少内存占用。 享元模式需要将对象分成内部状态和外部状态两个部分…

网络原理(4)—— 网络层详解

目录 一. IP协议报头结构 二. 地址管理 2.1 路由器 2.1.1 路由选择 2.1.2 WAN口&#xff08;Wide Area Network&#xff09; 2.1.3 LAN口&#xff08;Local Area Network&#xff09; 2.1.4 WLAN口&#xff08;Wireless Local Area Network&#xff09; 2.2 网段划分…

基于深度学习的输电线路缺陷检测算法研究(论文+源码)

输电线路关键部件的缺陷检测对于电网安全运行至关重要&#xff0c;传统方法存在效率低、准确性不高等问题。本研究探讨了利用深度学习技术进行输电线路关键组件的缺陷检测&#xff0c;目的是提升检测的效率与准确度。选用了YOLOv8模型作为基础&#xff0c;并通过加入CA注意力机…

Android --- handler详解

handler 理解 handler 是一套Android 消息传递机制&#xff0c;主要用于线程间通信。 tips&#xff1a; binder/socket 用于进程间通信。 参考&#xff1a; Android 进程间通信-CSDN博客 handler 就是主线程在起了一个子线程&#xff0c;子线程运行并生成message &#xff0c;l…

vim如何解决‘’文件非法关闭后,遗留交换文件‘’的问题

过程描述&#xff1a; 由于我修改文件时&#xff08;一定得修改了文件&#xff0c;不做任何修改不会产生这个问题&#xff09;的非法关闭&#xff0c;比如直接关闭虚拟机&#xff0c;或者直接断开远程工具的远程连接&#xff0c;产生了以下遗留交换文件的问题&#xff1a; 点击…

六十分之三十七——一转眼、时光飞逝

一、目标 明确可落地&#xff0c;对于自身执行完成需要一定的努力才可以完成的 1.第三版分组、激励、立体化权限、智能设备、AIPPT做课 2.8本书 3.得到&#xff1a;头条、吴军来信2、卓克科技参考3 4.总结思考 二、计划 科学规律的&#xff0c;要结合番茄工作法、快速阅读、…

Linux环境下的Java项目部署技巧:安装 Mysql

查看 myslq 是否安装&#xff1a; rpm -qa|grep mysql 如果已经安装&#xff0c;可执行命令来删除软件包&#xff1a; rpm -e --nodeps 包名 下载 repo 源&#xff1a; http://dev.mysql.com/get/mysql80-community-release-el7-7.noarch.rpm 执行命令安装 rpm 源(根据下载的…

css三角图标

案例三角&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><s…

Shadow DOM举例

这东西具有隔离效果&#xff0c;对于一些插件需要append一些div倒是不错的选择 <!DOCTYPE html> <html lang"zh-CN"> <head> <meta charset"utf-8"> <title>演示例子</title> </head> <body> <style&g…

万字长文深入浅出负载均衡器

前言 本篇博客主要分享Load Balancing&#xff08;负载均衡&#xff09;&#xff0c;将从以下方面循序渐进地全面展开阐述&#xff1a; 介绍什么是负载均衡介绍常见的负载均衡算法 负载均衡简介 初识负载均衡 负载均衡是系统设计中的一个关键组成部分&#xff0c;它有助于…

ChatGPT-4o和ChatGPT-4o mini的差异点

在人工智能领域&#xff0c;OpenAI再次引领创新潮流&#xff0c;近日正式发布了其最新模型——ChatGPT-4o及其经济实惠的小型版本ChatGPT-4o Mini。这两款模型虽同属于ChatGPT系列&#xff0c;但在性能、应用场景及成本上展现出显著的差异。本文将通过图文并茂的方式&#xff0…