mirror of
https://github.com/owntone/owntone-server.git
synced 2025-01-13 16:03:23 -05:00
[alsa] Made resync period configurable 1..20 seconds, with default=10. (#604)
The unconfigurable resync period of 10 seconds was not frequent enough to keep my own ALSA device in sync with the AirPlay stream. Now the period is configurable. The default is still at 10 seconds, to prevent any change in behavior unless opted in by the user. Currently the adjustment causes a tiny "click" distortion in the ALSA output, so it is better to make the check as infrequent as possible, while still being frequent enough to stay in sync over lengthy sessions of playback. Added source_sample_rate, target_sample_rate to alsa_session. This is a first step toward rendering ALSA at a different sampling rate than the AirPlay stream, so that (a) we will be able to dynamically adjust the ALSA sampling rate for an improved sync algorithm, and (b) later, a more generalized resampling algorithm can accommodate very different hardware sampling rates like 22050 Hz or 48000 Hz. Reworked alsa_session_free() so that it can be used to tear down a partially initialized alsa_session if an error occurs in the middle of alsa_session_make(). This simplifies the error handling logic in alsa_session_make(). This refactoring will be helpful later when resampling is added, because more data structures will be dynamically allocated during initialization. Signed-off-by: Don Cross <cosinekitty@gmail.com>
This commit is contained in:
parent
830d8594aa
commit
07e46d75c8
@ -221,6 +221,11 @@ audio {
|
||||
# negative correspond to delaying it. The unit is samples, where is
|
||||
# 44100 = 1 second. The offset must be between -44100 and 44100.
|
||||
# offset = 0
|
||||
|
||||
# How often to check and correct for drift between ALSA and AirPlay.
|
||||
# The value is an integer expressed in seconds.
|
||||
# Clamped to the range 1..20.
|
||||
# adjust_period_seconds = 10
|
||||
}
|
||||
|
||||
# Pipe output
|
||||
|
@ -114,6 +114,7 @@ static cfg_opt_t sec_audio[] =
|
||||
CFG_STR("mixer", NULL, CFGF_NONE),
|
||||
CFG_STR("mixer_device", NULL, CFGF_NONE),
|
||||
CFG_INT("offset", 0, CFGF_NONE),
|
||||
CFG_INT("adjust_period_seconds", 10, CFGF_NONE),
|
||||
CFG_END()
|
||||
};
|
||||
|
||||
|
@ -57,6 +57,7 @@ static snd_mixer_elem_t *vol_elem;
|
||||
static long vol_min;
|
||||
static long vol_max;
|
||||
static int offset;
|
||||
static int adjust_period_seconds;
|
||||
|
||||
#define ALSA_F_STARTED (1 << 15)
|
||||
|
||||
@ -86,6 +87,8 @@ struct alsa_session
|
||||
|
||||
int32_t last_latency;
|
||||
int sync_counter;
|
||||
unsigned source_sample_rate; // raw input audio sample rate in Hz
|
||||
unsigned target_sample_rate; // output rate in Hz to configure ALSA device
|
||||
|
||||
// An array that will hold the packets we prebuffer. The length of the array
|
||||
// is prebuf_len (measured in rtp_packets)
|
||||
@ -136,7 +139,8 @@ alsa_session_free(struct alsa_session *as)
|
||||
if (!as)
|
||||
return;
|
||||
|
||||
event_free(as->deferredev);
|
||||
if (as->deferredev)
|
||||
event_free(as->deferredev);
|
||||
|
||||
prebuf_free(as);
|
||||
|
||||
@ -168,47 +172,46 @@ alsa_session_cleanup(struct alsa_session *as)
|
||||
static struct alsa_session *
|
||||
alsa_session_make(struct output_device *device, output_status_cb cb)
|
||||
{
|
||||
struct output_session *os;
|
||||
struct alsa_session *as;
|
||||
|
||||
os = calloc(1, sizeof(struct output_session));
|
||||
if (!os)
|
||||
{
|
||||
DPRINTF(E_LOG, L_LAUDIO, "Out of memory for ALSA session (os)\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
as = calloc(1, sizeof(struct alsa_session));
|
||||
if (!as)
|
||||
{
|
||||
DPRINTF(E_LOG, L_LAUDIO, "Out of memory for ALSA session (as)\n");
|
||||
free(os);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
as->output_session = calloc(1, sizeof(struct output_session));
|
||||
if (!as->output_session)
|
||||
{
|
||||
DPRINTF(E_LOG, L_LAUDIO, "Out of memory for ALSA session (output_session)\n");
|
||||
goto failure_cleanup;
|
||||
}
|
||||
as->output_session->session = as;
|
||||
as->output_session->type = device->type;
|
||||
|
||||
as->deferredev = evtimer_new(evbase_player, defer_cb, as);
|
||||
if (!as->deferredev)
|
||||
{
|
||||
DPRINTF(E_LOG, L_LAUDIO, "Out of memory for ALSA deferred event\n");
|
||||
free(os);
|
||||
free(as);
|
||||
return NULL;
|
||||
goto failure_cleanup;
|
||||
}
|
||||
|
||||
os->session = as;
|
||||
os->type = device->type;
|
||||
|
||||
as->output_session = os;
|
||||
as->state = ALSA_STATE_STOPPED;
|
||||
as->device = device;
|
||||
as->status_cb = cb;
|
||||
as->volume = device->volume;
|
||||
as->devname = card_name;
|
||||
as->source_sample_rate = 44100;
|
||||
as->target_sample_rate = 44100; // TODO: make ALSA device sample rate configurable
|
||||
|
||||
as->next = sessions;
|
||||
sessions = as;
|
||||
|
||||
return as;
|
||||
|
||||
failure_cleanup:
|
||||
alsa_session_free(as);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
@ -466,10 +469,10 @@ device_open(struct alsa_session *as)
|
||||
goto out_fail;
|
||||
}
|
||||
|
||||
ret = snd_pcm_hw_params_set_rate(hdl, hw_params, 44100, 0);
|
||||
ret = snd_pcm_hw_params_set_rate(hdl, hw_params, as->target_sample_rate, 0);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_LAUDIO, "Hardware doesn't support 44.1 kHz: %s\n", snd_strerror(ret));
|
||||
DPRINTF(E_LOG, L_LAUDIO, "Hardware doesn't support %u Hz: %s\n", as->target_sample_rate, snd_strerror(ret));
|
||||
|
||||
goto out_fail;
|
||||
}
|
||||
@ -691,8 +694,8 @@ sync_check(struct alsa_session *as, uint64_t rtptime, snd_pcm_sframes_t delay, i
|
||||
as->sync_counter = 0;
|
||||
sync = ALSA_SYNC_OK;
|
||||
}
|
||||
// If we have measured a consistent latency for 10 seconds, then we take action
|
||||
else if (as->sync_counter >= 10 * 126)
|
||||
// If we have measured a consistent latency for configured period, then we take action
|
||||
else if (as->sync_counter >= adjust_period_seconds * 126)
|
||||
{
|
||||
DPRINTF(E_INFO, L_LAUDIO, "Taking action to compensate for ALSA latency of %d samples\n", latency);
|
||||
|
||||
@ -994,6 +997,7 @@ alsa_init(void)
|
||||
cfg_t *cfg_audio;
|
||||
char *nickname;
|
||||
char *type;
|
||||
int original_adjust;
|
||||
|
||||
cfg_audio = cfg_getsec(cfg, "audio");
|
||||
type = cfg_getstr(cfg_audio, "type");
|
||||
@ -1014,6 +1018,14 @@ alsa_init(void)
|
||||
offset = 44100 * (offset/abs(offset));
|
||||
}
|
||||
|
||||
original_adjust = adjust_period_seconds = cfg_getint(cfg_audio, "adjust_period_seconds");
|
||||
if (adjust_period_seconds < 1)
|
||||
adjust_period_seconds = 1;
|
||||
else if (adjust_period_seconds > 20)
|
||||
adjust_period_seconds = 20;
|
||||
if (original_adjust != adjust_period_seconds)
|
||||
DPRINTF(E_LOG, L_LAUDIO, "Clamped ALSA adjust_period_seconds from %d to %d\n", original_adjust, adjust_period_seconds);
|
||||
|
||||
device = calloc(1, sizeof(struct output_device));
|
||||
if (!device)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user