📄 audio_portaudio.c
字号:
/*
* 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 + -