Janus Error: only sent -1 bytes?

Hi, I am running into this error when using a usb camera that already does h264 encoding, the error just repeats hundreds of times per second when I have more than 2 viewers watching the stream:

[ERR] [ice.c:janus_ice_outgoing_traffic_handle:4893] [8733826651045854] ... only sent -1 bytes? (was 1430)
[ERR] [ice.c:janus_ice_outgoing_traffic_handle:4893] [8733826651045854] ... only sent -1 bytes? (was 1430)
[ERR] [ice.c:janus_ice_outgoing_traffic_handle:4893] [8733826651045854] ... only sent -1 bytes? (was 1430)
[ERR] [ice.c:janus_ice_outgoing_traffic_handle:4893] [8733826651045854] ... only sent -1 bytes? (was 1430)
[ERR] [ice.c:janus_ice_outgoing_traffic_handle:4893] [8733826651045854] ... only sent -1 bytes? (was 1430)
[ERR] [ice.c:janus_ice_outgoing_traffic_handle:4893] [8733826651045854] ... only sent -1 bytes? (was 1430)
[ERR] [ice.c:janus_ice_outgoing_traffic_handle:4893] [8733826651045854] ... only sent -1 bytes? (was 1430)
[ERR] [ice.c:janus_ice_outgoing_traffic_handle:4893] [8733826651045854] ... only sent -1 bytes? (was 46)
[ERR] [ice.c:janus_ice_outgoing_traffic_handle:4893] [8733826651045854] ... only sent -1 bytes? (was 52)
[ERR] [ice.c:janus_ice_outgoing_traffic_handle:4893] [8733826651045854] ... only sent -1 bytes? (was 433)
[ERR] [ice.c:janus_ice_outgoing_traffic_handle:4893] [8733826651045854] ... only sent -1 bytes? (was 1430)
[ERR] [ice.c:janus_ice_outgoing_traffic_handle:4893] [8733826651045854] ... only sent -1 bytes? (was 1430)
[ERR] [ice.c:janus_ice_outgoing_traffic_handle:4893] [8733826651045854] ... only sent -1 bytes? (was 1430)

this is the gstreamer pipeline I am using, it is part of a script that sends the output of rtph264pay to a local nginx:

pipeline_str = f"v4l2src device={self.device} ! rtph264pay name=pay0"

for comparison, I also have another usb camera that doesnt do h264 encoding but as a result it has higher latency due to videoconvert BUT I dont get the error even when 10 viewers are active, here is the gstreamer pipeline:

pipeline_str = f"videotestsrc ! videoconvert ! openh264enc ! video/x-h264,profile=high,level=(string)4 ! rtph264pay name=pay0"

Both cameras are ELP usb cameras, the one that does the H264 encoding produces a better image so I’m guessing its a way higher bitrate and this could be the bottleneck.

Any help is appreciated.

Are you using ICE-TCP or TURN-TCP? -1 usually means a reliable connection that broke.
That said, 1430 is probably too big. Try telling GStreamer to packetize smaller RTP messages (~1200).

Hi Lorenzo, thanks for replying.

I believe I am using TURN-TCP. The client-side uses TURN and so does Janus, I have tried without having TURN enabled in Janus and I cant establish a connection to Janus from the client. I believe part of the problem is that my h264 camera is sending a bitrate thats too high for the network upload speed and it starts having issues when too many viewers are connected since each viewer is receiving the full bitrate. I want to try to preserve the bitrate so that the image quality is the same for every viewer and also I require the sub-second latency of WebRTC for all viewers, any thoughts on how to proceed? Also can you review my janus,jcfg I feel like I’m missing something that might help with the initial issue:

