Merge branch 'node-driver-clock-properties' into 'master'

node-driver: Add clock ID prop params to SPA prop types and allow changing clock at runtime through these prop params

See merge request pipewire/pipewire!2579
This commit is contained in:
Carlos Rafael Giani 2025-10-28 07:19:02 +00:00
commit 8560853367
3 changed files with 307 additions and 58 deletions

View file

@ -40,6 +40,9 @@ static const struct spa_type_info spa_type_props[] = {
{ SPA_PROP_quality, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "quality", NULL },
{ SPA_PROP_bluetoothAudioCodec, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE "bluetoothAudioCodec", spa_type_bluetooth_audio_codec },
{ SPA_PROP_bluetoothOffloadActive, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "bluetoothOffloadActive", NULL },
{ SPA_PROP_clockId, SPA_TYPE_String, SPA_TYPE_INFO_PROPS_BASE "clockId", NULL },
{ SPA_PROP_clockDevice, SPA_TYPE_String, SPA_TYPE_INFO_PROPS_BASE "clockDevice", NULL },
{ SPA_PROP_clockInterface, SPA_TYPE_String, SPA_TYPE_INFO_PROPS_BASE "clockInterface", NULL },
{ SPA_PROP_waveType, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "waveType", NULL },
{ SPA_PROP_frequency, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "frequency", NULL },

View file

@ -55,6 +55,9 @@ enum spa_prop {
SPA_PROP_quality,
SPA_PROP_bluetoothAudioCodec,
SPA_PROP_bluetoothOffloadActive,
SPA_PROP_clockId,
SPA_PROP_clockDevice,
SPA_PROP_clockInterface,
SPA_PROP_START_Audio = 0x10000, /**< audio related properties */
SPA_PROP_waveType,

View file

@ -26,6 +26,8 @@
#include <spa/node/io.h>
#include <spa/node/utils.h>
#include <spa/param/param.h>
#include <spa/pod/filter.h>
#include <spa/pod/parser.h>
SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.driver");
@ -48,12 +50,16 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.driver");
#define BW_PERIOD (3 * SPA_NSEC_PER_SEC)
#define MAX_ERROR_MS 1
#define CLOCK_NAME_MAX 64
struct props {
bool freewheel;
char clock_name[64];
char clock_name[CLOCK_NAME_MAX];
clockid_t clock_id;
uint32_t freewheel_wait;
float resync_ms;
char clock_device[CLOCK_NAME_MAX];
char clock_interface[CLOCK_NAME_MAX];
};
struct clock_offset {
@ -73,7 +79,10 @@ struct impl {
uint64_t info_all;
struct spa_node_info info;
struct spa_param_info params[1];
#define NODE_PropInfo 0
#define NODE_Props 1
#define N_NODE_PARAMS 2
struct spa_param_info params[N_NODE_PARAMS];
struct spa_hook_list hooks;
struct spa_callbacks callbacks;
@ -99,13 +108,20 @@ struct impl {
struct clock_offset nsec_offset;
};
static void reset_props_strings(struct props *props)
{
spa_zero(props->clock_name);
spa_zero(props->clock_device);
spa_zero(props->clock_interface);
}
static void reset_props(struct props *props)
{
props->freewheel = DEFAULT_FREEWHEEL;
spa_zero(props->clock_name);
props->clock_id = CLOCK_MONOTONIC;
props->freewheel_wait = DEFAULT_FREEWHEEL_WAIT;
props->resync_ms = DEFAULT_RESYNC_MS;
reset_props_strings(props);
}
static const struct clock_info {
@ -598,10 +614,280 @@ static int impl_node_process(void *object)
return SPA_STATUS_HAVE_DATA | SPA_STATUS_NEED_DATA;
}
static int impl_node_enum_params(void *object, int seq,
uint32_t id, uint32_t start, uint32_t num,
const struct spa_pod *filter)
{
struct impl *this = object;
struct spa_pod *param;
struct spa_pod_builder b = { 0 };
uint8_t buffer[4096];
struct spa_result_node_params result;
uint32_t count = 0;
spa_return_val_if_fail(this != NULL, -EINVAL);
spa_return_val_if_fail(num != 0, -EINVAL);
result.id = id;
result.next = start;
next:
result.index = result.next++;
spa_pod_builder_init(&b, buffer, sizeof(buffer));
switch (id) {
case SPA_PARAM_PropInfo:
{
struct props *p = &this->props;
switch (result.index) {
case 0:
param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_PropInfo, id,
SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_clockId),
SPA_PROP_INFO_description, SPA_POD_String("The clock id (monotonic, realtime, etc.)"),
SPA_PROP_INFO_type, SPA_POD_String(clock_id_to_name(p->clock_id)));
break;
case 1:
param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_PropInfo, id,
SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_clockDevice),
SPA_PROP_INFO_description, SPA_POD_String("The clock device (eg. /dev/ptp0)"),
SPA_PROP_INFO_type, SPA_POD_Stringn(p->clock_device, sizeof(p->clock_device)));
break;
case 2:
param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_PropInfo, id,
SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_clockInterface),
SPA_PROP_INFO_description, SPA_POD_String("The clock network interface (eg. eth0)"),
SPA_PROP_INFO_type, SPA_POD_Stringn(p->clock_interface, sizeof(p->clock_interface)));
break;
default:
return 0;
}
break;
}
case SPA_PARAM_Props:
{
struct props *p = &this->props;
switch (result.index) {
case 0:
param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Props, id,
SPA_PROP_clockId, SPA_POD_String(clock_id_to_name(p->clock_id))
);
break;
case 1:
param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Props, id,
SPA_PROP_clockDevice, SPA_POD_Stringn(p->clock_device, sizeof(p->clock_device))
);
break;
case 2:
param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Props, id,
SPA_PROP_clockInterface, SPA_POD_Stringn(p->clock_interface, sizeof(p->clock_interface))
);
break;
default:
return 0;
}
break;
}
default:
return -ENOENT;
}
if (spa_pod_filter(&b, &result.param, param, filter) < 0)
goto next;
spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result);
if (++count != num)
goto next;
return 0;
}
static int get_phc_index(struct spa_system *s, const char *name) {
#ifdef ETHTOOL_GET_TS_INFO
struct ethtool_ts_info info = {0};
struct ifreq ifr = {0};
int fd, err;
info.cmd = ETHTOOL_GET_TS_INFO;
strncpy(ifr.ifr_name, name, IFNAMSIZ - 1);
ifr.ifr_data = (char *) &info;
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0)
return -errno;
err = spa_system_ioctl(s, fd, SIOCETHTOOL, &ifr);
close(fd);
if (err < 0)
return -errno;
return info.phc_index;
#else
return -ENOTSUP;
#endif
}
static bool parse_clock_id(struct impl *this, const char *s)
{
int id = clock_name_to_id(s);
if (id == -1) {
spa_log_info(this->log, "unknown clock id '%s'", s);
return false;
}
this->props.clock_id = id;
if (this->clock_fd >= 0) {
close(this->clock_fd);
this->clock_fd = -1;
}
return true;
}
static bool parse_clock_device(struct impl *this, const char *s)
{
int fd = open(s, O_RDONLY);
if (fd == -1) {
spa_log_info(this->log, "failed to open clock device '%s': %m", s);
return false;
}
if (this->clock_fd >= 0) {
close(this->clock_fd);
}
this->clock_fd = fd;
this->props.clock_id = FD_TO_CLOCKID(this->clock_fd);
return true;
}
static bool parse_clock_interface(struct impl *this, const char *s)
{
int phc_index = get_phc_index(this->data_system, s);
if (phc_index < 0) {
spa_log_info(this->log, "failed to get phc device index for interface '%s': %s",
s, spa_strerror(phc_index));
return false;
} else {
char dev[19];
spa_scnprintf(dev, sizeof(dev), "/dev/ptp%d", phc_index);
if (!parse_clock_device(this, dev)) {
spa_log_info(this->log, "failed to open clock device '%s' "
"for interface '%s': %m", dev, s);
return false;
}
}
return true;
}
static void ensure_clock_name(struct impl *this)
{
struct props *p = &this->props;
if (p->clock_name[0] == '\0') {
const char *name = clock_id_to_name(p->clock_id);
if (p->clock_device[0])
name = p->clock_device;
if (p->clock_interface[0])
name = p->clock_interface;
spa_scnprintf(p->clock_name, sizeof(p->clock_name),
"%s.%s", DEFAULT_CLOCK_PREFIX, name);
}
}
static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
const struct spa_pod *param)
{
struct impl *this = object;
spa_return_val_if_fail(this != NULL, -EINVAL);
switch (id) {
case SPA_PARAM_Props:
{
struct props *p = &this->props;
bool notify = false;
char buffer[CLOCK_NAME_MAX];
int count;
if (param == NULL) {
return 0;
}
/* Note that the length passed to SPA_POD_OPT_Stringn() also
* includes room for the null terminator, so the content of the
* buffer variable is always guaranteed to be null terminated. */
spa_zero(buffer);
count = spa_pod_parse_object(param,
SPA_TYPE_OBJECT_Props, NULL,
SPA_PROP_clockId, SPA_POD_OPT_Stringn(buffer, sizeof(buffer))
);
if (count && parse_clock_id(this, buffer))
{
reset_props_strings(p);
notify = true;
}
spa_zero(buffer);
count = spa_pod_parse_object(param,
SPA_TYPE_OBJECT_Props, NULL,
SPA_PROP_clockDevice, SPA_POD_OPT_Stringn(buffer, sizeof(buffer))
);
if (count && parse_clock_device(this, buffer))
{
reset_props_strings(p);
strncpy(p->clock_device, buffer, sizeof(p->clock_device));
notify = true;
}
spa_zero(buffer);
count = spa_pod_parse_object(param,
SPA_TYPE_OBJECT_Props, NULL,
SPA_PROP_clockInterface, SPA_POD_OPT_Stringn(buffer, sizeof(buffer))
);
if (count && parse_clock_interface(this, buffer))
{
reset_props_strings(p);
strncpy(p->clock_interface, buffer, sizeof(p->clock_interface));
notify = true;
}
if (notify)
{
ensure_clock_name(this);
spa_log_info(this->log, "%p: setting clock to '%s'", this, p->clock_name);
if (this->started) {
do_stop(this);
do_start(this);
}
emit_node_info(this, true);
}
break;
}
default:
return -ENOENT;
break;
}
return 0;
}
static const struct spa_node_methods impl_node = {
SPA_VERSION_NODE_METHODS,
.add_listener = impl_node_add_listener,
.set_callbacks = impl_node_set_callbacks,
.enum_params = impl_node_enum_params,
.set_param = impl_node_set_param,
.set_io = impl_node_set_io,
.send_command = impl_node_send_command,
.process = impl_node_process,
@ -655,31 +941,6 @@ impl_get_size(const struct spa_handle_factory *factory,
return sizeof(struct impl);
}
static int get_phc_index(struct spa_system *s, const char *name) {
#ifdef ETHTOOL_GET_TS_INFO
struct ethtool_ts_info info = {0};
struct ifreq ifr = {0};
int fd, err;
info.cmd = ETHTOOL_GET_TS_INFO;
strncpy(ifr.ifr_name, name, IFNAMSIZ - 1);
ifr.ifr_data = (char *) &info;
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0)
return -errno;
err = spa_system_ioctl(s, fd, SIOCETHTOOL, &ifr);
close(fd);
if (err < 0)
return -errno;
return info.phc_index;
#else
return -ENOTSUP;
#endif
}
static int
impl_init(const struct spa_handle_factory *factory,
struct spa_handle *handle,
@ -727,9 +988,10 @@ impl_init(const struct spa_handle_factory *factory,
this->info.max_input_ports = 0;
this->info.max_output_ports = 0;
this->info.flags = SPA_NODE_FLAG_RT;
this->params[0] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE);
this->params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ);
this->params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE);
this->info.params = this->params;
this->info.n_params = 0;
this->info.n_params = N_NODE_PARAMS;
reset_props(&this->props);
@ -742,37 +1004,17 @@ impl_init(const struct spa_handle_factory *factory,
spa_scnprintf(this->props.clock_name,
sizeof(this->props.clock_name), "%s", s);
} else if (spa_streq(k, "clock.id") && this->clock_fd < 0) {
this->props.clock_id = clock_name_to_id(s);
if (this->props.clock_id == -1) {
spa_log_warn(this->log, "unknown clock id '%s'", s);
this->props.clock_id = DEFAULT_CLOCK_ID;
}
if (parse_clock_id(this, s))
reset_props_strings(&this->props);
} else if (spa_streq(k, "clock.device")) {
if (this->clock_fd >= 0) {
close(this->clock_fd);
}
this->clock_fd = open(s, O_RDONLY);
if (this->clock_fd == -1) {
spa_log_warn(this->log, "failed to open clock device '%s': %m", s);
} else {
this->props.clock_id = FD_TO_CLOCKID(this->clock_fd);
if (parse_clock_device(this, s)) {
reset_props_strings(&this->props);
strncpy(this->props.clock_device, s, sizeof(this->props.clock_device)-1);
}
} else if (spa_streq(k, "clock.interface") && this->clock_fd < 0) {
int phc_index = get_phc_index(this->data_system, s);
if (phc_index < 0) {
spa_log_warn(this->log, "failed to get phc device index for interface '%s': %s",
s, spa_strerror(phc_index));
} else {
char dev[19];
spa_scnprintf(dev, sizeof(dev), "/dev/ptp%d", phc_index);
this->clock_fd = open(dev, O_RDONLY);
if (this->clock_fd == -1) {
spa_log_warn(this->log, "failed to open clock device '%s' "
"for interface '%s': %m", dev, s);
} else {
this->props.clock_id = FD_TO_CLOCKID(this->clock_fd);
}
if (parse_clock_interface(this, s)) {
reset_props_strings(&this->props);
strncpy(this->props.clock_interface, s, sizeof(this->props.clock_interface)-1);
}
} else if (spa_streq(k, "freewheel.wait")) {
this->props.freewheel_wait = atoi(s);
@ -785,6 +1027,7 @@ impl_init(const struct spa_handle_factory *factory,
"%s.%s", DEFAULT_CLOCK_PREFIX,
clock_id_to_name(this->props.clock_id));
}
ensure_clock_name(this);
this->tracking = !clock_for_timerfd(this->props.clock_id);
this->timer_clockid = this->tracking ? CLOCK_MONOTONIC : this->props.clock_id;