Skip to content

Commit f7e303e

Browse files
committed
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).
1 parent e640aa4 commit f7e303e

7 files changed

+93
-19
lines changed

src/ba-transport-pcm.c

+51-7
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <string.h>
2525
#include <unistd.h>
2626

27+
#include <gio/gio.h>
2728
#include <glib.h>
2829

2930
#include "audio.h"
@@ -631,7 +632,9 @@ void ba_transport_pcm_volume_set(
631632

632633
}
633634

634-
int ba_transport_pcm_volume_update(struct ba_transport_pcm *pcm) {
635+
/**
636+
* Synchronize PCM volume level with remote Bluetooth device. */
637+
int ba_transport_pcm_volume_sync(struct ba_transport_pcm *pcm) {
635638

636639
struct ba_transport *t = pcm->t;
637640

@@ -684,8 +687,6 @@ int ba_transport_pcm_volume_update(struct ba_transport_pcm *pcm) {
684687
}
685688

686689
final:
687-
/* notify connected clients (including requester) */
688-
bluealsa_dbus_pcm_update(pcm, BA_DBUS_PCM_UPDATE_VOLUME);
689690
return 0;
690691
}
691692

@@ -716,20 +717,63 @@ int ba_transport_pcm_get_hardware_volume(
716717
return 0;
717718
}
718719

719-
int ba_transport_pcm_get_delay(const struct ba_transport_pcm *pcm) {
720+
/**
721+
* Get PCM playback/capture cumulative delay. */
722+
int ba_transport_pcm_delay_get(const struct ba_transport_pcm *pcm) {
720723

721724
const struct ba_transport *t = pcm->t;
725+
int delay = 0;
722726

723-
int delay = pcm->codec_delay_dms + pcm->processing_delay_dms;
727+
delay += pcm->codec_delay_dms;
728+
delay += pcm->processing_delay_dms;
724729

725-
if (t->profile & BA_TRANSPORT_PROFILE_MASK_A2DP)
730+
/* Add delay reported by BlueZ but only for A2DP Source profile. In case
731+
* of A2DP Sink, the BlueZ delay value is in fact our client delay. */
732+
if (t->profile & BA_TRANSPORT_PROFILE_A2DP_SOURCE)
726733
delay += t->a2dp.delay;
727-
if (t->profile & BA_TRANSPORT_PROFILE_MASK_SCO)
734+
/* HFP/HSP profiles do not provide any delay information. However, we can
735+
* assume some arbitrary value here - for now it will be 10 ms. */
736+
else if (t->profile & BA_TRANSPORT_PROFILE_MASK_AG)
728737
delay += 10;
729738

730739
return delay;
731740
}
732741

742+
/**
743+
* Synchronize PCM playback delay with remote Bluetooth device. */
744+
int ba_transport_pcm_delay_sync(struct ba_transport_pcm *pcm) {
745+
746+
struct ba_transport *t = pcm->t;
747+
int delay = 0;
748+
749+
delay += pcm->codec_delay_dms;
750+
delay += pcm->processing_delay_dms;
751+
delay += pcm->client_delay_dms;
752+
753+
/* In case of A2DP Sink, update the delay property of the BlueZ media
754+
* transport interface. BlueZ should forward this value to the remote
755+
* device, so it can adjust audio/video synchronization. */
756+
if (t->profile == BA_TRANSPORT_PROFILE_A2DP_SINK &&
757+
!t->a2dp.read_only_delay_quirk &&
758+
abs(delay - t->a2dp.delay) >= 100 /* 10ms */) {
759+
760+
GError *err = NULL;
761+
t->a2dp.delay = delay;
762+
g_dbus_set_property(config.dbus, t->bluez_dbus_owner, t->bluez_dbus_path,
763+
BLUEZ_IFACE_MEDIA_TRANSPORT, "Delay", g_variant_new_uint16(delay), &err);
764+
765+
if (err != NULL) {
766+
warn("Couldn't set A2DP transport delay: %s", err->message);
767+
if (err->code == G_DBUS_ERROR_PROPERTY_READ_ONLY)
768+
t->a2dp.read_only_delay_quirk = true;
769+
g_error_free(err);
770+
}
771+
772+
}
773+
774+
return 0;
775+
}
776+
733777
const char *ba_transport_pcm_channel_to_string(
734778
enum ba_transport_pcm_channel channel) {
735779
switch (channel) {

src/ba-transport-pcm.h

+5-2
Original file line numberDiff line numberDiff line change
@@ -247,15 +247,18 @@ void ba_transport_pcm_volume_set(
247247
const bool *soft_mute,
248248
const bool *hard_mute);
249249

250-
int ba_transport_pcm_volume_update(
250+
int ba_transport_pcm_volume_sync(
251251
struct ba_transport_pcm *pcm);
252252

253253
int ba_transport_pcm_get_hardware_volume(
254254
const struct ba_transport_pcm *pcm);
255255

256-
int ba_transport_pcm_get_delay(
256+
int ba_transport_pcm_delay_get(
257257
const struct ba_transport_pcm *pcm);
258258

259+
int ba_transport_pcm_delay_sync(
260+
struct ba_transport_pcm *pcm);
261+
259262
const char *ba_transport_pcm_channel_to_string(
260263
enum ba_transport_pcm_channel channel);
261264

src/ba-transport.h

+6
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,12 @@ struct ba_transport {
143143
* subsequent ioctl() calls. */
144144
int bt_fd_coutq_init;
145145

146+
/* Even though BlueZ documentation says that the Delay property is
147+
* read-write, it might not be true. In case when the delay write
148+
* operation fails with "not writable" error, we should not try to
149+
* update the delay value any more. */
150+
bool read_only_delay_quirk;
151+
146152
} a2dp;
147153

148154
struct {

src/bluealsa-dbus.c

+4-2
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ static GVariant *ba_variant_new_pcm_codec_config(const struct ba_transport_pcm *
281281
}
282282

283283
static GVariant *ba_variant_new_pcm_delay(const struct ba_transport_pcm *pcm) {
284-
return g_variant_new_uint16(ba_transport_pcm_get_delay(pcm));
284+
return g_variant_new_uint16(ba_transport_pcm_delay_get(pcm));
285285
}
286286

287287
static GVariant *ba_variant_new_pcm_client_delay(const struct ba_transport_pcm *pcm) {
@@ -962,6 +962,7 @@ static bool bluealsa_pcm_set_property(const char *property, GVariant *value,
962962

963963
if (strcmp(property, "ClientDelay") == 0) {
964964
pcm->client_delay_dms = g_variant_get_int16(value);
965+
ba_transport_pcm_delay_sync(pcm);
965966
bluealsa_dbus_pcm_update(pcm, BA_DBUS_PCM_UPDATE_CLIENT_DELAY);
966967
return TRUE;
967968
}
@@ -1017,7 +1018,8 @@ static bool bluealsa_pcm_set_property(const char *property, GVariant *value,
10171018

10181019
pthread_mutex_unlock(&pcm->mutex);
10191020

1020-
ba_transport_pcm_volume_update(pcm);
1021+
ba_transport_pcm_volume_sync(pcm);
1022+
bluealsa_dbus_pcm_update(pcm, BA_DBUS_PCM_UPDATE_VOLUME);
10211023
return true;
10221024
}
10231025

test/test-rfcomm.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ CK_START_TEST(test_rfcomm_hfp_ag) {
327327
ba_transport_pcm_volume_set(&pcm->volume[0], &level, NULL, NULL);
328328
pthread_mutex_unlock(&pcm->mutex);
329329
/* use internal API to update volume */
330-
ba_transport_pcm_volume_update(pcm);
330+
ba_transport_pcm_volume_sync(pcm);
331331
ck_assert_rfcomm_recv(fd, "\r\n+VGS:7\r\n");
332332

333333
ba_transport_destroy(sco);

test/test-utils-ctl.c

+5-5
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ CK_START_TEST(test_client_delay) {
264264

265265
struct spawn_process sp_ba_mock;
266266
ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, NULL, true,
267-
"--profile=a2dp-source",
267+
"--profile=a2dp-sink",
268268
NULL), -1);
269269

270270
char output[4096];
@@ -276,21 +276,21 @@ CK_START_TEST(test_client_delay) {
276276

277277
/* check default client delay */
278278
ck_assert_int_eq(run_bluealsactl(output, sizeof(output),
279-
"client-delay", "/org/bluealsa/hci11/dev_12_34_56_78_9A_BC/a2dpsrc/sink",
279+
"client-delay", "/org/bluealsa/hci11/dev_12_34_56_78_9A_BC/a2dpsnk/source",
280280
NULL), 0);
281281
ck_assert_ptr_ne(strstr(output, "ClientDelay: 0.0 ms"), NULL);
282282

283283
/* check setting client delay */
284284
ck_assert_int_eq(run_bluealsactl(output, sizeof(output),
285-
"client-delay", "/org/bluealsa/hci11/dev_12_34_56_78_9A_BC/a2dpsrc/sink", "-7.5",
285+
"client-delay", "/org/bluealsa/hci11/dev_12_34_56_78_9A_BC/a2dpsnk/source", "-7.5",
286286
NULL), 0);
287287

288288
/* check that setting client delay does not affect delay */
289289
ck_assert_int_eq(run_bluealsactl(output, sizeof(output),
290-
"info", "/org/bluealsa/hci11/dev_12_34_56_78_9A_BC/a2dpsrc/sink",
290+
"info", "/org/bluealsa/hci11/dev_12_34_56_78_9A_BC/a2dpsnk/source",
291291
NULL), 0);
292292
ck_assert_ptr_ne(strstr(output, "ClientDelay: -7.5 ms"), NULL);
293-
ck_assert_ptr_ne(strstr(output, "Delay: 10.0 ms"), NULL);
293+
ck_assert_ptr_ne(strstr(output, "Delay: 0.0 ms"), NULL);
294294

295295
spawn_terminate(&sp_ba_mock, 0);
296296
spawn_close(&sp_ba_mock, NULL);

utils/aplay/aplay.c

+21-2
Original file line numberDiff line numberDiff line change
@@ -563,8 +563,7 @@ static void *io_worker_routine(struct io_worker *w) {
563563
w->ba_pcm.pcm_path, softvol ? "software" : "pass-through");
564564
if (softvol != w->ba_pcm.soft_volume) {
565565
w->ba_pcm.soft_volume = softvol;
566-
if (!ba_dbus_pcm_update(&dbus_ctx, &w->ba_pcm,
567-
BLUEALSA_PCM_SOFT_VOLUME, &err)) {
566+
if (!ba_dbus_pcm_update(&dbus_ctx, &w->ba_pcm, BLUEALSA_PCM_SOFT_VOLUME, &err)) {
568567
error("Couldn't set BlueALSA source PCM volume mode: %s", err.message);
569568
dbus_error_free(&err);
570569
goto fail;
@@ -806,6 +805,26 @@ static void *io_worker_routine(struct io_worker *w) {
806805
/* move leftovers to the beginning and reposition tail */
807806
ffb_shift(&buffer, frames * w->ba_pcm.channels);
808807

808+
int ret;
809+
snd_pcm_sframes_t delay_frames;
810+
if ((ret = snd_pcm_delay(w->snd_pcm, &delay_frames)) != 0)
811+
warn("Couldn't get PCM delay: %s", snd_strerror(ret));
812+
else {
813+
814+
const int delay = delay_frames * 10000 / w->ba_pcm.rate;
815+
if (abs(delay - w->ba_pcm.client_delay) >= 100 /* 10ms */) {
816+
817+
w->ba_pcm.client_delay = delay;
818+
if (!ba_dbus_pcm_update(&dbus_ctx, &w->ba_pcm, BLUEALSA_PCM_CLIENT_DELAY, &err)) {
819+
error("Couldn't update BlueALSA PCM client delay: %s", err.message);
820+
dbus_error_free(&err);
821+
goto fail;
822+
}
823+
824+
}
825+
826+
}
827+
809828
continue;
810829

811830
close_alsa:

0 commit comments

Comments
 (0)