# General configuration: folders where the configuration and the plugins
# can be found, how output should be logged, whether Janus should run as
# a daemon or in foreground, default interface to use, debug/logging level
# and, if needed, shared apisecret and/or token authentication mechanism
# between application(s) and Janus.
general: {
	configs_folder = "/usr/local/etc/janus"			# Configuration files folder
	plugins_folder = "/usr/local/lib/janus/plugins"			# Plugins folder
	transports_folder = "/usr/local/lib/janus/transports"	# Transports folder
	events_folder = "/usr/local/lib/janus/events"			# Event handlers folder
	loggers_folder = "/usr/local/lib/janus/loggers"			# External loggers folder

		# The next settings configure logging
	#log_to_stdout = false					# Whether the Janus output should be written
											# to stdout or not (default=true)
	#log_to_file = "/path/to/janus.log"		# Whether to use a log file or not
	debug_level = 4							# Debug/logging level, valid values are 0-7
	#debug_timestamps = true				# Whether to show a timestamp for each log line
	#debug_colors = false					# Whether colors should be disabled in the log
	#debug_locks = true						# Whether to enable debugging of locks (very verbose!)
	#log_prefix = "[janus] "				# In case you want log lines to be prefixed by some
											# custom text, you can use the 'log_prefix' property.
											# It supports terminal colors, meaning something like
											# "[\x1b[32mjanus\x1b[0m] " would show a green "janus"
											# string in square brackets (assuming debug_colors=true).

		# This is what you configure if you want to launch Janus as a daemon
	#daemonize = true						# Whether Janus should run as a daemon
											# or not (default=run in foreground)
	#pid_file = "/path/to/janus.pid"		# PID file to create when Janus has been
											# started, and to destroy at shutdown

		# There are different ways you can authenticate the Janus and Admin APIs
	#api_secret = "janusrocks"		# String that all Janus requests must contain
									# to be accepted/authorized by the Janus core.
									# Useful if you're wrapping all Janus API requests
									# in your servers (that is, not in the browser,
									# where you do the things your way) and you
									# don't want other application to mess with
									# this Janus instance.
	#token_auth = true				# Enable a token based authentication
									# mechanism to force users to always provide
									# a valid token in all requests. Useful if
									# you want to authenticate requests from web
									# users.
	#token_auth_secret = "janus"	# Use HMAC-SHA1 signed tokens (with token_auth). Note that
									# without this, the Admin API MUST
									# be enabled, as tokens are added and removed
									# through messages sent there.
	admin_secret = "janusoverlord"	# String that all Janus requests must contain
									# to be accepted/authorized by the admin/monitor.
									# only needed if you enabled the admin API
									# in any of the available transports.

		# Generic settings
	#interface = "1.2.3.4"			# Interface to use (will be used in SDP)
	#server_name = "MyJanusInstance"# Public name of this Janus instance
									# as it will appear in an info request
	#session_timeout = 60			# How long (in seconds) we should wait before
									# deciding a Janus session has timed out. A
									# session times out when no request is received
									# for session_timeout seconds (default=60s).
									# Setting this to 0 will disable the timeout
									# mechanism, which is NOT suggested as it may
									# risk having orphaned sessions (sessions not
									# controlled by any transport and never freed).
									# To avoid timeouts, keep-alives can be used.
	#candidates_timeout = 45		# How long (in seconds) we should keep hold of
									# pending (trickle) candidates before discarding
									# them (default=45s). Notice that setting this
									# to 0 will NOT disable the timeout, but will
									# be considered an invalid value and ignored.
	#reclaim_session_timeout = 0	# How long (in seconds) we should wait for a
									# janus session to be reclaimed after the transport
									# is gone. After the transport is gone, a session
									# times out when no request is received for
									# reclaim_session_timeout seconds (default=0s).
									# Setting this to 0 will disable the timeout
									# mechanism, and sessions will be destroyed immediately
									# if the transport is gone.
	#recordings_tmp_ext = "tmp"		# The extension for recordings, in Janus, is
									# .mjr, a custom format we devised ourselves.
									# By default, we save to .mjr directly. If you'd
									# rather the recording filename have a temporary
									# extension while it's being saved, and only
									# have the .mjr extension when the recording
									# is over (e.g., to automatically trigger some
									# external scripts), then uncomment and set the
									# recordings_tmp_ext property to the extension
									# to add to the base (e.g., tmp --> .mjr.tmp).
	#event_loops = 8				# By default, Janus handles each have their own
									# event loop and related thread for all the media
									# routing and management. If for some reason you'd
									# rather limit the number of loop/threads, and
									# you want handles to share those, you can do that
									# configuring the event_loops property: this will
									# spawn the specified amount of threads at startup,
									# run a separate event loop on each of them, and
									# add new handles to one of them when attaching.
									# Notice that, while cutting the number of threads
									# and possibly reducing context switching, this
									# might have an impact on the media delivery,
									# especially if the available loops can't take
									# care of all the handles and their media in time.
									# As such, if you want to use this you should
									# provision the correct value according to the
									# available resources (e.g., CPUs available).
	#allow_loop_indication = true	# In case a static number of event loops is
									# configured as explained above, by default
									# new handles will be allocated on one loop or
									# another by the Janus core itself. In some cases
									# it may be helpful to manually tell the Janus
									# core which loop a handle should be added to,
									# e.g., to group viewers of the same stream on
									# the same loop. This is possible via the Janus
									# API when performing the 'attach' request, but
									# only if allow_loop_indication is set to true;
									# it's set to false by default to avoid abuses.
									# Don't change if you don't know what you're doing!
	#task_pool_size = 100			# By default, while the Janus core is single thread
									# when it comes to processing incoming messages, it
									# also uses a task pool with an indefinite amount
									# of helper threads spawned on demand to handle
									# messages addressed to plugins. If you want to
									# limit this task pool size with a maximum number
									# of concurrent threads, set the 'task_pool_size'
									# property accordingly: a value of '0' means
									# 'indefinite' and is the default. Notice that
									# threads are automatically destroyed when unused
									# for a while, so whatever value you choose simply
									# puts a cap on the maximum concurrency.
									# Don't change if you don't know what you're doing!
	#opaqueid_in_api = true			# Opaque IDs set by applications are typically
									# only passed to event handlers for correlation
									# purposes, but not sent back to the user or
									# application in the related Janus API responses
									# or events; in case you need them to be in the
									# Janus API too, set this property to 'true'.
	#hide_dependencies = true		# By default, a call to the "info" endpoint of
									# either the Janus or Admin API now also returns
									# the versions of the main dependencies (e.g.,
									# libnice, libsrtp, which crypto library is in
									# use and so on). Should you want that info not
									# to be disclose, set 'hide_dependencies' to true.
	#exit_on_dl_error = false		# If a Janus shared libary cannot be loaded or an expected
									# symbol is not found, exit immediately.

		# The following is ONLY useful when debugging RTP/RTCP packets,
		# e.g., to look at unencrypted live traffic with a browser. By
		# default it is obviously disabled, as WebRTC mandates encryption.
	#no_webrtc_encryption = true

		# Janus provides ways via its API to specify custom paths to save
		# files to (e.g., recordings, pcap captures and the like). In order
		# to avoid people can mess with folders they're not supposed to,
		# you can configure an array of folders that Janus should prevent
		# creating files in. If the 'protected_folder' property below is
		# commented, no folder is protected.
		# Notice that at the moment this only covers attempts to start
		# an .mjr recording and pcap/text2pcap packet captures.
	protected_folders = [
		"/bin",
		"/boot",
		"/dev",
		"/etc",
		"/initrd",
		"/lib",
		"/lib32",
		"/lib64",
		"/proc",
		"/sbin",
		"/sys",
		"/usr",
		"/var",
			# We add what are usually the folders Janus is installed to
			# as well: we don't just put "/opt/janus" because that would
			# include folders like "/opt/janus/share" that is where
			# recordings might be saved to by some plugins
		"/opt/janus/bin",
		"/opt/janus/etc",
		"/opt/janus/include",
		"/opt/janus/lib",
		"/opt/janus/lib32",
		"/opt/janus/lib64",
		"/opt/janus/sbin"
	]
}

