RTSP解复用器

libavformat/rtspdec.c

const FFInputFormat ff_rtsp_demuxer = {
    .p.name         = "rtsp",
    .p.long_name    = NULL_IF_CONFIG_SMALL("RTSP input"),
    .p.flags        = AVFMT_NOFILE,
    .p.priv_class   = &rtsp_demuxer_class,
    .priv_data_size = sizeof(RTSPState),
    .read_probe     = rtsp_probe,
    .read_header    = rtsp_read_header,
    .read_packet    = rtsp_read_packet,
    .read_close     = rtsp_read_close,
    .read_seek      = rtsp_read_seek,
    .read_play      = rtsp_read_play,
    .read_pause     = rtsp_read_pause,
};
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

探测输入格式根据URL起始字符进行匹配

static int rtsp_probe(const AVProbeData *p)
{
    if (
#if CONFIG_TLS_PROTOCOL
        av_strstart(p->filename, "rtsps:", NULL) ||
#endif
        av_strstart(p->filename, "satip:", NULL) ||
        av_strstart(p->filename, "rtsp:", NULL))
        return AVPROBE_SCORE_MAX;
    return 0;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

sdp_parse_line实现SDP分析

libavformat/rtsp.c实现RTSP协议的分析

>	ffplayd.exe!ff_sdp_parse(AVFormatContext * s, const char * content)721	C
 	ffplayd.exe!ff_rtsp_setup_input_streams(AVFormatContext * s, RTSPMessageHeader * reply)643	C
 	ffplayd.exe!ff_rtsp_connect(AVFormatContext * s)1967	C
 	ffplayd.exe!rtsp_read_header(AVFormatContext * s)757	C
 	ffplayd.exe!avformat_open_input(AVFormatContext * * ps, const char * filename, const AVInputFormat * fmt, AVDictionary * * options)305	C
 	ffplayd.exe!read_thread(void * arg)2858	C
 	ffplayd.exe!SDL_RunThread(void * data)283	C
 	ffplayd.exe!RunThread(void * data)91	C
 	ffplayd.exe!RunThreadViaBeginThreadEx(void * data)106	C
 	ucrtbased.dll!00007ff8de7a4fb8()	未知
 	ucrtbased.dll!00007ff8de7a4bf1()	未知
 	kernel32.dll!00007ff92b737c24()	未知
 	ntdll.dll!00007ff92c9ad721()	未知
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

ff_rtsp_setup_input_streams创建rtsp交互连接

ff_rtsp_send_cmd优先发送DESCRIBE指令,海康和大华RTSP指令交互的区别在于,大华在OPTIONS阶段就会请求认证信息RTSP/1.0 401 Unauthorized

int ff_sdp_parse(AVFormatContext *s, const char *content) 解析SDP中的内容,实际上这里就已经完全知道码流的数据格式以及所有的流信息,根本不需要探测码流格式

sdp_parse_rtpmap函数分析出h264码流格式,保存在AVFormatContext中的stream流中

/* parse the rtpmap description: <codec_name>/<clock_rate>[/<other params>] */
static int sdp_parse_rtpmap(AVFormatContext *s,
                            AVStream *st, RTSPStream *rtsp_st,
                            int payload_type, const char *p)
{
//*p = H264/90000
//最终st中保存探测的码流格式
    AVCodecParameters *par = st->codecpar;
    char buf[256];
    int i;
    const AVCodecDescriptor *desc;
    const char *c_name;    /* See if we can handle this kind of payload.
     * The space should normally not be there but some Real streams or
     * particular servers ("RealServer Version 6.1.3.970", see issue 1658)
     * have a trailing space. */
//关键,buf获取到H264
    get_word_sep(buf, sizeof(buf), "/ ", &p);
    if (payload_type < RTP_PT_PRIVATE) {
        /* We are in a standard case
         * (from http://www.iana.org/assignments/rtp-parameters). */
        par->codec_id = ff_rtp_codec_id(buf, par->codec_type);
    }    if (par->codec_id == AV_CODEC_ID_NONE) {
        const RTPDynamicProtocolHandler *handler =
            ff_rtp_handler_find_by_name(buf, par->codec_type);
        init_rtp_handler(handler, rtsp_st, st);
        /* If no dynamic handler was found, check with the list of standard
         * allocated types, if such a stream for some reason happens to
         * use a private payload type. This isn't handled in rtpdec.c, since
         * the format name from the rtpmap line never is passed into rtpdec. */
        if (!rtsp_st->dynamic_handler)
            par->codec_id = ff_rtp_codec_id(buf, par->codec_type);
    }    desc = avcodec_descriptor_get(par->codec_id);
    if (desc && desc->name)
        c_name = desc->name;
    else
        c_name = "(null)";    get_word_sep(buf, sizeof(buf), "/", &p);
    i = atoi(buf);
    switch (par->codec_type) {
    case AVMEDIA_TYPE_AUDIO:
        av_log(s, AV_LOG_DEBUG, "audio codec set to: %s\n", c_name);
        par->sample_rate = RTSP_DEFAULT_AUDIO_SAMPLERATE;
        par->channels = RTSP_DEFAULT_NB_AUDIO_CHANNELS;
        if (i > 0) {
            par->sample_rate = i;
            avpriv_set_pts_info(st, 32, 1, par->sample_rate);
            get_word_sep(buf, sizeof(buf), "/", &p);
            i = atoi(buf);
            if (i > 0)
                par->channels = i;
        }
        av_log(s, AV_LOG_DEBUG, "audio samplerate set to: %i\n",
               par->sample_rate);
        av_log(s, AV_LOG_DEBUG, "audio channels set to: %i\n",
               par->channels);
        break;
    case AVMEDIA_TYPE_VIDEO:
        av_log(s, AV_LOG_DEBUG, "video codec set to: %s\n", c_name);
        if (i > 0)
            avpriv_set_pts_info(st, 32, 1, i);
        break;
    default:
        break;
    }
    finalize_rtp_handler_init(s, rtsp_st, st);
    return 0;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.

附上SDP数据

v=0
o=- 1625669797472718 1625669797472718 IN IP4 192.168.18.204
s=Media Presentation
e=NONE
b=AS:5050
t=0 0
a=control:rtsp://192.168.18.204:554/h264/ch1/main/av_stream/
m=video 0 RTP/AVP 96
b=AS:5000
a=control:rtsp://192.168.18.204:554/h264/ch1/main/av_stream/trackID=1
a=rtpmap:96 H264/90000
a=fmtp:96 profile-level-id=420029; packetization-mode=1; sprop-parameter-sets=Z0IAKpY1QPAET8s3AQEBAg==,aM48gA==
a=Media_header:MEDIAINFO=494D4B48010100000400000100000000000000000000000000000000000000000000000000000000;
a=appversion:1.0
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

连接代码堆栈

ffplayd.exe!ff_poll_interrupt(pollfd * p, unsigned long nfds, int timeout, AVIOInterruptCB * cb)166	C
 	ffplayd.exe!ff_connect_parallel(addrinfo * addrs, int timeout_ms_per_address, int parallel, URLContext * h, int * fd, void(*)(void *, int) customize_fd, void * customize_ctx)461	C
 	ffplayd.exe!tcp_open(URLContext * h, const char * uri, int flags)198	C
 	ffplayd.exe!ffurl_connect(URLContext * uc, AVDictionary * * options)205	C
 	ffplayd.exe!ffurl_open_whitelist(URLContext * * puc, const char * filename, int flags, const AVIOInterruptCB * int_cb, AVDictionary * * options, const char * whitelist, const char * blacklist, URLContext * parent)345	C
 	ffplayd.exe!ff_rtsp_connect(AVFormatContext * s)1841	C
 	ffplayd.exe!rtsp_read_header(AVFormatContext * s)726	C
>	ffplayd.exe!avformat_open_input(AVFormatContext * * ps, const char * filename, AVInputFormat * fmt, AVDictionary * * options)631	C
 	ffplayd.exe!read_thread(void * arg)2780	C
 	ffplayd.exe!SDL_RunThread(void * data)283	C
 	ffplayd.exe!RunThread(void * data)91	C
 	ffplayd.exe!RunThreadViaBeginThreadEx(void * data)106	C
 	ucrtbased.dll!00007ffbf3f14fb8()	未知
 	ucrtbased.dll!00007ffbf3f14bf1()	未知
 	kernel32.dll!00007ffc6c1e7c24()	未知
 	ntdll.dll!00007ffc6d74d721()	未知
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

接收代码堆栈

ffplayd.exe!ff_network_wait_fd_timeout(int fd, int write, __int64 timeout, AVIOInterruptCB * int_cb)84	C
 	ffplayd.exe!tcp_read(URLContext * h, unsigned char * buf, int size)240	C
 	ffplayd.exe!retry_transfer_wrapper(URLContext * h, unsigned char * buf, int size, int size_min, int(*)(URLContext *, unsigned char *, int) transfer_func)376	C
 	ffplayd.exe!ffurl_read_complete(URLContext * h, unsigned char * buf, int size)419	C
 	ffplayd.exe!ff_rtsp_tcp_read_packet(AVFormatContext * s, RTSPStream * * prtsp_st, unsigned char * buf, int buf_size)771	C
 	ffplayd.exe!read_packet(AVFormatContext * s, RTSPStream * * rtsp_st, RTSPStream * first_queue_st, __int64 wait_end)2111	C
 	ffplayd.exe!ff_rtsp_fetch_packet(AVFormatContext * s, AVPacket * pkt)2202	C
 	ffplayd.exe!rtsp_read_packet(AVFormatContext * s, AVPacket * pkt)879	C
 	ffplayd.exe!ff_read_packet(AVFormatContext * s, AVPacket * pkt)856	C
 	ffplayd.exe!read_frame_internal(AVFormatContext * s, AVPacket * pkt)1582	C
 	ffplayd.exe!av_read_frame(AVFormatContext * s, AVPacket * pkt)1776	C
 	ffplayd.exe!read_thread(void * arg)3009	C
 	ffplayd.exe!SDL_RunThread(void * data)283	C
 	ffplayd.exe!RunThread(void * data)91	C
 	ffplayd.exe!RunThreadViaBeginThreadEx(void * data)106	C
 	ucrtbased.dll!00007fff78404fb8()	未知
 	ucrtbased.dll!00007fff78404bf1()	未知
 	kernel32.dll!00007fffc9f77c24()	未知
 	ntdll.dll!00007fffcacad721()	未知
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

FU-A分包处理逻辑

重点分析函数ff_rtsp_fetch_packet,该函数调用read_packet获取到RTP数据,调用ff_rtp_parse_packet分析RTP数据,去掉RTP包头,添加起始码,然后封装成AVPacket,但是封装的AVPacket并不是完整的NAL单元的视频流,对于FU-A分包的数据,仍然需要对多个AVPacket进行重新组装

对FU-A分包的RTP格式数据,会根据是否是第一个包添加起始码,,关键是(h264_handle_packet_fu_a)start_bit    = fu_header >> 7;

ff_h264_handle_frag_packet函数根据start_bit,确定是否需要添加起始码

av_read_frame分包代码剖析

ff_h264_handle_frag_packet函数会为每一个RTP包携带的NALU分片申请一个AVPacket包保存,因此需要通过ff_combine_frame函数构建一个完整的NALU单元

ffplayd.exe!ff_h264_handle_frag_packet(AVPacket * pkt, const unsigned char * buf, int len, int start_bit, const unsigned char * nal_header, int nal_header_len)270	C
 	ffplayd.exe!h264_handle_packet_fu_a(AVFormatContext * ctx, PayloadContext * data, AVPacket * pkt, const unsigned char * buf, int len, int * nal_counters, int nal_mask)310	C
 	ffplayd.exe!h264_handle_packet(AVFormatContext * ctx, PayloadContext * data, AVStream * st, AVPacket * pkt, unsigned int * timestamp, const unsigned char * buf, int len, unsigned short seq, int flags)360	C
 	ffplayd.exe!rtp_parse_packet_internal(RTPDemuxContext * s, AVPacket * pkt, const unsigned char * buf, int len)763	C
 	ffplayd.exe!rtp_parse_one_packet(RTPDemuxContext * s, AVPacket * pkt, unsigned char * * bufptr, int len)917	C
 	ffplayd.exe!ff_rtp_parse_packet(RTPDemuxContext * s, AVPacket * pkt, unsigned char * * bufptr, int len)951	C
 	ffplayd.exe!ff_rtsp_fetch_packet(AVFormatContext * s, AVPacket * pkt)2288	C
 	ffplayd.exe!rtsp_read_packet(AVFormatContext * s, AVPacket * pkt)914	C
 	ffplayd.exe!ff_read_packet(AVFormatContext * s, AVPacket * pkt)646	C
 	ffplayd.exe!read_frame_internal(AVFormatContext * s, AVPacket * pkt)1335	C
>	ffplayd.exe!av_read_frame(AVFormatContext * s, AVPacket * pkt)1547	C
 	ffplayd.exe!read_thread(void * arg)3087	C
 	ffplayd.exe!SDL_RunThread(void * data)283	C
 	ffplayd.exe!RunThread(void * data)91	C
 	ffplayd.exe!RunThreadViaBeginThreadEx(void * data)106	C
 	ucrtbased.dll!00007ff8de734fb8()	未知
 	ucrtbased.dll!00007ff8de734bf1()	未知
 	kernel32.dll!00007ff92b737c24()	未知
 	ntdll.dll!00007ff92c9ad721()	未知
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

HEVC码流探测剖析

例如:"rtsp://admin:admin12345@192.168.28.136:554/h265/ch1/main/av_stream"

请求海康摄像机H265码流

SDP报文如下:
v=0
o=- 1566124110963848 1566124110963848 IN IP4 192.168.28.136
s=Media Presentation
e=NONE
b=AS:5100
t=0 0
a=control:rtsp://192.168.28.136:554/h265/ch1/main/av_stream/
m=video 0 RTP/AVP 96
c=IN IP4 0.0.0.0
b=AS:5000
a=recvonly
a=x-dimensions:1920,1080
a=control:rtsp://192.168.28.136:554/h265/ch1/main/av_stream/trackID=1
a=rtpmap:96 H265/90000
a=fmtp:96 sprop-sps=QgEBAWAAAAMAsAAAAwAAAwB7oAPAgBDlja5JMvTcBAQEAg==; sprop-pps=RAHA8vA8kAA=
m=audio 0 RTP/AVP 8
c=IN IP4 0.0.0.0
b=AS:50
a=recvonly
a=control:rtsp://192.168.28.136:554/h265/ch1/main/av_stream/trackID=2
a=rtpmap:8 PCMA/8000
a=Media_header:MEDIAINFO=494D4B48010200000400050011710110401F000000FA000000000000000000000000000000000000;
a=appversion:1.0

static int sdp_parse_rtpmap(AVFormatContext *s,
                            AVStream *st, RTSPStream *rtsp_st,
                            int payload_type, const char *p)

通过sdp_parse_rtpmap函数分析SDP中的a=rtpmap:96 H265/90000
在调用函数        const RTPDynamicProtocolHandler *handler =
            ff_rtp_handler_find_by_name(buf, par->codec_type);中 获取到h265的处理句柄

rtsp 分包NALU

       if (codec_id == AV_CODEC_ID_HEVC)
            ret = hevc_parse_nal_header(nal, logctx);
        else
            ret = h264_parse_nal_header(nal, logctx);

static void parse_fmtp(AVFormatContext *s, RTSPState *rt,
                       int payload_type, const char *line)
通过h265的句柄调用
static av_cold int hevc_parse_sdp_line(AVFormatContext *ctx, int st_index,
                                       PayloadContext *hevc_data, const char *line)
该函数将SPS/PPS的内容保存在(AVFormatContext结构体中的AVStream流中的extradata变量

创建视频流和音频流
update_stream_avctx函数只是将avcodec_parameters_to_context(st->internal->avctx, st->codecpar);
并没有做其他的操作