/*
 * libalsa output driver. This file is part of Shairport.
 * Copyright (c) Muffinman, Skaman 2013
 * Copyright (c) Mike Brady 2014 -- 2019
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#define ALSA_PCM_NEW_HW_PARAMS_API

#include <alsa/asoundlib.h>
#include <inttypes.h>
#include <math.h>
#include <memory.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

#include "config.h"

#include "activity_monitor.h"
#include "audio.h"
#include "common.h"

enum alsa_backend_mode {
  abm_disconnected,
  abm_connected,
  abm_playing
} alsa_backend_state; // under the control of alsa_mutex

typedef struct {
  snd_pcm_format_t alsa_code;
  int frame_size;
} format_record;

static void help(void);
static int init(int argc, char **argv);
static void deinit(void);
static void start(int i_sample_rate, int i_sample_format);
static int play(void *buf, int samples);
static void stop(void);
static void flush(void);
int delay(long *the_delay);
int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played);
void *alsa_buffer_monitor_thread_code(void *arg);

static void volume(double vol);
void do_volume(double vol);
int prepare(void);
int do_play(void *buf, int samples);

static void parameters(audio_parameters *info);
int mute(int do_mute); // returns true if it actually is allowed to use the mute
static double set_volume;
static int output_method_signalled = 0; // for reporting whether it's using mmap or not
int delay_type_notified = -1; // for controlling the reporting of whether the output device can do
                              // precision delays (e.g. alsa->pulsaudio virtual devices can't)
int use_monotonic_clock = 0;  // this value will be set when the hardware is initialised

audio_output audio_alsa = {
    .name = "alsa",
    .help = &help,
    .init = &init,
    .deinit = &deinit,
    .prepare = &prepare,
    .start = &start,
    .stop = &stop,
    .is_running = NULL,
    .flush = &flush,
    .delay = &delay,
    .play = &play,
    .rate_info = &get_rate_information,
    .mute = NULL,        // a function will be provided if it can, and is allowed to,
                         // do hardware mute
    .volume = NULL,      // a function will be provided if it can do hardware volume
    .parameters = NULL}; // a function will be provided if it can do hardware volume

static pthread_mutex_t alsa_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t alsa_mixer_mutex = PTHREAD_MUTEX_INITIALIZER;

pthread_t alsa_buffer_monitor_thread;

// for deciding when to activate mute
// there are two sources of requests to mute -- the backend itself, e.g. when it
// is flushing
// and the player, e.g. when volume goes down to -144, i.e. mute.

// we may not be allowed to use hardware mute, so we must reflect that too.

int mute_requested_externally = 0;
int mute_requested_internally = 0;

// for tracking how long the output device has stalled
uint64_t stall_monitor_start_time;      // zero if not initialised / not started /
                                        // zeroed by flush
long stall_monitor_frame_count;         // set to delay at start of time, incremented by
                                        // any writes
uint64_t stall_monitor_error_threshold; // if the time is longer than this, it's
                                        // an error

static snd_output_t *output = NULL;
int frame_size; // in bytes for interleaved stereo

int alsa_device_initialised; // boolean to ensure the initialisation is only
                             // done once

yndk_type precision_delay_available_status =
    YNDK_DONT_KNOW; // initially, we don't know if the device can do precision delay

snd_pcm_t *alsa_handle = NULL;
static snd_pcm_hw_params_t *alsa_params = NULL;
static snd_pcm_sw_params_t *alsa_swparams = NULL;
static snd_ctl_t *ctl = NULL;
static snd_ctl_elem_id_t *elem_id = NULL;
static snd_mixer_t *alsa_mix_handle = NULL;
static snd_mixer_elem_t *alsa_mix_elem = NULL;
static snd_mixer_selem_id_t *alsa_mix_sid = NULL;
static long alsa_mix_minv, alsa_mix_maxv;
static long alsa_mix_mindb, alsa_mix_maxdb;

static char *alsa_out_dev = "default";
static char *alsa_mix_dev = NULL;
static char *alsa_mix_ctrl = NULL;
static int alsa_mix_index = 0;
static int has_softvol = 0;

int64_t dither_random_number_store = 0;

static int volume_set_request = 0; // set when an external request is made to set the volume.

int mixer_volume_setting_gives_mute = 0; // set when it is discovered that
                                         // particular mixer volume setting
                                         // causes a mute.
long alsa_mix_mute;                      // setting the volume to this value mutes output, if
                                         // mixer_volume_setting_gives_mute is true
int volume_based_mute_is_active =
    0; // set when muting is being done by a setting the volume to a magic value

// use this to allow the use of snd_pcm_writei or snd_pcm_mmap_writei
snd_pcm_sframes_t (*alsa_pcm_write)(snd_pcm_t *, const void *, snd_pcm_uframes_t) = snd_pcm_writei;

int precision_delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay,
                               yndk_type *using_update_timestamps);
int standard_delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay,
                              yndk_type *using_update_timestamps);

// use this to allow the use of standard or precision delay calculations, with standard the, uh,
// standard.
int (*delay_and_status)(snd_pcm_state_t *state, snd_pcm_sframes_t *delay,
                        yndk_type *using_update_timestamps) = standard_delay_and_status;

// this will return true if the DAC can return precision delay information and false if not
// if it is not yet known, it will test the output device to find out

// note -- once it has done the test, it decides -- even if the delay comes back with
// "don't know", it will take that as a "No" and remember it.
// If you want it to check again, set precision_delay_available_status to YNDK_DONT_KNOW
// first.

int precision_delay_available() {
  if (precision_delay_available_status == YNDK_DONT_KNOW) {
    // this is very crude -- if the device is a hardware device, then it's assumed the delay is
    // precise
    const char *output_device_name = snd_pcm_name(alsa_handle);
    int is_a_real_hardware_device = (strstr(output_device_name, "hw:") == output_device_name);

    // The criteria as to whether precision delay is available
    // is whether the device driver returns non-zero update timestamps
    // If it does, and the device is a hardware device (i.e. its name begins with "hw:"),
    // it is considered that precision delay is available. Otherwise, it's considered to be
    // unavailable.

    // To test, we play a silence buffer (fairly large to avoid underflow)
    // and then we check the delay return. It will tell us if it
    // was able to use the (non-zero) update timestamps

    int frames_of_silence = 4410;
    size_t size_of_silence_buffer = frames_of_silence * frame_size;
    void *silence = malloc(size_of_silence_buffer);
    if (silence == NULL) {
      debug(1, "alsa: precision_delay_available -- failed to "
               "allocate memory for a "
               "silent frame buffer.");
    } else {
      pthread_cleanup_push(malloc_cleanup, silence);
      int use_dither = 0;
      if ((alsa_mix_ctrl == NULL) && (config.ignore_volume_control == 0) &&
          (config.airplay_volume != 0.0))
        use_dither = 1;
      dither_random_number_store =
          generate_zero_frames(silence, frames_of_silence, config.output_format,
                               use_dither, // i.e. with dither
                               dither_random_number_store);
      do_play(silence, frames_of_silence);
      pthread_cleanup_pop(1);
      // now we can get the delay, and we'll note if it uses update timestamps
      yndk_type uses_update_timestamps;
      snd_pcm_state_t state;
      snd_pcm_sframes_t delay;
      int ret = precision_delay_and_status(&state, &delay, &uses_update_timestamps);
      // debug(3,"alsa: precision_delay_available asking for delay and status with a return status
      // of %d, a delay of %ld and a uses_update_timestamps of %d.", ret, delay,
      // uses_update_timestamps);
      if (ret == 0) {
        if ((uses_update_timestamps == YNDK_YES) && (is_a_real_hardware_device)) {
          precision_delay_available_status = YNDK_YES;
          debug(2, "alsa: precision delay timing is available.");
        } else {
          if ((uses_update_timestamps == YNDK_YES) && (!is_a_real_hardware_device)) {
            debug(2, "alsa: precision delay timing is not available because it's not definitely a "
                     "hardware device.");
          } else {
            debug(2, "alsa: precision delay timing is not available.");
          }
          precision_delay_available_status = YNDK_NO;
        }
      }
    }
  }
  return (precision_delay_available_status == YNDK_YES);
}

// static int play_number;
// static int64_t accumulated_delay, accumulated_da_delay;
int alsa_characteristics_already_listed = 0;

static snd_pcm_uframes_t period_size_requested, buffer_size_requested;
static int set_period_size_request, set_buffer_size_request;

static uint64_t measurement_start_time;
static uint64_t frames_played_at_measurement_start_time;

static uint64_t measurement_time;
static uint64_t frames_played_at_measurement_time;

static uint64_t frames_sent_for_playing;
static uint64_t frame_index;
static int measurement_data_is_valid;

static void help(void) {
  printf("    -d output-device    set the output device, default is \"default\".\n"
         "    -c mixer-control    set the mixer control name, default is to use no mixer.\n"
         "    -m mixer-device     set the mixer device, default is the output device.\n"
         "    -i mixer-index      set the mixer index, default is 0.\n");
  int r = system("if [ -d /proc/asound ] ; then echo \"    hardware output devices:\" ; ls -al "
                 "/proc/asound/ 2>/dev/null | grep '\\->' | tr -s ' ' | cut -d ' ' -f 9 | while "
                 "read line; do echo \"      \\\"hw:$line\\\"\" ; done ; fi");
  if (r != 0)
    debug(2, "error %d executing a script to list alsa hardware device names", r);
}

void set_alsa_out_dev(char *dev) { alsa_out_dev = dev; }

// assuming pthread cancellation is disabled
int open_mixer() {
  int response = 0;
  if (alsa_mix_ctrl != NULL) {
    debug(3, "Open Mixer");
    int ret = 0;
    snd_mixer_selem_id_alloca(&alsa_mix_sid);
    snd_mixer_selem_id_set_index(alsa_mix_sid, alsa_mix_index);
    snd_mixer_selem_id_set_name(alsa_mix_sid, alsa_mix_ctrl);

    if ((snd_mixer_open(&alsa_mix_handle, 0)) < 0) {
      debug(1, "Failed to open mixer");
      response = -1;
    } else {
      debug(3, "Mixer device name is \"%s\".", alsa_mix_dev);
      if ((snd_mixer_attach(alsa_mix_handle, alsa_mix_dev)) < 0) {
        debug(1, "Failed to attach mixer");
        response = -2;
      } else {
        if ((snd_mixer_selem_register(alsa_mix_handle, NULL, NULL)) < 0) {
          debug(1, "Failed to register mixer element");
          response = -3;
        } else {
          ret = snd_mixer_load(alsa_mix_handle);
          if (ret < 0) {
            debug(1, "Failed to load mixer element");
            response = -4;
          } else {
            debug(3, "Mixer Control name is \"%s\".", alsa_mix_ctrl);
            alsa_mix_elem = snd_mixer_find_selem(alsa_mix_handle, alsa_mix_sid);
            if (!alsa_mix_elem) {
              warn("failed to find mixer control \"%s\".", alsa_mix_ctrl);
              response = -5;
            } else {
              response = 1; // we found a hardware mixer and successfully opened it
            }
          }
        }
      }
    }
  }
  return response;
}

// assuming pthread cancellation is disabled
void close_mixer() {
  if (alsa_mix_handle) {
    snd_mixer_close(alsa_mix_handle);
    alsa_mix_handle = NULL;
  }
}

// assuming pthread cancellation is disabled
void do_snd_mixer_selem_set_playback_dB_all(snd_mixer_elem_t *mix_elem, double vol) {
  if (snd_mixer_selem_set_playback_dB_all(mix_elem, vol, 0) != 0) {
    debug(1, "Can't set playback volume accurately to %f dB.", vol);
    if (snd_mixer_selem_set_playback_dB_all(mix_elem, vol, -1) != 0)
      if (snd_mixer_selem_set_playback_dB_all(mix_elem, vol, 1) != 0)
        debug(1, "Could not set playback dB volume on the mixer.");
  }
}

void actual_close_alsa_device() {
  debug(1, "actual close");
  if (alsa_handle) {
    int derr;
    if ((derr = snd_pcm_hw_free(alsa_handle)))
      debug(1,
            "Error %d (\"%s\") freeing the output device hardware while "
            "closing it.",
            derr, snd_strerror(derr));

    if ((derr = snd_pcm_close(alsa_handle)))
      debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr));
    alsa_handle = NULL;
  }
}

// This array is a sequence of the output rates to be tried if automatic speed selection is
// requested.
// There is no benefit to upconverting the frame rate, other than for compatibility.
// The lowest rate that the DAC is capable of is chosen.

unsigned int auto_speed_output_rates[] = {
    44100,
    88200,
    176400,
    352800,
};

// This array is of all the formats known to Shairport Sync, in order of the SPS_FORMAT definitions,
// with their equivalent alsa codes and their frame sizes.
// If just one format is requested, then its entry is searched for in the array and checked on the
// device
// If auto format is requested, then each entry in turn is tried until a working format is found.
// So, it should be in the search order.

format_record fr[] = {
    {SND_PCM_FORMAT_UNKNOWN, 0}, // unknown
    {SND_PCM_FORMAT_S8, 2},      {SND_PCM_FORMAT_U8, 2},      {SND_PCM_FORMAT_S16, 4},
    {SND_PCM_FORMAT_S16_LE, 4},  {SND_PCM_FORMAT_S16_BE, 4},  {SND_PCM_FORMAT_S24, 8},
    {SND_PCM_FORMAT_S24_LE, 8},  {SND_PCM_FORMAT_S24_BE, 8},  {SND_PCM_FORMAT_S24_3LE, 6},
    {SND_PCM_FORMAT_S24_3BE, 6}, {SND_PCM_FORMAT_S32, 8},     {SND_PCM_FORMAT_S32_LE, 8},
    {SND_PCM_FORMAT_S32_BE, 8},  {SND_PCM_FORMAT_UNKNOWN, 0}, // auto
    {SND_PCM_FORMAT_UNKNOWN, 0},                              // illegal
};

// This array is the sequence of formats to be tried if automatic selection of the format is
// requested.
// Ideally, audio should pass through Shairport Sync unaltered, apart from occasional interpolation.
// If the user chooses a hardware mixer, then audio could go straight through, unaltered, as signed
// 16 bit stereo.
// However, the user might, at any point, select an option that requires modification, such as
// stereo to mono mixing,
// additional volume attenuation, convolution, and so on. For this reason,
// we look for the greatest depth the DAC is capable of, since upconverting it is completely
// lossless.
// If audio processing is required, then the dither that must be added will
// be added at the lowest possible level.
// Hence, selecting the greatest bit depth is always either beneficial or neutral.

sps_format_t auto_format_check_sequence[] = {
    SPS_FORMAT_S32,    SPS_FORMAT_S32_LE,  SPS_FORMAT_S32_BE,  SPS_FORMAT_S24, SPS_FORMAT_S24_LE,
    SPS_FORMAT_S24_BE, SPS_FORMAT_S24_3LE, SPS_FORMAT_S24_3BE, SPS_FORMAT_S16, SPS_FORMAT_S16_LE,
    SPS_FORMAT_S16_BE, SPS_FORMAT_S8,      SPS_FORMAT_U8,
};

// assuming pthread cancellation is disabled
// if do_auto_setting is true and auto format or auto speed has been requested,
// select the settings as appropriate and store them
int actual_open_alsa_device(int do_auto_setup) {
  // the alsa mutex is already acquired when this is called
  const snd_pcm_uframes_t minimal_buffer_headroom =
      352 * 2; // we accept this much headroom in the hardware buffer, but we'll
               // accept less
               /*
                 const snd_pcm_uframes_t requested_buffer_headroom =
                     minimal_buffer_headroom + 2048; // we ask for this much headroom in the
                                                     // hardware buffer, but we'll accept
                 less
               */

  int ret, dir = 0;
  unsigned int
      actual_sample_rate; // this will be given the rate requested and will be given the actual rate
  // snd_pcm_uframes_t frames = 441 * 10;
  snd_pcm_uframes_t actual_buffer_length;
  snd_pcm_access_t access;

  // ensure no calls are made to the alsa device enquiring about the buffer
  // length if
  // synchronisation is disabled.
  if (config.no_sync != 0)
    audio_alsa.delay = NULL;

  // ensure no calls are made to the alsa device enquiring about the buffer
  // length if
  // synchronisation is disabled.
  if (config.no_sync != 0)
    audio_alsa.delay = NULL;

  ret = snd_pcm_open(&alsa_handle, alsa_out_dev, SND_PCM_STREAM_PLAYBACK, 0);
  if (ret < 0) {
    if (ret == -ENOENT) {
      warn("the alsa output_device \"%s\" can not be found.", alsa_out_dev);
    } else {
      char errorstring[1024];
      strerror_r(-ret, (char *)errorstring, sizeof(errorstring));
      warn("alsa: error %d (\"%s\") opening alsa device \"%s\".", ret, (char *)errorstring,
           alsa_out_dev);
    }
    return ret;
  }

  snd_pcm_hw_params_alloca(&alsa_params);
  snd_pcm_sw_params_alloca(&alsa_swparams);

  ret = snd_pcm_hw_params_any(alsa_handle, alsa_params);
  if (ret < 0) {
    die("audio_alsa: Broken configuration for device \"%s\": no configurations "
        "available",
        alsa_out_dev);
    return ret;
  }

  if ((config.no_mmap == 0) &&
      (snd_pcm_hw_params_set_access(alsa_handle, alsa_params, SND_PCM_ACCESS_MMAP_INTERLEAVED) >=
       0)) {
    if (output_method_signalled == 0) {
      debug(3, "Output written using MMAP");
      output_method_signalled = 1;
    }
    access = SND_PCM_ACCESS_MMAP_INTERLEAVED;
    alsa_pcm_write = snd_pcm_mmap_writei;
  } else {
    if (output_method_signalled == 0) {
      debug(3, "Output written with RW");
      output_method_signalled = 1;
    }
    access = SND_PCM_ACCESS_RW_INTERLEAVED;
    alsa_pcm_write = snd_pcm_writei;
  }

  ret = snd_pcm_hw_params_set_access(alsa_handle, alsa_params, access);
  if (ret < 0) {
    warn("audio_alsa: Access type not available for device \"%s\": %s", alsa_out_dev,
         snd_strerror(ret));
    return ret;
  }

  ret = snd_pcm_hw_params_set_channels(alsa_handle, alsa_params, 2);
  if (ret < 0) {
    warn("audio_alsa: Channels count (2) not available for device \"%s\": %s", alsa_out_dev,
         snd_strerror(ret));
    return ret;
  }

  snd_pcm_format_t sf;

  if ((do_auto_setup == 0) || (config.output_format_auto_requested == 0)) { // no auto format
    if ((config.output_format > SPS_FORMAT_UNKNOWN) && (config.output_format < SPS_FORMAT_AUTO)) {
      sf = fr[config.output_format].alsa_code;
      frame_size = fr[config.output_format].frame_size;
    } else {
      warn("alsa: unexpected output format %d. Set to S16_LE.", config.output_format);
      config.output_format = SPS_FORMAT_S16_LE;
      sf = fr[config.output_format].alsa_code;
      frame_size = fr[config.output_format].frame_size;
    }
    ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sf);
    if (ret < 0) {
      warn("audio_alsa: Alsa sample format %d not available for device \"%s\": %s", sf,
           alsa_out_dev, snd_strerror(ret));
      return ret;
    }
  } else { // auto format
    int number_of_formats_to_try;
    sps_format_t *formats;
    formats = auto_format_check_sequence;
    number_of_formats_to_try = sizeof(auto_format_check_sequence) / sizeof(sps_format_t);
    int i = 0;
    int format_found = 0;
    sps_format_t trial_format = SPS_FORMAT_UNKNOWN;
    while ((i < number_of_formats_to_try) && (format_found == 0)) {
      trial_format = formats[i];
      sf = fr[trial_format].alsa_code;
      frame_size = fr[trial_format].frame_size;
      ret = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, sf);
      if (ret == 0)
        format_found = 1;
      else
        i++;
    }
    if (ret == 0) {
      config.output_format = trial_format;
      debug(1, "alsa: output format chosen is \"%s\".",
            sps_format_description_string(config.output_format));
    } else {
      warn("audio_alsa: Could not automatically set the output format for device \"%s\": %s",
           alsa_out_dev, snd_strerror(ret));
      return ret;
    }
  }

  if ((do_auto_setup == 0) || (config.output_rate_auto_requested == 0)) { // no auto format
    actual_sample_rate =
        config.output_rate; // this is the requested rate -- it'll be changed to the actual rate
    ret = snd_pcm_hw_params_set_rate_near(alsa_handle, alsa_params, &actual_sample_rate, &dir);
    if (ret < 0) {
      warn("audio_alsa: Rate %iHz not available for playback: %s", config.output_rate,
           snd_strerror(ret));
      return ret;
    }
  } else {
    int number_of_speeds_to_try;
    unsigned int *speeds;

    speeds = auto_speed_output_rates;
    number_of_speeds_to_try = sizeof(auto_speed_output_rates) / sizeof(int);

    int i = 0;
    int speed_found = 0;

    while ((i < number_of_speeds_to_try) && (speed_found == 0)) {
      actual_sample_rate = speeds[i];
      ret = snd_pcm_hw_params_set_rate_near(alsa_handle, alsa_params, &actual_sample_rate, &dir);
      if (ret == 0) {
        speed_found = 1;
        if (actual_sample_rate != speeds[i])
          warn("Speed requested: %d. Speed available: %d.", speeds[i], actual_sample_rate);
      } else {
        i++;
      }
    }
    if (ret == 0) {
      config.output_rate = actual_sample_rate;
      debug(1, "alsa: output speed chosen is %d.", config.output_rate);
    } else {
      warn("audio_alsa: Could not automatically set the output rate for device \"%s\": %s",
           alsa_out_dev, snd_strerror(ret));
      return ret;
    }
  }

  if (set_period_size_request != 0) {
    debug(1, "Attempting to set the period size to %lu", period_size_requested);
    ret = snd_pcm_hw_params_set_period_size_near(alsa_handle, alsa_params, &period_size_requested,
                                                 &dir);
    if (ret < 0) {
      warn("audio_alsa: cannot set period size of %lu: %s", period_size_requested,
           snd_strerror(ret));
      return ret;
    } else {
      snd_pcm_uframes_t actual_period_size;
      snd_pcm_hw_params_get_period_size(alsa_params, &actual_period_size, &dir);
      if (actual_period_size != period_size_requested)
        inform("Actual period size set to a different value than requested. "
               "Requested: %lu, actual "
               "setting: %lu",
               period_size_requested, actual_period_size);
    }
  }

  if (set_buffer_size_request != 0) {
    debug(1, "Attempting to set the buffer size to %lu", buffer_size_requested);
    ret = snd_pcm_hw_params_set_buffer_size_near(alsa_handle, alsa_params, &buffer_size_requested);
    if (ret < 0) {
      warn("audio_alsa: cannot set buffer size of %lu: %s", buffer_size_requested,
           snd_strerror(ret));
      return ret;
    } else {
      snd_pcm_uframes_t actual_buffer_size;
      snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_size);
      if (actual_buffer_size != buffer_size_requested)
        inform("Actual period size set to a different value than requested. "
               "Requested: %lu, actual "
               "setting: %lu",
               buffer_size_requested, actual_buffer_size);
    }
  }

  ret = snd_pcm_hw_params(alsa_handle, alsa_params);
  if (ret < 0) {
    warn("audio_alsa: Unable to set hw parameters for device \"%s\": %s.", alsa_out_dev,
         snd_strerror(ret));
    return ret;
  }

  // check parameters after attempting to set them

  if (set_period_size_request != 0) {
    snd_pcm_uframes_t actual_period_size;
    snd_pcm_hw_params_get_period_size(alsa_params, &actual_period_size, &dir);
    if (actual_period_size != period_size_requested)
      inform("Actual period size set to a different value than requested. "
             "Requested: %lu, actual "
             "setting: %lu",
             period_size_requested, actual_period_size);
  }

  if (set_buffer_size_request != 0) {
    snd_pcm_uframes_t actual_buffer_size;
    snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_size);
    if (actual_buffer_size != buffer_size_requested)
      inform("Actual period size set to a different value than requested. "
             "Requested: %lu, actual "
             "setting: %lu",
             buffer_size_requested, actual_buffer_size);
  }

  if (actual_sample_rate != config.output_rate) {
    warn("Can't set the D/A converter to sample rate %d.", config.output_rate);
    return -EINVAL;
  }

  use_monotonic_clock = snd_pcm_hw_params_is_monotonic(alsa_params);

  ret = snd_pcm_hw_params_get_buffer_size(alsa_params, &actual_buffer_length);
  if (ret < 0) {
    warn("audio_alsa: Unable to get hw buffer length for device \"%s\": %s.", alsa_out_dev,
         snd_strerror(ret));
    return ret;
  }

  ret = snd_pcm_sw_params_current(alsa_handle, alsa_swparams);
  if (ret < 0) {
    warn("audio_alsa: Unable to get current sw parameters for device \"%s\": "
         "%s.",
         alsa_out_dev, snd_strerror(ret));
    return ret;
  }

  ret = snd_pcm_sw_params_set_tstamp_mode(alsa_handle, alsa_swparams, SND_PCM_TSTAMP_ENABLE);
  if (ret < 0) {
    warn("audio_alsa: Can't enable timestamp mode of device: \"%s\": %s.", alsa_out_dev,
         snd_strerror(ret));
    return ret;
  }

  /* write the sw parameters */
  ret = snd_pcm_sw_params(alsa_handle, alsa_swparams);
  if (ret < 0) {
    warn("audio_alsa: Unable to set software parameters of device: \"%s\": %s.", alsa_out_dev,
         snd_strerror(ret));
    return ret;
  }

  ret = snd_pcm_prepare(alsa_handle);
  if (ret < 0) {
    warn("audio_alsa: Unable to prepare the device: \"%s\": %s.", alsa_out_dev, snd_strerror(ret));
    return ret;
  }

  if (actual_buffer_length < config.audio_backend_buffer_desired_length + minimal_buffer_headroom) {
    /*
    // the dac buffer is too small, so let's try to set it
    buffer_size =
        config.audio_backend_buffer_desired_length + requested_buffer_headroom;
    ret = snd_pcm_hw_params_set_buffer_size_near(alsa_handle, alsa_params,
                                                 &buffer_size);
    if (ret < 0)
      die("audio_alsa: Unable to set hw buffer size to %lu for device \"%s\": "
          "%s.",
          config.audio_backend_buffer_desired_length +
              requested_buffer_headroom,
          alsa_out_dev, snd_strerror(ret));
    if (config.audio_backend_buffer_desired_length + minimal_buffer_headroom >
        buffer_size) {
      die("audio_alsa: Can't set hw buffer size to %lu or more for device "
          "\"%s\". Requested size: %lu, granted size: %lu.",
          config.audio_backend_buffer_desired_length + minimal_buffer_headroom,
          alsa_out_dev, config.audio_backend_buffer_desired_length +
                            requested_buffer_headroom,
          buffer_size);
    }
    */
    debug(1,
          "The alsa buffer is smaller (%lu bytes) than the desired backend "
          "buffer "
          "length (%ld) you have chosen.",
          actual_buffer_length, config.audio_backend_buffer_desired_length);
  }

  if (config.use_precision_timing == YNA_YES)
    delay_and_status = precision_delay_and_status;
  else if (config.use_precision_timing == YNA_AUTO) {
    if (precision_delay_available()) {
      delay_and_status = precision_delay_and_status;
      debug(2, "alsa: precision timing selected for \"auto\" mode");
    }
  }

  if (alsa_characteristics_already_listed == 0) {
    alsa_characteristics_already_listed = 1;
    int log_level = 2; // the level at which debug information should be output
                       //    int rc;
    snd_pcm_access_t access_type;
    snd_pcm_format_t format_type;
    snd_pcm_subformat_t subformat_type;
    //    unsigned int val, val2;
    unsigned int uval, uval2;
    int sval;
    int dir;
    snd_pcm_uframes_t frames;

    debug(log_level, "PCM handle name = '%s'", snd_pcm_name(alsa_handle));

    //      ret = snd_pcm_hw_params_any(alsa_handle, alsa_params);
    //      if (ret < 0) {
    //        die("audio_alsa: Cannpot get configuration for
    // device
    //\"%s\":
    // no
    // configurations
    //"
    //            "available",
    //            alsa_out_dev);
    //      }

    debug(log_level, "alsa device parameters:");

    snd_pcm_hw_params_get_access(alsa_params, &access_type);
    debug(log_level, "  access type = %s", snd_pcm_access_name(access_type));

    snd_pcm_hw_params_get_format(alsa_params, &format_type);
    debug(log_level, "  format = '%s' (%s)", snd_pcm_format_name(format_type),
          snd_pcm_format_description(format_type));

    snd_pcm_hw_params_get_subformat(alsa_params, &subformat_type);
    debug(log_level, "  subformat = '%s' (%s)", snd_pcm_subformat_name(subformat_type),
          snd_pcm_subformat_description(subformat_type));

    snd_pcm_hw_params_get_channels(alsa_params, &uval);
    debug(log_level, "  number of channels = %u", uval);

    sval = snd_pcm_hw_params_get_sbits(alsa_params);
    debug(log_level, "  number of significant bits = %d", sval);

    snd_pcm_hw_params_get_rate(alsa_params, &uval, &dir);
    switch (dir) {
    case -1:
      debug(log_level, "  rate = %u frames per second (<).", uval);
      break;
    case 0:
      debug(log_level, "  rate = %u frames per second (precisely).", uval);
      break;
    case 1:
      debug(log_level, "  rate = %u frames per second (>).", uval);
      break;
    }

    if (snd_pcm_hw_params_get_rate_numden(alsa_params, &uval, &uval2) == 0)
      debug(log_level, "  precise (rational) rate = %.3f frames per second (i.e. %u/%u).", uval,
            uval2, ((double)uval) / uval2);
    else
      debug(log_level, "  precise (rational) rate information unavailable.");

    snd_pcm_hw_params_get_period_time(alsa_params, &uval, &dir);
    switch (dir) {
    case -1:
      debug(log_level, "  period_time = %u us (<).", uval);
      break;
    case 0:
      debug(log_level, "  period_time = %u us (precisely).", uval);
      break;
    case 1:
      debug(log_level, "  period_time = %u us (>).", uval);
      break;
    }

    snd_pcm_hw_params_get_period_size(alsa_params, &frames, &dir);
    switch (dir) {
    case -1:
      debug(log_level, "  period_size = %lu frames (<).", frames);
      break;
    case 0:
      debug(log_level, "  period_size = %lu frames (precisely).", frames);
      break;
    case 1:
      debug(log_level, "  period_size = %lu frames (>).", frames);
      break;
    }

    snd_pcm_hw_params_get_buffer_time(alsa_params, &uval, &dir);
    switch (dir) {
    case -1:
      debug(log_level, "  buffer_time = %u us (<).", uval);
      break;
    case 0:
      debug(log_level, "  buffer_time = %u us (precisely).", uval);
      break;
    case 1:
      debug(log_level, "  buffer_time = %u us (>).", uval);
      break;
    }

    snd_pcm_hw_params_get_buffer_size(alsa_params, &frames);
    switch (dir) {
    case -1:
      debug(log_level, "  buffer_size = %lu frames (<).", frames);
      break;
    case 0:
      debug(log_level, "  buffer_size = %lu frames (precisely).", frames);
      break;
    case 1:
      debug(log_level, "  buffer_size = %lu frames (>).", frames);
      break;
    }

    snd_pcm_hw_params_get_periods(alsa_params, &uval, &dir);
    switch (dir) {
    case -1:
      debug(log_level, "  periods_per_buffer = %u (<).", uval);
      break;
    case 0:
      debug(log_level, "  periods_per_buffer = %u (precisely).", uval);
      break;
    case 1:
      debug(log_level, "  periods_per_buffer = %u (>).", uval);
      break;
    }
  }
  return 0;
}

