1
0
Fork 0
flightgear/3rdparty/iaxclient/lib/audio_openal.c
James Turner 8ae98ea543 IAXClient OpenAL: clean up our buffers
Avoid a console warning from OpenAL-soft about leaked buffers on
shutdown, by ensuring IAXClient backend does matching cleanup 
for the buffers it allocates.
2022-06-01 11:50:38 +01:00

325 lines
8.5 KiB
C

#include "iaxclient_lib.h"
// changed by James Turner to use non-prefixed OpenAL include paths,
// which is what CMake official FindOpenAL.cmake and the OpenAL-soft Config.cmake
// expect to provide. This avoids any pre-processor logic to decide which
// prefix is correct.
#include <al.h>
#include <alc.h>
struct openal_priv_data
{
int sample_rate;
int num_buffers;
int buffers_head;
int buffers_tail;
int buffers_free;
ALuint* buffers;
ALCcontext* out_ctx;
ALuint source;
ALCdevice* in_dev;
float input_level;
float output_level;
};
static struct iaxc_audio_device device = {
"default",
IAXC_AD_INPUT | IAXC_AD_OUTPUT | IAXC_AD_RING | IAXC_AD_INPUT_DEFAULT | IAXC_AD_OUTPUT_DEFAULT | IAXC_AD_RING_DEFAULT,
0
};
static int openal_error(const char* function, int err)
{
fprintf(stderr, "OpenAl function %s failed with code %d\n", function, err);
return -1;
}
int openal_input(struct iaxc_audio_driver *d, void *samples, int *nSamples)
{
int err;
struct openal_priv_data* priv = (struct openal_priv_data*)(d->priv);
ALCint available;
ALCsizei request;
alcGetIntegerv(priv->in_dev, ALC_CAPTURE_SAMPLES, sizeof(available), &available);
/* do not return less data than caller wanted, iaxclient does not like it */
request = (available < *nSamples) ? 0 : *nSamples;
if (request > 0)
{
err = alcGetError(priv->in_dev);
alcCaptureSamples(priv->in_dev, samples, request);
err = alcGetError(priv->in_dev);
if (err)
{
openal_error("alcCaptureSamples", err);
*nSamples = 0;
return 1;
}
// software mute, but keep data flowing for sync purposes
if (priv->input_level == 0)
{
memset(samples, 0, 2 * request);
}
}
*nSamples = request;
return 0;
}
static void openal_unqueue(struct openal_priv_data* priv)
{
int i;
ALint err;
ALint processed;
alGetSourcei(priv->source, AL_BUFFERS_PROCESSED, &processed);
#ifdef OPENAL_DEBUG
{
ALint queued;
ALint state;
alGetSourcei(priv->source, AL_BUFFERS_QUEUED, &queued);
alGetSourcei(priv->source, AL_SOURCE_STATE, &state);
fprintf(stderr, "free: %d processed: %d queued: %d head: %d tail: %d state: %d\n",
priv->buffers_free, processed, queued, priv->buffers_head, priv->buffers_tail, state);
}
#endif
alGetError();
for(i = 0; i < processed; i++)
{
alSourceUnqueueBuffers(priv->source, 1, priv->buffers + priv->buffers_tail);
err = alGetError();
if (err)
{
openal_error("alSourceUnqueueBuffers", err);
break;
}
if (++priv->buffers_tail >= priv->num_buffers)
{
priv->buffers_tail = 0;
}
++priv->buffers_free;
}
}
int openal_output(struct iaxc_audio_driver *d, void *samples, int nSamples)
{
struct openal_priv_data* priv = (struct openal_priv_data*)(d->priv);
openal_unqueue(priv);
/* If we run out of buffers, wait for an arbitrary number to become free */
if (priv->buffers_free == 0)
{
while(priv->buffers_free < 4)
{
iaxc_millisleep(100);
openal_unqueue(priv);
}
}
if (priv->buffers_free > 0)
{
ALuint buffer = priv->buffers[priv->buffers_head++];
if (priv->buffers_head >= priv->num_buffers)
{
priv->buffers_head = 0;
}
alBufferData(buffer, AL_FORMAT_MONO16, samples, nSamples * 2, priv->sample_rate);
alSourceQueueBuffers(priv->source, 1, &buffer);
--priv->buffers_free;
/* delay start of output until we have 2 buffers */
if (priv->buffers_free == priv->num_buffers - 2)
{
ALint state;
alGetSourcei(priv->source, AL_SOURCE_STATE, &state);
if (state != AL_PLAYING)
{
#ifdef OPENAL_DEBUG
fprintf(stderr, "calling alSourcePlay\n");
#endif
alSourcePlay(priv->source);
}
}
} else {
fprintf(stderr, "openal_output buffer overflow\n");
return 1;
}
return 0;
}
int openal_select_devices(struct iaxc_audio_driver *d, int input, int output, int ring)
{
return (input != 0 || output !=0 || ring != 0) ? -1 : 0;
}
int openal_selected_devices(struct iaxc_audio_driver *d, int *input, int *output, int *ring)
{
*input = 0;
*output = 0;
*ring = 0;
return 0;
}
/*
Apparently iaxclient calls openal_start a gazillion times and doesn't call openal_stop.
So let's just make them no-ops.
*/
int openal_start(struct iaxc_audio_driver *d)
{
int iret = 0;
struct openal_priv_data* priv = (struct openal_priv_data*)(d->priv);
if (priv) /* just to stop compiler noise */
iret = 0;
return iret;
}
int openal_stop(struct iaxc_audio_driver *d)
{
int iret = 0;
struct openal_priv_data* priv = (struct openal_priv_data*)(d->priv);
if (priv) /* just to stop compiler noise */
iret = 0;
return iret;
}
float openal_input_level_get(struct iaxc_audio_driver *d)
{
struct openal_priv_data* priv = (struct openal_priv_data*)(d->priv);
return priv->input_level;
}
float openal_output_level_get(struct iaxc_audio_driver *d)
{
struct openal_priv_data* priv = (struct openal_priv_data*)(d->priv);
return priv->output_level;
}
int openal_input_level_set(struct iaxc_audio_driver *d, float level)
{
struct openal_priv_data* priv = (struct openal_priv_data*)(d->priv);
priv->input_level = (level < 0.5) ? 0 : 1;
return 0;
}
int openal_output_level_set(struct iaxc_audio_driver *d, float level)
{
struct openal_priv_data* priv = (struct openal_priv_data*)(d->priv);
priv->output_level = level;
alSourcef(priv->source, AL_GAIN, level);
return 0;
}
int openal_play_sound(struct iaxc_sound *s, int ring)
{
return 0;
}
int openal_stop_sound(int id)
{
return 0;
}
int openal_mic_boost_get(struct iaxc_audio_driver *d)
{
return 0;
}
int openal_mic_boost_set(struct iaxc_audio_driver *d, int enable)
{
return 0;
}
int openal_destroy(struct iaxc_audio_driver *d)
{
struct openal_priv_data* priv = (struct openal_priv_data*)(d->priv);
alcCaptureStop(priv->in_dev);
alcCaptureCloseDevice(priv->in_dev);
alDeleteSources(1, &priv->source);
// FlightGear: added by James to avoid a warning from OpenAL-soft
// when we destroy the context in SGSoundMgr_OpenAL: since we
// genBuffers below, we should balance that out.
alDeleteBuffers(priv->num_buffers, priv->buffers);
return 0;
}
int openal_initialize(struct iaxc_audio_driver *d, int sample_rate)
{
struct openal_priv_data* priv = malloc(sizeof(struct openal_priv_data));
int err = alGetError();
d->priv = priv;
// First we are looking for output device
priv->out_ctx = alcGetCurrentContext();
if( priv->out_ctx == NULL ) { // FGCom standalone only
ALCdevice* out_dev = alcOpenDevice(NULL);
if (out_dev == 0) return openal_error("alcOpenDevice", alGetError());
priv->out_ctx = alcCreateContext(out_dev, 0);
if (priv->out_ctx == 0) return openal_error("alcCreateContext", alGetError());
}
alcMakeContextCurrent(priv->out_ctx);
if ((err = alGetError())) return openal_error("alcMakeContextCurrent", err);
// Then we look for input device
priv->in_dev = alcCaptureOpenDevice(NULL, 8000, AL_FORMAT_MONO16, 800);
if (!priv->in_dev) return openal_error("alcCaptureOpenDevice", alGetError());
if ((err = alGetError())) return openal_error("alcCaptureOpenDevice1", alGetError());
alcCaptureStart(priv->in_dev);
if ((err = alGetError())) return openal_error("alcCaptureStart", err);
priv->sample_rate = sample_rate;
priv->num_buffers = 20;
priv->input_level = 0;
priv->output_level = 1;
priv->buffers_head = 0;
priv->buffers_tail = 0;
priv->buffers_free = priv->num_buffers;
priv->buffers = (ALuint*)malloc(sizeof(ALuint) * priv->num_buffers);
alGenBuffers(priv->num_buffers, priv->buffers);
if ((err = alGetError())) return openal_error("alGenBuffers", err);
alGenSources(1, &priv->source);
if ((err = alGetError())) return openal_error("alGenSources", err);
d->initialize = openal_initialize;
d->destroy = openal_destroy;
d->select_devices = openal_select_devices;
d->selected_devices = openal_selected_devices;
d->start = openal_start;
d->stop = openal_stop;
d->output = openal_output;
d->input = openal_input;
d->input_level_get = openal_input_level_get;
d->input_level_set = openal_input_level_set;
d->output_level_get = openal_output_level_get;
d->output_level_set = openal_output_level_set;
d->mic_boost_get = openal_mic_boost_get;
d->mic_boost_set = openal_mic_boost_set;
d->play_sound = openal_play_sound;
d->stop_sound = openal_stop_sound;
d->nDevices = 1;
d->devices = &device;
return 0;
}