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.
This commit is contained in:
Martin Geier 2026-05-26 12:34:03 +02:00 committed by Carlos Rafael Giani
parent 943bce9c85
commit 32fe4dbda3
3 changed files with 18 additions and 0 deletions

View file

@ -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 */

View file

@ -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));
}

View file

@ -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) {