bluez5: handle BAP presentation delay and transport latency

For BAP server audio sink, set buffering target so that we try to match
the target presentation delay.  Also adjust requested node latency to be
smaller than the delay.

Also fix BAP transport presentation delay value parsing, and parse also
the other BAP transport properties. Of these, transport latency value
needs to be taken into account in the total sink latency.
This commit is contained in:
Pauli Virtanen 2023-02-19 17:25:14 +02:00
parent 192044f1d9
commit b9d7ecb5b2
4 changed files with 180 additions and 21 deletions

View file

@ -2197,7 +2197,8 @@ 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->delay_us = SPA_BT_UNKNOWN_DELAY;
t->latency_us = SPA_BT_UNKNOWN_DELAY;
t->user_data = SPA_PTROFF(t, sizeof(struct spa_bt_transport), void);
spa_hook_list_init(&t->listener_list);
spa_list_init(&t->bap_transport_linked);
@ -2536,8 +2537,17 @@ int 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;
if (t->delay_us != SPA_BT_UNKNOWN_DELAY) {
/* end-to-end delay = (presentation) delay + transport latency
*
* For BAP, see Core v5.3 Vol 6/G Sec 3.2.2 Fig. 3.2 &
* BAP v1.0 Sec 7.1.1.
*/
int64_t delay = t->delay_us;
if (t->latency_us != SPA_BT_UNKNOWN_DELAY)
delay += t->latency_us;
return delay * SPA_NSEC_PER_USEC;
}
/* Fallback values when device does not provide information */
@ -2698,27 +2708,40 @@ static int transport_update_props(struct spa_bt_transport *transport,
spa_bt_transport_volume_changed(transport);
}
else if (spa_streq(key, "Delay")) {
if (transport->profile & (SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE)) {
uint32_t value;
if (type != DBUS_TYPE_UINT32)
goto next;
dbus_message_iter_get_basic(&it[1], &value);
spa_log_debug(monitor->log, "transport %p: %s=%d", transport, key, (int)value);
transport->delay_us = value;
} else {
uint16_t value;
if (type != DBUS_TYPE_UINT16)
goto next;
dbus_message_iter_get_basic(&it[1], &value);
spa_log_debug(monitor->log, "transport %p: %s=%d", transport, key, (int)value);
transport->delay_us = value * 100;
}
spa_bt_transport_emit_delay_changed(transport);
}
else if (spa_streq(key, "Latency")) {
uint16_t value;
if (type != DBUS_TYPE_UINT16)
goto next;
dbus_message_iter_get_basic(&it[1], &value);
spa_log_debug(monitor->log, "transport %p: %s=%02x", transport, key, value);
spa_log_debug(monitor->log, "transport %p: %s=%d", transport, key, (int)value);
transport->delay = value;
spa_bt_transport_emit_delay_changed(transport);
}
else if (spa_streq(key, "PresentationDelay")) {
uint32_t value;
if (type != DBUS_TYPE_UINT32)
goto next;
dbus_message_iter_get_basic(&it[1], &value);
spa_log_debug(monitor->log, "transport %p: %s=%02x", transport, key, value);
transport->delay = value / 100;
transport->latency_us = value * 1000;
spa_bt_transport_emit_delay_changed(transport);
}
else if (spa_streq(key, "Links")) {
@ -2750,6 +2773,42 @@ static int transport_update_props(struct spa_bt_transport *transport,
dbus_message_iter_next(&iter);
}
}
else if (spa_streq(key, "Interval")) {
uint32_t value;
if (type != DBUS_TYPE_UINT32)
goto next;
dbus_message_iter_get_basic(&it[1], &value);
spa_log_debug(monitor->log, "transport %p: %s=%d", transport, key, (int)value);
}
else if (spa_streq(key, "Framing")) {
dbus_bool_t value;
if (type != DBUS_TYPE_BOOLEAN)
goto next;
dbus_message_iter_get_basic(&it[1], &value);
spa_log_debug(monitor->log, "transport %p: %s=%d", transport, key, (int)value);
}
else if (spa_streq(key, "SDU")) {
uint16_t value;
if (type != DBUS_TYPE_UINT16)
goto next;
dbus_message_iter_get_basic(&it[1], &value);
spa_log_debug(monitor->log, "transport %p: %s=%d", transport, key, (int)value);
}
else if (spa_streq(key, "Retransmissions")) {
uint8_t value;
if (type != DBUS_TYPE_BYTE)
goto next;
dbus_message_iter_get_basic(&it[1], &value);
spa_log_debug(monitor->log, "transport %p: %s=%d", transport, key, (int)value);
}
next:
dbus_message_iter_next(props_iter);
}

