音视频入门基础:RTP专题(5)——FFmpeg源码中,解析SDP的实现

news2025/2/4 15:43:27

一、引言

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));
                } else
                    av_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);
                }
            } else
                get_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 4568
            rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];
            get_word(buf1, sizeof(buf1), &p); // ignore tag
            get_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 wildcard
            get_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 occurs
    int 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));
                } else
                    av_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);
                }
            } else
                get_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 4568
            rtsp_st = rt->rtsp_streams[rt->nb_rtsp_streams - 1];
            get_word(buf1, sizeof(buf1), &p); // ignore tag
            get_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 wildcard
            get_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.coloradmin.cn/o/2291857.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

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

四、GPIO中断实现按键功能

4.1 GPIO简介 输入输出&#xff08;I/O&#xff09;是一个非常重要的概念。I/O泛指所有类型的输入输出端口&#xff0c;包括单向的端口如逻辑门电路的输入输出管脚和双向的GPIO端口。而GPIO&#xff08;General-Purpose Input/Output&#xff09;则是一个常见的术语&#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;是为我们做命令行解释的。 命令行这个东西实际上是我们…

XCCL、NCCL、HCCL通信库

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

【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…

S4 HANA明确税金汇差科目(OBYY)

本文主要介绍在S4 HANA OP中明确税金汇差科目(OBYY)相关设置。具体请参照如下内容&#xff1a; 1. 明确税金汇差科目(OBYY) 以上配置点定义了在外币挂账时&#xff0c;当凭证抬头汇率和税金行项目汇率不一致时&#xff0c;造成的差异金额进入哪个科目。此类情况只发生在FB60/F…

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

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

Airflow:深入理解Apache Airflow Task

Apache Airflow是一个开源工作流管理平台&#xff0c;支持以编程方式编写、调度和监控工作流。由于其灵活性、可扩展性和强大的社区支持&#xff0c;它已迅速成为编排复杂数据管道的首选工具。在这篇博文中&#xff0c;我们将深入研究Apache Airflow 中的任务概念&#xff0c;探…

93,【1】buuctf web [网鼎杯 2020 朱雀组]phpweb

进入靶场 页面一直在刷新 在 PHP 中&#xff0c;date() 函数是一个非常常用的处理日期和时间的函数&#xff0c;所以应该用到了 再看看警告的那句话 Warning: date(): It is not safe to rely on the systems timezone settings. You are *required* to use the date.timez…

ChatGPT怎么回事?

纯属发现&#xff0c;调侃一下~ 这段时间deepseek不是特别火吗&#xff0c;尤其是它的推理功能&#xff0c;突发奇想&#xff0c;想用deepseek回答一些问题&#xff0c;回答一个问题之后就回复服务器繁忙&#xff08;估计还在被攻击吧~_~&#xff09; 然后就转向了GPT&#xf…

本地部署DeepSeek教程(Mac版本)

第一步、下载 Ollama 官网地址&#xff1a;Ollama 点击 Download 下载 我这里是 macOS 环境 以 macOS 环境为主 下载完成后是一个压缩包&#xff0c;双击解压之后移到应用程序&#xff1a; 打开后会提示你到命令行中运行一下命令&#xff0c;附上截图&#xff1a; 若遇…

2月3日星期一今日早报简报微语报早读

2月3日星期一&#xff0c;农历正月初六&#xff0c;早报#微语早读。 1、多个景区发布公告&#xff1a;售票数量已达上限&#xff0c;请游客合理安排行程&#xff1b; 2、2025春节档总票房破70亿&#xff0c;《哪吒之魔童闹海》破31亿&#xff1b; 3、美宣布对中国商品加征10…