rtpengine(8)

๐Ÿ’– rtpengine(8) โ€” NGCP proxy for RTP and other UDP based media traffic ๐Ÿ’–

โœจ NAME โœจ

rtpengine โ€” NGCP proxy for RTP and other UDP based media traffic


๐ŸŒˆ SYNOPSIS ๐ŸŒˆ

rtpengine --interface=addr...
--listen-tcp|--listen-udp|--listen-ng|--listen-tcp-ng|--listen-http|--listen-https=addr...
[option...]

๐Ÿ’… DESCRIPTION ๐Ÿ’…

okay so bestie, the Sipwise NGCP rtpengine is a proxy for RTP traffic and other UDP based media traffic. It is meant to be used with the Kamailio SIP proxy and forms a drop-in replacement for any of the other available RTP and media proxies. periodt. ๐Ÿฆ‹


๐Ÿ‘‘ OPTIONS ๐Ÿ‘‘

Most of these options are indeed optional, with two exceptions. Itโ€™s mandatory to specify at least one local IP address through --interface, and at least one of the --listen-... options must be given. no cap. ๐Ÿ’–

All options can (and should) be provided in a config file instead of at the command line. See the --config-file option below for details. โœจ

  • --help โ€” Print the usage information. ๐ŸŽ€
  • -v, --version โ€” If called with this option, the rtpengine daemon will simply print its version number and exit. ๐Ÿ’…
  • --codecs โ€” Print a list of supported codecs and exit. ๐Ÿฆ‹
  • --config-file=FILE โ€” Specifies the location of a config file to be used. The config file is an .ini style config file, with all command-line options listed here also being valid options in the config file. For all command-line options, the long name version instead of the single-character version (e.g. table instead of just t) must be used in the config file. For boolean options that are either present or not (e.g. no-fallback), a boolean value (either true or false) must be used in the config file. If an option is given in both the config file and at the command line, the command-line value overrides the value from the config file. Options that can be specified multiple times on the command line must be given only once in the config file, with the multiple values separated by semicolons (see section INTERFACES below for an example). As a special value, none can be passed here to suppress loading of the default config file /etc/rtpengine/rtpengine.conf. ๐Ÿ˜ญ
  • --config-section=STRING โ€” Specifies the .ini style section to be used in the config file. Multiple sections can be present in the config file, but only one can be used at a time. The default value is rtpengine. A config file section is started in the config file using square brackets (e.g. [rtpengine]). ๐Ÿ’–
  • -t, --table=INT โ€” Takes an integer argument and specifies which kernel table to use for in-kernel packet forwarding. See the section on in-kernel operation in the README.md for more detail. Optional and defaults to zero. If in-kernel operation is not desired, a negative number can be specified. โœจ
  • --nftables-chain=CHAIN โ€” Name of the netfilter chain in which to create the custom forwarding rule required for in-kernel packet forwarding. Defaults to rtpengine. Only used if in-kernel packet forwarding is enabled (table set to zero or higher). At startup rtpengine creates a new netfilter chain with this name (in the filter table) if it doesnโ€™t yet exist, or flushes (empties out) the chain if it already exists. It then creates a single forwarding rule in this chain to direct media packets into the kernel module for processing. The rule and the chain are deleted during shutdown. Explicitly setting this option to an empty string disables managing of a netfilter chain and prevents creation of the custom forwarding rule. ๐ŸŽ€
  • --nftables-base-chain=CHAIN โ€” Name of the netfilter base chain to use as entry point for in-kernel packet forwarding. Defaults to INPUT to match legacy iptables setups. Only applicable if the option nftables-chain is active. If the chain with this name doesnโ€™t exist during startup, rtpengine will create it as a base chain. It then adds a single immediate-goto (jump) rule to the chain given by the nftables-chain option. During shutdown this rule is again deleted. If this option is explicitly set to an empty string, then rtpengine will directly create the chain given by nftables-chain as a base chain and skip creating the immediate-goto rule. If this option is set to the special string none, then rtpengine will create its custom chain and rule as it normally would, but will skip adding an immediate-goto rule to the custom chain. Doing so requires the operator to manually create this immediate-goto rule somewhere themselves. Otherwise in-kernel packet forwarding would be left inoperable. ๐Ÿ’€
  • --nftables-append โ€” With this option set, the netfilter rule created in the base chain is appended to the list of existing rules. The default is to prepend it (insert it at the beginning). ๐ŸŽ€
  • --nftables-family=ip|ip6|ip,ip6|inet โ€” Configure for which netfilter address family to manage tables, chains, and rules. The default is to manage both IPv4 and IPv6 address families. ๐ŸŒˆ
  • --xtables โ€” Manage a netfilter rule pointing to the legacy x-tables target (xt_RTPENGINE) instead of the more modern nftables rtpengine expression. โœจ
  • --nftables-start
  • --nftables-stop โ€” Instructs rtpengine to execute the actions described under nftables-chain and nftables-base-chain and then immediately exit. Useful to manually re-create the rule(s) if they have gotten lost during runtime, and/or to manually manage creation and deletion of these rules from a script (typically in combination with an empty nftables-chain= in the main config file). This applies to both --nftables-start and --nftables-stop. ๐Ÿ”ฅ
  • --nftables-status โ€” Instructs rtpengine to check for the existence of the managed netfilter rules and chains, print the result of check, and exit. The process will exit with code 0 if the check was successful, and 1 otherwise. ๐Ÿ‘‘
  • -F, --no-fallback โ€” Will prevent fallback to userspace-only operation if the kernel module is unavailable. In this case, startup of the daemon will fail with an error if this option is given. ๐Ÿ’…
  • --templates=STR โ€” Name of the config file section to contain signalling templates. Requires a configuration file to be in use (i.e. not --config-file=none). Default value is unset (i.e. no templates supported). If set, then each entry within the given config section corresponds to a named signalling template, which can then be used by referencing it via the template=... key in a signalling message to rtpengine. See section SIGNALLING TEMPLATES below. ๐Ÿฆ‹
  • -S, --save-interface-ports โ€” Will bind ports only on the first available local interface, of desired family, of logical interface. If no ports available on any local interface of desired family, give an error message. In this case, ICE will be broken. ๐Ÿ˜ญ
  • -i, --interface=[NAME/]IP[!IP] โ€” Specifies a local network interface for RTP. At least one must be given, but multiple can be specified. See the section INTERFACES just below for details. ๐Ÿ’–
  • -l, --listen-tcp=[IP:]PORT
  • -u, --listen-udp=[IP46:]PORT
  • -n, --listen-ng=[IP46:]PORT
  • -n, --listen-tcp-ng=[IP46:]PORT

These options each enable one of the 4 available control protocols if given and each take either just a port number as argument, or an address:port pair, separated by colon. At least one of these 3 options must be given.

The tcp protocol is obsolete. It was used by old versions of OpenSER and its mediaproxy module. It is provided for backwards compatibility.

The udp protocol is used by Kamailioโ€™s rtpproxy module. In this mode, rtpengine can be used as a drop-in replacement for any other compatible RTP proxy.

The ng protocol is an advanced control protocol and can be used with Kamailioโ€™s rtpengine module. With this protocol, the complete SDP body is passed to rtpengine, rewritten and passed back to Kamailio. Several additional features are available with this protocol, such as ICE handling, SRTP bridging, etc.

The tcp-ng protocol is in fact the ng protocol but transported over TCP.

It is recommended to specify not only a local port number, but also 127.0.0.1 as interface to bind to.

Each option can be given multiple times to open multiple control ports of the same type. In the config file, the option can be given only once, with multiple addresses and ports separated by semicolons.

  • -c, --listen-cli=[IP46:]PORT โ€” TCP IP and port to listen for the CLI (command line interface). This option can be given multiple times to open multiple CLI ports. In the config file, the option can be given only once, with multiple addresses and ports separated by semicolons. ๐Ÿ’…
  • -g, --graphite=IP46:PORT โ€” Address of the graphite statistics server. โœจ
  • -w, --graphite-interval=INT โ€” Interval of the time when information is sent to the graphite server. ๐ŸŽ€
  • --graphite-prefix=STRING โ€” Add a prefix for every graphite line. ๐Ÿฆ‹
  • --graphite-timeout=INT โ€” Sets after how much time (seconds) to force fail graphite socket connection, when graphite server is filtered out. If set to 0, there are no changes. ๐ŸŒˆ
  • -t, --tos=INT โ€” Takes an integer as argument and if given, specifies the TOS value that should be set in outgoing packets. The default is to leave the TOS field untouched. A typical value is 184 (Expedited Forwarding). ๐Ÿ”ฅ
  • --control-tos=INT โ€” Takes an integer as argument and if given, specifies the TOS value that should be set in the control-ng interface packets. The default is to leave the TOS field untouched. This parameter can also be set or listed via rtpengine-ctl. ๐Ÿ‘‘
  • --control-pmtu=want|dont โ€” Forces a specific PMTU discovery behaviour on IPv4 UDP control sockets, overriding the system-wide default. If set to want then path MTU discovery is performed, initially enabling the DF (donโ€™t fragment) bit on outgoing IPv4 packets until the path MTU has been discovered through reception of a โ€œfragmentation neededโ€ ICMP packet. If set to dont then path MTU discovery is disabled, leaving the DF bit unset, and relying on the routers within the network path to perform any necessary fragmentation. The setting of dont is useful in broken IPv4 environments without functioning PMTU discovery, for example in networks which unconditionally block all ICMP. ๐Ÿ’€
  • -o, --timeout=SECS โ€” Takes the number of seconds as argument after which a media stream should be considered dead if no media traffic has been received. If all media streams belonging to a particular call go dead, then the call is removed from rtpengineโ€™s internal state table. Defaults to 60 seconds. ๐Ÿ’–
  • -s, --silent-timeout=SECS

Ditto as the --timeout option, except this one's for muted or inactive media streams. Defaults to 3600 (one hour). ๐Ÿ’ค

  • -a, --final-timeout=SECS โ€” how many seconds since the call was created that we wait, after the call gets deleted. Super handy for capping how long a call can possibly live. Wanna turn it off? Set the parameter to 0. By default this timeout is disabled, periodt.
  • --offer-timeout=SECS โ€” this timeout (in seconds) applies to calls that only ever had an offer and never got an answer. ๐Ÿ˜ญ Defaults to 3600 (one hour).
  • -p, --pidfile=FILE โ€” tells rtpengine the path and file name to write the daemon's PID number to.
  • -f, --foreground โ€” if you pass this, the daemon will not daemonize, meaning it stays in the foreground. Iconic for debugging. ๐Ÿ”
  • -m, --port-min=INT โ€” takes an integer, and together with --port-max defines the local port range that rtpengine allocates UDP ports from for media traffic relay. Defaults to 30000.
  • -M, --port-max=INT โ€” the other half of that port range, also an integer. Defaults to 40000.

So like, quick recap: --port-min defaults to 30000, --port-max defaults to 40000, and together they're your range.

If you've got multiple interfaces configured and you're using the new configuration-file based syntax, then different port ranges can be configured for different interfaces (see below). โœจ

  • -L, --log-level=INT โ€” takes an integer and controls the highest log level that gets sent to syslog. Ngl this is merely the default log level used for logging subsystems (see below) that don't explicitly have their own separate log level configured.

The log levels line up with the ones in the syslog(3) man page. Default is 6, which is LOG_INFO. Highest possible value is 7 (LOG_DEBUG), which logs everything. ๐Ÿ‘‘

At runtime you can decrease the log level by sending SIGURS1 to the daemon, and increase it with SIGUSR2. Yes bestie, that's the exact spelling in here, we keep it fr fr.

  • --log-level-subsystem=INT โ€” configures a log level for one of the logging subsystems. A subsystem that doesn't have a level explicitly configured inherits its default from the log-level setting above โ€” with the exception of the internals subsystem, which by default has all logging disabled. ๐Ÿ™ˆ

You can see the full list of logging subsystems by pulling up the --help online help. Some (if not all) of them are: core, spandsp (messages generated by SpanDSP itself), ffmpeg (messages generated by ffmpeg libraries themselves), transcoding (messages related to RTP/media transcoding), codec (messages related to codec negotiation), rtcp, ice, crypto (messages related to crypto/SRTP/SDES/DTLS negotiation), srtp (messages related to RTP/SRTP en/decryption), internals (disabled by default), http (includes WebSocket), control (messages related to control protocols, including SDP exchanges), dtx.

  • --log-facilty=daemon|local0|...|local7|... โ€” the syslog facilty to use when sending log messages to the syslog daemon. Defaults to daemon. (And yes, that's genuinely how it's spelled here. ๐Ÿ’…)
  • --log-facilty-cdr=daemon|local0|...|local7|... โ€” same as --log-facility, except the difference is that only CDRs get written to this log facility.
  • --log-facilty-rtcp=daemon|local0|...|local7|... โ€” same as --log-facility, except only RTCP data gets written here. โš ๏ธ Be careful with this one, because there may be a lot of information written to it.
  • --log-facilty-dtmf=daemon|local0|...|local7|... โ€” same as --log-facility, except only DTMF events get written here. Those DTMF events are extracted from RTP packets conforming to RFC 4733, encoded in JSON format, and written as soon as the end of an event is detected. ๐ŸŽ€
  • --log-format=default|parsable โ€” picks between multiple log output styles. The default prefixes log lines with a description of the relevant entity, like [CALLID] or [CALLID port 12345]. The parsable output style is similar, but makes the ID easier to parse by enclosing it in quotes, like [ID=โ€œCALLIDโ€] or [ID=โ€œCALLIDโ€ port=โ€œ12345โ€].
  • --dtmf-log-dest=IP46:PORT โ€” configures a target address for logging detected DTMF events. Similar vibe to the feature enabled by --log-facilty-dtmf, but instead of writing detected DTMF events to syslog, this sends the JSON payload to the given address as UDP packets. ๐Ÿ›ฐ๏ธ
  • --dtmf-log-ng-tcp โ€” if --listen-tcp-ng is enabled, this sends DTMF events to all connected clients encoded in bencode format.
  • --dtmf-no-log-injects โ€” if --dtmf-no-log-injects is enabled, then DTMF events resulting from a call to inject-DTMF won't be sent to --dtmf-log-dest= or --listen-tcp-ng.
  • --dtmf-no-suppress โ€” some RTP clients keep sending audio RTP packets during a DTMF event, which means both audio packets and DTMF packets show up at the same time. ๐Ÿ˜ตโ€๐Ÿ’ซ By default, when transcoding, rtpengine suppresses audio packets during a DTMF event and will only send DTMF packets until the DTMF event is over. Setting this option disables that feature.
  • --log-srtp-keys โ€” write SRTP keys to the error log instead of the debug log. ๐Ÿ”
  • -E, --log-stderr โ€” log to stderr instead of syslog. Only useful in combination with --foreground.
  • --split-logs โ€” split multi-line log messages into individual log messages, so every single line gets its own log line prefix.
  • --max-log-line-length=INT โ€” split log lines into multiple lines once they exceed the character count you give here. You can set it to a negative value to allow unlimited-length log lines. Set it to zero for the default value, which is unlimited if logging to stderr, or 500 if logging to syslog.
  • --no-log-timestamps โ€” don't add timestamps to log lines written to stderr. Only useful in combination with --log-stderr.
  • --log-name=STRING โ€” sets the id that gets printed in syslog. Defaults to rtpengine.
  • --log-mark-prefix=STRING โ€” a prefix added to particular data fields in log files that are deemed sensitive and/or private information. Defaults to an empty string.
  • --log-mark-suffix=STRING โ€” a suffix added to particular data fields in log files that are deemed sensitive and/or private information. Defaults to an empty string. ๐Ÿคซ
  • --num-threads=INT โ€” how many worker threads to create, and it must be at least one. The default is to create as many threads as there are CPU cores available. If the number of CPU cores can't be determined, or if it's less than four, then the default is four.
  • --media-num-threads=INT โ€” number of threads to launch for media playback. Defaults to the same number as num-threads. You can set it to zero if you don't want any media playback functionality at all. ๐Ÿ™…โ€โ™€๏ธ

Media playback is actually handled by two threads: one for reading and decoding the media file, and another to schedule and send out RTP packets. So for example, if this option is set to 4, in total 8 threads will be launched.

  • --codec-num-threads=INT โ€” enables asynchroneous transcoding operation using the specified number of worker threads. Heads up though: this is an experimental feature and probably doesn't bring any benefits over normal synchroneous transcoding. ๐Ÿงช
  • --poller-size=INT โ€” sets the maximum number of event items (file descriptors) to retrieve from the underlying system poll mechanism per iteration. Defaults to 128. A lower number can lead to improved load-balancing among a large number of threads.
  • --thread-stack=INT โ€” sets the stack size of each thread to the value given in kB. Defaults to 2048 kB. Can be set to -1 to leave the default provided by the OS unchanged.
  • --evs-lib-path=FILE โ€” points to the shared object file (.so) containing the reference implementation for the EVS codec. See the README for more details.
  • --sip-source โ€” okay so, the original rtpproxy as well as older versions of rtpengine by default did not honour IP addresses given in the SDP body, and instead used the source address of the received SIP message as the default endpoint address. Newer versions of rtpengine reverse this behaviour and honour the addresses given in the SDP body by default. This option restores the old behaviour. ๐Ÿ•ฐ๏ธ
  • --dtls-passive โ€” enables the DTLS=passive flag for all calls, unconditionally.
  • -d, --delete-delay=INT โ€” delete the call from memory after the specified delay. Can be set to zero for immediate call deletion.
  • -r, --redis=[PW@]IP:PORT/INT โ€” connect to the specified Redis database (with the given database number) and use it for persistence storage. The format of this option is ADDRESS:PORT/DBNUM, for example 127.0.0.1:6379/12 to connect to Redis DB number 12 running on localhost on the default Redis port.

If the Redis database is protected with an authentication password, you can supply that password by prefixing the argument value with the password, separated by an @ symbol, for example foobar@127.0.0.1:6379/12. โš ๏ธ But note that this leaves the password visible in the process list, which poses a security risk if untrusted users access the same system. As an alternative, the password can also be supplied in the shell environment through the environment variable RTPENGINE_REDIS_AUTH_PW.

On startup, rtpengine will read the contents of this database and restore all calls stored therein. During runtime operation, rtpengine will continually update the database's contents to keep it current, so that in case of a service disruption, the last state can be restored upon a restart. ๐Ÿซถ

When this option is given, rtpengine will delay startup until the Redis database adopts the master role (but see below).

  • -w, --redis-write=[PW@]IP:PORT/INT โ€” configures a second Redis database, this time for write operations. If you give this option in addition to the first one, then the first database gets used for read operations (i.e. to restore calls from) while the second one gets used for write operations (to update states in the database).

For password protected Redis servers, the environment variable for the password is RTPENGINE_REDIS_WRITE_AUTH_PW.

When both options are given, rtpengine will start and use the Redis database regardless of the database's role (master or slave). ๐Ÿ’ช

  • --redis-subscribe=[PW@]IP:PORT[/INT] โ€” configures a Redis database for subscribing and receiving notifications. This option takes precedence over --redis, if configured. When --subscribe-keyspace is also configured, the keyspace part of --redis-subscribe= is not used โ€” the former takes precedence. The keyspace number can also be omitted altogether.