int open_alsa_device(int do_auto_setup) {
  int result;
  int oldState;
  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
  result = actual_open_alsa_device(do_auto_setup);
  pthread_setcancelstate(oldState, NULL);
  return result;
}

int prepare_mixer() {
  int response = 0;
  // do any alsa device initialisation (general case)
  // at present, this is only needed if a hardware mixer is being used
  // if there's a hardware mixer, it needs to be initialised before use
  if (alsa_mix_ctrl == NULL) {
    audio_alsa.volume = NULL;
    audio_alsa.parameters = NULL;
    audio_alsa.mute = NULL;
  } else {
    debug(2, "alsa: hardware mixer prepare");
    int oldState;
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable

    if (alsa_mix_dev == NULL)
      alsa_mix_dev = alsa_out_dev;

    // Now, start trying to initialise the alsa device with the settings
    // obtained
    pthread_cleanup_debug_mutex_lock(&alsa_mixer_mutex, 1000, 1);
    if (open_mixer() == 1) {
      if (snd_mixer_selem_get_playback_volume_range(alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv) <
          0)
        debug(1, "Can't read mixer's [linear] min and max volumes.");
      else {
        if (snd_mixer_selem_get_playback_dB_range(alsa_mix_elem, &alsa_mix_mindb,
                                                  &alsa_mix_maxdb) == 0) {

          audio_alsa.volume = &volume;         // insert the volume function now we
                                               // know it can do dB stuff
          audio_alsa.parameters = &parameters; // likewise the parameters stuff
          if (alsa_mix_mindb == SND_CTL_TLV_DB_GAIN_MUTE) {
            // For instance, the Raspberry Pi does this
            debug(1, "Lowest dB value is a mute");
            mixer_volume_setting_gives_mute = 1;
            alsa_mix_mute = SND_CTL_TLV_DB_GAIN_MUTE; // this may not be
                                                      // necessary -- it's
                                                      // always
            // going to be SND_CTL_TLV_DB_GAIN_MUTE, right?
            // debug(1, "Try minimum volume + 1 as lowest true attenuation
            // value");
            if (snd_mixer_selem_ask_playback_vol_dB(alsa_mix_elem, alsa_mix_minv + 1,
                                                    &alsa_mix_mindb) != 0)
              debug(1, "Can't get dB value corresponding to a minimum volume "
                       "+ 1.");
          }
          debug(3, "Hardware mixer has dB volume from %f to %f.", (1.0 * alsa_mix_mindb) / 100.0,
                (1.0 * alsa_mix_maxdb) / 100.0);
        } else {
          // use the linear scale and do the db conversion ourselves
          warn("The hardware mixer specified -- \"%s\" -- does not have "
               "a dB volume scale.",
               alsa_mix_ctrl);

          if (snd_ctl_open(&ctl, alsa_mix_dev, 0) < 0) {
            warn("Cannot open control \"%s\"", alsa_mix_dev);
            response = -1;
          }
          if (snd_ctl_elem_id_malloc(&elem_id) < 0) {
            debug(1, "Cannot allocate memory for control \"%s\"", alsa_mix_dev);
            elem_id = NULL;
            response = -2;
          } else {
            snd_ctl_elem_id_set_interface(elem_id, SND_CTL_ELEM_IFACE_MIXER);
            snd_ctl_elem_id_set_name(elem_id, alsa_mix_ctrl);

            if (snd_ctl_get_dB_range(ctl, elem_id, &alsa_mix_mindb, &alsa_mix_maxdb) == 0) {
              debug(1,
                    "alsa: hardware mixer \"%s\" selected, with dB volume "
                    "from %f to %f.",
                    alsa_mix_ctrl, (1.0 * alsa_mix_mindb) / 100.0, (1.0 * alsa_mix_maxdb) / 100.0);
              has_softvol = 1;
              audio_alsa.volume = &volume;         // insert the volume function now
                                                   // we know it can do dB stuff
              audio_alsa.parameters = &parameters; // likewise the parameters stuff
            } else {
              debug(1, "Cannot get the dB range from the volume control \"%s\"", alsa_mix_ctrl);
            }
          }
          /*
          debug(1, "Min and max volumes are %d and
          %d.",alsa_mix_minv,alsa_mix_maxv);
          alsa_mix_maxdb = 0;
          if ((alsa_mix_maxv!=0) && (alsa_mix_minv!=0))
            alsa_mix_mindb =
          -20*100*(log10(alsa_mix_maxv*1.0)-log10(alsa_mix_minv*1.0));
          else if (alsa_mix_maxv!=0)
            alsa_mix_mindb = -20*100*log10(alsa_mix_maxv*1.0);
          audio_alsa.volume = &linear_volume; // insert the linear volume
          function
          audio_alsa.parameters = &parameters; // likewise the parameters
          stuff
          debug(1,"Max and min dB calculated are %d and
          %d.",alsa_mix_maxdb,alsa_mix_mindb);
          */
        }
      }
      if (((config.alsa_use_hardware_mute == 1) &&
           (snd_mixer_selem_has_playback_switch(alsa_mix_elem))) ||
          mixer_volume_setting_gives_mute) {
        audio_alsa.mute = &mute; // insert the mute function now we know it
                                 // can do muting stuff
        // debug(1, "Has mixer and mute ability we will use.");
      } else {
        // debug(1, "Has mixer but not using hardware mute.");
      }
      close_mixer();
    }
    debug_mutex_unlock(&alsa_mixer_mutex, 3); // release the mutex
    pthread_cleanup_pop(0);
    pthread_setcancelstate(oldState, NULL);
  }
  return response;
}

int alsa_device_init() { return prepare_mixer(); }

static int init(int argc, char **argv) {
  // for debugging
  snd_output_stdio_attach(&output, stdout, 0);

  // debug(2,"audio_alsa init called.");
  int response = 0; // this will be what we return to the caller.
  alsa_device_initialised = 0;
  const char *str;
  int value;
  // double dvalue;

  // set up default values first

  alsa_backend_state = abm_disconnected; // startup state
  debug(2, "alsa: init() -- alsa_backend_state => abm_disconnected.");
  set_period_size_request = 0;
  set_buffer_size_request = 0;
  config.alsa_use_hardware_mute = 0; // don't use it by default

  config.audio_backend_latency_offset = 0;
  config.audio_backend_buffer_desired_length = 0.200;
  config.audio_backend_buffer_interpolation_threshold_in_seconds =
      0.120; // below this, basic interpolation will be used to save time.
  config.alsa_maximum_stall_time = 0.200; // 200 milliseconds -- if it takes longer, it's a problem
  config.disable_standby_mode_silence_threshold =
      0.040; // start sending silent frames if the delay goes below this time
  config.disable_standby_mode_silence_scan_interval = 0.004; // check silence threshold this often

  stall_monitor_error_threshold =
      (uint64_t)1000000 * config.alsa_maximum_stall_time; // stall time max to microseconds;
  stall_monitor_error_threshold = (stall_monitor_error_threshold << 32) / 1000000; // now in fp form
  debug(1, "alsa: alsa_maximum_stall_time of %f sec.", config.alsa_maximum_stall_time);

  stall_monitor_start_time = 0;
  stall_monitor_frame_count = 0;

  config.disable_standby_mode = disable_standby_off;
  config.keep_dac_busy = 0;
  config.use_precision_timing = YNA_AUTO;

  // get settings from settings file first, allow them to be overridden by
  // command line options

  // do the "general" audio  options. Note, these options are in the "general"
  // stanza!
  parse_general_audio_options();

  if (config.cfg != NULL) {
    double dvalue;

    /* Get the Output Device Name. */
    if (config_lookup_string(config.cfg, "alsa.output_device", &str)) {
      alsa_out_dev = (char *)str;
    }

    /* Get the Mixer Type setting. */

    if (config_lookup_string(config.cfg, "alsa.mixer_type", &str)) {
      inform("The alsa mixer_type setting is deprecated and has been ignored. "
             "FYI, using the \"mixer_control_name\" setting automatically "
             "chooses a hardware mixer.");
    }

    /* Get the Mixer Device Name. */
    if (config_lookup_string(config.cfg, "alsa.mixer_device", &str)) {
      alsa_mix_dev = (char *)str;
    }

    /* Get the Mixer Control Name. */
    if (config_lookup_string(config.cfg, "alsa.mixer_control_name", &str)) {
      alsa_mix_ctrl = (char *)str;
    }

    /* Get the disable_synchronization setting. */
    if (config_lookup_string(config.cfg, "alsa.disable_synchronization", &str)) {
      if (strcasecmp(str, "no") == 0)
        config.no_sync = 0;
      else if (strcasecmp(str, "yes") == 0)
        config.no_sync = 1;
      else {
        warn("Invalid disable_synchronization option choice \"%s\". It should "
             "be \"yes\" or "
             "\"no\". It is set to \"no\".");
        config.no_sync = 0;
      }
    }

    /* Get the mute_using_playback_switch setting. */
    if (config_lookup_string(config.cfg, "alsa.mute_using_playback_switch", &str)) {
      inform("The alsa \"mute_using_playback_switch\" setting is deprecated. "
             "Please use the \"use_hardware_mute_if_available\" setting instead.");
      if (strcasecmp(str, "no") == 0)
        config.alsa_use_hardware_mute = 0;
      else if (strcasecmp(str, "yes") == 0)
        config.alsa_use_hardware_mute = 1;
      else {
        warn("Invalid mute_using_playback_switch option choice \"%s\". It "
             "should be \"yes\" or "
             "\"no\". It is set to \"no\".");
        config.alsa_use_hardware_mute = 0;
      }
    }

    /* Get the use_hardware_mute_if_available setting. */
    if (config_lookup_string(config.cfg, "alsa.use_hardware_mute_if_available", &str)) {
      if (strcasecmp(str, "no") == 0)
        config.alsa_use_hardware_mute = 0;
      else if (strcasecmp(str, "yes") == 0)
        config.alsa_use_hardware_mute = 1;
      else {
        warn("Invalid use_hardware_mute_if_available option choice \"%s\". It "
             "should be \"yes\" or "
             "\"no\". It is set to \"no\".");
        config.alsa_use_hardware_mute = 0;
      }
    }

    /* Get the output format, using the same names as aplay does*/
    if (config_lookup_string(config.cfg, "alsa.output_format", &str)) {
      int temp_output_format_auto_requested = config.output_format_auto_requested;
      config.output_format_auto_requested = 0; // assume a valid format will be given.
      if (strcasecmp(str, "S16") == 0)
        config.output_format = SPS_FORMAT_S16;
      else if (strcasecmp(str, "S16_LE") == 0)
        config.output_format = SPS_FORMAT_S16_LE;
      else if (strcasecmp(str, "S16_BE") == 0)
        config.output_format = SPS_FORMAT_S16_BE;
      else if (strcasecmp(str, "S24") == 0)
        config.output_format = SPS_FORMAT_S24;
      else if (strcasecmp(str, "S24_LE") == 0)
        config.output_format = SPS_FORMAT_S24_LE;
      else if (strcasecmp(str, "S24_BE") == 0)
        config.output_format = SPS_FORMAT_S24_BE;
      else if (strcasecmp(str, "S24_3LE") == 0)
        config.output_format = SPS_FORMAT_S24_3LE;
      else if (strcasecmp(str, "S24_3BE") == 0)
        config.output_format = SPS_FORMAT_S24_3BE;
      else if (strcasecmp(str, "S32") == 0)
        config.output_format = SPS_FORMAT_S32;
      else if (strcasecmp(str, "S32_LE") == 0)
        config.output_format = SPS_FORMAT_S32_LE;
      else if (strcasecmp(str, "S32_BE") == 0)
        config.output_format = SPS_FORMAT_S32_BE;
      else if (strcasecmp(str, "U8") == 0)
        config.output_format = SPS_FORMAT_U8;
      else if (strcasecmp(str, "S8") == 0)
        config.output_format = SPS_FORMAT_S8;
      else if (strcasecmp(str, "auto") == 0)
        config.output_format_auto_requested = 1;
      else {
        config.output_format_auto_requested =
            temp_output_format_auto_requested; // format was invalid; recall the original setting
        warn("Invalid output format \"%s\". It should be \"auto\", \"U8\", \"S8\", "
             "\"S16\", \"S24\", \"S24_LE\", \"S24_BE\", "
             "\"S24_3LE\", \"S24_3BE\" or "
             "\"S32\", \"S32_LE\", \"S32_BE\". It remains set to \"%s\".",
             str,
             config.output_format_auto_requested == 1
                 ? "auto"
                 : sps_format_description_string(config.output_format));
      }
    }

    if (config_lookup_string(config.cfg, "alsa.output_rate", &str)) {
      if (strcasecmp(str, "auto") == 0) {
        config.output_rate_auto_requested = 1;
      } else {
        if (config.output_rate_auto_requested == 1)
          warn("Invalid output rate \"%s\". It should be \"auto\", 44100, 88200, 176400 or 352800. "
               "It remains set to \"auto\". Note: numbers should not be placed in quotes.",
               str);
        else
          warn("Invalid output rate \"%s\". It should be \"auto\", 44100, 88200, 176400 or 352800. "
               "It remains set to %d. Note: numbers should not be placed in quotes.",
               str, config.output_rate);
      }
    }

    /* Get the output rate, which must be a multiple of 44,100*/
    if (config_lookup_int(config.cfg, "alsa.output_rate", &value)) {
      debug(1, "alsa output rate is %d frames per second", value);
      switch (value) {
      case 44100:
      case 88200:
      case 176400:
      case 352800:
        config.output_rate = value;
        config.output_rate_auto_requested = 0;
        break;
      default:
        if (config.output_rate_auto_requested == 1)
          warn("Invalid output rate \"%d\". It should be \"auto\", 44100, 88200, 176400 or 352800. "
               "It remains set to \"auto\".",
               value);
        else
          warn("Invalid output rate \"%d\".It should be \"auto\", 44100, 88200, 176400 or 352800. "
               "It remains set to %d.",
               value, config.output_rate);
      }
    }

    /* Get the use_mmap_if_available setting. */
    if (config_lookup_string(config.cfg, "alsa.use_mmap_if_available", &str)) {
      if (strcasecmp(str, "no") == 0)
        config.no_mmap = 1;
      else if (strcasecmp(str, "yes") == 0)
        config.no_mmap = 0;
      else {
        warn("Invalid use_mmap_if_available option choice \"%s\". It should be "
             "\"yes\" or \"no\". "
             "It remains set to \"yes\".");
        config.no_mmap = 0;
      }
    }
    /* Get the optional period size value */
    if (config_lookup_int(config.cfg, "alsa.period_size", &value)) {
      set_period_size_request = 1;
      debug(1, "Value read for period size is %d.", value);
      if (value < 0) {
        warn("Invalid alsa period size setting \"%d\". It "
             "must be greater than 0. No setting is made.",
             value);
        set_period_size_request = 0;
      } else {
        period_size_requested = value;
      }
    }

    /* Get the optional buffer size value */
    if (config_lookup_int(config.cfg, "alsa.buffer_size", &value)) {
      set_buffer_size_request = 1;
      debug(1, "Value read for buffer size is %d.", value);
      if (value < 0) {
        warn("Invalid alsa buffer size setting \"%d\". It "
             "must be greater than 0. No setting is made.",
             value);
        set_buffer_size_request = 0;
      } else {
        buffer_size_requested = value;
      }
    }

    /* Get the optional alsa_maximum_stall_time setting. */
    if (config_lookup_float(config.cfg, "alsa.maximum_stall_time", &dvalue)) {
      if (dvalue < 0.0) {
        warn("Invalid alsa maximum write time setting \"%f\". It "
             "must be greater than 0. Default is \"%f\". No setting is made.",
             dvalue, config.alsa_maximum_stall_time);
      } else {
        config.alsa_maximum_stall_time = dvalue;
      }
    }

    /* Get the optional disable_standby_mode_silence_threshold setting. */
    if (config_lookup_float(config.cfg, "alsa.disable_standby_mode_silence_threshold", &dvalue)) {
      if (dvalue < 0.0) {
        warn("Invalid alsa disable_standby_mode_silence_threshold setting \"%f\". It "
             "must be greater than 0. Default is \"%f\". No setting is made.",
             dvalue, config.disable_standby_mode_silence_threshold);
      } else {
        config.disable_standby_mode_silence_threshold = dvalue;
      }
    }

    /* Get the optional disable_standby_mode_silence_scan_interval setting. */
    if (config_lookup_float(config.cfg, "alsa.disable_standby_mode_silence_scan_interval",
                            &dvalue)) {
      if (dvalue < 0.0) {
        warn("Invalid alsa disable_standby_mode_silence_scan_interval setting \"%f\". It "
             "must be greater than 0. Default is \"%f\". No setting is made.",
             dvalue, config.disable_standby_mode_silence_scan_interval);
      } else {
        config.disable_standby_mode_silence_scan_interval = dvalue;
      }
    }

    /* Get the optional disable_standby_mode setting. */
    if (config_lookup_string(config.cfg, "alsa.disable_standby_mode", &str)) {
      if ((strcasecmp(str, "no") == 0) || (strcasecmp(str, "off") == 0) ||
          (strcasecmp(str, "never") == 0))
        config.disable_standby_mode = disable_standby_off;
      else if ((strcasecmp(str, "yes") == 0) || (strcasecmp(str, "on") == 0) ||
               (strcasecmp(str, "always") == 0)) {
        config.disable_standby_mode = disable_standby_always;
        config.keep_dac_busy = 1;
      } else if (strcasecmp(str, "auto") == 0)
        config.disable_standby_mode = disable_standby_auto;
      else {
        warn("Invalid disable_standby_mode option choice \"%s\". It should be "
             "\"always\", \"auto\" or \"never\". "
             "It remains set to \"never\".",
             str);
      }
    }

    if (config_lookup_string(config.cfg, "alsa.use_precision_timing", &str)) {
      if ((strcasecmp(str, "no") == 0) || (strcasecmp(str, "off") == 0) ||
          (strcasecmp(str, "never") == 0))
        config.use_precision_timing = YNA_NO;
      else if ((strcasecmp(str, "yes") == 0) || (strcasecmp(str, "on") == 0) ||
               (strcasecmp(str, "always") == 0)) {
        config.use_precision_timing = YNA_YES;
        config.keep_dac_busy = 1;
      } else if (strcasecmp(str, "auto") == 0)
        config.use_precision_timing = YNA_AUTO;
      else {
        warn("Invalid use_precision_timing option choice \"%s\". It should be "
             "\"yes\", \"auto\" or \"no\". "
             "It remains set to \"%s\".",
             config.use_precision_timing == YNA_NO
                 ? "no"
                 : config.use_precision_timing == YNA_AUTO ? "auto" : "yes");
      }
    }

    debug(1, "alsa: disable_standby_mode is \"%s\".",
          config.disable_standby_mode == disable_standby_off
              ? "never"
              : config.disable_standby_mode == disable_standby_always ? "always" : "auto");
    debug(1, "alsa: disable_standby_mode_silence_threshold is %f seconds.",
          config.disable_standby_mode_silence_threshold);
    debug(1, "alsa: disable_standby_mode_silence_scan_interval is %f seconds.",
          config.disable_standby_mode_silence_scan_interval);
  }

  optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour
  argv--;     // so we shift the arguments to satisfy getopt()
  argc++;
  // some platforms apparently require optreset = 1; - which?
  int opt;
  while ((opt = getopt(argc, argv, "d:t:m:c:i:")) > 0) {
    switch (opt) {
    case 'd':
      alsa_out_dev = optarg;
      break;

    case 't':
      inform("The alsa backend -t option is deprecated and has been ignored. "
             "FYI, using the -c option automatically chooses a hardware "
             "mixer.");
      break;

    case 'm':
      alsa_mix_dev = optarg;
      break;
    case 'c':
      alsa_mix_ctrl = optarg;
      break;
    case 'i':
      alsa_mix_index = strtol(optarg, NULL, 10);
      break;
    default:
      warn("Invalid audio option \"-%c\" specified -- ignored.", opt);
      help();
    }
  }

  if (optind < argc) {
    warn("Invalid audio argument: \"%s\" -- ignored", argv[optind]);
  }

  debug(1, "alsa: output device name is \"%s\".", alsa_out_dev);

  // so, now, if the option to keep the DAC running has been selected, start a
  // thread to monitor the
  // length of the queue
  // if the queue gets too short, stuff it with silence

  pthread_create(&alsa_buffer_monitor_thread, NULL, &alsa_buffer_monitor_thread_code, NULL);

  return response;
}

