mirror of
https://github.com/owntone/owntone-server.git
synced 2025-02-04 02:16:01 -05:00
[alsa] Make sync and sync evaluation period configurable
This commit is contained in:
parent
40934e7162
commit
dc65cb5b76
@ -217,12 +217,23 @@ audio {
|
|||||||
# If not set, the value for "card" will be used.
|
# If not set, the value for "card" will be used.
|
||||||
# mixer_device = ""
|
# mixer_device = ""
|
||||||
|
|
||||||
# Synchronization
|
# Enable or disable audio resampling to keep local audio in sync with
|
||||||
# If your local audio is out of sync with other speakers, e.g. Airplay,
|
# e.g. Airplay. This feature relies on accurate ALSA measurements of
|
||||||
# adjust this value. Negative values correspond to moving local audio
|
# delay, and some devices don't provide that. If that is the case you
|
||||||
# ahead, positive correspond to delaying it. The unit is milliseconds.
|
# are better off disabling the feature.
|
||||||
# The offset must be between -1000 and 1000 (+/- 1 sec).
|
# 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
|
# 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
|
# Pipe output
|
||||||
|
@ -115,8 +115,10 @@ static cfg_opt_t sec_audio[] =
|
|||||||
CFG_STR("card", "default", CFGF_NONE),
|
CFG_STR("card", "default", CFGF_NONE),
|
||||||
CFG_STR("mixer", NULL, CFGF_NONE),
|
CFG_STR("mixer", NULL, CFGF_NONE),
|
||||||
CFG_STR("mixer_device", 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", 0, CFGF_NONE), // deprecated
|
||||||
CFG_INT("offset_ms", 0, CFGF_NONE),
|
CFG_INT("offset_ms", 0, CFGF_NONE),
|
||||||
|
CFG_INT("adjust_period_seconds", 100, CFGF_NONE),
|
||||||
CFG_END()
|
CFG_END()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -49,9 +49,6 @@
|
|||||||
// if r2 is below this value we won't attempt to correct sync.
|
// if r2 is below this value we won't attempt to correct sync.
|
||||||
#define ALSA_MAX_VARIANCE 0.2
|
#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
|
// We correct latency by adjusting the sample rate in steps. However, if the
|
||||||
// latency keeps drifting we give up after reaching this step.
|
// latency keeps drifting we give up after reaching this step.
|
||||||
#define ALSA_RESAMPLE_STEP_MAX 8
|
#define ALSA_RESAMPLE_STEP_MAX 8
|
||||||
@ -100,7 +97,7 @@ struct alsa_session
|
|||||||
|
|
||||||
// Array of latency calculations, where latency_counter tells how many are
|
// Array of latency calculations, where latency_counter tells how many are
|
||||||
// currently in the array
|
// currently in the array
|
||||||
double latency_history[ALSA_LATENCY_HISTORY_SIZE];
|
double *latency_history;
|
||||||
int latency_counter;
|
int latency_counter;
|
||||||
|
|
||||||
int sync_resample_step;
|
int sync_resample_step;
|
||||||
@ -123,6 +120,9 @@ struct alsa_session
|
|||||||
|
|
||||||
static struct alsa_session *sessions;
|
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
|
// 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
|
// doesn't support that we resample to the fallback quality
|
||||||
static struct media_quality alsa_fallback_quality = { 44100, 16, 2 };
|
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;
|
exp_pos = (uint64_t)elapsed * as->quality.sample_rate / 1000;
|
||||||
diff = cur_pos - exp_pos;
|
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",
|
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);
|
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
|
// Add the latency to our measurement history
|
||||||
as->latency_history[as->latency_counter] = (double)diff;
|
as->latency_history[as->latency_counter] = (double)diff;
|
||||||
as->latency_counter++;
|
as->latency_counter++;
|
||||||
|
|
||||||
// Haven't collected enough samples for sync evaluation yet, so just return
|
// 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;
|
return ALSA_SYNC_OK;
|
||||||
|
|
||||||
as->latency_counter = 0;
|
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)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_WARN, L_LAUDIO, "Linear regression of collected latency samples failed\n");
|
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
|
// 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)
|
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
|
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)
|
// 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);
|
ret = snd_pcm_delay(as->hdl, &delay);
|
||||||
if (ret == 0)
|
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));
|
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);
|
snd_pcm_status_malloc(&as->pcm_status);
|
||||||
|
|
||||||
ret = device_open(as);
|
ret = device_open(as);
|
||||||
@ -1065,6 +1067,9 @@ alsa_init(void)
|
|||||||
if (type && (strcasecmp(type, "alsa") != 0))
|
if (type && (strcasecmp(type, "alsa") != 0))
|
||||||
return -1;
|
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)));
|
CHECK_NULL(L_LAUDIO, device = calloc(1, sizeof(struct output_device)));
|
||||||
|
|
||||||
device->id = 0;
|
device->id = 0;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user