# Certificate and key to use for DTLS (and passphrase if needed). If missing,
# Janus will autogenerate a self-signed certificate to use. Notice that
# self-signed certificates are fine for the purpose of WebRTC DTLS
# connectivity, for the time being, at least until Identity Providers
# are standardized and implemented in browsers. If for some reason you
# want to enforce the DTLS stack in Janus to enforce valid certificates
# from peers, though, you can do that setting 'dtls_accept_selfsigned' to
# 'false' below: DO NOT TOUCH THAT IF YOU DO NOT KNOW WHAT YOU'RE DOING!
# You can also configure the DTLS ciphers to offer: the default if not
# set is "DEFAULT:!NULL:!aNULL:!SHA256:!SHA384:!aECDH:!AESGCM+AES256:!aPSK"
# Finally, by default NIST P-256 certificates are generated (see #1997),
# but RSA generation is still supported if you set 'rsa_private_key' to 'true'.
certificates: {
	#cert_pem = "/path/to/certificate.pem"
	#cert_key = "/path/to/key.pem"
	#cert_pwd = "secretpassphrase"
	#dtls_accept_selfsigned = false
	#dtls_ciphers = "your-desired-openssl-ciphers"
	#rsa_private_key = false
}

# Media-related stuff: you can configure whether if you want to enable IPv6
# support (and link-local IPs), the minimum size of the NACK queue (in ms,
# defaults to 200ms) for retransmissions no matter the RTT, the range of
# ports to use for RTP and RTCP (by default, no range is envisaged), the
# starting MTU for DTLS (1200 by default, it adapts automatically),
# how much time, in seconds, should pass with no media (audio or
# video) being received before Janus notifies you about this (default=1s,
# 0 disables these events entirely), how many lost packets should trigger a
# 'slowlink' event to users (default=0, disabled), and how often, in milliseconds,
# to send the Transport Wide Congestion Control feedback information back
# to senders, if negotiated (default=200ms). Finally, if you're using BoringSSL
# you can customize the frequency of retransmissions: OpenSSL has a fixed
# value of 1 second (the default), while BoringSSL can override that. Notice
# that lower values (e.g., 100ms) will typically get you faster connection
# times, but may not work in case the RTT of the user is high: as such,
# you should pick a reasonable trade-off (usually 2*max expected RTT).
media: {
	#ipv6 = true
	#ipv6_linklocal = true
	#min_nack_queue = 500
	#rtp_port_range = "20000-40000"
	#dtls_mtu = 1200
	#no_media_timer = 1
	#slowlink_threshold = 4
	#twcc_period = 100
	#dtls_timeout = 500

	# Janus can do some optimizations on the NACK queue, specifically when
	# keyframes are involved. Namely, you can configure Janus so that any
	# time a keyframe is sent to a user, the NACK buffer for that connection
	# is emptied. This allows Janus to ignore NACK requests for packets
	# sent shortly before the keyframe was sent, since it can be assumed
	# that the keyframe will restore a complete working image for the user
	# anyway (which is the main reason why video retransmissions are typically
	# required). While this optimization is known to work fine in most cases,
	# it can backfire in some edge cases, and so is disabled by default.
	#nack_optimizations = true

	# If you need DSCP packet marking and prioritization, you can configure
	# the 'dscp' property to a specific values, and Janus will try to
	# set it on all outgoing packets using libnice. Normally, the specs
	# suggest to use different values depending on whether audio, video
	# or data are used, but since all PeerConnections in Janus are bundled,
	# we can only use one. You can refer to this document for more info:
	# https://tools.ietf.org/html/draft-ietf-tsvwg-rtcweb-qos-18#page-6
	# That said, DON'T TOUCH THIS IF YOU DON'T KNOW WHAT IT MEANS!
	#dscp = 46
}