For password protected Redis servers, the environment variable for the password is RTPENGINE_REDIS_SUBSCRIBE_AUTH_PW.

  • -k, --subscribe-keyspace=INT
  • --subscribe-keyspace= โ€” the actual list of redis keyspaces you wanna subscribe to. If this and --redis-subscribe= are both missing, nothing gets subscribed (that's the default vibe). And if you use it together with --redis-subscribe=, then --subscribe-keyspace= just straight-up overwrites the keyspaces to subscribe to.

Later on you can add or remove subscriptions at runtime via rtpengine-ctl ksadd / rtpengine-ctl ksrm. Doing that might flip the redis keyspace notification feature on or off, jsyk.

You're allowed to leave the subscription list empty at first โ€” but ๐Ÿ’€ if you ever want to add keyspaces later during runtime, the feature still has to be configured at rtpengine startup. You do that either by setting --redis-subscribe= to a valid address, or by listing the single value -1 under --subscribe-keyspace=.

  • --redis-num-threads=INT โ€” how many redis restore threads to spin up. Default is 4.
  • --redis-expires=INT โ€” expire time for redis keys, in seconds. Default is 86400.
  • --active-switchover โ€” turn this on and any activity on a call (think signalling, media, whatever) that was created through a Redis keyspace notification makes rtpengine take control of that call. Without it, rtpengine needs an explicit command to take control of a call (or to give it up). No autopilot, bestie.
  • -q, --no-redis-required โ€” with this flag present (or NO*REDIS*REQUIRED=yes'or1in the config file), rtpengine will start even when there's no initial connection to the redis databases โ€” whether that's-r, -w`, or both of them.
Heads up though: if the -r redis can't be connected to initially, sessions do not get reloaded when rtpengine starts. rtpengine still starts, but yeah. Worth knowing. ๐Ÿซถ
  • --redis-allowed-errors โ€” if this is present and its value is >= 0, it configures how many consecutive errors are allowed while talking to a redis server before redis communication gets temporarily disabled for that server. While it's disabled, there are no reconnect attempts and no commands sent to that server. Default value is -1, which means the whole feature is off. You can also set or list this parameter via rtpengine-ctl.
  • --redis-disable-time โ€” how many seconds redis communication stays disabled because of errors. It works in tandem with the redis-allowed-errors parameter. Default is 10. Also settable / listable via rtpengine-ctl.
  • --redis-cmd-timeout=INT โ€” set this to a non-zero value and it becomes the timeout, in milliseconds, for each command sent to the redis server. If redis doesn't reply in time, that command just fails. Default is 0, meaning commands block with no timeout at all. Settable / listable via rtpengine-ctl too โ€” but note that setting this parameter to 0 requires a reconnect on all configured redis servers. ๐Ÿ˜ญ
  • --redis-connect-timeout=INT โ€” the timeout value, in milliseconds, for connecting to a redis server. If it can't connect within that window, the connection fails. Important detail: on failure, when reconnecting to redis, a PING command is issued before attempting to connect, so the --redis-cmd-timeout value gets added onto the total waiting time. Super useful if you're using --redis-allowed-errors and trying to estimate the total lost time when redis takes a lil nap. Default connection timeout is 1000ms. Also settable / listable via rtpengine-ctl.
  • --redis-resolve-on-reconnect โ€” enables the redis resolve on reconnect functionality: when re-connecting to the remote redis server, it tries to re-resolve if the redis hostname was an FQDN and not an IP address. It's a boolean value (either true or false).
  • --redis-format=bencode|JSON โ€” picks the format for serialised call data written to Redis or KeyDB. The old default (and honestly the only option before) was a JSON object. The new default is bencode formatting. Bencode lowkey slays performance-wise โ€” better speed, lower CPU usage โ€” at the cost of being way less human readable. ๐Ÿ‘‘

Both formats can be restored from, no matter what this setting is. So you're not locked in.

  • --redis-tcp-keepalive-time=INT
  • --redis-tcp-keepalive-intvl=INT
  • --redis-tcp-keepalive-probes=INT

These three control TCP keepalive behaviour on connections to Redis. The time value defaults to zero, and it must be set to a non-zero positive value to actually enable keepalives โ€” it's the number of seconds a socket has to sit idle before keepalives start getting sent.

intvl is the time in seconds between keepalives, and probes is how many keepalive probes get sent before the connection is deemed dead. Defaults are 1 and 3 respectively.

  • -b, --b2b-url=STRING โ€” enables and sets the URI for an XMLRPC callback that fires when a call is torn down due to packet timeout. The special code %% can stand in for an IP address, and in that case the source address of the originating request gets used (or alternatively the address set with the xmlrpc-callback ng protocol option).
  • -x, --xmlrpc-format=INT โ€” selects the internal format of the XMLRPC callback message for B2BUA call teardown. 0 is for SEMS, 1 is a generic format that only contains the call-ID, 2 is for Kamailio.
  • --max-sessions=INT โ€” caps the number of maximum concurrent sessions. Set it at startup via max-sessions in the config file. Set it at runtime via the rtpengine-ctl util. And listen โ€” rtpengine-ctl set maxsessions 0 can be used for draining rtpengine sessions. The whole flow:
    • Enable feature: max-sessions=1000
    • Enable feature: rtpengine-ctl set maxsessions >= 0
    • Disable feature: rtpengine-ctl set maxsessions -1

By default the feature is disabled, i.e. maxsessions == -1.

  • --max-load=FLOAT โ€” if the current 1-minute load average goes over the value you put here, new sessions get rejected until the load average drops back under the threshold. Vibe check for your box. ๐Ÿ“‰
  • --max-cpu=FLOAT โ€” if current CPU usage (in percent) goes over the value you put here, new sessions get rejected until CPU usage drops back under the threshold. CPU usage is sampled every 0.5 seconds. Only supported on systems that provide a Linux-style /proc/stat. So like, Linux things only, bestie.
  • --max-bandwidth=INT โ€” if current bandwidth usage (in bytes per second) goes over the value you put here, new sessions get rejected until bandwidth usage drops back under the threshold. Bandwidth usage is sampled every 1 second and is based on received packets, not sent ones. Important distinction, don't forget it.
  • --max-recv-iters=INT โ€” sets the maximum continuous reading cycles in the UDP poller loop. This can help dodge dropped-packet errors on bursty streams. Default is 50.
  • --homer=IP46:PORT โ€” enables sending the decoded contents of RTCP packets to a Homer SIP capture server. Transport is HEP version 3 and the payload format is JSON. This argument takes an IP address and a port number as its value. It also enables sending the control NG traffic to a capturing agent โ€” but payload format does not apply in that case.
  • --homer-protocol=udp|tcp โ€” can be either udp or tcp, with udp being the default.
  • --homer-id=INT โ€” the HEP protocol Homer uses contains a โ€œcapture IDโ€ to tell different sources of capture data apart. You specify that ID with this argument.
  • --homer-disable-rtcp-stats โ€” kills the default behaviour where RTCP stats get sent when the homer parameter is set. This way, sending of RTCP and NG are decoupled from each other.
  • --homer-enable-ng โ€” enables sending control NG packages to Homer capturing software. The capturing agent part is not officially supported OOTB, but you can make it happen with Kamailio by using the config. For this feature to work you have to set at least the homer parameter.
  • --homer-ng-capture-proto=INT โ€” the HEP protocol Homer uses contains a โ€œCapture protocol typeโ€ UINT8 that the capturing agent and UI use to do further processing. Some values are registered, but currently 0x3d values onwards are free. Default value is 0x3d (61).
  • --recording-dir=FILE โ€” an optional argument that specifies a path to a directory where PCAP recording files and recording metadata files should be stored. If you don't specify it, support for call recording is disabled. Period.

rtpengine supports multiple mechanisms for recording calls โ€” peep recording-method below for the list. The default recording method pcap is the one described in this section.

PCAP files get stored inside a pcap subdirectory, and metadata inside a metadata subdirectory.

The format for a metadata file is (with a trailing newline):

/path/to/recording-pcap.pcap

SDP mode: offer
SDP before RTP packet: 1

first SDP

SDP mode: answer
SDP before RTP packet: 1

second SDP

...

SDP mode: answer
SDP before RTP packet: 100

n-th and final SDP

start timestamp (YYYY-MM-DDThh:mm:ss)
end timestamp   (YYYY-MM-DDThh:mm:ss)

generic metadata

There are two empty lines between each logical block of metadata. All answer SDP gets written out, each one separated from the others by one empty line. That generic metadata at the end can be any length, with any number of lines. Metadata files show up in the subdirectory once the call completes. PCAP files get written to the subdirectory as the call is being recorded โ€” live, bestie. ๐ŸŽ€

Because call recording via this method happens entirely in userspace, in-kernel packet forwarding cannot be used for calls that are currently being recorded. Packet forwarding will thus be done in userspace only. No kernel shortcuts for these calls, sorry.
  • --recording-method=pcap|proc|all โ€” multiple methods of call recording are supported, and this option picks one. Currently supported: pcap, proc, and all. The default method is pcap, and it's the one described above.

The proc recording method works by writing metadata files directly into the recording-dir (i.e. not into a subdirectory), and instead of recording RTP packet data into pcap files, the packet data is exposed via a special interface in the /proc filesystem. Packets then have to be retrieved from that interface by a dedicated userspace component (usually a daemon, like the recording-daemon included in this repository).


So packet data just vibes in kernel memory until the userspace component comes and retrieves it ๐Ÿ’พ but heads up: only a limited number of packets (default 10) per media stream get held. If they're not retrieved in time, they simply get discarded โ€” no drama, just gone. The whole point of this is that you can flag every call to be recorded, and then it's up to the userspace component to decide whether it actually wants to use that packet data for anything or not. Main character energy, honestly โœจ

And bestie, in-kernel packet forwarding is fully supported with this recording method, even for calls that are being recorded. It's giving "you can absolutely have both" ๐Ÿ’…

Oh also โ€” the recording method all turns on both pcap and proc at the same time. Slay.

  • --recording-format=raw|eth โ€” when you're recording to a pcap file in raw (the default), there is no ethernet header. Set it to eth instead and a fake ethernet header gets added, which makes every package a whole 14 bytes bigger. ๐ŸŽ€
  • --record-egress โ€” apply media recording to egress media streams (as they're sent out by rtpengine) instead of to media streams as they're received. This is what lets you capture manipulated and generated media (like, say, stuff from the play media command) inside your recordings. ๐Ÿ”ฅ
  • --iptables-chain=STRING โ€” this one enables explicit management of an iptables chain. When you turn it on, rtpengine takes control of the given iptables chain, and that chain must already exist before you start the daemon, okay? On startup, rtpengine flushes the chain, then adds one ACCEPT rule for each media port (RTP/RTCP) opened. Each rule matches the individual port and destination IP address exactly, and gets created with the call ID as an iptables comment. The rule gets deleted when the port is closed. Iconic bookkeeping ๐Ÿ’…

This option is what lets you build a firewall with a default DROP policy across the entire port range rtpengine uses, and then reference that iptables chain so you only selectively allow the ports actually in use.

Note: this applies only to media ports โ€” it does not apply to any other ports (like the control ports) that rtpengine uses.

Also a reality check: the iptables API is not the most efficient one out there and it does not lend itself to fast dynamic creation and deletion of rules. If you've got a high call volume, especially tons of call attempts per second, you might hit a significant performance impact. This is not rtpengine's fault, bestie โ€” it's iptables and its API implementation in the Linux kernel. In that case, the recommended move is to add a static iptables rule covering the whole media port range instead, and just not use this option.

  • --scheduling=default|...
  • --priority=INT
  • --idle-scheduling=default|...
  • --idle-priority=INT

These four are the thread-scheduling knobs ๐ŸŒธ The scheduling and priority settings apply to the main worker threads, while the idle- versions of those same settings apply to the various lower-priority threads, like timer runs.

The scheduling settings take the name of one of the supported scheduler policies. Setting it to default or none is equivalent to not setting the option at all and leaves the system default in place. The strings fifo and rr refer to realtime scheduling policies. other is the Linux default scheduling policy. batch is similar to other except for a small wake-up scheduling penalty. idle is an extremely low priority scheduling policy. The Linux-specific deadline policy is not supported by rtpengine. And ngl, not all systems necessarily support all scheduling policies, so refer to your system's sched(7) man page for the details.

The priority settings correspond to the scheduling priority for realtime (fifo or rr) scheduling policies, and must be in the range of 1 (low) through 99 (high). For all the other scheduling policies (including when no policy is specified), the priority settings correspond to the nice value and should be in the range of -20 (high) through 19 (low). Heads up: not all systems support thread-specific nice values, and on such a system, using these settings might have unexpected results. (Linux does support thread-specific nice values, so you're good there.) Refer to your system's sched(7) man page.

  • --mysql-host=HOST|IP
  • --mysql-port=INT
  • --mysql-user=USERNAME
  • --mysql-pass=PASSWORD

This is the configuration for playing back media files that live in a MySQL (or MariaDB) database ๐Ÿ“ At minimum, mysql-host must be configured for any of this to work. The others are optional and default to their respective values from the MySQL/MariaDB client library.

  • --mysql-query=STRING โ€” the query used for retrieving media files from the database. No default exists, so this is a mandatory configuration for media playback from a database. The query string you provide must contain the single format placeholder %llu and must not contain any other format placeholders. The ID value passed to rtpengine in the db-id key of the play media message gets used in place of the placeholder when querying the database.

An example configuration might look like this:

mysql-query = select data from voip.files where id = %llu
  • --endpoint-learning=delayed|immediate|off|heuristic โ€” picks one of the available algorithms for learning RTP endpoint addresses ๐Ÿฆ‹ The legacy setting is delayed, which waits 3 seconds before committing to an endpoint address, and the address is then learned from the first incoming RTP packet seen after that delay. The setting immediate learns the endpoint address from the first incoming packet seen, no 3-second delay. Using off disables endpoint learning altogether โ€” which will likely break clients behind NAT, so be careful. The setting heuristic includes that same 3-second delay, but source addresses seen from incoming RTP packets get ranked according to preference: if a packet with a source address and port matching the SDP address is seen, that address is used. Otherwise, if a packet with a matching source address (but a different port) is seen, that address is used. Otherwise, if a packet with a matching source port (but a different address) is seen, that address is used. Otherwise, the source address of any incoming packet seen is used.
  • --jitter-buffer=INT โ€” size of the (incoming) jitter buffer, in packets. A value of zero (which is the default) disables the jitter buffer. Also worth knowing: the jitter buffer is currently only implemented for userspace operation.

When adaptive jitter buffer sizing is enabled (see --jb-adaptive below), this value serves as the base capacity for burst-aware packet discard, and as a fallback for when adaptive sizing isn't available yet (like during initial packet collection).

  • --jb-clock-drift โ€” enable clock drift compensation for the jitter buffer โŒš
  • --jb-adaptive โ€” enable adaptive jitter buffer sizing โœจ When it's on, the jitter buffer size is automatically adjusted based on measured network jitter using statistical analysis. The buffer size is calculated as the mean jitter plus four standard deviations (ฮผ + 4ฯƒ), providing 99.99% coverage of packet arrival variations.

Adaptive sizing needs at least 10 jitter samples before it can calculate the optimal buffer size, and the calculation gets updated every 10 packets. The calculated buffer size is constrained by the --jb-adaptive-min and --jb-adaptive-max limits.

Burst-aware packet discard is always active (regardless of this setting) and figures out the minimum number of packets to remove during buffer overflow based on the time elapsed since the last packet, instead of discarding all buffered packets. This typically results in an 80-90% reduction in packet loss during burst events. When adaptive sizing is enabled, burst-aware discard uses the dynamically calculated buffer size; otherwise it uses the static buffer size from --jitter-buffer.

If this option is not specified, the jitter buffer just uses a fixed size as specified by --jitter-buffer.

  • --jb-adaptive-min=INT โ€” minimum adaptive jitter buffer size in milliseconds ๐ŸŒˆ This sets the lower bound for the automatically calculated buffer size when --jb-adaptive is enabled. Must be greater than or equal to zero. If not specified, it defaults to zero.

This one's useful for services with strict latency requirements, making sure the adaptive buffer never shrinks below a certain threshold even in low-jitter network conditions. ๐Ÿ’œ

  • --jb-adaptive-max=INT โ€” maximum adaptive jitter buffer size in milliseconds ๐ŸŒธ This sets the upper bound for the automatically calculated buffer size when --jb-adaptive is enabled. Must be greater than or equal to zero. If not specified when --jb-adaptive is enabled, it defaults to 300 milliseconds. The maximum allowed value is 1000 milliseconds.

This option stops the adaptive buffer from growing excessively large in high-jitter network conditions, which could introduce unacceptable latency. ๐Ÿ˜ญ

Watch out: if --jb-adaptive-min is greater than --jb-adaptive-max, the daemon will exit with an error during startup. No vibes, just death. ๐Ÿ’€
  • --debug-srtp โ€” turn on extra log messages to help debug SRTP issues ๐Ÿ” Per-packet details โ€” sequence numbers, ROC, payloads (both plain text and encrypted), authentication tags, all of it โ€” get recorded to the log. Every RTCP packet gets logged like this, while every 512th RTP packet gets logged. This only applies to packets forwarded/processed in userspace.
  • --reject-invalid-sdp โ€” with this set, refuse to process SDP bodies that couldn't be cleanly parsed, instead of skipping over the parsing error and processing the SDP anyway. Currently this only affects the processing of SDP bodies that end in a blank line.
  • --listen-http=[IP|HOSTNAME:]PORT
  • --listen-https=[IP|HOSTNAME:]PORT

Enable listening for HTTP or WebSocket connections, or their TLS-secured counterparts HTTPS and WSS ๐Ÿซถ If no interface is specified, then the listening socket gets bound to all interfaces.

The HTTP listener supports both HTTP and WS, while the HTTPS listener supports both HTTPS and WSS. Iconic range, honestly. ๐Ÿ’…

If HTTPS/WSS is enabled, a certificate must also be provided using the options below.

  • --https-cert=FILE
  • --https-key=FILE

Provide a server certificate and its corresponding private key for the HTTPS/WSS listener, in PEM format ๐Ÿ”

  • --http-threads=INT โ€” number of worker threads for HTTP/HTTPS/WS/WSS. If not specified, then the same number as given under num-threads gets used. And if no HTTP listeners are enabled at all, then no threads are created. Efficient! โœจ
  • --http-buf-size=INT

And this one sets the internal buffer size used by the HTTP/HTTPS/WS/WSS library, in kilobytes. If you leave it unset, or set it to something โ‰ค zero, the implementation-defined default gets used instead. Bump it to 1024 (that's one whole megabyte, bestie) or larger if your WS activity is highkey poppin' off โ€” it improves performance and prevents those possible protocol hiccups. ๐Ÿฆ‹

  • --http-idle-timeout=INT โ€” how many seconds idle HTTP/HTTPS keepalive connections are allowed to justโ€ฆ sit there before the server closes them. If it's zero (which is the default), the libwebsockets default is used instead (that's 5 seconds for HTTP/1.1). Set it to a positive value (like 60) to align with your load balancer's idle timeouts, or just to keep those connections open longer. โœจ
  • --software-id=STRING โ€” sets a free-form string used to identify this software towards external systems, for example in outgoing ICE/STUN requests. Defaults to rtpengine-VERSION. The string gets sanitised โ€” every non-alphanumeric character is replaced with a dash โ€” so it's universally usable. No cap, that's the whole thing.
  • --dtx-delay=INT โ€” processing delay in milliseconds for handling discontinuous transmission (DTX) or other transmission gaps. Defaults to zero, i.e. disabled, and it's applicable to transcoded audio streams only. When you turn it on, it delays processing of received packets for the specified time (very much like a jitter buffer) so that DTX handling gets triggered when a transmission gap happens. The decoder is then told to fill in the missing time during the gap โ€” generating comfort noise, for example. Configure the delay to be higher than the incoming jitter you expect, okay? That's the vibe.
  • --max-dtx=INT โ€” maximum duration for DTX handling, in seconds. If no further RTP media comes in within this time frame, DTX processing stops. You can set it to zero or negative to disable that and keep DTX processing going indefinitely. Defaults to 30 seconds. โณ
  • --dtx-buffer=INT
  • --dtx-lag=INT

These two besties work together to control the maximum number of packets and the amount of audio that's allowed to be held in the DTX buffer. The dtx-buffer option limits the number of packets held there, while dtx-lag limits the amount of audio (in milliseconds) held there. A DTX buffer overflow is declared when both limits are exceeded, and in that case DTX processing gets sped up by dtx-shift milliseconds. ๐Ÿš€

The defaults are 10 packets and 100 milliseconds.

  • --dtx-shift=INT โ€” the amount of time in milliseconds that DTX processing is shifted forward (sped up) or backwards (delayed) when there's a DTX buffer overflow or underflow. An underflow happens when RTP packets arrive slower than expected; an overflow happens when they arrive faster than expected. ๐Ÿข๐Ÿ‡

If you set this to zero, then no adjustments of the DTX timer get made. Instead, to keep up with the flow of received RTP packets, packets get dropped or extra DTX audio gets generated as needed.

  • --dtx-cn-params=INT โ€” specify one comfort noise parameter. This option follows the exact same format as cn-payload, described below. ๐Ÿ‘‡

It's applicable to audio generated to fill in transmission gaps during a DTX event. The default setting is no value, which means silence gets generated to fill in those DTX gaps.

If any CN parameters are configured, they get passed to an RFC 3389 CN decoder, and the generated comfort noise is what fills in the DTX gaps.

  • --amr-dtx=native|CN
  • --evs-dtx=native|CN

Select the DTX behaviour for AMR and EVS codecs, respectively. The default is to use the codec's internal processing: during a DTX event, a "no data" frame is passed to the decoder and the output is used as the audio data.

If you pick CN here instead, AMR/EVS use the same DTX mechanism that other codecs use โ€” filling in DTX gaps with either silence or RFC 3389 comfort noise (peep dtx-cn-params). This also changes how received SID frames get processed: SID frames won't be passed to the codec, they'll be replaced by generated silence or comfort noise instead. ๐Ÿ’…

  • --silence-detect=FLOAT โ€” enable silence detection and specify the threshold as a percent. This option is applicable to transcoded streams only, and it defaults to zero (disabled). ๐Ÿคซ

When enabled, silence detection runs on all transcoded audio streams. The threshold you give is the sensitivity for detecting silence: higher thresholds mean more audio gets detected as silence, while lower thresholds mean less audio gets detected as silence. The threshold is a percent between zero and 100. Set it to 100 and literally all audio would be detected as silence; set it to 50 and any audio quieter than 50% of the maximum volume gets detected as silence; and so on. Setting it to zero disables silence detection. If you only want to catch silence that's very near or exactly equal to absolute silence, set this to a low number like 0.01.

Heads up: for certain codecs such as PCMA, a higher minimum threshold is required to detect complete silence, because their compressed payloads don't decode to actual silence โ€” they've got a residual DC offset instead. For PCMA the minimum value is 0.013.

Audio that gets detected as silence is replaced by comfort noise as specified by the cn-payload option (see below ๐Ÿ‘‡). Currently this is applicable only to RTP peers that have advertised support for the CN RTP payload type โ€” in that case, the silence audio frames get replaced by CN RTP frames. ๐ŸŽ€

  • --cn-payload=INT โ€” specify one comfort noise parameter. This option can be given multiple times, and the format follows RFC 3389. When you're at the command line, list the --cn-payload= option multiple times, each one carrying a single CN parameter. When you're in the config file, list the option only a single time and put multiple CN parameters separated by semicolons (e.g. cn-payload = 20;40;60).

The first CN payload value you give is the noise level, specified as -dBov per RFC 3389. That means a noise level of zero corresponds to maximum volume, while higher numbers correspond to lower volumes. The highest allowable number is 127, corresponding to -127 dBov, which is near silence. ๐Ÿ”‡

Subsequent CN payload values carry spectral information (reflection coefficients) per RFC 3389. Allowable values for each coefficient are between 0 and 254. Specifying spectral info is optional, and the number of coefficients you list (the model order) is variable.

This option is applicable only to CN packets generated from the silence detection mechanism described just above. The configured CN parameters are used directly as the payload of CN packets sent by rtpengine.

The default values are 32 (-32 dBov) for the noise level, and no spectral information.

  • --player-cache โ€” enable caching of encoded media packets for the media player. This applies to media playback initiated through the play media command. When it's on, rtpengine won't just decode given media files and then encode the media to RTP on demand and on the fly โ€” instead it decodes and encodes each media file in full the first time playback is requested, then caches the resulting RTP packets in memory. This happens once for each media file and for each output RTP codec requested. ๐ŸŽง

Caching is done based on unique file name (with zero consideration given to different file names that might point at the same actual file), or by integer index for media files played from the database. No verification of changing content of files or database entries is done. Media files provided as a binary blob are also cached, although in that case a hash over the entire media file has to be performed โ€” so that usage is not recommended. ๐Ÿ™ˆ

It's not possible to choose a different start-pos for playback with this option enabled.

RTP data is cached and retained in memory for the lifetime of the process. It never leaves. It lives there now. ๐Ÿ’€
  • --moh-max-duration=INT โ€” music-on-hold max possible duration, in ms. When it's not defined (set to 0), it takes the 1800000ms default value, which is half an hour. ๐ŸŽต
  • --moh-max-repeats=INT โ€” music-on-hold max possible repeats. moh-max-duration always takes precedence over it. By default it's always 999 if not defined otherwise.
  • --moh-attr-name=STRING โ€” controls the value to be added to the session level of SDP whenever MoH is triggered. If it's not defined, then it's not in use. ๐ŸŒธ
  • --moh-prevent-double-hold โ€” protects against double MoH being played, like when two rtpengine instances in the interaction chain both inadvertently try to trigger MoH. ๐Ÿ˜ญ

It works in combination with moh-attr-name, which must be defined in order to give rtpengine a clue about what the session level attribute for double hold detection is.

If it's not defined, then it's not in use.

  • --kernel-player=INT
  • --kernel-player-media=INT

Enables and configures the kernel-based media player. It's disabled by default, only available if the kernel module is in use, and it requires player-cache to be enabled too. ๐Ÿ‘‘

When enabled, media playback gets handled by a set of kernel threads. The kernel-player option defaults to zero and needs to be set to non-zero to enable the feature โ€” the number you hand it is the maximum number of concurrent kernel media players that can be used.

The kernel-player-media option configures the maximum number of unique media "files" that can be stored for playback in the kernel module. Media files requested for playback are first decoded by the player-cache feature, then handed to the kernel module in a pre-encoded format for quick playback. Defaults to 128.

Both player slots and media slots are shared among all instances of rtpengine running on a system using the same kernel module (even ones using different kernel table IDs). Unused slots use minimal resources, so no stress. โœจ
  • --preload-media-files=FILE โ€” enables reading of media files at startup and caching them in memory for playback. Multiple files can be specified. On the command line, you have to give the option multiple times to do that โ€” while in the config file you give the option only once, with the list of files separated by semicolons. ๐Ÿ“‚

All listed files will be read into memory at startup and cached there for the lifetime of the daemon. So when somebody asks for one of these media files to play back, we don't go touching the disk at all โ€” we serve it straight from the cached copy. โœจ

Big caveat tho: the file name in the play media request has to match the file name in the config option exactly. Like, character for character. If the name differs (or someone just requests a totally different file for playback), then playback happens from file as usual โ€” no cache, no magic. ๐Ÿ’…

And bestie, there's a special string: on-demand. Slap that in instead of a file name and you unlock on-demand loading and caching of media files. Any file requested for playback that wasn't already chilling in the memory cache gets read once and then retained in the cache for the lifetime of the daemon. ๐Ÿซถ


  • --media-files-reload=SECONDS โ€” spawns a background thread that periodically checks the media files kept in the memory cache and updates them if needed. Each file's modification timestamp gets checked against the last time it was read, and if it got updated since then, it's re-read and replaces the previous cached contents. ๐Ÿ”„
  • --preload-db-media=INT โ€” like preload-media-files but pulling media from the database instead of reading files. Each entry has to be an integer pointing at an index from the database. On-demand loading works here too โ€” just supply the special string on-demand instead of an index number. ๐Ÿ’พ
  • --db-media-reload=SECONDS โ€” same energy as media-files-reload but for media loaded from the database. Heads up tho: media stored in a database has no modification timestamp, which means all media will always be reloaded from the database at the given interval. No skipping, no shortcuts. ๐Ÿ˜ญ
  • --db-media-cache=PATH โ€” turns on filesystem-backed caching of media entries from the database. The path you give has to be readable and writeable by rtpengine. ๐Ÿ“

Whenever playback of media from the database is requested, rtpengine first checks if a corresponding cached file within the given path exists. If it exists โ€” media gets read from that file instead of from the database, slay. If it doesn't exist โ€” media gets read from the database, and then rtpengine creates the cache file so it's ready for the next time that same media is requested. ๐Ÿ‘‘

  • --preload-db-cache=INT โ€” like preload-db-media but it populates the filesystem-backed cache instead of storing the media in memory. On-demand loading is supported here too. โœจ
  • --cache-media-reload=SECONDS โ€” similar to db-media-reload, but it applies to media stored in the filesystem-backed cache. ๐Ÿงš

  • --expiry-timer=SECONDS
  • --media-files-expire=SECONDS
  • --db-media-expire=SECONDS
  • --db-cache-expire=SECONDS

These four control the automatic removal of entries from the various media caches, when and if those entries go unused for a certain amount of time. By default, automatic removal is disabled โ€” nothing gets yeeted on its own. ๐Ÿ’€

The option expiry-timer must be set to non-zero for any automatic removal to happen. It enables creation of a background thread and controls how often that thread runs to check for expired, unused cache entries.

The other options set the maximum allowed age for entries in their respective caches. If an entry wasn't used for longer than the given time, it gets removed from the cache. Bye bestie ๐Ÿ‘‹


  • --audio-buffer-length=INT โ€” sets the buffer length used by the audio player (see below) in milliseconds. The default is 500 milliseconds. ๐ŸŽง

The buffer has to be long enough to accommodate at least two frames of audio from all contributing sources, which means at least 40 ms or 60 ms for most cases. And if media playback (via the play media command) is desired, then the buffer must be able to fit at least one full frame from the source media file โ€” and the length of that can vary depending on the format of the source media file. For 8 kHz .wav files this is 256 ms (2048 samples). So yeah, 500 ms is the recommended value, no cap.

  • --audio-buffer-delay=INT โ€” initial delay for new sources contributing to an audio buffer (used by the audio player, see below), in milliseconds. The default is 5 ms. โฑ๏ธ

This initial delay exists to compensate for varying inter-arrival times of media packets โ€” aka jitter. Set it too low and intermittent high jitter will give you gaps in the output audio ๐Ÿ˜ญ. Set it too high and your output audio gets an unnecessary latency added to it. It's giving delicate balance.

  • --audio-player=on-demand|play-media|transcoding|always โ€” defines when to enable the audio player if you're not explicitly instructing otherwise. The default setting is on-demand. ๐ŸŽต

Enabling the audio player for a party to a call makes rtpengine produce its own audio RTP stream, instead of just forwarding an audio stream received from elsewhere. The audio is generated from a circular audio buffer (see above) and all contributing audio sources get mixed into that one buffer. The contributing audio sources are audio streams received from elsewhere (the ones that would otherwise just be forwarded) plus audio produced by the play media command.

Here's the vibe check on each value:

  • on-demand โ€” the audio player is enabled only if explicitly requested by the user for a particular call, via the audio-player= option used in a signalling message.
  • play-media โ€” the audio player is enabled only while media playback via the play media command is active. Once playback finishes, the audio player goes back to being disabled and audio goes back to simply being forwarded.
  • transcoding โ€” leaves the audio player disabled unless any sort of transcoding is required for a call.
  • always โ€” the audio player is enabled for all calls, unless explicitly disabled via the audio-player= option used in a signalling message. This forces all audio through the transcoding engine, even if the input and output codecs are the same. ๐Ÿ’…

Audio player usage can be changed on a call-by-call basis by including the audio-player= option in a signalling message. That option supports the values transcoding and always, which do exactly what's described up there, plus off โ€” which forces the audio player to be disabled regardless of this setting. Periodt.

  • --poller-per-thread โ€” enables the poller per thread functionality: for every worker thread (see the --num-threads option) a poller will be created. With this on, it's guaranteed that only a single thread will ever read from a particular socket, which maintains the order of the packets. Might genuinely help if you're having issues with DTMF packets (RFC 2833). ๐Ÿ“ž
  • --timer-accuracy=INT โ€” the minimum number of microseconds that a timer thread is allowed to sleep. In other words, if a timed event is scheduled sooner than this many microseconds in the future, then the event gets executed immediately. Set it to 0 for highest accuracy. Default is 500 microseconds (half a millisecond, bestie). โœจ
  • --io-uring โ€” enables experimental support for io_uring. Requires Linux kernel 6.0 or later. ๐Ÿง

When enabled, instead of the usual polling mechanism, each worker thread sets up its own io_uring and uses it for polling, as well as for directly sending and receiving certain network data. In particular, userspace media data is sent and received directly via io_uring.

NOTE: as of the time of writing, worker threads sleeping in an io_uring poll get attributed to the host system as I/O wait CPU usage, with up to 99% CPU time spent in I/O wait (depending on the number of worker threads), but without being attributed to any process or thread. This is not actual CPU usage โ€” it just indicates time spent waiting for a network event, and so should be considered the same as idle CPU time. Don't panic when you see it. ๐Ÿ™ˆ
  • --io-uring-buffers=INT โ€” number of io_uring entries in the buffer allocated from the kernel, per thread. Defaults to 16384. It must be large enough so that submission entries and completion entries are always available when needed. ๐Ÿ”ฅ
  • --dtls-cert-cipher=prime256v1|RSA โ€” chooses the type of key to use for the signature used by the self-signed certificate used for DTLS. The previous default was RSA. The current default and the only other option is prime256v1, which is a 256-bit elliptic-curve key. ๐ŸŒˆ
  • --dtls-signature=SHA-256|SHA-1 โ€” chooses the hash algorithm used for the signature of the self-signed certificate used for DTLS. The default is SHA-256. Do not confuse this with the hash algorithm used for the certificate fingerprint inserted into the SDP (a=fingerprint:), which is independent of the certificate's signature and can be selected during runtime. Yes this trips people up, iykyk. ๐Ÿ’€
  • --dtls-rsa-key-size=INT โ€” size in bits of the RSA key used by the DTLS certificate, if RSA is in use. Default is 2048 bits. ๐Ÿ”
  • --dtls-ciphers=STRING โ€” the ciphers allowed during the DTLS key exchange (again, not to be confused with the cipher used by the DTLS certificate). The format of this string is an OpenSSL cipher list. The default is:
DEFAULT:!NULL:!aNULL:!SHA256:!SHA384:!aECDH:!AESGCM+AES256:!aPSK
  • --dtls-mtu=INT โ€” sets the DTLS MTU to enable fragmenting of large DTLS packets. Defaults to 1200. The minimum value is 576, since the internet protocol requires that hosts must be able to process IP datagrams of at least 576 bytes (for IPv4) or 1280 bytes (for IPv6). This does not preclude link layers with an MTU smaller than this minimum MTU from conveying IP data. Internet IPv4 path MTU is 68 bytes. ๐Ÿงš
  • --mqtt-host=HOST|IP โ€” host or IP address of the Mosquitto broker to connect to. This must be set to enable exporting stats to Mosquitto. ๐Ÿ“ก
  • --mqtt-port=INT โ€” port of the Mosquitto broker. Defaults to 1883.
  • --mqtt-id=STRING โ€” client ID to use for Mosquitto. Default is a generated random string. ๐ŸŽฒ
  • --mqtt-keepalive=INT โ€” keepalive interval in seconds. Defaults to 30.
  • --mqtt-user=USERNAME
  • --mqtt-pass=PASSWORD

Credentials to connect to the Mosquitto broker. At least a username must be given to enable authentication. ๐Ÿ”‘

  • --mqtt-cafile=FILE
  • --mqtt-capath=PATH
  • --mqtt-certfile=FILE
  • --mqtt-keyfile=FILE
  • --mqtt-tls-alpn=STRING

These enable TLS to connect to the Mosquitto broker, optionally with client certificate authentication. At least cafile or capath must be given to enable TLS. To enable client certificate authentication, both certfile and keyfile must be set. All files must be in PEM format. Password-protected files are not supported. ๐Ÿ™…

The tls-alpn can be set (e.g. mqtt) if a service like AWS IoT Core shares the same TLS port for two different network protocols. ๐ŸŒ

  • --mqtt-publish-qos=0|1|2 ๐Ÿ‘€

QoS value to use for publishing to Mosquitto. See the Mosquitto docs for the deets. ๐Ÿซถ

  • --mqtt-publish-topic=STRING โ€” the topic string you publish to Mosquitto with. Has to be a non-empty string, no empty vibes allowed, bestie. ๐Ÿšซ
  • --mqtt-publish-interval=MILLISECONDS โ€” how often (in milliseconds) you publish to Mosquitto. Defaults to 5000 (aka 5 whole seconds, iykyk).
  • --mqtt-publish-scope=global|summary|call|media โ€” what actually goes out in those messages:
    • summary ๐Ÿ’ซ โ€” one message gets published to Mosquitto every interval milliseconds, and it holds all the global stats.
    • global ๐ŸŒ โ€” literally the same as summary, except it also carries a list of every running call, stats for each one included.
    • call ๐Ÿ“ž โ€” one message per call gets published to Mosquitto with that call's stats, every interval milliseconds, plus one message every interval milliseconds with global stats.
    • media ๐ŸŽฌ โ€” one message per call media gets published (usually one media per call participant, so usually 2 media per call) with that call media's stats, every interval milliseconds, plus one message every interval milliseconds with global stats.
  • --mos=CQ|LQ|G.107|G.107.2|legacy โ€” these are the knobs that shape the MOS (Mean Opinion Score) calculation formula โœจ. You can stack multiple options by passing multiple --mos=... arguments on the command line, or by using a semicolon-separated list in a single mos=... line in the config file.

CQ and LQ are mutually exclusive, so only one of them can ever be in effect. CQ is the default โ€” that's conversational quality, it takes RTT into account, which means it needs peers to send RTCP correctly or it's gonna have a bad time ๐Ÿ˜ญ. If you set LQ (listening quality) then RTT is straight-up ignored, so you can still get a MOS calculated even with zero RTCP. Very forgiving of you. ๐Ÿ’…

The rest of the options all pick a MOS formula, and those are mutually exclusive too. The default is G.107, which is a simplified version of the G.107 formula. The one that used to be the default (and was the only option, iconic behavior) is legacy โ€” a custom formula that gives you slightly higher MOS values than G.107.

G.107.2 uses G.107.2 for fullband audio codecs and the simplified G.107 formula for everything else audio. Heads up: the full G.107.2 formula is kinda math-heavy ๐Ÿงฎ and gives higher MOS values for fullband audio codecs than plain G.107 does.

  • --measure-rtp โ€” turns on measuring RTP metrics even for plain RTP passthrough scenarios. Without it, RTP metrics only get measured in transcoding scenarios. ๐Ÿคท
  • --reporting=full|inline|global|none โ€” controls the format of the SSRC statistics (this is mainly a MOS-and-other-RTP-metrics thing) inside the responses to delete and query messages.
    • inline โ€” the stats of media sources relevant to a media stream get tucked inside the reporting section that belongs to that media stream. Cozy. ๐Ÿ 
    • global โ€” a media stream's own section only lists the numeric SSRC identifiers relevant to it, and the actual statistics live in a global section, indexed by those SSRC identifiers.
    • full โ€” combines both of the above, which duplicates all the statistics and therefore makes message size grow significantly. ๐Ÿ“ˆ
    • The current default is full because that's the legacy format and some controlling agents require it. Not our fault, blame the agents. ๐Ÿ™ˆ
  • --rtcp-interval=INT โ€” the delay in milliseconds between RTCP packets when the generate-rtcp flag is on. The effective value also includes a random dispersion of 0..1 seconds on top, so the timer execution period is randomized and can be up to 1 second greater than the value you gave in ms. Defaults to 5000 ms (5 seconds).
  • --socket-cpu-affinity=INT โ€” enables setting socket CPU affinity via the SO_INCOMING_CPU socket option, if it's available. Default is zero, which disables the whole feature. If you set it to a positive number, then the CPU affinity for all sockets belonging to the same call gets set to the same value. The number specifies the upper limit of the affinity to set, and values are used round-robin (e.g. set it to 8 and the values 0 through 7 get used to set affinity). If you set it to a negative number, then the number of available CPU cores is used instead. ๐Ÿ”ฅ
  • --client-timeout=INT
  • --client-retries=INT โ€” for requests sent to another NG peer, these configure the timeout and the maximum number of retries. The timeout is in milliseconds, must be larger than zero, and defaults to 50. The number of retries must be larger than zero and defaults to 5. Bestie, you got five shots. Make them count. ๐ŸŽฏ
  • --transcode-config=STR โ€” sets the prefix for the sections in the config file that get used for transcode configurations. Peep the section TRANSCODE CONFIG below. ๐Ÿ‘€

๐ŸŒˆ INTERFACES ๐ŸŒˆ

This section is all about the legacy syntax for configuring interfaces โ€” and the cute thing about it is you can use it equally from the configuration file or from the command line. The new syntax for configuring interfaces can only be used from the config file, and it's described at the end of this section. And ngl, the new one is now the preferred method because it supports additional options. Periodt. ๐Ÿ’…

๐ŸŽ€ Legacy Syntax ๐ŸŽ€

The command-line options -i or --interface, or equivalently the interface config file option, specify local network interfaces for RTP. You need at least one, but you can totally specify multiple. The format of the value is:

[NAME/]IP[!IP]

where IP is either an IPv4 address, an IPv6 address, the name of a system network interface (like eth0), a DNS host name (like test.example.com), or any. ๐ŸŒ

Okay so do not confuse configuring a network interface by name with the logical interface name that rtpengine uses internally (more on that below). The NAME token in that syntax up there is the internal logical interface name, while the name of a system network interface goes in place of the first IP token. For example: to configure a logical network interface called int using all the addresses from the existing system network interface eth0, you'd write int/eth0. (And unless it's omitted, the second IP token โ€” the one used for the advertised address โ€” has to be an actual network address. It cannot be an interface name. Sorry. ๐Ÿšซ)

If DNS host names are used instead of addresses or interface names, the lookup happens only once, during daemon start-up. One and done. โœจ

The special keyword any can be used to listen on any and all available local interface addresses, except from loopback devices. This keyword should only be given once, replacing a more explicit interface configuration.

To configure multiple interfaces with the command-line options, just present multiple -i or --interface options. But when you're using the config file, only use a single interface line โ€” and put multiple values in it separated by semicolons, like this:

interface = internal/12.23.34.45;external/23.34.45.54

๐Ÿฆ‹ System Network Interfaces ๐Ÿฆ‹

If an interface option is given using a system interface name in place of a network address, and multiple network addresses are found configured on that system network interface, then rtpengine behaves as if you'd passed multiple --interface options. Like, for example, if interface eth0 exists with both addresses 192.168.1.120 and 2001:db8:85a3::7334 configured on it, and you give the option --interface=ext/eth0, then rtpengine would behave as if both of these had been specified:

--interface=ext/192.168.1.120
--interface=ext/2001:db8:85a3::7334

She does the work for you. ๐Ÿ‘‘

๐Ÿ’– Advertised Address ๐Ÿ’–

That second IP address after the exclamation point is optional, and you use it when the address you want to advertise in outgoing SDP bodies should be different from the actual local address. This is super useful in certain cases, like when your SIP proxy is sitting behind NAT. For example:

--interface=10.65.76.2!192.0.2.4

means 10.65.76.2 is the actual local address on the server, but outgoing SDP bodies should advertise 192.0.2.4 as the address endpoints should talk to. ๐Ÿ—ฃ๏ธ

โš ๏ธ Heads up: you may have to escape the exclamation point from your shell when using command-line options โ€” so like, \!. Your shell will absolutely eat it otherwise. ๐Ÿ˜ญ

๐ŸŒธ Interface Names ๐ŸŒธ

Giving an interface a name (separated from the address by a slash) is optional; if you omit it, the name default gets used. Names are useful because they let you create logical interfaces made of one or more local addresses. Then you can tell rtpengine to use particular interfaces when it's processing an SDP message, so it uses different local addresses when talking to different endpoints. The most common use case for this is bridging between one or more private IP networks and the public internet. ๐ŸŒ‰

For example: if clients coming from a private IP network must communicate their RTP with the local address 10.35.2.75, while clients coming from the public internet must communicate with your other local address 192.0.2.67, you could create one logical interface pub and a second one priv like this:

--interface=pub/192.0.2.67 --interface=priv/10.35.2.75

Then you use the direction option to tell rtpengine which local address to use for which endpoints (either pub or priv). ๐Ÿ’ซ

If multiple logical interfaces are configured but the direction option isn't given in a particular call, then the first interface given on the command line is the one that gets used.

๐Ÿงš Multiple Addresses per Interface ๐Ÿงš

You can specify multiple addresses for the same logical interface (same name). Most commonly that's one IPv4 address and one IPv6 address, like so:

--interface=192.168.63.1 --interface=fe80::800:27ff:fe00:0

In this example no interface name is given, so both addresses get added to a logical interface named default. Then you'd use the address family option to tell rtpengine which address to use in a particular case. ๐Ÿ 

It's also possible to have multiple addresses of the same family on a logical network interface. In that case, the first address (of a particular family) given for an interface is the primary address used by rtpengine for most purposes. Any additional addresses get advertised as additional ICE candidates with increasingly lower priority. This one's great on multi-homed systems โ€” it lets endpoints pick the best possible path to reach the RTP proxy. ๐Ÿ›ฃ๏ธ Just know that if ICE isn't being used, those additional addresses go unused, even though ports would still get allocated on those interfaces. ๐Ÿ’€

๐Ÿ’ซ Round-Robin Address Selection ๐Ÿ’ซ

okay bestie here's another little trick โœจ instead of plain interface names, you can hand over names in the format BASE:SUFFIX. Why would you do that? Because it lets interfaces get used in a round-robin fashion, which is super handy when you're load-balancing the port ranges across a whole bunch of interfaces ๐Ÿ”ฅ

Say what now? Take this config for example: --interface=pub:1/192.0.2.67 --interface=pub:2/10.35.2.75. Those two interfaces can still be referenced directly by their full names (like direction=pub:1, no drama ๐Ÿ’…), but now you can also reference just the base name on its own (i.e. direction=pub). And when you use the base name, one of the two interfaces gets selected round-robin style โ€” but only if that interface actually has enough open ports available. Rude to pick an interface with no ports, so it doesn't ๐Ÿ™ˆ

The whole point? This makes it possible to effectively increase the number of available media ports across multiple IP addresses. And there is no limit on how many interfaces can share the same base name. Go off, honestly ๐Ÿ‘‘

You can also combine the BASE:SUFFIX notation with specifying multiple addresses for the same interface name. An advanced example could be (this one's in config file notation, and we're omitting the actual network addresses, obviously):

interface = pub:1/IPv4;pub:1/IPv4;pub:1/IPv6;pub:2/IPv4;pub:2/IPv6;pub:3/IPv6;pub:4/IPv4

So in this lil example: when direction=pub and IPv4 is needed as a primary address, either pub:1, pub:2, or pub:4 might get selected. When pub:1 is the chosen one, one IPv4 and one IPv6 address will be used as additional ICE alternatives. For pub:2, only one IPv6 is used as an ICE alternative, and for pub:4 no alternatives would be used at all. And when IPv6 is needed as a primary address, either pub:1, pub:2, or pub:3 might be selected ๐Ÿฆ‹

And if at any given time there aren't enough ports available on any interface? It will not be selected by the round-robin algorithm. No ports no service, that's the vibe check ๐Ÿ˜ญ

Oh and โ€” plot twist โ€” you can use the round-robin algorithm even if the direction is not given. If the first given interface has the BASE:SUFFIX format, then the round-robin algorithm is used and it'll select interfaces with the same BASE name. Sneaky but iconic ๐ŸŽ€

๐Ÿท๏ธ Alias Names ๐Ÿท๏ธ

Interface alias names can be created using the ALIAS=NAME syntax. Important detail bestie, don't skip: the alias must be listed after the primary interface that it references. So to create an actual logical interface pub1 and then an alias pub pointing at it:

interface = pub1/IPv4;pub=pub1

Interface aliases are useful in combination with Redis replication. Here's the tea โ˜• if an interface is referred to via an alias name (e.g. direction=pub), then the interface's actual name (pub1 in this example) is propagated into the Redis storage and thus to any dependent standby instances. Those standby instances can then have different address configurations for that interface, which makes it possible to facilitate failover with static addressing (for example behind an IP load balancer). Chef's kiss ๐Ÿซถ

๐Ÿ•ฐ๏ธ Legacy Protocols ๐Ÿ•ฐ๏ธ

If you are not using the NG protocol but rather the legacy UDP protocol used by the rtpproxy module, then listen up: the interfaces must be named internal and external, corresponding to the i and e flags, if you wish to use network bridging in this mode. That's just how the old-school era rolls, no cap ๐Ÿ’€

๐Ÿ†• New Configuration-File Based Syntax ๐Ÿ†•

When a configuration file is in use, instead of having to list multiple interfaces in one long line in the config file, it's now possible to use config file sections (or "groups") for a more convenient way to configure and manage multiple interfaces โœจ

To use it: instead of setting the interface option to any value, the option interfaces-config must be set to a non-empty string. This string is a prefix, which rtpengine uses to go look in the config file for config sections (groups) that contain interface configurations.

For example, if the setting interfaces-config = interface is present in the config file, rtpengine would consider config file sections starting with the string interface and followed by a dash to be interface configurations. The remainder of the name of the config section (the part after the dash) becomes the default name of the interface. The name for the interface can then be overridden within the config section (see below ๐Ÿ‘‡).

NOTE: The names of config sections must be unique within the config file, and each interface config can list only a single address. To add multiple addresses to the same logical interface, the name of the logical interfaces must necessarily be explicitly set in each config section, instead of relying on the name extracted from the name of the config section. Planning ahead is the main character move here ๐Ÿ’…

Each config section must at least define an interface address by setting the address option, or define an alias interface (as described above) by setting the alias option. The name option can be set to override the default name extracted from the name of the config section.

Non-alias interfaces support the additional option advertised to set the advertised address, as well as port-min and port-max to set a port range different from the global setting, and exclude-ports taking a list of semicolon-separated port numbers to exclude individual ports from the port range. Multiple interfaces sharing the same IP address are allowed to use different, possibly overlapping, port ranges. Round-robin interface usage is supported in the same way as described above, i.e. by using a colon and a suffix as part of the interface name ๐ŸŒธ

Interface sections are processed in order, and as such the first one listed becomes the default interface. And if both legacy syntax and new configuration-file based syntax are in use? Then interfaces from the legacy syntax are processed first. Order matters, bestie ๐Ÿงš

A complete example (and she ate, honestly):

[rtpengine]
interfaces-config = interface

# Create an interface "default" and pick up any non-local addresses
# that are bound to the system at startup.
[interface-default]
address = any

# Create an interface "external" and pick up any addresses bound
# to the physical interface "enp63s0".
[interface-external]
address = enp63s0

# Create an interface "internal" with an explicitly set address,
# and also set the advertised address.
[interface-internal]
address = 192.168.67.43
advertised = 203.0.113.7

# Create an interface "ICE" and add all addresses bound to the
# physical interface "enp63s0". Use a mismatched but unique name for
# the interface section so more addresses can be added to this
# interface.
[interface-ICE-1]
name = ICE
address = enp63s0

# Add addresses from interface "enp35s0" to the interface "ICE"
# and use a different port range.
[interface-ICE-2]
name = ICE
address = enp35s0
port-min = 10000
port-max = 59999
exclude-ports = 20000; 20001; 20002

# Create an alias interface "virt" pointing to "external".
[interface-virt]
alias = external

# Create two interfaces that will be used in a round-robin way
# by referring to the interface "rr".
[interface-rr:0]
address = enp5p0
exclude-ports = 29999

[interface-rr:1]
address = enp15p0

๐Ÿ“ฃ SIGNALLING TEMPLATES ๐Ÿ“ฃ

ok so here's the situation bestie ๐Ÿ˜ฎโ€๐Ÿ’จ a lot of what rtpengine does is controlled by flags and keys/values that you hand it at runtime, as part of the signalling control protocol used for communication between the controlling agent (e.g. a SIP proxy) and the rtpengine process. Which means there's often a need to repeatedly give the exact same set of default flags and values to rtpengine for every single message sent to it. And that leads to controlling scripts that are hard to maintain or hard to read. Not the vibe ๐Ÿ˜ญ

To alleviate this problem, rtpengine supports signalling templates that can be configured in its main configuration file and can then be referred to by short names. Short names >>> wall of flags, periodt ๐Ÿ’…

To use this feature, a configuration file must be in use (by default /etc/rtpengine/rtpengine.conf) and the configuration key templates=... must be set to a non-empty string. The value gives the name of the section in the configuration file that contains signalling templates. So for example, if the value is set to templates=templates, then the section [templates] will be used to read signalling templates โœจ

Each key/value in this file section then corresponds to one signalling template, and can be referred to via template=... in any control message.

For example, in order to make an offer to a WebRTC-compliant client, a Kamailio or OpenSIPS script may have used:

rtpengine_offer("transport-protocol=UDP/TLS/RTP/SAVPF ICE=force trickle-ICE rtcp-mux=[offer require] no-rtcp-attribute SDES=off generate-mid");

This entire string of flags can now be converted into a signalling template in the config file as such:

[rtpengine]
...
templates = templates
...

[templates]
WebRTC = transport-protocol=UDP/TLS/RTP/SAVPF ICE=force trickle-ICE rtcp-mux=[offer require] no-rtcp-attribute SDES=off generate-mid

And the offer command in Kamailio or OpenSIPS can then simply be turned into:

rtpengine_offer("template=WebRTC");

Look how clean ๐Ÿ˜ญโœจ obsessed.

In addition to named signalling templates, rtpengine supports default signalling templates that are automatically applied. Default signalling templates are templates using a name that matches a signalling command (e.g. offer or start recording), or the special name default, which is a template that is applied to all signalling messages. These templates are automatically applied without needing to refer to them by name using template=... from within the signalling message. Automatic glow-up, she doesn't even need to be called ๐Ÿ‘‘

๐ŸŽ›๏ธ TRANSCODE CONFIG ๐ŸŽ›๏ธ

Okay bestie, so if the setting transcode-config is set to a non-empty string, then rtpengine goes looking in the configuration file for sections that start with the prefix strings you gave it (plus a dash) and treats those as transcode configurations. Like, say you set transcode-config = tx โ€” then sections in the config file like tx-1 or tx-foobar get treated as transcode configs. And heads up: the names of all sections in the config file have to be unique. No duplicates, periodt. ๐Ÿ’…

Every transcoding section has to list a source codec as source=... and a destination codec as destination=.... Codecs can just be named by their basic name (like PCMA) or with a full complete SDP-like definition (like opus/48000/2). Each section also has to list an action or a verdict. Non-negotiable. โœจ

So for example, if source=PCMU and destination=PCMA are set, then that particular transcoding config section is the one that gets considered when the received codec is PCMU and the requested output codec is PCMA. Slay.

๐ŸŒธ preference Verdict ๐ŸŒธ

This verdict can be used all by itself, or it can vibe alongside some other verdict. What it does is configure a preference for a particular codec combination, relative to the other codec combinations. The default preference (nothing configured, just chilling) is zero. Codec combos with higher (positive) preference values get considered as better options than the default, while lower (negative) preference values get considered as worse options. It's literally just ranking.

For example, consider the following configuration:

transcode-config = tc

[tc-PCMA-PCMU-higher]
source = PCMA
destination = PCMU
preference = 5

[tc-GSM-G723-lower]
source = G723
destination = GSM
preference = -5

Now imagine a received offer SDP listing codecs GSM, PCMU, and G722 (in that order), with transcoding to PCMA and G723 requested. The outgoing offer SDP would therefore list GSM, PCMU, G722, PCMA, and G723 (in that order). Same order, just with the transcoding targets tacked on the end. ๐Ÿซถ

If the answer were to accept any of GSM, PCMU, or G722, then pass-through without transcoding would occur. Easy, no work needed, love that for us.

If the answer were to accept any of the other codecs that were requested for transcoding (PCMA or G723), then normally the first offered codec (GSM) would be used as the transcoding partner. But bestie, there's a but:

  • If the answer were to accept PCMA, then GSM as transcoding partner would be considered with a preference of zero, as no config section matches this pair. However the config section tc-PCMA-PCMU-higher matches PCMU as transcoding partner for PCMA and would give that pair a higher preference (5). So PCMU would win with the highest preference, and transcoding would occur between PCMA and PCMU. ๐ŸŽ€
  • If the answer were to accept G723, then the config section tc-GSM-G723-lower would be considered and would give GSM as transcoding partner a negative preference of -5. The next possible codec (PCMU) doesn't have a matching config section and so would be considered with a preference of zero. So PCMU would win and transcoding would occur between G723 and PCMU. ๐Ÿ’€ (in a good way)
NOTE: These config sections operate directionally, meaning that in the above example, a config section listing source = GSM and destination = G723 would not be considered. If a codec pair ought to receive the same preference value regardless of the direction, then it must be listed twice, with source and destination swapped. Yes, twice. It's giving extra homework but that's just how it is. ๐Ÿ˜ญ

๐Ÿ’œ transform Verdict ๐Ÿ’œ

To use the transform verdict, the config key transform=... must be set. The value is a remote NG peer endpoint โ€” like the NG control port of another rtpengine instance โ€” in the usual address:port notation. This tells rtpengine to send a transform NG request to the remote peer, asking it to remotely transcode the media. If it succeeds, rtpengine will forward the unmodified media stream to the remote peer, to the address and port it received from the NG response, and will expect the transcoded media to be sent back in return, which it will then forward to its appropriate destination. In other words โ€” it facilitates a designated remote transcoding node, instead of doing the transcoding locally. Outsourcing, but make it media. โœจ

Optional config keys you can set are:

  • local-interface=... โ€” to select the local interface to use to communicate with the remote transform gateway.
  • remote-interface=... โ€” to tell the remote gateway to use a particular interface on its side.
  • address-family=... โ€” to select between IPv4 and IPv6.

๐Ÿšช EXIT STATUS ๐Ÿšช

  • 0 โ€” Successful termination. She ate and left no crumbs. ๐Ÿ‘‘
  • 1 โ€” An error occurred. ๐Ÿ˜ญ

๐ŸŒˆ ENVIRONMENT ๐ŸŒˆ

  • RTPENGINE_REDIS_AUTH_PW โ€” Redis server password for persistent state storage.
  • RTPENGINE_REDIS_WRITE_AUTH_PW โ€” Redis server password for write operations, if --redis has been specified, in which case the one specified in --redis will be used for read operations only. Read-only bestie, she's not doing writes. ๐Ÿ™ˆ

๐Ÿ“ FILES ๐Ÿ“

  • /etc/rtpengine/rtpengine.conf โ€” Configuration file. The main character of the config world. ๐ŸŽ€

๐Ÿงš EXAMPLES ๐Ÿงš

A typical command line (enabling both UDP and NG protocols) may look like:

rtpengine --table=0 --interface=10.64.73.31 --interface=2001:db8::4f3:3d \
  --listen-udp=127.0.0.1:22222 --listen-ng=127.0.0.1:2223 --tos=184 \
  --pidfile=/run/rtpengine.pid

Copy it, run it, watch it slay. ๐Ÿ”ฅ


๐Ÿ‘€ SEE ALSO ๐Ÿ‘€


And that's a wrap on the whole rtpengine(8) page, bestie!! ๐Ÿ’– If you ever need to double-check which version of these facts you're reading, this one's 26.0.1.21+0~mr26.0.1.21, dated 2026-07-31. Screenshot it, cite it, live your truth. ๐Ÿฆ‹โœจ

๐Ÿ“ฆ where this page comes from