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..5170e3c12 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. * @@ -189,11 +189,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..a1e312327 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. * @@ -77,15 +77,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.