From 80252d8910b372ed235fdb58fab16b3d326fb27a Mon Sep 17 00:00:00 2001 From: Arkadiusz Bokowy Date: Thu, 16 Jan 2025 18:41:30 +0100 Subject: [PATCH 1/2] Calculate codec processing delay on first transfer --- src/a2dp-aac.c | 13 +++++++----- src/a2dp-aptx-hd.c | 13 +++++++----- src/a2dp-aptx.c | 14 ++++++++----- src/a2dp-faststream.c | 13 +++++++----- src/a2dp-lc3plus.c | 13 +++++++----- src/a2dp-ldac.c | 13 +++++++----- src/a2dp-lhdc.c | 13 +++++++----- src/a2dp-mpeg.c | 13 +++++++----- src/a2dp-opus.c | 13 +++++++----- src/a2dp-sbc.c | 18 ++++++++++++----- src/io.c | 1 + src/io.h | 5 ++++- src/sco-cvsd.c | 6 ++---- src/sco-lc3-swb.c | 13 ++++++++---- src/sco-msbc.c | 12 +++++++---- src/shared/a2dp-codecs.c | 2 +- src/shared/rt.c | 43 ++++++++++++++++++++++++++++------------ src/shared/rt.h | 38 +++++++++++------------------------ 18 files changed, 153 insertions(+), 103 deletions(-) diff --git a/src/a2dp-aac.c b/src/a2dp-aac.c index 2e8a680d0..102e66e21 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. * @@ -374,6 +374,12 @@ 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); + io.initiated = true; + } + /* resend RTP header */ len -= RTP_HEADER_LEN; @@ -390,14 +396,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. */ diff --git a/src/a2dp-aptx-hd.c b/src/a2dp-aptx-hd.c index 16664f21d..1763b0bec 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. * @@ -205,15 +205,18 @@ 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); + 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..ae82795da 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 @@ -189,11 +190,14 @@ 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); + 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..2b0d090e1 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. * @@ -213,15 +213,18 @@ 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); + 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..a86827398 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. * @@ -353,6 +353,12 @@ 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); + io.initiated = true; + } + /* resend RTP headers */ len -= rtp_headers_len; @@ -370,14 +376,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..9ecc2e10e 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. * @@ -244,6 +244,12 @@ 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); + 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 +262,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..804ed47d6 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. @@ -275,6 +275,12 @@ 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); + 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 +293,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..4bbb81fd9 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. * @@ -294,6 +294,12 @@ 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); + io.initiated = true; + } + /* account written payload only */ len -= RTP_HEADER_LEN + sizeof(*rtp_mpeg_audio_header); @@ -309,14 +315,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..41140d143 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. * @@ -230,14 +230,17 @@ 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); + 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 diff --git a/src/a2dp-sbc.c b/src/a2dp-sbc.c index b62a83d4f..53bf667e3 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. * @@ -259,14 +259,22 @@ 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); + 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/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..7d4252fb3 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,6 +16,7 @@ #include #include +#include #include #include #include @@ -77,15 +78,19 @@ 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); + 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..5b8dda6a4 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. * @@ -84,15 +84,19 @@ 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); + 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. From 9819781ae7743b69ecd37cd7a12c1bd3b743d7e0 Mon Sep 17 00:00:00 2001 From: borine <32966433+borine@users.noreply.github.com> Date: Tue, 29 Oct 2024 09:29:00 +0000 Subject: [PATCH 2/2] Enable D-Bus delay signals for processing delay Make sure that clients are informed of significant changes to the decoder internal processing delay, even when used with remote devices that do not report their own delay. --- doc/a2dpconf.1.rst | 2 +- src/a2dp-aac.c | 4 +++ src/a2dp-aptx-hd.c | 2 ++ src/a2dp-aptx.c | 2 ++ src/a2dp-faststream.c | 3 +++ src/a2dp-lc3plus.c | 3 +++ src/a2dp-ldac.c | 3 +++ src/a2dp-lhdc.c | 3 +++ src/a2dp-mpeg.c | 3 +++ src/a2dp-opus.c | 4 +++ src/a2dp-sbc.c | 3 +++ src/ba-transport-pcm.c | 60 ++++++++++++++++++++++++++---------------- src/ba-transport-pcm.h | 3 +++ src/sco-lc3-swb.c | 3 +++ src/sco-msbc.c | 3 +++ test/test-a2dp.c | 2 ++ 16 files changed, 79 insertions(+), 24 deletions(-) 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 102e66e21..a51a154db 100644 --- a/src/a2dp-aac.c +++ b/src/a2dp-aac.c @@ -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 */ @@ -377,6 +379,7 @@ void *a2dp_aac_enc_thread(struct ba_transport_pcm *t_pcm) { 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; } @@ -556,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 1763b0bec..963b91583 100644 --- a/src/a2dp-aptx-hd.c +++ b/src/a2dp-aptx-hd.c @@ -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" @@ -208,6 +209,7 @@ void *a2dp_aptx_hd_enc_thread(struct ba_transport_pcm *t_pcm) { 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; } diff --git a/src/a2dp-aptx.c b/src/a2dp-aptx.c index ae82795da..0c5c81a2b 100644 --- a/src/a2dp-aptx.c +++ b/src/a2dp-aptx.c @@ -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 "shared/a2dp-codecs.h" @@ -193,6 +194,7 @@ void *a2dp_aptx_enc_thread(struct ba_transport_pcm *t_pcm) { 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; } diff --git a/src/a2dp-faststream.c b/src/a2dp-faststream.c index 2b0d090e1..8259aa397 100644 --- a/src/a2dp-faststream.c +++ b/src/a2dp-faststream.c @@ -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);;) { @@ -216,6 +218,7 @@ void *a2dp_fs_enc_thread(struct ba_transport_pcm *t_pcm) { 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; } diff --git a/src/a2dp-lc3plus.c b/src/a2dp-lc3plus.c index a86827398..b128dcd4a 100644 --- a/src/a2dp-lc3plus.c +++ b/src/a2dp-lc3plus.c @@ -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; @@ -356,6 +358,7 @@ void *a2dp_lc3plus_enc_thread(struct ba_transport_pcm *t_pcm) { 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; } diff --git a/src/a2dp-ldac.c b/src/a2dp-ldac.c index 9ecc2e10e..1e10e0c06 100644 --- a/src/a2dp-ldac.c +++ b/src/a2dp-ldac.c @@ -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; @@ -247,6 +249,7 @@ void *a2dp_ldac_enc_thread(struct ba_transport_pcm *t_pcm) { 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; } diff --git a/src/a2dp-lhdc.c b/src/a2dp-lhdc.c index 804ed47d6..80be556f1 100644 --- a/src/a2dp-lhdc.c +++ b/src/a2dp-lhdc.c @@ -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; @@ -278,6 +280,7 @@ void *a2dp_lhdc_enc_thread(struct ba_transport_pcm *t_pcm) { 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; } diff --git a/src/a2dp-mpeg.c b/src/a2dp-mpeg.c index 4bbb81fd9..19568f8be 100644 --- a/src/a2dp-mpeg.c +++ b/src/a2dp-mpeg.c @@ -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; @@ -297,6 +299,7 @@ void *a2dp_mp3_enc_thread(struct ba_transport_pcm *t_pcm) { 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; } diff --git a/src/a2dp-opus.c b/src/a2dp-opus.c index 41140d143..02b3c4832 100644 --- a/src/a2dp-opus.c +++ b/src/a2dp-opus.c @@ -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; @@ -233,6 +235,7 @@ void *a2dp_opus_enc_thread(struct ba_transport_pcm *t_pcm) { 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; } @@ -306,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 53bf667e3..71845a720 100644 --- a/src/a2dp-sbc.c +++ b/src/a2dp-sbc.c @@ -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; @@ -267,6 +269,7 @@ void *a2dp_sbc_enc_thread(struct ba_transport_pcm *t_pcm) { * 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; } 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/sco-lc3-swb.c b/src/sco-lc3-swb.c index 7d4252fb3..196340f50 100644 --- a/src/sco-lc3-swb.c +++ b/src/sco-lc3-swb.c @@ -23,6 +23,7 @@ #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" @@ -45,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);;) { @@ -81,6 +83,7 @@ void *sco_lc3_swb_enc_thread(struct ba_transport_pcm *t_pcm) { 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; } diff --git a/src/sco-msbc.c b/src/sco-msbc.c index 5b8dda6a4..c1c817c25 100644 --- a/src/sco-msbc.c +++ b/src/sco-msbc.c @@ -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);;) { @@ -87,6 +89,7 @@ void *sco_msbc_enc_thread(struct ba_transport_pcm *t_pcm) { 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; } 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);