View file

@ -217,6 +217,8 @@ struct spa_bt_decode_buffer
uint32_t underrun;
uint32_t pos;
uint32_t target; /**< target buffer (0: automatic) */
uint8_t received:1;
uint8_t buffering:1;
};
@ -266,6 +268,7 @@ static int spa_bt_decode_buffer_init(struct spa_bt_decode_buffer *this, struct s
this->buffer_size = this->frame_size * quantum_limit * 2;
this->buffer_size += this->buffer_reserve;
this->corr = 1.0;
this->target = 0;
this->buffering = true;
spa_bt_rate_control_init(&this->ctl, 0);
@ -366,6 +369,12 @@ static void spa_bt_decode_buffer_recover(struct spa_bt_decode_buffer *this)
spa_bt_rate_control_init(&this->ctl, level);
}
static SPA_UNUSED
void spa_bt_decode_buffer_set_target_latency(struct spa_bt_decode_buffer *this, uint32_t samples)
{
this->target = samples;
}
static void spa_bt_decode_buffer_process(struct spa_bt_decode_buffer *this, uint32_t samples, uint32_t duration)
{
const uint32_t data_size = samples * this->frame_size;
@ -409,13 +418,16 @@ static void spa_bt_decode_buffer_process(struct spa_bt_decode_buffer *this, uint
spa_bt_ptp_update(&this->spike, this->ctl.avg - level, this->prev_consumed);
/* Update target level */
target = BUFFERING_TARGET(this->spike.max, packet_size);
if (this->target)
target = this->target;
else
target = BUFFERING_TARGET(this->spike.max, packet_size);
if (level > SPA_MAX(4 * target, 2*(int32_t)duration) &&
avail > data_size) {
/* Lagging too much: drop data */
uint32_t size = SPA_MIN(avail - data_size,
(level - target*5/2) * this->frame_size);
(level - target) * this->frame_size);
spa_bt_decode_buffer_read(this, size);
spa_log_trace(this->log, "%p overrun samples:%d level:%d target:%d",

View file

@ -603,7 +603,8 @@ struct spa_bt_transport {
int fd;
uint16_t read_mtu;
uint16_t write_mtu;
uint16_t delay;
unsigned int delay_us;
unsigned int latency_us;
struct spa_bt_sco_io *sco_io;

View file

@ -124,6 +124,8 @@ struct impl {
unsigned int is_duplex:1;
unsigned int use_duplex_source:1;
unsigned int node_latency;
int fd;
struct spa_source source;
@ -300,6 +302,31 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size)
static void emit_node_info(struct impl *this, bool full);
static void set_latency(struct impl *this, bool emit_latency)
{
if (this->codec->bap && !this->is_input && this->transport &&
this->transport->delay_us != SPA_BT_UNKNOWN_DELAY) {
unsigned int node_latency = 2048;
unsigned int target = this->transport->delay_us*48000ll/SPA_USEC_PER_SEC * 1/2;
/* Adjust requested node latency to be somewhat (~1/2) smaller
* than presentation delay. The difference functions as room
* for buffering rate control.
*/
while (node_latency > 64 && node_latency > target)
node_latency /= 2;
if (this->node_latency != node_latency) {
this->node_latency = node_latency;
if (emit_latency)
emit_node_info(this, false);
}
spa_log_info(this->log, "BAP presentation delay %d us, node latency %u/48000",
(int)this->transport->delay_us, node_latency);
}
}
static int apply_props(struct impl *this, const struct spa_pod *param)
{
struct props new_props = this->props;
@ -851,16 +878,19 @@ static int impl_node_send_command(void *object, const struct spa_command *comman
static void emit_node_info(struct impl *this, bool full)
{
uint64_t old = full ? this->info.change_mask : 0;
char latency[64];
struct spa_dict_item node_info_items[] = {
{ SPA_KEY_DEVICE_API, "bluez5" },
{ SPA_KEY_MEDIA_CLASS, this->is_input ? "Audio/Source" : "Stream/Output/Audio" },
{ SPA_KEY_NODE_LATENCY, this->is_input ? "" : "512/48000" },
{ SPA_KEY_NODE_LATENCY, this->is_input ? "" : latency },
{ "media.name", ((this->transport && this->transport->device->name) ?
this->transport->device->name : this->codec->bap ? "BAP" : "A2DP") },
{ SPA_KEY_NODE_DRIVER, this->is_input ? "true" : "false" },
};
spa_scnprintf(latency, sizeof(latency), "%d/48000", this->node_latency);
if (full)
this->info.change_mask = this->info_all;
if (this->info.change_mask) {
@ -1297,6 +1327,48 @@ static uint32_t get_samples(struct impl *this, uint32_t *duration)
return samples;
}
static void update_target_latency(struct impl *this)
{
struct port *port = &this->port;
uint32_t samples, duration;
if (this->transport == NULL || !port->have_format)
return;
if (!this->codec->bap || this->is_input ||
this->transport->delay_us == SPA_BT_UNKNOWN_DELAY)
return;
get_samples(this, &duration);
/* Presentation delay for BAP server
*
* This assumes the time when we receive the packet is (on average)
* the SDU synchronization reference (see Core v5.3 Vol 6/G Sec 3.2.2 Fig. 3.2,
* BAP v1.0 Sec 7.1.1).
*
* XXX: This is not exactly true, there might be some latency in between,
* XXX: but currently kernel does not provide us any better information.
* XXX: Some controllers (e.g. Intel AX210) also do not seem to set timestamps
* XXX: to the HCI ISO data packets, so it's not clear what we can do here
* XXX: better.
*/
samples = (uint64_t)this->transport->delay_us *
port->current_format.info.raw.rate / SPA_USEC_PER_SEC;
if (samples > duration)
samples -= duration;
else
samples = 1;
/* Too small target latency might not produce working audio.
* The minimum (Presentation_Delay_Min) is configured in endpoint
* DBus properties, with some default value on BlueZ side if unspecified.
*/
spa_bt_decode_buffer_set_target_latency(&port->buffer, samples);
}
static void process_buffering(struct impl *this)
{
struct port *port = &this->port;
@ -1305,6 +1377,8 @@ static void process_buffering(struct impl *this)
uint32_t avail;
void *buf;
update_target_latency(this);
spa_bt_decode_buffer_process(&port->buffer, samples, duration);
setup_matching(this);
@ -1441,6 +1515,14 @@ static const struct spa_node_methods impl_node = {
.process = impl_node_process,
};
static void transport_delay_changed(void *data)
{
struct impl *this = data;
spa_log_debug(this->log, "transport %p delay changed", this->transport);
set_latency(this, true);
}
static int do_transport_destroy(struct spa_loop *loop,
bool async,
uint32_t seq,
@ -1463,6 +1545,7 @@ static void transport_destroy(void *data)
static const struct spa_bt_transport_events transport_events = {
SPA_VERSION_BT_TRANSPORT_EVENTS,
.delay_changed = transport_delay_changed,
.destroy = transport_destroy,
};
@ -1644,6 +1727,10 @@ impl_init(const struct spa_handle_factory *factory,
this->duplex_timerfd = -1;
}
this->node_latency = 512;
set_latency(this, false);
this->fd = -1;
return 0;