// ------------------------------------------------------------------------
// audioio-alsa.cpp: ALSA PCM input and output
// Copyright (C) 1999-2004,2008,2019 Kai Vehmanen
// Copyright (C) 2001,2002 Jeremy Hall 
//
// Attributes:
//     eca-style-version: 3
//
// References:
//     http://alsa-project.org/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
// ------------------------------------------------------------------------

#include <string>
#include <cstring>
#include <cstdio>
#include <dlfcn.h>
#include <unistd.h>
#include <errno.h>

#include <alsa/version.h>

#define MY_SND_LIB_VERSION(maj,min,sub) \
  ((maj<<16)| (min<<8)| sub)

  
/* error if alsa-lib older than 0.9.0, use old API if 0.9.0->0.9.8, 
   otherwise do nothing */
#if SND_LIB_MAJOR < 1 && SND_LIB_MINOR == 9
#if SND_LIB_SUBMINOR > 0 || SND_LIB_EXTRAVER >= 100004
#define ALSA_PCM_NEW_HW_PARAMS_API
#define ALSA_PCM_NEW_SW_PARAMS_API
#else
#error "Unable to compile ALSA-support. Alsa-lib version 0.9.0rc4 or newer is required!"
#endif
#endif

#include <alsa/asoundlib.h>

/* Linux-kernel specific errnos */
#ifndef ESTRPIPE
#define ESTRPIPE 86
#endif

#include <kvu_dbc.h>
#include <kvu_message_item.h>
#include <kvu_numtostr.h>
#include <kvu_inttypes.h>
#include <kvu_utils.h>

#include "samplebuffer.h"
#include "audioio-device.h"
#include "audioio_alsa.h"

#include "eca-version.h"
#include "eca-error.h"
#include "eca-logger.h"

using std::cerr;
using std::endl;