# NAT-related stuff: specifically, you can configure the STUN/TURN
# servers to use to gather candidates if the gateway is behind a NAT,
# and srflx/relay candidates are needed. In case STUN is not enough and
# this is needed (it shouldn't), you can also configure Janus to use a
# TURN server# please notice that this does NOT refer to TURN usage in
# browsers, but in the gathering of relay candidates by Janus itself,
# e.g., if you want to limit the ports used by a Janus instance on a
# private machine. Furthermore, you can choose whether Janus should be
# configured to do full-trickle (Janus also trickles its candidates to
# users) rather than the default half-trickle (Janus supports trickle
# candidates from users, but sends its own within the SDP), and whether
# it should work in ICE-Lite mode (by default it doesn't). If libnice is
# at least 0.1.15, you can choose which ICE nomination mode to use: valid
# values are "regular" and "aggressive" (the default depends on the libnice
# version itself; if we can set it, we set aggressive nomination). If
# libnice is at least 0.1.19, you can enable consent freshness checks for
# PeerConnections as well: this will issue regular checks to check whether
# or not the WebRTC peer isn't available anymore. Enabling consent freshness
# will automatically also enable using connectivity checks as keep-alives, which
# might help detecting when a peer is no longer available (notice that
# current libnice master is breaking connections after 50 seconds when
# keepalive-conncheck is being used, so if you want to use it, better
# sticking to 0.1.18 until the issue is addressed upstream). Finally,
# you can also enable ICE-TCP support (beware that this may lead to problems
# if you do not enable ICE Lite as well), choose which interfaces should
# be used for gathering candidates, and enable or disable the
# internal libnice debugging, if needed.
nat: {
	stun_server = "stun.l.google.com"
	stun_port = 19302
	#nice_debug = true
	#full_trickle = true
	#ice_nomination = "regular"
	#ice_consent_freshness = true
	#ice_keepalive_conncheck = true
	#ice_lite = true
        #ice_tcp = true

	# By default Janus tries to resolve mDNS (.local) candidates: even
	# though this is now done asynchronously and shouldn't keep the API
	# busy, even in case mDNS resolution takes a long time to timeout,
	# you can choose to drop all .local candidates instead, which is
	# helpful in case you know clients will never be in the same private
	# network as the one the Janus instance is running from. Notice that
	# this will cause ICE to fail if mDNS is the only way to connect!
	ignore_mdns = true

	# In case you're deploying Janus on a server which is configured with
	# a 1:1 NAT (e.g., Amazon EC2), you might want to also specify the public
	# address of the machine using the setting below. This will result in
	# all host candidates (which normally have a private IP address) to
	# be rewritten with the public address provided in the settings. As
	# such, use the option with caution and only if you know what you're doing.
	# Make sure you keep ICE Lite disabled, though, as it's not strictly
	# speaking a publicly reachable server, and a NAT is still involved.
	# If you'd rather keep the private IP address in place, rather than
	# replacing it (and so have both of them as advertised candidates),
	# then set the 'keep_private_host' property to true.
	# Multiple public IP addresses can be specified as a comma separated list
	# if the Janus is deployed in a DMZ between two 1-1 NAT for internal and
	# external users.
	#nat_1_1_mapping = "100.92.153.143"
	#keep_private_host = true

	# You can configure a TURN server in two different ways: specifying a
	# statically configured TURN server, and thus provide the address of the
	# TURN server, the transport (udp/tcp/tls) to use, and a set of valid
	# credentials to authenticate. Notice that you should NEVER configure
	# a TURN server for Janus unless it's really what you want! If you want
	# *users* to use TURN, then you need to configure that on the client
	# side, and NOT in Janus. The following TURN configuration should ONLY
	# be enabled when Janus itself is sitting behind a restrictive firewall
	# (e.g., it's part of a service installed on a box in a private home).
	turn_server = "myturnserveraddress"
	turn_port = 80
	#turn_type = "udp"
	turn_user = "private"
	turn_pwd = "private"

	# You can also make use of the TURN REST API to get info on one or more
	# TURN services dynamically. This makes use of the proposed standard of
	# such an API (https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00)
	# which is currently available in both rfc5766-turn-server and coturn.
	# You enable this by specifying the address of your TURN REST API backend,
	# the HTTP method to use (GET or POST) and, if required, the API key Janus
	# must provide. The timeout can be configured in seconds, with a default of
	# 10 seconds and a minimum of 1 second. Notice that the 'opaque_id' provided
	# via Janus API will be used as the username for a specific PeerConnection
	# by default; if that one is missing, the 'session_id' will be used as the
	# username instead.
	#turn_rest_api = "http://yourbackend.com/path/to/api"
	#turn_rest_api_key = "anyapikeyyoumayhaveset"
	#turn_rest_api_method = "GET"
	#turn_rest_api_timeout = 10

	# In case a TURN server is provided, you can allow applications to force
	# Janus to use TURN (https://github.com/meetecho/janus-gateway/pull/2774).
	# This is NOT allowed by default: only enable it if you know what you're doing.
	#allow_force_relay = true

	# You can also choose which interfaces should be explicitly used by the
	# gateway for the purpose of ICE candidates gathering, thus excluding
	# others that may be available. To do so, use the 'ice_enforce_list'
	# setting and pass it a comma-separated list of interfaces or IP addresses
	# to enforce. This is especially useful if the server hosting the gateway
	# has several interfaces, and you only want a subset to be used. Any of
	# the following examples are valid:
	#     ice_enforce_list = "eth0"
	#     ice_enforce_list = "eth0,eth1"
	#     ice_enforce_list = "eth0,192.168."
	#     ice_enforce_list = "eth0,192.168.0.1"
	# By default, no interface is enforced, meaning Janus will try to use them all.
	#ice_enforce_list = "eth0"

	# In case you don't want to specify specific interfaces to use, but would
	# rather tell Janus to use all the available interfaces except some that
	# you don't want to involve, you can also choose which interfaces or IP
	# addresses should be excluded and ignored by the gateway for the purpose
	# of ICE candidates gathering. To do so, use the 'ice_ignore_list' setting
	# and pass it a comma-separated list of interfaces or IP addresses to
	# ignore. This is especially useful if the server hosting the gateway
	# has several interfaces you already know will not be used or will simply
	# always slow down ICE (e.g., virtual interfaces created by VMware).
	# Partial strings are supported, which means that any of the following
	# examples are valid:
	#     ice_ignore_list = "vmnet8,192.168.0.1,10.0.0.1"
	#     ice_ignore_list = "vmnet,192.168."
	# Just beware that the ICE ignore list is not used if an enforce list
	# has been configured. By default, Janus ignores all interfaces whose
	# name starts with 'vmnet', to skip VMware interfaces:
	ice_ignore_list = "vmnet"

	# In case you want to allow Janus to start even if the configured STUN or TURN
	# server is unreachable, you can set 'ignore_unreachable_ice_server' to true.
	# WARNING: We do not recommend to ignore reachability problems, particularly
	# if you run Janus in the cloud. Before enabling this flag, make sure your
	# system is correctly configured and Janus starts after the network layer of
	# your machine is ready. Note that Linux distributions offer such directives.
	# You could use the following directive in systemd: 'After=network-online.target'
	# https://www.freedesktop.org/software/systemd/man/systemd.unit.html#Before=
	#ignore_unreachable_ice_server = true
}

