From gitlab at videolan.org Mon Sep 11 07:05:45 2023 From: gitlab at videolan.org (Christophe Massiot (@cmassiot)) Date: Mon, 11 Sep 2023 09:05:45 +0200 Subject: [multicat-devel] [Git][videolan/multicat][master] Add new smooThS program to minimize jitter. Message-ID: <64febc49833c5_32b75d4669dc785819846@gitlab.mail> 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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef POLLRDHUP +# define POLLRDHUP 0 +#endif + +#include +#include +#include + +#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 ] [-l ] [-L ] -c " ); + msg_Raw( NULL, " item format: [[:]][@[]]" ); + 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 + +/** @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 +#include + +/** @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