[FFmpeg-trac] #2764(undetermined:new): FFMPEG is blocked when multicast group membership is lost (on linux kernel < 2.6.27)
FFmpeg
trac at avcodec.org
Tue Jul 9 17:04:50 CEST 2013
#2764: FFMPEG is blocked when multicast group membership is lost (on linux kernel
< 2.6.27)
-------------------------------------+-------------------------------------
Reporter: asif | Type:
Status: new | enhancement
Component: | Priority: normal
undetermined | Version: 0.10.7
Keywords: udp | Blocked By:
multicast blocked lost group | Reproduced by developer: 0
membership |
Blocking: |
Analyzed by developer: 0 |
-------------------------------------+-------------------------------------
Summary of the bug:
FFmpeg is blocking until it receives the data packet or there is some
socket error. The issue is group membership of the socket is being flushed
from kernel group membership table whenever there is some disruption in
the network. It does happen on centos as it is using an older version of
the kernel (older version: “2.6.18”). As the ffmpeg udp.c is waiting on
receiving packets and not checking whether there is any problem with its
group membership, it will continue to wait for packets.
The Issue was Reproduced on:
CentOS kernel: 2.6.18
ffmpeg version: 0.10
How to reproduce:
1. Start ffmpeg stream transmission:
$ ./ffmpeg -i INPUT -f mpegts udp://233.19.204.1:5501
2. Start ffmpeg stream receiver:
$ ./ffplay udp://233.19.204.1:5501
3. The multicast group member can be lost due to disruption in the
network. One way is also to restarting network service:
$ /etc/init.d/network restart
The receiver will keep on waiting for the udp packets without checking the
multicast group membership.
Note:
The issue happens only on kernel versions < 2.6.27. The issue in the
multicast group membership seemed to be fixed by linux kerenl in version
2.6.27:
http://mirror.linux.org.au/linux/kernel/v2.6/ChangeLog-2.6.27
However, some linux flavors including CentOS 5.7 still uses 2.6.18 which
has this issue.
Possible Fix:
in libavformat/udp.c Check the multicastgroupmembership as below:
{{{
#ifdef _MULTICAST_HANDLELOSTMEMBERSHIP
#define PATH_PROCNET_IGMP "/proc/net/igmp"
//TODO: Support ipv6
static int udp_check_multicastgroupmembership(void *_URLContext, struct
sockaddr *addr)
{
FILE *f_igmp;
char igmp_line[8192];
char target_addr[10];
int result = -1;
URLContext *h = _URLContext;
if(addr->sa_family != AF_INET)
{
av_log(h, AV_LOG_INFO, "udp.c: IPPROTO_IPV6 NOT SUPPORTED\n");
return -1; //NOT SUPPORTED
}
if(NULL == (f_igmp = fopen(PATH_PROCNET_IGMP, "r")))
{
av_log(h, AV_LOG_ERROR, "udp.c: Unable to open %s\n",
PATH_PROCNET_IGMP);
return -1;
}
snprintf(target_addr, 9, "%X", ((struct sockaddr_in
*)addr)->sin_addr.s_addr);
if(fgets(igmp_line, sizeof(igmp_line), f_igmp)) {
if(strstr(igmp_line, "Device") == NULL) {
av_log(h, AV_LOG_INFO, "udp.c: IPPROTO_IPV6 NOT
SUPPORTED\n");
fclose(f_igmp);
return -1;
}
}
result = 0;
while (!feof(f_igmp)) {
if(fgets(igmp_line, sizeof(igmp_line), f_igmp)){
if(NULL != strstr(igmp_line, target_addr)){
result = 1;
break;
}
}
};
fclose(f_igmp);
return result;
}
#endif
}}}
The function can be called from circular_buffer_task function as below:
{{{
#if HAVE_PTHREADS
static void *circular_buffer_task( void *_URLContext)
{
URLContext *h = _URLContext;
UDPContext *s = h->priv_data;
fd_set rfds;
struct timeval tv;
#ifdef _MULTICAST_HANDLELOSTMEMBERSHIP
int timeout_count = 0;
const int timeout_max_value = 5; // 5 second timeout
#endif
while(!s->exit_thread) {
int left;
int ret;
int len;
if (ff_check_interrupt(&h->interrupt_callback)) {
s->circular_buffer_error = EIO;
goto end;
}
FD_ZERO(&rfds);
FD_SET(s->udp_fd, &rfds);
tv.tv_sec = 1;
tv.tv_usec = 0;
ret = select(s->udp_fd + 1, &rfds, NULL, NULL, &tv);
if (ret < 0) {
if (ff_neterrno() == AVERROR(EINTR)) {
av_log(h, AV_LOG_INFO, "Got ERROR EINTR");
continue;
}
s->circular_buffer_error = EIO;
goto end;
}
#ifdef _MULTICAST_HANDLELOSTMEMBERSHIP
else if(ret == 0 && s->is_multicast && (h->flags &
AVIO_FLAG_READ))
{
if(++timeout_count >= timeout_max_value)
{
av_log(h, AV_LOG_DEBUG, "No Packet for %d seconds\n",
timeout_max_value);
//check if the problem is due to lost group membership
if(0 == udp_check_multicastgroupmembership(h, (struct
sockaddr *)&s->dest_addr))
{
//if the problem is due to lost multicast group
membership, reinitialize group membership
av_log(h, AV_LOG_DEBUG, "Restoring group
membership\n");
udp_leave_multicast_group(s->udp_fd, (struct sockaddr
*)&s->dest_addr);
if (udp_join_multicast_group(s->udp_fd, (struct
sockaddr *)&s->dest_addr) < 0)
{
av_log(h, AV_LOG_ERROR, "udp.c: groupmembership
retry failed\n");
}
}
timeout_count = 0;
}
}
#endif
if (!(ret > 0 && FD_ISSET(s->udp_fd, &rfds)))
continue;
/* How much do we have left to the end of the buffer */
/* Whats the minimum we can read so that we dont comletely fill
the buffer */
left = av_fifo_space(s->fifo);
/* No Space left, error, what do we do now */
if(left < UDP_MAX_PKT_SIZE + 4) {
av_log(h, AV_LOG_ERROR, "circular_buffer: OVERRUN\n");
s->circular_buffer_error = EIO;
goto end;
}
left = FFMIN(left, s->fifo->end - s->fifo->wptr);
len = recv(s->udp_fd, s->tmp+4, sizeof(s->tmp)-4, 0);
if (len < 0) {
if (ff_neterrno() != AVERROR(EAGAIN) && ff_neterrno() !=
AVERROR(EINTR)) {
s->circular_buffer_error = EIO;
goto end;
}
continue;
}
AV_WL32(s->tmp, len);
pthread_mutex_lock(&s->mutex);
av_fifo_generic_write(s->fifo, s->tmp, len+4, NULL);
pthread_cond_signal(&s->cond);
pthread_mutex_unlock(&s->mutex);
}
end:
pthread_mutex_lock(&s->mutex);
pthread_cond_signal(&s->cond);
pthread_mutex_unlock(&s->mutex);
return NULL;
}
#endif
}}}
--
Ticket URL: <https://ffmpeg.org/trac/ffmpeg/ticket/2764>
FFmpeg <http://ffmpeg.org>
FFmpeg issue tracker
More information about the FFmpeg-trac
mailing list