[player] Fix handling of underrun/read_deficit

* Also call full_cb() from input_wait if buffer is full
* Make read_deficit count missing bytes instead of clock ticks
* Make read_deficit a part of the playback session
This commit is contained in:
ejurgensen 2019-02-24 00:44:11 +01:00
parent 143708368c
commit 977f8570a5
2 changed files with 30 additions and 16 deletions

View File

@ -477,6 +477,12 @@ input_wait(void)
// Is the buffer full? // Is the buffer full?
if (evbuffer_get_length(input_buffer.evbuf) > INPUT_BUFFER_THRESHOLD) if (evbuffer_get_length(input_buffer.evbuf) > INPUT_BUFFER_THRESHOLD)
{ {
if (input_buffer.full_cb)
{
input_buffer.full_cb();
input_buffer.full_cb = NULL;
}
pthread_mutex_unlock(&input_buffer.mutex); pthread_mutex_unlock(&input_buffer.mutex);
return -1; return -1;
} }

View File

@ -241,6 +241,11 @@ struct player_session
// Equals current number of samples written to outputs // Equals current number of samples written to outputs
uint32_t pos; uint32_t pos;
// We try to read a fixed number of bytes from the source each clock tick,
// but if it gives us less we increase this correspondingly
size_t read_deficit;
size_t read_deficit_max;
// The item from the queue being read by the input now, previously and next // The item from the queue being read by the input now, previously and next
struct player_source *reading_now; struct player_source *reading_now;
struct player_source *reading_next; struct player_source *reading_next;
@ -289,11 +294,7 @@ static struct timespec player_timer_res;
static struct timeval player_pause_timeout = { PLAYER_PAUSE_TIME_MAX, 0 }; static struct timeval player_pause_timeout = { PLAYER_PAUSE_TIME_MAX, 0 };
// How many writes we owe the output (when the input is underrunning) // PLAYER_WRITE_BEHIND_MAX converted to clock ticks
static int pb_read_deficit;
// PLAYER_READ_BEHIND_MAX and PLAYER_WRITE_BEHIND_MAX converted to clock ticks
static int pb_read_deficit_max;
static int pb_write_deficit_max; static int pb_write_deficit_max;
// True if we are trying to recover from a major playback timer overrun (write problems) // True if we are trying to recover from a major playback timer overrun (write problems)
@ -879,6 +880,7 @@ session_update_read_quality(struct media_quality *quality)
pb_session.reading_now->output_buffer_samples = OUTPUTS_BUFFER_DURATION * quality->sample_rate; pb_session.reading_now->output_buffer_samples = OUTPUTS_BUFFER_DURATION * quality->sample_rate;
pb_session.bufsize = STOB(samples_per_read, quality->bits_per_sample, quality->channels); pb_session.bufsize = STOB(samples_per_read, quality->bits_per_sample, quality->channels);
pb_session.read_deficit_max = STOB(((uint64_t)quality->sample_rate * PLAYER_READ_BEHIND_MAX) / 1000, quality->bits_per_sample, quality->channels);
DPRINTF(E_DBG, L_PLAYER, "New session values (q=%d/%d/%d, spr=%d, bufsize=%zu)\n", DPRINTF(E_DBG, L_PLAYER, "New session values (q=%d/%d/%d, spr=%d, bufsize=%zu)\n",
quality->sample_rate, quality->bits_per_sample, quality->channels, samples_per_read, pb_session.bufsize); quality->sample_rate, quality->bits_per_sample, quality->channels, samples_per_read, pb_session.bufsize);
@ -1155,46 +1157,55 @@ playback_cb(int fd, short what, void *arg)
session_dump(true); session_dump(true);
#endif #endif
// The pessimistic approach: Assume you won't get anything, then anything that
// comes your way is a positive surprise.
pb_session.read_deficit += (1 + overrun) * pb_session.bufsize;
// If there was an overrun, we will try to read/write a corresponding number // If there was an overrun, we will try to read/write a corresponding number
// of times so we catch up. The read from the input is non-blocking, so it // of times so we catch up. The read from the input is non-blocking, so it
// should not bring us further behind, even if there is no data. // should not bring us further behind, even if there is no data.
for (i = 1 + overrun + pb_read_deficit; i > 0; i--) for (i = 1 + overrun; i > 0; i--)
{ {
ret = source_read(&nbytes, &nsamples, &quality, pb_session.buffer, pb_session.bufsize); ret = source_read(&nbytes, &nsamples, &quality, pb_session.buffer, pb_session.bufsize);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_PLAYER, "Error reading from source\n"); DPRINTF(E_LOG, L_PLAYER, "Error reading from source\n");
pb_session.read_deficit -= pb_session.bufsize;
break; break;
} }
if (nbytes == 0) if (nbytes == 0)
{ {
pb_read_deficit++;
break; break;
} }
pb_session.read_deficit -= nbytes;
outputs_write(pb_session.buffer, pb_session.bufsize, &quality, nsamples, &pb_session.pts); outputs_write(pb_session.buffer, pb_session.bufsize, &quality, nsamples, &pb_session.pts);
if (nbytes < pb_session.bufsize) if (nbytes < pb_session.bufsize)
{ {
DPRINTF(E_DBG, L_PLAYER, "Incomplete read, wanted %zu, got %d\n", pb_session.bufsize, nbytes); DPRINTF(E_DBG, L_PLAYER, "Incomplete read, wanted %zu, got %d, deficit %zu\n", pb_session.bufsize, nbytes, pb_session.read_deficit);
// How much the number of samples we got corresponds to in time (nanoseconds) // How much the number of samples we got corresponds to in time (nanoseconds)
ts.tv_sec = 0; ts.tv_sec = 0;
ts.tv_nsec = 1000000000L * nsamples / quality.sample_rate; ts.tv_nsec = 1000000000L * nsamples / quality.sample_rate;
pb_session.pts = timespec_add(pb_session.pts, ts); pb_session.pts = timespec_add(pb_session.pts, ts);
pb_read_deficit++;
} }
else else
{ {
// We got a full frame, so that means we can also advance the presentation timestamp by a full tick // We got a full frame, so that means we can also advance the presentation timestamp by a full tick
pb_session.pts = timespec_add(pb_session.pts, player_tick_interval); pb_session.pts = timespec_add(pb_session.pts, player_tick_interval);
if (pb_read_deficit > 0)
pb_read_deficit--; // It is going well, lets take another round to repay our debt
if (i == 1 && pb_session.read_deficit > pb_session.bufsize)
i = 2;
} }
} }
if (pb_read_deficit > pb_read_deficit_max) if (pb_session.read_deficit_max && pb_session.read_deficit > pb_session.read_deficit_max)
{ {
DPRINTF(E_LOG, L_PLAYER, "Source is not providing sufficient data, temporarily suspending playback (deficit=%d)\n", pb_read_deficit); DPRINTF(E_LOG, L_PLAYER, "Source is not providing sufficient data, temporarily suspending playback (deficit=%zu/%zu bytes)\n",
pb_session.read_deficit, pb_session.read_deficit_max);
playback_suspend(); playback_suspend();
} }
} }
@ -1794,8 +1805,6 @@ playback_start_bh(void *arg, int *retval)
{ {
int ret; int ret;
pb_read_deficit = 0;
ret = playback_timer_start(); ret = playback_timer_start();
if (ret < 0) if (ret < 0)
goto error; goto error;
@ -3197,7 +3206,6 @@ player_init(void)
player_tick_interval.tv_nsec = interval; player_tick_interval.tv_nsec = interval;
pb_write_deficit_max = (PLAYER_WRITE_BEHIND_MAX * 1000000 / interval); pb_write_deficit_max = (PLAYER_WRITE_BEHIND_MAX * 1000000 / interval);
pb_read_deficit_max = (PLAYER_READ_BEHIND_MAX * 1000000 / interval);
// Create the playback timer // Create the playback timer
#ifdef HAVE_TIMERFD #ifdef HAVE_TIMERFD