static void deinit(void) {
  int oldState;
  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
  // debug(2,"audio_alsa deinit called.");
  stop();
  debug(2, "Cancel buffer monitor thread.");
  pthread_cancel(alsa_buffer_monitor_thread);
  debug(3, "Join buffer monitor thread.");
  pthread_join(alsa_buffer_monitor_thread, NULL);
  pthread_setcancelstate(oldState, NULL);
}

int set_mute_state() {
  int response = 1; // some problem expected, e.g. no mixer or not allowed to use it or disconnected
  int oldState;
  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
  pthread_cleanup_debug_mutex_lock(&alsa_mixer_mutex, 10000, 0);
  if ((alsa_backend_state != abm_disconnected) && (config.alsa_use_hardware_mute == 1) &&
      (open_mixer() == 1)) {
    response = 0; // okay if actually using the mute facility
    debug(2, "alsa: actually set_mute_state");
    int mute = 0;
    if ((mute_requested_externally != 0) || (mute_requested_internally != 0))
      mute = 1;
    if (mute == 1) {
      debug(2, "alsa: hardware mute switched on");
      if (snd_mixer_selem_has_playback_switch(alsa_mix_elem))
        snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 0);
      else {
        volume_based_mute_is_active = 1;
        do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, alsa_mix_mute);
      }
    } else {
      debug(2, "alsa: hardware mute switched off");
      if (snd_mixer_selem_has_playback_switch(alsa_mix_elem))
        snd_mixer_selem_set_playback_switch_all(alsa_mix_elem, 1);
      else {
        volume_based_mute_is_active = 0;
        do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, set_volume);
      }
    }
    close_mixer();
  }
  debug_mutex_unlock(&alsa_mixer_mutex, 3); // release the mutex
  pthread_cleanup_pop(0);                   // release the mutex
  pthread_setcancelstate(oldState, NULL);
  return response;
}

