mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-31 22:25:38 -04:00
bluez5: improve decode-buffer latency accuracy
Interpolate buffer level to current playback position, and change its definition so it directly corresponds to the total buffer latency. This is also a bit simpler.
This commit is contained in:
parent
41ca76685c
commit
46c89f1e0c
3 changed files with 126 additions and 136 deletions
|
|
@ -21,12 +21,8 @@
|
|||
* The regular timer cycle cannot be aligned with this, so process()
|
||||
* may occur at any time.
|
||||
*
|
||||
* The buffer level is the difference between the number of samples in
|
||||
* buffer immediately after receiving a packet, and the samples consumed
|
||||
* before receiving the next packet.
|
||||
*
|
||||
* The buffer level indicates how much any packet can be delayed without
|
||||
* underrun. If it is positive, there are no underruns.
|
||||
* The buffer level is the position of last received sample, relative to the current
|
||||
* playback position. If it is larger than duration, there is no underrun.
|
||||
*
|
||||
* The rate correction aims to maintain the average level at a safety margin.
|
||||
*/
|
||||
|
|
@ -45,16 +41,6 @@
|
|||
#define BUFFERING_RATE_DIFF_MAX 0.005
|
||||
|
||||
|
||||
/**
|
||||
* Safety margin.
|
||||
*
|
||||
* The spike is the long-window maximum difference
|
||||
* between minimum and average buffer level.
|
||||
*/
|
||||
#define BUFFERING_TARGET(spike,packet_size,max_buf) \
|
||||
SPA_CLAMP((spike)*3/2, (packet_size), (max_buf) - 2*(packet_size))
|
||||
|
||||
|
||||
struct spa_bt_decode_buffer
|
||||
{
|
||||
struct spa_log *log;
|
||||
|
|
@ -74,20 +60,20 @@ struct spa_bt_decode_buffer
|
|||
struct spa_bt_rate_control ctl;
|
||||
double corr;
|
||||
|
||||
uint32_t prev_consumed;
|
||||
uint32_t prev_avail;
|
||||
uint32_t prev_duration;
|
||||
uint32_t underrun;
|
||||
uint32_t duration;
|
||||
uint32_t pos;
|
||||
|
||||
int32_t target; /**< target buffer (0: automatic) */
|
||||
int32_t max_target;
|
||||
int32_t max_extra;
|
||||
|
||||
int32_t level;
|
||||
uint64_t next_nsec;
|
||||
double rate_diff;
|
||||
|
||||
uint8_t received:1;
|
||||
uint8_t buffering:1;
|
||||
};
|
||||
|
||||
static int spa_bt_decode_buffer_init(struct spa_bt_decode_buffer *this, struct spa_log *log,
|
||||
static inline int spa_bt_decode_buffer_init(struct spa_bt_decode_buffer *this, struct spa_log *log,
|
||||
uint32_t frame_size, uint32_t rate, uint32_t quantum_limit, uint32_t reserve)
|
||||
{
|
||||
spa_zero(*this);
|
||||
|
|
@ -100,7 +86,7 @@ static int spa_bt_decode_buffer_init(struct spa_bt_decode_buffer *this, struct s
|
|||
this->corr = 1.0;
|
||||
this->target = 0;
|
||||
this->buffering = true;
|
||||
this->max_target = INT32_MAX;
|
||||
this->max_extra = INT32_MAX;
|
||||
|
||||
spa_bt_rate_control_init(&this->ctl, 0);
|
||||
|
||||
|
|
@ -114,13 +100,13 @@ static int spa_bt_decode_buffer_init(struct spa_bt_decode_buffer *this, struct s
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void spa_bt_decode_buffer_clear(struct spa_bt_decode_buffer *this)
|
||||
static inline void spa_bt_decode_buffer_clear(struct spa_bt_decode_buffer *this)
|
||||
{
|
||||
free(this->buffer_decoded);
|
||||
spa_zero(*this);
|
||||
}
|
||||
|
||||
static void spa_bt_decode_buffer_compact(struct spa_bt_decode_buffer *this)
|
||||
static inline void spa_bt_decode_buffer_compact(struct spa_bt_decode_buffer *this)
|
||||
{
|
||||
uint32_t avail;
|
||||
|
||||
|
|
@ -153,23 +139,7 @@ done:
|
|||
spa_assert(this->buffer_size - this->write_index >= this->buffer_reserve);
|
||||
}
|
||||
|
||||
static void *spa_bt_decode_buffer_get_write(struct spa_bt_decode_buffer *this, uint32_t *avail)
|
||||
{
|
||||
spa_bt_decode_buffer_compact(this);
|
||||
spa_assert(this->buffer_size >= this->write_index);
|
||||
*avail = this->buffer_size - this->write_index;
|
||||
return SPA_PTROFF(this->buffer_decoded, this->write_index, void);
|
||||
}
|
||||
|
||||
static void spa_bt_decode_buffer_write_packet(struct spa_bt_decode_buffer *this, uint32_t size)
|
||||
{
|
||||
spa_assert(size % this->frame_size == 0);
|
||||
this->write_index += size;
|
||||
this->received = true;
|
||||
spa_bt_ptp_update(&this->packet_size, size / this->frame_size, size / this->frame_size);
|
||||
}
|
||||
|
||||
static void *spa_bt_decode_buffer_get_read(struct spa_bt_decode_buffer *this, uint32_t *avail)
|
||||
static inline void *spa_bt_decode_buffer_get_read(struct spa_bt_decode_buffer *this, uint32_t *avail)
|
||||
{
|
||||
spa_assert(this->write_index >= this->read_index);
|
||||
if (!this->buffering)
|
||||
|
|
@ -179,25 +149,49 @@ static void *spa_bt_decode_buffer_get_read(struct spa_bt_decode_buffer *this, ui
|
|||
return SPA_PTROFF(this->buffer_decoded, this->read_index, void);
|
||||
}
|
||||
|
||||
static void spa_bt_decode_buffer_read(struct spa_bt_decode_buffer *this, uint32_t size)
|
||||
static inline void spa_bt_decode_buffer_read(struct spa_bt_decode_buffer *this, uint32_t size)
|
||||
{
|
||||
spa_assert(size % this->frame_size == 0);
|
||||
this->read_index += size;
|
||||
}
|
||||
|
||||
static void spa_bt_decode_buffer_recover(struct spa_bt_decode_buffer *this)
|
||||
static inline void *spa_bt_decode_buffer_get_write(struct spa_bt_decode_buffer *this, uint32_t *avail)
|
||||
{
|
||||
spa_bt_decode_buffer_compact(this);
|
||||
spa_assert(this->buffer_size >= this->write_index);
|
||||
*avail = this->buffer_size - this->write_index;
|
||||
return SPA_PTROFF(this->buffer_decoded, this->write_index, void);
|
||||
}
|
||||
|
||||
static inline void spa_bt_decode_buffer_write_packet(struct spa_bt_decode_buffer *this, uint32_t size, uint64_t nsec)
|
||||
{
|
||||
int32_t remain;
|
||||
uint32_t avail;
|
||||
|
||||
spa_assert(size % this->frame_size == 0);
|
||||
this->write_index += size;
|
||||
spa_bt_ptp_update(&this->packet_size, size / this->frame_size, size / this->frame_size);
|
||||
|
||||
if (nsec && this->next_nsec && this->rate_diff != 0.0) {
|
||||
int64_t dt = (this->next_nsec >= nsec) ?
|
||||
(int64_t)(this->next_nsec - nsec) : -(int64_t)(nsec - this->next_nsec);
|
||||
remain = (int32_t)SPA_CLAMP(dt * this->rate_diff * this->rate / SPA_NSEC_PER_SEC,
|
||||
-(int32_t)this->duration, this->duration);
|
||||
} else {
|
||||
remain = 0;
|
||||
}
|
||||
|
||||
spa_bt_decode_buffer_get_read(this, &avail);
|
||||
this->level = avail / this->frame_size + remain;
|
||||
}
|
||||
|
||||
static inline void spa_bt_decode_buffer_recover(struct spa_bt_decode_buffer *this)
|
||||
{
|
||||
int32_t size = (this->write_index - this->read_index) / this->frame_size;
|
||||
int32_t level;
|
||||
|
||||
this->prev_avail = size * this->frame_size;
|
||||
this->prev_consumed = this->prev_duration;
|
||||
|
||||
level = (int32_t)this->prev_avail/this->frame_size
|
||||
- (int32_t)this->prev_duration;
|
||||
this->level = size;
|
||||
this->corr = 1.0;
|
||||
|
||||
spa_bt_rate_control_init(&this->ctl, level);
|
||||
spa_bt_rate_control_init(&this->ctl, size);
|
||||
}
|
||||
|
||||
static inline void spa_bt_decode_buffer_set_target_latency(struct spa_bt_decode_buffer *this, int32_t samples)
|
||||
|
|
@ -205,37 +199,49 @@ static inline void spa_bt_decode_buffer_set_target_latency(struct spa_bt_decode_
|
|||
this->target = samples;
|
||||
}
|
||||
|
||||
static inline void spa_bt_decode_buffer_set_max_latency(struct spa_bt_decode_buffer *this, int32_t samples)
|
||||
static inline void spa_bt_decode_buffer_set_max_extra_latency(struct spa_bt_decode_buffer *this, int32_t samples)
|
||||
{
|
||||
this->max_target = samples;
|
||||
this->max_extra = samples;
|
||||
}
|
||||
|
||||
static inline int32_t spa_bt_decode_buffer_get_target(struct spa_bt_decode_buffer *this)
|
||||
static inline int32_t spa_bt_decode_buffer_get_target_latency(struct spa_bt_decode_buffer *this)
|
||||
{
|
||||
const int32_t duration = this->duration;
|
||||
const int32_t packet_size = SPA_CLAMP(this->packet_size.max, 0, INT32_MAX/8);
|
||||
const int32_t max_buf = (this->buffer_size - this->buffer_reserve) / this->frame_size;
|
||||
const int32_t spike = SPA_CLAMP(this->spike.max, 0, max_buf);
|
||||
int32_t target;
|
||||
|
||||
if (this->target)
|
||||
target = this->target;
|
||||
else
|
||||
target = BUFFERING_TARGET(this->spike.max, packet_size, max_buf);
|
||||
target = SPA_CLAMP(SPA_ROUND_UP(SPA_MAX(spike * 3/2, duration),
|
||||
SPA_CLAMP((int)this->rate / 50, 1, INT32_MAX)),
|
||||
duration, max_buf - 2*packet_size);
|
||||
|
||||
return SPA_MIN(target, this->max_target);
|
||||
return SPA_MIN(target, duration + SPA_CLAMP(this->max_extra, 0, INT32_MAX - duration));
|
||||
}
|
||||
|
||||
static void spa_bt_decode_buffer_process(struct spa_bt_decode_buffer *this, uint32_t samples, uint32_t duration)
|
||||
static inline void spa_bt_decode_buffer_process(struct spa_bt_decode_buffer *this, uint32_t samples, uint32_t duration,
|
||||
double rate_diff, uint64_t next_nsec)
|
||||
{
|
||||
const uint32_t data_size = samples * this->frame_size;
|
||||
const int32_t packet_size = SPA_CLAMP(this->packet_size.max, 0, INT32_MAX/8);
|
||||
const int32_t max_level = SPA_MAX(8 * packet_size, (int32_t)duration);
|
||||
const uint32_t avg_period = (uint64_t)this->rate * BUFFERING_SHORT_MSEC / 1000;
|
||||
int32_t target;
|
||||
uint32_t avail;
|
||||
|
||||
if (SPA_UNLIKELY(duration != this->prev_duration)) {
|
||||
this->prev_duration = duration;
|
||||
this->rate_diff = rate_diff;
|
||||
this->next_nsec = next_nsec;
|
||||
|
||||
if (SPA_UNLIKELY(duration != this->duration)) {
|
||||
this->duration = duration;
|
||||
spa_bt_decode_buffer_recover(this);
|
||||
}
|
||||
|
||||
target = spa_bt_decode_buffer_get_target_latency(this);
|
||||
|
||||
if (SPA_UNLIKELY(this->buffering)) {
|
||||
int32_t size = (this->write_index - this->read_index) / this->frame_size;
|
||||
|
||||
|
|
@ -243,83 +249,63 @@ static void spa_bt_decode_buffer_process(struct spa_bt_decode_buffer *this, uint
|
|||
|
||||
spa_log_trace(this->log, "%p buffering size:%d", this, (int)size);
|
||||
|
||||
if (this->received &&
|
||||
packet_size > 0 &&
|
||||
size >= SPA_MAX(3*packet_size, (int32_t)duration))
|
||||
if (size >= SPA_MAX((int)duration, target))
|
||||
this->buffering = false;
|
||||
else
|
||||
return;
|
||||
|
||||
spa_bt_ptp_update(&this->spike, packet_size, duration);
|
||||
spa_bt_decode_buffer_recover(this);
|
||||
}
|
||||
|
||||
spa_bt_decode_buffer_get_read(this, &avail);
|
||||
|
||||
if (this->received) {
|
||||
const uint32_t avg_period = (uint64_t)this->rate * BUFFERING_SHORT_MSEC / 1000;
|
||||
int32_t level, target;
|
||||
/* Track buffer level */
|
||||
this->level = SPA_MAX(this->level, -max_level);
|
||||
|
||||
/* Track buffer level */
|
||||
level = (int32_t)(this->prev_avail/this->frame_size) - (int32_t)this->prev_consumed;
|
||||
level = SPA_MAX(level, -max_level);
|
||||
this->prev_consumed = SPA_MIN(this->prev_consumed, avg_period);
|
||||
spa_bt_ptp_update(&this->spike, (int32_t)this->ctl.avg - this->level, duration);
|
||||
|
||||
spa_bt_ptp_update(&this->spike, (int32_t)(this->ctl.avg - level), this->prev_consumed);
|
||||
if (this->level > SPA_MAX(4 * target, 3*(int32_t)duration) &&
|
||||
avail > data_size) {
|
||||
/* Lagging too much: drop data */
|
||||
uint32_t size = SPA_MIN(avail - data_size,
|
||||
(this->level - target) * this->frame_size);
|
||||
|
||||
/* Update target level */
|
||||
target = spa_bt_decode_buffer_get_target(this);
|
||||
spa_bt_decode_buffer_read(this, size);
|
||||
spa_log_trace(this->log, "%p overrun samples:%d level:%d target:%d",
|
||||
this, (int)size/this->frame_size,
|
||||
(int)this->level, (int)target);
|
||||
|
||||
if (level > SPA_MAX(4 * target, 2*(int32_t)duration) &&
|
||||
avail > data_size) {
|
||||
/* Lagging too much: drop data */
|
||||
uint32_t size = SPA_MIN(avail - data_size,
|
||||
(level - target) * this->frame_size);
|
||||
|
||||
spa_bt_decode_buffer_read(this, size);
|
||||
spa_log_trace(this->log, "%p overrun samples:%d level:%d target:%d",
|
||||
this, (int)size/this->frame_size,
|
||||
(int)level, (int)target);
|
||||
|
||||
spa_bt_decode_buffer_recover(this);
|
||||
}
|
||||
|
||||
this->pos += this->prev_consumed;
|
||||
if (this->pos > this->rate) {
|
||||
spa_log_debug(this->log,
|
||||
"%p avg:%d target:%d level:%d buffer:%d spike:%d corr:%f",
|
||||
this,
|
||||
(int)this->ctl.avg,
|
||||
(int)target,
|
||||
(int)level,
|
||||
(int)(avail / this->frame_size),
|
||||
(int)this->spike.max,
|
||||
(double)this->corr);
|
||||
this->pos = 0;
|
||||
}
|
||||
|
||||
this->corr = spa_bt_rate_control_update(&this->ctl,
|
||||
level, target, this->prev_consumed, avg_period,
|
||||
BUFFERING_RATE_DIFF_MAX);
|
||||
|
||||
spa_bt_decode_buffer_get_read(this, &avail);
|
||||
|
||||
this->prev_consumed = 0;
|
||||
this->prev_avail = avail;
|
||||
this->underrun = 0;
|
||||
this->received = false;
|
||||
spa_bt_decode_buffer_recover(this);
|
||||
}
|
||||
|
||||
this->pos += duration;
|
||||
if (this->pos > this->rate) {
|
||||
spa_log_debug(this->log,
|
||||
"%p avg:%d target:%d level:%d buffer:%d spike:%d corr:%f",
|
||||
this,
|
||||
(int)this->ctl.avg,
|
||||
(int)target,
|
||||
(int)this->level,
|
||||
(int)(avail / this->frame_size),
|
||||
(int)this->spike.max,
|
||||
(double)this->corr);
|
||||
this->pos = 0;
|
||||
}
|
||||
|
||||
this->corr = spa_bt_rate_control_update(&this->ctl,
|
||||
this->level, target, duration, avg_period,
|
||||
BUFFERING_RATE_DIFF_MAX);
|
||||
|
||||
this->level -= duration;
|
||||
|
||||
spa_bt_decode_buffer_get_read(this, &avail);
|
||||
if (avail < data_size) {
|
||||
spa_log_trace(this->log, "%p underrun samples:%d", this,
|
||||
(data_size - avail) / this->frame_size);
|
||||
this->underrun += samples;
|
||||
if (this->underrun >= SPA_MIN((uint32_t)max_level, this->buffer_size / this->frame_size)) {
|
||||
this->buffering = true;
|
||||
spa_log_debug(this->log, "%p underrun too much: start buffering", this);
|
||||
}
|
||||
this->buffering = true;
|
||||
spa_bt_ptp_update(&this->spike, (int32_t)this->ctl.avg - this->level, duration);
|
||||
}
|
||||
|
||||
this->prev_consumed += samples;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue