From c39d374ca81b391b5c64fe3ab6da0614459c138f Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 17 Aug 2021 11:57:12 +0200 Subject: [PATCH] alsa: hook up latencyOffsetNsec in ALSA sink/source This property is exposed on the device Route and forwarded to the nodes. It then configured the process_latency.ns field, which influences the reported port latency. This makes it possible to change the internal port latency on the sink and source with pavucontrol and tweak the synchronization to compensate for internal latencies in the device. --- spa/plugins/alsa/acp/acp.h | 2 ++ spa/plugins/alsa/alsa-acp-device.c | 43 ++++++++++++++++++++++++++ spa/plugins/alsa/alsa-pcm-sink.c | 49 ++++++++++++++++++++++++------ spa/plugins/alsa/alsa-pcm-source.c | 49 ++++++++++++++++++++++++------ 4 files changed, 125 insertions(+), 18 deletions(-) diff --git a/spa/plugins/alsa/acp/acp.h b/spa/plugins/alsa/acp/acp.h index 66d4bb661..22a4c5d98 100644 --- a/spa/plugins/alsa/acp/acp.h +++ b/spa/plugins/alsa/acp/acp.h @@ -225,6 +225,8 @@ struct acp_device { uint32_t n_ports; struct acp_port **ports; + + int64_t latency_ns; }; struct acp_card_profile { diff --git a/spa/plugins/alsa/alsa-acp-device.c b/spa/plugins/alsa/alsa-acp-device.c index bba035fbf..bcc1885a1 100644 --- a/spa/plugins/alsa/alsa-acp-device.c +++ b/spa/plugins/alsa/alsa-acp-device.c @@ -460,6 +460,9 @@ static struct spa_pod *build_route(struct spa_pod_builder *b, uint32_t id, spa_pod_builder_array(b, sizeof(float), SPA_TYPE_Float, channels, soft_volumes); + spa_pod_builder_prop(b, SPA_PROP_latencyOffsetNsec, 0); + spa_pod_builder_long(b, dev->latency_ns); + spa_pod_builder_pop(b, &f[1]); } spa_pod_builder_prop(b, SPA_PARAM_ROUTE_devices, 0); @@ -574,6 +577,32 @@ static int impl_enum_params(void *object, int seq, return 0; } +static void on_latency_changed(void *data, struct acp_device *dev) +{ + struct impl *this = data; + struct spa_event *event; + uint8_t buffer[4096]; + struct spa_pod_builder b = { 0 }; + struct spa_pod_frame f[1]; + + spa_log_info(this->log, "device %s latency changed", dev->name); + this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; + this->params[IDX_Route].user++; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + spa_pod_builder_push_object(&b, &f[0], + SPA_TYPE_EVENT_Device, SPA_DEVICE_EVENT_ObjectConfig); + spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Object, 0); + spa_pod_builder_int(&b, dev->index); + spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Props, 0); + spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Props, SPA_EVENT_DEVICE_Props, + SPA_PROP_latencyOffsetNsec, SPA_POD_Long(dev->latency_ns)); + event = spa_pod_builder_pop(&b, &f[0]); + + spa_device_emit_event(&this->hooks, event); +} + static int apply_device_props(struct impl *this, struct acp_device *dev, struct spa_pod *props) { float volume = 0; @@ -614,6 +643,20 @@ static int apply_device_props(struct impl *this, struct acp_device *dev, struct changed++; } break; + case SPA_PROP_latencyOffsetNsec: + { + int64_t latency_ns; + if (spa_pod_get_long(&prop->value, &latency_ns) == 0) { + if (dev->latency_ns != latency_ns) { + dev->latency_ns = latency_ns; + on_latency_changed(this, dev); + changed++; + } + } + break; + } + default: + break; } } if (n_volumes > 0) diff --git a/spa/plugins/alsa/alsa-pcm-sink.c b/spa/plugins/alsa/alsa-pcm-sink.c index 9d106d8ce..1bb52099b 100644 --- a/spa/plugins/alsa/alsa-pcm-sink.c +++ b/spa/plugins/alsa/alsa-pcm-sink.c @@ -162,6 +162,13 @@ static int impl_node_enum_params(void *object, int seq, SPA_PROP_INFO_name, SPA_POD_String("Use the driver channelmap"), SPA_PROP_INFO_type, SPA_POD_Bool(p->use_chmap)); break; + case 6: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_latencyOffsetNsec), + SPA_PROP_INFO_name, SPA_POD_String("Latency offset (ns)"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Long(0, 0, INT64_MAX)); + break; default: return 0; } @@ -180,7 +187,8 @@ static int impl_node_enum_params(void *object, int seq, SPA_PROP_cardName, SPA_POD_Stringn(p->card_name, sizeof(p->card_name)), SPA_PROP_minLatency, SPA_POD_Int(p->min_latency), SPA_PROP_maxLatency, SPA_POD_Int(p->max_latency), - SPA_PROP_START_CUSTOM, SPA_POD_Bool(p->use_chmap)); + SPA_PROP_START_CUSTOM, SPA_POD_Bool(p->use_chmap), + SPA_PROP_latencyOffsetNsec, SPA_POD_Long(this->process_latency.ns)); break; default: return 0; @@ -252,6 +260,29 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) return 0; } +static void handle_process_latency(struct state *this, + const struct spa_process_latency_info *info) +{ + bool ns_changed = this->process_latency.ns != info->ns; + + if (this->process_latency.quantum == info->quantum && + this->process_latency.rate == info->rate && + !ns_changed) + return; + + this->process_latency = *info; + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + if (ns_changed) + this->params[NODE_Props].flags ^= SPA_PARAM_INFO_SERIAL; + this->params[NODE_ProcessLatency].flags ^= SPA_PARAM_INFO_SERIAL; + emit_node_info(this, false); + + this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + this->port_params[PORT_Latency].flags ^= SPA_PARAM_INFO_SERIAL; + emit_port_info(this, false); +} + static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { @@ -264,17 +295,24 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, case SPA_PARAM_Props: { struct props *p = &this->props; + struct spa_process_latency_info info; if (param == NULL) { reset_props(p); return 0; } + + info = this->process_latency; + spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_device, SPA_POD_OPT_Stringn(p->device, sizeof(p->device)), SPA_PROP_minLatency, SPA_POD_OPT_Int(&p->min_latency), SPA_PROP_maxLatency, SPA_POD_OPT_Int(&p->max_latency), + SPA_PROP_latencyOffsetNsec, SPA_POD_OPT_Long(&info.ns), SPA_PROP_START_CUSTOM, SPA_POD_OPT_Bool(&p->use_chmap)); + + handle_process_latency(this, &info); break; } case SPA_PARAM_ProcessLatency: @@ -282,15 +320,8 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, struct spa_process_latency_info info; if ((res = spa_process_latency_parse(param, &info)) < 0) return res; - this->process_latency = info; - this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; - this->params[NODE_ProcessLatency].flags ^= SPA_PARAM_INFO_SERIAL; - emit_node_info(this, false); - - this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; - this->port_params[PORT_Latency].flags ^= SPA_PARAM_INFO_SERIAL; - emit_port_info(this, false); + handle_process_latency(this, &info); break; } default: diff --git a/spa/plugins/alsa/alsa-pcm-source.c b/spa/plugins/alsa/alsa-pcm-source.c index b0d1d408b..1c2328285 100644 --- a/spa/plugins/alsa/alsa-pcm-source.c +++ b/spa/plugins/alsa/alsa-pcm-source.c @@ -161,6 +161,13 @@ static int impl_node_enum_params(void *object, int seq, SPA_PROP_INFO_name, SPA_POD_String("Use the driver channelmap"), SPA_PROP_INFO_type, SPA_POD_Bool(p->use_chmap)); break; + case 6: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_latencyOffsetNsec), + SPA_PROP_INFO_name, SPA_POD_String("Latency offset (ns)"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Long(0, 0, INT64_MAX)); + break; default: return 0; } @@ -176,7 +183,8 @@ static int impl_node_enum_params(void *object, int seq, SPA_PROP_cardName, SPA_POD_Stringn(p->card_name, sizeof(p->card_name)), SPA_PROP_minLatency, SPA_POD_Int(p->min_latency), SPA_PROP_maxLatency, SPA_POD_Int(p->max_latency), - SPA_PROP_START_CUSTOM, SPA_POD_Bool(p->use_chmap)); + SPA_PROP_START_CUSTOM, SPA_POD_Bool(p->use_chmap), + SPA_PROP_latencyOffsetNsec, SPA_POD_Long(this->process_latency.ns)); break; default: return 0; @@ -249,6 +257,29 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) return 0; } +static void handle_process_latency(struct state *this, + const struct spa_process_latency_info *info) +{ + bool ns_changed = this->process_latency.ns != info->ns; + + if (this->process_latency.quantum == info->quantum && + this->process_latency.rate == info->rate && + !ns_changed) + return; + + this->process_latency = *info; + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + if (ns_changed) + this->params[NODE_Props].flags ^= SPA_PARAM_INFO_SERIAL; + this->params[NODE_ProcessLatency].flags ^= SPA_PARAM_INFO_SERIAL; + emit_node_info(this, false); + + this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + this->port_params[PORT_Latency].flags ^= SPA_PARAM_INFO_SERIAL; + emit_port_info(this, false); +} + static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { @@ -261,17 +292,24 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, case SPA_PARAM_Props: { struct props *p = &this->props; + struct spa_process_latency_info info; if (param == NULL) { reset_props(p); return 0; } + + info = this->process_latency; + spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_device, SPA_POD_OPT_Stringn(p->device, sizeof(p->device)), SPA_PROP_minLatency, SPA_POD_OPT_Int(&p->min_latency), SPA_PROP_maxLatency, SPA_POD_OPT_Int(&p->max_latency), + SPA_PROP_latencyOffsetNsec, SPA_POD_OPT_Long(&info.ns), SPA_PROP_START_CUSTOM, SPA_POD_OPT_Bool(&p->use_chmap)); + + handle_process_latency(this, &info); break; } case SPA_PARAM_ProcessLatency: @@ -279,15 +317,8 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, struct spa_process_latency_info info; if ((res = spa_process_latency_parse(param, &info)) < 0) return res; - this->process_latency = info; - this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; - this->params[NODE_ProcessLatency].flags ^= SPA_PARAM_INFO_SERIAL; - emit_node_info(this, false); - - this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; - this->port_params[PORT_Latency].flags ^= SPA_PARAM_INFO_SERIAL; - emit_port_info(this, false); + handle_process_latency(this, &info); break; } default: