📄 audio_portaudio.c
字号:
{
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;
#ifndef _POCKETPC_
/* re-open mixers if necessary */
if(iMixer)
{
Px_CloseMixer(iMixer);
iMixer = NULL;
}
if(oMixer)
{
Px_CloseMixer(oMixer);
oMixer = NULL;
}
#endif
//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;
#ifndef _POCKETPC_
iMixer = Px_OpenMixer(iStream, 0);
#endif
if(!oneStream){
err = Pa_StartStream(oStream);
#ifndef _POCKETPC_
oMixer = Px_OpenMixer(oStream, 0);
#endif
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;
}
}
#ifndef _POCKETPC_
/* select the microphone as the input source */
if ( iMixer != NULL )
{
/* 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 */
Px_SetMicrophoneBoost( iMixer, 0 ) ;
}
#endif
running = 1;
return 0;
}
static int pa_stop (struct iaxc_audio_driver *d ) {
PaError err;
if(!running) return 0;
if(sounds) return 0;
// if(iMixer)
// Px_CloseMixer(iMixer);
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 );
#ifndef _POCKETPC_
if(iMixer)
Px_CloseMixer(iMixer);
#endif
}
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
free(d->devices);
#ifndef _POCKETPC_
if(iMixer)
{
Px_CloseMixer(iMixer);
iMixer = NULL;
}
#endif
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 */
#ifndef _POCKETPC_
if(!iMixer) return -1;
/* make sure this device supports input volume controls */
if ( Px_GetNumInputSources( iMixer ) == 0 )
return -1 ;
return Px_GetInputVolume(iMixer);
#else
return 0;
#endif
}
static double pa_output_level_get(struct iaxc_audio_driver *d){
#ifndef _POCKETPC_
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 );
#endif
}
static int pa_input_level_set(struct iaxc_audio_driver *d, double level){
#ifndef _POCKETPC_
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);
#endif
return 0;
}
static int pa_output_level_set(struct iaxc_audio_driver *d, double level){
#ifndef _POCKETPC_
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 );
#endif
return 0;
}
static int pa_mic_boost_get( struct iaxc_audio_driver* d )
{
int enable = -1 ;
#ifndef _POCKETPC_
if ( iMixer != NULL )
enable = Px_GetMicrophoneBoost( iMixer ) ;
#endif
return enable ;
}
int pa_mic_boost_set( struct iaxc_audio_driver* d, int enable )
{
#ifndef _POCKETPC_
int err = -1 ;
if ( iMixer != NULL )
err = Px_SetMicrophoneBoost( iMixer, enable ) ;
return err ;
#else
return 0;
#endif
}
/* 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;
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -