2023-02-08 18:12:00 +01:00
|
|
|
/* PipeWire */
|
|
|
|
|
/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans <wim.taymans@gmail.com> */
|
|
|
|
|
/* SPDX-License-Identifier: MIT */
|
2022-10-04 09:16:17 +02:00
|
|
|
|
2022-10-04 13:19:14 +02:00
|
|
|
#include "config.h"
|
|
|
|
|
|
2022-10-04 09:16:17 +02:00
|
|
|
#include <limits.h>
|
2023-01-25 17:40:49 +03:00
|
|
|
#include <string.h>
|
2022-10-04 09:16:17 +02:00
|
|
|
#include <unistd.h>
|
2022-10-05 13:04:08 +02:00
|
|
|
#include <sys/stat.h>
|
|
|
|
|
#include <sys/socket.h>
|
|
|
|
|
#include <sys/ioctl.h>
|
2022-10-04 09:16:17 +02:00
|
|
|
#include <arpa/inet.h>
|
|
|
|
|
#include <netinet/in.h>
|
2022-10-05 13:04:08 +02:00
|
|
|
#include <net/if.h>
|
2022-10-04 18:12:48 +02:00
|
|
|
#include <ctype.h>
|
2022-10-04 09:16:17 +02:00
|
|
|
|
2022-10-04 13:19:14 +02:00
|
|
|
#include <spa/utils/hook.h>
|
2023-01-20 16:08:38 +01:00
|
|
|
#include <spa/utils/result.h>
|
2022-10-04 20:44:07 +02:00
|
|
|
#include <spa/utils/ringbuffer.h>
|
2023-06-16 17:18:35 +03:00
|
|
|
#include <spa/utils/defs.h>
|
2022-10-05 13:04:08 +02:00
|
|
|
#include <spa/utils/dll.h>
|
2023-02-21 17:18:52 +01:00
|
|
|
#include <spa/utils/json.h>
|
2025-07-02 18:31:17 +02:00
|
|
|
#include <spa/utils/ratelimit.h>
|
2023-01-20 16:08:38 +01:00
|
|
|
#include <spa/param/audio/format-utils.h>
|
2023-02-16 09:52:04 +01:00
|
|
|
#include <spa/control/control.h>
|
2023-02-21 17:18:52 +01:00
|
|
|
#include <spa/debug/types.h>
|
|
|
|
|
#include <spa/debug/mem.h>
|
2022-10-04 09:16:17 +02:00
|
|
|
|
|
|
|
|
#include <pipewire/pipewire.h>
|
2023-01-20 16:08:38 +01:00
|
|
|
#include <pipewire/impl.h>
|
2022-10-04 13:19:14 +02:00
|
|
|
|
2023-03-02 19:55:44 +01:00
|
|
|
#include <module-rtp/stream.h>
|
2024-02-25 09:19:11 +01:00
|
|
|
#include "network-utils.h"
|
2022-10-04 13:19:14 +02:00
|
|
|
|
2023-11-18 15:11:03 +02:00
|
|
|
/** \page page_module_rtp_source RTP source
|
2022-10-04 09:16:17 +02:00
|
|
|
*
|
2025-07-23 21:05:10 +02:00
|
|
|
* The `rtp-source` module creates a PipeWire source that receives audio RTP packets.
|
|
|
|
|
* These RTP packets may contain raw PCM data, Opus encoded audio, or midi audio.
|
2022-10-04 09:16:17 +02:00
|
|
|
*
|
2023-09-07 16:30:46 +02:00
|
|
|
* This module is usually loaded from the \ref page_module_rtp_sap so that the
|
2025-07-23 21:05:10 +02:00
|
|
|
* source.ip and source.port and format parameters matches that of the sender that
|
|
|
|
|
* is announced via SAP.
|
2023-07-05 15:29:56 +02:00
|
|
|
*
|
2023-11-20 00:23:03 +02:00
|
|
|
* ## Module Name
|
|
|
|
|
*
|
|
|
|
|
* `libpipewire-module-rtp-source`
|
|
|
|
|
*
|
2022-10-04 09:16:17 +02:00
|
|
|
* ## Module Options
|
|
|
|
|
*
|
|
|
|
|
* Options specific to the behavior of this module
|
|
|
|
|
*
|
2022-10-05 18:00:11 +02:00
|
|
|
* - `local.ifname = <str>`: interface name to use
|
2024-01-16 15:48:24 +01:00
|
|
|
* - `source.ip = <str>`: the source ip address, default 224.0.0.56. Set this to the IP address
|
|
|
|
|
* you want to receive packets from or 0.0.0.0 to receive from any source address.
|
2023-07-05 15:29:56 +02:00
|
|
|
* - `source.port = <int>`: the source port
|
2023-02-21 17:18:52 +01:00
|
|
|
* - `node.always-process = <bool>`: true to receive even when not running
|
2024-01-25 15:42:16 +01:00
|
|
|
* - `sess.latency.msec = <float>`: target network latency in milliseconds, default 100
|
2023-07-06 13:08:21 +02:00
|
|
|
* - `sess.ignore-ssrc = <bool>`: ignore SSRC, default false
|
2023-04-06 11:49:25 +02:00
|
|
|
* - `sess.media = <string>`: the media type audio|midi|opus, default audio
|
2025-07-23 21:05:10 +02:00
|
|
|
* - `sess.ts-direct = <bool>`: use direct timestamp mode, default false
|
|
|
|
|
* (see the Buffer Modes section below)
|
2025-01-20 16:44:57 +01:00
|
|
|
* - `stream.may-pause = <bool>`: pause the stream when no data is reveived, default false
|
2022-10-06 11:41:01 +02:00
|
|
|
* - `stream.props = {}`: properties to be passed to the stream
|
2022-10-04 09:16:17 +02:00
|
|
|
*
|
|
|
|
|
* ## General options
|
|
|
|
|
*
|
|
|
|
|
* Options with well-known behavior:
|
|
|
|
|
*
|
2023-02-21 17:18:52 +01:00
|
|
|
* - \ref PW_KEY_REMOTE_NAME
|
|
|
|
|
* - \ref PW_KEY_AUDIO_FORMAT
|
|
|
|
|
* - \ref PW_KEY_AUDIO_RATE
|
|
|
|
|
* - \ref PW_KEY_AUDIO_CHANNELS
|
|
|
|
|
* - \ref SPA_KEY_AUDIO_POSITION
|
2022-10-04 09:16:17 +02:00
|
|
|
* - \ref PW_KEY_MEDIA_NAME
|
2022-10-06 11:41:01 +02:00
|
|
|
* - \ref PW_KEY_MEDIA_CLASS
|
2023-02-21 17:18:52 +01:00
|
|
|
* - \ref PW_KEY_NODE_NAME
|
|
|
|
|
* - \ref PW_KEY_NODE_DESCRIPTION
|
|
|
|
|
* - \ref PW_KEY_NODE_GROUP
|
|
|
|
|
* - \ref PW_KEY_NODE_LATENCY
|
|
|
|
|
* - \ref PW_KEY_NODE_VIRTUAL
|
2022-10-04 09:16:17 +02:00
|
|
|
*
|
|
|
|
|
* ## Example configuration
|
|
|
|
|
*\code{.unparsed}
|
2024-09-14 15:43:25 +03:00
|
|
|
* # ~/.config/pipewire/pipewire.conf.d/my-rtp-source.conf
|
|
|
|
|
*
|
2022-10-04 09:16:17 +02:00
|
|
|
* context.modules = [
|
2023-01-25 14:58:15 +01:00
|
|
|
* { name = libpipewire-module-rtp-source
|
|
|
|
|
* args = {
|
|
|
|
|
* #local.ifname = eth0
|
2023-07-05 15:29:56 +02:00
|
|
|
* #source.ip = 224.0.0.56
|
|
|
|
|
* #source.port = 0
|
2023-01-25 14:58:15 +01:00
|
|
|
* sess.latency.msec = 100
|
2023-07-06 13:08:21 +02:00
|
|
|
* #sess.ignore-ssrc = false
|
2023-02-21 17:18:52 +01:00
|
|
|
* #node.always-process = false
|
2023-03-02 20:04:24 +01:00
|
|
|
* #sess.media = "audio"
|
2023-02-21 17:18:52 +01:00
|
|
|
* #audio.format = "S16BE"
|
|
|
|
|
* #audio.rate = 48000
|
|
|
|
|
* #audio.channels = 2
|
|
|
|
|
* #audio.position = [ FL FR ]
|
2023-01-25 14:58:15 +01:00
|
|
|
* stream.props = {
|
|
|
|
|
* #media.class = "Audio/Source"
|
2023-02-21 17:18:52 +01:00
|
|
|
* node.name = "rtp-source"
|
2023-01-25 14:58:15 +01:00
|
|
|
* }
|
|
|
|
|
* }
|
|
|
|
|
* }
|
|
|
|
|
* ]
|
2022-10-04 09:16:17 +02:00
|
|
|
*\endcode
|
|
|
|
|
*
|
2025-07-23 21:05:10 +02:00
|
|
|
* ## Buffer modes
|
|
|
|
|
*
|
|
|
|
|
* RTP source nodes created by this module use an internal ring buffer. Received RTP audio
|
|
|
|
|
* data is written into this ring buffer. When the node's process callback is run, it reads
|
|
|
|
|
* from that ring buffer and provides audio data from it to the graph.
|
|
|
|
|
*
|
|
|
|
|
* The `sess.ts-direct` option controls the _buffer mode_, which defines how this ring buffer
|
|
|
|
|
* is used. The RTP source nodes created by this module can operate in one of two of these
|
|
|
|
|
* buffer modes. In both modes, the RTP source node uses the timestamps of incoming RTP
|
|
|
|
|
* packets to write into the ring buffer (more specifically, at the position
|
|
|
|
|
* `timestamp + latency from the sess.latency.msec option`). The modes are:
|
|
|
|
|
*
|
|
|
|
|
* -# *Constant latency mode*: This is the default mode. It is used when `sess.ts-direct`
|
|
|
|
|
* is set to false. `sess.latency.msec` then defines the ideal fill level of the ring
|
|
|
|
|
* buffer. If the fill level is above or below this, then a DLL is used to adjust the
|
|
|
|
|
* consumption of the buffer contents. If the fill level is below a critical value
|
|
|
|
|
* (that's the amount of data that is needed in a cycle), or if the fill level equals
|
|
|
|
|
* the total buffer size (meaning that no more data can be fed into the buffer), the
|
|
|
|
|
* buffer contents are resynchronized, meaning that the existing contents are thrown
|
|
|
|
|
* away, and the ring buffer is reset. This buffer mode is useful for when a constant
|
|
|
|
|
* latency is desired, and the actual moment playback starts is unimportant (meaning
|
|
|
|
|
* that playback is not necessarily in sync with other devices). This mode requires
|
|
|
|
|
* no special graph driver.
|
|
|
|
|
* -# *Direct timestamp mode*: This is an alternate mode, used when `sess.ts-direct` is
|
|
|
|
|
* set to true. In this mode, ring buffer over- and underrun and fill level are not
|
|
|
|
|
* directly tracked; instead, they are handled implicitly. There is no constant latency
|
|
|
|
|
* maintained. The current time (more specifically, the \ref spa_io_clock::position field
|
|
|
|
|
* of \ref spa_io_position::clock) is directly used during playback to retrieve audio
|
|
|
|
|
* data. This assumes that a graph driver is used whose time is somehow synchronized
|
|
|
|
|
* to the sender's. Since the current time is directly used as an offset within the
|
|
|
|
|
* ring buffer, the correct data is always pulled from the ring buffer, that is, the
|
|
|
|
|
* data that shall be played now, in sync with the sender (and with other receivers).
|
|
|
|
|
* This buffer mode is useful for when receivers shall play in sync with each other,
|
|
|
|
|
* and shall use one common synchronized time, provided through the \ref spa_io_clock .
|
|
|
|
|
* `sess.latency.msec` functions as a configurable assumed maximum transport delay
|
|
|
|
|
* instead of a constant latency quantity in this mode. The DLL is not used in this
|
|
|
|
|
* mode, since the graph driver is assumed to be synchronized to the sender, as said,
|
|
|
|
|
* so any output sinks in the graph will already adjust their consumption pace to
|
|
|
|
|
* match the pace of the graph driver.
|
|
|
|
|
* AES67 sessions use this mode, for example.
|
|
|
|
|
*
|
2022-10-13 13:16:33 +02:00
|
|
|
* \since 0.3.60
|
2022-10-04 09:16:17 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#define NAME "rtp-source"
|
|
|
|
|
|
2024-04-16 10:21:40 +02:00
|
|
|
PW_LOG_TOPIC(mod_topic, "mod." NAME);
|
2022-10-04 09:16:17 +02:00
|
|
|
#define PW_LOG_TOPIC_DEFAULT mod_topic
|
|
|
|
|
|
2023-02-21 17:18:52 +01:00
|
|
|
#define DEFAULT_CLEANUP_SEC 60
|
|
|
|
|
#define DEFAULT_SOURCE_IP "224.0.0.56"
|
|
|
|
|
|
2023-03-06 10:46:21 +01:00
|
|
|
#define DEFAULT_TS_OFFSET -1
|
|
|
|
|
|
2023-03-22 16:35:55 +01:00
|
|
|
#define USAGE "( local.ifname=<local interface name to use> ) " \
|
|
|
|
|
"( source.ip=<source IP address, default:"DEFAULT_SOURCE_IP"> ) " \
|
|
|
|
|
"source.port=<int, source port> " \
|
2023-04-06 11:49:25 +02:00
|
|
|
"( sess.latency.msec=<target network latency, default "SPA_STRINGIFY(DEFAULT_SESS_LATENCY)"> ) "\
|
2023-07-06 13:08:21 +02:00
|
|
|
"( sess.ignore-ssrc=<to ignore SSRC, default false> ) "\
|
2023-04-06 11:49:25 +02:00
|
|
|
"( sess.media=<string, the media type audio|midi|opus, default audio> ) " \
|
2023-03-22 16:35:55 +01:00
|
|
|
"( audio.format=<format, default:"DEFAULT_FORMAT"> ) " \
|
|
|
|
|
"( audio.rate=<sample rate, default:"SPA_STRINGIFY(DEFAULT_RATE)"> ) " \
|
|
|
|
|
"( audio.channels=<number of channels, default:"SPA_STRINGIFY(DEFAULT_CHANNELS)"> ) " \
|
|
|
|
|
"( audio.position=<channel map, default:"DEFAULT_POSITION"> ) " \
|
|
|
|
|
"( stream.props= { key=value ... } ) "
|
2022-10-06 11:41:01 +02:00
|
|
|
|
|
|
|
|
static const struct spa_dict_item module_info[] = {
|
|
|
|
|
{ PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
|
|
|
|
|
{ PW_KEY_MODULE_DESCRIPTION, "RTP Source" },
|
|
|
|
|
{ PW_KEY_MODULE_USAGE, USAGE },
|
|
|
|
|
{ PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
|
|
|
|
|
};
|
2022-10-05 18:00:11 +02:00
|
|
|
|
2022-10-04 09:16:17 +02:00
|
|
|
struct impl {
|
|
|
|
|
struct pw_impl_module *module;
|
|
|
|
|
struct spa_hook module_listener;
|
|
|
|
|
struct pw_properties *props;
|
2023-02-21 19:16:23 +01:00
|
|
|
struct pw_context *context;
|
2022-10-04 09:16:17 +02:00
|
|
|
|
2025-05-30 11:59:35 +02:00
|
|
|
struct pw_loop *main_loop;
|
2022-10-07 13:04:46 +02:00
|
|
|
struct pw_loop *data_loop;
|
2022-10-04 09:16:17 +02:00
|
|
|
|
|
|
|
|
struct pw_core *core;
|
|
|
|
|
struct spa_hook core_listener;
|
|
|
|
|
struct spa_hook core_proxy_listener;
|
|
|
|
|
unsigned int do_disconnect:1;
|
|
|
|
|
|
2025-07-02 18:31:17 +02:00
|
|
|
struct spa_ratelimit rate_limit;
|
|
|
|
|
|
2022-10-05 13:04:08 +02:00
|
|
|
char *ifname;
|
2023-01-25 19:03:17 +03:00
|
|
|
bool always_process;
|
2022-10-31 19:19:15 +03:00
|
|
|
uint32_t cleanup_interval;
|
2022-10-04 20:44:07 +02:00
|
|
|
|
2025-05-19 16:14:08 +02:00
|
|
|
struct spa_source *standby_timer;
|
2025-05-20 12:57:13 +02:00
|
|
|
/* This timer is used when the first stream_start() call fails because
|
|
|
|
|
* of an ENODEV error (see the stream_start() code for details) */
|
|
|
|
|
struct spa_source *stream_start_retry_timer;
|
2022-10-04 09:16:17 +02:00
|
|
|
|
2023-02-21 17:18:52 +01:00
|
|
|
struct pw_properties *stream_props;
|
2023-03-02 19:55:44 +01:00
|
|
|
struct rtp_stream *stream;
|
2022-10-05 13:04:08 +02:00
|
|
|
|
2023-02-21 17:18:52 +01:00
|
|
|
uint16_t src_port;
|
|
|
|
|
struct sockaddr_storage src_addr;
|
|
|
|
|
socklen_t src_len;
|
|
|
|
|
struct spa_source *source;
|
|
|
|
|
|
2024-11-11 12:03:32 +01:00
|
|
|
uint8_t *buffer;
|
|
|
|
|
size_t buffer_size;
|
|
|
|
|
|
2025-01-20 14:00:21 +01:00
|
|
|
bool receiving;
|
2025-01-20 16:44:57 +01:00
|
|
|
bool may_pause;
|
|
|
|
|
bool standby;
|
|
|
|
|
bool waiting;
|
2023-02-21 17:18:52 +01:00
|
|
|
};
|
|
|
|
|
|
2025-07-02 18:31:17 +02:00
|
|
|
static inline uint64_t get_time_ns(void)
|
|
|
|
|
{
|
|
|
|
|
struct timespec ts;
|
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
|
|
|
return SPA_TIMESPEC_TO_NSEC(&ts);
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-20 16:44:57 +01:00
|
|
|
static int do_start(struct spa_loop *loop, bool async, uint32_t seq, const void *data,
|
|
|
|
|
size_t size, void *user_data)
|
|
|
|
|
{
|
|
|
|
|
struct impl *impl = user_data;
|
|
|
|
|
if (impl->waiting) {
|
|
|
|
|
struct spa_dict_item item[1];
|
|
|
|
|
|
|
|
|
|
impl->waiting = false;
|
|
|
|
|
impl->standby = false;
|
|
|
|
|
|
|
|
|
|
pw_log_info("resume RTP source");
|
|
|
|
|
|
|
|
|
|
item[0] = SPA_DICT_ITEM_INIT("rtp.receiving", "true");
|
|
|
|
|
rtp_stream_update_properties(impl->stream, &SPA_DICT_INIT(item, 1));
|
|
|
|
|
|
|
|
|
|
if (impl->may_pause)
|
|
|
|
|
rtp_stream_set_active(impl->stream, true);
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-04 22:48:26 +02:00
|
|
|
static void
|
|
|
|
|
on_rtp_io(void *data, int fd, uint32_t mask)
|
|
|
|
|
{
|
2023-02-21 17:18:52 +01:00
|
|
|
struct impl *impl = data;
|
2023-03-02 19:55:44 +01:00
|
|
|
ssize_t len;
|
2025-07-02 18:31:17 +02:00
|
|
|
int suppressed;
|
2022-10-04 22:48:26 +02:00
|
|
|
|
|
|
|
|
if (mask & SPA_IO_IN) {
|
2024-11-11 12:03:32 +01:00
|
|
|
if ((len = recv(fd, impl->buffer, impl->buffer_size, 0)) < 0)
|
2022-10-05 13:04:08 +02:00
|
|
|
goto receive_error;
|
|
|
|
|
|
2022-10-04 22:48:26 +02:00
|
|
|
if (len < 12)
|
2022-10-05 13:04:08 +02:00
|
|
|
goto short_packet;
|
2022-10-04 22:48:26 +02:00
|
|
|
|
2024-06-20 10:57:33 -04:00
|
|
|
if (SPA_LIKELY(impl->stream)) {
|
2024-11-11 12:03:32 +01:00
|
|
|
if (rtp_stream_receive_packet(impl->stream, impl->buffer, len) < 0)
|
2024-06-20 10:57:33 -04:00
|
|
|
goto receive_error;
|
|
|
|
|
}
|
2022-10-05 13:04:08 +02:00
|
|
|
|
2025-01-20 16:44:57 +01:00
|
|
|
if (!impl->receiving) {
|
|
|
|
|
impl->receiving = true;
|
2025-05-30 11:59:35 +02:00
|
|
|
pw_loop_invoke(impl->main_loop, do_start, 1, NULL, 0, false, impl);
|
2025-01-20 16:44:57 +01:00
|
|
|
}
|
2022-10-04 22:48:26 +02:00
|
|
|
}
|
2022-10-05 13:04:08 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
receive_error:
|
2025-07-02 18:31:17 +02:00
|
|
|
if ((suppressed = spa_ratelimit_test(&impl->rate_limit, get_time_ns())) >= 0)
|
|
|
|
|
pw_log_warn("(%d suppressed) recv() error: %m", suppressed);
|
2022-10-05 13:04:08 +02:00
|
|
|
return;
|
|
|
|
|
short_packet:
|
2025-07-02 18:31:17 +02:00
|
|
|
if ((suppressed = spa_ratelimit_test(&impl->rate_limit, get_time_ns())) >= 0)
|
|
|
|
|
pw_log_warn("(%d suppressed) short packet of len %zd received",
|
|
|
|
|
suppressed, len);
|
2022-10-05 13:04:08 +02:00
|
|
|
return;
|
2022-10-04 22:48:26 +02:00
|
|
|
}
|
|
|
|
|
|
2022-10-07 10:33:24 +02:00
|
|
|
static int make_socket(const struct sockaddr* sa, socklen_t salen, char *ifname)
|
2022-10-04 22:48:26 +02:00
|
|
|
{
|
|
|
|
|
int af, fd, val, res;
|
2022-10-05 13:04:08 +02:00
|
|
|
struct ifreq req;
|
2024-01-15 18:17:23 +01:00
|
|
|
struct sockaddr_storage ba = *(struct sockaddr_storage *)sa;
|
|
|
|
|
bool do_connect = false;
|
2024-02-16 09:53:24 +01:00
|
|
|
char addr[128];
|
2022-10-04 22:48:26 +02:00
|
|
|
|
|
|
|
|
af = sa->sa_family;
|
|
|
|
|
if ((fd = socket(af, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) {
|
2024-03-25 13:21:42 +01:00
|
|
|
res = -errno;
|
2022-10-04 22:48:26 +02:00
|
|
|
pw_log_error("socket failed: %m");
|
2024-03-25 13:21:42 +01:00
|
|
|
return res;
|
2022-10-04 22:48:26 +02:00
|
|
|
}
|
|
|
|
|
#ifdef SO_TIMESTAMP
|
|
|
|
|
val = 1;
|
|
|
|
|
if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &val, sizeof(val)) < 0) {
|
|
|
|
|
res = -errno;
|
|
|
|
|
pw_log_error("setsockopt failed: %m");
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
val = 1;
|
|
|
|
|
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) {
|
|
|
|
|
res = -errno;
|
|
|
|
|
pw_log_error("setsockopt failed: %m");
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
2022-10-05 13:04:08 +02:00
|
|
|
|
|
|
|
|
spa_zero(req);
|
|
|
|
|
if (ifname) {
|
|
|
|
|
snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", ifname);
|
|
|
|
|
res = ioctl(fd, SIOCGIFINDEX, &req);
|
|
|
|
|
if (res < 0)
|
|
|
|
|
pw_log_warn("SIOCGIFINDEX %s failed: %m", ifname);
|
|
|
|
|
}
|
2022-10-04 22:48:26 +02:00
|
|
|
res = 0;
|
|
|
|
|
if (af == AF_INET) {
|
|
|
|
|
static const uint32_t ipv4_mcast_mask = 0xe0000000;
|
2022-10-07 10:33:24 +02:00
|
|
|
struct sockaddr_in *sa4 = (struct sockaddr_in*)sa;
|
2022-10-04 22:48:26 +02:00
|
|
|
if ((ntohl(sa4->sin_addr.s_addr) & ipv4_mcast_mask) == ipv4_mcast_mask) {
|
2022-10-05 13:04:08 +02:00
|
|
|
struct ip_mreqn mr4;
|
2022-10-04 22:48:26 +02:00
|
|
|
memset(&mr4, 0, sizeof(mr4));
|
|
|
|
|
mr4.imr_multiaddr = sa4->sin_addr;
|
2022-10-05 13:04:08 +02:00
|
|
|
mr4.imr_ifindex = req.ifr_ifindex;
|
2024-02-26 15:17:48 +01:00
|
|
|
pw_net_get_ip((struct sockaddr_storage*)sa, addr, sizeof(addr), NULL, NULL);
|
2024-02-16 12:37:14 +01:00
|
|
|
pw_log_info("join IPv4 group: %s", addr);
|
2022-10-04 22:48:26 +02:00
|
|
|
res = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mr4, sizeof(mr4));
|
2022-10-07 10:33:24 +02:00
|
|
|
} else {
|
2024-01-15 18:17:23 +01:00
|
|
|
struct sockaddr_in *ba4 = (struct sockaddr_in*)&ba;
|
|
|
|
|
if (ba4->sin_addr.s_addr != INADDR_ANY) {
|
|
|
|
|
ba4->sin_addr.s_addr = INADDR_ANY;
|
|
|
|
|
do_connect = true;
|
|
|
|
|
}
|
2022-10-04 22:48:26 +02:00
|
|
|
}
|
|
|
|
|
} else if (af == AF_INET6) {
|
2022-10-07 10:33:24 +02:00
|
|
|
struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)sa;
|
2022-10-04 22:48:26 +02:00
|
|
|
if (sa6->sin6_addr.s6_addr[0] == 0xff) {
|
|
|
|
|
struct ipv6_mreq mr6;
|
|
|
|
|
memset(&mr6, 0, sizeof(mr6));
|
|
|
|
|
mr6.ipv6mr_multiaddr = sa6->sin6_addr;
|
2022-10-05 13:04:08 +02:00
|
|
|
mr6.ipv6mr_interface = req.ifr_ifindex;
|
2024-02-26 15:17:48 +01:00
|
|
|
pw_net_get_ip((struct sockaddr_storage*)sa, addr, sizeof(addr), NULL, NULL);
|
2024-02-16 12:37:14 +01:00
|
|
|
pw_log_info("join IPv6 group: %s", addr);
|
2022-10-04 22:48:26 +02:00
|
|
|
res = setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mr6, sizeof(mr6));
|
2022-10-07 10:33:24 +02:00
|
|
|
} else {
|
2024-01-15 18:17:23 +01:00
|
|
|
struct sockaddr_in6 *ba6 = (struct sockaddr_in6*)&ba;
|
|
|
|
|
ba6->sin6_addr = in6addr_any;
|
2022-10-04 22:48:26 +02:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
res = -EINVAL;
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (res < 0) {
|
|
|
|
|
res = -errno;
|
|
|
|
|
pw_log_error("join mcast failed: %m");
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-15 18:17:23 +01:00
|
|
|
if (bind(fd, (struct sockaddr*)&ba, salen) < 0) {
|
2022-10-04 22:48:26 +02:00
|
|
|
res = -errno;
|
2022-10-07 10:33:24 +02:00
|
|
|
pw_log_error("bind() failed: %m");
|
2022-10-04 22:48:26 +02:00
|
|
|
goto error;
|
|
|
|
|
}
|
2024-01-15 18:17:23 +01:00
|
|
|
if (do_connect) {
|
|
|
|
|
if (connect(fd, sa, salen) < 0) {
|
|
|
|
|
res = -errno;
|
|
|
|
|
pw_log_error("connect() failed: %m");
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-10-04 22:48:26 +02:00
|
|
|
return fd;
|
|
|
|
|
error:
|
2023-03-16 12:28:51 +01:00
|
|
|
close(fd);
|
2022-10-04 22:48:26 +02:00
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
module-rtp: Replace state_changed callbacks
The state_changed callbacks fulfill multiple roles, which is both a problem
regarding separation of concerns and regarding code clarity. De facto,
these callbacks cover error reporting, opening connections, and closing
connection, all in one, depending on a state that is arguably an internal
stream detail. The code in these callbacks tie these internal states to
assumptions that opening/closing callbacks is directly tied to specific
state changes in a common way, which is not always true. For example,
stopping the stream may not _actually_ stop it if a background send timer
is still running.
The notion of a "state_changed" callback is also problematic because the
pw_streams that are used in rtp-sink and rtp-source also have a callback
for state changes, causing confusion.
Solve this by replacing state_changed with three new callbacks:
1. report_error : Used for reporting nonrecoverable errors to the caller.
Note that currently, no one does such error reporting, but the feature
does exist, so this callback is introduced to preserve said feature.
2. open_connection : Used for opening a connection. Its optional return
value informs about success or failure.
3. close_connection : Used for opening a connection. Its optional return
value informs about success or failure.
Importantly, these callbacks do not export any internal stream state. This
improves encapsulation, and also makes it possible to invoke these
callbacks in situations that may not neatly map to a state change. One
example could be to close the connection as part of a stream_start call
to close any connection(s) left over from a previous run. (Followup commits
will in fact introduce such measures.)
2025-08-21 13:30:11 +02:00
|
|
|
static void stream_open_connection(void *data, int *result);
|
2025-05-20 12:57:13 +02:00
|
|
|
|
module-rtp: Replace state_changed callbacks
The state_changed callbacks fulfill multiple roles, which is both a problem
regarding separation of concerns and regarding code clarity. De facto,
these callbacks cover error reporting, opening connections, and closing
connection, all in one, depending on a state that is arguably an internal
stream detail. The code in these callbacks tie these internal states to
assumptions that opening/closing callbacks is directly tied to specific
state changes in a common way, which is not always true. For example,
stopping the stream may not _actually_ stop it if a background send timer
is still running.
The notion of a "state_changed" callback is also problematic because the
pw_streams that are used in rtp-sink and rtp-source also have a callback
for state changes, causing confusion.
Solve this by replacing state_changed with three new callbacks:
1. report_error : Used for reporting nonrecoverable errors to the caller.
Note that currently, no one does such error reporting, but the feature
does exist, so this callback is introduced to preserve said feature.
2. open_connection : Used for opening a connection. Its optional return
value informs about success or failure.
3. close_connection : Used for opening a connection. Its optional return
value informs about success or failure.
Importantly, these callbacks do not export any internal stream state. This
improves encapsulation, and also makes it possible to invoke these
callbacks in situations that may not neatly map to a state change. One
example could be to close the connection as part of a stream_start call
to close any connection(s) left over from a previous run. (Followup commits
will in fact introduce such measures.)
2025-08-21 13:30:11 +02:00
|
|
|
static void on_open_connection_retry_timer_event(void *data, uint64_t expirations)
|
2025-05-20 12:57:13 +02:00
|
|
|
{
|
|
|
|
|
struct impl *impl = data;
|
module-rtp: Replace state_changed callbacks
The state_changed callbacks fulfill multiple roles, which is both a problem
regarding separation of concerns and regarding code clarity. De facto,
these callbacks cover error reporting, opening connections, and closing
connection, all in one, depending on a state that is arguably an internal
stream detail. The code in these callbacks tie these internal states to
assumptions that opening/closing callbacks is directly tied to specific
state changes in a common way, which is not always true. For example,
stopping the stream may not _actually_ stop it if a background send timer
is still running.
The notion of a "state_changed" callback is also problematic because the
pw_streams that are used in rtp-sink and rtp-source also have a callback
for state changes, causing confusion.
Solve this by replacing state_changed with three new callbacks:
1. report_error : Used for reporting nonrecoverable errors to the caller.
Note that currently, no one does such error reporting, but the feature
does exist, so this callback is introduced to preserve said feature.
2. open_connection : Used for opening a connection. Its optional return
value informs about success or failure.
3. close_connection : Used for opening a connection. Its optional return
value informs about success or failure.
Importantly, these callbacks do not export any internal stream state. This
improves encapsulation, and also makes it possible to invoke these
callbacks in situations that may not neatly map to a state change. One
example could be to close the connection as part of a stream_start call
to close any connection(s) left over from a previous run. (Followup commits
will in fact introduce such measures.)
2025-08-21 13:30:11 +02:00
|
|
|
pw_log_debug("trying again to open connection after previous attempt failed with ENODEV");
|
|
|
|
|
stream_open_connection(impl, NULL);
|
2025-05-20 12:57:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void destroy_stream_start_retry_timer(struct impl *impl)
|
|
|
|
|
{
|
|
|
|
|
if (impl->stream_start_retry_timer != NULL) {
|
2025-05-30 11:59:35 +02:00
|
|
|
pw_loop_destroy_source(impl->main_loop, impl->stream_start_retry_timer);
|
2025-05-20 12:57:13 +02:00
|
|
|
impl->stream_start_retry_timer = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
module-rtp: Replace state_changed callbacks
The state_changed callbacks fulfill multiple roles, which is both a problem
regarding separation of concerns and regarding code clarity. De facto,
these callbacks cover error reporting, opening connections, and closing
connection, all in one, depending on a state that is arguably an internal
stream detail. The code in these callbacks tie these internal states to
assumptions that opening/closing callbacks is directly tied to specific
state changes in a common way, which is not always true. For example,
stopping the stream may not _actually_ stop it if a background send timer
is still running.
The notion of a "state_changed" callback is also problematic because the
pw_streams that are used in rtp-sink and rtp-source also have a callback
for state changes, causing confusion.
Solve this by replacing state_changed with three new callbacks:
1. report_error : Used for reporting nonrecoverable errors to the caller.
Note that currently, no one does such error reporting, but the feature
does exist, so this callback is introduced to preserve said feature.
2. open_connection : Used for opening a connection. Its optional return
value informs about success or failure.
3. close_connection : Used for opening a connection. Its optional return
value informs about success or failure.
Importantly, these callbacks do not export any internal stream state. This
improves encapsulation, and also makes it possible to invoke these
callbacks in situations that may not neatly map to a state change. One
example could be to close the connection as part of a stream_start call
to close any connection(s) left over from a previous run. (Followup commits
will in fact introduce such measures.)
2025-08-21 13:30:11 +02:00
|
|
|
static void stream_report_error(void *data, const char *error)
|
2022-10-07 10:33:24 +02:00
|
|
|
{
|
module-rtp: Replace state_changed callbacks
The state_changed callbacks fulfill multiple roles, which is both a problem
regarding separation of concerns and regarding code clarity. De facto,
these callbacks cover error reporting, opening connections, and closing
connection, all in one, depending on a state that is arguably an internal
stream detail. The code in these callbacks tie these internal states to
assumptions that opening/closing callbacks is directly tied to specific
state changes in a common way, which is not always true. For example,
stopping the stream may not _actually_ stop it if a background send timer
is still running.
The notion of a "state_changed" callback is also problematic because the
pw_streams that are used in rtp-sink and rtp-source also have a callback
for state changes, causing confusion.
Solve this by replacing state_changed with three new callbacks:
1. report_error : Used for reporting nonrecoverable errors to the caller.
Note that currently, no one does such error reporting, but the feature
does exist, so this callback is introduced to preserve said feature.
2. open_connection : Used for opening a connection. Its optional return
value informs about success or failure.
3. close_connection : Used for opening a connection. Its optional return
value informs about success or failure.
Importantly, these callbacks do not export any internal stream state. This
improves encapsulation, and also makes it possible to invoke these
callbacks in situations that may not neatly map to a state change. One
example could be to close the connection as part of a stream_start call
to close any connection(s) left over from a previous run. (Followup commits
will in fact introduce such measures.)
2025-08-21 13:30:11 +02:00
|
|
|
struct impl *impl = data;
|
|
|
|
|
if (error) {
|
|
|
|
|
pw_log_error("stream error: %s", error);
|
|
|
|
|
pw_impl_module_schedule_destroy(impl->module);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void stream_open_connection(void *data, int *result)
|
|
|
|
|
{
|
|
|
|
|
int res = 0;
|
2023-01-25 19:03:17 +03:00
|
|
|
int fd;
|
module-rtp: Replace state_changed callbacks
The state_changed callbacks fulfill multiple roles, which is both a problem
regarding separation of concerns and regarding code clarity. De facto,
these callbacks cover error reporting, opening connections, and closing
connection, all in one, depending on a state that is arguably an internal
stream detail. The code in these callbacks tie these internal states to
assumptions that opening/closing callbacks is directly tied to specific
state changes in a common way, which is not always true. For example,
stopping the stream may not _actually_ stop it if a background send timer
is still running.
The notion of a "state_changed" callback is also problematic because the
pw_streams that are used in rtp-sink and rtp-source also have a callback
for state changes, causing confusion.
Solve this by replacing state_changed with three new callbacks:
1. report_error : Used for reporting nonrecoverable errors to the caller.
Note that currently, no one does such error reporting, but the feature
does exist, so this callback is introduced to preserve said feature.
2. open_connection : Used for opening a connection. Its optional return
value informs about success or failure.
3. close_connection : Used for opening a connection. Its optional return
value informs about success or failure.
Importantly, these callbacks do not export any internal stream state. This
improves encapsulation, and also makes it possible to invoke these
callbacks in situations that may not neatly map to a state change. One
example could be to close the connection as part of a stream_start call
to close any connection(s) left over from a previous run. (Followup commits
will in fact introduce such measures.)
2025-08-21 13:30:11 +02:00
|
|
|
struct impl *impl = data;
|
2023-02-21 17:18:52 +01:00
|
|
|
|
|
|
|
|
if (impl->source != NULL)
|
module-rtp: Replace state_changed callbacks
The state_changed callbacks fulfill multiple roles, which is both a problem
regarding separation of concerns and regarding code clarity. De facto,
these callbacks cover error reporting, opening connections, and closing
connection, all in one, depending on a state that is arguably an internal
stream detail. The code in these callbacks tie these internal states to
assumptions that opening/closing callbacks is directly tied to specific
state changes in a common way, which is not always true. For example,
stopping the stream may not _actually_ stop it if a background send timer
is still running.
The notion of a "state_changed" callback is also problematic because the
pw_streams that are used in rtp-sink and rtp-source also have a callback
for state changes, causing confusion.
Solve this by replacing state_changed with three new callbacks:
1. report_error : Used for reporting nonrecoverable errors to the caller.
Note that currently, no one does such error reporting, but the feature
does exist, so this callback is introduced to preserve said feature.
2. open_connection : Used for opening a connection. Its optional return
value informs about success or failure.
3. close_connection : Used for opening a connection. Its optional return
value informs about success or failure.
Importantly, these callbacks do not export any internal stream state. This
improves encapsulation, and also makes it possible to invoke these
callbacks in situations that may not neatly map to a state change. One
example could be to close the connection as part of a stream_start call
to close any connection(s) left over from a previous run. (Followup commits
will in fact introduce such measures.)
2025-08-21 13:30:11 +02:00
|
|
|
goto finish;
|
2023-01-25 19:03:17 +03:00
|
|
|
|
|
|
|
|
pw_log_info("starting RTP listener");
|
|
|
|
|
|
2023-02-21 17:18:52 +01:00
|
|
|
if ((fd = make_socket((const struct sockaddr *)&impl->src_addr,
|
|
|
|
|
impl->src_len, impl->ifname)) < 0) {
|
2025-05-20 12:57:13 +02:00
|
|
|
/* If make_socket() tries to create a socket and join to a multicast
|
|
|
|
|
* group while the network interfaces are not ready yet to do so
|
|
|
|
|
* (usually because a network manager component is still setting up
|
|
|
|
|
* those network interfaces), ENODEV will be returned. This is essentially
|
|
|
|
|
* a race condition. There is no discernible way to be notified when the
|
|
|
|
|
* network interfaces are ready for that operation, so the next best
|
|
|
|
|
* approach is to essentially do a form of polling by retrying the
|
|
|
|
|
* stream_start() call after some time. The stream_start_retry_timer exists
|
|
|
|
|
* precisely for that purpose. This means that ENODEV is not treated as
|
|
|
|
|
* an error, but instead, it triggers the creation of that timer. */
|
|
|
|
|
if (errno == ENODEV) {
|
|
|
|
|
pw_log_warn("failed to create socket because network device is not ready "
|
|
|
|
|
"and present yet; will try again");
|
|
|
|
|
|
|
|
|
|
if (impl->stream_start_retry_timer == NULL) {
|
|
|
|
|
struct timespec value, interval;
|
|
|
|
|
|
2025-05-30 11:59:35 +02:00
|
|
|
impl->stream_start_retry_timer = pw_loop_add_timer(impl->main_loop,
|
module-rtp: Replace state_changed callbacks
The state_changed callbacks fulfill multiple roles, which is both a problem
regarding separation of concerns and regarding code clarity. De facto,
these callbacks cover error reporting, opening connections, and closing
connection, all in one, depending on a state that is arguably an internal
stream detail. The code in these callbacks tie these internal states to
assumptions that opening/closing callbacks is directly tied to specific
state changes in a common way, which is not always true. For example,
stopping the stream may not _actually_ stop it if a background send timer
is still running.
The notion of a "state_changed" callback is also problematic because the
pw_streams that are used in rtp-sink and rtp-source also have a callback
for state changes, causing confusion.
Solve this by replacing state_changed with three new callbacks:
1. report_error : Used for reporting nonrecoverable errors to the caller.
Note that currently, no one does such error reporting, but the feature
does exist, so this callback is introduced to preserve said feature.
2. open_connection : Used for opening a connection. Its optional return
value informs about success or failure.
3. close_connection : Used for opening a connection. Its optional return
value informs about success or failure.
Importantly, these callbacks do not export any internal stream state. This
improves encapsulation, and also makes it possible to invoke these
callbacks in situations that may not neatly map to a state change. One
example could be to close the connection as part of a stream_start call
to close any connection(s) left over from a previous run. (Followup commits
will in fact introduce such measures.)
2025-08-21 13:30:11 +02:00
|
|
|
on_open_connection_retry_timer_event, impl);
|
2025-05-20 12:57:13 +02:00
|
|
|
/* Use a 1-second retry interval. The network interfaces
|
|
|
|
|
* are likely to be up and running then. */
|
|
|
|
|
value.tv_sec = 1;
|
|
|
|
|
value.tv_nsec = 0;
|
|
|
|
|
interval.tv_sec = 1;
|
|
|
|
|
interval.tv_nsec = 0;
|
2025-05-30 11:59:35 +02:00
|
|
|
pw_loop_update_timer(impl->main_loop, impl->stream_start_retry_timer, &value,
|
2025-05-20 12:57:13 +02:00
|
|
|
&interval, false);
|
|
|
|
|
}
|
|
|
|
|
/* Do nothing if the timer is already up. */
|
|
|
|
|
|
|
|
|
|
/* It is important to return 0 in this case. Otherwise, the nonzero return
|
|
|
|
|
* value will later be propagated through the core as an error. */
|
module-rtp: Replace state_changed callbacks
The state_changed callbacks fulfill multiple roles, which is both a problem
regarding separation of concerns and regarding code clarity. De facto,
these callbacks cover error reporting, opening connections, and closing
connection, all in one, depending on a state that is arguably an internal
stream detail. The code in these callbacks tie these internal states to
assumptions that opening/closing callbacks is directly tied to specific
state changes in a common way, which is not always true. For example,
stopping the stream may not _actually_ stop it if a background send timer
is still running.
The notion of a "state_changed" callback is also problematic because the
pw_streams that are used in rtp-sink and rtp-source also have a callback
for state changes, causing confusion.
Solve this by replacing state_changed with three new callbacks:
1. report_error : Used for reporting nonrecoverable errors to the caller.
Note that currently, no one does such error reporting, but the feature
does exist, so this callback is introduced to preserve said feature.
2. open_connection : Used for opening a connection. Its optional return
value informs about success or failure.
3. close_connection : Used for opening a connection. Its optional return
value informs about success or failure.
Importantly, these callbacks do not export any internal stream state. This
improves encapsulation, and also makes it possible to invoke these
callbacks in situations that may not neatly map to a state change. One
example could be to close the connection as part of a stream_start call
to close any connection(s) left over from a previous run. (Followup commits
will in fact introduce such measures.)
2025-08-21 13:30:11 +02:00
|
|
|
res = 0;
|
|
|
|
|
goto finish;
|
2025-05-20 12:57:13 +02:00
|
|
|
} else {
|
|
|
|
|
pw_log_error("failed to create socket: %m");
|
|
|
|
|
/* If ENODEV was returned earlier, and the stream_start_retry_timer
|
|
|
|
|
* was consequently created, but then a non-ENODEV error occurred,
|
|
|
|
|
* the timer must be stopped and removed. */
|
|
|
|
|
destroy_stream_start_retry_timer(impl);
|
module-rtp: Replace state_changed callbacks
The state_changed callbacks fulfill multiple roles, which is both a problem
regarding separation of concerns and regarding code clarity. De facto,
these callbacks cover error reporting, opening connections, and closing
connection, all in one, depending on a state that is arguably an internal
stream detail. The code in these callbacks tie these internal states to
assumptions that opening/closing callbacks is directly tied to specific
state changes in a common way, which is not always true. For example,
stopping the stream may not _actually_ stop it if a background send timer
is still running.
The notion of a "state_changed" callback is also problematic because the
pw_streams that are used in rtp-sink and rtp-source also have a callback
for state changes, causing confusion.
Solve this by replacing state_changed with three new callbacks:
1. report_error : Used for reporting nonrecoverable errors to the caller.
Note that currently, no one does such error reporting, but the feature
does exist, so this callback is introduced to preserve said feature.
2. open_connection : Used for opening a connection. Its optional return
value informs about success or failure.
3. close_connection : Used for opening a connection. Its optional return
value informs about success or failure.
Importantly, these callbacks do not export any internal stream state. This
improves encapsulation, and also makes it possible to invoke these
callbacks in situations that may not neatly map to a state change. One
example could be to close the connection as part of a stream_start call
to close any connection(s) left over from a previous run. (Followup commits
will in fact introduce such measures.)
2025-08-21 13:30:11 +02:00
|
|
|
res = -errno;
|
|
|
|
|
goto finish;
|
2025-05-20 12:57:13 +02:00
|
|
|
}
|
2023-01-25 19:03:17 +03:00
|
|
|
}
|
|
|
|
|
|
2025-05-20 12:57:13 +02:00
|
|
|
/* Cleanup the timer in case ENODEV occurred earlier, and this time,
|
|
|
|
|
* the socket creation succeeded. */
|
|
|
|
|
destroy_stream_start_retry_timer(impl);
|
|
|
|
|
|
2023-02-21 17:18:52 +01:00
|
|
|
impl->source = pw_loop_add_io(impl->data_loop, fd,
|
|
|
|
|
SPA_IO_IN, true, on_rtp_io, impl);
|
|
|
|
|
if (impl->source == NULL) {
|
2023-01-25 19:03:17 +03:00
|
|
|
pw_log_error("can't create io source: %m");
|
|
|
|
|
close(fd);
|
module-rtp: Replace state_changed callbacks
The state_changed callbacks fulfill multiple roles, which is both a problem
regarding separation of concerns and regarding code clarity. De facto,
these callbacks cover error reporting, opening connections, and closing
connection, all in one, depending on a state that is arguably an internal
stream detail. The code in these callbacks tie these internal states to
assumptions that opening/closing callbacks is directly tied to specific
state changes in a common way, which is not always true. For example,
stopping the stream may not _actually_ stop it if a background send timer
is still running.
The notion of a "state_changed" callback is also problematic because the
pw_streams that are used in rtp-sink and rtp-source also have a callback
for state changes, causing confusion.
Solve this by replacing state_changed with three new callbacks:
1. report_error : Used for reporting nonrecoverable errors to the caller.
Note that currently, no one does such error reporting, but the feature
does exist, so this callback is introduced to preserve said feature.
2. open_connection : Used for opening a connection. Its optional return
value informs about success or failure.
3. close_connection : Used for opening a connection. Its optional return
value informs about success or failure.
Importantly, these callbacks do not export any internal stream state. This
improves encapsulation, and also makes it possible to invoke these
callbacks in situations that may not neatly map to a state change. One
example could be to close the connection as part of a stream_start call
to close any connection(s) left over from a previous run. (Followup commits
will in fact introduce such measures.)
2025-08-21 13:30:11 +02:00
|
|
|
res = -errno;
|
|
|
|
|
goto finish;
|
2023-01-25 19:03:17 +03:00
|
|
|
}
|
module-rtp: Replace state_changed callbacks
The state_changed callbacks fulfill multiple roles, which is both a problem
regarding separation of concerns and regarding code clarity. De facto,
these callbacks cover error reporting, opening connections, and closing
connection, all in one, depending on a state that is arguably an internal
stream detail. The code in these callbacks tie these internal states to
assumptions that opening/closing callbacks is directly tied to specific
state changes in a common way, which is not always true. For example,
stopping the stream may not _actually_ stop it if a background send timer
is still running.
The notion of a "state_changed" callback is also problematic because the
pw_streams that are used in rtp-sink and rtp-source also have a callback
for state changes, causing confusion.
Solve this by replacing state_changed with three new callbacks:
1. report_error : Used for reporting nonrecoverable errors to the caller.
Note that currently, no one does such error reporting, but the feature
does exist, so this callback is introduced to preserve said feature.
2. open_connection : Used for opening a connection. Its optional return
value informs about success or failure.
3. close_connection : Used for opening a connection. Its optional return
value informs about success or failure.
Importantly, these callbacks do not export any internal stream state. This
improves encapsulation, and also makes it possible to invoke these
callbacks in situations that may not neatly map to a state change. One
example could be to close the connection as part of a stream_start call
to close any connection(s) left over from a previous run. (Followup commits
will in fact introduce such measures.)
2025-08-21 13:30:11 +02:00
|
|
|
|
|
|
|
|
finish:
|
|
|
|
|
if (res != 0) {
|
|
|
|
|
pw_log_error("failed to start RTP stream: %s", spa_strerror(res));
|
|
|
|
|
rtp_stream_set_error(impl->stream, res, "Can't start RTP stream");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (result)
|
|
|
|
|
*result = res;
|
2023-01-25 19:03:17 +03:00
|
|
|
}
|
|
|
|
|
|
module-rtp: Replace state_changed callbacks
The state_changed callbacks fulfill multiple roles, which is both a problem
regarding separation of concerns and regarding code clarity. De facto,
these callbacks cover error reporting, opening connections, and closing
connection, all in one, depending on a state that is arguably an internal
stream detail. The code in these callbacks tie these internal states to
assumptions that opening/closing callbacks is directly tied to specific
state changes in a common way, which is not always true. For example,
stopping the stream may not _actually_ stop it if a background send timer
is still running.
The notion of a "state_changed" callback is also problematic because the
pw_streams that are used in rtp-sink and rtp-source also have a callback
for state changes, causing confusion.
Solve this by replacing state_changed with three new callbacks:
1. report_error : Used for reporting nonrecoverable errors to the caller.
Note that currently, no one does such error reporting, but the feature
does exist, so this callback is introduced to preserve said feature.
2. open_connection : Used for opening a connection. Its optional return
value informs about success or failure.
3. close_connection : Used for opening a connection. Its optional return
value informs about success or failure.
Importantly, these callbacks do not export any internal stream state. This
improves encapsulation, and also makes it possible to invoke these
callbacks in situations that may not neatly map to a state change. One
example could be to close the connection as part of a stream_start call
to close any connection(s) left over from a previous run. (Followup commits
will in fact introduce such measures.)
2025-08-21 13:30:11 +02:00
|
|
|
static void stream_close_connection(void *data, int *result)
|
2023-02-21 17:18:52 +01:00
|
|
|
{
|
module-rtp: Replace state_changed callbacks
The state_changed callbacks fulfill multiple roles, which is both a problem
regarding separation of concerns and regarding code clarity. De facto,
these callbacks cover error reporting, opening connections, and closing
connection, all in one, depending on a state that is arguably an internal
stream detail. The code in these callbacks tie these internal states to
assumptions that opening/closing callbacks is directly tied to specific
state changes in a common way, which is not always true. For example,
stopping the stream may not _actually_ stop it if a background send timer
is still running.
The notion of a "state_changed" callback is also problematic because the
pw_streams that are used in rtp-sink and rtp-source also have a callback
for state changes, causing confusion.
Solve this by replacing state_changed with three new callbacks:
1. report_error : Used for reporting nonrecoverable errors to the caller.
Note that currently, no one does such error reporting, but the feature
does exist, so this callback is introduced to preserve said feature.
2. open_connection : Used for opening a connection. Its optional return
value informs about success or failure.
3. close_connection : Used for opening a connection. Its optional return
value informs about success or failure.
Importantly, these callbacks do not export any internal stream state. This
improves encapsulation, and also makes it possible to invoke these
callbacks in situations that may not neatly map to a state change. One
example could be to close the connection as part of a stream_start call
to close any connection(s) left over from a previous run. (Followup commits
will in fact introduce such measures.)
2025-08-21 13:30:11 +02:00
|
|
|
struct impl *impl = data;
|
|
|
|
|
|
|
|
|
|
if (result)
|
|
|
|
|
*result = 0;
|
|
|
|
|
|
2023-02-21 17:18:52 +01:00
|
|
|
if (!impl->source)
|
2023-01-25 19:03:17 +03:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
pw_log_info("stopping RTP listener");
|
|
|
|
|
|
2025-05-20 12:57:13 +02:00
|
|
|
destroy_stream_start_retry_timer(impl);
|
|
|
|
|
|
2023-02-21 17:18:52 +01:00
|
|
|
pw_loop_destroy_source(impl->data_loop, impl->source);
|
|
|
|
|
impl->source = NULL;
|
2023-01-25 19:03:17 +03:00
|
|
|
}
|
|
|
|
|
|
2023-03-02 19:55:44 +01:00
|
|
|
static void stream_destroy(void *d)
|
2023-01-25 19:03:17 +03:00
|
|
|
{
|
2023-02-21 17:18:52 +01:00
|
|
|
struct impl *impl = d;
|
2023-03-02 19:55:44 +01:00
|
|
|
impl->stream = NULL;
|
2023-01-25 19:03:17 +03:00
|
|
|
}
|
|
|
|
|
|
2024-02-06 15:04:43 +01:00
|
|
|
static void stream_props_changed(struct impl *impl, uint32_t id, const struct spa_pod *param)
|
|
|
|
|
{
|
|
|
|
|
struct spa_pod_object *obj = (struct spa_pod_object *)param;
|
|
|
|
|
struct spa_pod_prop *prop;
|
|
|
|
|
|
|
|
|
|
if (param == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
SPA_POD_OBJECT_FOREACH(obj, prop) {
|
|
|
|
|
if (prop->key == SPA_PROP_params) {
|
|
|
|
|
struct spa_pod *params = NULL;
|
|
|
|
|
struct spa_pod_parser prs;
|
|
|
|
|
struct spa_pod_frame f;
|
|
|
|
|
const char *key;
|
|
|
|
|
struct spa_pod *pod;
|
|
|
|
|
const char *value;
|
|
|
|
|
|
|
|
|
|
if (spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_params,
|
|
|
|
|
SPA_POD_OPT_Pod(¶ms)) < 0)
|
|
|
|
|
return;
|
|
|
|
|
spa_pod_parser_pod(&prs, params);
|
|
|
|
|
if (spa_pod_parser_push_struct(&prs, &f) < 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
if (spa_pod_parser_get_string(&prs, &key) < 0)
|
|
|
|
|
break;
|
|
|
|
|
if (spa_pod_parser_get_pod(&prs, &pod) < 0)
|
|
|
|
|
break;
|
|
|
|
|
if (spa_pod_get_string(pod, &value) < 0)
|
|
|
|
|
continue;
|
|
|
|
|
pw_log_info("key '%s', value '%s'", key, value);
|
|
|
|
|
if (!spa_streq(key, "source.ip"))
|
|
|
|
|
continue;
|
2024-02-26 15:17:48 +01:00
|
|
|
if (pw_net_parse_address(value, impl->src_port, &impl->src_addr,
|
2024-02-06 15:04:43 +01:00
|
|
|
&impl->src_len) < 0) {
|
|
|
|
|
pw_log_error("invalid source.ip: '%s'", value);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
pw_properties_set(impl->stream_props, "rtp.source.ip", value);
|
|
|
|
|
struct spa_dict_item item[1];
|
|
|
|
|
item[0] = SPA_DICT_ITEM_INIT("rtp.source.ip", value);
|
|
|
|
|
rtp_stream_update_properties(impl->stream, &SPA_DICT_INIT(item, 1));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void stream_param_changed(void *data, uint32_t id, const struct spa_pod *param)
|
|
|
|
|
{
|
|
|
|
|
struct impl *impl = data;
|
|
|
|
|
|
|
|
|
|
switch (id) {
|
|
|
|
|
case SPA_PARAM_Props:
|
|
|
|
|
if (param != NULL)
|
|
|
|
|
stream_props_changed(impl, id, param);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-02 19:55:44 +01:00
|
|
|
static const struct rtp_stream_events stream_events = {
|
|
|
|
|
RTP_VERSION_STREAM_EVENTS,
|
|
|
|
|
.destroy = stream_destroy,
|
module-rtp: Replace state_changed callbacks
The state_changed callbacks fulfill multiple roles, which is both a problem
regarding separation of concerns and regarding code clarity. De facto,
these callbacks cover error reporting, opening connections, and closing
connection, all in one, depending on a state that is arguably an internal
stream detail. The code in these callbacks tie these internal states to
assumptions that opening/closing callbacks is directly tied to specific
state changes in a common way, which is not always true. For example,
stopping the stream may not _actually_ stop it if a background send timer
is still running.
The notion of a "state_changed" callback is also problematic because the
pw_streams that are used in rtp-sink and rtp-source also have a callback
for state changes, causing confusion.
Solve this by replacing state_changed with three new callbacks:
1. report_error : Used for reporting nonrecoverable errors to the caller.
Note that currently, no one does such error reporting, but the feature
does exist, so this callback is introduced to preserve said feature.
2. open_connection : Used for opening a connection. Its optional return
value informs about success or failure.
3. close_connection : Used for opening a connection. Its optional return
value informs about success or failure.
Importantly, these callbacks do not export any internal stream state. This
improves encapsulation, and also makes it possible to invoke these
callbacks in situations that may not neatly map to a state change. One
example could be to close the connection as part of a stream_start call
to close any connection(s) left over from a previous run. (Followup commits
will in fact introduce such measures.)
2025-08-21 13:30:11 +02:00
|
|
|
.report_error = stream_report_error,
|
|
|
|
|
.open_connection = stream_open_connection,
|
|
|
|
|
.close_connection = stream_close_connection,
|
2024-02-06 15:04:43 +01:00
|
|
|
.param_changed = stream_param_changed,
|
2023-03-02 19:55:44 +01:00
|
|
|
};
|
|
|
|
|
|
2025-05-19 16:14:08 +02:00
|
|
|
static void on_standby_timer_event(void *data, uint64_t expirations)
|
2022-10-05 13:47:28 +02:00
|
|
|
{
|
|
|
|
|
struct impl *impl = data;
|
2023-02-21 17:18:52 +01:00
|
|
|
|
2025-05-19 16:14:08 +02:00
|
|
|
pw_log_debug("standby timer event; receiving: %d standby: %d waiting: %d",
|
|
|
|
|
impl->receiving, impl->standby, impl->waiting);
|
2024-06-19 10:22:08 -04:00
|
|
|
|
2025-01-20 16:44:57 +01:00
|
|
|
if (!impl->receiving) {
|
|
|
|
|
if (!impl->standby) {
|
|
|
|
|
struct spa_dict_item item[1];
|
2024-06-19 10:22:08 -04:00
|
|
|
|
2025-01-20 16:44:57 +01:00
|
|
|
pw_log_info("timeout, standby RTP source");
|
|
|
|
|
impl->standby = true;
|
|
|
|
|
impl->waiting = true;
|
2024-06-19 10:22:08 -04:00
|
|
|
|
2025-01-20 16:44:57 +01:00
|
|
|
item[0] = SPA_DICT_ITEM_INIT("rtp.receiving", "false");
|
|
|
|
|
rtp_stream_update_properties(impl->stream, &SPA_DICT_INIT(item, 1));
|
|
|
|
|
|
|
|
|
|
if (impl->may_pause)
|
|
|
|
|
rtp_stream_set_active(impl->stream, false);
|
|
|
|
|
}
|
2023-02-21 17:42:01 +01:00
|
|
|
//pw_impl_module_schedule_destroy(impl->module);
|
2023-02-21 17:18:52 +01:00
|
|
|
} else {
|
|
|
|
|
pw_log_debug("timeout, keeping active RTP source");
|
2022-10-05 13:47:28 +02:00
|
|
|
}
|
2023-02-21 17:18:52 +01:00
|
|
|
impl->receiving = false;
|
2022-10-05 13:47:28 +02:00
|
|
|
}
|
|
|
|
|
|
2022-10-04 20:44:07 +02:00
|
|
|
static void core_destroy(void *d)
|
|
|
|
|
{
|
|
|
|
|
struct impl *impl = d;
|
|
|
|
|
spa_hook_remove(&impl->core_listener);
|
|
|
|
|
impl->core = NULL;
|
|
|
|
|
pw_impl_module_schedule_destroy(impl->module);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct pw_proxy_events core_proxy_events = {
|
|
|
|
|
.destroy = core_destroy,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void impl_destroy(struct impl *impl)
|
|
|
|
|
{
|
2023-02-21 17:18:52 +01:00
|
|
|
if (impl->stream)
|
2023-03-02 19:55:44 +01:00
|
|
|
rtp_stream_destroy(impl->stream);
|
2023-02-21 17:18:52 +01:00
|
|
|
if (impl->source)
|
|
|
|
|
pw_loop_destroy_source(impl->data_loop, impl->source);
|
2022-10-04 20:44:07 +02:00
|
|
|
|
|
|
|
|
if (impl->core && impl->do_disconnect)
|
|
|
|
|
pw_core_disconnect(impl->core);
|
|
|
|
|
|
2025-05-19 16:14:08 +02:00
|
|
|
if (impl->standby_timer)
|
2025-05-30 11:59:35 +02:00
|
|
|
pw_loop_destroy_source(impl->main_loop, impl->standby_timer);
|
2022-10-05 13:47:28 +02:00
|
|
|
|
2025-05-20 12:57:13 +02:00
|
|
|
destroy_stream_start_retry_timer(impl);
|
|
|
|
|
|
2024-04-22 16:19:02 +02:00
|
|
|
if (impl->data_loop)
|
|
|
|
|
pw_context_release_loop(impl->context, impl->data_loop);
|
|
|
|
|
|
2022-10-05 13:04:08 +02:00
|
|
|
pw_properties_free(impl->stream_props);
|
2022-10-04 20:44:07 +02:00
|
|
|
pw_properties_free(impl->props);
|
|
|
|
|
|
2024-11-11 12:03:32 +01:00
|
|
|
free(impl->buffer);
|
2022-10-05 13:04:08 +02:00
|
|
|
free(impl->ifname);
|
2022-10-04 20:44:07 +02:00
|
|
|
free(impl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void module_destroy(void *d)
|
|
|
|
|
{
|
|
|
|
|
struct impl *impl = d;
|
|
|
|
|
spa_hook_remove(&impl->module_listener);
|
|
|
|
|
impl_destroy(impl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct pw_impl_module_events module_events = {
|
|
|
|
|
PW_VERSION_IMPL_MODULE_EVENTS,
|
|
|
|
|
.destroy = module_destroy,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void on_core_error(void *d, uint32_t id, int seq, int res, const char *message)
|
|
|
|
|
{
|
|
|
|
|
struct impl *impl = d;
|
|
|
|
|
|
|
|
|
|
pw_log_error("error id:%u seq:%d res:%d (%s): %s",
|
|
|
|
|
id, seq, res, spa_strerror(res), message);
|
|
|
|
|
|
|
|
|
|
if (id == PW_ID_CORE && res == -EPIPE)
|
|
|
|
|
pw_impl_module_schedule_destroy(impl->module);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct pw_core_events core_events = {
|
|
|
|
|
PW_VERSION_CORE_EVENTS,
|
|
|
|
|
.error = on_core_error,
|
|
|
|
|
};
|
|
|
|
|
|
2023-02-21 17:18:52 +01:00
|
|
|
static void copy_props(struct impl *impl, struct pw_properties *props, const char *key)
|
|
|
|
|
{
|
|
|
|
|
const char *str;
|
|
|
|
|
if ((str = pw_properties_get(props, key)) != NULL) {
|
|
|
|
|
if (pw_properties_get(impl->stream_props, key) == NULL)
|
|
|
|
|
pw_properties_set(impl->stream_props, key, str);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-04 09:16:17 +02:00
|
|
|
SPA_EXPORT
|
|
|
|
|
int pipewire__module_init(struct pw_impl_module *module, const char *args)
|
|
|
|
|
{
|
|
|
|
|
struct pw_context *context = pw_impl_module_get_context(module);
|
|
|
|
|
struct impl *impl;
|
2023-03-02 19:55:44 +01:00
|
|
|
const char *str, *sess_name;
|
2022-10-05 13:47:28 +02:00
|
|
|
struct timespec value, interval;
|
2023-02-21 17:18:52 +01:00
|
|
|
struct pw_properties *props, *stream_props;
|
2023-03-06 10:46:21 +01:00
|
|
|
int64_t ts_offset;
|
2023-12-11 10:04:14 +01:00
|
|
|
char addr[128];
|
2022-10-04 09:16:17 +02:00
|
|
|
int res = 0;
|
2025-01-24 12:43:34 +01:00
|
|
|
uint32_t header_size;
|
2022-10-04 09:16:17 +02:00
|
|
|
|
|
|
|
|
PW_LOG_TOPIC_INIT(mod_topic);
|
|
|
|
|
|
|
|
|
|
impl = calloc(1, sizeof(struct impl));
|
|
|
|
|
if (impl == NULL)
|
|
|
|
|
return -errno;
|
|
|
|
|
|
|
|
|
|
if (args == NULL)
|
|
|
|
|
args = "";
|
|
|
|
|
|
2023-02-21 17:18:52 +01:00
|
|
|
props = impl->props = pw_properties_new_string(args);
|
|
|
|
|
stream_props = impl->stream_props = pw_properties_new(NULL, NULL);
|
|
|
|
|
if (props == NULL || stream_props == NULL) {
|
2022-10-04 09:16:17 +02:00
|
|
|
res = -errno;
|
|
|
|
|
pw_log_error( "can't create properties: %m");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl->module = module;
|
2023-02-21 19:16:23 +01:00
|
|
|
impl->context = context;
|
2025-05-30 11:59:35 +02:00
|
|
|
impl->main_loop = pw_context_get_main_loop(context);
|
2024-04-22 16:19:02 +02:00
|
|
|
impl->data_loop = pw_context_acquire_loop(context, &props->dict);
|
2022-10-04 09:16:17 +02:00
|
|
|
|
2025-07-02 18:31:17 +02:00
|
|
|
impl->rate_limit.interval = 2 * SPA_NSEC_PER_SEC;
|
|
|
|
|
impl->rate_limit.burst = 1;
|
|
|
|
|
|
2023-03-02 19:55:44 +01:00
|
|
|
if ((sess_name = pw_properties_get(props, "sess.name")) == NULL)
|
|
|
|
|
sess_name = pw_get_host_name();
|
2023-02-21 17:18:52 +01:00
|
|
|
|
2024-04-22 16:19:02 +02:00
|
|
|
pw_properties_set(props, PW_KEY_NODE_LOOP_NAME, impl->data_loop->name);
|
2023-02-21 17:18:52 +01:00
|
|
|
if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL)
|
2023-03-02 19:55:44 +01:00
|
|
|
pw_properties_setf(props, PW_KEY_NODE_NAME, "rtp_session.%s", sess_name);
|
2023-02-21 17:18:52 +01:00
|
|
|
if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL)
|
2023-03-02 19:55:44 +01:00
|
|
|
pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION, "%s", sess_name);
|
2023-02-21 17:18:52 +01:00
|
|
|
if (pw_properties_get(props, PW_KEY_MEDIA_NAME) == NULL)
|
2023-03-02 19:55:44 +01:00
|
|
|
pw_properties_setf(props, PW_KEY_MEDIA_NAME, "RTP Session with %s",
|
|
|
|
|
sess_name);
|
2023-02-21 17:18:52 +01:00
|
|
|
|
|
|
|
|
if ((str = pw_properties_get(props, "stream.props")) != NULL)
|
|
|
|
|
pw_properties_update_string(stream_props, str, strlen(str));
|
|
|
|
|
|
2024-04-22 16:19:02 +02:00
|
|
|
copy_props(impl, props, PW_KEY_NODE_LOOP_NAME);
|
2023-02-21 17:18:52 +01:00
|
|
|
copy_props(impl, props, PW_KEY_AUDIO_FORMAT);
|
|
|
|
|
copy_props(impl, props, PW_KEY_AUDIO_RATE);
|
|
|
|
|
copy_props(impl, props, PW_KEY_AUDIO_CHANNELS);
|
|
|
|
|
copy_props(impl, props, SPA_KEY_AUDIO_POSITION);
|
|
|
|
|
copy_props(impl, props, PW_KEY_NODE_NAME);
|
|
|
|
|
copy_props(impl, props, PW_KEY_NODE_DESCRIPTION);
|
|
|
|
|
copy_props(impl, props, PW_KEY_NODE_GROUP);
|
|
|
|
|
copy_props(impl, props, PW_KEY_NODE_LATENCY);
|
|
|
|
|
copy_props(impl, props, PW_KEY_NODE_VIRTUAL);
|
|
|
|
|
copy_props(impl, props, PW_KEY_NODE_CHANNELNAMES);
|
|
|
|
|
copy_props(impl, props, PW_KEY_MEDIA_NAME);
|
|
|
|
|
copy_props(impl, props, PW_KEY_MEDIA_CLASS);
|
2023-03-02 19:55:44 +01:00
|
|
|
copy_props(impl, props, "net.mtu");
|
2023-03-02 20:04:24 +01:00
|
|
|
copy_props(impl, props, "sess.media");
|
2023-03-02 19:55:44 +01:00
|
|
|
copy_props(impl, props, "sess.name");
|
|
|
|
|
copy_props(impl, props, "sess.min-ptime");
|
|
|
|
|
copy_props(impl, props, "sess.max-ptime");
|
|
|
|
|
copy_props(impl, props, "sess.latency.msec");
|
2023-03-06 10:46:21 +01:00
|
|
|
copy_props(impl, props, "sess.ts-direct");
|
2023-07-06 13:08:21 +02:00
|
|
|
copy_props(impl, props, "sess.ignore-ssrc");
|
2025-01-20 16:44:57 +01:00
|
|
|
copy_props(impl, props, "stream.may-pause");
|
2022-10-07 16:12:22 +02:00
|
|
|
|
2023-02-21 17:18:52 +01:00
|
|
|
str = pw_properties_get(props, "local.ifname");
|
2022-10-05 13:04:08 +02:00
|
|
|
impl->ifname = str ? strdup(str) : NULL;
|
|
|
|
|
|
2023-03-02 20:04:24 +01:00
|
|
|
impl->src_port = pw_properties_get_uint32(props, "source.port", 0);
|
2023-02-21 17:18:52 +01:00
|
|
|
if (impl->src_port == 0) {
|
2023-12-11 10:03:51 +01:00
|
|
|
res = -EINVAL;
|
2023-02-21 17:18:52 +01:00
|
|
|
pw_log_error("invalid source.port");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
2023-03-02 20:12:49 +01:00
|
|
|
if ((str = pw_properties_get(props, "source.ip")) == NULL)
|
|
|
|
|
str = DEFAULT_SOURCE_IP;
|
2024-02-26 15:17:48 +01:00
|
|
|
if ((res = pw_net_parse_address(str, impl->src_port, &impl->src_addr, &impl->src_len)) < 0) {
|
2023-02-21 17:18:52 +01:00
|
|
|
pw_log_error("invalid source.ip %s: %s", str, spa_strerror(res));
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
2024-02-26 15:17:48 +01:00
|
|
|
pw_net_get_ip(&impl->src_addr, addr, sizeof(addr), NULL, NULL);
|
2023-12-11 10:04:14 +01:00
|
|
|
pw_properties_set(stream_props, "rtp.source.ip", addr);
|
|
|
|
|
pw_properties_setf(stream_props, "rtp.source.port", "%u", impl->src_port);
|
2023-02-21 17:18:52 +01:00
|
|
|
|
2025-01-24 12:43:34 +01:00
|
|
|
header_size = impl->src_addr.ss_family == AF_INET ?
|
|
|
|
|
IP4_HEADER_SIZE : IP6_HEADER_SIZE;
|
|
|
|
|
header_size += UDP_HEADER_SIZE;
|
|
|
|
|
pw_properties_setf(stream_props, "net.header", "%u", header_size);
|
|
|
|
|
|
2023-03-06 10:46:21 +01:00
|
|
|
ts_offset = pw_properties_get_int64(props, "sess.ts-offset", DEFAULT_TS_OFFSET);
|
|
|
|
|
if (ts_offset == -1)
|
|
|
|
|
ts_offset = pw_rand32();
|
|
|
|
|
pw_properties_setf(stream_props, "rtp.receiver-ts-offset", "%u", (uint32_t)ts_offset);
|
|
|
|
|
|
2023-02-21 17:18:52 +01:00
|
|
|
impl->always_process = pw_properties_get_bool(stream_props,
|
|
|
|
|
PW_KEY_NODE_ALWAYS_PROCESS, true);
|
2025-01-20 16:44:57 +01:00
|
|
|
impl->may_pause = pw_properties_get_bool(stream_props,
|
|
|
|
|
"stream.may-pause", false);
|
|
|
|
|
impl->standby = false;
|
|
|
|
|
impl->waiting = true;
|
2025-01-28 10:59:48 -05:00
|
|
|
/* Because we don't know the stream receiving state at the start, we try to fake it
|
|
|
|
|
* till we make it (or get timed out) */
|
|
|
|
|
pw_properties_set(stream_props, "rtp.receiving", "true");
|
2023-02-21 17:18:52 +01:00
|
|
|
|
|
|
|
|
impl->cleanup_interval = pw_properties_get_uint32(props,
|
|
|
|
|
"cleanup.sec", DEFAULT_CLEANUP_SEC);
|
|
|
|
|
|
2023-02-21 19:16:23 +01:00
|
|
|
impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core);
|
2022-10-04 09:16:17 +02:00
|
|
|
if (impl->core == NULL) {
|
2023-02-21 17:18:52 +01:00
|
|
|
str = pw_properties_get(props, PW_KEY_REMOTE_NAME);
|
2023-02-21 19:16:23 +01:00
|
|
|
impl->core = pw_context_connect(impl->context,
|
2022-10-04 09:16:17 +02:00
|
|
|
pw_properties_new(
|
|
|
|
|
PW_KEY_REMOTE_NAME, str,
|
|
|
|
|
NULL),
|
|
|
|
|
0);
|
|
|
|
|
impl->do_disconnect = true;
|
|
|
|
|
}
|
|
|
|
|
if (impl->core == NULL) {
|
|
|
|
|
res = -errno;
|
|
|
|
|
pw_log_error("can't connect: %m");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pw_proxy_add_listener((struct pw_proxy*)impl->core,
|
|
|
|
|
&impl->core_proxy_listener,
|
|
|
|
|
&core_proxy_events, impl);
|
|
|
|
|
pw_core_add_listener(impl->core,
|
|
|
|
|
&impl->core_listener,
|
|
|
|
|
&core_events, impl);
|
|
|
|
|
|
2025-05-30 11:59:35 +02:00
|
|
|
impl->standby_timer = pw_loop_add_timer(impl->main_loop, on_standby_timer_event, impl);
|
2025-05-19 16:14:08 +02:00
|
|
|
if (impl->standby_timer == NULL) {
|
2022-10-05 13:47:28 +02:00
|
|
|
res = -errno;
|
|
|
|
|
pw_log_error("can't create timer source: %m");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
2023-02-21 17:18:52 +01:00
|
|
|
value.tv_sec = impl->cleanup_interval;
|
|
|
|
|
value.tv_nsec = 0;
|
2022-10-31 19:19:15 +03:00
|
|
|
interval.tv_sec = impl->cleanup_interval;
|
2022-10-05 13:47:28 +02:00
|
|
|
interval.tv_nsec = 0;
|
2025-05-30 11:59:35 +02:00
|
|
|
pw_loop_update_timer(impl->main_loop, impl->standby_timer, &value, &interval, false);
|
2022-10-05 13:47:28 +02:00
|
|
|
|
2023-03-02 19:55:44 +01:00
|
|
|
impl->stream = rtp_stream_new(impl->core,
|
|
|
|
|
PW_DIRECTION_OUTPUT, pw_properties_copy(stream_props),
|
|
|
|
|
&stream_events, impl);
|
|
|
|
|
if (impl->stream == NULL) {
|
|
|
|
|
res = -errno;
|
|
|
|
|
pw_log_error("can't create stream: %m");
|
2022-10-04 09:16:17 +02:00
|
|
|
goto out;
|
2023-03-02 19:55:44 +01:00
|
|
|
}
|
2022-10-04 09:16:17 +02:00
|
|
|
|
2024-11-11 12:03:32 +01:00
|
|
|
impl->buffer_size = rtp_stream_get_mtu(impl->stream);
|
|
|
|
|
impl->buffer = calloc(1, impl->buffer_size);
|
|
|
|
|
if (impl->buffer == NULL) {
|
|
|
|
|
res = -errno;
|
|
|
|
|
pw_log_error("can't create packet buffer of size %zd: %m", impl->buffer_size);
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-04 09:16:17 +02:00
|
|
|
pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
|
|
|
|
|
|
|
|
|
|
pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_info));
|
|
|
|
|
|
|
|
|
|
pw_log_info("Successfully loaded module-rtp-source");
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
out:
|
|
|
|
|
impl_destroy(impl);
|
|
|
|
|
return res;
|
|
|
|
|
}
|