static void start(__attribute__((unused)) int i_sample_rate,
                  __attribute__((unused)) int i_sample_format) {
  debug(3, "audio_alsa start called.");

  frame_index = 0;
  measurement_data_is_valid = 0;

  stall_monitor_start_time = 0;
  stall_monitor_frame_count = 0;
  if (alsa_device_initialised == 0) {
    debug(1, "alsa: start() calling alsa_device_init.");
    alsa_device_init();
    alsa_device_initialised = 1;
  }
}

int standard_delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay,
                              yndk_type *using_update_timestamps) {
  int ret = 0;
  if (using_update_timestamps)
    *using_update_timestamps = YNDK_NO;
  *state = snd_pcm_state(alsa_handle);
  if ((*state == SND_PCM_STATE_RUNNING) || (*state == SND_PCM_STATE_DRAINING)) {
    ret = snd_pcm_delay(alsa_handle, delay);
  } else {
    // not running, thus no delay information, thus can't check for frame
    // rates
    frame_index = 0; // we'll be starting over...
    measurement_data_is_valid = 0;
    *delay = 0;
  }

  stall_monitor_start_time = 0;  // zero if not initialised / not started / zeroed by flush
  stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes

  return ret;
}

int precision_delay_and_status(snd_pcm_state_t *state, snd_pcm_sframes_t *delay,
                               yndk_type *using_update_timestamps) {
  snd_pcm_status_t *alsa_snd_pcm_status;
  snd_pcm_status_alloca(&alsa_snd_pcm_status);

  if (using_update_timestamps)
    *using_update_timestamps = YNDK_DONT_KNOW;

  struct timespec tn;                // time now
  snd_htimestamp_t update_timestamp; // actually a struct timespec

  int ret = snd_pcm_status(alsa_handle, alsa_snd_pcm_status);
  if (ret == 0) {

    snd_pcm_status_get_htstamp(alsa_snd_pcm_status, &update_timestamp);

/*
// must be 1.1 or later to use snd_pcm_status_get_driver_htstamp
#if SND_LIB_MINOR != 0
    snd_htimestamp_t driver_htstamp;
    snd_pcm_status_get_driver_htstamp(alsa_snd_pcm_status, &driver_htstamp);
    uint64_t driver_htstamp_ns = driver_htstamp.tv_sec;
    driver_htstamp_ns = driver_htstamp_ns * 1000000000;
    driver_htstamp_ns = driver_htstamp_ns + driver_htstamp.tv_nsec;
    debug(1,"driver_htstamp: %f.", driver_htstamp_ns * 0.000000001);
#endif
*/

    *state = snd_pcm_status_get_state(alsa_snd_pcm_status);

    if ((*state == SND_PCM_STATE_RUNNING) || (*state == SND_PCM_STATE_DRAINING)) {

 //     uint64_t update_timestamp_ns =
 //         update_timestamp.tv_sec * (uint64_t)1000000000 + update_timestamp.tv_nsec;

      uint64_t update_timestamp_ns = update_timestamp.tv_sec;
      update_timestamp_ns = update_timestamp_ns * 1000000000;
      update_timestamp_ns = update_timestamp_ns + update_timestamp.tv_nsec;


      // if the update_timestamp is zero, we take this to mean that the device doesn't report
      // interrupt timings. (It could be that it's not a real hardware device.)
      // so we switch to getting the delay the regular way
      // i.e. using snd_pcm_delay	()
      if (using_update_timestamps) {
        if (update_timestamp_ns == 0)
          *using_update_timestamps = YNDK_NO;
        else
          *using_update_timestamps = YNDK_YES;
      }

      // user information
      if (update_timestamp_ns == 0) {
        if (delay_type_notified != 1) {
          debug(2, "alsa: update timestamps unavailable");
          delay_type_notified = 1;
        }
      } else {
        // diagnostic
        if (delay_type_notified != 0) {
          debug(2, "alsa: update timestamps available");
          delay_type_notified = 0;
        }
      }

      if (update_timestamp_ns == 0) {
        ret = snd_pcm_delay(alsa_handle, delay);
      } else {
        snd_pcm_sframes_t delay_temp = snd_pcm_status_get_delay(alsa_snd_pcm_status);

        /*
        // It seems that the alsa library uses CLOCK_REALTIME before 1.0.28, even though
        // the check for monotonic returns true. Might have to watch out for this.
          #if SND_LIB_MINOR == 0 && SND_LIB_SUBMINOR < 28
                clock_gettime(CLOCK_REALTIME, &tn);
          #else
                clock_gettime(CLOCK_MONOTONIC, &tn);
          #endif
        */

        if (use_monotonic_clock)
          clock_gettime(CLOCK_MONOTONIC, &tn);
        else
          clock_gettime(CLOCK_REALTIME, &tn);

        // uint64_t time_now_ns = tn.tv_sec * (uint64_t)1000000000 + tn.tv_nsec;
        uint64_t time_now_ns = tn.tv_sec;
        time_now_ns = time_now_ns * 1000000000;
        time_now_ns = time_now_ns + tn.tv_nsec;


        // see if it's stalled

        if ((stall_monitor_start_time != 0) && (stall_monitor_frame_count == delay_temp)) {
          // hasn't outputted anything since the last call to delay()

          if (((update_timestamp_ns - stall_monitor_start_time) > stall_monitor_error_threshold) ||
              ((time_now_ns - stall_monitor_start_time) > stall_monitor_error_threshold)) {
            debug(2,
                  "DAC seems to have stalled with time_now_ns: %" PRIX64
                  ", update_timestamp_ns: %" PRIX64 ", stall_monitor_start_time %" PRIX64
                  ", stall_monitor_error_threshold %" PRIX64 ".",
                  time_now_ns, update_timestamp_ns, stall_monitor_start_time,
                  stall_monitor_error_threshold);
            debug(2,
                  "DAC seems to have stalled with time_now: %lx,%lx"
                  ", update_timestamp: %lx,%lx, stall_monitor_start_time %" PRIX64
                  ", stall_monitor_error_threshold %" PRIX64 ".",
                  tn.tv_sec, tn.tv_nsec, update_timestamp.tv_sec, update_timestamp.tv_nsec,
                  stall_monitor_start_time, stall_monitor_error_threshold);
            ret = sps_extra_code_output_stalled;
          }
        } else {
          stall_monitor_start_time = update_timestamp_ns;
          stall_monitor_frame_count = delay_temp;
        }

        if (ret == 0) {
          uint64_t delta = time_now_ns - update_timestamp_ns;

//          uint64_t frames_played_since_last_interrupt =
//              ((uint64_t)config.output_rate * delta) / 1000000000;

            uint64_t frames_played_since_last_interrupt = config.output_rate;
            frames_played_since_last_interrupt = frames_played_since_last_interrupt * delta;
            frames_played_since_last_interrupt = frames_played_since_last_interrupt / 1000000000;


          snd_pcm_sframes_t frames_played_since_last_interrupt_sized =
              frames_played_since_last_interrupt;
          if ((frames_played_since_last_interrupt_sized < 0) || ((uint64_t)frames_played_since_last_interrupt_sized != frames_played_since_last_interrupt))
            debug(1,"overflow resizing frames_played_since_last_interrupt % " PRIx64 " to frames_played_since_last_interrupt %lx.", frames_played_since_last_interrupt, frames_played_since_last_interrupt_sized);
          delay_temp = delay_temp - frames_played_since_last_interrupt_sized;
        }
        *delay = delay_temp;
      }
    } else { // not running, thus no delay information, thus can't check for
             // stall
      *delay = 0;
      stall_monitor_start_time = 0;  // zero if not initialised / not started / zeroed by flush
      stall_monitor_frame_count = 0; // set to delay at start of time, incremented by any writes

      // not running, thus no delay information, thus can't check for frame
      // rates
      frame_index = 0; // we'll be starting over...
      measurement_data_is_valid = 0;
    }
  } else {
    debug(1, "alsa: can't get device's status.");
  }
  return ret;
}

