连接超时设置
默认情况下,不设置连接超时,服务器处于离线状态,函数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