mirror of
				https://gitlab.freedesktop.org/pipewire/pipewire.git
				synced 2025-10-29 05:40:27 -04:00 
			
		
		
		
	bluez5: add delay adjustment property + fallback value for a2dp-sink
Not all devices report their A2DP delay. In those cases, use a fallback value of 150ms by default. Make the delay adjustable with a SPA_Prop, and expose it as a part of the route. Implement the corresponding parts in media-session.
This commit is contained in:
		
							parent
							
								
									df1dbee687
								
							
						
					
					
						commit
						d75a79babc
					
				
					 6 changed files with 116 additions and 8 deletions
				
			
		|  | @ -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, | ||||
|  |  | |||
|  | @ -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 }, | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Pauli Virtanen
						Pauli Virtanen