int delay(long *the_delay) {
  // returns 0 if the device is in a valid state -- SND_PCM_STATE_RUNNING or
  // SND_PCM_STATE_PREPARED
  // or SND_PCM_STATE_DRAINING
  // and returns the actual delay if running or 0 if prepared in *the_delay

  // otherwise return an error code
  // the error code could be a Unix errno code or a snderror code, or
  // the sps_extra_code_output_stalled or the
  // sps_extra_code_output_state_cannot_make_ready codes
  int ret = 0;
  *the_delay = 0;

  int oldState;

  snd_pcm_state_t state;
  snd_pcm_sframes_t my_delay = 0; // this initialisation is to silence a clang warning

  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
  pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 0);

  if (alsa_handle == NULL)
    ret = ENODEV;
  else
    ret = delay_and_status(&state, &my_delay, NULL);

  debug_mutex_unlock(&alsa_mutex, 0);
  pthread_cleanup_pop(0);
  pthread_setcancelstate(oldState, NULL);

  *the_delay = my_delay; // note: snd_pcm_sframes_t is a long

  return ret;
}

int get_rate_information(uint64_t *elapsed_time, uint64_t *frames_played) {
  // elapsed_time is in nanoseconds
  int response = 0; // zero means okay
  if (measurement_data_is_valid) {
    *elapsed_time = measurement_time - measurement_start_time;
    *frames_played = frames_played_at_measurement_time - frames_played_at_measurement_start_time;
  } else {
    *elapsed_time = 0;
    *frames_played = 0;
    response = -1;
  }
  return response;
}

