/*
 * 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
 *
 */

#if defined(WIN32)  ||  defined(_WIN32_WCE)
#include <stdlib.h>
#define strcasecmp _stricmp
#else
#include <strings.h>
#endif

#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

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


typedef short SAMPLE;

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

static int selectedInput, selectedOutput, selectedRing;

static int sample_rate = 8000;
static int mixers_initialized;


#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;

/* forward declarations */
static int pa_start (struct iaxc_audio_driver *d ) ;
static void handle_paerror(PaError err, char * where);
static int pa_input_level_set(struct iaxc_audio_driver *d, double level);
static double pa_input_level_get(struct iaxc_audio_driver *d);

/* 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_GetDeviceCount();
    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]);

		if ( pa )	//frik: under Terminal Services this is NULL
        {
			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_GetDefaultInputDevice())
				dev->capabilities |= IAXC_AD_INPUT_DEFAULT;
		
			if(i == Pa_GetDefaultOutputDevice()) 
			{
				dev->capabilities |= IAXC_AD_OUTPUT_DEFAULT;
				dev->capabilities |= IAXC_AD_RING_DEFAULT;
		   	}
		}
        else //frik: under Terminal Services
        {
            dev->name = "Not usable device";
            dev->devID = i;
            dev->capabilities = 0;
        }

    }
    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) < (unsigned long int) (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 int) inputBuffer[i]) << 15) - bias) >> 14;
	inputBuffer[i] -= (short int) (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, const PaStreamCallbackTimeInfo* outTime, PaStreamCallbackFlags statusFlags, 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, const PaStreamCallbackTimeInfo* outTime, PaStreamCallbackFlags statusFlags, 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;
	PaDeviceInfo	*result;
	
	struct PaStreamParameters in_stream_params, out_stream_params, no_device;
	in_stream_params.device = selectedInput;
	in_stream_params.channelCount = (inMono ? 1 : 2);
	in_stream_params.sampleFormat = paInt16;
	result = (PaDeviceInfo *)Pa_GetDeviceInfo(selectedInput);
	if ( result == NULL ) return -1;
	in_stream_params.suggestedLatency = result->defaultLowInputLatency;
	in_stream_params.hostApiSpecificStreamInfo = NULL;

	out_stream_params.device = selectedOutput;
	out_stream_params.channelCount = (outMono ? 1 : 2);
	out_stream_params.sampleFormat = paInt16;
	result = (PaDeviceInfo *)Pa_GetDeviceInfo(selectedOutput);
	if ( result == NULL ) return -1;
	out_stream_params.suggestedLatency = result->defaultLowOutputLatency;
	out_stream_params.hostApiSpecificStreamInfo = NULL;

	no_device.device = paNoDevice;
	no_device.channelCount = 0;
	no_device.sampleFormat = paInt16;
	result = (PaDeviceInfo *)Pa_GetDeviceInfo(selectedInput);
	if ( result == NULL ) return -1;
	no_device.suggestedLatency = result->defaultLowInputLatency; // FEEDBACK - unsure if appropriate
	no_device.hostApiSpecificStreamInfo = NULL;

	if ( single ) 
	{
		err = Pa_OpenStream(&iStream, 
			&in_stream_params,
			&out_stream_params,
			sample_rate, 
			paFramesPerBufferUnspecified, //FEEBACK - unsure if appropriate
			paNoFlag,
			(PaStreamCallback *)pa_callback,
			NULL);
		if (err != paNoError) return -1;
		oStream = iStream;
		oneStream = 1;
    } else 
	{
		err = Pa_OpenStream(&iStream, 
			&in_stream_params,
			&no_device,
			sample_rate, 
			paFramesPerBufferUnspecified, //FEEBACK - unsure if appropriate
			paNoFlag,
			(PaStreamCallback *)pa_callback,
			NULL);
		if ( err != paNoError ) return -1;
		
		err = Pa_OpenStream(&oStream, 
			&no_device,
			&out_stream_params,
			sample_rate, 
			paFramesPerBufferUnspecified, //FEEBACK - unsure if appropriate
			paNoFlag,
			(PaStreamCallback *)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;

	/* FEEDBACK - iaxclient seems to assume that the ring device is a mono device.
	I can't find any mono devices on the Mac and so ring device opening will fail.
	My code assumes the ring device is a stereo device - this might break stuff */
	struct PaStreamParameters ring_stream_params, no_device;

	struct PaStreamParameters in_stream_params;
	in_stream_params.device = selectedInput;
	in_stream_params.channelCount = virtualMonoOut + 1;
	in_stream_params.sampleFormat = paInt16;
	in_stream_params.suggestedLatency = Pa_GetDeviceInfo(selectedInput)->defaultLowInputLatency;
	in_stream_params.hostApiSpecificStreamInfo = NULL;

	ring_stream_params.device = selectedRing;
	ring_stream_params.channelCount = virtualMonoOut+1;
	ring_stream_params.sampleFormat = paInt16;
	ring_stream_params.suggestedLatency = Pa_GetDeviceInfo(selectedRing)->defaultLowOutputLatency;
	ring_stream_params.hostApiSpecificStreamInfo = NULL;

	no_device.device = paNoDevice;
	no_device.channelCount = 0;
	no_device.sampleFormat = paInt16;
	no_device.suggestedLatency=Pa_GetDeviceInfo(selectedInput)->defaultLowInputLatency; //TODOC
	no_device.hostApiSpecificStreamInfo = NULL;

    err = Pa_OpenStream ( &aStream,
		&in_stream_params,
		&ring_stream_params,
		sample_rate,
		paFramesPerBufferUnspecified, //FEEBACK - unsure if appropriate
		paNoFlag,
		(PaStreamCallback *)pa_aux_callback,
		NULL); 
    if( err != paNoError ) 
    {
	/* FEEDBACK, we try one more time, maybe ring device is a mono output device */
	err = Pa_OpenStream ( &aStream,
		&no_device,
		&ring_stream_params,
		sample_rate,
		paFramesPerBufferUnspecified, //FEEBACK - unsure if appropriate
              	paNoFlag,   /* flags */
		(PaStreamCallback *)pa_aux_callback,
		NULL);
    }

    // mmok, failure...
    if(err != paNoError)
    {
	//fprintf(stderr, "Failure opening ring device with params: id: %d, output %d, default output %d\n", selectedRing, selectedOutput, Pa_GetDefaultOutputDevice());

	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;
    double level;

    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 && !mixers_initialized )
    {
	  /* First, select the "microphone" device, if it's available */
	  /* try the new method, reverting to the old if it fails */
	  if ( Px_SetCurrentInputSourceByName( iMixer, "microphone" ) != 0 )
	  {
		  int n = Px_GetNumInputSources( iMixer ) - 1 ;
		  for ( ; n > 0 ; --n )
		  {
			  if ( strcasecmp( "microphone", Px_GetInputSourceName( iMixer, n ) ) == 0 )
			  {
				  Px_SetCurrentInputSource( iMixer, n ) ;
			  }
		  }
	  }
	  
	  /* try to set the microphone boost -- we just turn off this "boost" feature, because
	      it often leads to clipping, which we can't fix later -- but we can deal with low input levels
	      much more gracefully */
	  Px_SetMicrophoneBoost( iMixer, 0 ) ;

	  /* if the input level is very low, raise it up a bit.  Otherwise, AGC cannot detect speech, 
	     and cannot adjust levels */
	  level = pa_input_level_get(d);
	  if(level < 0.5)
	    pa_input_level_set(d,0.6);
	  mixers_initialized = 1;
    }

    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;
}
/* Mihai: apparently nobody loves this function. Some actually hate it.
 * I bet if it's gone, no one will miss it.  Such a cold, cold world!
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 > (int) RBOUTTARGET_BYTES  &&  outRingLenAvg > (int) 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 ) 
{
	return Pa_Terminate();
}

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, (float) 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, (float) level);
	else 
		Px_SetMasterVolume(mix, (float) level);

    return 0;
}

static int pa_mic_boost_get( struct iaxc_audio_driver* d )
{
	int enable = -1 ;
	if ( iMixer != NULL )
		enable = Px_GetMicrophoneBoost( iMixer ) ;
	return enable ;
}

int pa_mic_boost_set( struct iaxc_audio_driver* d, int enable )
{
	int err = -1 ;
	if ( iMixer != NULL )
		err = Px_SetMicrophoneBoost( iMixer, enable ) ;
	return err ;
}

/* initialize audio driver */
static 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_GetDefaultInputDevice();
    selectedOutput = Pa_GetDefaultOutputDevice();
    selectedRing   = Pa_GetDefaultOutputDevice();
    sounds	   = NULL;
    MUTEXINIT(&sound_lock);

    

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

    running = 0;


    return 0;
}

/* standard initialization:  Do the normal initialization, and then
   also initialize mixers and levels */
int pa_initialize (struct iaxc_audio_driver *d, int sr) {
    _pa_initialize(d, sr);
    /* start/stop audio, in order to initialize mixers and levels */
    pa_start(d);
    pa_stop(d);
    return 0;
}

/* alternate initialization:  delay mixer/level initialization until
   we actually start the device.  This is somewhat useful when you're about to start
   the device as soon as you've initialized it, and want to avoid the time it
   takes to start/stop the device before starting it again */
int pa_initialize_deferred(struct iaxc_audio_driver *d, int sr) {
    _pa_initialize(d, sr);
    return 0;
}