# You can choose which of the available plugins should be
# enabled or not. Use the 'disable' directive to prevent Janus from
# loading one or more plugins: use a comma separated list of plugin file
# names to identify the plugins to disable. By default all available
# plugins are enabled and loaded at startup.
plugins: {
	#disable = "libjanus_echotest.so,libjanus_recordplay.so"
}

# You can choose which of the available transports should be enabled or
# not. Use the 'disable' directive to prevent Janus from loading one
# or more transport: use a comma separated list of transport file names
# to identify the transports to disable. By default all available
# transports are enabled and loaded at startup.
transports: {
	#disable = "libjanus_rabbitmq.so"
}

# As a core feature, Janus can log either on the standard output, or to
# a local file. Should you need more advanced logging functionality, you
# can make use of one of the custom loggers, or write one yourself. Use the
# 'disable' directive to prevent Janus from loading one or more loggers:
# use a comma separated list of logger file names to identify the loggers
# to disable. By default all available loggers are enabled and loaded at startup.
loggers: {
	#disable = "libjanus_jsonlog.so"
}

# Event handlers allow you to receive live events from Janus happening
# in core and/or plugins. Since this can require some more resources,
# the feature is disabled by default. Setting broadcast to yes will
# enable them. You can then choose which of the available event handlers
# should be loaded or not. Use the 'disable' directive to prevent Janus
# from loading one or more event handlers: use a comma separated list of
# file names to identify the event handlers to disable. By default, if
# broadcast is set to yes all available event handlers are enabled and
# loaded at startup. Finally, you can choose how often media statistics
# (packets sent/received, losses, etc.) should be sent: by default it's
# once per second (audio and video statistics sent separately), but may
# considered too verbose, or you may want to limit the number of events,
# especially if you have many PeerConnections active. To change this,
# just set 'stats_period' to the number of seconds that should pass in
# between statistics for each handle. Setting it to 0 disables them (but
# not other media-related events). By default Janus sends single media
# statistic events per media (audio, video and simulcast layers as separate
# events): if you'd rather receive a single containing all media stats in a
# single array, set 'combine_media_stats' to true.
events: {
	#broadcast = true
	#combine_media_stats = true
	#disable = "libjanus_sampleevh.so"
	#stats_period = 5
}

Thank you again for your help.