int do_play(void *buf, int samples) {
  // assuming the alsa_mutex has been acquired
  // debug(3,"audio_alsa play called.");
  int oldState;
  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable

  snd_pcm_state_t state;
  snd_pcm_sframes_t my_delay;
  int ret = delay_and_status(&state, &my_delay, NULL);

  if (ret == 0) { // will be non-zero if an error or a stall

    if ((samples != 0) && (buf != NULL)) {

      // jut check the state of the DAC

      if ((state != SND_PCM_STATE_PREPARED) && (state != SND_PCM_STATE_RUNNING) &&
          (state != SND_PCM_STATE_XRUN)) {
        debug(1, "alsa: DAC in odd SND_PCM_STATE_* %d prior to writing.", state);
      }

      // debug(3, "write %d frames.", samples);
      ret = alsa_pcm_write(alsa_handle, buf, samples);
      if (ret == samples) {
        stall_monitor_frame_count += samples;

        if (frame_index == 0) {
          frames_sent_for_playing = samples;
        } else {
          frames_sent_for_playing += samples;
        }

        const uint64_t start_measurement_from_this_frame =
            (2 * config.output_rate) / 352; // two seconds of frames

        frame_index++;

        if ((frame_index == start_measurement_from_this_frame) ||
            ((frame_index > start_measurement_from_this_frame) && (frame_index % 32 == 0))) {

          measurement_time = get_absolute_time_in_ns();
          frames_played_at_measurement_time = frames_sent_for_playing - my_delay - samples;

          if (frame_index == start_measurement_from_this_frame) {
            // debug(1, "Start frame counting");
            frames_played_at_measurement_start_time = frames_played_at_measurement_time;
            measurement_start_time = measurement_time;
            measurement_data_is_valid = 1;
          }
        }
      } else {
        frame_index = 0;
        measurement_data_is_valid = 0;
        if (ret == -EPIPE) { /* underrun */
          debug(1, "alsa: underrun while writing %d samples to alsa device.", samples);
          int tret = snd_pcm_recover(alsa_handle, ret, 1);
          if (tret < 0) {
            warn("alsa: can't recover from SND_PCM_STATE_XRUN: %s.", snd_strerror(tret));
          }
        } else if (ret == -ESTRPIPE) { /* suspended */
          debug(1, "alsa: suspended while writing %d samples to alsa device.", samples);
          int tret;
          while ((tret = snd_pcm_resume(alsa_handle)) == -EAGAIN) {
            sleep(1); /* wait until the suspend flag is released */
            if (tret < 0) {
              warn("alsa: can't recover from SND_PCM_STATE_SUSPENDED state, "
                   "snd_pcm_prepare() "
                   "failed: %s.",
                   snd_strerror(tret));
            }
          }
        } else {
          char errorstring[1024];
          strerror_r(-ret, (char *)errorstring, sizeof(errorstring));
          debug(1, "alsa: error %d (\"%s\") writing %d samples to alsa device.", ret,
                (char *)errorstring, samples);
        }
      }
    }
  } else {
    debug(1,
          "alsa: device status returns fault status %d and SND_PCM_STATE_* "
          "%d  for play.",
          ret, state);
    frame_index = 0;
    measurement_data_is_valid = 0;
  }

  pthread_setcancelstate(oldState, NULL);
  return ret;
}

int do_open(int do_auto_setup) {
  int ret = 0;
  if (alsa_backend_state != abm_disconnected)
    debug(1, "alsa: do_open() -- opening the output device when it is already "
             "connected");
  if (alsa_handle == NULL) {
    // debug(1,"alsa: do_open() -- opening the output device");
    ret = open_alsa_device(do_auto_setup);
    if (ret == 0) {
      mute_requested_internally = 0;
      if (audio_alsa.volume)
        do_volume(set_volume);
      if (audio_alsa.mute) {
        debug(2, "do_open() set_mute_state");
        set_mute_state(); // the mute_requested_externally flag will have been
                          // set accordingly
        // do_mute(0); // complete unmute
      }

      alsa_backend_state = abm_connected; // only do this if it really opened it.
    }
  } else {
    debug(1, "alsa: do_open() -- output device already open.");
  }
  return ret;
}

int do_close() {
  if (alsa_backend_state == abm_disconnected)
    debug(1, "alsa: do_close() -- closing the output device when it is already "
             "disconnected");
  int derr = 0;
  if (alsa_handle) {
    // debug(1,"alsa: do_close() -- closing the output device");
    if ((derr = snd_pcm_drop(alsa_handle)))
      debug(1, "Error %d (\"%s\") dropping output device.", derr, snd_strerror(derr));
    usleep(5000);
    if ((derr = snd_pcm_hw_free(alsa_handle)))
      debug(1, "Error %d (\"%s\") freeing the output device hardware.", derr, snd_strerror(derr));

    // flush also closes the device
    debug(2, "alsa: do_close() -- closing alsa handle");
    if ((derr = snd_pcm_close(alsa_handle)))
      debug(1, "Error %d (\"%s\") closing the output device.", derr, snd_strerror(derr));
    alsa_handle = NULL;
  } else {
    debug(1, "alsa: do_close() -- output device already closed.");
  }
  alsa_backend_state = abm_disconnected;
  return derr;
}

int play(void *buf, int samples) {

  // play() will change the state of the alsa_backend_mode to abm_playing
  // also, if the present alsa_backend_state is abm_disconnected, then first the
  // DAC must be
  // connected

  // debug(3,"audio_alsa play called.");
  int ret = 0;

  pthread_cleanup_debug_mutex_lock(&alsa_mutex, 50000, 0);

  if (alsa_backend_state == abm_disconnected) {
    ret = do_open(0); // don't try to auto setup
    if (ret == 0)
      debug(2, "alsa: play() -- opened output device");
  }

  if (ret == 0) {
    if (alsa_backend_state != abm_playing) {
      debug(2, "alsa: play() -- alsa_backend_state => abm_playing");
      alsa_backend_state = abm_playing;

      // mute_requested_internally = 0; // stop requesting a mute for backend's own
      // reasons, which might have been a flush
      // debug(2, "play() set_mute_state");
      // set_mute_state(); // try to action the request and return a status
      // do_mute(0); // unmute for backend's reason
    }
    ret = do_play(buf, samples);
  }

  debug_mutex_unlock(&alsa_mutex, 0);
  pthread_cleanup_pop(0); // release the mutex
  return ret;
}

