音视频入门基础: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…...
MyBatis XML文件配置
目录 一、 配置连接字符串和MyBatis 二、书写持久层代码 2.1 添加Mapper接口 2.2 添加UserlnfoXMLMapper.xml 三、增删改查 3.1 、增(Insert) 3.2、删(Delete) 3.3、改 (Update) 3.4、查 (Select) MyBatisXML的方式需要以下两步&am…...
【Leetcode 热题 100】1143. 最长公共子序列
问题背景 给定两个字符串 t e x t 1 text_1 text1 和 t e x t 2 text_2 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 0 0。 一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变…...
【算法】动态规划专题④ ——LCS(最长公共子序列)+ LPS(最长回文子序列) python
目录 前置知识LCS举一反三LPS 前置知识 【算法】动态规划专题③ ——二维DP python 子序列定义为: 不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。 LCS 最长公共子序列 https://www.lanqiao.cn/problems/1189/learning/?p…...
Cesium点集中获取点的id,使用viewer.value.entities.getById报错的解决方法
错误代码: viewer.value.entities.getById(pickedObject.id) 报错: 可以正常获取movement.position但是一直出现如下报错,无法获得航点的id,通过断点定位为 viewer.value.entities.getById(pickedObject.id)导致的报错 解决方…...
360手机刷机 360手机解Bootloader 360手机ROOT
360手机刷机 360手机解Bootloader 360手机ROOT 问:360手机已停产,现在和以后,能刷机吗? 答:360手机,是肯定能刷机的 360手机资源下载网站 360手机-360手机刷机RootTwrp 360os.top 360rom.github.io 一、…...
深度探索DeepSeek-R1:AI大模型的本地应用与个人知识库构建
深度探索DeepSeek-R1:AI大模型的本地应用与个人知识库构建 引言 在当今这个信息爆炸的时代,如何高效地存储、处理和获取知识,已经成为每个人面临的挑战。想象一下,如果你能在没有互联网连接的情况下,构建一个属于自己…...
LabVIEW图像采集与应变场测量系统
开发了一种基于LabVIEW的图像采集与应变场测量系统,提供一种高精度、非接触式的测量技术,用于监测物体的全场位移和应变。系统整合了实时监控、数据记录和自动对焦等功能,适用于工程应用和科学研究。 项目背景 传统的位移和应变测量技术往往…...
解决DeepSeek服务器繁忙问题:本地部署与优化方案
deepseek服务器崩了,手把手教你如何在手机端部署一个VIP通道! 引言 随着人工智能技术的快速发展,DeepSeek等大语言模型的应用越来越广泛。然而,许多用户在使用过程中遇到了服务器繁忙、响应缓慢等问题。本文将探讨如何通过本地部…...
今日AI和商界事件(2025-02-05)
今日AI领域的相关事件主要包括以下几个方面: 一、DeepSeek引发全球关注 性能与成本优势: DeepSeek推出的R1模型性能出色,成本较低,在全球AI行业引发震动。该模型在数学、代码处理等方面性能优异,受到广泛赞誉。 平台…...
SQL 秒变 ER 图 sql转er图
🚀SQL 秒变 ER 图,校园小助手神了! 学数据库的宝子们集合🙋♀️ 是不是每次碰到 SQL 转 ER 图就头皮发麻?看着密密麻麻的代码,脑子直接死机,好不容易理清一点头绪,又被复杂的表关…...
SQL server 创建DB Link 详解
在日常工作中,经常涉及到跨库操作,为使跨数据库的操作变得更加灵活高效,我们可以在 SQL Server 中建立数据库链接( DB Link),实现 SQL Server 数据库与其他数据库(如 Oracle, MySQL 等ÿ…...
25.2.5学习记录
今天主要学的是哈希表的理论知识,但是都是c实现,C语言的代码实现还没有完全搞明白。 在写题的时候,懵懂的学着正确代码,用C语言模拟实现哈希表去解题。 在哈希表的理论知识中,学到哈希函数,了解哈希冲突产…...
C# List 列表综合运用实例⁓Hypak原始数据处理编程小结
C# List 列表综合运用实例⁓Hypak原始数据处理编程小结 1、一个数组解决很麻烦引出的问题1.1、RAW 文件尾部数据如下:1.2、自定义标头 ADD 或 DEL 的数据结构如下: 2、程序 C# 源代码的编写和剖析2.1、使用 ref 关键字,通过引用将参数传递,以…...
不可信的搜索路径(CWE-426)
漏洞描述:程序使用关键资源时(如动态链接库、执行文件、配置文件等)没有明确的指定资源的路径,而是依赖操作系统去搜索资源,这种行为可能被攻击者利用,通过在搜索优先级较高的目录放置不良资源,…...
Unity 2D实战小游戏开发跳跳鸟 - 记录显示最高分
上一篇文章中我们实现了游戏的开始界面,在开始界面中有一个最高分数的UI,本文将接着实现记录最高分数以及在开始界面中显示最高分数的功能。 添加跳跳鸟死亡事件 要记录最高分,则需要在跳跳鸟死亡时去进行判断当前的分数是否是最高分,如果是最高分则进行记录,如果低于之前…...
openeuler 22.03 lts sp4 使用 cri-o 和 静态 pod 的方式部署 k8s-v1.32.0 高可用集群
前情提要 整篇文章会非常的长…可以选择性阅读,另外,这篇文章是自己学习使用的,用于生产,还请三思和斟酌 静态 pod 的部署方式和二进制部署的方式是差不多的,区别在于 master 组件的管理方式是 kubectl 还是 systemctl有 kubeadm 工具,为什么还要用静态 pod 的方式部署?…...
穷举vs暴搜vs深搜vs回溯vs剪枝系列一>黄金矿工
目录 决策树:代码设计代码: 决策树: 代码设计 代码: class Solution {boolean[][] vis;int ret,m,n;public int getMaximumGold(int[][] grid) {m grid.length;n grid[0].length;vis new boolean[m][n]; for(int i 0; i <…...
SQL Server配置管理器无法连接到 WMI 提供程序
目录 第一步第二部 第一步 发现没有资源管理器 在文件夹找到管理器 打开发现报这个错误 配置管理器无法连接到 WMI 提供程序第二部 https://blog.csdn.net/thb369208315/article/details/126954074...
微信小程序获取openid和其他接口同时并发请求如何保证先获取到openid
在微信小程序中,如果你需要并发请求获取 openid 和其他接口的数据,并且希望确保先获取到 openid 之后再进行后续操作,可以考虑以下几种方法: 方法一:使用 Promise 链 1, 先请求 openid:使用 Promise 来请求 openid。 2, 在获取到 openid 后再请求其他接口。 function g…...
为AI聊天工具添加一个知识系统 之87 详细设计之28 Derivation 统一建模元模型 之1
文本要点 要点 Derivation 统一建模元模型 Derivation 统一建模元模型:意识原型的祖传代码,即支撑 程序框架的 符号学中的 自然和逻辑树。 这棵树的雏形中描述了三种建模工件:语用钩子,语法糖和语义胶水。 三种工件对应的三“…...
java进阶知识点
java回收机制 浅谈java中的反射 依赖注入的简单理解 通过接口的引用和构造方法的表达,将一些事情整好了反过来传给需要用到的地方~ 这样做得好处:做到了单一职责,并且提高了复用性,解耦了之后,任你如何实现…...
63.视频推荐的算法|Marscode AI刷题
1.题目 问题描述 西瓜视频正在开发一个新功能,旨在将访问量达到80百分位数以上的视频展示在首页的推荐列表中。实现一个程序,计算给定数据中的80百分位数。 例如:假设有一个包含从1到100的整数数组,80百分位数的值为80…...
19.[前端开发]Day19-王者荣项目耀实战(二)
01_(掌握)王者荣耀-main-banner展示实现 完整代码 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewpor…...
Leetcode面试高频题分类刷题总结
https://zhuanlan.zhihu.com/p/349940945 以下8个门类是面试中最常考的算法与数据结构知识点。 排序类(Sort): 基础知识:快速排序(Quick Sort), 归并排序(Merge Sort)的…...
JPA使用@EntityGraph立即加载关联实体
在JpaRepository接口中实现自定义查询的时候,必然会遇到一个问题,通过findBy等语句查询出来的结果通常情况下不会加载到关联的实体。例如我有一个Material类,其中有一个属性supplier使用了多对一关联到Supplier类,并开启懒加载&am…...
python多版本管理工具之pyenv
pyenv 是一个用于管理多个 Python 版本的工具,允许用户在同一台机器上轻松安装、切换和隔离不同版本的 Python 解释器。它特别适合需要同时处理多个项目的开发者(例如,不同项目依赖不同 Python 版本的情况)。以下是 pyenv 的详细指南: 本文基于Ubuntu 22.04版本进行安装,…...
107,【7】buuctf web [CISCN2019 华北赛区 Day2 Web1]Hack World
这次先不进入靶场 看到红框里面的话就想先看看uuid是啥 定义与概念 UUID 是 Universally Unique Identifier 的缩写,即通用唯一识别码。它是一种由数字和字母组成的 128 位标识符,在理论上可以保证在全球范围内的唯一性。UUID 的设计目的是让分布式系…...
9. k8s二进制集群之kube-controller-manager部署
同样在部署主机上创建证书请求文件(为之后的证书生成做准备)根据上面的证书文件创建证书(结果会在当前目录下产生kube-controller-manager证书)创建kube-controller-manager服务配置文件创建kube-controller-manager服务启动文件同步kube-controller-manager证书到对应mast…...
keil 单步调试技巧
一、常见错误分析 warningerror警告错误 不影响编译过程 能够输出Hex文件 无法完成编译 不输出Hex文件 注意的是,warning的信息是要去关注的。 下面的UNCALLED SEGMENT除外 二、单步调试配置 1、在keil中添加单片机型号 本文不详细介绍,如有需要可查看这篇文章:...
[leetcode]两数之和等于target
源代码 #include <iostream> #include <list> #include <iterator> // for std::prev using namespace std; int main() { int target 9; list<int> l{ 2, 3, 4, 6, 8 }; l.sort(); // 确保列表是排序的,因为双指针法要求输入是…...
Go语言的转义字符
文章目录 1. Go语言的转义字符(escapechar)2. 小结和提示 1. Go语言的转义字符(escapechar) 说明:常用的转义字符有如下: \t : 表示一个制表符,通常使用它可以排版\n :换行符\\ :一个\\" :一个"\r :一个回…...
低代码系统-产品架构案例介绍、蓝凌(十三)
蓝凌低代码系统,依旧是从下到上,从左至右的顺序。 技术平台h/iPaas 指低层使用了哪些技术,例如:微服务架构,MySql数据库。个人认为,如果是市场的主流,就没必要赘述了。 新一代门户 门户设计器&a…...
【大数据技术】搭建完全分布式高可用大数据集群(Hadoop+MapReduce+Yarn)
搭建完全分布式高可用大数据集群(Hadoop+MapReduce+Yarn) jdk-8u361-linux-x64.tarhadoop-3.3.6.tar.gz注:请在阅读本篇文章前,将以上资源下载下来。 写在前面 本文主要介绍搭建完全分布式高可用集群Hadoop+MapReduce+Yarn的详细步骤。 注意: 统一约定将软件安装包存放…...
Rapidjson 实战
Rapidjson 是一款 C 的 json 库. 支持处理 json 格式的文档. 其设计风格是头文件库, 包含头文件即可使用, 小巧轻便并且性能强悍. 本文结合样例来介绍 Rapidjson 一些常见的用法. 环境要求 有如何的几种方法可以将 Rapidjson 集成到您的项目中. Vcpkg安装: 使用 vcpkg instal…...
string类OJ练习题
目录 文章目录 前言 一、反转字符串 二、反转字符串 II 三、反转字符串中的单词 III 四、验证一个字符串是否是回文 五、字符串相加(大数加法) 六、字符串相乘(大数乘法) 七、把字符串转化为整数(atoi) 总结…...
Python进行模型优化与调参
在数据科学与机器学习领域,模型的优化与调参是提高模型性能的重要步骤之一。模型优化可以帮助提高模型的准确性和泛化能力,而合理的调参则能够充分发挥模型的潜力。这篇教程将重点介绍几种常用的模型优化与调参方法,特别是超参数调整和正则化技术的应用。这些技术能够有效地…...
Ollama+deepseek+Docker+Open WebUI实现与AI聊天
1、下载并安装Ollama 官方网址:Ollama 安装好后,在命令行输入, ollama --version 返回以下信息,则表明安装成功, 2、 下载AI大模型 这里以deepseek-r1:1.5b模型为例, 在命令行中,执行&…...
【PDF多区域识别】如何批量PDF指定多个区域识别改名,基于Windows自带的UWP的文字识别实现方案
海关在对进口货物进行查验时,需要核对报关单上的各项信息。对报关单 PDF 批量指定区域识别改名后,海关工作人员可以更高效地从文件名中获取关键信息,如货物来源地、申报价值等。例如文件名 “[原产国]_[申报价值].pdf”,有助于海关快速筛选重点查验对象,提高查验效率和监管…...
第一个Qt开发实例(一个Push Button按钮和两个Label)【包括如何在QtCreator中创建新工程、代码详解、编译、环境变量配置、测试程序运行等】
目录 Qt开发环境QtCreator的安装、配置在QtCreator中创建新工程在Forms→mainwindow.ui中拖曳出我们要的图形按钮查看拖曳出按钮后的代码为pushButton这个图形添加回调函数编译工程关闭开发板上QT的GUI(选做)禁止LCD黑屏(选做)设置Qt运行的环境变量运行Qt程序如何让程序在系统启…...
算法题(58):盛水最多的容器
审题: 需要我们找到数组height中的数据构建的可以盛水最多的容器,并把容量返回 思路: 容量 最短的容器边界 * 容器宽度 方法一:双层for循环 我们可以把所有情况枚举出来,然后维护一个最大容量 方法二:双指…...
MyBatis持久层框架
第1章 Mybatis框架入门 1.1 Mybatis简介 MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到Github。 MyBati…...
DeePseek结合PS!批量处理图片的方法教程
今天我们来聊聊如何利用deepseek和Photoshop(PS)实现图片的批量处理。 传统上,批量修改图片尺寸、分辨率等任务往往需要编写脚本或手动处理,而现在有了AI的辅助,我们可以轻松生成PS脚本,实现自动化处…...
【C++】多态(下)
大家好,我是苏貝,本篇博客带大家了解C的多态,如果你觉得我写的还不错的话,可以给我一个赞👍吗,感谢❤️ 目录 4. 多态的原理4.1 虚函数表4.2 多态的原理4.3 动态绑定与静态绑定 5. 单继承和多继承关系的虚…...
C++ 入门速通-第4章【黑马】
内容来源于:黑马 集成开发环境:CLion 先前学习完了C第1章的内容: C 入门速通-第1章【黑马】-CSDN博客 C 入门速通-第2章【黑马】-CSDN博客 C 入门速通-第3章【黑马】-CSDN博客 下面继续学习第4章: 结构体的基本应用࿱…...
Gauss高斯:分布键
分布键是决定数据分布到不同节点的列,直接影响数据的存储位置和后续查询的数据流向. 在分布式数据库系统中,分布键用于决定数据如何在不同的节点或分区中分布。 作用 分类 分布键的选择 避免常量过滤条件的字段:在选择分布键时,…...
Spring Security(maven项目) 3.0.3.1版本 - 动态JDBC认证
前言: 通过实践而发现真理,又通过实践而证实真理和发展真理。从感性认识而能动地发展到理性认识,又从理性认识而能动地指导革命实践,改造主观世界和客观世界。实践、认识、再实践、再认识,这种形式,循环往…...
2024年半导体行业IPO与融资情况统计分析
重点内容速览: 1. IPO企业主要集中在科创板 2. 全年半导体行业融资事件超700起 2024年半导体行业的IPO和融资情况呈现出了显著的波动和变化。由于IPO政策的收紧,2024年成功上市的企业相比2023年的22家和2022年的45家有了显著降低,今年仅有1…...
Java 进阶 01 —— 5 分钟回顾一下 Java 基础知识
Java 进阶 01 —— 5 分钟回顾一下 Java 基础知识 Java 生态圈Java 跨平台的语言 Java 虚拟机规范JVM 跨语言的平台多语言混合编程两种架构 举例 JVM 的生命周期 虚拟机的启动虚拟机的执行虚拟机的退出 JVM 发展历程 Sun Classic VMExact VMHotSpotBEA 的 JRockitIBM 的 J9 …...
java进阶文章链接
java 泛型:java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一 Java 泛型,你了解类型擦除吗? java 注解:深入理解Java注解类型 秒懂,Java 注解 (Annotation)你可以这样学 jav…...