diff --git a/doc/a2dpconf.1.rst b/doc/a2dpconf.1.rst index 4c7cd6049..53720e564 100644 --- a/doc/a2dpconf.1.rst +++ b/doc/a2dpconf.1.rst @@ -36,7 +36,7 @@ OPTIONS Print version and exit. -v, --verbose - Sow verbose bit-stream details. + Show verbose bit-stream details. Display each field as a binary mask with each bit represented by a single character. diff --git a/src/a2dp-aac.c b/src/a2dp-aac.c index 2e8a680d0..a51a154db 100644 --- a/src/a2dp-aac.c +++ b/src/a2dp-aac.c @@ -1,6 +1,6 @@ /* * BlueALSA - a2dp-aac.c - * Copyright (c) 2016-2024 Arkadiusz Bokowy + * Copyright (c) 2016-2025 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -30,6 +30,7 @@ #include "ba-config.h" #include "ba-transport.h" #include "ba-transport-pcm.h" +#include "bluealsa-dbus.h" #include "io.h" #include "rtp.h" #include "utils.h" @@ -290,6 +291,7 @@ void *a2dp_aac_enc_thread(struct ba_transport_pcm *t_pcm) { /* Get the delay introduced by the encoder. */ t_pcm->codec_delay_dms = info.nDelay * 10000 / rate; + ba_transport_pcm_delay_sync(t_pcm, BA_DBUS_PCM_UPDATE_DELAY); rtp_header_t *rtp_header; /* initialize RTP header and get anchor for payload */ @@ -374,6 +376,13 @@ void *a2dp_aac_enc_thread(struct ba_transport_pcm *t_pcm) { goto fail; } + if (!io.initiated) { + /* Get the delay due to codec processing. */ + t_pcm->processing_delay_dms = asrsync_get_dms_since_last_sync(&io.asrs); + ba_transport_pcm_delay_sync(t_pcm, BA_DBUS_PCM_UPDATE_DELAY); + io.initiated = true; + } + /* resend RTP header */ len -= RTP_HEADER_LEN; @@ -390,14 +399,11 @@ void *a2dp_aac_enc_thread(struct ba_transport_pcm *t_pcm) { } unsigned int pcm_frames = out_args.numInSamples / info.inputChannels; - /* keep data transfer at a constant bit rate */ + /* Keep data transfer at a constant bit rate. */ asrsync_sync(&io.asrs, pcm_frames); /* move forward RTP timestamp clock */ rtp_state_update(&rtp, pcm_frames); - /* update busy delay (encoding overhead) */ - t_pcm->processing_delay_dms = asrsync_get_busy_usec(&io.asrs) / 100; - /* If the input buffer was not consumed, we have to append new data to * the existing one. Since we do not use ring buffer, we will simply * move unprocessed data to the front of our linear buffer. */ @@ -553,6 +559,7 @@ void *a2dp_aac_dec_thread(struct ba_transport_pcm *t_pcm) { /* Update the delay introduced by the decoder. */ t_pcm->codec_delay_dms = info->outputDelay * 10000 / rate; + ba_transport_pcm_delay_sync(t_pcm, BA_DBUS_PCM_UPDATE_DELAY); /* update local state with decoded PCM frames */ rtp_state_update(&rtp, info->frameSize); diff --git a/src/a2dp-aptx-hd.c b/src/a2dp-aptx-hd.c index 16664f21d..963b91583 100644 --- a/src/a2dp-aptx-hd.c +++ b/src/a2dp-aptx-hd.c @@ -1,6 +1,6 @@ /* * BlueALSA - a2dp-aptx-hd.c - * Copyright (c) 2016-2024 Arkadiusz Bokowy + * Copyright (c) 2016-2025 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -26,6 +26,7 @@ #include "ba-config.h" #include "ba-transport.h" #include "ba-transport-pcm.h" +#include "bluealsa-dbus.h" #include "codec-aptx.h" #include "io.h" #include "rtp.h" @@ -205,15 +206,19 @@ void *a2dp_aptx_hd_enc_thread(struct ba_transport_pcm *t_pcm) { goto fail; } + if (!io.initiated) { + /* Get the delay due to codec processing. */ + t_pcm->processing_delay_dms = asrsync_get_dms_since_last_sync(&io.asrs); + ba_transport_pcm_delay_sync(t_pcm, BA_DBUS_PCM_UPDATE_DELAY); + io.initiated = true; + } + unsigned int pcm_frames = pcm_samples / channels; - /* keep data transfer at a constant bit rate */ + /* Keep data transfer at a constant bit rate. */ asrsync_sync(&io.asrs, pcm_frames); /* move forward RTP timestamp clock */ rtp.ts_pcm_frames += pcm_frames; - /* update busy delay (encoding overhead) */ - t_pcm->processing_delay_dms = asrsync_get_busy_usec(&io.asrs) / 100; - /* reinitialize output buffer */ ffb_rewind(&bt); diff --git a/src/a2dp-aptx.c b/src/a2dp-aptx.c index 171bb3445..0c5c81a2b 100644 --- a/src/a2dp-aptx.c +++ b/src/a2dp-aptx.c @@ -1,6 +1,6 @@ /* * BlueALSA - a2dp-aptx.c - * Copyright (c) 2016-2024 Arkadiusz Bokowy + * Copyright (c) 2016-2025 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -25,6 +26,7 @@ #include "ba-config.h" #include "ba-transport.h" #include "ba-transport-pcm.h" +#include "bluealsa-dbus.h" #include "codec-aptx.h" #include "io.h" #include "shared/a2dp-codecs.h" @@ -189,11 +191,15 @@ void *a2dp_aptx_enc_thread(struct ba_transport_pcm *t_pcm) { goto fail; } - /* keep data transfer at a constant bit rate */ - asrsync_sync(&io.asrs, pcm_samples / channels); + if (!io.initiated) { + /* Get the delay due to codec processing. */ + t_pcm->processing_delay_dms = asrsync_get_dms_since_last_sync(&io.asrs); + ba_transport_pcm_delay_sync(t_pcm, BA_DBUS_PCM_UPDATE_DELAY); + io.initiated = true; + } - /* update busy delay (encoding overhead) */ - t_pcm->processing_delay_dms = asrsync_get_busy_usec(&io.asrs) / 100; + /* Keep data transfer at a constant bit rate. */ + asrsync_sync(&io.asrs, pcm_samples / channels); /* reinitialize output buffer */ ffb_rewind(&bt); diff --git a/src/a2dp-faststream.c b/src/a2dp-faststream.c index 08b704928..8259aa397 100644 --- a/src/a2dp-faststream.c +++ b/src/a2dp-faststream.c @@ -1,6 +1,6 @@ /* * BlueALSA - a2dp-faststream.c - * Copyright (c) 2016-2024 Arkadiusz Bokowy + * Copyright (c) 2016-2025 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -24,6 +24,7 @@ #include "ba-config.h" #include "ba-transport.h" #include "ba-transport-pcm.h" +#include "bluealsa-dbus.h" #include "codec-sbc.h" #include "io.h" #include "shared/a2dp-codecs.h" @@ -156,6 +157,7 @@ void *a2dp_fs_enc_thread(struct ba_transport_pcm *t_pcm) { const unsigned int sbc_delay_frames = 73; /* Get the total delay introduced by the codec. */ t_pcm->codec_delay_dms = sbc_delay_frames * 10000 / rate; + ba_transport_pcm_delay_sync(t_pcm, BA_DBUS_PCM_UPDATE_DELAY); debug_transport_pcm_thread_loop(t_pcm, "START"); for (ba_transport_pcm_state_set_running(t_pcm);;) { @@ -213,15 +215,19 @@ void *a2dp_fs_enc_thread(struct ba_transport_pcm *t_pcm) { goto fail; } + if (!io.initiated) { + /* Get the delay due to codec processing. */ + t_pcm->processing_delay_dms = asrsync_get_dms_since_last_sync(&io.asrs); + ba_transport_pcm_delay_sync(t_pcm, BA_DBUS_PCM_UPDATE_DELAY); + io.initiated = true; + } + /* make room for new FastStream frames */ ffb_rewind(&bt); - /* keep data transfer at a constant bit rate */ + /* Keep data transfer at a constant bit rate. */ asrsync_sync(&io.asrs, pcm_frames); - /* update busy delay (encoding overhead) */ - t_pcm->processing_delay_dms = asrsync_get_busy_usec(&io.asrs) / 100; - /* If the input buffer was not consumed (due to codesize limit), we * have to append new data to the existing one. Since we do not use * ring buffer, we will simply move unprocessed data to the front diff --git a/src/a2dp-lc3plus.c b/src/a2dp-lc3plus.c index 66f782cfd..b128dcd4a 100644 --- a/src/a2dp-lc3plus.c +++ b/src/a2dp-lc3plus.c @@ -1,6 +1,6 @@ /* * BlueALSA - a2dp-lc3plus.c - * Copyright (c) 2016-2024 Arkadiusz Bokowy + * Copyright (c) 2016-2025 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -30,6 +30,7 @@ #include "ba-config.h" #include "ba-transport.h" #include "ba-transport-pcm.h" +#include "bluealsa-dbus.h" #include "io.h" #include "rtp.h" #include "utils.h" @@ -253,6 +254,7 @@ void *a2dp_lc3plus_enc_thread(struct ba_transport_pcm *t_pcm) { * delay in the decoder thread. */ const int lc3plus_delay_frames = lc3plus_enc_get_delay(handle); t_pcm->codec_delay_dms = lc3plus_delay_frames * 10000 / rate; + ba_transport_pcm_delay_sync(t_pcm, BA_DBUS_PCM_UPDATE_DELAY); rtp_header_t *rtp_header; rtp_media_header_t *rtp_media_header; @@ -353,6 +355,13 @@ void *a2dp_lc3plus_enc_thread(struct ba_transport_pcm *t_pcm) { goto fail; } + if (!io.initiated) { + /* Get the delay due to codec processing. */ + t_pcm->processing_delay_dms = asrsync_get_dms_since_last_sync(&io.asrs); + ba_transport_pcm_delay_sync(t_pcm, BA_DBUS_PCM_UPDATE_DELAY); + io.initiated = true; + } + /* resend RTP headers */ len -= rtp_headers_len; @@ -370,14 +379,11 @@ void *a2dp_lc3plus_enc_thread(struct ba_transport_pcm *t_pcm) { } - /* keep data transfer at a constant bit rate */ + /* Keep data transfer at a constant bit rate. */ asrsync_sync(&io.asrs, pcm_frames); /* move forward RTP timestamp clock */ rtp_state_update(&rtp, pcm_frames); - /* update busy delay (encoding overhead) */ - t_pcm->processing_delay_dms = asrsync_get_busy_usec(&io.asrs) / 100; - /* If the input buffer was not consumed (due to codesize limit), we * have to append new data to the existing one. Since we do not use * ring buffer, we will simply move unprocessed data to the front diff --git a/src/a2dp-ldac.c b/src/a2dp-ldac.c index 53004d615..1e10e0c06 100644 --- a/src/a2dp-ldac.c +++ b/src/a2dp-ldac.c @@ -1,6 +1,6 @@ /* * BlueALSA - a2dp-ldac.c - * Copyright (c) 2016-2024 Arkadiusz Bokowy + * Copyright (c) 2016-2025 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -27,6 +27,7 @@ #include "ba-transport.h" #include "ba-transport-pcm.h" #include "ba-config.h" +#include "bluealsa-dbus.h" #include "io.h" #include "rtp.h" #include "utils.h" @@ -169,6 +170,7 @@ void *a2dp_ldac_enc_thread(struct ba_transport_pcm *t_pcm) { const unsigned int ldac_delay_frames = 128; /* Get the total delay introduced by the codec. */ t_pcm->codec_delay_dms = ldac_delay_frames * 10000 / rate; + ba_transport_pcm_delay_sync(t_pcm, BA_DBUS_PCM_UPDATE_DELAY); rtp_header_t *rtp_header; rtp_media_header_t *rtp_media_header; @@ -244,6 +246,13 @@ void *a2dp_ldac_enc_thread(struct ba_transport_pcm *t_pcm) { goto fail; } + if (!io.initiated) { + /* Get the delay due to codec processing. */ + t_pcm->processing_delay_dms = asrsync_get_dms_since_last_sync(&io.asrs); + ba_transport_pcm_delay_sync(t_pcm, BA_DBUS_PCM_UPDATE_DELAY); + io.initiated = true; + } + if (errno == EAGAIN) /* The io_bt_write() call was blocking due to not enough * space in the BT socket. Set the queued_bytes to some @@ -256,14 +265,11 @@ void *a2dp_ldac_enc_thread(struct ba_transport_pcm *t_pcm) { } unsigned int pcm_frames = pcm_samples / channels; - /* keep data transfer at a constant bit rate */ + /* Keep data transfer at a constant bit rate. */ asrsync_sync(&io.asrs, pcm_frames); /* move forward RTP timestamp clock */ rtp_state_update(&rtp, pcm_frames); - /* update busy delay (encoding overhead) */ - t_pcm->processing_delay_dms = asrsync_get_busy_usec(&io.asrs) / 100; - } /* If the input buffer was not consumed (due to codesize limit), we diff --git a/src/a2dp-lhdc.c b/src/a2dp-lhdc.c index 3df791637..80be556f1 100644 --- a/src/a2dp-lhdc.c +++ b/src/a2dp-lhdc.c @@ -1,6 +1,6 @@ /* * BlueALSA - a2dp-lhdc.c - * Copyright (c) 2016-2024 Arkadiusz Bokowy + * Copyright (c) 2016-2025 Arkadiusz Bokowy * Copyright (c) 2023 anonymix007 * * This file is a part of bluez-alsa. @@ -29,6 +29,7 @@ #include "ba-transport.h" #include "ba-transport-pcm.h" #include "ba-config.h" +#include "bluealsa-dbus.h" #include "io.h" #include "rtp.h" #include "utils.h" @@ -197,6 +198,7 @@ void *a2dp_lhdc_enc_thread(struct ba_transport_pcm *t_pcm) { const unsigned int ldac_delay_frames = 1024; /* Get the total delay introduced by the codec. */ t_pcm->codec_delay_dms = ldac_delay_frames * 10000 / rate; + ba_transport_pcm_delay_sync(t_pcm, BA_DBUS_PCM_UPDATE_DELAY); rtp_header_t *rtp_header; rtp_lhdc_media_header_t *rtp_lhdc_media_header; @@ -275,6 +277,13 @@ void *a2dp_lhdc_enc_thread(struct ba_transport_pcm *t_pcm) { goto fail; } + if (!io.initiated) { + /* Get the delay due to codec processing. */ + t_pcm->processing_delay_dms = asrsync_get_dms_since_last_sync(&io.asrs); + ba_transport_pcm_delay_sync(t_pcm, BA_DBUS_PCM_UPDATE_DELAY); + io.initiated = true; + } + if (errno == EAGAIN) /* The io_bt_write() call was blocking due to not enough * space in the BT socket. Set the queued_bytes to some @@ -287,14 +296,11 @@ void *a2dp_lhdc_enc_thread(struct ba_transport_pcm *t_pcm) { } unsigned int pcm_frames = lhdc_pcm_samples / channels; - /* keep data transfer at a constant bit rate */ + /* Keep data transfer at a constant bit rate. */ asrsync_sync(&io.asrs, pcm_frames); /* move forward RTP timestamp clock */ rtp_state_update(&rtp, pcm_frames); - /* update busy delay (encoding overhead) */ - t_pcm->processing_delay_dms = asrsync_get_busy_usec(&io.asrs) / 100; - } /* If the input buffer was not consumed (due to codesize limit), we diff --git a/src/a2dp-mpeg.c b/src/a2dp-mpeg.c index cf9cefc87..19568f8be 100644 --- a/src/a2dp-mpeg.c +++ b/src/a2dp-mpeg.c @@ -1,6 +1,6 @@ /* * BlueALSA - a2dp-mpeg.c - * Copyright (c) 2016-2024 Arkadiusz Bokowy + * Copyright (c) 2016-2025 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -35,6 +35,7 @@ #include "ba-config.h" #include "ba-transport.h" #include "ba-transport-pcm.h" +#include "bluealsa-dbus.h" #include "io.h" #include "rtp.h" #include "utils.h" @@ -229,6 +230,7 @@ void *a2dp_mp3_enc_thread(struct ba_transport_pcm *t_pcm) { /* Get the total delay introduced by the codec. */ const int mpeg_delay_frames = lame_get_encoder_delay(handle); t_pcm->codec_delay_dms = mpeg_delay_frames * 10000 / rate; + ba_transport_pcm_delay_sync(t_pcm, BA_DBUS_PCM_UPDATE_DELAY); rtp_header_t *rtp_header; rtp_mpeg_audio_header_t *rtp_mpeg_audio_header; @@ -294,6 +296,13 @@ void *a2dp_mp3_enc_thread(struct ba_transport_pcm *t_pcm) { goto fail; } + if (!io.initiated) { + /* Get the delay due to codec processing. */ + t_pcm->processing_delay_dms = asrsync_get_dms_since_last_sync(&io.asrs); + ba_transport_pcm_delay_sync(t_pcm, BA_DBUS_PCM_UPDATE_DELAY); + io.initiated = true; + } + /* account written payload only */ len -= RTP_HEADER_LEN + sizeof(*rtp_mpeg_audio_header); @@ -309,14 +318,11 @@ void *a2dp_mp3_enc_thread(struct ba_transport_pcm *t_pcm) { } - /* keep data transfer at a constant bit rate */ + /* Keep data transfer at a constant bit rate. */ asrsync_sync(&io.asrs, pcm_frames); /* move forward RTP timestamp clock */ rtp_state_update(&rtp, pcm_frames); - /* update busy delay (encoding overhead) */ - t_pcm->processing_delay_dms = asrsync_get_busy_usec(&io.asrs) / 100; - /* If the input buffer was not consumed (due to frame alignment), we * have to append new data to the existing one. Since we do not use * ring buffer, we will simply move unprocessed data to the front diff --git a/src/a2dp-opus.c b/src/a2dp-opus.c index 2e82f86fb..02b3c4832 100644 --- a/src/a2dp-opus.c +++ b/src/a2dp-opus.c @@ -1,6 +1,6 @@ /* * BlueALSA - a2dp-opus.c - * Copyright (c) 2016-2024 Arkadiusz Bokowy + * Copyright (c) 2016-2025 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -28,6 +28,7 @@ #include "ba-config.h" #include "ba-transport.h" #include "ba-transport-pcm.h" +#include "bluealsa-dbus.h" #include "io.h" #include "rtp.h" #include "shared/a2dp-codecs.h" @@ -172,6 +173,7 @@ void *a2dp_opus_enc_thread(struct ba_transport_pcm *t_pcm) { /* Get the delay introduced by the encoder. */ opus_encoder_ctl(opus, OPUS_GET_LOOKAHEAD(&opus_delay_frames)); t_pcm->codec_delay_dms = opus_delay_frames * 10000 / rate; + ba_transport_pcm_delay_sync(t_pcm, BA_DBUS_PCM_UPDATE_DELAY); rtp_header_t *rtp_header; rtp_media_header_t *rtp_media_header; @@ -230,14 +232,18 @@ void *a2dp_opus_enc_thread(struct ba_transport_pcm *t_pcm) { goto fail; } - /* keep data transfer at a constant bit rate */ + if (!io.initiated) { + /* Get the delay due to codec processing. */ + t_pcm->processing_delay_dms = asrsync_get_dms_since_last_sync(&io.asrs); + ba_transport_pcm_delay_sync(t_pcm, BA_DBUS_PCM_UPDATE_DELAY); + io.initiated = true; + } + + /* Keep data transfer at a constant bit rate. */ asrsync_sync(&io.asrs, opus_frame_pcm_frames); /* move forward RTP timestamp clock */ rtp_state_update(&rtp, opus_frame_pcm_frames); - /* update busy delay (encoding overhead) */ - t_pcm->processing_delay_dms = asrsync_get_busy_usec(&io.asrs) / 100; - /* If the input buffer was not consumed (due to encoder frame * constraint), we have to append new data to the existing one. * Since we do not use ring buffer, we will simply move data @@ -303,6 +309,7 @@ void *a2dp_opus_dec_thread(struct ba_transport_pcm *t_pcm) { /* Get the delay introduced by the decoder. */ opus_decoder_ctl(opus, OPUS_GET_LOOKAHEAD(&opus_delay_frames)); t_pcm->codec_delay_dms = opus_delay_frames * 10000 / rate; + ba_transport_pcm_delay_sync(t_pcm, BA_DBUS_PCM_UPDATE_DELAY); struct rtp_state rtp = { .synced = false }; /* RTP clock frequency equal to PCM sample rate */ diff --git a/src/a2dp-sbc.c b/src/a2dp-sbc.c index b62a83d4f..71845a720 100644 --- a/src/a2dp-sbc.c +++ b/src/a2dp-sbc.c @@ -1,6 +1,6 @@ /* * BlueALSA - a2dp-sbc.c - * Copyright (c) 2016-2024 Arkadiusz Bokowy + * Copyright (c) 2016-2025 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -29,6 +29,7 @@ #include "ba-transport.h" #include "ba-transport-pcm.h" #include "ba-config.h" +#include "bluealsa-dbus.h" #include "codec-sbc.h" #include "io.h" #include "rtp.h" @@ -180,6 +181,7 @@ void *a2dp_sbc_enc_thread(struct ba_transport_pcm *t_pcm) { const unsigned int sbc_delay_frames = 73; /* Get the total delay introduced by the codec. */ t_pcm->codec_delay_dms = sbc_delay_frames * 10000 / rate; + ba_transport_pcm_delay_sync(t_pcm, BA_DBUS_PCM_UPDATE_DELAY); rtp_header_t *rtp_header; rtp_media_header_t *rtp_media_header; @@ -259,14 +261,23 @@ void *a2dp_sbc_enc_thread(struct ba_transport_pcm *t_pcm) { goto fail; } - /* keep data transfer at a constant bit rate */ + if (!io.initiated) { + /* Get the codec processing delay, which is a time spent in the + * processing loop between reading PCM data and writing the first + * encoded SBC frame. Subsequently encoded frames do not contribute + * to the delay, because (assuming no underruns) since the first + * frame is written, the BT sink can start decoding and playing + * audio in a continuous fashion. */ + t_pcm->processing_delay_dms = asrsync_get_dms_since_last_sync(&io.asrs); + ba_transport_pcm_delay_sync(t_pcm, BA_DBUS_PCM_UPDATE_DELAY); + io.initiated = true; + } + + /* Keep data transfer at a constant bit rate. */ asrsync_sync(&io.asrs, pcm_frames); /* move forward RTP timestamp clock */ rtp_state_update(&rtp, pcm_frames); - /* update busy delay (encoding overhead) */ - t_pcm->processing_delay_dms = asrsync_get_busy_usec(&io.asrs) / 100; - /* If the input buffer was not consumed (due to codesize limit), we * have to append new data to the existing one. Since we do not use * ring buffer, we will simply move unprocessed data to the front diff --git a/src/ba-transport-pcm.c b/src/ba-transport-pcm.c index d9306aa14..3e5678843 100644 --- a/src/ba-transport-pcm.c +++ b/src/ba-transport-pcm.c @@ -750,39 +750,53 @@ int ba_transport_pcm_delay_get(const struct ba_transport_pcm *pcm) { int ba_transport_pcm_delay_sync(struct ba_transport_pcm *pcm, unsigned int update_mask) { struct ba_transport *t = pcm->t; - int delay = 0; - - delay += pcm->codec_delay_dms; - delay += pcm->processing_delay_dms; - delay += pcm->client_delay_dms; /* In case of A2DP Sink, update the delay property of the BlueZ media * transport interface. BlueZ should forward this value to the remote * device, so it can adjust audio/video synchronization. */ - if (t->profile == BA_TRANSPORT_PROFILE_A2DP_SINK && - t->a2dp.delay_reporting && - abs(delay - t->a2dp.delay) >= 100 /* 10ms */) { - - GError *err = NULL; - t->a2dp.delay = delay; - 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) - /* Even though BlueZ documentation says that the Delay property is - * read-write, it might not be true. In case when the delay write - * operation fails with "not writable" error, we should not try to - * update the delay report value any more. */ - t->a2dp.delay_reporting = false; - warn("Couldn't set A2DP transport delay: %s", err->message); - g_error_free(err); + if (t->profile == BA_TRANSPORT_PROFILE_A2DP_SINK) { + + int delay = 0; + delay += pcm->codec_delay_dms; + delay += pcm->processing_delay_dms; + delay += pcm->client_delay_dms; + + if (t->a2dp.delay_reporting && + abs(delay - t->a2dp.delay) >= 100 /* 10ms */) { + + GError *err = NULL; + t->a2dp.delay = delay; + 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) + /* Even though BlueZ documentation says that the Delay + * property is read-write, it might not be true. In case + * when the delay write operation fails with "not writable" + * error, we should not try to update the delay report + * value any more. */ + t->a2dp.delay_reporting = false; + warn("Couldn't set A2DP transport delay: %s", err->message); + g_error_free(err); + } + } + } + if (update_mask & BA_DBUS_PCM_UPDATE_DELAY) { + /* To avoid creating a flood of D-Bus signals, we only notify clients + * when the codec + processing value changes by more than 10ms. */ + int delay = pcm->codec_delay_dms + pcm->processing_delay_dms; + if (abs(delay - (int)pcm->reported_codec_delay_dms) < 100 /* 10ms */) + goto final; + pcm->reported_codec_delay_dms = delay; } /* Notify all connected D-Bus clients. */ bluealsa_dbus_pcm_update(pcm, update_mask); + +final: return 0; } diff --git a/src/ba-transport-pcm.h b/src/ba-transport-pcm.h index acd1e9d85..7ff4f436c 100644 --- a/src/ba-transport-pcm.h +++ b/src/ba-transport-pcm.h @@ -129,6 +129,9 @@ struct ba_transport_pcm { * the host computational power. It is used to compensate for the time * required to encode or decode audio. */ unsigned int processing_delay_dms; + /* The last reported total codec + processing delay. It is used to limit + * the rate at which changes are reported via D-Bus. */ + unsigned int reported_codec_delay_dms; /* Positive (or negative) delay reported by the client. */ int client_delay_dms; diff --git a/src/io.c b/src/io.c index 2bbcfc7c8..1a8f9dfbf 100644 --- a/src/io.c +++ b/src/io.c @@ -347,6 +347,7 @@ ssize_t io_poll_and_read_pcm( case BA_TRANSPORT_PCM_SIGNAL_OPEN: case BA_TRANSPORT_PCM_SIGNAL_RESUME: io->asrs.frames = 0; + io->initiated = false; io->timeout = -1; goto repoll; case BA_TRANSPORT_PCM_SIGNAL_CLOSE: diff --git a/src/io.h b/src/io.h index d619e3976..eb29f3aa4 100644 --- a/src/io.h +++ b/src/io.h @@ -1,6 +1,6 @@ /* * BlueALSA - io.h - * Copyright (c) 2016-2024 Arkadiusz Bokowy + * Copyright (c) 2016-2025 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -16,6 +16,7 @@ # include #endif +#include #include #include "ba-transport-pcm.h" @@ -30,6 +31,8 @@ struct io_poll { /* transfer bit rate synchronization */ struct asrsync asrs; + /* transfer has been initiated */ + bool initiated; /* keep-alive and sync timeout */ int timeout; }; diff --git a/src/sco-cvsd.c b/src/sco-cvsd.c index dea164c16..e59b1d688 100644 --- a/src/sco-cvsd.c +++ b/src/sco-cvsd.c @@ -1,6 +1,6 @@ /* * BlueALSA - sco-cvsd.c - * Copyright (c) 2016-2024 Arkadiusz Bokowy + * Copyright (c) 2016-2025 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -76,10 +76,8 @@ void *sco_cvsd_enc_thread(struct ba_transport_pcm *t_pcm) { input += mtu_samples; input_samples -= mtu_samples; - /* keep data transfer at a constant bit rate */ + /* Keep data transfer at a constant bit rate. */ asrsync_sync(&io.asrs, mtu_samples); - /* update busy delay (encoding overhead) */ - t_pcm->processing_delay_dms = asrsync_get_busy_usec(&io.asrs) / 100; } diff --git a/src/sco-lc3-swb.c b/src/sco-lc3-swb.c index b9efb590f..196340f50 100644 --- a/src/sco-lc3-swb.c +++ b/src/sco-lc3-swb.c @@ -1,6 +1,6 @@ /* * BlueALSA - sco-lc3-swb.c - * Copyright (c) 2016-2024 Arkadiusz Bokowy + * Copyright (c) 2016-2025 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -16,12 +16,14 @@ #include #include +#include #include #include #include #include "ba-transport.h" #include "ba-transport-pcm.h" +#include "bluealsa-dbus.h" #include "codec-lc3-swb.h" #include "io.h" #include "shared/defs.h" @@ -44,6 +46,7 @@ void *sco_lc3_swb_enc_thread(struct ba_transport_pcm *t_pcm) { /* Get the total delay introduced by the codec. */ const ssize_t lc3_swb_delay_frames = lc3_swb_get_delay(&codec); t_pcm->codec_delay_dms = lc3_swb_delay_frames * 10000 / t_pcm->rate; + ba_transport_pcm_delay_sync(t_pcm, BA_DBUS_PCM_UPDATE_DELAY); debug_transport_pcm_thread_loop(t_pcm, "START"); for (ba_transport_pcm_state_set_running(t_pcm);;) { @@ -77,15 +80,20 @@ void *sco_lc3_swb_enc_thread(struct ba_transport_pcm *t_pcm) { goto exit; } + if (!io.initiated) { + /* Get the delay due to codec processing. */ + t_pcm->processing_delay_dms = asrsync_get_dms_since_last_sync(&io.asrs); + ba_transport_pcm_delay_sync(t_pcm, BA_DBUS_PCM_UPDATE_DELAY); + io.initiated = true; + } + data += len; data_len -= len; } - /* keep data transfer at a constant bit rate */ + /* Keep data transfer at a constant bit rate. */ asrsync_sync(&io.asrs, codec.frames * LC3_SWB_CODESAMPLES); - /* update busy delay (encoding overhead) */ - t_pcm->processing_delay_dms = asrsync_get_busy_usec(&io.asrs) / 100; /* Move unprocessed data to the front of our linear * buffer and clear the LC3-SWB frame counter. */ diff --git a/src/sco-msbc.c b/src/sco-msbc.c index 3c9ceaae7..c1c817c25 100644 --- a/src/sco-msbc.c +++ b/src/sco-msbc.c @@ -1,6 +1,6 @@ /* * BlueALSA - sco-msbc.c - * Copyright (c) 2016-2024 Arkadiusz Bokowy + * Copyright (c) 2016-2025 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -19,6 +19,7 @@ #include "ba-transport.h" #include "ba-transport-pcm.h" +#include "bluealsa-dbus.h" #include "codec-msbc.h" #include "io.h" #include "shared/defs.h" @@ -46,6 +47,7 @@ void *sco_msbc_enc_thread(struct ba_transport_pcm *t_pcm) { const unsigned int sbc_delay_frames = 73; /* Get the total delay introduced by the codec. */ t_pcm->codec_delay_dms = sbc_delay_frames * 10000 / t_pcm->rate; + ba_transport_pcm_delay_sync(t_pcm, BA_DBUS_PCM_UPDATE_DELAY); debug_transport_pcm_thread_loop(t_pcm, "START"); for (ba_transport_pcm_state_set_running(t_pcm);;) { @@ -84,15 +86,20 @@ void *sco_msbc_enc_thread(struct ba_transport_pcm *t_pcm) { goto exit; } + if (!io.initiated) { + /* Get the delay due to codec processing. */ + t_pcm->processing_delay_dms = asrsync_get_dms_since_last_sync(&io.asrs); + ba_transport_pcm_delay_sync(t_pcm, BA_DBUS_PCM_UPDATE_DELAY); + io.initiated = true; + } + data += len; data_len -= len; } - /* keep data transfer at a constant bit rate */ + /* Keep data transfer at a constant bit rate. */ asrsync_sync(&io.asrs, msbc.frames * MSBC_CODESAMPLES); - /* update busy delay (encoding overhead) */ - t_pcm->processing_delay_dms = asrsync_get_busy_usec(&io.asrs) / 100; /* Move unprocessed data to the front of our linear * buffer and clear the mSBC frame counter. */ diff --git a/src/shared/a2dp-codecs.c b/src/shared/a2dp-codecs.c index 8470794fe..b49f7d4a8 100644 --- a/src/shared/a2dp-codecs.c +++ b/src/shared/a2dp-codecs.c @@ -1,6 +1,6 @@ /* * BlueALSA - a2dp-codecs.c - * Copyright (c) 2016-2024 Arkadiusz Bokowy + * Copyright (c) 2016-2025 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * diff --git a/src/shared/rt.c b/src/shared/rt.c index 5db47b4a2..c746edbd8 100644 --- a/src/shared/rt.c +++ b/src/shared/rt.c @@ -1,6 +1,6 @@ /* * BlueALSA - rt.h - * Copyright (c) 2016-2021 Arkadiusz Bokowy + * Copyright (c) 2016-2025 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -13,6 +13,18 @@ #include #include +/** + * Initialize rate synchronization. + * + * @param asrs Pointer to the rate synchronization structure. + * @param rate Synchronization sample rate. */ +void asrsync_init(struct asrsync *asrs, unsigned int rate) { + asrs->rate = rate; + gettimestamp(&asrs->ts0); + asrs->ts = asrs->ts0; + asrs->frames = 0; +} + /** * Synchronize time with the sample rate. * @@ -22,18 +34,13 @@ * the asrsync structure definition), this counter should be initialized * (zeroed) upon every transfer stop. * - * @param asrs Pointer to the time synchronization structure. - * @param frames Number of frames since the last call to this function. - * @return This function returns a positive value or zero respectively for - * the case, when the synchronization was required or when blocking was - * not necessary. If an error has occurred, -1 is returned and errno is - * set to indicate the error. */ -int asrsync_sync(struct asrsync *asrs, unsigned int frames) { + * @param asrs Pointer to the rate synchronization structure. + * @param frames Number of frames since the last call to this function. */ +void asrsync_sync(struct asrsync *asrs, unsigned int frames) { const unsigned int rate = asrs->rate; struct timespec ts_rate; struct timespec ts; - int rv = 0; asrs->frames += frames; frames = asrs->frames; @@ -42,18 +49,28 @@ int asrsync_sync(struct asrsync *asrs, unsigned int frames) { ts_rate.tv_nsec = 1000000000L / rate * (frames % rate); gettimestamp(&ts); - /* calculate delay since the last sync */ - timespecsub(&ts, &asrs->ts, &asrs->ts_busy); + asrs->synced = false; /* maintain constant rate */ timespecsub(&ts, &asrs->ts0, &ts); if (difftimespec(&ts, &ts_rate, &asrs->ts_idle) > 0) { nanosleep(&asrs->ts_idle, NULL); - rv = 1; + asrs->synced = true; } gettimestamp(&asrs->ts); - return rv; + +} + +/** + * Get the time duration in 1/10 of milliseconds since the last sync. */ +unsigned int asrsync_get_dms_since_last_sync(const struct asrsync *asrs) { + + struct timespec ts; + gettimestamp(&ts); + + timespecsub(&ts, &asrs->ts, &ts); + return ts.tv_sec * (1000000 / 100) + ts.tv_nsec / (1000 * 100); } /** diff --git a/src/shared/rt.h b/src/shared/rt.h index 1fc9db136..794fa1846 100644 --- a/src/shared/rt.h +++ b/src/shared/rt.h @@ -1,6 +1,6 @@ /* * BlueALSA - rt.h - * Copyright (c) 2016-2024 Arkadiusz Bokowy + * Copyright (c) 2016-2025 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -16,6 +16,7 @@ # include #endif +#include #include #include @@ -41,7 +42,7 @@ #endif /** - * Structure used for time synchronization. + * Structure used for rate synchronization. * * With the size of the frame counter being 32 bits, it is possible to track * up to ~24 hours, with the sample rate of 48 kHz. If it is insufficient, @@ -58,34 +59,19 @@ struct asrsync { /* transferred frames since ts0 */ uint32_t frames; - /* time spent outside of the sync function */ - struct timespec ts_busy; - /* If the asrsync_sync() returns a positive value, then this variable - * contains an amount of time used for synchronization. Otherwise, it - * contains an overdue time - synchronization was not possible due to - * too much time spent outside of the sync function. */ + /* Indicate whether the synchronization was required. */ + bool synced; + /* If synchronization was required this variable contains an amount of + * time used for the synchronization. Otherwise, it contains an overdue + * time - synchronization was not possible due to too much time spent + * outside of the sync function. */ struct timespec ts_idle; }; -/** - * Start (initialize) time synchronization. - * - * @param asrs Pointer to the time synchronization structure. - * @param sr Synchronization sample rate. */ -#define asrsync_init(asrs, sr) do { \ - (asrs)->rate = sr; \ - gettimestamp(&(asrs)->ts0); \ - (asrs)->ts = (asrs)->ts0; \ - (asrs)->frames = 0; \ - } while (0) - -int asrsync_sync(struct asrsync *asrs, unsigned int frames); - -/** - * Get the number of microseconds spent outside of the sync function. */ -#define asrsync_get_busy_usec(asrs) \ - ((asrs)->ts_busy.tv_nsec / 1000) +void asrsync_init(struct asrsync *asrs, unsigned int rate); +void asrsync_sync(struct asrsync *asrs, unsigned int frames); +unsigned int asrsync_get_dms_since_last_sync(const struct asrsync *asrs); /** * Get system monotonic time-stamp. diff --git a/test/test-a2dp.c b/test/test-a2dp.c index c69f9828e..0c1c95d70 100644 --- a/test/test-a2dp.c +++ b/test/test-a2dp.c @@ -51,6 +51,8 @@ int ba_transport_pcm_state_set(struct ba_transport_pcm *pcm, enum ba_transport_pcm_signal ba_transport_pcm_signal_recv(struct ba_transport_pcm *pcm) { (void)pcm; return -1; } void ba_transport_pcm_thread_cleanup(struct ba_transport_pcm *pcm) { (void)pcm; } +int ba_transport_pcm_delay_sync(struct ba_transport_pcm *pcm, unsigned int update_mask) { + (void)pcm; (void)update_mask; return -1; } CK_START_TEST(test_a2dp_codecs_codec_id_from_string) { ck_assert_uint_eq(a2dp_codecs_codec_id_from_string("SBC"), A2DP_CODEC_SBC);