⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 audio_portaudio.c

📁 ppciaxclient softphone
💻 C
📖 第 1 页 / 共 2 页
字号:
/*
 * 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 <string.h>
#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

#if (!defined(USE_MEC2) && !defined(SPAN_EC) && !defined(SPEEX_EC))
void *ec = NULL;
#endif


#if defined(_MSC_VER)
#define strcasecmp strcmp
#endif

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


typedef short SAMPLE;

static PortAudioStream *iStream, *oStream, *aStream;
#ifndef _POCKETPC_
static PxMixer *iMixer = NULL, *oMixer = NULL;
#endif

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);
	if(!pa)
		continue;
	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)

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -