/*
 * iaxclient: a cross-platform IAX softphone library
 *
 * Copyrights:
 * Copyright (C) 2003 HorizonLive.com, (c) 2004, Horizon Wimba, Inc.
 *
 * Contributors:
 * Steve Kann <stevek@stevek.com>
 * Michael Van Donselaar <mvand@vandonselaar.org> 
 * Shawn Lawrence <shawn.lawrence@terracecomm.com>
 *
 *
 * This program is free software, distributed under the terms of
 * the GNU Lesser (Library) General Public License
 *
 * Module: audio_portaudio
 * Purpose: Audio code to provide portaudio driver support for IAX library
 * Developed by: Shawn Lawrence, Terrace Communications Inc.
 * Creation Date: April 18, 2003
 *
 * This library uses the PortAudio Portable Audio Library
 * For more information see: http://www.portaudio.com
 * PortAudio Copyright (c) 1999-2000 Ross Bencina and Phil Burk
 *
 */

#include "iaxclient_lib.h"
#include "pablio.h"
#include "portmixer/px_common/portmixer.h"

#ifdef USE_MEC2
#include "mec3.h"
static echo_can_state_t *ec;
#endif

#ifdef SPAN_EC
#include "ec/echo.h"
static echo_can_state_t *ec;
#endif

#ifdef SPEEX_EC
#include "speex/speex_echo.h"
static SpeexEchoState *ec;
#endif

#ifdef	WIN32
// alternate volume control initialization routines
typedef unsigned int MMRESULT;   /* from mmsystem.h */
struct PxInfo;
struct PxMixer;

MMRESULT _Px_InitInputVolumeControls(struct PxInfo* mixer, int hWaveIn ) ;
MMRESULT _Px_InitOutputVolumeControls(struct PxInfo* mixer, int hWaveOut ) ;

// toggle microphone boost function
MMRESULT _Px_SetMicrophoneBoost(struct PxMixer* mixer, int enable ) ;
int _Px_GetMicrophoneBoost(struct PxMixer* mixer ) ;

// set input source by name
MMRESULT _Px_SetCurrentInputSourceByName(struct PxInfo* mixer, const char* line_name ) ;
#endif


#define EC_RING_SZ  8192 /* must be pow(2) */


typedef short SAMPLE;

static PortAudioStream *iStream, *oStream, *aStream;
static PxMixer *iMixer = NULL, *oMixer = NULL;

static int selectedInput, selectedOutput, selectedRing;

static int sample_rate = 8000;


#define MAX_SAMPLE_RATE	      48000
#ifndef MS_PER_FRAME
# define MS_PER_FRAME	      40
#endif
#define SAMPLES_PER_FRAME     (MS_PER_FRAME * sample_rate / 1000)

/* static frame buffer allocation */
#define MAX_SAMPLES_PER_FRAME (MS_PER_FRAME * MAX_SAMPLE_RATE  / 1000)

#define ECHO_TAIL	  4096 /* echo_tail length, in frames must be pow(2) for mec/span ? */

/* RingBuffer Size; Needs to be Pow(2), 1024 = 512 samples = 64ms */
#ifndef OUTRBSZ
# define OUTRBSZ 32768 
#endif

/* Input ringbuffer size;  this doesn't seem to be as critical, and making it big
 * causes issues when we're answering calls, etc., and the audio system is running
 * but not being drained */
#ifndef INRBSZ
# define INRBSZ  2048
#endif

/* TUNING:  The following constants may help in tuning for situations
 * where you are getting audio-level under/overruns.
 *
 * If you are running iaxclient on a system where you cannot get
 * low-latency scheduling, you may need to increase these.  This tends
 * to be an issue on non-MacOSX unix systems, when you are not running
 * as root, and cannot ask the OS for higher priority.  
 *
 * RBOUTTARGET:  This a target size of the output ringbuffer, in milliseconds, 
 * where audio for your speakers goes after being decoded and mixed, and
 * before the audio callback asks for it.  It can get larger than this
 * (up to OUTRBSZ, above), but when it does, for a bit, we will start
 * dropping some frames.  For no drops at all, this needs to be set to
 * contain the number of samples in your largest scheduling gap
 *
 * PA_NUMBUFFERS:  This is the number of buffers that the low-level
 * operating system driver will use, for buffering our output (and also
 * our input) between the soundcard and portaudio.  This should also be
 * set to the maximum scheduling delay.  Some drivers, though, will
 * callback _into_ portaudio with a higher priority, so this doesn't
 * necessarily need to be as big as RBOUTMAXSZ, although on linux, it
 * does.  The default is to leave this up to portaudio..
 */

/* 80ms if average outRing length is more than this many bytes, start dropping */
#ifndef RBOUTTARGET
# define RBOUTTARGET (80)
#endif

/* size in bytes of ringbuffer target */
#define RBOUTTARGET_BYTES (RBOUTTARGET * (sample_rate / 1000) * sizeof(SAMPLE))

#define PA_NUMBUFFERS 0
// try setting this to our RBOUTMAXSZ?
//#define PA_NUMBUFFERS (RBOUTMAXSZ / (2*SAMPLES_PER_FRAME))
//
static char inRingBuf[INRBSZ], outRingBuf[OUTRBSZ];
static RingBuffer inRing, outRing;

static int outRingLenAvg;

static int oneStream;
static int auxStream;
static int virtualMonoIn;
static int virtualMonoOut;

static int running;

static struct iaxc_sound *sounds;
static int  nextSoundId = 1;

static MUTEX sound_lock;

static int pa_start (struct iaxc_audio_driver *d ) ;
static void handle_paerror(PaError err, char * where);

/* scan devices and stash pointers to dev structures. 
 *  But, these structures only remain valid while Pa is initialized,
 *  which, with pablio, is only while it's running!
 *  Also, storing these things in two separate arrays loses the actual
 *  PaDeviceID's associated with devices (since their index in these
 *  input/output arrays isn't the same as their index in the combined
 *  array */
static int scan_devices(struct iaxc_audio_driver *d) {
    int nDevices; 
    int i;

    d->nDevices = nDevices = Pa_CountDevices();
    d->devices = malloc(nDevices * sizeof(struct iaxc_audio_device));

    for(i=0;i<nDevices;i++)
    {
	const PaDeviceInfo *pa;	
	struct iaxc_audio_device *dev;

	pa=Pa_GetDeviceInfo(i);
	dev = &(d->devices[i]);

	dev->name = (char *)pa->name;
	dev->devID = i;
	dev->capabilities = 0;

	if(pa->maxInputChannels > 0)
	  dev->capabilities |= IAXC_AD_INPUT;

	if(pa->maxOutputChannels > 0) {
	  dev->capabilities |= IAXC_AD_OUTPUT;
	  dev->capabilities |= IAXC_AD_RING;
	}

	if(i == Pa_GetDefaultInputDeviceID())
	  dev->capabilities |= IAXC_AD_INPUT_DEFAULT;

	if(i == Pa_GetDefaultOutputDeviceID()) {
	  dev->capabilities |= IAXC_AD_OUTPUT_DEFAULT;
	  dev->capabilities |= IAXC_AD_RING_DEFAULT;
	}
    }
    return 0;
}

static void mono2stereo(SAMPLE *out, SAMPLE *in, int nSamples) {
    int i;
    //fprintf(stderr, "mono2stereo: %d samples\n", nSamples);
    for(i=0;i<nSamples;i++) {
	*(out++) = *in;
	*(out++) = *(in++); 
    }
}

static void stereo2mono(SAMPLE *out, SAMPLE *in, int nSamples) {
    int i;
    //fprintf(stderr, "stereo2mono: %d samples\n", nSamples);
    for(i=0;i<nSamples;i++) {
	*(out) = *(in++);
	out++; in++;
	//*(out++) += *(in++);
    }
}

static void mix_slin(short *dst, short *src, int samples) {
    int i=0,val=0;
    for (i=0;i<samples;i++) {

        if(virtualMonoOut)
	  val = ((short *)dst)[2*i] + ((short *)src)[i];
	else
	  val = ((short *)dst)[i] + ((short *)src)[i];

        if(val > 0x7fff) {
            val = 0x7fff-1;
        } else if (val < -0x7fff) {
            val = -0x7fff+1;
        } 

	if(virtualMonoOut) {
	    dst[2*i] = val;
	    dst[2*i+1] = val;
	} else {
	    dst[i] = val;
	}
	
    }
}

static int pa_mix_sounds (void *outputBuffer, unsigned long frames, int channel) {
    struct iaxc_sound *s;
    struct iaxc_sound **sp;
    unsigned long outpos;


  MUTEXLOCK(&sound_lock);
    /* mix each sound into the outputBuffer */
    sp = &sounds;
    while(sp && *sp)  {
      s = *sp;
      outpos = 0;
      
      if(s->channel == channel) {
	/* loop over the sound until we've played it enough times, or we've filled the outputBuffer */
	for(;;) {
	  int n;

	  if(outpos == frames) break;  /* we've filled the buffer */
	  if(s->pos == s->len) {
	    if(s->repeat == 0) {
	       // XXX free the sound structure, and maybe the buffer!
	       (*sp) = s->next;
	       if(s->malloced)
		  free(s->data);
	       free(s);
	       break; 
	    }
	    s->pos = 0;
	    s->repeat--;
	  }

	  /* how many frames do we add in this loop? */
	  n = ((frames - outpos) < (s->len - s->pos)) ? (frames - outpos) : (s->len - s->pos);

	  /* mix in the frames */
	  mix_slin((short *)outputBuffer + outpos, s->data+s->pos, n); 

	  s->pos += n;
	  outpos += n;
	}
      }
	if((*sp)) /* don't advance if we removed this member */
	  sp = &((*sp)->next);
    }
  MUTEXUNLOCK(&sound_lock);
  return 0;
}

static int pa_play_sound(struct iaxc_sound *inSound, int ring) {
  struct iaxc_sound *sound;

  sound = (struct iaxc_sound *)malloc(sizeof(struct iaxc_sound));
  if(!sound) return 1;

  *sound = *inSound;
  
  MUTEXLOCK(&sound_lock);
  sound->channel = ring;
  sound->id = nextSoundId++; 
  sound->pos = 0;
    
  sound->next = sounds;
  sounds = sound;
  MUTEXUNLOCK(&sound_lock);

  if(!running) pa_start(NULL); /* XXX fixme: start/stop semantics */

  return sound->id; 
}

static int pa_stop_sound(int soundID) {
    struct iaxc_sound **sp;
    struct iaxc_sound *s;
    int retval = 1; /* not found */

  MUTEXLOCK(&sound_lock);
    for(sp = &sounds; *sp; (*sp) = (*sp)->next) {
	s = *sp;	
	if(s->id == soundID) {
	   if(s->malloced)
	     free(s->data);
	   /* remove from list */ 
	   (*sp) = s->next;
	   free(s);
	   
	   retval= 0; /* found */
	   break;
	}
    }
  MUTEXUNLOCK(&sound_lock);

  return retval; /* found? */
}

static void iaxc_echo_can(short *inputBuffer, short *outputBuffer, int n)
{
    static RingBuffer outRing;
    static char outRingBuf[EC_RING_SZ];
    static long bias = 0;
    short  delayedBuf[1024];
    int i;

    /* remove bias -- whether ec is on or not. */
    for(i = 0; i < n; i++) {
	bias +=  ((((long)inputBuffer[i]) << 15) - bias) >> 14;
	inputBuffer[i] -= (bias >> 15);
    }


    /* if ec is off, clear ec state -- this way, we start fresh if/when
     * it's turned back on. */
    if(!(iaxc_filters & IAXC_FILTER_ECHO)) {
	if(ec)  {
#if defined(USE_MEC2) || defined(SPAN_EC)
	  echo_can_free(ec); 
	  ec = NULL;
#endif
#if defined(SPEEX_EC)
	  speex_echo_state_destroy(ec);
	  ec = NULL;
#endif
	}
	    
	return;
    }

    /* we want echo cancellation */

    if(!ec) {
	RingBuffer_Init(&outRing, EC_RING_SZ, &outRingBuf);
#if defined(USE_MEC2) || defined(SPAN_EC)
	ec = echo_can_create(ECHO_TAIL, 0);
#endif
#if defined(SPEEX_EC)
	ec = speex_echo_state_init(SAMPLES_PER_FRAME, ECHO_TAIL); 
#endif
    }

    /* fill outRing */
    RingBuffer_Write(&outRing, outputBuffer, n * 2);

    // Make sure we have enough buffer.
    // Currently, just one SAMPLES_PER_FRAME's worth.
    if(RingBuffer_GetReadAvailable(&outRing) < ((n + SAMPLES_PER_FRAME) * 2) ) 
      return;

    RingBuffer_Read(&outRing, delayedBuf, n * 2);

    
    
#if defined(SPEEX_EC)
    {
      short cancelledBuffer[1024];

      speex_echo_cancel(ec, inputBuffer, delayedBuf, cancelledBuffer, NULL);

      for(i=0;i<n;i++)
	  inputBuffer[i] =  cancelledBuffer[i];
    }
#endif

#if defined(USE_MEC2) || defined(SPAN_EC)
      for(i=0;i<n;i++)  
	inputBuffer[i] = echo_can_update(ec, delayedBuf[i], inputBuffer[i]);
#endif

}

static int pa_callback(void *inputBuffer, void *outputBuffer,
	    unsigned long samplesPerFrame, PaTimestamp outTime, void *userData ) {

    int totBytes = samplesPerFrame * sizeof(SAMPLE);

    short virtualInBuffer[MAX_SAMPLES_PER_FRAME * 2];
    short virtualOutBuffer[MAX_SAMPLES_PER_FRAME * 2];

#if 0
    /* I think this can't happen */
    if(virtualMono && samplesPerFrame > SAMPLES_PER_FRAME) {
	fprintf(stderr, "ERROR: buffer in callback is too big!\n");
	exit(1);
    }
#endif

    if(outputBuffer)
    {  
	int bWritten;
	/* output underflow might happen here */
	if(virtualMonoOut) {
	  bWritten = RingBuffer_Read(&outRing, virtualOutBuffer, totBytes);
	  /* we zero "virtualOutBuffer", then convert the whole thing,
	   * yes, because we use virtualOutBuffer for ec below */
	  if(bWritten < totBytes) {
	      memset(((char *)virtualOutBuffer) + bWritten, 0, totBytes - bWritten);
	      //fprintf(stderr, "*U*");
	  }
	  mono2stereo(outputBuffer, virtualOutBuffer, samplesPerFrame);
	} else {
	  bWritten = RingBuffer_Read(&outRing, outputBuffer, totBytes);
	  if(bWritten < totBytes) {
	      memset((char *)outputBuffer + bWritten, 0, totBytes - bWritten);
	      //fprintf(stderr, "*U*");
	  }
	}

	/* zero underflowed space [ silence might be more golden than garbage? ] */

	pa_mix_sounds(outputBuffer, samplesPerFrame, 0);

	if(!auxStream)
	    pa_mix_sounds(outputBuffer, samplesPerFrame, 1);
    }


    if(inputBuffer) {
	/* input overflow might happen here */
	if(virtualMonoIn) {
	  stereo2mono(virtualInBuffer, inputBuffer, samplesPerFrame);
	  iaxc_echo_can(virtualInBuffer, virtualOutBuffer, samplesPerFrame);

	  RingBuffer_Write(&inRing, virtualInBuffer, totBytes);
	} else {

	  iaxc_echo_can(inputBuffer, outputBuffer, samplesPerFrame);

	  RingBuffer_Write(&inRing, inputBuffer, totBytes);
	}
    }

    return 0; 
}

static int pa_aux_callback(void *inputBuffer, void *outputBuffer,
	    unsigned long samplesPerFrame, PaTimestamp outTime, void *userData ) {

    int totBytes = samplesPerFrame * sizeof(SAMPLE);

    /* XXX: need to handle virtualMonoOut case!!! */
    if(outputBuffer)
    {  
        memset((char *)outputBuffer, 0, totBytes);
	pa_mix_sounds(outputBuffer, samplesPerFrame, 1);
    }
    return 0; 
}

static int pa_open(int single, int inMono, int outMono)
{
    PaError err;
    if (single) {
        err = Pa_OpenStream(&iStream, 
	      selectedInput, (inMono ? 1 : 2), paInt16, NULL,
	      selectedOutput, (outMono ? 1 : 2), paInt16, NULL,
	      sample_rate, 
	      SAMPLES_PER_FRAME,
	      PA_NUMBUFFERS,
	      0,
	      pa_callback, 
	      NULL);
    
        if (err != paNoError) {
            return -1;
        }

        oStream = iStream;
        oneStream = 1;
    } else {
        err = Pa_OpenStream(&iStream, 
	      selectedInput, (inMono ? 1 : 2), paInt16, NULL,
	      paNoDevice, 0, paInt16, NULL,
	      sample_rate, 
	      SAMPLES_PER_FRAME,
	      PA_NUMBUFFERS,
	      0,
	      pa_callback, 
	      NULL);
        if (err != paNoError) {
	    return -1;
        }

        err = Pa_OpenStream(&oStream, 
	      paNoDevice, 0, paInt16, NULL,
	      selectedOutput, (outMono ? 1 : 2), paInt16, NULL,
	      sample_rate, 
	      SAMPLES_PER_FRAME,
	      PA_NUMBUFFERS,
	      0,
	      pa_callback, 
	      NULL);
        if (err != paNoError) {
	    Pa_CloseStream(iStream);
            iStream = NULL;
	    return -1;
        }

        oneStream = 0;
    }

    virtualMonoIn = (inMono ? 0 : 1);
    virtualMonoOut = (outMono ? 0 : 1);
    return 0;
}

/* some commentary here:
 * 1: MacOSX: MacOSX often needs "virtual mono" and a single stream.  
 * That doesn't work for some USB devices (a Platronics headset), so 
 * mono in, virtual mono out, and mono in/out are also tried.
 *
 * 2: Unix/OSS: most cards are OK with real mono, and a single stream.
 * Except some.  For those, a single open with real mono will succeed,
 * but execution will fail.  Maybe others will open OK with a single
 * stream, and real mono, but fail later? Two stream mono is tried first,
 * since it reportedly provides better sound quality with ALSA
 * and Sound Blaster Live.
 *
 * The failure mode I saw with a volunteer was that reads/writes would
 * return -enodev (down in the portaudio code).  Bummer.
 *
 * Win32 works fine, in all cases, with a single stream and real mono,
 * so far.
 *
 * We could probably do this more cleanly, because there are still cases
 * where we will fail (i.e. if the user has only mono in and out on a Mac).
 *
 * */
static int pa_openstreams (struct iaxc_audio_driver *d ) {
    int err;
#ifdef LINUX
    err = pa_open(0, 1, 1) && /* two stream mono */
        pa_open(1, 1, 1) &&   /* one stream mono */
        pa_open(0, 0, 0);     /* two stream stereo */
#else
#ifdef MACOSX
    err = pa_open(1, 0, 0) &&  /* one stream stereo */
        pa_open(1, 1, 0) &&    /* one stream mono in stereo out */
        pa_open(1, 1, 1) &&    /* one stream mono */
        pa_open(0, 0, 0);      /* two stream stereo */
#else
    err = pa_open(1, 1, 1) &&  /* one stream mono */
        pa_open(1, 0, 0) &&    /* one stream stereo */
        pa_open(1, 1, 0) &&    /* one stream mono in stereo out */
        pa_open(0, 0, 0);      /* two stream stereo */
#endif /*MACOSX */
#endif /* LINUX */

    if (err) {
	handle_paerror(err, "Unable to open streams");
	return -1;
    }
    return 0;
}

static int pa_openauxstream (struct iaxc_audio_driver *d ) {
    PaError err;

    err = Pa_OpenStream ( &aStream, 
	      paNoDevice, 0, paInt16, NULL,  /* input info */
	      selectedRing,  virtualMonoOut+1, paInt16, NULL,  /* output info */
	      sample_rate, 
	      SAMPLES_PER_FRAME,  /* frames per buffer -- 10ms */
	      PA_NUMBUFFERS,   /* numbuffers */  /* use default */
	      0,   /* flags */
	      pa_aux_callback, 
	      NULL /* userdata */
      );
    if( err != paNoError ) 
    {
	handle_paerror(err, "opening separate ring stream");
	return -1;
    }

    return 0;
}

static int pa_start (struct iaxc_audio_driver *d ) {
    PaError err;
    static int errcnt=0;

    if(running) return 0;

    /* re-open mixers if necessary */
    if(iMixer)
    { 
	Px_CloseMixer(iMixer);
	iMixer = NULL;
    }

    if(oMixer) 
    {
	Px_CloseMixer(oMixer);
	oMixer = NULL;
    }
	
    //fprintf(stderr, "starting pa\n");

    if(errcnt > 5) {
	iaxc_usermsg(IAXC_TEXT_TYPE_FATALERROR,
		"iaxclient audio: Can't open Audio Device.  Perhaps you do not have an input or output device?");
	/* OK, we'll give the application the option to abort or not here, but we will throw a fatal error
	 * anyway */
	iaxc_millisleep(1000);
	//return -1; // Give Up.  Too many errors.
    }

    /* flush the ringbuffers */
    RingBuffer_Init(&inRing, INRBSZ, inRingBuf);
    RingBuffer_Init(&outRing, OUTRBSZ, outRingBuf);

    if(pa_openstreams(d))  {
	errcnt++;
        return -1;
    }

    errcnt = 0; // only count consecutive errors.

    err = Pa_StartStream(iStream); 
    if(err != paNoError)
	return -1;

    iMixer = Px_OpenMixer(iStream, 0);

    if(!oneStream){ 
	err = Pa_StartStream(oStream);
	oMixer = Px_OpenMixer(oStream, 0);
	if(err != paNoError) {
	    Pa_StopStream(iStream);
	    return -1;
	}
    }
    
    if(selectedRing != selectedOutput) {
        auxStream = 1;
    } else {
        auxStream = 0;
    }

    if(auxStream){ 
        pa_openauxstream(d); 
	err = Pa_StartStream(aStream);
	if(err != paNoError) {
	    auxStream = 0;
	}
    }

	/* select the microphone as the input source */
	if ( iMixer != NULL )
	{
#ifdef WIN32 // temporary until other impl have this function
		_Px_SetCurrentInputSourceByName( iMixer, "microphone" ) ;
		_Px_SetMicrophoneBoost( iMixer, 0 ) ;
#else
		int n = Px_GetNumInputSources( iMixer ) - 1 ;
		for ( ; n > 0 ; --n )
		{
			if ( strcasecmp( "microphone", Px_GetInputSourceName( iMixer, n ) ) == 0 )
			{
				Px_SetCurrentInputSource( iMixer, n ) ;
			}
		}
#endif
	}

    running = 1;
    return 0;
}

static int pa_stop (struct iaxc_audio_driver *d ) {
    PaError err;

    if(!running) return 0;
    if(sounds) return 0;

    err = Pa_AbortStream(iStream); 
    err = Pa_CloseStream(iStream); 


    if(!oneStream){ 
	err = Pa_AbortStream(oStream);
	err = Pa_CloseStream(oStream);
    }

    if(auxStream){ 
	err = Pa_AbortStream(aStream);
	err = Pa_CloseStream(aStream);
    }

    running = 0;
    return 0;
}

static void pa_shutdown() {
    CloseAudioStream( iStream );
    if(!oneStream) CloseAudioStream( oStream );
    if(auxStream) CloseAudioStream( aStream );
}

static void handle_paerror(PaError err, char * where) {
	fprintf(stderr, "PortAudio error at %s: %s\n", where, Pa_GetErrorText(err));
}

static int pa_input(struct iaxc_audio_driver *d, void *samples, int *nSamples) {
	int bytestoread;

	bytestoread = *nSamples * sizeof(SAMPLE);

	/* we don't return partial buffers */
	if(RingBuffer_GetReadAvailable(&inRing) < bytestoread) {
	    *nSamples = 0;
	    return 0;	
	}

	RingBuffer_Read(&inRing, samples, bytestoread);

	return 0;
}

static int pa_output(struct iaxc_audio_driver *d, void *samples, int nSamples) {
	int bytestowrite = nSamples * sizeof(SAMPLE);
	int outRingLen;

	outRingLen = RingBuffer_GetReadAvailable(&outRing);
	outRingLenAvg = (outRingLenAvg * 9 + outRingLen ) / 10;

	/* if we've got a big output buffer, drop this */
	if(outRingLen > (RBOUTTARGET_BYTES) && outRingLenAvg > RBOUTTARGET_BYTES)  {
	  //fprintf(stderr, "*O*");
	  return outRingLen/2;
	}


	//if(RingBuffer_GetWriteAvailable(&outRing) < bytestowrite)  fprintf(stderr, "O");

	RingBuffer_Write(&outRing, samples, bytestowrite);

	return (outRingLen + bytestowrite)/2;

}

static int pa_select_devices (struct iaxc_audio_driver *d, int input, int output, int ring) {
    selectedInput = input;
    selectedOutput = output;
    selectedRing = ring;
    if(running) {
      pa_stop(d);
      pa_start(d);
    }
    return 0;
}

static int pa_selected_devices (struct iaxc_audio_driver *d, int *input, int *output, int *ring) {
    *input = selectedInput;
    *output = selectedOutput;
    *ring = selectedRing;
    return 0;
}

