[multicat-devel] [Git][videolan/multicat][master] Add new smooThS program to minimize jitter.
Christophe Massiot (@cmassiot)
gitlab at videolan.org
Mon Sep 11 07:05:45 UTC 2023
Christophe Massiot pushed to branch master at VideoLAN / multicat
Commits:
5e784d4b by Christophe Massiot at 2023-09-11T09:05:00+02:00
Add new smooThS program to minimize jitter.
- - - - -
9 changed files:
- .gitignore
- Makefile
- NEWS
- README
- multilive.c
- + smooths.c
- + ulist.h
- util.c
- util.h
Changes:
=====================================
.gitignore
=====================================
@@ -8,3 +8,4 @@
/multilive
/offsets
/reordertp
+/smooths
=====================================
Makefile
=====================================
@@ -16,12 +16,13 @@ OBJ_OFFSETS = offsets.o util.o
OBJ_LASTS = lasts.o
OBJ_MULTICAT_VALIDATE = multicat_validate.o util.o
OBJ_MULTILIVE = multilive.o util.o
+OBJ_SMOOTHS = smooths.o util.o
PREFIX ?= /usr/local
BIN = $(DESTDIR)/$(PREFIX)/bin
MAN = $(DESTDIR)/$(PREFIX)/share/man/man1
-all: multicat ingests aggregartp reordertp offsets lasts multicat_validate multilive
+all: multicat ingests aggregartp reordertp offsets lasts multicat_validate multilive smooths
$(OBJ_MULTICAT): Makefile util.h
$(OBJ_INGESTS): Makefile util.h
@@ -31,6 +32,7 @@ $(OBJ_OFFSETS): Makefile util.h
$(OBJ_LASTS): Makefile
$(OBJ_MULTICAT_VALIDATE): Makefile util.h
$(OBJ_MULTILIVE): Makefile util.h
+$(OBJ_SMOOTHS): Makefile util.h ulist.h
multicat: $(OBJ_MULTICAT)
$(CC) $(LDFLAGS) -o $@ $(OBJ_MULTICAT) $(LDLIBS)
@@ -56,17 +58,20 @@ multicat_validate: $(OBJ_MULTICAT_VALIDATE)
multilive: $(OBJ_MULTILIVE)
$(CC) $(LDFLAGS) -o $@ $(OBJ_MULTILIVE) $(LDLIBS)
+smooths: $(OBJ_SMOOTHS)
+ $(CC) $(LDFLAGS) -o $@ $(OBJ_SMOOTHS) $(LDLIBS)
+
clean:
- -rm -f multicat $(OBJ_MULTICAT) ingests $(OBJ_INGESTS) aggregartp $(OBJ_AGGREGARTP) reordertp $(OBJ_REORDERTP) offsets $(OBJ_OFFSETS) lasts $(OBJ_LASTS) multicat_validate $(OBJ_MULTICAT_VALIDATE) multilive $(OBJ_MULTILIVE)
+ -rm -f multicat $(OBJ_MULTICAT) ingests $(OBJ_INGESTS) aggregartp $(OBJ_AGGREGARTP) reordertp $(OBJ_REORDERTP) offsets $(OBJ_OFFSETS) lasts $(OBJ_LASTS) multicat_validate $(OBJ_MULTICAT_VALIDATE) multilive $(OBJ_MULTILIVE) smooths $(OBJ_SMOOTHS)
install: all
@install -d $(BIN)
@install -d $(MAN)
- @install multicat ingests aggregartp reordertp offsets lasts multicat_validate multilive $(BIN)
+ @install multicat ingests aggregartp reordertp offsets lasts multicat_validate multilive smooths $(BIN)
@install multicat.1 ingests.1 aggregartp.1 reordertp.1 offsets.1 lasts.1 $(MAN)
uninstall:
- @rm $(BIN)/multicat $(BIN)/ingests $(BIN)/aggregartp $(BIN)/reordertp $(BIN)/offsets $(BIN)/lasts $(BIN)/multicat_validate $(BIN)/multilive
+ @rm $(BIN)/multicat $(BIN)/ingests $(BIN)/aggregartp $(BIN)/reordertp $(BIN)/offsets $(BIN)/lasts $(BIN)/multicat_validate $(BIN)/multilive $(BIN)/smooths
@rm $(MAN)/multicat.1 $(MAN)/ingests.1 $(MAN)/aggregartp.1 $(MAN)/reordertp.1 $(MAN)/offsets.1 $(MAN)/lasts.1
dist:
=====================================
NEWS
=====================================
@@ -1,6 +1,7 @@
Changes between 2.3 and 2.4:
----------------------------
* Fix multilive
+ * Add new smooths program
Changes between 2.2 and 2.3:
----------------------------
=====================================
README
=====================================
@@ -35,6 +35,10 @@ of lost packets via an additional UDP or TCP connection. ReordeRTP can also
smooth up the reception of a stream from a link that is known to reorder
and add jitter to packets.
+To minimize jitter and please IAT analysers, you can also use smooThS.
+SmooThS reads the RTP timestamp and actively waits for the proper time
+to send the packet.
+
The multicat suite of applications is very lightweight and designed to
operate in tight environments. Memory and CPU usages are kept to a minimum,
and they feature only one thread of execution.
@@ -231,3 +235,15 @@ Running another master on a different machine at a higher priority (who will
preempt the other master):
multilive -y 1001 @239.255.255.255:1025 239.255.255.255:1025
+
+
+Using smooThS
+=============
+
+SmooThS command line is close to multicat's:
+
+smooths -c /etc/smooths.conf @239.255.255.255:5004
+
+where smooths.conf contains a list of destinations such as:
+
+239.255.255.254:5004
=====================================
multilive.c
=====================================
@@ -40,7 +40,6 @@
#include "util.h"
-#define CLOCK_FREQ UINT64_C(27000000)
#define DEFAULT_PRIORITY 1
#define DEFAULT_PERIOD (CLOCK_FREQ / 5)
#define DEFAULT_DEAD 5
=====================================
smooths.c
=====================================
@@ -0,0 +1,532 @@
+/*****************************************************************************
+ * smooths.c: smooth a transport stream
+ *****************************************************************************
+ * Copyright (C) 2023 VideoLAN
+ *
+ * Authors: Christophe Massiot <cmassiot at upipe.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+/* POLLRDHUP */
+#define _GNU_SOURCE 1
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <inttypes.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <pthread.h>
+#include <poll.h>
+#include <sys/ioctl.h>
+#include <syslog.h>
+#include <sys/uio.h>
+#include <limits.h>
+#include <ctype.h>
+
+#ifndef POLLRDHUP
+# define POLLRDHUP 0
+#endif
+
+#include <bitstream/ietf/rtp.h>
+#include <bitstream/mpeg/ts.h>
+#include <bitstream/mpeg/pes.h>
+
+#include "util.h"
+#include "ulist.h"
+
+#define POLL_TIMEOUT 0 /* non-blocking mode */
+#define DEFAULT_LATENCY (CLOCK_FREQ / 5)
+#define WARN_JITTER (CLOCK_FREQ / 1000)
+#define PACKET_SIZE 1328
+
+/*****************************************************************************
+ * Local declarations
+ *****************************************************************************/
+struct packet {
+ struct uchain uchain;
+ uint8_t p_buffer[PACKET_SIZE];
+};
+
+UBASE_FROM_TO(packet, uchain, uchain, uchain)
+
+struct output {
+ struct uchain uchain;
+ int i_fd;
+ char *psz_uri;
+ bool b_raw_packets;
+ bool b_udp;
+ bool b_found;
+ struct udprawpkt pktheader;
+};
+
+UBASE_FROM_TO(output, uchain, uchain, uchain)
+
+static struct uchain output_list;
+static int i_input_fd;
+
+static volatile sig_atomic_t b_die = 0, b_error = 0, b_reload = 1;
+
+static void usage(void)
+{
+ msg_Raw( NULL, "Usage: smooths [-i <RT priority>] [-l <syslogtag>] [-L <latency>] -c <conf file> <input item>" );
+ msg_Raw( NULL, " item format: [<connect addr>[:<connect port>]][@[<bind addr][:<bind port>]]" );
+ msg_Raw( NULL, " latency in 27000000 MHz units" );
+ exit(EXIT_FAILURE);
+}
+
+/*****************************************************************************
+ * udp_*: UDP socket handlers
+ *****************************************************************************/
+static ssize_t udp_Read( void *p_buf, size_t i_len )
+{
+ ssize_t i_ret;
+
+ if ( (i_ret = recv( i_input_fd, p_buf, i_len, MSG_DONTWAIT )) < 0 &&
+ errno != EAGAIN && errno != EWOULDBLOCK )
+ {
+ msg_Err( NULL, "recv error (%s)", strerror(errno) );
+ b_die = b_error = 1;
+ return 0;
+ }
+
+ return i_ret > 0 ? i_ret : 0;
+}
+
+static void udp_ExitRead(void)
+{
+ close( i_input_fd );
+}
+
+static int udp_InitRead( const char *psz_arg )
+{
+ if ( (i_input_fd = OpenSocket( psz_arg, 0, DEFAULT_PORT, 0,
+ NULL, NULL, NULL )) < 0 )
+ return -1;
+
+ return 0;
+}
+
+static ssize_t raw_Write( struct output *p_output,
+ const void *p_buf, size_t i_len )
+{
+#ifndef __APPLE__
+ ssize_t i_ret;
+ struct iovec iov[2];
+
+ #if defined(__FreeBSD__)
+ p_output->pktheader.udph.uh_ulen
+ #else
+ p_output->pktheader.udph.len
+ #endif
+ = htons(sizeof(struct udphdr) + i_len);
+
+ #if defined(__FreeBSD__)
+ p_output->pktheader.iph.ip_len = htons(sizeof(struct udprawpkt) + i_len);
+ #endif
+
+ iov[0].iov_base = &p_output->pktheader;
+ iov[0].iov_len = sizeof(struct udprawpkt);
+
+ iov[1].iov_base = (void *) p_buf;
+ iov[1].iov_len = i_len;
+
+ if ( (i_ret = writev( p_output->i_fd, iov, 2 )) < 0 )
+ {
+ if ( errno == EBADF || errno == ECONNRESET || errno == EPIPE )
+ {
+ msg_Err( NULL, "write error (%s) on output %s", strerror(errno),
+ p_output->psz_uri );
+ b_die = b_error = 1;
+ }
+ /* otherwise do not set b_die because these errors can be transient */
+ return 0;
+ }
+
+ return i_ret;
+#else
+ return -1;
+#endif
+}
+
+static ssize_t udp_Write( struct output *p_output,
+ const void *p_buf, size_t i_len )
+{
+ if ( p_output->b_udp )
+ {
+ p_buf += RTP_HEADER_SIZE;
+ i_len -= RTP_HEADER_SIZE;
+ }
+
+ if ( p_output->b_raw_packets )
+ return raw_Write( p_output, p_buf, i_len );
+
+ ssize_t i_ret;
+ if ( (i_ret = send( p_output->i_fd, p_buf, i_len, 0 )) < 0 )
+ {
+ if ( errno == EBADF || errno == ECONNRESET || errno == EPIPE )
+ {
+ msg_Err( NULL, "write error (%s) on output %s", strerror(errno),
+ p_output->psz_uri );
+ b_die = b_error = 1;
+ }
+ /* otherwise do not set b_die because these errors can be transient */
+ return 0;
+ }
+
+ return i_ret;
+}
+
+static void udp_ExitWrite( struct output *p_output )
+{
+ msg_Info( NULL, "closing %s", p_output->psz_uri );
+ close( p_output->i_fd );
+ free( p_output->psz_uri );
+ free( p_output );
+}
+
+static struct output *udp_InitWrite( const char *psz_arg )
+{
+ struct output *p_output = malloc(sizeof(struct output));
+ struct opensocket_opt opt;
+
+ msg_Info( NULL, "opening %s", psz_arg );
+ memset(&opt, 0, sizeof(struct opensocket_opt));
+ opt.p_raw_pktheader = &p_output->pktheader;
+ opt.pb_raw_packets = &p_output->b_raw_packets;
+ opt.pb_udp = &p_output->b_udp;
+ if ( (p_output->i_fd = OpenSocket( psz_arg, 0, 0, DEFAULT_PORT,
+ NULL, NULL, &opt )) > 0 )
+ {
+ p_output->psz_uri = strdup(psz_arg);
+ return p_output;
+ }
+ free(p_output);
+ return NULL;
+}
+
+/*****************************************************************************
+ * config_*: configuration file related functions
+ *****************************************************************************/
+static void config_ReadFile( const char *psz_conf_file )
+{
+ FILE *p_file;
+ char psz_line[2048];
+
+ if ( (p_file = fopen( psz_conf_file, "r" )) == NULL )
+ {
+ msg_Err( NULL, "can't fopen config file %s", psz_conf_file );
+ return;
+ }
+
+ while ( fgets( psz_line, sizeof(psz_line), p_file ) != NULL )
+ {
+ struct uchain *p_uchain;
+ char *psz_parser;
+
+ psz_parser = strpbrk( psz_line, " #\n" );
+ if ( psz_parser != NULL )
+ *psz_parser-- = '\0';
+ while ( psz_parser >= psz_line && isblank( *psz_parser ) )
+ *psz_parser-- = '\0';
+ if ( psz_line[0] == '\0' )
+ continue;
+
+ /* Find out if we already have this output */
+ ulist_foreach (&output_list, p_uchain)
+ {
+ struct output *p_output = output_from_uchain(p_uchain);
+ if (!strcmp(p_output->psz_uri, psz_line))
+ {
+ p_output->b_found = true;
+ break;
+ }
+ }
+ if ( p_uchain != &output_list )
+ continue;
+
+ /* Not found, open it */
+ struct output *p_output = udp_InitWrite( psz_line );
+ if ( p_output == NULL )
+ {
+ msg_Warn( NULL, "couldn't parse %s", psz_line );
+ continue;
+ }
+ p_output->b_found = true;
+ ulist_add(&output_list, output_to_uchain(p_output));
+ }
+
+ fclose( p_file );
+
+ /* Now close outputs that were not in the file, and reset b_found flag */
+ struct uchain *p_uchain, *p_tmp;
+ ulist_delete_foreach (&output_list, p_uchain, p_tmp)
+ {
+ struct output *p_output = output_from_uchain(p_uchain);
+ if (!p_output->b_found)
+ {
+ ulist_delete(p_uchain);
+ udp_ExitWrite(p_output);
+ }
+ else
+ p_output->b_found = false;
+ }
+}
+
+static void config_Free(void)
+{
+ struct uchain *p_uchain, *p_tmp;
+ ulist_delete_foreach (&output_list, p_uchain, p_tmp)
+ {
+ udp_ExitWrite(output_from_uchain(p_uchain));
+ }
+}
+
+/*****************************************************************************
+ * CompareSequences: Compare the sequence numbers from 2 RTP packets
+ *****************************************************************************/
+static int CompareSequences( struct uchain *p_uchain1,
+ struct uchain *p_uchain2 )
+{
+ struct packet *p_packet1 = packet_from_uchain(p_uchain1);
+ struct packet *p_packet2 = packet_from_uchain(p_uchain2);
+ uint16_t i_seqnum1 = rtp_get_seqnum(p_packet1->p_buffer);
+ uint16_t i_seqnum2 = rtp_get_seqnum(p_packet2->p_buffer);
+
+ int i_diff = i_seqnum1 - i_seqnum2;
+ if (i_diff > 0)
+ return (i_diff < 0x8000) ? i_diff : -i_diff;
+ else if (i_diff < 0)
+ return (i_diff > -0x8000) ? i_diff : -i_diff;
+ else
+ return 0;
+}
+
+/*****************************************************************************
+ * Signal Handler
+ *****************************************************************************/
+static void SigHandler( int i_signal )
+{
+ if ( i_signal != SIGHUP )
+ b_die = b_error = 1;
+ else
+ b_reload = 1;
+}
+
+/*****************************************************************************
+ * Entry point
+ *****************************************************************************/
+int main( int i_argc, char **pp_argv )
+{
+ int i_priority = -1;
+ const char *psz_syslog_tag = NULL;
+ uint64_t i_latency = DEFAULT_LATENCY;
+ const char *psz_conf_file = NULL;
+ int c;
+ struct sigaction sa;
+ sigset_t set;
+ struct uchain packet_list;
+ struct packet *p_packet;
+ uint64_t i_next_stc = UINT64_MAX;
+
+ /* Parse options */
+ while ( (c = getopt( i_argc, pp_argv, "i:l:L:c:h" )) != -1 )
+ {
+ switch ( c )
+ {
+ case 'i':
+ i_priority = strtol( optarg, NULL, 0 );
+ break;
+
+ case 'l':
+ psz_syslog_tag = optarg;
+ break;
+
+ case 'L':
+ i_latency = strtol( optarg, NULL, 0 );
+ break;
+
+ case 'c':
+ psz_conf_file = optarg;
+ break;
+
+ case 'h':
+ default:
+ usage();
+ break;
+ }
+ }
+ if ( optind >= i_argc || psz_conf_file == NULL )
+ usage();
+
+ if ( psz_syslog_tag != NULL )
+ msg_Openlog( psz_syslog_tag, LOG_NDELAY, LOG_USER );
+
+ /* Open sockets */
+ if ( udp_InitRead( pp_argv[optind] ) < 0 )
+ {
+ msg_Err( NULL, "input not found, exiting" );
+ exit(EXIT_FAILURE);
+ }
+ optind++;
+
+ ulist_init(&output_list);
+
+ /* Real-time priority */
+ if ( i_priority > 0 )
+ {
+ struct sched_param param;
+ int i_error;
+
+ memset( ¶m, 0, sizeof(struct sched_param) );
+ param.sched_priority = i_priority;
+ if ( (i_error = pthread_setschedparam( pthread_self(), SCHED_FIFO,
+ ¶m )) )
+ {
+ msg_Warn( NULL, "couldn't set thread priority: %s",
+ strerror(i_error) );
+ }
+ }
+
+ /* Set signal handlers */
+ memset( &sa, 0, sizeof(struct sigaction) );
+ sa.sa_handler = SigHandler;
+ sigfillset( &set );
+
+ if ( sigaction( SIGTERM, &sa, NULL ) == -1 ||
+ sigaction( SIGHUP, &sa, NULL ) == -1 ||
+ sigaction( SIGINT, &sa, NULL ) == -1 ||
+ sigaction( SIGPIPE, &sa, NULL ) == -1 )
+ {
+ msg_Err( NULL, "couldn't set signal handler: %s", strerror(errno) );
+ exit(EXIT_FAILURE);
+ }
+
+ /* Main loop */
+ ulist_init(&packet_list);
+ p_packet = malloc( sizeof(struct packet) );
+ uchain_init(packet_to_uchain(p_packet));
+ while ( !b_die )
+ {
+ if ( b_reload )
+ {
+ config_ReadFile( psz_conf_file );
+ b_reload = 0;
+ }
+
+ uint64_t i_stc = wall_Date();
+
+ if ( i_next_stc <= i_stc )
+ {
+ /* Output packet */
+ struct uchain *p_uchain;
+ struct packet *p_current = packet_from_uchain(ulist_pop(&packet_list));
+ ulist_foreach (&output_list, p_uchain)
+ {
+ struct output *output = output_from_uchain(p_uchain);
+ udp_Write( output, p_current->p_buffer, PACKET_SIZE );
+ }
+
+ if ( i_next_stc <= i_stc - WARN_JITTER )
+ {
+ msg_Warn( NULL, "CR was missed by %"PRIu64" us",
+ (i_stc - i_next_stc) / 27 );
+ }
+
+ /* Now calculate the date of the next packet */
+ if ( ulist_empty(&packet_list) )
+ {
+ i_next_stc = UINT64_MAX;
+ }
+ else
+ {
+ uint32_t i_current_timestamp =
+ rtp_get_timestamp( p_current->p_buffer );
+ struct packet *p_next =
+ packet_from_uchain(ulist_peek(&packet_list));
+ uint32_t i_next_timestamp =
+ rtp_get_timestamp( p_next->p_buffer );
+ uint64_t i_diff =
+ ((UINT32_MAX + 1 + (uint64_t)i_next_timestamp -
+ i_current_timestamp) & UINT32_MAX) * 300;
+
+ if ( i_diff > i_latency )
+ {
+ i_next_stc += i_latency;
+ msg_Warn( NULL, "resetting CR due to too long delay (%"PRIu64" ms)",
+ i_diff * 1000 / CLOCK_FREQ );
+ }
+ else
+ {
+ i_next_stc += i_diff;
+ }
+
+ free(p_current);
+ }
+
+ /* Check if there is not another packet to send */
+ continue;
+ }
+
+ /* Read and queue */
+ ssize_t i_read_size = udp_Read( p_packet->p_buffer, PACKET_SIZE );
+ if ( i_read_size > 0 )
+ {
+ if ( !rtp_check_hdr( p_packet->p_buffer ) )
+ {
+ msg_Warn( NULL, "invalid RTP packet received" );
+ continue;
+ }
+
+ /* Reorder packet if needed */
+ ulist_bubble_reverse(&packet_list, packet_to_uchain(p_packet),
+ CompareSequences);
+
+ if ( i_next_stc == UINT64_MAX )
+ {
+ i_next_stc = i_stc + i_latency;
+ msg_Warn( NULL, "resetting CR due to empty buffer" );
+ }
+
+ p_packet = malloc( sizeof(struct packet) );
+ uchain_init(packet_to_uchain(p_packet));
+ }
+ }
+
+ free(p_packet);
+ struct uchain *p_uchain, *p_tmp;
+ ulist_delete_foreach (&packet_list, p_uchain, p_tmp)
+ {
+ free(packet_from_uchain(p_uchain));
+ }
+
+ udp_ExitRead();
+ config_Free();
+
+ if ( psz_syslog_tag != NULL )
+ msg_Closelog();
+
+ return b_error ? EXIT_FAILURE : EXIT_SUCCESS;
+}
=====================================
ulist.h
=====================================
@@ -0,0 +1,406 @@
+/* part of upipe/include/upipe/ubase.h */
+
+/*
+ * Copyright (C) 2012-2019 OpenHeadend S.A.R.L.
+ * Copyright (C) 2020 EasyTools
+ *
+ * Authors: Christophe Massiot
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject
+ * to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stddef.h>
+
+/** @This marks a function or variable as possibly unused (suppresses compiler
+ * warnings). */
+#define UBASE_UNUSED __attribute__ ((unused))
+
+#ifndef container_of
+/** @This is used to retrieve the private portion of a structure. */
+# define container_of(ptr, type, member) ({ \
+ const __typeof__( ((type *)0)->member ) *_mptr = (ptr); \
+ (type *)( (char *)_mptr - offsetof(type,member) );})
+#endif
+
+/** @This declares two functions dealing with substructures included into a
+ * larger structure.
+ *
+ * @param STRUCTURE name of the larger structure
+ * @param SUBSTRUCT name of the smaller substructure
+ * @param SUBNAME name to use for the functions
+ * (STRUCTURE##_{to,from}_##SUBNAME)
+ * @param SUB name of the @tt{struct SUBSTRUCT} field of @tt{struct STRUCTURE}
+ */
+#define UBASE_FROM_TO(STRUCTURE, SUBSTRUCT, SUBNAME, SUB) \
+/** @internal @This returns a pointer to SUBNAME. \
+ * \
+ * @param STRUCTURE pointer to struct STRUCTURE \
+ * @return pointer to struct SUBSTRUCT \
+ */ \
+static UBASE_UNUSED inline struct SUBSTRUCT * \
+ STRUCTURE##_to_##SUBNAME(struct STRUCTURE *s) \
+{ \
+ return &s->SUB; \
+} \
+/** @internal @This returns a pointer to SUBNAME. \
+ * \
+ * @param sub pointer to struct SUBSTRUCT \
+ * @return pointer to struct STRUCTURE \
+ */ \
+static UBASE_UNUSED inline struct STRUCTURE * \
+ STRUCTURE##_from_##SUBNAME(struct SUBSTRUCT *sub) \
+{ \
+ return container_of(sub, struct STRUCTURE, SUB); \
+}
+
+/** @This is designed to chain uref and ubuf in a list. */
+struct uchain {
+ /** pointer to next element */
+ struct uchain *next;
+ /** pointer to previous element */
+ struct uchain *prev;
+};
+
+/** @This initializes a uchain.
+ *
+ * @param uchain pointer to a uchain structure
+ */
+static inline void uchain_init(struct uchain *uchain)
+{
+ uchain->next = uchain->prev = NULL;
+}
+
+
+/* part of upipe/include/upipe/ulist.h */
+
+/*
+ * Copyright (C) 2012-2016 OpenHeadend S.A.R.L.
+ * Copyright (C) 2020 EasyTools
+ *
+ * Authors: Christophe Massiot
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject
+ * to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/** @file
+ * @short Upipe implementation of lists of structures (NOT thread-safe)
+ *
+ * Please note that ulists cannot be assigned, as in:
+ * struct uchain mylist = mystruct->mylist;
+ */
+
+#include <stdlib.h>
+#include <stdbool.h>
+
+/** @This initializes a ulist.
+ *
+ * @param uchain pointer to a ulist
+ */
+static inline void ulist_init(struct uchain *ulist)
+{
+ ulist->next = ulist->prev = ulist;
+}
+
+/** @This checks if the element is the first of the list.
+ *
+ * @param ulist pointer to a ulist
+ * @param element pointer to element
+ * @return true if the element is the first
+ */
+static inline bool ulist_is_first(struct uchain *ulist, struct uchain *element)
+{
+ return element->prev == ulist;
+}
+
+/** @This checks if the element is the last of the list.
+ *
+ * @param ulist pointer to a ulist
+ * @param element pointer to element
+ * @return true if the element is the last
+ */
+static inline bool ulist_is_last(struct uchain *ulist, struct uchain *element)
+{
+ return element->next == ulist;
+}
+
+/** @This checks if the element is in a list.
+ *
+ * @param element pointer to element
+ * @return true if the element is in the list
+ */
+static inline bool ulist_is_in(struct uchain *element)
+{
+ return !(element->next == NULL);
+}
+
+/** @This checks if the list is empty.
+ *
+ * @param ulist pointer to a ulist
+ * @return true if the list is empty
+ */
+static inline bool ulist_empty(struct uchain *ulist)
+{
+ return ulist_is_last(ulist, ulist);
+}
+
+/** @This calculates the depth of the list (suboptimal, only for debug).
+ *
+ * @param ulist pointer to a ulist
+ * @return the depth of the list
+ */
+static inline size_t ulist_depth(struct uchain *ulist)
+{
+ struct uchain *uchain = ulist->next;
+ size_t depth = 0;
+ while (uchain != ulist) {
+ depth++;
+ uchain = uchain->next;
+ }
+ return depth;
+}
+
+/** @This adds a new element to a ulist at the given position.
+ *
+ * @param element pointer to element to add
+ * @param prev pointer to previous element
+ * @param next pointer to next element
+ */
+static inline void ulist_insert(struct uchain *prev, struct uchain *next,
+ struct uchain *element)
+{
+ next->prev = element;
+ element->next = next;
+ element->prev = prev;
+ prev->next = element;
+}
+
+/** @This deletes an element from a ulist.
+ *
+ * @param element pointer to element to delete
+ */
+static inline void ulist_delete(struct uchain *element)
+{
+ element->prev->next = element->next;
+ element->next->prev = element->prev;
+ uchain_init(element);
+}
+
+/** @This adds a new element at the end.
+ *
+ * @param ulist pointer to a ulist structure
+ * @param element pointer to element to add
+ */
+static inline void ulist_add(struct uchain *ulist, struct uchain *element)
+{
+ ulist_insert(ulist->prev, ulist, element);
+}
+
+/** @This adds a new element at the beginning.
+ *
+ * @param ulist pointer to a ulist
+ * @param element pointer to the first element to add
+ */
+static inline void ulist_unshift(struct uchain *ulist, struct uchain *element)
+{
+ ulist_insert(ulist, ulist->next, element);
+}
+
+/** @This returns a pointer to the first element of the list (without
+ * removing it).
+ *
+ * @param ulist pointer to a ulist
+ * @return pointer to the first element
+ */
+static inline struct uchain *ulist_peek(struct uchain *ulist)
+{
+ if (ulist_empty(ulist))
+ return NULL;
+ return ulist->next;
+}
+
+/** @This returns a pointer to the last element of the list (without
+ * removing it).
+ *
+ * @param ulist pointer to a ulist
+ * @return pointer to the last element or NULL if the list is empty
+ */
+static inline struct uchain *ulist_peek_last(struct uchain *ulist)
+{
+ if (ulist_empty(ulist))
+ return NULL;
+ return ulist->prev;
+}
+
+/** @This returns a pointer to the first element of the list and removes
+ * it.
+ *
+ * @param ulist pointer to a ulist
+ * @return pointer to the first element
+ */
+static inline struct uchain *ulist_pop(struct uchain *ulist)
+{
+ if (ulist_empty(ulist))
+ return NULL;
+ struct uchain *element = ulist->next;
+ ulist->next = element->next;
+ ulist->next->prev = ulist;
+ uchain_init(element);
+ return element;
+}
+
+/** @This return a pointer to the element at the given index.
+ *
+ * @param ulist pointer to a ulist
+ * @param index the index in the list
+ * @return pointer to the element at index
+ */
+static inline struct uchain *ulist_at(struct uchain *ulist, unsigned index)
+{
+ struct uchain *uchain;
+ for (uchain = ulist->next; uchain != ulist; uchain = uchain->next)
+ if (!index--)
+ return uchain;
+ return NULL;
+}
+
+/** @This sorts through a list using a comparison function.
+ *
+ * @param ulist pointer to a ulist
+ * @param compar comparison function accepting two uchains as arguments
+ */
+static inline void ulist_sort(struct uchain *ulist,
+ int (*compar)(struct uchain **, struct uchain **))
+{
+ size_t depth = ulist_depth(ulist);
+ size_t i;
+ if (!depth)
+ return;
+
+ struct uchain *array[depth];
+ for (i = 0; i < depth; i++)
+ array[i] = ulist_pop(ulist);
+
+ qsort(array, depth, sizeof(struct uchain *),
+ (int (*)(const void *, const void *))compar);
+
+ for (i = 0; i < depth; i++)
+ ulist_add(ulist, array[i]);
+}
+
+/** @This walks through a ulist. Please note that the list may not be altered
+ * during the walk (see @ref #ulist_delete_foreach).
+ *
+ * @param ulist pointer to a ulist
+ * @param uchain iterator
+ */
+#define ulist_foreach(ulist, uchain) \
+ for ((uchain) = (ulist)->next; (uchain) != (ulist); \
+ (uchain) = (uchain)->next)
+
+/** @This walks through a ulist in reverse. Please note that the list may not be altered
+ * during the walk (see @ref #ulist_delete_foreach_reverse).
+ *
+ * @param ulist pointer to a ulist
+ * @param uchain iterator
+ */
+#define ulist_foreach_reverse(ulist, uchain) \
+ for ((uchain) = (ulist)->prev; (uchain) != (ulist); \
+ (uchain) = (uchain)->prev)
+
+/** @This walks through a ulist. This variant allows to remove the current
+ * element safely.
+ *
+ * @param ulist pointer to a ulist
+ * @param uchain iterator
+ * @param uchain_tmp uchain to use for temporary storage
+ */
+#define ulist_delete_foreach(ulist, uchain, uchain_tmp) \
+ for ((uchain) = (ulist)->next, (uchain_tmp) = (uchain)->next; \
+ (uchain) != (ulist); \
+ (uchain) = (uchain_tmp), (uchain_tmp) = (uchain)->next)
+
+/** @This walks through a ulist in reverse. This variant allows to remove the current
+ * element safely.
+ *
+ * @param ulist pointer to a ulist
+ * @param uchain iterator
+ * @param uchain_tmp uchain to use for temporary storage
+ */
+#define ulist_delete_foreach_reverse(ulist, uchain, uchain_tmp) \
+ for ((uchain) = (ulist)->prev, (uchain_tmp) = (uchain)->prev; \
+ (uchain) != (ulist); \
+ (uchain) = (uchain_tmp), (uchain_tmp) = (uchain)->prev)
+
+/** @This inserts a new element into a list using a comparison function.
+ *
+ * @param ulist pointer to a ulist
+ * @param element element to insert
+ * @param compar comparison function accepting two uchains as arguments
+ */
+static inline void ulist_bubble(struct uchain *ulist, struct uchain *element,
+ int (*compar)(struct uchain *, struct uchain *))
+{
+ struct uchain *uchain;
+ ulist_foreach (ulist, uchain) {
+ if (compar(element, uchain) < 0) {
+ ulist_insert(uchain->prev, uchain, element);
+ return;
+ }
+ }
+ ulist_insert(ulist->prev, ulist, element);
+}
+
+/** @This inserts a new element into a list using a comparison function, from
+ * the end.
+ *
+ * @param ulist pointer to a ulist
+ * @param element element to insert
+ * @param compar comparison function accepting two uchains as arguments
+ */
+static inline void ulist_bubble_reverse(struct uchain *ulist,
+ struct uchain *element,
+ int (*compar)(struct uchain *, struct uchain *))
+{
+ struct uchain *uchain;
+ ulist_foreach_reverse (ulist, uchain) {
+ if (compar(element, uchain) > 0) {
+ ulist_insert(uchain, uchain->next, element);
+ return;
+ }
+ }
+ ulist_insert(ulist, ulist->next, element);
+}
=====================================
util.c
=====================================
@@ -569,8 +569,15 @@ int OpenSocket( const char *_psz_arg, int i_ttl, uint16_t i_bind_port,
pb_tcp = &b_tcp;
*pb_tcp = false;
- if ( p_opt != NULL && p_opt->pb_multicast != NULL )
- *p_opt->pb_multicast = false;
+ if ( p_opt )
+ {
+ if ( p_opt->pb_multicast )
+ *p_opt->pb_multicast = false;
+ if ( p_opt->pb_raw_packets )
+ *p_opt->pb_raw_packets = false;
+ if ( p_opt->pb_udp )
+ *p_opt->pb_udp = false;
+ }
psz_token2 = strrchr( psz_arg, ',' );
if ( psz_token2 )
@@ -659,6 +666,11 @@ int OpenSocket( const char *_psz_arg, int i_ttl, uint16_t i_bind_port,
i_tos = strtol( ARG_OPTION("tos="), NULL, 0 );
else if ( IS_OPTION("tcp") )
*pb_tcp = true;
+ else if ( IS_OPTION("udp") )
+ {
+ if ( p_opt && p_opt->pb_udp )
+ *p_opt->pb_udp = true;
+ }
else if ( IS_OPTION("srcaddr=") )
{
char *option = config_stropt( ARG_OPTION("srcaddr=") );
@@ -712,12 +724,14 @@ int OpenSocket( const char *_psz_arg, int i_ttl, uint16_t i_bind_port,
/* Socket configuration */
if ( i_fd < 0 )
{
- if (b_raw_packets && b_host)
+ if (b_raw_packets && i_raw_srcaddr != INADDR_ANY && b_host)
{
RawFillHeaders(p_opt->p_raw_pktheader,
i_raw_srcaddr, connect_addr.sin.sin_addr.s_addr, i_raw_srcport,
ntohs(connect_addr.sin.sin_port), i_ttl, i_tos, 0);
i_fd = socket( AF_INET, SOCK_RAW, IPPROTO_RAW );
+ if ( p_opt->pb_raw_packets != NULL )
+ *p_opt->pb_raw_packets = true;
#ifdef __FreeBSD__
if ( setsockopt( i_fd, IPPROTO_IP, IP_HDRINCL, &hincl, sizeof(hincl)) == -1 )
{
=====================================
util.h
=====================================
@@ -40,6 +40,7 @@
#define DEFAULT_ROTATE_OFFSET UINT64_C(0)
#define TS_SIZE 188
#define RTP_HEADER_SIZE 12
+#define CLOCK_FREQ UINT64_C(27000000)
#define VERB_DBG 3
#define VERB_INFO 2
@@ -76,6 +77,8 @@ struct udprawpkt {
struct opensocket_opt {
struct udprawpkt *p_raw_pktheader;
bool *pb_multicast;
+ bool *pb_raw_packets;
+ bool *pb_udp;
};
View it on GitLab: https://code.videolan.org/videolan/multicat/-/commit/5e784d4b0436143ae63f1f2c38f30f027fa9a776
--
View it on GitLab: https://code.videolan.org/videolan/multicat/-/commit/5e784d4b0436143ae63f1f2c38f30f027fa9a776
You're receiving this email because of your account on code.videolan.org.
VideoLAN code repository instance
More information about the multicat-devel
mailing list