[vlc-devel] [vlc-commits] access_out: srt: refactor to support connection recovery
Thomas Guillem
thomas at gllm.fr
Tue Apr 3 10:23:13 CEST 2018
On Tue, Apr 3, 2018, at 10:21, Steve Lhomme wrote:
> Can this be backported to 3.0 ?
>
> Same with ba758d7fa8a8d1e0de6c15dda11fd7d38fb24d84
OK, for all 3 SRT commits (access, access_out, contrib).
>
>
> Le 28/03/2018 à 14:00, Justin Kim a écrit :
> > vlc | branch: master | Justin Kim <justin.kim at collabora.com> | Wed Mar 28 19:29:44 2018 +0900| [867f005c508b1d38058d38761e5ca9273bed3a82] | committer: Thomas Guillem
> >
> > access_out: srt: refactor to support connection recovery
> >
> > This is a refactoring code to support SRT connection recovery.
> > While doing this work, SRT modules support new features comparing
> > to the previous ones.
> >
> > - Connection Recovery
> > When lost a SRT connection, this module can detect and try to re-connect.
> >
> > - Interruptible by SRT APIs
> > 'srt_epoll_wait' will be interrupted when all socket descriptors
> > are removed from its montoring list. Fortunately, SRT modules are using
> > only one socket.
> >
> > - Platform Independent
> > Now, SRT modules no longer require to use a pipe. Therfore, from this version,
> > SRT modules can support Win32 platforms.
> >
> > Based on code from:
> > Roman Diouskine <rdiouskine at haivision.com>
> >
> > Signed-off-by: Justin Kim <justin.kim at collabora.com>
> > Signed-off-by: Thomas Guillem <thomas at gllm.fr>
> >
> >> http://git.videolan.org/gitweb.cgi/vlc.git/?a=commit;h=867f005c508b1d38058d38761e5ca9273bed3a82
> > ---
> >
> > modules/access_output/Makefile.am | 1 +
> > modules/access_output/srt.c | 396 ++++++++++++++++++++++++--------------
> > 2 files changed, 249 insertions(+), 148 deletions(-)
> >
> > diff --git a/modules/access_output/Makefile.am b/modules/access_output/Makefile.am
> > index bc954ee03b..26560f400c 100644
> > --- a/modules/access_output/Makefile.am
> > +++ b/modules/access_output/Makefile.am
> > @@ -29,6 +29,7 @@ EXTRA_LTLIBRARIES += libaccess_output_shout_plugin.la
> >
> > ### SRT ###
> > libaccess_output_srt_plugin_la_SOURCES = access_output/srt.c
> > +libaccess_output_srt_plugin_la_CFLAGS = $(AM_CFLAGS) $(SRT_CFLAGS)
> > libaccess_output_srt_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) $(SRT_CPPFLAGS)
> > libaccess_output_srt_plugin_la_LIBADD = $(SRT_LIBS) $(LIBPTHREAD)
> > libaccess_output_srt_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(access_outdir)'
> > diff --git a/modules/access_output/srt.c b/modules/access_output/srt.c
> > index 904d8f56d0..f76bf52302 100644
> > --- a/modules/access_output/srt.c
> > +++ b/modules/access_output/srt.c
> > @@ -1,9 +1,11 @@
> > /*****************************************************************************
> > * srt.c: SRT (Secure Reliable Transport) output module
> > *****************************************************************************
> > - * Copyright (C) 2017, Collabora Ltd.
> > + * Copyright (C) 2017-2018, Collabora Ltd.
> > + * Copyright (C) 2018, Haivision Systems Inc.
> > *
> > * Authors: Justin Kim <justin.kim at collabora.com>
> > + * Roman Diouskine <rdiouskine at haivision.com>
> > *
> > * This program is free software; you can redistribute it and/or modify it
> > * under the terms of the GNU Lesser General Public License as published by
> > @@ -24,9 +26,6 @@
> > # include "config.h"
> > #endif
> >
> > -#include <errno.h>
> > -#include <fcntl.h>
> > -
> > #include <vlc_common.h>
> > #include <vlc_interrupt.h>
> > #include <vlc_fs.h>
> > @@ -62,27 +61,158 @@ struct sout_access_out_sys_t
> > {
> > SRTSOCKET sock;
> > int i_poll_id;
> > - int i_poll_timeout;
> > - int i_latency;
> > - size_t i_chunk_size;
> > - int i_pipe_fds[2];
> > + bool b_interrupted;
> > + vlc_mutex_t lock;
> > };
> >
> > static void srt_wait_interrupted(void *p_data)
> > {
> > sout_access_out_t *p_access = p_data;
> > sout_access_out_sys_t *p_sys = p_access->p_sys;
> > - msg_Dbg( p_access, "Waking up srt_epoll_wait");
> > - if ( write( p_sys->i_pipe_fds[1], &( bool ) { true }, sizeof( bool ) ) < 0 )
> > +
> > + vlc_mutex_lock( &p_sys->lock );
> > + if ( p_sys->i_poll_id >= 0 && p_sys->sock != SRT_INVALID_SOCK )
> > {
> > - msg_Err( p_access, "Failed to send data to event fd");
> > + p_sys->b_interrupted = true;
> > +
> > + msg_Dbg( p_access, "Waking up srt_epoll_wait");
> > +
> > + /* Removing all socket descriptors from the monitoring list
> > + * wakes up SRT's threads. We only have one to remove. */
> > + srt_epoll_remove_usock( p_sys->i_poll_id, p_sys->sock );
> > }
> > + vlc_mutex_unlock( &p_sys->lock );
> > +}
> > +
> > +static bool srt_schedule_reconnect(sout_access_out_t *p_access)
> > +{
> > + char *psz_dst_addr = NULL;
> > + int i_dst_port;
> > + int i_latency;
> > + int stat;
> > + char *psz_passphrase = NULL;
> > +
> > + struct addrinfo hints = {
> > + .ai_socktype = SOCK_DGRAM,
> > + }, *res = NULL;
> > +
> > + sout_access_out_sys_t *p_sys = p_access->p_sys;
> > + bool failed = false;
> > +
> > + psz_passphrase = var_InheritString( p_access, "passphrase" );
> > +
> > + i_dst_port = SRT_DEFAULT_PORT;
> > + char *psz_parser = psz_dst_addr = strdup( p_access->psz_path );
> > + if( !psz_dst_addr )
> > + {
> > + failed = true;
> > + goto out;
> > + }
> > +
> > + if ( psz_parser[0] == '[' )
> > + psz_parser = strchr( psz_parser, ']' );
> > +
> > + psz_parser = strchr( psz_parser ? psz_parser : psz_dst_addr, ':' );
> > + if ( psz_parser != NULL )
> > + {
> > + *psz_parser++ = '\0';
> > + i_dst_port = atoi( psz_parser );
> > + }
> > +
> > + stat = vlc_getaddrinfo( psz_dst_addr, i_dst_port, &hints, &res );
> > + if ( stat )
> > + {
> > + msg_Err( p_access, "Cannot resolve [%s]:%d (reason: %s)",
> > + psz_dst_addr,
> > + i_dst_port,
> > + gai_strerror( stat ) );
> > +
> > + failed = true;
> > + goto out;
> > + }
> > +
> > + /* Always start with a fresh socket */
> > + if ( p_sys->sock != SRT_INVALID_SOCK )
> > + {
> > + srt_epoll_remove_usock( p_sys->i_poll_id, p_sys->sock );
> > + srt_close( p_sys->sock );
> > + }
> > +
> > + p_sys->sock = srt_socket( res->ai_family, SOCK_DGRAM, 0 );
> > + if ( p_sys->sock == SRT_INVALID_SOCK )
> > + {
> > + msg_Err( p_access, "Failed to open socket." );
> > + failed = true;
> > + goto out;
> > + }
> > +
> > + /* Make SRT non-blocking */
> > + srt_setsockopt( p_sys->sock, 0, SRTO_SNDSYN,
> > + &(bool) { false }, sizeof( bool ) );
> > + srt_setsockopt( p_sys->sock, 0, SRTO_RCVSYN,
> > + &(bool) { false }, sizeof( bool ) );
> > +
> > + /* Make sure TSBPD mode is enable (SRT mode) */
> > + srt_setsockopt( p_sys->sock, 0, SRTO_TSBPDMODE,
> > + &(int) { 1 }, sizeof( int ) );
> > +
> > + /* This is an access_out so it is always a sender */
> > + srt_setsockopt( p_sys->sock, 0, SRTO_SENDER,
> > + &(int) { 1 }, sizeof( int ) );
> > +
> > + /* Set latency */
> > + i_latency = var_InheritInteger( p_access, "latency" );
> > + srt_setsockopt( p_sys->sock, 0, SRTO_TSBPDDELAY,
> > + &i_latency, sizeof( int ) );
> > +
> > + if ( psz_passphrase != NULL && psz_passphrase[0] != '\0')
> > + {
> > + int i_key_length = var_InheritInteger( p_access, "key-length" );
> > + srt_setsockopt( p_sys->sock, 0, SRTO_PASSPHRASE,
> > + psz_passphrase, strlen( psz_passphrase ) );
> > + srt_setsockopt( p_sys->sock, 0, SRTO_PBKEYLEN,
> > + &i_key_length, sizeof( int ) );
> > + }
> > +
> > + srt_setsockopt( p_sys->sock, 0, SRTO_SENDER, &(int) { 1 }, sizeof(int) );
> > +
> > + srt_epoll_add_usock( p_sys->i_poll_id, p_sys->sock,
> > + &(int) { SRT_EPOLL_ERR | SRT_EPOLL_OUT });
> > +
> > + /* Schedule a connect */
> > + msg_Dbg( p_access, "Schedule SRT connect (dest addresss: %s, port: %d).",
> > + psz_dst_addr, i_dst_port );
> > +
> > + stat = srt_connect( p_sys->sock, res->ai_addr, res->ai_addrlen );
> > + if ( stat == SRT_ERROR )
> > + {
> > + msg_Err( p_access, "Failed to connect to server (reason: %s)",
> > + srt_getlasterror_str() );
> > + failed = true;
> > + }
> > +
> > +out:
> > + if (failed && p_sys->sock != SRT_INVALID_SOCK)
> > + {
> > + srt_epoll_remove_usock( p_sys->i_poll_id, p_sys->sock );
> > + srt_close(p_sys->sock);
> > + p_sys->sock = SRT_INVALID_SOCK;
> > + }
> > +
> > + free( psz_passphrase );
> > + free( psz_dst_addr );
> > + freeaddrinfo( res );
> > +
> > + return !failed;
> > }
> >
> > static ssize_t Write( sout_access_out_t *p_access, block_t *p_buffer )
> > {
> > sout_access_out_sys_t *p_sys = p_access->p_sys;
> > int i_len = 0;
> > + size_t i_chunk_size = var_InheritInteger( p_access, "chunk-size" );
> > + int i_poll_timeout = var_InheritInteger( p_access, "poll-timeout" );
> > + bool b_interrupted = false;
> >
> > vlc_interrupt_register( srt_wait_interrupted, p_access);
> >
> > @@ -94,56 +224,116 @@ static ssize_t Write( sout_access_out_t *p_access, block_t *p_buffer )
> >
> > while( p_buffer->i_buffer )
> > {
> > - size_t i_write = __MIN( p_buffer->i_buffer, p_sys->i_chunk_size );
> > - SRTSOCKET ready[2];
> > + if ( vlc_killed() )
> > + {
> > + /* We are told to stop. Stop. */
> > + i_len = VLC_EGENERIC;
> > + goto out;
> > + }
> > +
> > + switch( srt_getsockstate( p_sys->sock ) )
> > + {
> > + case SRTS_CONNECTED:
> > + /* Good to go */
> > + break;
> > + case SRTS_BROKEN:
> > + case SRTS_NONEXIST:
> > + case SRTS_CLOSED:
> > + /* Failed. Schedule recovery. */
> > + if ( !srt_schedule_reconnect( p_access ) )
> > + msg_Err( p_access, "Failed to schedule connect");
> > + /* Fall-through */
> > + default:
> > + /* Not ready */
> > + i_len = VLC_EGENERIC;
> > + goto out;
> > + }
> >
> > -retry:
> > + SRTSOCKET ready[1];
> > + int readycnt = 1;
> > if ( srt_epoll_wait( p_sys->i_poll_id,
> > - 0, 0, ready, &(int){ 2 }, p_sys->i_poll_timeout,
> > - &(int) { p_sys->i_pipe_fds[0] }, &(int) { 1 }, NULL, 0 ) == -1 )
> > + 0, 0, &ready[0], &readycnt,
> > + i_poll_timeout, NULL, 0, NULL, 0 ) < 0)
> > {
> > - /* Assuming that timeout error is normal when SRT socket is connected. */
> > - if ( srt_getlasterror( NULL ) == SRT_ETIMEOUT &&
> > - srt_getsockstate( p_sys->sock ) == SRTS_CONNECTED )
> > + if ( vlc_killed() )
> > {
> > - srt_clearlasterror();
> > - goto retry;
> > + /* We are told to stop. Stop. */
> > + i_len = VLC_EGENERIC;
> > + goto out;
> > }
> >
> > - i_len = VLC_EGENERIC;
> > - goto out;
> > - }
> > + /* if 'srt_epoll_wait' is interrupted, we still need to
> > + * finish sending current block or it may be sent only
> > + * partially. TODO: this delay can be prevented,
> > + * possibly with a FIFO and an additional thread.
> > + */
> > + vlc_mutex_lock( &p_sys->lock );
> > + if ( p_sys->b_interrupted )
> > + {
> > + srt_epoll_add_usock( p_sys->i_poll_id, p_sys->sock,
> > + &(int) { SRT_EPOLL_ERR | SRT_EPOLL_OUT });
> > + p_sys->b_interrupted = false;
> > + b_interrupted = true;
> > + }
> > + vlc_mutex_unlock( &p_sys->lock );
> >
> > - bool cancel = 0;
> > - int ret = read( p_sys->i_pipe_fds[0], &cancel, sizeof( bool ) );
> > - if ( ret > 0 && cancel )
> > - {
> > - msg_Dbg( p_access, "Cancelled running" );
> > - i_len = 0;
> > - goto out;
> > + if ( !b_interrupted )
> > + {
> > + continue;
> > + }
> > + else if ( (true) )
> > + {
> > + msg_Dbg( p_access, "srt_epoll_wait was interrupted");
> > + }
> > }
> >
> > - if ( srt_sendmsg2( p_sys->sock, (char *)p_buffer->p_buffer, i_write, 0 ) == SRT_ERROR )
> > - msg_Warn( p_access, "send error: %s", srt_getlasterror_str() );
> > + if ( readycnt > 0 && ready[0] == p_sys->sock
> > + && srt_getsockstate( p_sys->sock ) == SRTS_CONNECTED)
> > + {
> > + size_t i_write = __MIN( p_buffer->i_buffer, i_chunk_size );
> > + if (srt_sendmsg2( p_sys->sock,
> > + (char *)p_buffer->p_buffer, i_write, 0 ) == SRT_ERROR )
> > + {
> > + msg_Warn( p_access, "send error: %s", srt_getlasterror_str() );
> > + i_len = VLC_EGENERIC;
> > + goto out;
> > + }
> >
> > - p_buffer->p_buffer += i_write;
> > - p_buffer->i_buffer -= i_write;
> > + p_buffer->p_buffer += i_write;
> > + p_buffer->i_buffer -= i_write;
> > + }
> > }
> >
> > p_next = p_buffer->p_next;
> > block_Release( p_buffer );
> > p_buffer = p_next;
> > +
> > + if ( b_interrupted )
> > + {
> > + goto out;
> > + }
> > }
> >
> > out:
> > vlc_interrupt_unregister();
> > +
> > + /* Re-add the socket to the poll if we were interrupted */
> > + vlc_mutex_lock( &p_sys->lock );
> > + if ( p_sys->b_interrupted )
> > + {
> > + srt_epoll_add_usock( p_sys->i_poll_id, p_sys->sock,
> > + &(int) { SRT_EPOLL_ERR | SRT_EPOLL_OUT } );
> > + p_sys->b_interrupted = false;
> > + }
> > + vlc_mutex_unlock( &p_sys->lock );
> > +
> > if ( i_len <= 0 ) block_ChainRelease( p_buffer );
> > return i_len;
> > }
> >
> > static int Control( sout_access_out_t *p_access, int i_query, va_list args )
> > {
> > - VLC_UNUSED (p_access);
> > + VLC_UNUSED( p_access );
> >
> > int i_ret = VLC_SUCCESS;
> >
> > @@ -166,15 +356,6 @@ static int Open( vlc_object_t *p_this )
> > sout_access_out_t *p_access = (sout_access_out_t*)p_this;
> > sout_access_out_sys_t *p_sys = NULL;
> >
> > - char *psz_dst_addr = NULL;
> > - int i_dst_port;
> > - int stat;
> > - char *psz_passphrase = NULL;
> > -
> > - struct addrinfo hints = {
> > - .ai_socktype = SOCK_DGRAM,
> > - }, *res = NULL;
> > -
> > if (var_Create ( p_access, "dst-port", VLC_VAR_INTEGER )
> > || var_Create ( p_access, "src-port", VLC_VAR_INTEGER )
> > || var_Create ( p_access, "dst-addr", VLC_VAR_STRING )
> > @@ -184,77 +365,15 @@ static int Open( vlc_object_t *p_this )
> > return VLC_ENOMEM;
> > }
> >
> > - if( !( p_sys = malloc ( sizeof( *p_sys ) ) ) )
> > + p_sys = vlc_obj_calloc( p_this, 1, sizeof( *p_sys ) );
> > + if( unlikely( p_sys == NULL ) )
> > return VLC_ENOMEM;
> >
> > - p_sys->i_chunk_size = var_InheritInteger( p_access, "chunk-size" );
> > - p_sys->i_poll_timeout = var_InheritInteger( p_access, "poll-timeout" );
> > - p_sys->i_latency = var_InheritInteger( p_access, "latency" );
> > - p_sys->i_poll_id = -1;
> > + srt_startup();
> >
> > - p_access->p_sys = p_sys;
> > -
> > - psz_passphrase = var_InheritString( p_access, "passphrase" );
> > -
> > - i_dst_port = SRT_DEFAULT_PORT;
> > - char *psz_parser = psz_dst_addr = strdup( p_access->psz_path );
> > - if( !psz_dst_addr )
> > - {
> > - free( p_sys );
> > - return VLC_ENOMEM;
> > - }
> > -
> > - if (psz_parser[0] == '[')
> > - psz_parser = strchr (psz_parser, ']');
> > -
> > - psz_parser = strchr (psz_parser ? psz_parser : psz_dst_addr, ':');
> > - if (psz_parser != NULL)
> > - {
> > - *psz_parser++ = '\0';
> > - i_dst_port = atoi (psz_parser);
> > - }
> > -
> > - msg_Dbg( p_access, "Setting SRT socket (dest addresss: %s, port: %d).",
> > - psz_dst_addr, i_dst_port );
> > -
> > - stat = vlc_getaddrinfo( psz_dst_addr, i_dst_port, &hints, &res );
> > - if ( stat )
> > - {
> > - msg_Err( p_access, "Cannot resolve [%s]:%d (reason: %s)",
> > - psz_dst_addr,
> > - i_dst_port,
> > - gai_strerror( stat ) );
> > + vlc_mutex_init( &p_sys->lock );
> >
> > - goto failed;
> > - }
> > -
> > - p_sys->sock = srt_socket( res->ai_family, SOCK_DGRAM, 0 );
> > - if ( p_sys->sock == SRT_ERROR )
> > - {
> > - msg_Err( p_access, "Failed to open socket." );
> > - goto failed;
> > - }
> > -
> > - /* Make SRT non-blocking */
> > - srt_setsockopt( p_sys->sock, 0, SRTO_SNDSYN, &(bool) { false }, sizeof( bool ) );
> > -
> > - /* Make sure TSBPD mode is enable (SRT mode) */
> > - srt_setsockopt( p_sys->sock, 0, SRTO_TSBPDMODE, &(int) { 1 }, sizeof( int ) );
> > -
> > - /* This is an access_out so it is always a sender */
> > - srt_setsockopt( p_sys->sock, 0, SRTO_SENDER, &(int) { 1 }, sizeof( int ) );
> > -
> > - /* Set latency */
> > - srt_setsockopt( p_sys->sock, 0, SRTO_TSBPDDELAY, &p_sys->i_latency, sizeof( int ) );
> > -
> > - if ( psz_passphrase != NULL && psz_passphrase[0] != '\0')
> > - {
> > - int i_key_length = var_InheritInteger( p_access, "key-length" );
> > - srt_setsockopt( p_sys->sock, 0, SRTO_PASSPHRASE,
> > - psz_passphrase, strlen( psz_passphrase ) );
> > - srt_setsockopt( p_sys->sock, 0, SRTO_PBKEYLEN,
> > - &i_key_length, sizeof( int ) );
> > - }
> > + p_access->p_sys = p_sys;
> >
> > p_sys->i_poll_id = srt_epoll_create();
> > if ( p_sys->i_poll_id == -1 )
> > @@ -265,53 +384,28 @@ static int Open( vlc_object_t *p_this )
> > goto failed;
> > }
> >
> > - if ( vlc_pipe( p_sys->i_pipe_fds ) != 0 )
> > + if ( !srt_schedule_reconnect( p_access ) )
> > {
> > - msg_Err( p_access, "Failed to create pipe fds." );
> > - goto failed;
> > - }
> > - fcntl( p_sys->i_pipe_fds[0], F_SETFL, O_NONBLOCK | fcntl( p_sys->i_pipe_fds[0], F_GETFL ) );
> > -
> > - srt_epoll_add_usock( p_sys->i_poll_id, p_sys->sock, &(int) { SRT_EPOLL_OUT });
> > - srt_setsockopt( p_sys->sock, 0, SRTO_SENDER, &(int) { 1 }, sizeof(int) );
> > + msg_Err( p_access, "Failed to schedule connect");
> >
> > - srt_epoll_add_ssock( p_sys->i_poll_id, p_sys->i_pipe_fds[0], &(int) { SRT_EPOLL_IN } );
> > -
> > - stat = srt_connect( p_sys->sock, res->ai_addr, sizeof (struct sockaddr));
> > - if ( stat == SRT_ERROR )
> > - {
> > - msg_Err( p_access, "Failed to connect to server (reason: %s)",
> > - srt_getlasterror_str() );
> > goto failed;
> > }
> >
> > p_access->pf_write = Write;
> > p_access->pf_control = Control;
> >
> > - free( psz_passphrase );
> > - free( psz_dst_addr );
> > - freeaddrinfo( res );
> > -
> > return VLC_SUCCESS;
> >
> > failed:
> > - free( psz_passphrase );
> > -
> > - if ( psz_dst_addr != NULL)
> > - free( psz_dst_addr );
> > -
> > - if ( res != NULL )
> > - freeaddrinfo( res );
> > -
> > - vlc_close( p_sys->i_pipe_fds[0] );
> > - vlc_close( p_sys->i_pipe_fds[1] );
> > + vlc_mutex_destroy( &p_sys->lock );
> >
> > if ( p_sys != NULL )
> > {
> > - if ( p_sys->i_poll_id != -1 ) srt_epoll_release( p_sys->i_poll_id );
> > if ( p_sys->sock != -1 ) srt_close( p_sys->sock );
> > + if ( p_sys->i_poll_id != -1 ) srt_epoll_release( p_sys->i_poll_id );
> >
> > - free( p_sys );
> > + vlc_obj_free( p_this, p_sys );
> > + p_access->p_sys = NULL;
> > }
> >
> > return VLC_EGENERIC;
> > @@ -322,13 +416,19 @@ static void Close( vlc_object_t * p_this )
> > sout_access_out_t *p_access = (sout_access_out_t*)p_this;
> > sout_access_out_sys_t *p_sys = p_access->p_sys;
> >
> > - srt_epoll_release( p_sys->i_poll_id );
> > - srt_close( p_sys->sock );
> > + if ( p_sys )
> > + {
> > + vlc_mutex_destroy( &p_sys->lock );
> > +
> > + srt_epoll_remove_usock( p_sys->i_poll_id, p_sys->sock );
> > + srt_close( p_sys->sock );
> > + srt_epoll_release( p_sys->i_poll_id );
> >
> > - vlc_close( p_sys->i_pipe_fds[0] );
> > - vlc_close( p_sys->i_pipe_fds[1] );
> > + vlc_obj_free( p_this, p_sys );
> > + p_access->p_sys = NULL;
> > + }
> >
> > - free( p_sys );
> > + srt_cleanup();
> > }
> >
> > /* Module descriptor */
> >
> > _______________________________________________
> > vlc-commits mailing list
> > vlc-commits at videolan.org
> > https://mailman.videolan.org/listinfo/vlc-commits
>
> _______________________________________________
> vlc-devel mailing list
> To unsubscribe or modify your subscription options:
> https://mailman.videolan.org/listinfo/vlc-devel
More information about the vlc-devel
mailing list