static int pa_destroy (struct iaxc_audio_driver *d ) {
    //implementme
    return 0;
}

static double pa_input_level_get(struct iaxc_audio_driver *d)
{
	/* iMixer should be non-null if we using either one or two streams */
    if(!iMixer) return -1;

	/* make sure this device supports input volume controls */
	if ( Px_GetNumInputSources( iMixer ) == 0 )
		return -1 ;

    return Px_GetInputVolume(iMixer);
}

static double pa_output_level_get(struct iaxc_audio_driver *d){
    PxMixer *mix;

	/* oMixer may be null if we're using one stream,
	   in which case, iMixer should not be null,
	   if it is, return an error */
	   
    if(oMixer)
      mix = oMixer;
    else if (iMixer)
      mix = iMixer;
    else
      return -1;

	/* prefer the pcm output, but default to the master output */
    if (Px_SupportsPCMOutputVolume(mix)) return Px_GetPCMOutputVolume( mix );
    else return Px_GetMasterVolume( mix );       
}

static int pa_input_level_set(struct iaxc_audio_driver *d, double level){
    if(!iMixer) return -1;
     
	/* make sure this device supports input volume controls */
	if ( Px_GetNumInputSources( iMixer ) == 0 )
		return -1 ;

    //fprintf(stderr, "setting input level to %f\n", level);
    Px_SetInputVolume(iMixer, level);
    return 0;
}

static int pa_output_level_set(struct iaxc_audio_driver *d, double level){
    PxMixer *mix;

    if(oMixer)
      mix = oMixer;
    else if (iMixer)
      mix = iMixer;
    else
      return -1;

	/* prefer the pcm output, but default to the master output */
    if (Px_SupportsPCMOutputVolume( mix )) Px_SetPCMOutputVolume( mix, level );
    else Px_SetMasterVolume( mix, level );     

    return 0;
}

static int pa_mic_boost_get( struct iaxc_audio_driver* d )
{
	int enable = -1 ;
#ifdef WIN32
	if ( iMixer != NULL )
	{
		enable = _Px_GetMicrophoneBoost( iMixer ) ;
	}
#endif
	return enable ;
}

int pa_mic_boost_set( struct iaxc_audio_driver* d, int enable )
{
	int err = -1 ;
#ifdef WIN32
	if ( iMixer != NULL )
	{
		err = _Px_SetMicrophoneBoost( iMixer, enable ) ;
	}
#endif
	return err ;
}

/* initialize audio driver */
int pa_initialize (struct iaxc_audio_driver *d, int sr) {
    PaError  err;

    sample_rate = sr;

    /* initialize portaudio */
    if(paNoError != (err = Pa_Initialize()))
	return err;

    /* scan devices */
    scan_devices(d);

    /* setup methods */
    d->initialize = pa_initialize;
    d->destroy = pa_destroy;
    d->select_devices = pa_select_devices;
    d->selected_devices = pa_selected_devices;
    d->start = pa_start;
    d->stop = pa_stop;
    d->output = pa_output;
    d->input = pa_input;
    d->input_level_get = pa_input_level_get;
    d->input_level_set = pa_input_level_set;
    d->output_level_get = pa_output_level_get;
    d->output_level_set = pa_output_level_set;
    d->play_sound = pa_play_sound;
    d->stop_sound = pa_stop_sound;
    d->mic_boost_get = pa_mic_boost_get;
    d->mic_boost_set = pa_mic_boost_set;

    /* setup private data stuff */
    selectedInput  = Pa_GetDefaultInputDeviceID();
    selectedOutput = Pa_GetDefaultOutputDeviceID();
    selectedRing   = Pa_GetDefaultOutputDeviceID();
    sounds	   = NULL;
    MUTEXINIT(&sound_lock);

    

    RingBuffer_Init(&inRing, INRBSZ, inRingBuf);
    RingBuffer_Init(&outRing, OUTRBSZ, outRingBuf);

    running = 0;

    /* start, then stop streams, in order to test devices, and get mixers */
    pa_start(d);
    /* if input level is very low, raise it a bit;  helps AAGC work properly */
    {
      double level;
      level = pa_input_level_get(d);
      if(level < 0.5)
	pa_input_level_set(d,0.6);
    }
    pa_stop(d);

    return 0;
}
