[alsa] Make sync and sync evaluation period configurable

This commit is contained in:
ejurgensen 2019-04-09 21:45:16 +02:00
parent 40934e7162
commit dc65cb5b76
3 changed files with 33 additions and 15 deletions

View File

@ -217,12 +217,23 @@ audio {
# If not set, the value for "card" will be used.
# mixer_device = ""
# Synchronization
# If your local audio is out of sync with other speakers, e.g. Airplay,
# adjust this value. Negative values correspond to moving local audio
# ahead, positive correspond to delaying it. The unit is milliseconds.
# The offset must be between -1000 and 1000 (+/- 1 sec).
# Enable or disable audio resampling to keep local audio in sync with
# e.g. Airplay. This feature relies on accurate ALSA measurements of
# delay, and some devices don't provide that. If that is the case you
# are better off disabling the feature.
# sync_disable = false
# Here you can adjust when local audio is started relative to other
# speakers, e.g. Airplay. Negative values correspond to moving local
# audio ahead, positive correspond to delaying it. The unit is
# milliseconds. The offset must be between -1000 and 1000 (+/- 1 sec).
# offset_ms = 0
# To calculate what and if resampling is required, local audio delay is
# measured each second. After a period the collected measurements are
# used to estimate drift and latency, which determines if corrections
# are required. This setting sets the length of that period in seconds.
# adjust_period_seconds = 100
}
# Pipe output

View File

@ -115,8 +115,10 @@ static cfg_opt_t sec_audio[] =
CFG_STR("card", "default", CFGF_NONE),
CFG_STR("mixer", NULL, CFGF_NONE),
CFG_STR("mixer_device", NULL, CFGF_NONE),
CFG_BOOL("sync_disable", cfg_false, CFGF_NONE),
CFG_INT("offset", 0, CFGF_NONE), // deprecated
CFG_INT("offset_ms", 0, CFGF_NONE),
CFG_INT("adjust_period_seconds", 100, CFGF_NONE),
CFG_END()
};

View File

@ -49,9 +49,6 @@
// if r2 is below this value we won't attempt to correct sync.
#define ALSA_MAX_VARIANCE 0.2
// How many latency calculations we keep in the latency_history buffer
#define ALSA_LATENCY_HISTORY_SIZE 100
// We correct latency by adjusting the sample rate in steps. However, if the
// latency keeps drifting we give up after reaching this step.
#define ALSA_RESAMPLE_STEP_MAX 8
@ -100,7 +97,7 @@ struct alsa_session
// Array of latency calculations, where latency_counter tells how many are
// currently in the array
double latency_history[ALSA_LATENCY_HISTORY_SIZE];
double *latency_history;
int latency_counter;
int sync_resample_step;
@ -123,6 +120,9 @@ struct alsa_session
static struct alsa_session *sessions;
static bool alsa_sync_disable;
static int alsa_latency_history_size;
// We will try to play the music with the source quality, but if the card
// doesn't support that we resample to the fallback quality
static struct media_quality alsa_fallback_quality = { 44100, 16, 2 };
@ -636,20 +636,20 @@ sync_check(double *drift, double *latency, struct alsa_session *as, snd_pcm_sfra
exp_pos = (uint64_t)elapsed * as->quality.sample_rate / 1000;
diff = cur_pos - exp_pos;
DPRINTF(E_DBG, L_LAUDIO, "counter %d/%d, stamp %lu:%lu, now %lu:%lu, elapsed is %d ms, cur_pos=%" PRIu64 ", exp_pos=%" PRIu64 ", diff=%d\n",
as->latency_counter, ALSA_LATENCY_HISTORY_SIZE, as->stamp_pts.tv_sec, as->stamp_pts.tv_nsec / 1000000, ts.tv_sec, ts.tv_nsec / 1000000, elapsed, cur_pos, exp_pos, diff);
DPRINTF(E_SPAM, L_LAUDIO, "counter %d/%d, stamp %lu:%lu, now %lu:%lu, elapsed is %d ms, cur_pos=%" PRIu64 ", exp_pos=%" PRIu64 ", diff=%d\n",
as->latency_counter, alsa_latency_history_size, as->stamp_pts.tv_sec, as->stamp_pts.tv_nsec / 1000000, ts.tv_sec, ts.tv_nsec / 1000000, elapsed, cur_pos, exp_pos, diff);
// Add the latency to our measurement history
as->latency_history[as->latency_counter] = (double)diff;
as->latency_counter++;
// Haven't collected enough samples for sync evaluation yet, so just return
if (as->latency_counter < ALSA_LATENCY_HISTORY_SIZE)
if (as->latency_counter < alsa_latency_history_size)
return ALSA_SYNC_OK;
as->latency_counter = 0;
ret = linear_regression(drift, latency, &r2, NULL, as->latency_history, ALSA_LATENCY_HISTORY_SIZE);
ret = linear_regression(drift, latency, &r2, NULL, as->latency_history, alsa_latency_history_size);
if (ret < 0)
{
DPRINTF(E_WARN, L_LAUDIO, "Linear regression of collected latency samples failed\n");
@ -657,7 +657,7 @@ sync_check(double *drift, double *latency, struct alsa_session *as, snd_pcm_sfra
}
// Set *latency to the "average" within the period
*latency = (*drift) * ALSA_LATENCY_HISTORY_SIZE / 2 + (*latency);
*latency = (*drift) * alsa_latency_history_size / 2 + (*latency);
if (abs(*latency) < ALSA_MAX_LATENCY && abs(*drift) < ALSA_MAX_DRIFT)
sync = ALSA_SYNC_OK; // If both latency and drift are within thresholds -> no action
@ -757,7 +757,7 @@ playback_write(struct alsa_session *as, struct output_buffer *obuf)
}
// Check sync each second (or if this is first write where last_pts is zero)
if (obuf->pts.tv_sec != as->last_pts.tv_sec)
if (!alsa_sync_disable && (obuf->pts.tv_sec != as->last_pts.tv_sec))
{
ret = snd_pcm_delay(as->hdl, &delay);
if (ret == 0)
@ -874,6 +874,8 @@ alsa_session_make(struct output_device *device, int callback_id)
as->offset_ms = 1000 * (as->offset_ms/abs(as->offset_ms));
}
CHECK_NULL(L_LAUDIO, as->latency_history = calloc(alsa_latency_history_size, sizeof(double)));
snd_pcm_status_malloc(&as->pcm_status);
ret = device_open(as);
@ -1065,6 +1067,9 @@ alsa_init(void)
if (type && (strcasecmp(type, "alsa") != 0))
return -1;
alsa_sync_disable = cfg_getbool(cfg_audio, "sync_disable");
alsa_latency_history_size = cfg_getint(cfg_audio, "adjust_period_seconds");
CHECK_NULL(L_LAUDIO, device = calloc(1, sizeof(struct output_device)));
device->id = 0;