int prepare(void) {
  // this will leave the DAC open / connected.
  int ret = 0;

  pthread_cleanup_debug_mutex_lock(&alsa_mutex, 50000, 0);

  if (alsa_backend_state == abm_disconnected) {
    if (alsa_device_initialised == 0) {
      // debug(1, "alsa: prepare() calling alsa_device_init.");
      alsa_device_init();
      alsa_device_initialised = 1;
    }
    ret = do_open(1); // do auto setup
    if (ret == 0)
      debug(2, "alsa: prepare() -- opened output device");
  }

  debug_mutex_unlock(&alsa_mutex, 0);
  pthread_cleanup_pop(0); // release the mutex
  return ret;
}

static void flush(void) {
  // debug(2,"audio_alsa flush called.");
  pthread_cleanup_debug_mutex_lock(&alsa_mutex, 10000, 1);
  // mute_requested_internally = 1; // request a mute for backend's reasons
  // debug(2, "flush() set_mute_state");
  // set_mute_state();
  // do_mute(1); // mute for backend's own reasons
  if (alsa_backend_state != abm_disconnected) { // must be playing or connected...
    if (config.keep_dac_busy != 0) {
      debug(2, "alsa: flush() -- alsa_backend_state => abm_connected.");
      alsa_backend_state = abm_connected;
    } else {
      debug(2, "alsa: flush() -- closing the output device");
      do_close(); // will change the state to disconnected
      debug(2, "alsa: flush() -- alsa_backend_state => abm_disconnected.");
    }
  } else
    debug(3, "alsa: flush() -- called on a disconnected alsa backend");
  debug_mutex_unlock(&alsa_mutex, 3);
  pthread_cleanup_pop(0); // release the mutex
}

static void stop(void) {
  // debug(2,"audio_alsa stop called.");
  flush(); // flush will also close the device if appropriate
}

static void parameters(audio_parameters *info) {
  info->minimum_volume_dB = alsa_mix_mindb;
  info->maximum_volume_dB = alsa_mix_maxdb;
}

void do_volume(double vol) { // caller is assumed to have the alsa_mutex when
                             // using this function
  debug(3, "Setting volume db to %f.", vol);
  int oldState;
  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
  set_volume = vol;
  pthread_cleanup_debug_mutex_lock(&alsa_mixer_mutex, 1000, 1);
  if (volume_set_request && (open_mixer() == 1)) {
    if (has_softvol) {
      if (ctl && elem_id) {
        snd_ctl_elem_value_t *value;
        long raw;

        if (snd_ctl_convert_from_dB(ctl, elem_id, vol, &raw, 0) < 0)
          debug(1, "Failed converting dB gain to raw volume value for the "
                   "software volume control.");

        snd_ctl_elem_value_alloca(&value);
        snd_ctl_elem_value_set_id(value, elem_id);
        snd_ctl_elem_value_set_integer(value, 0, raw);
        snd_ctl_elem_value_set_integer(value, 1, raw);
        if (snd_ctl_elem_write(ctl, value) < 0)
          debug(1, "Failed to set playback dB volume for the software volume "
                   "control.");
      }
    } else {
      if (volume_based_mute_is_active == 0) {
        // debug(1,"Set alsa volume.");
        do_snd_mixer_selem_set_playback_dB_all(alsa_mix_elem, vol);
      } else {
        debug(2, "Not setting volume because volume-based mute is active");
      }
    }
    volume_set_request = 0; // any external request that has been made is now satisfied
    close_mixer();
  }
  debug_mutex_unlock(&alsa_mixer_mutex, 3);
  pthread_cleanup_pop(0); // release the mutex
  pthread_setcancelstate(oldState, NULL);
}

void volume(double vol) {
  volume_set_request = 1; // an external request has been made to set the volume
  do_volume(vol);
}

/*
static void linear_volume(double vol) {
  debug(2, "Setting linear volume to %f.", vol);
  set_volume = vol;
  if ((alsa_mix_ctrl == NULL) && alsa_mix_handle) {
    double linear_volume = pow(10, vol);
    // debug(1,"Linear volume is %f.",linear_volume);
    long int_vol = alsa_mix_minv + (alsa_mix_maxv - alsa_mix_minv) *
linear_volume;
    // debug(1,"Setting volume to %ld, for volume input of %f.",int_vol,vol);
    if (alsa_mix_handle) {
      if (snd_mixer_selem_set_playback_volume_all(alsa_mix_elem, int_vol) != 0)
        die("Failed to set playback volume");

    }
  }
}
*/

int mute(int mute_state_requested) {                // these would be for external reasons, not
                                                    // because of the
                                                    // state of the backend.
  mute_requested_externally = mute_state_requested; // request a mute for external reasons
  debug(2, "mute(%d) set_mute_state", mute_state_requested);
  return set_mute_state();
}
/*
void alsa_buffer_monitor_thread_cleanup_function(__attribute__((unused)) void
*arg) {
  debug(1, "alsa: alsa_buffer_monitor_thread_cleanup_function called.");
}
*/

void *alsa_buffer_monitor_thread_code(__attribute__((unused)) void *arg) {
  int frame_count = 0;
  int error_count = 0;
  int error_detected = 0;
  int okb = -1;
  while (error_detected ==
         0) { // if too many play errors occur early on, we will turn off the disable stanby mode
    if (okb != config.keep_dac_busy) {
      debug(2, "keep_dac_busy is now \"%s\"", config.keep_dac_busy == 0 ? "no" : "yes");
      okb = config.keep_dac_busy;
    }
    if ((config.keep_dac_busy != 0) && (alsa_device_initialised == 0)) {
      debug(2, "alsa: alsa_buffer_monitor_thread_code() calling "
               "alsa_device_init.");
      alsa_device_init();
      alsa_device_initialised = 1;
    }
    int sleep_time_us = (int)(config.disable_standby_mode_silence_scan_interval * 1000000);
    pthread_cleanup_debug_mutex_lock(&alsa_mutex, 200000, 0);
    // check possible state transitions here
    if ((alsa_backend_state == abm_disconnected) && (config.keep_dac_busy != 0)) {
      // open the dac and move to abm_connected mode
      if (do_open(1) == 0) // no automatic setup of rate and speed if necessary
        debug(2, "alsa: alsa_buffer_monitor_thread_code() -- output device opened; "
                 "alsa_backend_state => abm_connected");
    } else if ((alsa_backend_state == abm_connected) && (config.keep_dac_busy == 0)) {
      stall_monitor_start_time = 0;
      frame_index = 0;
      measurement_data_is_valid = 0;
      debug(2, "alsa: alsa_buffer_monitor_thread_code() -- closing the output "
               "device");
      do_close();
      debug(2, "alsa: alsa_buffer_monitor_thread_code() -- alsa_backend_state "
               "=> abm_disconnected");
    }
    // now, if the backend is not in the abm_disconnected state
    // and config.keep_dac_busy is true (at the present, this has to be the case
    // to be in the
    // abm_connected state in the first place...) then do the silence-filling
    // thing, if needed /* only if the output device is capable of precision delay */.
    if ((alsa_backend_state != abm_disconnected) &&
        (config.keep_dac_busy != 0) /* && precision_delay_available() */) {
      int reply;
      long buffer_size = 0;
      snd_pcm_state_t state;
      reply = delay_and_status(&state, &buffer_size, NULL);
      if (reply != 0) {
        buffer_size = 0;
        char errorstring[1024];
        strerror_r(-reply, (char *)errorstring, sizeof(errorstring));
        debug(1, "alsa: alsa_buffer_monitor_thread_code delay error %d: \"%s\".", reply,
              (char *)errorstring);
      }
      long buffer_size_threshold =
          (long)(config.disable_standby_mode_silence_threshold * config.output_rate);
      size_t size_of_silence_buffer;
      if (buffer_size < buffer_size_threshold) {
        int frames_of_silence = 1024;
        size_of_silence_buffer = frames_of_silence * frame_size;
        void *silence = malloc(size_of_silence_buffer);
        if (silence == NULL) {
          warn("disable_standby_mode has been turned off because a memory allocation error "
               "occurred.");
          error_detected = 1;
        } else {
          int ret;
          pthread_cleanup_push(malloc_cleanup, silence);
          int use_dither = 0;
          if ((alsa_mix_ctrl == NULL) && (config.ignore_volume_control == 0) &&
              (config.airplay_volume != 0.0))
            use_dither = 1;
          dither_random_number_store =
              generate_zero_frames(silence, frames_of_silence, config.output_format,
                                   use_dither, // i.e. with dither
                                   dither_random_number_store);
          ret = do_play(silence, frames_of_silence);
          frame_count++;
          pthread_cleanup_pop(1); // free malloced buffer
          if (ret < 0) {
            error_count++;
            char errorstring[1024];
            strerror_r(-ret, (char *)errorstring, sizeof(errorstring));
            debug(2,
                  "alsa: alsa_buffer_monitor_thread_code error %d (\"%s\") writing %d samples "
                  "to alsa device -- %d errors in %d trials.",
                  ret, (char *)errorstring, frames_of_silence, error_count, frame_count);
            if ((error_count > 40) && (frame_count < 100)) {
              warn("disable_standby_mode has been turned off because too many underruns "
                   "occurred. Is Shairport Sync outputting to a virtual device or running in a "
                   "virtual machine?");
              error_detected = 1;
            }
          }
        }
      }
    }
    debug_mutex_unlock(&alsa_mutex, 0);
    pthread_cleanup_pop(0); // release the mutex
    usleep(sleep_time_us);  // has a cancellation point in it
  }
  pthread_exit(NULL);
}