#ifndef timersub
#define	timersub(a, b, result) \
do { \
	(result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
	(result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
	if ((result)->tv_usec < 0) { \
		--(result)->tv_sec; \
		(result)->tv_usec += 1000000; \
	} \
} while (0)
#endif

#ifndef timermsub
#define	timermsub(a, b, result) \
do { \
	(result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
	(result)->tv_nsec = (a)->tv_nsec - (b)->tv_nsec; \
	if ((result)->tv_nsec < 0) { \
		--(result)->tv_sec; \
		(result)->tv_nsec += 1000000000; \
	} \
} while (0)
#endif

const string AUDIO_IO_ALSA_PCM::default_pcm_device_rep = "default";

AUDIO_IO_ALSA_PCM::AUDIO_IO_ALSA_PCM (int card, 
				      int device, 
				      int subdevice) 
  : AUDIO_IO_DEVICE()
{
  // ECA_LOG_MSG(ECA_LOGGER::system_objects, "construct");
  card_number_rep = card;
  device_number_rep = device;
  subdevice_number_rep = subdevice;
  trigger_request_rep = false;
  overruns_rep = underruns_rep = 0;
  nbufs_repp = 0;
  allocate_structs();
}

AUDIO_IO_ALSA_PCM::~AUDIO_IO_ALSA_PCM(void)
{
  if (is_open() == true && is_running()) stop();

  if (is_open() == true) {
    close();
  }

  if (io_mode() != io_read) {
    if (underruns_rep != 0) {
      cerr << "WARNING! While writing to ALSA-pcm device ";
      cerr << "C" << card_number_rep << "D" << device_number_rep;
      cerr << ", there were " << underruns_rep << " underruns.\n";
    }
  }
  else {
    if (overruns_rep != 0) {
      cerr << "WARNING! While reading from ALSA-pcm device ";
      cerr << "C" << card_number_rep << "D" << device_number_rep;
      cerr << ", there were " << overruns_rep << " overruns.\n";
    }
  }

  if (nbufs_repp != 0)
    delete nbufs_repp;

  deallocate_structs();
}

AUDIO_IO_ALSA_PCM* AUDIO_IO_ALSA_PCM::clone(void) const
{
  AUDIO_IO_ALSA_PCM* target = new AUDIO_IO_ALSA_PCM();
  for(int n = 0; n < number_of_params(); n++) {
    target->set_parameter(n + 1, get_parameter(n + 1));
  }
  return target;
}

void AUDIO_IO_ALSA_PCM::allocate_structs(void)
{
  int err = snd_pcm_hw_params_malloc(&pcm_hw_params_repp);
  DBC_CHECK(!err);

  err = snd_pcm_sw_params_malloc(&pcm_sw_params_repp);
  DBC_CHECK(!err);
}

void AUDIO_IO_ALSA_PCM::deallocate_structs(void)
{
  snd_pcm_hw_params_free(pcm_hw_params_repp);
  snd_pcm_sw_params_free(pcm_sw_params_repp);
}


void AUDIO_IO_ALSA_PCM::open_device(void)
{
  ECA_LOG_MSG(ECA_LOGGER::system_objects, "open");

  // -------------------------------------------------------------------
  // Device name initialization

  string device_name = pcm_device_name();

  // -------------------------------------------------------------------
  // Open devices

  int err;
  if (io_mode() == io_read) {
    pcm_stream_rep = SND_PCM_STREAM_CAPTURE;
    err = snd_pcm_open(&audio_fd_repp, 
			 (char*)device_name.c_str(),
			 pcm_stream_rep,
			 SND_PCM_NONBLOCK);

    if (err < 0) {
      throw(SETUP_ERROR(SETUP_ERROR::io_mode, "AUDIOIO-ALSA: Unable to open ALSA--device for capture; error: " + 
			string(snd_strerror(err))));
    }
  }    
  else if (io_mode() == io_write) {
    pcm_stream_rep = SND_PCM_STREAM_PLAYBACK;
    err = snd_pcm_open(&audio_fd_repp, 
			 (char*)device_name.c_str(),
			 pcm_stream_rep,
			 SND_PCM_NONBLOCK);
    
    if (err < 0) {
      throw(SETUP_ERROR(SETUP_ERROR::io_mode, "AUDIOIO-ALSA: Unable to open ALSA-device for playback; error: " +  
			string(snd_strerror(err))));
    }
  }
  else if (io_mode() == io_readwrite) {
    throw(SETUP_ERROR(SETUP_ERROR::io_mode, "AUDIOIO-ALSA: Simultaneous input/output not supported."));
  }

  // -------------------------------------------------------------------
  // enables blocking mode
  snd_pcm_nonblock(audio_fd_repp, 0);
}

void AUDIO_IO_ALSA_PCM::set_audio_format_params(void)
{
  ECA_LOG_MSG(ECA_LOGGER::system_objects, "set_audio_format_params");
  format_rep = SND_PCM_FORMAT_LAST;
  switch(sample_format()) 
    {
    case ECA_AUDIO_FORMAT::sfmt_u8:  { format_rep = SND_PCM_FORMAT_U8; break; }
    case ECA_AUDIO_FORMAT::sfmt_s8:  { format_rep = SND_PCM_FORMAT_S8; break; }
    case ECA_AUDIO_FORMAT::sfmt_s16_le:  { format_rep = SND_PCM_FORMAT_S16_LE; break; }
    case ECA_AUDIO_FORMAT::sfmt_s16_be:  { format_rep = SND_PCM_FORMAT_S16_BE; break; }
    case ECA_AUDIO_FORMAT::sfmt_s24_le:  { format_rep = SND_PCM_FORMAT_S24_3LE; break; }
    case ECA_AUDIO_FORMAT::sfmt_s24_be:  { format_rep = SND_PCM_FORMAT_S24_3BE; break; }
    case ECA_AUDIO_FORMAT::sfmt_s32_le:  { format_rep = SND_PCM_FORMAT_S32_LE; break; }
    case ECA_AUDIO_FORMAT::sfmt_s32_be:  { format_rep = SND_PCM_FORMAT_S32_BE; break; }
      
    default:
      {
	throw(SETUP_ERROR(SETUP_ERROR::sample_format, "AUDIOIO-ALSA: Error when setting audio format not supported (1)"));
      }
    }
}

void AUDIO_IO_ALSA_PCM::print_pcm_info(void)
{
}

void AUDIO_IO_ALSA_PCM::fill_and_set_hw_params(void)
{
  ECA_LOG_MSG(ECA_LOGGER::system_objects, "fill_and_set_hw_params");

  /* 1. create one param combination */
  int err = snd_pcm_hw_params_any(audio_fd_repp, pcm_hw_params_repp);
  if (err < 0) throw(SETUP_ERROR(SETUP_ERROR::unexpected, "AUDIOIO-ALSA: Error when setting up hwparams/any: " + string(snd_strerror(err))));
  
  /* 2. set interleaving mode */
  if (interleaved_channels() == true)
    ECA_LOG_MSG(ECA_LOGGER::user_objects, "Using interleaved stream format.");
  else
    ECA_LOG_MSG(ECA_LOGGER::user_objects, "Using noninterleaved stream format.");

  if (interleaved_channels() == true)
    err = snd_pcm_hw_params_set_access(audio_fd_repp, pcm_hw_params_repp,
					 SND_PCM_ACCESS_RW_INTERLEAVED);
  else
    err = snd_pcm_hw_params_set_access(audio_fd_repp, pcm_hw_params_repp,
					 SND_PCM_ACCESS_RW_NONINTERLEAVED);
  if (err < 0) throw(SETUP_ERROR(SETUP_ERROR::unexpected, "AUDIOIO-ALSA: Error when setting up hwparams/access: " + string(snd_strerror(err))));

  /* 3. set sample format */
  err = snd_pcm_hw_params_set_format(audio_fd_repp, 
				     pcm_hw_params_repp, 
				     format_rep);
  if (err < 0) throw(SETUP_ERROR(SETUP_ERROR::sample_format, "AUDIOIO-ALSA: Audio format not supported."));

  /* 4. set channel count */
  err = snd_pcm_hw_params_set_channels(audio_fd_repp, 
					 pcm_hw_params_repp, 
					 channels());
  if (err < 0) throw(SETUP_ERROR(SETUP_ERROR::channels, "AUDIOIO-ALSA: Channel count " +
				 kvu_numtostr(channels()) + " is out of range!"));

  /* 5. set sampling rate */
  unsigned int uivalue = samples_per_second();
  err = snd_pcm_hw_params_set_rate_near(audio_fd_repp, 
					pcm_hw_params_repp,
					&uivalue, 
					0);
  if (err < 0) throw(SETUP_ERROR(SETUP_ERROR::sample_rate, "AUDIOIO-ALSA: Sample rate " +
				 kvu_numtostr(samples_per_second()) + " is out of range!"));
  if (static_cast<int>(uivalue) != samples_per_second()) {
    ECA_LOG_MSG(ECA_LOGGER::info,
                "exact sampling rate not supported, adjust from "
                + kvu_numtostr(samples_per_second()) + " to " +
                kvu_numtostr(uivalue));
    set_samples_per_second(uivalue);
  }

  /* 6. create buffers for noninterleaved i/o */
  if (interleaved_channels() != true) {
    if (nbufs_repp == 0)
      nbufs_repp = new unsigned char* [channels()];
  }

  snd_pcm_uframes_t fvalue = buffersize();
  /* 7. sets period size (period = one fragment) */
  err = snd_pcm_hw_params_set_period_size_near(audio_fd_repp, 
					       pcm_hw_params_repp,
					       &fvalue, 
					       0);
  if (err < 0) throw(SETUP_ERROR(SETUP_ERROR::buffersize, "AUDIOIO-ALSA: buffersize " +
				 kvu_numtostr(buffersize()) + " is out of range!"));

  /* 8. sets buffer size */
  if (max_buffers() == true) {
      snd_pcm_uframes_t bufferreq = buffersize() * 1024;
      err = snd_pcm_hw_params_set_buffer_size_near(audio_fd_repp, 
						   pcm_hw_params_repp,
						   &bufferreq);
    if (err < 0) throw(SETUP_ERROR(SETUP_ERROR::unexpected,
				   "AUDIOIO-ALSA: Error when setting up hwparams/btime (1): " + string(snd_strerror(err))));
  }
  else {
    snd_pcm_uframes_t bufferreq = buffersize() * 3;
    err = snd_pcm_hw_params_set_buffer_size_near(audio_fd_repp, 
						 pcm_hw_params_repp,
						 &bufferreq);
    if (err < 0) throw(SETUP_ERROR(SETUP_ERROR::unexpected,
				   "AUDIOIO-ALSA: Error when setting up hwparams/btime (2): " + string(snd_strerror(err))));
  }
   
  /* 9. print debug information */
  snd_pcm_hw_params_get_period_time(pcm_hw_params_repp, &uivalue, 0);
  ECA_LOG_MSG(ECA_LOGGER::system_objects, "period time set to " + kvu_numtostr(uivalue) + " usecs.");
  
  snd_pcm_hw_params_get_period_size(pcm_hw_params_repp, &period_size_rep, 0);
  ECA_LOG_MSG(ECA_LOGGER::system_objects, "period time set to " + kvu_numtostr(period_size_rep) + " frames.");
  if (period_size_rep != static_cast<unsigned int>(buffersize())) {
    ECA_LOG_MSG(ECA_LOGGER::info, 
		"Warning! Period-size differs from current client buffersize.");
  }

  snd_pcm_hw_params_get_buffer_time(pcm_hw_params_repp, &uivalue, 0);
  ECA_LOG_MSG(ECA_LOGGER::system_objects, "buffer time set to " + kvu_numtostr(uivalue) + " usecs.");

  snd_pcm_hw_params_get_buffer_size(pcm_hw_params_repp, &buffer_size_rep);
  ECA_LOG_MSG(ECA_LOGGER::system_objects, "buffer time set to " + kvu_numtostr(buffer_size_rep) + " frames.");
  ECA_LOG_MSG(ECA_LOGGER::system_objects, "total latency is " + kvu_numtostr(latency()) + " frames.");

  /* 9. all set, now active hw params */
  err = snd_pcm_hw_params(audio_fd_repp, pcm_hw_params_repp);
  if (err < 0) {
    throw(SETUP_ERROR(SETUP_ERROR::unexpected, "AUDIOIO-ALSA: Error when setting up hwparams: " + string(snd_strerror(err))));
  }
}

void AUDIO_IO_ALSA_PCM::fill_and_set_sw_params(void)
{
  ECA_LOG_MSG(ECA_LOGGER::system_objects, "fill_and_set_sw_params");

  /* 1. get current params */
  snd_pcm_sw_params_current(audio_fd_repp, pcm_sw_params_repp);

  /* 2. set start threshold (should be big enough so that processing 
        won't start until a explicit snd_pcm_start() is issued */
  int err = snd_pcm_sw_params_set_start_threshold(audio_fd_repp, 
						  pcm_sw_params_repp,
						  buffer_size_rep * 2);
  if (err < 0) throw(SETUP_ERROR(SETUP_ERROR::unexpected, "AUDIOIO-ALSA: Error when setting up pcm_sw_params/start_threshold: " + string(snd_strerror(err))));

#if SND_LIB_VERSION <= MY_SND_LIB_VERSION(1,0,15)
  /* note: deprecated in alsa-lib-1.0.16 (2008/Feb) */
  /* 3. set align to one frame (like the OSS-emulation layer) */
  err = snd_pcm_sw_params_set_xfer_align(audio_fd_repp,
                                         pcm_sw_params_repp,
                                         1);
  if (err < 0) throw(SETUP_ERROR(SETUP_ERROR::unexpected, "AUDIOIO-ALSA: Error when setting up pcm_sw_params_repp/xfer_align: " + string(snd_strerror(err))));
#endif

  /* 4. set tstamp mode */
  err = snd_pcm_sw_params_set_tstamp_mode(audio_fd_repp, pcm_sw_params_repp, SND_PCM_TSTAMP_ENABLE);
  if (err < 0) {
    ECA_LOG_MSG(ECA_LOGGER::info, "audio device does not support timestamp mode, unable to report accurate xrun duration info");
  }
#ifdef HAVE_SND_PCM_SW_PARAMS_SET_TSTAMP_TYPE
  else {
    err = snd_pcm_sw_params_set_tstamp_type(audio_fd_repp, pcm_sw_params_repp, SND_PCM_TSTAMP_TYPE_MONOTONIC);
  }
#endif

  /* 5. activate params */
  err = snd_pcm_sw_params(audio_fd_repp, pcm_sw_params_repp);
  if (err < 0) throw(SETUP_ERROR(SETUP_ERROR::unexpected, "AUDIOIO-ALSA: Error when setting up pcm_sw_params_repp: " + string(snd_strerror(err))));
}

void AUDIO_IO_ALSA_PCM::open(void) throw(AUDIO_IO::SETUP_ERROR&)
{
  open_device();
  set_audio_format_params();
  fill_and_set_hw_params();
  print_pcm_info();
  fill_and_set_sw_params();

  AUDIO_IO_DEVICE::open();
}

void AUDIO_IO_ALSA_PCM::stop(bool drain)
{
  if (drain == true && io_mode() != io_read) {

    /*
     * The sw-params combination:
     *    "silence-threshold==0 && silence-size>=boundary"
     * ... is a API special case to request silencing all
     * processed frames. This will ensure no stale data is
     * played out at the end of drain.
     */
    snd_pcm_uframes_t boundary;
    snd_pcm_sw_params_get_boundary(pcm_sw_params_repp, &boundary);
    snd_pcm_sw_params_set_silence_threshold(audio_fd_repp, pcm_sw_params_repp, 0);
    snd_pcm_sw_params_set_silence_size(audio_fd_repp, pcm_sw_params_repp, boundary);
    snd_pcm_sw_params(audio_fd_repp, pcm_sw_params_repp);

    ECA_LOG_MSG(ECA_LOGGER::user_objects, "drain - start / " + label());
    snd_pcm_drain(audio_fd_repp); /* blocking */
    ECA_LOG_MSG(ECA_LOGGER::user_objects, "drain complete / " + label());
  }
  else {
    snd_pcm_drop(audio_fd_repp); /* non-blocking */
  }
  
  ECA_LOG_MSG(ECA_LOGGER::user_objects, "stop - " + label() + ".");

  AUDIO_IO_DEVICE::stop();
}

void AUDIO_IO_ALSA_PCM::close(void)
{
  ECA_LOG_MSG(ECA_LOGGER::user_objects, "close - " + label() + ".");

  if (is_prepared() == true && is_running() == true) stop();
  snd_pcm_close(audio_fd_repp);

  AUDIO_IO_DEVICE::close();
}

void AUDIO_IO_ALSA_PCM::prepare(void)
{
  ECA_LOG_MSG(ECA_LOGGER::user_objects, "prepare - " + label() + ".");

  int err = snd_pcm_prepare(audio_fd_repp);
  if (err < 0)
    ECA_LOG_MSG(ECA_LOGGER::info, "Error when preparing stream: " + string(snd_strerror(err)));

  AUDIO_IO_DEVICE::prepare();
}

void AUDIO_IO_ALSA_PCM::start(void)
{
  ECA_LOG_MSG(ECA_LOGGER::user_objects, "start - " + label() + ".");
  snd_pcm_start(audio_fd_repp);

  AUDIO_IO_DEVICE::start();
}

long int AUDIO_IO_ALSA_PCM::read_samples(void* target_buffer, 
					   long int samples)
{
  // --
  DBC_REQUIRE(samples <= buffersize());
  // --

  long int realsamples = 0;

  if (interleaved_channels() == true) {
    realsamples = snd_pcm_readi(audio_fd_repp, target_buffer,
				buffersize());
    if (realsamples < 0) {
      /* Note! ALSA versions <=0.9.1 sometimes return -EIO in xrun-state;
       *       EPIPE=xrun, ESTRPIPE=xrun) */
      if (realsamples == -EPIPE || realsamples == -ESTRPIPE || realsamples == -EIO) {
	if (ignore_xruns() == true) {
	  handle_xrun_capture();
	  realsamples = snd_pcm_readi(audio_fd_repp, target_buffer,
				      buffersize());
	  if (realsamples < 0) realsamples = 0;
	}
	else {
	  cerr << "ALSA: Overrun! Stopping operation!" << endl;
	  stop();
	  close();
	}
      }
      else {
	cerr << "ALSA: Read error (" << realsamples << ")! Stopping operation." << endl;
	stop();
	close();
      }
    }
  }
  else {
    unsigned char* ptr_to_channel = reinterpret_cast<unsigned char*>(target_buffer);
    for (int channel = 0; channel < channels(); channel++) {
      nbufs_repp[channel] = ptr_to_channel;
      ptr_to_channel += samples * sample_size();
    }
    realsamples = snd_pcm_readn(audio_fd_repp, reinterpret_cast<void**>(target_buffer), buffersize());
    if (realsamples < 0) {
      /* Note! ALSA versions <=0.9.1 sometimes return -EIO in xrun-state;
       *       EPIPE=xrun, ESTRPIPE=xrun) */
      if (realsamples == -EPIPE || realsamples == -ESTRPIPE || realsamples == -EIO) {
	if (ignore_xruns() == true) {
	  handle_xrun_capture();
	  realsamples = snd_pcm_readn(audio_fd_repp, reinterpret_cast<void**>(target_buffer), buffersize());
	  if (realsamples < 0) realsamples = 0;
	}
	else {
	  cerr << "ALSA: Overrun! Stopping operation!" << endl;
	  stop();
	  close();
	}
      }
      else {
	cerr << "ALSA: Read error! Stopping operation." << endl;
	stop();
	close();
      }
    }
  }
  return realsamples;
}

void AUDIO_IO_ALSA_PCM::handle_xrun_capture(void)
{
  snd_pcm_status_t *status;
  snd_pcm_status_alloca(&status);

  int res = snd_pcm_status(audio_fd_repp, status);
  if (res >= 0) {
    snd_pcm_state_t state = snd_pcm_status_get_state(status);
    if (state == SND_PCM_STATE_XRUN) {
      cerr << "WARNING: ALSA recording overrun, some audio samples were lost!";
      handle_xrun_print_gap_duration(status);
      overruns_rep++;
      stop();
      prepare();
      start();
    }
    else if (state == SND_PCM_STATE_SUSPENDED) {
      cerr << "ALSA: Device suspended! Stopping operation!" << endl;
      stop();
      close();
    }
    else {
      cerr << "ALSA: Unknown device state '" 
	   << static_cast<int>(state) << "'" << endl;
    }
  }
  else {
    ECA_LOG_MSG(ECA_LOGGER::info, "snd_pcm_status() failed!");
  }
}

void AUDIO_IO_ALSA_PCM::write_samples(void* target_buffer, long int samples)
{
  if (trigger_request_rep == true) {
    trigger_request_rep = false;
    start();
  }

  if (interleaved_channels() == true) {
    long int count = snd_pcm_writei(audio_fd_repp, target_buffer, samples);
    if (count < 0) {
      /* Note! ALSA versions <=0.9.1 sometimes return -EIO in xrun-state;
       *       EPIPE=xrun, ESTRPIPE=xrun) */
      DBC_CHECK(count != -EINTR);
      if (count == -EPIPE || count == -EIO || count == -ESTRPIPE) {
	if (ignore_xruns() == true) {
	  handle_xrun_playback();
	  if (snd_pcm_writei(audio_fd_repp, target_buffer, samples) < 0) 
	    cerr << "ALSA: playback xrun handling failed!" << endl;
	  trigger_request_rep = true;
	}
	else {
	  cerr << "ALSA: Overrun! Stopping operation!" << endl;
	  stop();
	  close();
	}
      }
      else {
	cerr << "ALSA: Write error! Stopping operation (" << count << ")." << endl;
	stop();
	close();
      }
    }
  }
  else {
    unsigned char* ptr_to_channel = reinterpret_cast<unsigned char*>(target_buffer);
    for (int channel = 0; channel < channels(); channel++) {
      nbufs_repp[channel] = ptr_to_channel;
      // cerr << "Pointer to channel " << channel << ": " << reinterpret_cast<void*>(nbufs_repp[channel]) << endl;
      ptr_to_channel += samples * sample_size();
      // cerr << "Advancing pointer count by " << samples * sample_size() << " to " << reinterpret_cast<void*>(ptr_to_channel) << endl;
    }
    long int count =  snd_pcm_writen(audio_fd_repp,
				       reinterpret_cast<void**>(nbufs_repp), 
				       samples);
    if (count < 0) {
      /* Note! ALSA versions <=0.9.1 sometimes return -EIO in xrun-state;
       *       EPIPE=xrun, ESTRPIPE=xrun) */
      DBC_CHECK(count != -EINTR);
      if (count == -EPIPE || count == -EIO || count == -ESTRPIPE) {
	if (ignore_xruns() == true) {
	  handle_xrun_playback();
	  snd_pcm_writen(audio_fd_repp,
			   reinterpret_cast<void**>(nbufs_repp),
			   samples);
	  trigger_request_rep = true;
	}
	else {
	  cerr << "ALSA: Overrun! Stopping operation!" << endl;
	  stop();
	  close();
	}
      }
      else {
	cerr << "ALSA: Write error! Stopping operation." << endl;
	stop();
	close();
      }
    }
  }
}

void AUDIO_IO_ALSA_PCM::handle_xrun_print_gap_duration(snd_pcm_status_t *status)
{
  snd_pcm_tstamp_t tstamp_type = SND_PCM_TSTAMP_NONE;
  snd_timestamp_t tstamp;
  double seconds = 0;
  snd_pcm_sw_params_get_tstamp_mode(pcm_sw_params_repp, &tstamp_type);
  if (tstamp_type == SND_PCM_TSTAMP_ENABLE) {
    snd_pcm_status_get_trigger_tstamp(status, &tstamp);
#ifdef HAVE_CLOCK_GETTIME
    int monotonic = snd_pcm_hw_params_is_monotonic(pcm_hw_params_repp);
    if (monotonic) {
      struct timespec now, diff;
      struct timespec *tstamp_ptr = reinterpret_cast<struct timespec *>(&tstamp);
      clock_gettime(CLOCK_MONOTONIC, &now);
      timermsub(&now, tstamp_ptr, &diff);
      seconds = diff.tv_sec * 1000 + diff.tv_nsec / 1000000.0;
    }
    else
#endif
    {
      struct timeval now, diff;
      struct timeval *tstamp_ptr = static_cast<struct timeval *>(&tstamp);
      gettimeofday(&now, 0);
      timersub(&now, tstamp_ptr, &diff);
      seconds = diff.tv_sec * 1000 + diff.tv_usec / 1000.0;
    }
    cerr << " Break was at least " << kvu_numtostr(seconds)
         << " ms long." << endl;
  }
}

void AUDIO_IO_ALSA_PCM::handle_xrun_playback(void)
{
  snd_pcm_status_t *status;
  snd_pcm_status_alloca(&status);

  int res = snd_pcm_status(audio_fd_repp, status);
  if (res >= 0) {
    snd_pcm_state_t state = snd_pcm_status_get_state(status);
    if (state == SND_PCM_STATE_XRUN) {
      cerr << "WARNING: ALSA playback underrun, glitches in audio playback possible!";
      handle_xrun_print_gap_duration(status);
      underruns_rep++;
      stop();
      prepare();
      trigger_request_rep = true;
    }
    else if (state == SND_PCM_STATE_SUSPENDED) {
      cerr << "ALSA: Device suspended! Stopping operation!" << endl;
      stop();
      close();
    }
    else {
      cerr << "ALSA: Unknown device state '" 
	   << static_cast<int>(state) << "'" << endl;
    }
  }
  else {
    ECA_LOG_MSG(ECA_LOGGER::info, "snd_pcm_status() failed!");
  }
}

long int AUDIO_IO_ALSA_PCM::delay(void) const
{
  snd_pcm_sframes_t delay = 0;

  if (is_running() == true) {
    if (snd_pcm_delay(audio_fd_repp, &delay) != 0) {
      delay = 0;
    }
  }

  return static_cast<long int>(delay);
}

void AUDIO_IO_ALSA_PCM::set_parameter(int param, 
				      string value)
{
  switch (param) {
  case 1: 
    set_label(value);
    if (label().find("alsaplugin") != string::npos) {
      using_plugin_rep = true;
    }
    break;

  case 2: 
    card_number_rep = atoi(value.c_str());
    break;

  case 3: 
    device_number_rep = atoi(value.c_str());
    break;

  case 4: 
    subdevice_number_rep = atoi(value.c_str());
    break;
  }

  if (using_plugin_rep)
    pcm_device_name_rep = 
      string("plughw:") + 
      kvu_numtostr(card_number_rep) +
      "," +
      kvu_numtostr(device_number_rep) +
      "," +
      kvu_numtostr(subdevice_number_rep);
  else
    pcm_device_name_rep = 
      string("hw:") + 
      kvu_numtostr(card_number_rep) +
      "," +
      kvu_numtostr(device_number_rep) +
      "," +
      kvu_numtostr(subdevice_number_rep);
}

string AUDIO_IO_ALSA_PCM::get_parameter(int param) const
{
  switch (param) {
  case 1: 
    return label();

  case 2: 
    return kvu_numtostr(card_number_rep);

  case 3: 
    return kvu_numtostr(device_number_rep);

  case 4: 
    return kvu_numtostr(subdevice_number_rep);
  }
  return "";
}

void AUDIO_IO_ALSA_PCM::set_pcm_device_name(const string& n)
{ 
  if (n.size() > 0)
    pcm_device_name_rep = n; 
  else
    pcm_device_name_rep = default_pcm_device_rep;
}
