连接超时设置

默认情况下,不设置连接超时,服务器处于离线状态,函数avformat_open_input会一直处于阻塞状态,即使阻塞过程中服务器恢复正常,也无法正常退出阻塞状态。

因此需要设置连接超时

参数设置说明

const AVOption ff_rtsp_options[] 
#if FF_API_OLD_RTSP_OPTIONS
    { "timeout", "set maximum timeout (in seconds) to wait for incoming connections (-1 is infinite, imply flag listen) (deprecated, use listen_timeout)", OFFSET(initial_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, INT_MIN, INT_MAX, DEC },
    { "stimeout", "set timeout (in microseconds) of socket TCP I/O operations", OFFSET(stimeout), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, DEC },
#else
    { "timeout", "set timeout (in microseconds) of socket TCP I/O operations", OFFSET(stimeout), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, DEC },
#endif

设置stimeout参数,单位是微秒

例子说明

AVFormatContext* pAVFormatContext = NULL;
AVDictionary* opts = NULL;
av_dict_set(&opts, "stimeout", "1500000", 0);//设置连接超时1.5秒
avformat_open_input(&pAVFormatContext, "rtsp://192.168.18.204:554/h264/ch1/main/av_stream", NULL, &opts);
av_dict_free(&opts);

 

接收超时设置

默认情况下,不设置接收超时,服务器由于某种问题,虽然保持跟客户端的连接,但是没有发送任何的数据过来,函数av_read_frame会一直处于阻塞状态,直到服务器发送数据过来,或者由于连接异常断开,才会正常返回

因此需要设置接收超时

原理说明AVFormatContext结构体中interrupt_callback函数会被av_read_frame重复调用,当interrupt_callback返回1的时候,av_read_frame将会立刻返回,退出阻塞状态,当interrupt_callback返回0的时候,av_read_frame会继续等待数据

 

例子说明

//当前使用一个全局变量,实际上可以放在类中
std::int64_t m_tStart = time(NULL);

//中断函数定义

static int interrupt_callback(void* ctx)
{    
    if (time(NULL) - m_tStart >= 10)
    {
        std::cout << "超时10秒" << std::endl;
        //返回1,av_read_frame立刻返回,退出阻塞接收模式
        return 1;
    }
    else
    {
        //av_read_frame阻塞接收,不会返回
        return 0;
    }
}

//赋值中断函数
AVIOInterruptCB cb = { 0 };
cb.callback = interrupt_callback;
//这里可以传递一个可选参数,实际上传递类的指针,然后获取到当前执行
//av_read_frame前m_tStart的值
cb.opaque = this;

//设置中断
AVFormatContext* pAVFormatContext = avformat_alloc_context();
pAVFormatContext->interrupt_callback = cb;

//在开始接收前获取当前时间
m_tStart = time(NULL);
int e = av_read_frame(pAVFormatContext, pkt);
//返回之后,重新更新当前时间
m_tStart = time(NULL) ;

 

3 设置的接收超时中断,对于连接超时也同样有效

剖析代码

static inline int retry_transfer_wrapper(URLContext *h, uint8_t *buf,
                                         int size, int size_min,
                                         int (*transfer_func)(URLContext *h,
                                                              uint8_t *buf,
                                                              int size))
{
    int ret, len;
    int fast_retries = 5;
    int64_t wait_since = 0;

    len = 0;
    while (len < size_min) {

//这个位置是接收数据的,通过设置回调,可以直接返回
        if (ff_check_interrupt(&h->interrupt_callback))
            return AVERROR_EXIT;


接收缓存设置

    av_dict_set(&opts, "buffer_size", "1024000", 0); // 设置缓冲区大小(解决RTP丢包问题)
    av_dict_set(&opts, "fifo_size", "9000000", 0); // 设置缓冲大小(解决RTP丢包问题)

设置过大,会占用内存,例如设置为9000000,会每一个链接占用9M的内存空间,当前1920*1080分辨率设置为1024000

 记录fifo_size是否有设置的必要


5) 底层接收实现

#if !HAVE_POLL_H
int ff_poll(struct pollfd *fds, nfds_t numfds, int timeout)
{
    fd_set read_set;
    fd_set write_set;
    fd_set exception_set;
    nfds_t i;
    int n;
    int rc;

#if HAVE_WINSOCK2_H
    if (numfds >= FD_SETSIZE) {
        errno = EINVAL;
        return -1;
    }
#endif /* HAVE_WINSOCK2_H */

    FD_ZERO(&read_set);
    FD_ZERO(&write_set);
    FD_ZERO(&exception_set);

    n = 0;
    for (i = 0; i < numfds; i++) {
        if (fds[i].fd < 0)
            continue;
#if !HAVE_WINSOCK2_H
        if (fds[i].fd >= FD_SETSIZE) {
            errno = EINVAL;
            return -1;
        }
#endif /* !HAVE_WINSOCK2_H */

        if (fds[i].events & POLLIN)
            FD_SET(fds[i].fd, &read_set);
        if (fds[i].events & POLLOUT)
            FD_SET(fds[i].fd, &write_set);
        if (fds[i].events & POLLERR)
            FD_SET(fds[i].fd, &exception_set);

        if (fds[i].fd >= n)
            n = fds[i].fd + 1;
    }

    if (n == 0)
        /* Hey!? Nothing to poll, in fact!!! */
        return 0;

    if (timeout < 0) {
        rc = select(n, &read_set, &write_set, &exception_set, NULL);
    } else {
        struct timeval tv;
        tv.tv_sec  = timeout / 1000;
        tv.tv_usec = 1000 * (timeout % 1000);
        rc         = select(n, &read_set, &write_set, &exception_set, &tv);
    }

    if (rc < 0)
        return rc;

    for (i = 0; i < numfds; i++) {
        fds[i].revents = 0;

        if (FD_ISSET(fds[i].fd, &read_set))
            fds[i].revents |= POLLIN;
        if (FD_ISSET(fds[i].fd, &write_set))
            fds[i].revents |= POLLOUT;
        if (FD_ISSET(fds[i].fd, &exception_set))
            fds[i].revents |= POLLERR;
    }

    return rc;
}

通过select函数接收数据,超时返回

static int rtp_read(URLContext *h, uint8_t *buf, int size)
{
    RTPContext *s = h->priv_data;
    int len, n, i;
    struct pollfd p[2] = {{s->rtp_fd, POLLIN, 0}, {s->rtcp_fd, POLLIN, 0}};
    int poll_delay = h->flags & AVIO_FLAG_NONBLOCK ? 0 : 100;
    struct sockaddr_storage *addrs[2] = { &s->last_rtp_source, &s->last_rtcp_source };
    socklen_t *addr_lens[2] = { &s->last_rtp_source_len, &s->last_rtcp_source_len };

    for(;;) {
        if (ff_check_interrupt(&h->interrupt_callback))
            return AVERROR_EXIT;
        n = poll(p, 2, poll_delay);
        if (n > 0) {
            /* first try RTCP, then RTP */
            for (i = 1; i >= 0; i--) {
                if (!(p[i].revents & POLLIN))
                    continue;
                *addr_lens[i] = sizeof(*addrs[i]);
                len = recvfrom(p[i].fd, buf, size, 0,
                                (struct sockaddr *)addrs[i], addr_lens[i]);
                if (len < 0) {
                    if (ff_neterrno() == AVERROR(EAGAIN) ||
                        ff_neterrno() == AVERROR(EINTR))
                        continue;
                    return AVERROR(EIO);
                }
                if (ff_ip_check_source_lists(addrs[i], &s->filters))
                    continue;
                return len;
            }
        } else if (n < 0) {
            if (ff_neterrno() == AVERROR(EINTR))
                continue;
            return AVERROR(EIO);
        }
        if (h->flags & AVIO_FLAG_NONBLOCK)
            return AVERROR(EAGAIN);
    }
}

max delay reached. need to consume packet

网络不畅的情况下,等待RTP数据超时

[rtsp @ 0x7f14117220] max delay reached. need to consume packet
[rtsp @ 0x7f14117220] RTP: missed 4 packets