From d03eb9e7eafef60cf578b828bdc8501d6b8d24c2 Mon Sep 17 00:00:00 2001 From: Arkadiusz Bokowy Date: Sun, 21 Nov 2021 10:33:54 +0100 Subject: [PATCH] PCM client delay reporting for A2DP sink profile This feature will not work unless there will be a proper delay reporting implementation in BlueZ (for A2DP sink profile). --- doc/org.bluealsa.PCM1.7.rst | 5 +++++ src/ba-transport-pcm.c | 27 +++++++++++++++++++++++++++ src/ba-transport-pcm.h | 5 +++++ src/bluealsa-dbus.c | 14 ++++++++++++++ src/bluealsa-dbus.h | 7 ++++--- src/bluealsa-iface.xml | 1 + src/shared/dbus-client-pcm.c | 11 +++++++++++ src/shared/dbus-client-pcm.h | 3 +++ utils/aplay/aplay.c | 21 +++++++++++++++++++++ utils/cli/cli.c | 1 + 10 files changed, 92 insertions(+), 3 deletions(-) diff --git a/doc/org.bluealsa.PCM1.7.rst b/doc/org.bluealsa.PCM1.7.rst index 845cce1ba..5e91ec0e7 100644 --- a/doc/org.bluealsa.PCM1.7.rst +++ b/doc/org.bluealsa.PCM1.7.rst @@ -146,6 +146,11 @@ array{byte} CodecConfiguration [readonly] uint16 Delay [readonly] Approximate PCM delay in 1/10 of millisecond. +uint16 ClientDelay [readwrite] + Approximate client side delay in 1/10 of millisecond. This property shall + be set by the client in order to account for the client side delay when + capturing or playing audio respectively for PCM source or sink. + int16 DelayAdjustment [readonly] An adjustment (+/-) included within the reported Delay in 1/10 of millisecond to compensate for devices that do not report accurate delay diff --git a/src/ba-transport-pcm.c b/src/ba-transport-pcm.c index 666965816..c530ffd13 100644 --- a/src/ba-transport-pcm.c +++ b/src/ba-transport-pcm.c @@ -706,6 +706,33 @@ int ba_transport_pcm_get_delay(const struct ba_transport_pcm *pcm) { return delay; } +int ba_transport_pcm_set_delay( + struct ba_transport_pcm *pcm, + int delay) { + + const struct ba_transport *t = pcm->t; + + pcm->client_delay = delay; + + /* Forward client delay to BlueZ, but only in case of A2DP sink profile, + * so it will be sent to connected Bluetooth client, e.g. phone. */ + if (t->type.profile == BA_TRANSPORT_PROFILE_A2DP_SINK) { + + GError *err = NULL; + g_dbus_set_property(config.dbus, t->bluez_dbus_owner, t->bluez_dbus_path, + BLUEZ_IFACE_MEDIA_TRANSPORT, "Delay", g_variant_new_uint16(delay), &err); + + if (err != NULL) { + if (err->code != G_DBUS_ERROR_PROPERTY_READ_ONLY) + warn("Couldn't set A2DP transport delay: %s", err->message); + g_error_free(err); + } + + } + + return 0; +} + int16_t ba_transport_pcm_delay_adjustment_get( const struct ba_transport_pcm *pcm) { diff --git a/src/ba-transport-pcm.h b/src/ba-transport-pcm.h index 7cc3e2e52..5bf7719e1 100644 --- a/src/ba-transport-pcm.h +++ b/src/ba-transport-pcm.h @@ -113,6 +113,8 @@ struct ba_transport_pcm { /* Overall PCM delay in 1/10 of millisecond, caused by * audio encoding or decoding and data transfer. */ unsigned int delay; + /* Client delay in 1/10 of millisecond. */ + unsigned int client_delay; /* guard delay adjustments access */ pthread_mutex_t delay_adjustments_mtx; @@ -238,6 +240,9 @@ int ba_transport_pcm_volume_update( int ba_transport_pcm_get_delay( const struct ba_transport_pcm *pcm); +int ba_transport_pcm_set_delay( + struct ba_transport_pcm *pcm, + int delay); int16_t ba_transport_pcm_delay_adjustment_get( const struct ba_transport_pcm *pcm); diff --git a/src/bluealsa-dbus.c b/src/bluealsa-dbus.c index 1f5d0283e..d3fbed7eb 100644 --- a/src/bluealsa-dbus.c +++ b/src/bluealsa-dbus.c @@ -267,6 +267,10 @@ static GVariant *ba_variant_new_pcm_delay_adjustment(const struct ba_transport_p return g_variant_new_int16(ba_transport_pcm_delay_adjustment_get(pcm)); } +static GVariant *ba_variant_new_pcm_client_delay(const struct ba_transport_pcm *pcm) { + return g_variant_new_uint16(pcm->client_delay); +} + static GVariant *ba_variant_new_pcm_soft_volume(const struct ba_transport_pcm *pcm) { return g_variant_new_boolean(pcm->soft_volume); } @@ -877,6 +881,8 @@ static GVariant *bluealsa_pcm_get_property(const char *property, return ba_variant_new_pcm_delay(pcm); if (strcmp(property, "DelayAdjustment") == 0) return ba_variant_new_pcm_delay_adjustment(pcm); + if (strcmp(property, "ClientDelay") == 0) + return ba_variant_new_pcm_client_delay(pcm); if (strcmp(property, "SoftVolume") == 0) return ba_variant_new_pcm_soft_volume(pcm); if (strcmp(property, "Volume") == 0) @@ -898,6 +904,12 @@ static bool bluealsa_pcm_set_property(const char *property, GVariant *value, struct ba_transport_pcm *pcm = userdata; + if (strcmp(property, "ClientDelay") == 0) { + ba_transport_pcm_set_delay(pcm, g_variant_get_uint16(value)); + bluealsa_dbus_pcm_update(pcm, BA_DBUS_PCM_UPDATE_CLIENT_DELAY); + return TRUE; + } + if (strcmp(property, "SoftVolume") == 0) { pcm->soft_volume = g_variant_get_boolean(value); bluealsa_dbus_pcm_update(pcm, BA_DBUS_PCM_UPDATE_SOFT_VOLUME); @@ -1009,6 +1021,8 @@ void bluealsa_dbus_pcm_update(struct ba_transport_pcm *pcm, unsigned int mask) { g_variant_builder_add(&props, "{sv}", "Delay", ba_variant_new_pcm_delay(pcm)); if (mask & BA_DBUS_PCM_UPDATE_DELAY_ADJUSTMENT) g_variant_builder_add(&props, "{sv}", "DelayAdjustment", ba_variant_new_pcm_delay_adjustment(pcm)); + if (mask & BA_DBUS_PCM_UPDATE_CLIENT_DELAY) + g_variant_builder_add(&props, "{sv}", "ClientDelay", ba_variant_new_pcm_client_delay(pcm)); if (mask & BA_DBUS_PCM_UPDATE_SOFT_VOLUME) g_variant_builder_add(&props, "{sv}", "SoftVolume", ba_variant_new_pcm_soft_volume(pcm)); if (mask & BA_DBUS_PCM_UPDATE_VOLUME) diff --git a/src/bluealsa-dbus.h b/src/bluealsa-dbus.h index 74ad3763c..9cb30c2a3 100644 --- a/src/bluealsa-dbus.h +++ b/src/bluealsa-dbus.h @@ -29,9 +29,10 @@ #define BA_DBUS_PCM_UPDATE_CODEC_CONFIG (1 << 4) #define BA_DBUS_PCM_UPDATE_DELAY (1 << 5) #define BA_DBUS_PCM_UPDATE_DELAY_ADJUSTMENT (1 << 6) -#define BA_DBUS_PCM_UPDATE_SOFT_VOLUME (1 << 7) -#define BA_DBUS_PCM_UPDATE_VOLUME (1 << 8) -#define BA_DBUS_PCM_UPDATE_RUNNING (1 << 9) +#define BA_DBUS_PCM_UPDATE_CLIENT_DELAY (1 << 7) +#define BA_DBUS_PCM_UPDATE_SOFT_VOLUME (1 << 8) +#define BA_DBUS_PCM_UPDATE_VOLUME (1 << 9) +#define BA_DBUS_PCM_UPDATE_RUNNING (1 << 10) #define BA_DBUS_RFCOMM_UPDATE_FEATURES (1 << 0) #define BA_DBUS_RFCOMM_UPDATE_BATTERY (1 << 1) diff --git a/src/bluealsa-iface.xml b/src/bluealsa-iface.xml index aeef7f2c5..399c02b32 100644 --- a/src/bluealsa-iface.xml +++ b/src/bluealsa-iface.xml @@ -42,6 +42,7 @@ + diff --git a/src/shared/dbus-client-pcm.c b/src/shared/dbus-client-pcm.c index 8665f657a..0e0983ae3 100644 --- a/src/shared/dbus-client-pcm.c +++ b/src/shared/dbus-client-pcm.c @@ -478,6 +478,12 @@ dbus_bool_t ba_dbus_pcm_update( int type = -1; switch (property) { + case BLUEALSA_PCM_CLIENT_DELAY: + _property = "ClientDelay"; + variant = DBUS_TYPE_UINT16_AS_STRING; + value = &pcm->client_delay; + type = DBUS_TYPE_UINT16; + break; case BLUEALSA_PCM_SOFT_VOLUME: _property = "SoftVolume"; variant = DBUS_TYPE_BOOLEAN_AS_STRING; @@ -739,6 +745,11 @@ static dbus_bool_t dbus_message_iter_get_ba_pcm_props_cb(const char *key, goto fail; dbus_message_iter_get_basic(&variant, &pcm->delay_adjustment); } + else if (strcmp(key, "ClientDelay") == 0) { + if (type != (type_expected = DBUS_TYPE_UINT16)) + goto fail; + dbus_message_iter_get_basic(variant, &pcm->client_delay); + } else if (strcmp(key, "SoftVolume") == 0) { if (type != (type_expected = DBUS_TYPE_BOOLEAN)) goto fail; diff --git a/src/shared/dbus-client-pcm.h b/src/shared/dbus-client-pcm.h index dae5f5060..403cb3556 100644 --- a/src/shared/dbus-client-pcm.h +++ b/src/shared/dbus-client-pcm.h @@ -71,6 +71,7 @@ /** * BlueALSA PCM object property. */ enum ba_pcm_property { + BLUEALSA_PCM_CLIENT_DELAY, BLUEALSA_PCM_SOFT_VOLUME, BLUEALSA_PCM_VOLUME, }; @@ -128,6 +129,8 @@ struct ba_pcm { dbus_uint16_t delay; /* manual delay adjustment */ dbus_int16_t delay_adjustment; + /* approximate client delay */ + dbus_uint16_t client_delay; /* software volume */ dbus_bool_t soft_volume; diff --git a/utils/aplay/aplay.c b/utils/aplay/aplay.c index e420a9a36..020d35e72 100644 --- a/utils/aplay/aplay.c +++ b/utils/aplay/aplay.c @@ -776,6 +776,27 @@ static void *io_worker_routine(struct io_worker *w) { /* move leftovers to the beginning and reposition tail */ ffb_shift(&buffer, frames * w->ba_pcm.channels); + snd_pcm_sframes_t delay_frames; + if ((ret = snd_pcm_delay(w->pcm, &delay_frames)) != 0) + warn("Couldn't get PCM delay: %s", snd_strerror(ret)); + else { + + const int delay = delay_frames * 10000 / w->ba_pcm.sampling; + if (abs(delay - w->ba_pcm.client_delay) >= 500 /* update if >= 50ms */) { + + w->ba_pcm.client_delay = delay; + + DBusError err = DBUS_ERROR_INIT; + if (!bluealsa_dbus_pcm_update(&dbus_ctx, &w->ba_pcm, BLUEALSA_PCM_CLIENT_DELAY, &err)) { + error("Couldn't update PCM: %s", err.message); + dbus_error_free(&err); + goto fail; + } + + } + + } + continue; close_alsa: diff --git a/utils/cli/cli.c b/utils/cli/cli.c index 814f7d452..abf3f74c2 100644 --- a/utils/cli/cli.c +++ b/utils/cli/cli.c @@ -297,6 +297,7 @@ void cli_print_pcm_properties(const struct ba_pcm *pcm, DBusError *err) { cli_print_pcm_selected_codec(pcm); printf("Delay: %#.1f ms\n", (double)pcm->delay / 10); printf("DelayAdjustment: %#.1f ms\n", (double)pcm->delay_adjustment / 10); + printf("ClientDelay: %#.1f ms\n", (double)pcm->client_delay / 10); cli_print_pcm_soft_volume(pcm); cli_print_pcm_volume(pcm); cli_print_pcm_mute(pcm);