From ad01d65047120794a6e336d6b0641b5edcb52253 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Wed, 13 Apr 2016 20:12:37 +0200 Subject: [PATCH] [alsa] Let the user configure an offset to sync audio if required --- INSTALL | 4 ++-- forked-daapd.conf | 8 +++++++- src/conffile.c | 5 +---- src/outputs/alsa.c | 16 +++++++++++++--- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/INSTALL b/INSTALL index 4788aa37..908db207 100644 --- a/INSTALL +++ b/INSTALL @@ -136,8 +136,8 @@ Libraries: from - libunistring 0.9.3+ from - - libasound (optional - ALSA support, recommended for Linux) - normally already installed as part of your distro + - libasound (optional - local audio) + often already installed as part of your distro - libplist 0.16+ (optional - iTunes XML support) from - libspotify (optional - Spotify support) diff --git a/forked-daapd.conf b/forked-daapd.conf index e2d3a9e8..a80b9452 100644 --- a/forked-daapd.conf +++ b/forked-daapd.conf @@ -153,7 +153,7 @@ audio { # Name - used in the speaker list in Remote nickname = "Computer" - # Type of the output (alsa, oss4, dummy) + # Type of the output (alsa or dummy) # type = "alsa" # Audio device name for local audio output @@ -162,6 +162,12 @@ audio { # Mixer channel to use for volume control - ALSA/Linux only # If not set, PCM will be used if available, otherwise Master. # mixer = "" + + # If your local audio is out of sync with AirPlay, you can adjust this + # value. Positive values correspond to moving local audio ahead, + # negative correspond to delaying it. The unit is samples, where is + # 44100 = 1 second. The offset must be between -44100 and 44100. +# offset = 0 } # AirPlay/Airport Express device settings diff --git a/src/conffile.c b/src/conffile.c index c3fff9a1..c8571f3d 100644 --- a/src/conffile.c +++ b/src/conffile.c @@ -94,12 +94,9 @@ static cfg_opt_t sec_audio[] = { CFG_STR("nickname", "Computer", CFGF_NONE), CFG_STR("type", NULL, CFGF_NONE), -#ifdef __linux__ CFG_STR("card", "default", CFGF_NONE), -#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) - CFG_STR("card", "/dev/dsp", CFGF_NONE), -#endif CFG_STR("mixer", NULL, CFGF_NONE), + CFG_INT("offset", 0, CFGF_NONE), CFG_END() }; diff --git a/src/outputs/alsa.c b/src/outputs/alsa.c index ef906adb..ecec35cc 100644 --- a/src/outputs/alsa.c +++ b/src/outputs/alsa.c @@ -54,6 +54,7 @@ static snd_mixer_t *mixer_hdl; static snd_mixer_elem_t *vol_elem; static long vol_min; static long vol_max; +static int offset; #define ALSA_F_STARTED (1 << 15) @@ -552,13 +553,16 @@ playback_start(struct alsa_session *as, uint64_t pos, uint64_t start_pos) // Clear prebuffer in case start somehow got called twice without a stop in between prebuf_free(as); + // Adjust the starting position with the configured value + start_pos -= offset; + // The difference between pos and start_pos should match the 2 second // buffer that AirPlay uses. We will not use alsa's buffer for the initial // buffering, because my sound card's start_threshold is not to be counted on. // Instead we allocate our own buffer, and when it is time to play we write as // much as we can to alsa's buffer. as->prebuf_len = (start_pos - pos) / AIRTUNES_V2_PACKET_SAMPLES + 1; - if (as->prebuf_len > 2*126) + if (as->prebuf_len > (3 * 44100 - offset) / AIRTUNES_V2_PACKET_SAMPLES) { DPRINTF(E_LOG, L_LAUDIO, "Sanity check of prebuf_len (%" PRIu32 " packets) failed\n", as->prebuf_len); return; @@ -669,7 +673,7 @@ sync_check(struct alsa_session *as, uint64_t rtptime, snd_pcm_sframes_t delay, i npackets = 0; pb_pos = rtptime - delay - AIRTUNES_V2_PACKET_SAMPLES * npackets; - latency = cur_pos - pb_pos; + latency = cur_pos - (pb_pos - offset); // If the latency is low or very different from our last measurement, we reset the sync_counter if (abs(latency) < ALSA_MAX_LATENCY || abs(as->last_latency - latency) > ALSA_MAX_LATENCY_VARIANCE) @@ -692,7 +696,7 @@ sync_check(struct alsa_session *as, uint64_t rtptime, snd_pcm_sframes_t delay, i as->last_latency = latency; if (latency) - DPRINTF(E_DBG, L_LAUDIO, "Sync %d cur_pos %" PRIu64 ", pb_pos %" PRIu64 " (diff %d, delay %li), pos %" PRIu64 "\n", sync, cur_pos, pb_pos, latency, delay, as->pos); + DPRINTF(E_SPAM, L_LAUDIO, "Sync %d cur_pos %" PRIu64 ", pb_pos %" PRIu64 " (diff %d, delay %li), pos %" PRIu64 "\n", sync, cur_pos, pb_pos, latency, delay, as->pos); return sync; } @@ -987,6 +991,12 @@ alsa_init(void) card_name = cfg_getstr(cfg_audio, "card"); mixer_name = cfg_getstr(cfg_audio, "mixer"); nickname = cfg_getstr(cfg_audio, "nickname"); + offset = cfg_getint(cfg_audio, "offset"); + if (abs(offset) > 44100) + { + DPRINTF(E_LOG, L_LAUDIO, "The ALSA offset (%d) set in the configuration is out of bounds\n", offset); + offset = 44100 * (offset/abs(offset)); + } device = calloc(1, sizeof(struct output_device)); if (!device)