[vlc-devel] access_output: rist: refactor to use the librist library (we now support simple and main profiles)

Thomas Guillem thomas at gllm.fr
Mon Mar 23 09:38:31 CET 2020


Hello,

you are missing a contrib rules.

On Mon, Mar 23, 2020, at 08:19, Steve Lhomme wrote:
> Hi,
> 
> On 2020-03-22 16:30, Sergio M. Ammirata, Ph.D. wrote:
> >  From a114dd5f9491d1ce9e1171d95e7fa2083ad16ec9 Mon Sep 17 00:00:00 2001
> > From: Sergio Ammirata <sergio at ammirata.net <mailto:sergio at ammirata.net>>
> > Date: Sun, 22 Mar 2020 11:26:08 -0400
> > Subject: [PATCH] access_output: rist: refactor to use the librist 
> > library (we now support simple and main profiles)
> 
> This is a little vague (and too long). What do you replace by librist ? 
> There doesn't seem to be any new include.
> 
> > ---
> >   modules/access_output/Makefile.am |   5 +-
> >   modules/access_output/rist.c      | 815 ++++++------------------------
> >   2 files changed, 159 insertions(+), 661 deletions(-)
> > 
> > diff --git a/modules/access_output/Makefile.am 
> > b/modules/access_output/Makefile.am
> > index f6f78c0b73..5e54eae8ff 100644
> > --- a/modules/access_output/Makefile.am
> > +++ b/modules/access_output/Makefile.am
> > @@ -36,9 +36,10 @@ access_out_LTLIBRARIES += $(LTLIBaccess_output_srt)
> >   EXTRA_LTLIBRARIES += libaccess_output_srt_plugin.la
> >   ### RIST ###
> > -libaccess_output_rist_plugin_la_SOURCES = access_output/rist.c 
> > access/rist.h
> > -libaccess_output_rist_plugin_la_CFLAGS = $(AM_CFLAGS) $(BITSTREAM_CFLAGS)
> > +libaccess_output_rist_plugin_la_SOURCES = access_output/rist.c
> > +libaccess_output_rist_plugin_la_CFLAGS = $(AM_CFLAGS)
> >   libaccess_output_rist_plugin_la_LIBADD = $(SOCKET_LIBS)
> > +libaccess_output_rist_plugin_la_LDFLAGS = $(AM_LDFLAGS) -lrist
> 
> This sounds like something that should be detected and set by confingure.ac.
> 
> >   if HAVE_BITSTREAM
> >   access_out_LTLIBRARIES += libaccess_output_rist_plugin.la
> >   endif
> > diff --git a/modules/access_output/rist.c b/modules/access_output/rist.c
> > index 3f2f3901bd..8d044df7f0 100644
> > --- a/modules/access_output/rist.c
> > +++ b/modules/access_output/rist.c
> > @@ -2,7 +2,7 @@
> >    *  * rist.c: RIST (Reliable Internet Stream Transport) output module
> >    
> > *****************************************************************************
> >    * Copyright (C) 2018, DVEO, the Broadcast Division of Computer 
> > Modules, Inc.
> > - * Copyright (C) 2018, SipRadius LLC
> > + * Copyright (C) 2018-2020, SipRadius LLC
> >    *
> >    * Authors: Sergio Ammirata <sergio at ammirata.net 
> > <mailto:sergio at ammirata.net>>
> >    *          Daniele Lacamera <root at danielinux.net 
> > <mailto:root at danielinux.net>>
> > @@ -28,571 +28,63 @@
> >   #include <vlc_common.h>
> >   #include <vlc_interrupt.h>
> > -#include <vlc_fs.h>
> >   #include <vlc_plugin.h>
> >   #include <vlc_sout.h>
> >   #include <vlc_block.h>
> > -#include <vlc_network.h>
> > -#include <vlc_threads.h>
> >   #include <vlc_rand.h>
> > -#ifdef HAVE_POLL
> > -#include <poll.h>
> > -#endif
> >   #include <sys/time.h>
> > -#ifdef HAVE_SYS_SOCKET_H
> > -#include <sys/socket.h>
> > -#endif
> > -#include <bitstream/ietf/rtcp_rr.h>
> > -#include <bitstream/ietf/rtcp_sr.h>
> > -#include <bitstream/ietf/rtcp_fb.h>
> > -#include <bitstream/ietf/rtcp_sdes.h>
> > -#include <bitstream/ietf/rtp.h>
> > -
> > -#include "../access/rist.h"
> > -
> > -/* Uncomment the following to introduce induced packet loss for TESTING 
> > purposes only */
> > -/*#define TEST_PACKET_LOSS*/
> > +#include <librist.h>
> >   /* The default target packet size */
> > -#define RIST_TARGET_PACKET_SIZE 1328
> > -/* The default caching delay for output data */
> > -#define DEFAULT_CACHING_DELAY 50
> > -/* The default buffer size in ms */
> > -#define DEFAULT_BUFFER_SIZE 0
> > -/* Calculate and print stats once per second */
> > -#define STATS_INTERVAL 1000 /*ms*/
> > +#define RIST_TARGET_PACKET_SIZE 1316
> > -#define MPEG_II_TRANSPORT_STREAM (0x21)
> > -#define RIST_DEFAULT_PORT 1968
> > +/* The default max bitrate in Kbps */
> > +#define DEFAULT_MAX_BITRATE 100000
> > +/* The default buffer size in ms */
> > +#define DEFAULT_BUFFER_SIZE 1000
> > +/* Default ports for main profile mode */
> > +#define RIST_DEFAULT_SRC_PORT 1971
> > +#define RIST_DEFAULT_DST_PORT 1968
> > +/* Default profile */
> > +#define RIST_DEFAULT_PROFILE RIST_PROFILE_MAIN
> >   #define SOUT_CFG_PREFIX "sout-rist-"
> >   static const char *const ppsz_sout_options[] = {
> >       "packet-size",
> > -    "caching",
> >       "buffer-size",
> > -    "ssrc",
> >       "stream-name",
> > +    "profile",
> > +    "gre-src-port",
> > +    "gre-dst-port",
> > +    "max-bitrate",
> > +    "encryption-type",
> > +    "shared-secret",
> >       NULL
> >   };
> >   typedef struct
> >   {
> > -    struct       rist_flow *flow;
> > -    uint16_t     rtp_counter;
> > -    char         receiver_name[MAX_CNAME];
> > -    uint64_t     last_rtcp_tx;
> > -    vlc_thread_t ristthread;
> > -    vlc_thread_t senderthread;
> > -    size_t       i_packet_size;
> > -    bool         b_mtu_warning;
> > -    bool         b_ismulticast;
> > -    vlc_mutex_t  lock;
> > -    vlc_mutex_t  fd_lock;
> > -    block_t      *p_pktbuffer;
> > -    uint64_t     i_ticks_caching;
> > -    uint32_t     ssrc;
> > -    block_fifo_t *p_fifo;
> > -    /* stats variables */
> > -    uint64_t     i_last_stat;
> > -    uint32_t     i_retransmit_packets;
> > -    uint32_t     i_total_packets;
> > +    struct rist_client *client_ctx;
> > +    block_t            *p_pktbuffer;
> > +    int                gre_src_port;
> > +    int                gre_dst_port;
> > +    int                rist_profile;
> > +    bool               b_mtu_warning;
> > +    size_t             i_packet_size;
> >   } sout_access_out_sys_t;
> > -static struct rist_flow *rist_init_tx()
> > -{
> > -    struct rist_flow *flow = calloc(1, sizeof(struct rist_flow));
> > -    if (!flow)
> > -        return NULL;
> > -
> > -    flow->reset = 1;
> > -    flow->buffer = calloc(RIST_QUEUE_SIZE, sizeof(struct rtp_pkt));
> > -    if ( unlikely( flow->buffer == NULL ) )
> > -    {
> > -        free(flow);
> > -        return NULL;
> > -    }
> > -    flow->fd_out = -1;
> > -    flow->fd_rtcp = -1;
> > -    flow->fd_rtcp_m = -1;
> > -
> > -    return flow;
> > -}
> > -
> > -static struct rist_flow *rist_udp_transmitter(sout_access_out_t 
> > *p_access, char *psz_dst_server,
> > -    int i_dst_port, bool b_ismulticast)
> > -{
> > -    struct rist_flow *flow;
> > -    flow = rist_init_tx();
> > -    if (!flow)
> > -        return NULL;
> > -
> > -    flow->fd_out = net_ConnectDgram(p_access, psz_dst_server, 
> > i_dst_port, -1, IPPROTO_UDP );
> > -    if (flow->fd_out < 0)
> > -    {
> > -        msg_Err( p_access, "cannot open output socket" );
> > -        goto fail;
> > -    }
> > -
> > -    if (b_ismulticast) {
> > -        flow->fd_rtcp_m = net_OpenDgram(p_access, psz_dst_server, 
> > i_dst_port + 1,
> > -            NULL, 0, IPPROTO_UDP);
> > -        if (flow->fd_rtcp_m < 0)
> > -        {
> > -            msg_Err( p_access, "cannot open multicast nack socket" );
> > -            goto fail;
> > -        }
> > -    }
> > -
> > -    flow->fd_rtcp = net_ConnectDgram(p_access, psz_dst_server, 
> > i_dst_port + 1, -1, IPPROTO_UDP );
> > -    if (flow->fd_rtcp < 0)
> > -    {
> > -        msg_Err( p_access, "cannot open nack socket" );
> > -        goto fail;
> > -    }
> > -
> > -    char *psz_streamname = NULL;
> > -    psz_streamname = var_InheritString( p_access, SOUT_CFG_PREFIX 
> > "stream-name" );
> > -    if ( psz_streamname != NULL && psz_streamname[0] != '\0')
> > -    {
> > -        int name_length = snprintf(flow->cname, MAX_CNAME, "%s", 
> > psz_streamname);
> > -        if (name_length >= MAX_CNAME)
> > -            flow->cname[MAX_CNAME-1] = 0;
> > -        free( psz_streamname );
> > -    }
> > -    else
> > -        populate_cname(flow->fd_rtcp, flow->cname);
> > -
> > -    msg_Info(p_access, "our cname is %s", flow->cname);
> > -
> > -    return flow;
> > -
> > -fail:
> > -    if (flow->fd_out != -1)
> > -        vlc_close(flow->fd_out);
> > -    if (flow->fd_rtcp != -1)
> > -        vlc_close(flow->fd_rtcp);
> > -    if (flow->fd_rtcp_m != -1)
> > -        vlc_close(flow->fd_rtcp_m);
> > -    free(flow->buffer);
> > -    free(flow);
> > -    return NULL;
> > -}
> > -
> > -static void rist_retransmit(sout_access_out_t *p_access, struct 
> > rist_flow *flow, uint16_t seq)
> > -{
> > -    sout_access_out_sys_t *p_sys = p_access->p_sys;
> > -    struct rtp_pkt *pkt = &(flow->buffer[seq]);
> > -    if (pkt->buffer == NULL)
> > -    {
> > -        msg_Err(p_access, "RIST recovery: missing requested packet %d, 
> > buffer not yet full", seq);
> > -        return;
> > -    }
> > -
> > -    /* Mark SSID for retransmission (change the last bit of the ssrc to 
> > 1) */
> > -    pkt->buffer->p_buffer[11] |= (1 << 0);
> > -#ifdef TEST_PACKET_LOSS
> > -#   warning COMPILED WITH SELF INFLICTED PACKET LOSS
> > -        if ((flow->packets_count % 14) == 0) {
> > -            return;
> > -        }
> > -#endif
> > -    uint32_t rtp_age = flow->hi_timestamp - pkt->rtp_ts;
> > -    uint64_t age = ts_get_from_rtp(rtp_age)/1000;
> > -    if (flow->rtp_latency > 0 && rtp_age > flow->rtp_latency)
> > -    {
> > -        msg_Err(p_access, "   Not Sending Nack #%d, too old (age 
> > %"PRId64" ms), current seq is:" \
> > -            " [%d]. Perhaps you should increase the buffer-size ...", 
> > seq, age, flow->wi);
> > -    }
> > -    else
> > -    {
> > -        msg_Dbg(p_access, "   Sending Nack #%d (age %"PRId64" ms), 
> > current seq is: [%d]",
> > -            seq, age, flow->wi);
> > -        p_sys->i_retransmit_packets++;
> > -        vlc_mutex_lock( &p_sys->fd_lock );
> > -        if (rist_Write(flow->fd_out, pkt->buffer->p_buffer, 
> > pkt->buffer->i_buffer)
> > -                != (ssize_t)pkt->buffer->i_buffer) {
> > -            msg_Err(p_access, "Error sending retransmitted packet after 
> > 2 tries ...");
> > -        }
> > -
> > -        vlc_mutex_unlock( &p_sys->fd_lock );
> > -    }
> > -}
> > -
> > -static void process_nack(sout_access_out_t *p_access, uint8_t  ptype, 
> > uint16_t nrecords,
> > -    struct rist_flow *flow, uint8_t *pkt)
> > -{
> > -    sout_access_out_sys_t *p_sys = p_access->p_sys;
> > -    int i,j;
> > -
> > -    /*msg_Info(p_access, "   Nack (BbRR), %d record(s), Window: 
> > [%d:%d-->%d]", nrecords,
> > -        flow->ri, flow->wi, flow->wi-flow->ri);*/
> > -
> > -    if (ptype == RTCP_PT_RTPFR)
> > -    {
> > -        uint8_t pi_ssrc[4];
> > -        rtcp_fb_get_ssrc_media_src(pkt, pi_ssrc);
> > -        if (memcmp(pi_ssrc, "RIST", 4) != 0)
> > -        {
> > -            msg_Info(p_access, "   Ignoring Nack with name %s", pi_ssrc);
> > -            return; /* Ignore app-type not RIST */
> > -        }
> > -
> > -        for (i = 0; i < (nrecords-2); i++) {
> > -            uint16_t missing;
> > -            uint16_t additional;
> > -            uint8_t *rtp_nack_record = (pkt + 12 + i * 4);
> > -            missing = rtcp_fb_nack_get_range_start(rtp_nack_record);
> > -            additional = rtcp_fb_nack_get_range_extra(rtp_nack_record);
> > -            /*msg_Info(p_access, "   Nack (Range), %d, current seq is: 
> > [%d]", missing, flow->wi);*/
> > -            vlc_mutex_lock( &p_sys->lock );
> > -            rist_retransmit(p_access, flow, missing);
> > -            for (j = 0; j < additional; j++) {
> > -                rist_retransmit(p_access, flow, missing + j + 1);
> > -            }
> > -            vlc_mutex_unlock( &p_sys->lock );
> > -        }
> > -    }
> > -    else if (ptype == RTCP_PT_RTPFB)
> > -    {
> > -        for (i = 0; i < (nrecords-2); i++) {
> > -            uint16_t missing;
> > -            uint16_t bitmask;
> > -            uint8_t *rtp_nack_record = (pkt + 12 + i * 4);
> > -            missing = rtcp_fb_nack_get_packet_id(rtp_nack_record);
> > -            bitmask = rtcp_fb_nack_get_bitmask_lost(rtp_nack_record);
> > -            /*msg_Info(p_access, "  Nack (Bitmask), %d, current seq is: 
> > [%d]", missing, flow->wi);*/
> > -            vlc_mutex_lock( &p_sys->lock );
> > -            rist_retransmit(p_access, flow, missing);
> > -            for (j = 0; j < 16; j++) {
> > -                if ((bitmask & (1 << j)) == (1 << j)) {
> > -                    rist_retransmit(p_access, flow, missing + j + 1);
> > -                }
> > -            }
> > -            vlc_mutex_unlock( &p_sys->lock );
> > -        }
> > -    }
> > -    else
> > -    {
> > -        msg_Err(p_access, "   !!! Wrong feedback. Ptype is %02x!=%02x, 
> > FMT: %02x", ptype,
> > -            RTCP_PT_RTPFR, rtcp_fb_get_fmt(pkt));
> > -    }
> > -}
> > -
> > -static void rist_rtcp_recv(sout_access_out_t *p_access, struct 
> > rist_flow *flow, uint8_t *pkt_raw,
> > -    size_t len)
> > -{
> > -    sout_access_out_sys_t *p_sys = p_access->p_sys;
> > -    uint8_t *pkt = pkt_raw;
> > -    uint8_t  ptype;
> > -    uint16_t processed_bytes = 0;
> > -    uint16_t records;
> > -
> > -    while (processed_bytes < len) {
> > -        pkt = pkt_raw + processed_bytes;
> > -        /* safety checks */
> > -        uint16_t bytes_left = len - processed_bytes + 1;
> > -        if ( bytes_left < 4 )
> > -        {
> > -            /* we must have at least 4 bytes */
> > -            msg_Err(p_access, "Rist rtcp packet must have at least 4 
> > bytes, we have %d",
> > -                bytes_left);
> > -            return;
> > -        }
> > -        else if (!rtp_check_hdr(pkt))
> > -        {
> > -            /* check for a valid rtp header */
> > -            msg_Err(p_access, "Malformed feedback packet starting with 
> > %02x, ignoring.", pkt[0]);
> > -            return;
> > -        }
> > -
> > -        ptype =  rtcp_get_pt(pkt);
> > -        records = rtcp_get_length(pkt);
> > -        uint16_t bytes = (uint16_t)(4 * (1 + records));
> > -        if (bytes > bytes_left)
> > -        {
> > -            /* check for a sane number of bytes */
> > -            msg_Err(p_access, "Malformed feedback packet, wrong len %d, 
> > expecting %u bytes in the" \
> > -                " packet, got a buffer of %u bytes. ptype = %d", 
> > rtcp_get_length(pkt), bytes,
> > -                bytes_left, ptype);
> > -            return;
> > -        }
> > -
> > -        switch(ptype) {
> > -            case RTCP_PT_RTPFR:
> > -            case RTCP_PT_RTPFB:
> > -                process_nack(p_access, ptype, records, flow, pkt);
> > -                break;
> > -
> > -            case RTCP_PT_RR:
> > -                /*
> > -                if (p_sys->b_ismulticast == false)
> > -                    process_rr(f, pkt, len);
> > -                */
> > -                break;
> > -
> > -            case RTCP_PT_SDES:
> > -                {
> > -                    if (p_sys->b_ismulticast == false)
> > -                    {
> > -                        int8_t name_length = 
> > rtcp_sdes_get_name_length(pkt);
> > -                        if (name_length > bytes_left)
> > -                        {
> > -                            /* check for a sane number of bytes */
> > -                            msg_Err(p_access, "Malformed SDES packet, 
> > wrong cname len %u, got a " \
> > -                                "buffer of %u bytes.", name_length, 
> > bytes_left);
> > -                            return;
> > -                        }
> > -                        if (memcmp(pkt + RTCP_SDES_SIZE, 
> > p_sys->receiver_name, name_length) != 0)
> > -                        {
> > -                            memcpy(p_sys->receiver_name, pkt + 
> > RTCP_SDES_SIZE, name_length);
> > -                            msg_Info(p_access, "Receiver name: %s", 
> > p_sys->receiver_name);
> > -                        }
> > -                    }
> > -                }
> > -                break;
> > -
> > -            case RTCP_PT_SR:
> > -                break;
> > -
> > -            default:
> > -                msg_Err(p_access, "   Unrecognized RTCP packet with 
> > PTYPE=%02x!!", ptype);
> > -        }
> > -        processed_bytes += bytes;
> > -    }
> > -}
> > -
> > -static void rist_rtcp_send(sout_access_out_t *p_access)
> > -{
> > -    sout_access_out_sys_t *p_sys = p_access->p_sys;
> > -    struct rist_flow *flow = p_sys->flow;
> > -    uint8_t rtcp_buf[RTCP_SR_SIZE + RTCP_SDES_SIZE + MAX_CNAME] = { };
> > -    struct timeval tv;
> > -    int r;
> > -    uint64_t fractions;
> > -    uint16_t namelen = strlen(flow->cname) + 1;
> > -    gettimeofday(&tv, NULL);
> > -
> > -    /* Populate SR for sender report */
> > -    uint8_t *p_sr = rtcp_buf;
> > -    rtp_set_hdr(p_sr);
> > -    rtcp_sr_set_pt(p_sr);
> > -    rtcp_sr_set_length(p_sr, 6);
> > -    rtcp_fb_set_int_ssrc_pkt_sender(p_sr, p_sys->ssrc);
> > -    rtcp_sr_set_ntp_time_msw(p_sr, tv.tv_sec + SEVENTY_YEARS_OFFSET);
> > -    fractions = (uint64_t)tv.tv_usec;
> > -    fractions <<= 32ULL;
> > -    fractions /= 1000000ULL;
> > -    rtcp_sr_set_ntp_time_lsw(p_sr, (uint32_t)fractions);
> > -    rtcp_sr_set_rtp_time(p_sr, rtp_get_ts(vlc_tick_now()));
> > -    vlc_mutex_lock( &p_sys->lock );
> > -    rtcp_sr_set_packet_count(p_sr, flow->packets_count);
> > -    rtcp_sr_set_octet_count(p_sr, flow->bytes_count);
> > -    vlc_mutex_unlock( &p_sys->lock );
> > -
> > -    /* Populate SDES for sender description */
> > -    uint8_t *p_sdes = (rtcp_buf + RTCP_SR_SIZE);
> > -    /* we need to make sure it is a multiple of 4, pad if necessary */
> > -    if ((namelen - 2) & 0x3)
> > -        namelen = ((((namelen - 2) >> 2) + 1) << 2) + 2;
> > -    rtp_set_hdr(p_sdes);
> > -    rtp_set_cc(p_sdes, 1); /* Actually it is source count in this case */
> > -    rtcp_sdes_set_pt(p_sdes);
> > -    rtcp_set_length(p_sdes, (namelen >> 2) + 2);
> > -    rtcp_sdes_set_cname(p_sdes, 1);
> > -    rtcp_sdes_set_name_length(p_sdes, strlen(flow->cname));
> > -    p_sdes += RTCP_SDES_SIZE;
> > -    strlcpy((char *)p_sdes, flow->cname, namelen);
> > -
> > -    /* Send the rtcp message */
> > -    r = send(flow->fd_rtcp, rtcp_buf, RTCP_SR_SIZE + RTCP_SDES_SIZE + 
> > namelen, 0);
> > -    (void)r;
> > -}
> > -
> > -static void *rist_thread(void *data)
> > -{
> > -    sout_access_out_t *p_access = data;
> > -    sout_access_out_sys_t *p_sys = p_access->p_sys;
> > -    uint64_t now;
> > -    uint8_t pkt[RTP_PKT_SIZE];
> > -    struct pollfd pfd[2];
> > -    int ret;
> > -    ssize_t r;
> > -
> > -    int poll_sockets = 1;
> > -    pfd[0].fd = p_sys->flow->fd_rtcp;
> > -    pfd[0].events = POLLIN;
> > -    if (p_sys->b_ismulticast)
> > -    {
> > -        pfd[1].fd = p_sys->flow->fd_rtcp_m;
> > -        pfd[1].events = POLLIN;
> > -        poll_sockets++;
> > -    }
> > -
> > -    for (;;) {
> > -        ret = poll(pfd, poll_sockets, RTCP_INTERVAL >> 1);
> > -        int canc = vlc_savecancel();
> > -        if (ret > 0)
> > -        {
> > -            if (pfd[0].revents & POLLIN)
> > -            {
> > -                r = rist_Read(p_sys->flow->fd_rtcp, pkt, RTP_PKT_SIZE);
> > -                if (r == RTP_PKT_SIZE) {
> > -                    msg_Err(p_access, "Rist RTCP messsage is too big 
> > (%zd bytes) and was probably " \
> > -                        "cut, please keep it under %d bytes", r, 
> > RTP_PKT_SIZE);
> > -                }
> > -                if (unlikely(r == -1)) {
> > -                    msg_Err(p_access, "socket %d error: %s\n", 
> > p_sys->flow->fd_rtcp,
> > -                        gai_strerror(errno));
> > -                }
> > -                else {
> > -                    rist_rtcp_recv(p_access, p_sys->flow, pkt, r);
> > -                }
> > -            }
> > -            if (p_sys->b_ismulticast && (pfd[1].revents & POLLIN))
> > -            {
> > -                r = rist_Read(p_sys->flow->fd_rtcp_m, pkt, RTP_PKT_SIZE);
> > -                if (r == RTP_PKT_SIZE) {
> > -                    msg_Err(p_access, "Rist RTCP messsage is too big 
> > (%zd bytes) and was " \
> > -                        "probably cut, please keep it under %d bytes", 
> > r, RTP_PKT_SIZE);
> > -                }
> > -                if (unlikely(r == -1)) {
> > -                    msg_Err(p_access, "mcast socket %d error: %s\n", 
> > p_sys->flow->fd_rtcp_m,
> > -                        gai_strerror(errno));
> > -                }
> > -                else {
> > -                    rist_rtcp_recv(p_access, p_sys->flow, pkt, r);
> > -                }
> > -            }
> > -        }
> > -
> > -        /* And, in any case: */
> > -        now = vlc_tick_now();
> > -        if ((now - p_sys->last_rtcp_tx) > VLC_TICK_FROM_MS(RTCP_INTERVAL))
> > -        {
> > -            rist_rtcp_send(p_access);
> > -            p_sys->last_rtcp_tx = now;
> > -        }
> > -        vlc_restorecancel (canc);
> > -    }
> > -
> > -    return NULL;
> > -}
> > -
> > -/****************************************************************************
> > - * RTP send
> > - 
> > ****************************************************************************/
> > -static void* ThreadSend( void *data )
> > -{
> > -    sout_access_out_t *p_access = data;
> > -    sout_access_out_sys_t *p_sys = p_access->p_sys;
> > -    vlc_tick_t i_caching = p_sys->i_ticks_caching;
> > -    struct rist_flow *flow = p_sys->flow;
> > -
> > -    for (;;)
> > -    {
> > -        ssize_t len = 0;
> > -        uint16_t seq = 0;
> > -        uint32_t pkt_ts = 0;
> > -        block_t *out = block_FifoGet( p_sys->p_fifo );
> > -
> > -        block_cleanup_push( out );
> > -        vlc_tick_wait (out->i_dts + i_caching);
> > -        vlc_cleanup_pop();
> > -
> > -        len = out->i_buffer;
> > -        int canc = vlc_savecancel();
> > -
> > -        seq = rtp_get_seqnum(out->p_buffer);
> > -        pkt_ts = rtp_get_timestamp(out->p_buffer);
> > -
> > -        vlc_mutex_lock( &p_sys->fd_lock );
> > -#ifdef TEST_PACKET_LOSS
> > -#   warning COMPILED WITH SELF INFLICTED PACKET LOSS
> > -        if ((seq % 14) == 0) {
> > -            /*msg_Err(p_access, "Dropped packet with seq number %d 
> > ...", seq);*/
> > -        }
> > -        else
> > -        {
> > -            if (rist_Write(flow->fd_out, out->p_buffer, len) != len) {
> > -                msg_Err(p_access, "Error sending data packet after 2 
> > tries ...");
> > -            }
> > -        }
> > -#else
> > -        if (rist_Write(flow->fd_out, out->p_buffer, len) != len) {
> > -            msg_Err(p_access, "Error sending data packet after 2 tries 
> > ...");
> > -        }
> > -#endif
> > -        vlc_mutex_unlock( &p_sys->fd_lock );
> > -
> > -        /* Insert Into Queue */
> > -        vlc_mutex_lock( &p_sys->lock );
> > -        /* Always replace the existing one with the new one */
> > -        struct rtp_pkt *pkt;
> > -        pkt = &(flow->buffer[seq]);
> > -        if (pkt->buffer)
> > -        {
> > -            block_Release(pkt->buffer);
> > -            pkt->buffer = NULL;
> > -        }
> > -        pkt->rtp_ts = pkt_ts;
> > -        pkt->buffer = out;
> > -
> > -        if (flow->reset == 1)
> > -        {
> > -            msg_Info(p_access, "Traffic detected");
> > -            /* First packet in the queue */
> > -            flow->reset = 0;
> > -        }
> > -        flow->wi = seq;
> > -        flow->hi_timestamp = pkt_ts;
> > -        /* Stats for RTCP feedback packets */
> > -        flow->packets_count++;
> > -        flow->bytes_count += len;
> > -        flow->last_out = seq;
> > -        vlc_mutex_unlock( &p_sys->lock );
> > -
> > -        /* We print out the stats once per second */
> > -        uint64_t now = vlc_tick_now();
> > -        uint64_t interval = (now - p_sys->i_last_stat);
> > -        if ( interval > VLC_TICK_FROM_MS(STATS_INTERVAL) )
> > -        {
> > -            if (p_sys->i_retransmit_packets > 0)
> > -            {
> > -                float quality = 100;
> > -                if (p_sys->i_total_packets > 0)
> > -                    quality = (float)100 - 
> > (float)100*(float)(p_sys->i_retransmit_packets)
> > -                        /(float)p_sys->i_total_packets;
> > -                msg_Info(p_access, "STATS: Total %u, Retransmitted %u, 
> > Link Quality %.2f%%",
> > -                    p_sys->i_total_packets, 
> > p_sys->i_retransmit_packets, quality);
> > -            }
> > -            p_sys->i_last_stat = now;
> > -            p_sys->i_retransmit_packets = 0;
> > -            p_sys->i_total_packets = 0;
> > -        }
> > -        p_sys->i_total_packets++;
> > -
> > -        vlc_restorecancel (canc);
> > -    }
> > -    return NULL;
> > -}
> > -
> > -static void SendtoFIFO( sout_access_out_t *p_access, block_t *buffer )
> > +static uint64_t i_dts_to_ntp64( vlc_tick_t i_dts )
> >   {
> > -    sout_access_out_sys_t *p_sys = p_access->p_sys;
> > -    uint16_t seq = p_sys->rtp_counter++;
> > -
> > -    /* Set fresh rtp header data */
> > -    uint8_t *bufhdr = buffer->p_buffer;
> > -    rtp_set_hdr(bufhdr);
> > -    rtp_set_type(bufhdr, MPEG_II_TRANSPORT_STREAM);
> > -    rtp_set_seqnum(bufhdr, seq);
> > -    rtp_set_int_ssrc(bufhdr, p_sys->ssrc);
> > -    uint32_t pkt_ts = rtp_get_ts(buffer->i_dts);
> > -    rtp_set_timestamp(bufhdr, pkt_ts);
> > -
> > -    block_t *pkt = block_Duplicate(buffer);
> > -    block_FifoPut( p_sys->p_fifo, pkt );
> > +    lldiv_t d = lldiv (i_dts, CLOCK_FREQ);
> > +    struct timespec ts = { d.quot, NS_FROM_VLC_TICK( d.rem ) };
> > +    // Convert nanoseconds to 32-bits fraction (232 picosecond units)
> > +    uint64_t t = (uint64_t)(ts.tv_nsec) << 32;
> > +    t /= 1000000000;
> > +    // There is 70 years (incl. 17 leap ones) offset to the Unix Epoch.
> > +    // No leap seconds during that period since they were not invented yet.
> > +    t |= ((70LL * 365 + 17) * 24 * 60 * 60 + ts.tv_sec) << 32;
> > +    return t; // nanoseconds (technically, 232.831 picosecond units)
> >   }
> >   static ssize_t Write( sout_access_out_t *p_access, block_t *p_buffer )
> > @@ -616,8 +108,9 @@ static ssize_t Write( sout_access_out_t *p_access, 
> > block_t *p_buffer )
> >           /* Temp buffer is already too large, flush */
> >           if( p_sys->p_pktbuffer->i_buffer + p_buffer->i_buffer > 
> > p_sys->i_packet_size )
> >           {
> > -            SendtoFIFO(p_access, p_sys->p_pktbuffer);
> > -            p_sys->p_pktbuffer->i_buffer = RTP_HEADER_SIZE;
> > +            rist_client_write_timed(p_sys->client_ctx, 
> > p_sys->p_pktbuffer->p_buffer, p_sys->p_pktbuffer->i_buffer,
> > +                                    p_sys->gre_src_port, 
> > p_sys->gre_dst_port, i_dts_to_ntp64(p_sys->p_pktbuffer->i_dts));
> > +            p_sys->p_pktbuffer->i_buffer = 0;
> >           }
> >           i_len += p_buffer->i_buffer;
> > @@ -629,7 +122,7 @@ static ssize_t Write( sout_access_out_t *p_access, 
> > block_t *p_buffer )
> >               i_block_split++;
> > -            if( p_sys->p_pktbuffer->i_buffer == RTP_HEADER_SIZE )
> > +            if( p_sys->p_pktbuffer->i_buffer == 0 )
> >               {
> >                   p_sys->p_pktbuffer->i_dts = p_buffer->i_dts;
> >               }
> > @@ -646,8 +139,9 @@ static ssize_t Write( sout_access_out_t *p_access, 
> > block_t *p_buffer )
> >                *  larger than the packet-size because we need to 
> > continue the inner loop */
> >               if( p_sys->p_pktbuffer->i_buffer == p_sys->i_packet_size 
> > || i_block_split > 1 )
> >               {
> > -                SendtoFIFO(p_access, p_sys->p_pktbuffer);
> > -                p_sys->p_pktbuffer->i_buffer = RTP_HEADER_SIZE;
> > +                rist_client_write_timed(p_sys->client_ctx, 
> > p_sys->p_pktbuffer->p_buffer, p_sys->p_pktbuffer->i_buffer,
> > +                                        p_sys->gre_src_port, 
> > p_sys->gre_dst_port, i_dts_to_ntp64(p_sys->p_pktbuffer->i_dts));
> > +                p_sys->p_pktbuffer->i_buffer = 0;
> >               }
> >           }
> > @@ -684,54 +178,16 @@ static int Control( sout_access_out_t *p_access, 
> > int i_query, va_list args )
> >       return i_ret;
> >   }
> > -static void Clean( sout_access_out_t *p_access )
> > -{
> > -    sout_access_out_sys_t *p_sys = p_access->p_sys;
> > -
> > -    if( likely(p_sys->p_fifo != NULL) )
> > -        block_FifoRelease( p_sys->p_fifo );
> > -
> > -    if ( p_sys->flow )
> > -    {
> > -        if (p_sys->flow->fd_out >= 0) {
> > -            net_Close (p_sys->flow->fd_out);
> > -        }
> > -        if (p_sys->flow->fd_rtcp >= 0) {
> > -            net_Close (p_sys->flow->fd_rtcp);
> > -        }
> > -        if (p_sys->flow->fd_rtcp_m >= 0) {
> > -            net_Close (p_sys->flow->fd_rtcp_m);
> > -        }
> > -        for (int i=0; i<RIST_QUEUE_SIZE; i++) {
> > -            struct rtp_pkt *pkt = &(p_sys->flow->buffer[i]);
> > -            if (pkt->buffer)
> > -            {
> > -                block_Release(pkt->buffer);
> > -                pkt->buffer = NULL;
> > -            }
> > -        }
> > -        free(p_sys->flow->buffer);
> > -        free(p_sys->flow);
> > -    }
> > -
> > -    vlc_mutex_destroy( &p_sys->lock );
> > -    vlc_mutex_destroy( &p_sys->fd_lock );
> > -    if (p_sys->p_pktbuffer)
> > -        block_Release(p_sys->p_pktbuffer);
> > -}
> > -
> >   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;
> > -    vlc_cancel(p_sys->ristthread);
> > -    vlc_cancel(p_sys->senderthread);
> > -
> > -    vlc_join(p_sys->ristthread, NULL);
> > -    vlc_join(p_sys->senderthread, NULL);
> > +    if (p_sys->p_pktbuffer)
> > +        block_Release(p_sys->p_pktbuffer);
> > -    Clean( p_access );
> > +    rist_client_destroy(p_sys->client_ctx);
> > +    p_sys->client_ctx = NULL;
> >   }
> >   static int Open( vlc_object_t *p_this )
> > @@ -754,105 +210,139 @@ static int Open( vlc_object_t *p_this )
> >       if( unlikely( p_sys == NULL ) )
> >           return VLC_ENOMEM;
> > -    int i_dst_port = RIST_DEFAULT_PORT;
> > -    char *psz_dst_addr;
> > -    char *psz_parser = psz_dst_addr = strdup( p_access->psz_path );
> > -    if( !psz_dst_addr )
> > +    p_sys->gre_src_port = var_InheritInteger(p_access, SOUT_CFG_PREFIX 
> > "gre-src-port");
> > +    p_sys->gre_dst_port = var_InheritInteger(p_access, SOUT_CFG_PREFIX 
> > "gre-dst-port");
> > +    if (p_sys->gre_dst_port % 2 != 0) {
> > +        msg_Err( p_access, "GRE destination port must be an even 
> > number." );
> >           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 );
> >       }
> > -    vlc_mutex_init( &p_sys->lock );
> > -    vlc_mutex_init( &p_sys->fd_lock );
> > +    p_sys->i_packet_size = var_InheritInteger(p_access, SOUT_CFG_PREFIX 
> > "packet-size" );
> > +    p_sys->p_pktbuffer = block_Alloc( p_sys->i_packet_size );
> > +    if( unlikely(p_sys->p_pktbuffer == NULL) )
> > +        goto failed;
> > -    msg_Info(p_access, "Connecting RIST output to %s:%d and %s:%d", 
> > psz_dst_addr, i_dst_port,
> > -        psz_dst_addr, i_dst_port+1);
> > -    p_sys->b_ismulticast = is_multicast_address(psz_dst_addr);
> > -    struct rist_flow *flow = rist_udp_transmitter(p_access, 
> > psz_dst_addr, i_dst_port,
> > -        p_sys->b_ismulticast);
> > -    free (psz_dst_addr);
> > -    if (!flow)
> > +    p_sys->rist_profile = var_InheritInteger(p_access, SOUT_CFG_PREFIX 
> > "profile");
> > +    if (rist_client_create(&p_sys->client_ctx, p_sys->rist_profile) != 0) {
> > +        msg_Err( p_access, "Could not create rist client context\n");
> >           goto failed;
> > +    }
> > -    p_sys->flow = flow;
> > -    flow->latency = var_InheritInteger(p_access, SOUT_CFG_PREFIX 
> > "buffer-size");
> > -    flow->rtp_latency = rtp_get_ts(VLC_TICK_FROM_MS(flow->latency));
> > -    p_sys->ssrc = var_InheritInteger(p_access, SOUT_CFG_PREFIX "ssrc");
> > -    if (p_sys->ssrc == 0) {
> > -        vlc_rand_bytes(&p_sys->ssrc, 4);
> > +    char *psz_streamname = NULL;
> > +    psz_streamname = var_InheritString( p_access, SOUT_CFG_PREFIX 
> > "stream-name" );
> > +    if ( psz_streamname != NULL && psz_streamname[0] != '\0')
> > +    {
> > +        if (rist_client_set_cname(p_sys->client_ctx, psz_streamname, 
> > strlen(psz_streamname)) != 0) {
> > +            msg_Err( p_access, "Could not set the cname\n");
> > +            goto failed;
> > +        }
> > +        free( psz_streamname );
> >       }
> > -    /* Last bit of ssrc must be 0 for normal data and 1 for retries */
> > -    p_sys->ssrc &= ~(1 << 0);
> > -    msg_Info(p_access, "SSRC: 0x%08X", p_sys->ssrc);
> > -    p_sys->i_ticks_caching = VLC_TICK_FROM_MS(var_InheritInteger( 
> > p_access,
> > -        SOUT_CFG_PREFIX "caching"));
> > -    p_sys->i_packet_size = var_InheritInteger(p_access, SOUT_CFG_PREFIX 
> > "packet-size" );
> > -    p_sys->p_fifo = block_FifoNew();
> > -    if( unlikely(p_sys->p_fifo == NULL) )
> > -        goto failed;
> > -    p_sys->p_pktbuffer = block_Alloc( p_sys->i_packet_size );
> > -    if( unlikely(p_sys->p_pktbuffer == NULL) )
> > +    uint64_t now;
> > +    struct timeval time;
> > +    gettimeofday(&time, NULL);
> > +    now = time.tv_sec * 1000000;
> > +    now += time.tv_usec;
> > +    uint32_t adv_flow_id = (uint32_t)(now >> 16);
> > +    // It must me an even number
> > +    adv_flow_id &= ~(1UL << 0);
> > +
> > +    if(rist_client_init(p_sys->client_ctx, adv_flow_id, RIST_LOG_WARN, 
> > NULL, NULL, NULL) < 0) {
> > +        msg_Err( p_access, "Could not initialize rist client\n");
> >           goto failed;
> > +    }
> > -    p_sys->p_pktbuffer->i_buffer = RTP_HEADER_SIZE;
> > -
> > -    p_access->p_sys = p_sys;
> > +    int i_encryption_type = var_InheritInteger(p_access, 
> > SOUT_CFG_PREFIX "encryption-type");
> > +    char *psz_shared_secret = NULL;
> > +    psz_shared_secret = var_InheritString( p_access, SOUT_CFG_PREFIX 
> > "stream-name" );
> > +    if (psz_shared_secret != NULL && psz_shared_secret[0] != '\0') {
> > +        if (i_encryption_type) {
> > +            int keysize =  i_encryption_type == 1 ? 128 : 256;
> > +            if (rist_client_encrypt_enable(p_sys->client_ctx, 
> > psz_shared_secret, keysize) == -1) {
> > +                msg_Err( p_access, "Could not enable encryption\n");
> > +                free(psz_shared_secret);
> > +                goto failed;
> > +            }
> > +        }
> > +        free(psz_shared_secret);
> > +    }
> > -    if( vlc_clone(&p_sys->senderthread, ThreadSend, p_access, 
> > VLC_THREAD_PRIORITY_HIGHEST ) )
> > -    {
> > -        msg_Err(p_access, "Failed to create sender thread.");
> > +    const struct rist_peer_config peer_config = {
> > +        .address = p_access->psz_path,
> > +        .recovery_mode = RIST_RECOVERY_MODE_TIME,
> > +        .recovery_maxbitrate = var_InheritInteger(p_access, 
> > SOUT_CFG_PREFIX "max-bitrate"),
> > +        .recovery_maxbitrate_return = 0,
> > +        .recovery_length_min = var_InheritInteger(p_access, 
> > SOUT_CFG_PREFIX "buffer-size"),
> > +        .recovery_length_max = var_InheritInteger(p_access, 
> > SOUT_CFG_PREFIX "buffer-size"),
> > +        .recover_reorder_buffer = 70,
> > +        .recovery_rtt_min = 50,
> > +        .recovery_rtt_max = 500,
> > +        .weight = 5,
> > +        .bufferbloat_mode = RIST_BUFFER_BLOAT_MODE_NORMAL,
> > +        .bufferbloat_limit = 7,
> > +        .bufferbloat_hard_limit = 20
> > +    };
> > +
> > +    struct rist_peer *peer;
> > +    if (rist_client_add_peer(p_sys->client_ctx, &peer_config, &peer) == 
> > -1) {
> > +        msg_Err( p_access, "Could not add peer connector to client\n");
> >           goto failed;
> >       }
> > -    if (vlc_clone(&p_sys->ristthread, rist_thread, p_access, 
> > VLC_THREAD_PRIORITY_INPUT))
> > -    {
> > -        msg_Err(p_access, "Failed to create worker thread.");
> > -        vlc_cancel(p_sys->senderthread);
> > -        vlc_join(p_sys->senderthread, NULL);
> > +    if (rist_client_start(p_sys->client_ctx) == -1) {
> > +        msg_Err( p_access, "Could not start rist client\n");
> >           goto failed;
> >       }
> > +    p_access->p_sys = p_sys;
> >       p_access->pf_write = Write;
> >       p_access->pf_control = Control;
> >       return VLC_SUCCESS;
> >   failed:
> > -    Clean( p_access );
> > +    rist_client_destroy(p_sys->client_ctx);
> > +    p_sys->client_ctx = NULL;
> >       return VLC_EGENERIC;
> >   }
> > -#define CACHING_TEXT N_("RIST data output caching size (ms)")
> > -#define CACHING_LONGTEXT N_( \
> > -    "Having this cache will guarantee that the packets going out are " \
> > -    "delivered at a spacing determined by the chain timestamps thus 
> > ensuring " \
> > -    "a near jitter free output. Be aware that this setting will also 
> > add to " \
> > -    "the overall latency of the stream." )
> > -
> >   #define BUFFER_TEXT N_("RIST retry-buffer queue size (ms)")
> >   #define BUFFER_LONGTEXT N_( \
> > -    "This must match the buffer size (latency) configured on the server 
> > side. If you " \
> > -    "are not sure, leave the default of 0 which will set it the maximum " \
> > -    "value and will use about 100MB of RAM" )
> > +    "This must match the buffer size (latency) configured on the 
> > receiver side. If you " \
> > +    "are not sure, leave it blank and it will use 1000ms" )
> > -#define SSRC_TEXT N_("SSRC used in RTP output (default is random, i.e. 0)")
> > -#define SSRC_LONGTEXT N_( \
> > -    "Use this setting to specify a known SSRC for the RTP header. This 
> > is only useful " \
> > -    "if your receiver acts on it. When using VLC as receiver, it is not." )
> > +#define MAX_BITRATE_TEXT N_("Max bitrate in Kbps")
> > +#define MAX_BITRATE_LONGTEXT N_( \
> > +    "Use this value to guarantee that data+retries bitrate never 
> > exceeds your pipe size. " \
> > +    "Default value is 100000 Kbps (100 Mbps)" )
> >   #define NAME_TEXT N_("Stream name")
> >   #define NAME_LONGTEXT N_( \
> >       "This Stream name will be sent to the receiver using the rist RTCP 
> > channel" )
> > +#define PROFILE_TEXT N_("Rist Profile")
> > +#define PROFILE_LONGTEXT N_( \
> > +    "Select the rist profile to use, 0 is simple, 1 is main. Default is 
> > main (1)" )
> > +
> > +#define SRC_PORT_TEXT N_("GRE Source Port")
> > +#define SRC_PORT_LONGTEXT N_( \
> > +    "Source port to be used inside the reduced-mode of the main profile" )
> > +
> > +#define DST_PORT_TEXT N_("GRE Destination Port")
> > +#define DST_PORT_LONGTEXT N_( \
> > +    "Destination port to be used inside the reduced-mode of the main 
> > profile" )
> > +
> > +#define ENCRYPTION_TYPE_TEXT N_("Encryption Type")
> > +#define ENCRYPTION_TYPE_LONGTEXT N_( \
> > +    "Type of encryption to use: 0 = disabled, 1 = AES 128, 2 = AES 256, 
> > default is " \
> > +    "disabled" )
> > +
> > +#define SHARED_SECRET_TEXT N_("Shared Secret")
> > +#define SHARED_SECRET_LONGTEXT N_( \
> > +    "This shared secret is a passphare shared between sender and 
> > receiver. The AES key " \
> > +    "is derived from it" )
> > +
> >   /* Module descriptor */
> >   vlc_module_begin()
> > @@ -863,13 +353,20 @@ vlc_module_begin()
> >       add_integer( SOUT_CFG_PREFIX "packet-size", RIST_TARGET_PACKET_SIZE,
> >               N_("RIST target packet size (bytes)"), NULL, true )
> > -    add_integer( SOUT_CFG_PREFIX "caching", DEFAULT_CACHING_DELAY,
> > -            CACHING_TEXT, CACHING_LONGTEXT, true )
> >       add_integer( SOUT_CFG_PREFIX "buffer-size", DEFAULT_BUFFER_SIZE,
> >               BUFFER_TEXT, BUFFER_LONGTEXT, true )
> > -    add_integer( SOUT_CFG_PREFIX "ssrc", 0,
> > -            SSRC_TEXT, SSRC_LONGTEXT, true )
> > +    add_integer( SOUT_CFG_PREFIX "max-bitrate", DEFAULT_MAX_BITRATE,
> > +            MAX_BITRATE_TEXT, MAX_BITRATE_LONGTEXT, true )
> > +    add_integer( SOUT_CFG_PREFIX "profile", RIST_DEFAULT_PROFILE,
> > +            PROFILE_TEXT, PROFILE_LONGTEXT, true )
> > +    add_integer( SOUT_CFG_PREFIX "gre-src-port", RIST_DEFAULT_SRC_PORT,
> > +            SRC_PORT_TEXT, SRC_PORT_LONGTEXT, true )
> > +    add_integer( SOUT_CFG_PREFIX "gre-dst-port", RIST_DEFAULT_DST_PORT,
> > +            DST_PORT_TEXT, DST_PORT_LONGTEXT, true )
> > +    add_integer( SOUT_CFG_PREFIX "encryption-type", 0,
> > +            ENCRYPTION_TYPE_TEXT, ENCRYPTION_TYPE_LONGTEXT, true )
> >       add_string( SOUT_CFG_PREFIX "stream-name", NULL, NAME_TEXT, 
> > NAME_LONGTEXT, true )
> > +    add_string( SOUT_CFG_PREFIX "shared-secret", NULL, 
> > SHARED_SECRET_TEXT, SHARED_SECRET_LONGTEXT, true )
> >       set_capability( "sout access", 0 )
> >       add_shortcut( "rist", "tr06" )
> > -- 
> > 2.17.1
> > 
> > 
> > 
> > _______________________________________________
> > vlc-devel mailing list
> > To unsubscribe or modify your subscription options:
> > https://mailman.videolan.org/listinfo/vlc-devel
> > 
> 
> 
> _______________________________________________
> 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