From 32fe4dbda350eed6cd1e1ee6e537c9ed22ef921b Mon Sep 17 00:00:00 2001 From: Martin Geier Date: Tue, 26 May 2026 12:34:03 +0200 Subject: [PATCH] bluez: introduce media.min-latency-ms for media source Target number of samples stored in buffer is calculated by target = SPA_CLAMP(SPA_ROUND_UP(SPA_MAX(spike * 3/2, duration), SPA_CLAMP((int)this->rate / 50, 1, INT32_MAX)), duration, max_buf - 2*packet_size); At beginning of the playback is spike == 0, so the target is set to duration. Our BT is not able to maintain such low latency and drops so spike is increased. This can happens multiple times until playback is stable. This strategy causes few dropouts at the beginning of a new playback, in my case on each new track. Add min-latency-ms property to reduces spikes and the likelihood of dropouts at the beginning. --- spa/include/spa/utils/keys.h | 2 ++ spa/plugins/bluez5/decode-buffer.h | 8 ++++++++ spa/plugins/bluez5/media-source.c | 8 ++++++++ 3 files changed, 18 insertions(+) diff --git a/spa/include/spa/utils/keys.h b/spa/include/spa/utils/keys.h index 59dfc1139..f60d3a3ec 100644 --- a/spa/include/spa/utils/keys.h +++ b/spa/include/spa/utils/keys.h @@ -121,6 +121,8 @@ extern "C" { #define SPA_KEY_API_BLUEZ5_CLASS "api.bluez5.class" /**< a bluetooth class */ #define SPA_KEY_API_BLUEZ5_ICON "api.bluez5.icon" /**< a bluetooth icon */ #define SPA_KEY_API_BLUEZ5_ROLE "api.bluez5.role" /**< "client" or "server" */ +#define SPA_KEY_API_BLUEZ5_MIN_LATENCY_MS \ + "api.bluez5.min-latency-ms" /**< minimum possible latency, in milliseconds */ /** keys for jack api */ #define SPA_KEY_API_JACK "api.jack" /**< key for the JACK api */ diff --git a/spa/plugins/bluez5/decode-buffer.h b/spa/plugins/bluez5/decode-buffer.h index 776c2a0ce..beb34cfe0 100644 --- a/spa/plugins/bluez5/decode-buffer.h +++ b/spa/plugins/bluez5/decode-buffer.h @@ -85,6 +85,8 @@ struct spa_bt_decode_buffer int64_t duration_ns; int64_t next_nsec; + int32_t min_latency; + int32_t delay; int32_t delay_frac; uint32_t prev_samples; @@ -252,6 +254,11 @@ static inline void spa_bt_decode_buffer_set_max_extra_latency(struct spa_bt_deco this->max_extra = samples; } +static inline void spa_bt_decode_buffer_set_min_latency(struct spa_bt_decode_buffer *this, int32_t samples) +{ + this->min_latency = samples; +} + static inline int32_t spa_bt_decode_buffer_get_auto_latency(struct spa_bt_decode_buffer *this) { const int32_t duration = this->duration_ns * this->rate / SPA_NSEC_PER_SEC; @@ -264,6 +271,7 @@ static inline int32_t spa_bt_decode_buffer_get_auto_latency(struct spa_bt_decode SPA_CLAMP((int)this->rate / 50, 1, INT32_MAX)), duration, max_buf - 2*packet_size); + target = SPA_MAX(target, this->min_latency); return SPA_MIN(target, duration + SPA_CLAMP(this->max_extra, 0, INT32_MAX - duration)); } diff --git a/spa/plugins/bluez5/media-source.c b/spa/plugins/bluez5/media-source.c index 01bb4f2fd..73ea6c44e 100644 --- a/spa/plugins/bluez5/media-source.c +++ b/spa/plugins/bluez5/media-source.c @@ -148,6 +148,7 @@ struct impl { unsigned int decode_buffer_target; unsigned int node_latency; + uint32_t min_latency_ms; int fd; struct spa_source source; @@ -1039,6 +1040,11 @@ static int transport_start(struct impl *this) port->current_format.info.raw.rate * 80 / 1000); } + if (this->min_latency_ms) { + spa_bt_decode_buffer_set_min_latency(&port->buffer, + this->min_latency_ms * port->current_format.info.raw.rate / 1000); + } + this->delay.buffer = -1; this->delay.duration = 0; this->update_delay_event = spa_loop_utils_add_event(this->loop_utils, update_delay_event, this); @@ -2249,6 +2255,8 @@ impl_init(const struct spa_handle_factory *factory, spa_scnprintf(this->props.rate, sizeof(this->props.rate), "%s", str); this->props.has_rate = true; } + if ((str = spa_dict_lookup(info, SPA_KEY_API_BLUEZ5_MIN_LATENCY_MS)) != NULL) + spa_atou32(str, &this->min_latency_ms, 0); } if (this->is_duplex) {