diff --git a/spa/include/spa/param/props.h b/spa/include/spa/param/props.h index 0546f7caa..8ac9521ff 100644 --- a/spa/include/spa/param/props.h +++ b/spa/include/spa/param/props.h @@ -84,6 +84,7 @@ enum spa_prop { SPA_PROP_monitorMute, /**< mute (Bool) */ SPA_PROP_monitorVolumes, /**< a volume array, one volume per * channel (Array of Float) */ + SPA_PROP_latencyOffsetNsec, /**< delay adjustment */ SPA_PROP_START_Video = 0x20000, /**< video related properties */ SPA_PROP_brightness, diff --git a/spa/include/spa/param/type-info.h b/spa/include/spa/param/type-info.h index 79717b986..fb4445c8f 100644 --- a/spa/include/spa/param/type-info.h +++ b/spa/include/spa/param/type-info.h @@ -112,6 +112,7 @@ static const struct spa_type_info spa_type_props[] = { { SPA_PROP_channelMap, SPA_TYPE_Array, SPA_TYPE_INFO_PROPS_BASE "channelMap", spa_type_prop_channel_map }, { SPA_PROP_monitorMute, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "monitorMute", NULL }, { SPA_PROP_monitorVolumes, SPA_TYPE_Array, SPA_TYPE_INFO_PROPS_BASE "monitorVolumes", spa_type_prop_monitor_volume }, + { SPA_PROP_latencyOffsetNsec, SPA_TYPE_Long, SPA_TYPE_INFO_PROPS_BASE "latencyOffsetNsec", NULL }, { SPA_PROP_brightness, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "brightness", NULL }, { SPA_PROP_contrast, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "contrast", NULL }, diff --git a/spa/plugins/bluez5/a2dp-sink.c b/spa/plugins/bluez5/a2dp-sink.c index 92c09af3b..0ae359244 100644 --- a/spa/plugins/bluez5/a2dp-sink.c +++ b/spa/plugins/bluez5/a2dp-sink.c @@ -58,6 +58,7 @@ struct codec; struct props { uint32_t min_latency; uint32_t max_latency; + int64_t latency_offset; }; #define FILL_FRAMES 2 @@ -154,6 +155,7 @@ static void reset_props(struct props *props) { props->min_latency = default_min_latency; props->max_latency = default_max_latency; + props->latency_offset = 0; } static int impl_node_enum_params(void *object, int seq, @@ -197,6 +199,13 @@ static int impl_node_enum_params(void *object, int seq, SPA_PROP_INFO_name, SPA_POD_String("The maximum latency"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->max_latency, 1, INT32_MAX)); break; + case 2: + 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, INT64_MIN, INT64_MAX)); + break; default: return 0; } @@ -211,7 +220,8 @@ static int impl_node_enum_params(void *object, int seq, param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, id, SPA_PROP_minLatency, SPA_POD_Int(p->min_latency), - SPA_PROP_maxLatency, SPA_POD_Int(p->max_latency)); + SPA_PROP_maxLatency, SPA_POD_Int(p->max_latency), + SPA_PROP_latencyOffsetNsec, SPA_POD_Long(p->latency_offset)); break; default: return 0; @@ -298,6 +308,8 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) return 0; } +static void emit_node_props_changed(struct impl *this); + static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { @@ -312,12 +324,15 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, if (param == NULL) { reset_props(p); + emit_node_props_changed(this); return 0; } - spa_pod_parse_object(param, - SPA_TYPE_OBJECT_Props, NULL, - SPA_PROP_minLatency, SPA_POD_OPT_Int(&p->min_latency), - SPA_PROP_maxLatency, SPA_POD_OPT_Int(&p->max_latency)); + if (spa_pod_parse_object(param, + SPA_TYPE_OBJECT_Props, NULL, + 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(&p->latency_offset)) > 0) + emit_node_props_changed(this); break; } default: @@ -624,14 +639,20 @@ static void a2dp_on_timeout(struct spa_source *source) this->next_time = now_time + duration * SPA_NSEC_PER_SEC / rate; if (SPA_LIKELY(this->clock)) { + int64_t delay_nsec; + this->clock->nsec = now_time; this->clock->position += duration; this->clock->duration = duration; this->clock->rate_diff = 1.0f; this->clock->next_nsec = this->next_time; - // The bluetooth AVDTP delay value is measured in units of 100us - this->clock->delay = (100 * this->transport->delay * (int64_t) this->clock->rate.denom) / SPA_USEC_PER_SEC; + delay_nsec = spa_bt_transport_get_delay_nsec(this->transport); + + /* Negative delay doesn't work properly, so disallow it */ + delay_nsec += SPA_CLAMP(this->props.latency_offset, -delay_nsec, INT64_MAX / 2); + + this->clock->delay = (delay_nsec * this->clock->rate.denom) / SPA_NSEC_PER_SEC; } spa_log_debug(this->log, NAME" %p: timeout %"PRIu64" %"PRIu64"", this, @@ -679,7 +700,8 @@ static int do_start(struct impl *this) if (this->codec_data == NULL) return -EIO; - spa_log_info(this->log, NAME " %p: using A2DP codec %s", this, this->codec->description); + spa_log_info(this->log, NAME " %p: using A2DP codec %s, delay:%"PRIi64" ms", this, this->codec->description, + (int64_t)(spa_bt_transport_get_delay_nsec(this->transport) / SPA_NSEC_PER_MSEC)); this->seqnum = 0; @@ -847,6 +869,13 @@ static void emit_port_info(struct impl *this, struct port *port, bool full) } } +static void emit_node_props_changed(struct impl *this) +{ + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + this->params[1].flags ^= SPA_PARAM_INFO_SERIAL; + emit_node_info(this, false); +} + static int impl_node_add_listener(void *object, struct spa_hook *listener, diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index e2e9b19ec..9e4e8473a 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -1084,6 +1084,7 @@ struct spa_bt_transport *spa_bt_transport_create(struct spa_bt_monitor *monitor, t->path = path; t->fd = -1; t->sco_io = NULL; + t->delay = SPA_BT_UNKNOWN_DELAY; t->user_data = SPA_MEMBER(t, sizeof(struct spa_bt_transport), void); spa_hook_list_init(&t->listener_list); @@ -1260,6 +1261,40 @@ void spa_bt_transport_ensure_sco_io(struct spa_bt_transport *t, struct spa_loop } } +int64_t spa_bt_transport_get_delay_nsec(struct spa_bt_transport *t) +{ + if (t->delay != SPA_BT_UNKNOWN_DELAY) + return (int64_t)t->delay * 100 * SPA_NSEC_PER_USEC; + + /* Fallback values when device does not provide information */ + + if (t->a2dp_codec == NULL) + return 30 * SPA_NSEC_PER_MSEC; + + switch (t->a2dp_codec->codec_id) { + case A2DP_CODEC_SBC: + return 200 * SPA_NSEC_PER_MSEC; + case A2DP_CODEC_MPEG24: + return 200 * SPA_NSEC_PER_MSEC; + case A2DP_CODEC_VENDOR: + { + uint32_t vendor_id = t->a2dp_codec->vendor.vendor_id; + uint16_t codec_id = t->a2dp_codec->vendor.codec_id; + + if (vendor_id == APTX_VENDOR_ID && codec_id == APTX_CODEC_ID) + return 150 * SPA_NSEC_PER_MSEC; + if (vendor_id == APTX_HD_VENDOR_ID && codec_id == APTX_HD_CODEC_ID) + return 150 * SPA_NSEC_PER_MSEC; + if (vendor_id == LDAC_VENDOR_ID && codec_id == LDAC_CODEC_ID) + return 175 * SPA_NSEC_PER_MSEC; + break; + } + default: + break; + }; + return 150 * SPA_NSEC_PER_MSEC; +} + static int transport_update_props(struct spa_bt_transport *transport, DBusMessageIter *props_iter, DBusMessageIter *invalidated_iter) diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index 9e42d9d99..4698efb92 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -72,6 +72,7 @@ struct node { unsigned int active:1; unsigned int mute:1; uint32_t n_channels; + int64_t latency_offset; uint32_t channels[SPA_AUDIO_MAX_CHANNELS]; float volumes[SPA_AUDIO_MAX_CHANNELS]; }; @@ -795,6 +796,11 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, spa_pod_builder_array(b, sizeof(uint32_t), SPA_TYPE_Id, node->n_channels, node->channels); + if (this->profile == 1 && dev == DEVICE_ID_SINK) { + spa_pod_builder_prop(b, SPA_PROP_latencyOffsetNsec, 0); + spa_pod_builder_long(b, node->latency_offset); + } + spa_pod_builder_pop(b, &f[1]); } @@ -969,6 +975,33 @@ static int node_set_mute(struct impl *this, struct node *node, bool mute) return 0; } +static int node_set_latency_offset(struct impl *this, struct node *node, int64_t latency_offset) +{ + 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, "node %p latency offset %"PRIi64" nsec", node, latency_offset); + node->latency_offset = latency_offset; + + 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, node->id); + 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(latency_offset)); + event = spa_pod_builder_pop(&b, &f[0]); + + spa_device_emit_event(&this->hooks, event); + + return 0; +} + static int apply_device_props(struct impl *this, struct node *node, struct spa_pod *props) { float volume = 0; @@ -979,6 +1012,7 @@ static int apply_device_props(struct impl *this, struct node *node, struct spa_p float volumes[SPA_AUDIO_MAX_CHANNELS]; uint32_t channels[SPA_AUDIO_MAX_CHANNELS]; uint32_t n_volumes = 0, n_channels = 0; + int64_t latency_offset = 0; if (!spa_pod_is_object_type(props, SPA_TYPE_OBJECT_Props)) return -EINVAL; @@ -1009,6 +1043,11 @@ static int apply_device_props(struct impl *this, struct node *node, struct spa_p changed++; } break; + case SPA_PROP_latencyOffsetNsec: + if (spa_pod_get_long(&prop->value, &latency_offset) == 0) { + node_set_latency_offset(this, node, latency_offset); + changed++; + } } } if (n_volumes > 0) diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index f740800e0..2be2c65a5 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -143,6 +143,8 @@ extern "C" { #define A2DP_SINK_ENDPOINT A2DP_OBJECT_MANAGER_PATH "/A2DPSink" #define A2DP_SOURCE_ENDPOINT A2DP_OBJECT_MANAGER_PATH "/A2DPSource" +#define SPA_BT_UNKNOWN_DELAY 0 + /* HFP uses SBC encoding with precisely defined parameters. Hence, the size * of the input (number of PCM samples) and output is known up front. */ #define MSBC_DECODED_SIZE 240 @@ -481,6 +483,7 @@ struct spa_bt_transport *spa_bt_transport_find(struct spa_bt_monitor *monitor, c struct spa_bt_transport *spa_bt_transport_find_full(struct spa_bt_monitor *monitor, bool (*callback) (struct spa_bt_transport *t, const void *data), const void *data); +int64_t spa_bt_transport_get_delay_nsec(struct spa_bt_transport *t); int spa_bt_transport_acquire(struct spa_bt_transport *t, bool optional); int spa_bt_transport_release(struct spa_bt_transport *t);