原因:在进行RTP拉流时,会出现avforamt_open_input阻塞问题,故通过源码进行分析
概况:avforamt_open_input默认为阻塞模式,可以通过三种方式进行设置阻塞返回,但是不同的阻塞环节,不同协议方式也不全部可行。下面三种方式的伪代码,接下来通过源码进行分析并给出解决方案。
int callback_cb(void *p)
{
return 1;
}
AVFormatContext *pFormatCtx = nullptr;
pFormatCtx = avformat_alloc_context();
pFormatCtx->interrupt_callback.opaque = nullptr;
pFormatCtx->interrupt_callback.callback = callback_cb;//返回1,则退出阻塞
AVDictionary *pDictionary=nullptr;
av_dict_set_int(&pDictionary, "timeout", 6, 0);
pFormatCtx->flags |= AVIO_FLAG_NONBLOCK;
int ir = avformat_open_input(&pFormatCtx, url, nullptr, &pDictionary);
以rtp拉流为例,伪代码如下:可以简单理解为init_input方法根据输入的url进行判断当前协议是否支持,如果是文件的话则进行内部打开,由于是rtp协议,故阻塞发生在read_header中.
int avformat_open_input(AVFormatContext **ps, const char *filename,
AVInputFormat *fmt, AVDictionary **options)
{
if ((ret = init_input(s, filename, &tmp)) < 0)
goto fail;
f (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
if ((ret = s->iformat->read_header(s)) < 0)
goto fail;
}
接下来read_header的简答实现伪代码如下:通过下面的实现代码可以看出内部只是进行了一个read操作,如果读取失败则会进行跳出循环,故真实的阻塞发生在read中.
static int rtp_read_header(AVFormatContext *s)
{
while (1) {
ret = ffurl_read(in, recvbuf, sizeof(recvbuf));
if (ret == AVERROR(EAGAIN))
continue;
if (ret < 0)
goto fail;
if (ret < 12) {
av_log(s, AV_LOG_WARNING, "Received too short packet\n");
continue;
}
if ((recvbuf[0] & 0xc0) != 0x80) {
av_log(s, AV_LOG_WARNING, "Unsupported RTP version packet "
"received\n");
continue;
}
if (RTP_PT_IS_RTCP(recvbuf[1]))
continue;
payload_type = recvbuf[1] & 0x7f;
break;
}
}
重点分析下面的read内部流程代码实现如下:通过代码可以看出跳出循环有三种条件,第一:URLContext中的interrupt_callback设置回调.第二:URLContext中flag设置为AVIO_FLAG_NONBLOCK属性,第三:URLContext中的rw_timeout设置阻塞时长.
tatic 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;
ret = transfer_func(h, buf + len, size - len);
if (ret == AVERROR(EINTR))
continue;
if (h->flags & AVIO_FLAG_NONBLOCK)
return ret;
if (ret == AVERROR(EAGAIN)) {
ret = 0;
if (fast_retries) {
fast_retries--;
} else {
if (h->rw_timeout) {
if (!wait_since)
wait_since = av_gettime_relative();
else if (av_gettime_relative() > wait_since + h->rw_timeout)
return AVERROR(EIO);
}
av_usleep(1000);
}
} else if (ret < 1)
return (ret < 0 && ret != AVERROR_EOF) ? ret : len;
if (ret) {
fast_retries = FFMAX(fast_retries, 2);
wait_since = 0;
}
len += ret;
}
return len;
}
那么如何通过设置AVFomatContex属性,将阻塞配置赋值给URLContext对象,通过下面的伪代码可以清楚的看出目前只有interrupt_callback被进行了设置,故如果需要不同的跳出方式可以通过修改rtp源码进行添加即可。
static int rtp_read_header(AVFormatContext *s)
{
ret = ffurl_open_whitelist(&in, s->filename, AVIO_FLAG_READ,
&s->interrupt_callback, NULL, s->protocol_whitelist, s-
>protocol_blacklist, NULL);
while(1)
{
}
}
总结:通过RTP拉流分析,在RTP模式下跳出avforamt_open_input阻塞的方式为通过设置AVIOInterruptCB interrupt_callback.