diff options
Diffstat (limited to 'Alc/backends/portaudio.cpp')
-rw-r--r-- | Alc/backends/portaudio.cpp | 559 |
1 files changed, 559 insertions, 0 deletions
diff --git a/Alc/backends/portaudio.cpp b/Alc/backends/portaudio.cpp new file mode 100644 index 00000000..e1b1c265 --- /dev/null +++ b/Alc/backends/portaudio.cpp @@ -0,0 +1,559 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2007 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "alMain.h" +#include "alu.h" +#include "alconfig.h" +#include "ringbuffer.h" +#include "compat.h" + +#include "backends/base.h" + +#include <portaudio.h> + + +static const ALCchar pa_device[] = "PortAudio Default"; + + +#ifdef HAVE_DYNLOAD +static void *pa_handle; +#define MAKE_FUNC(x) static __typeof(x) * p##x +MAKE_FUNC(Pa_Initialize); +MAKE_FUNC(Pa_Terminate); +MAKE_FUNC(Pa_GetErrorText); +MAKE_FUNC(Pa_StartStream); +MAKE_FUNC(Pa_StopStream); +MAKE_FUNC(Pa_OpenStream); +MAKE_FUNC(Pa_CloseStream); +MAKE_FUNC(Pa_GetDefaultOutputDevice); +MAKE_FUNC(Pa_GetDefaultInputDevice); +MAKE_FUNC(Pa_GetStreamInfo); +#undef MAKE_FUNC + +#ifndef IN_IDE_PARSER +#define Pa_Initialize pPa_Initialize +#define Pa_Terminate pPa_Terminate +#define Pa_GetErrorText pPa_GetErrorText +#define Pa_StartStream pPa_StartStream +#define Pa_StopStream pPa_StopStream +#define Pa_OpenStream pPa_OpenStream +#define Pa_CloseStream pPa_CloseStream +#define Pa_GetDefaultOutputDevice pPa_GetDefaultOutputDevice +#define Pa_GetDefaultInputDevice pPa_GetDefaultInputDevice +#define Pa_GetStreamInfo pPa_GetStreamInfo +#endif +#endif + +static ALCboolean pa_load(void) +{ + PaError err; + +#ifdef HAVE_DYNLOAD + if(!pa_handle) + { +#ifdef _WIN32 +# define PALIB "portaudio.dll" +#elif defined(__APPLE__) && defined(__MACH__) +# define PALIB "libportaudio.2.dylib" +#elif defined(__OpenBSD__) +# define PALIB "libportaudio.so" +#else +# define PALIB "libportaudio.so.2" +#endif + + pa_handle = LoadLib(PALIB); + if(!pa_handle) + return ALC_FALSE; + +#define LOAD_FUNC(f) do { \ + p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(pa_handle, #f)); \ + if(p##f == nullptr) \ + { \ + CloseLib(pa_handle); \ + pa_handle = nullptr; \ + return ALC_FALSE; \ + } \ +} while(0) + LOAD_FUNC(Pa_Initialize); + LOAD_FUNC(Pa_Terminate); + LOAD_FUNC(Pa_GetErrorText); + LOAD_FUNC(Pa_StartStream); + LOAD_FUNC(Pa_StopStream); + LOAD_FUNC(Pa_OpenStream); + LOAD_FUNC(Pa_CloseStream); + LOAD_FUNC(Pa_GetDefaultOutputDevice); + LOAD_FUNC(Pa_GetDefaultInputDevice); + LOAD_FUNC(Pa_GetStreamInfo); +#undef LOAD_FUNC + + if((err=Pa_Initialize()) != paNoError) + { + ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err)); + CloseLib(pa_handle); + pa_handle = nullptr; + return ALC_FALSE; + } + } +#else + if((err=Pa_Initialize()) != paNoError) + { + ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err)); + return ALC_FALSE; + } +#endif + return ALC_TRUE; +} + + +struct ALCportPlayback final : public ALCbackend { + PaStream *stream; + PaStreamParameters params; + ALuint update_size; +}; + +static int ALCportPlayback_WriteCallback(const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, + const PaStreamCallbackFlags statusFlags, void *userData); + +static void ALCportPlayback_Construct(ALCportPlayback *self, ALCdevice *device); +static void ALCportPlayback_Destruct(ALCportPlayback *self); +static ALCenum ALCportPlayback_open(ALCportPlayback *self, const ALCchar *name); +static ALCboolean ALCportPlayback_reset(ALCportPlayback *self); +static ALCboolean ALCportPlayback_start(ALCportPlayback *self); +static void ALCportPlayback_stop(ALCportPlayback *self); +static DECLARE_FORWARD2(ALCportPlayback, ALCbackend, ALCenum, captureSamples, ALCvoid*, ALCuint) +static DECLARE_FORWARD(ALCportPlayback, ALCbackend, ALCuint, availableSamples) +static DECLARE_FORWARD(ALCportPlayback, ALCbackend, ClockLatency, getClockLatency) +static DECLARE_FORWARD(ALCportPlayback, ALCbackend, void, lock) +static DECLARE_FORWARD(ALCportPlayback, ALCbackend, void, unlock) +DECLARE_DEFAULT_ALLOCATORS(ALCportPlayback) + +DEFINE_ALCBACKEND_VTABLE(ALCportPlayback); + + +static void ALCportPlayback_Construct(ALCportPlayback *self, ALCdevice *device) +{ + new (self) ALCportPlayback{}; + ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device); + SET_VTABLE2(ALCportPlayback, ALCbackend, self); + + self->stream = nullptr; +} + +static void ALCportPlayback_Destruct(ALCportPlayback *self) +{ + PaError err = self->stream ? Pa_CloseStream(self->stream) : paNoError; + if(err != paNoError) + ERR("Error closing stream: %s\n", Pa_GetErrorText(err)); + self->stream = nullptr; + + ALCbackend_Destruct(STATIC_CAST(ALCbackend, self)); + self->~ALCportPlayback(); +} + + +static int ALCportPlayback_WriteCallback(const void *UNUSED(inputBuffer), void *outputBuffer, + unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *UNUSED(timeInfo), + const PaStreamCallbackFlags UNUSED(statusFlags), void *userData) +{ + ALCportPlayback *self = static_cast<ALCportPlayback*>(userData); + + ALCportPlayback_lock(self); + aluMixData(STATIC_CAST(ALCbackend, self)->mDevice, outputBuffer, framesPerBuffer); + ALCportPlayback_unlock(self); + return 0; +} + + +static ALCenum ALCportPlayback_open(ALCportPlayback *self, const ALCchar *name) +{ + ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice; + PaError err; + + if(!name) + name = pa_device; + else if(strcmp(name, pa_device) != 0) + return ALC_INVALID_VALUE; + + self->update_size = device->UpdateSize; + + self->params.device = -1; + if(!ConfigValueInt(nullptr, "port", "device", &self->params.device) || + self->params.device < 0) + self->params.device = Pa_GetDefaultOutputDevice(); + self->params.suggestedLatency = (device->UpdateSize*device->NumUpdates) / + (float)device->Frequency; + self->params.hostApiSpecificStreamInfo = nullptr; + + self->params.channelCount = ((device->FmtChans == DevFmtMono) ? 1 : 2); + + switch(device->FmtType) + { + case DevFmtByte: + self->params.sampleFormat = paInt8; + break; + case DevFmtUByte: + self->params.sampleFormat = paUInt8; + break; + case DevFmtUShort: + /* fall-through */ + case DevFmtShort: + self->params.sampleFormat = paInt16; + break; + case DevFmtUInt: + /* fall-through */ + case DevFmtInt: + self->params.sampleFormat = paInt32; + break; + case DevFmtFloat: + self->params.sampleFormat = paFloat32; + break; + } + +retry_open: + err = Pa_OpenStream(&self->stream, nullptr, &self->params, + device->Frequency, device->UpdateSize, paNoFlag, + ALCportPlayback_WriteCallback, self + ); + if(err != paNoError) + { + if(self->params.sampleFormat == paFloat32) + { + self->params.sampleFormat = paInt16; + goto retry_open; + } + ERR("Pa_OpenStream() returned an error: %s\n", Pa_GetErrorText(err)); + return ALC_INVALID_VALUE; + } + + alstr_copy_cstr(&device->DeviceName, name); + + return ALC_NO_ERROR; + +} + +static ALCboolean ALCportPlayback_reset(ALCportPlayback *self) +{ + ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice; + const PaStreamInfo *streamInfo; + + streamInfo = Pa_GetStreamInfo(self->stream); + device->Frequency = streamInfo->sampleRate; + device->UpdateSize = self->update_size; + + if(self->params.sampleFormat == paInt8) + device->FmtType = DevFmtByte; + else if(self->params.sampleFormat == paUInt8) + device->FmtType = DevFmtUByte; + else if(self->params.sampleFormat == paInt16) + device->FmtType = DevFmtShort; + else if(self->params.sampleFormat == paInt32) + device->FmtType = DevFmtInt; + else if(self->params.sampleFormat == paFloat32) + device->FmtType = DevFmtFloat; + else + { + ERR("Unexpected sample format: 0x%lx\n", self->params.sampleFormat); + return ALC_FALSE; + } + + if(self->params.channelCount == 2) + device->FmtChans = DevFmtStereo; + else if(self->params.channelCount == 1) + device->FmtChans = DevFmtMono; + else + { + ERR("Unexpected channel count: %u\n", self->params.channelCount); + return ALC_FALSE; + } + SetDefaultChannelOrder(device); + + return ALC_TRUE; +} + +static ALCboolean ALCportPlayback_start(ALCportPlayback *self) +{ + PaError err; + + err = Pa_StartStream(self->stream); + if(err != paNoError) + { + ERR("Pa_StartStream() returned an error: %s\n", Pa_GetErrorText(err)); + return ALC_FALSE; + } + + return ALC_TRUE; +} + +static void ALCportPlayback_stop(ALCportPlayback *self) +{ + PaError err = Pa_StopStream(self->stream); + if(err != paNoError) + ERR("Error stopping stream: %s\n", Pa_GetErrorText(err)); +} + + +struct ALCportCapture final : public ALCbackend { + PaStream *stream; + PaStreamParameters params; + + ll_ringbuffer_t *ring; +}; + +static int ALCportCapture_ReadCallback(const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, + const PaStreamCallbackFlags statusFlags, void *userData); + +static void ALCportCapture_Construct(ALCportCapture *self, ALCdevice *device); +static void ALCportCapture_Destruct(ALCportCapture *self); +static ALCenum ALCportCapture_open(ALCportCapture *self, const ALCchar *name); +static DECLARE_FORWARD(ALCportCapture, ALCbackend, ALCboolean, reset) +static ALCboolean ALCportCapture_start(ALCportCapture *self); +static void ALCportCapture_stop(ALCportCapture *self); +static ALCenum ALCportCapture_captureSamples(ALCportCapture *self, ALCvoid *buffer, ALCuint samples); +static ALCuint ALCportCapture_availableSamples(ALCportCapture *self); +static DECLARE_FORWARD(ALCportCapture, ALCbackend, ClockLatency, getClockLatency) +static DECLARE_FORWARD(ALCportCapture, ALCbackend, void, lock) +static DECLARE_FORWARD(ALCportCapture, ALCbackend, void, unlock) +DECLARE_DEFAULT_ALLOCATORS(ALCportCapture) + +DEFINE_ALCBACKEND_VTABLE(ALCportCapture); + + +static void ALCportCapture_Construct(ALCportCapture *self, ALCdevice *device) +{ + new (self) ALCportCapture{}; + ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device); + SET_VTABLE2(ALCportCapture, ALCbackend, self); + + self->stream = nullptr; + self->ring = nullptr; +} + +static void ALCportCapture_Destruct(ALCportCapture *self) +{ + PaError err = self->stream ? Pa_CloseStream(self->stream) : paNoError; + if(err != paNoError) + ERR("Error closing stream: %s\n", Pa_GetErrorText(err)); + self->stream = nullptr; + + ll_ringbuffer_free(self->ring); + self->ring = nullptr; + + ALCbackend_Destruct(STATIC_CAST(ALCbackend, self)); + self->~ALCportCapture(); +} + + +static int ALCportCapture_ReadCallback(const void *inputBuffer, void *UNUSED(outputBuffer), + unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *UNUSED(timeInfo), + const PaStreamCallbackFlags UNUSED(statusFlags), void *userData) +{ + ALCportCapture *self = static_cast<ALCportCapture*>(userData); + size_t writable = ll_ringbuffer_write_space(self->ring); + + if(framesPerBuffer > writable) framesPerBuffer = writable; + ll_ringbuffer_write(self->ring, static_cast<const char*>(inputBuffer), framesPerBuffer); + return 0; +} + + +static ALCenum ALCportCapture_open(ALCportCapture *self, const ALCchar *name) +{ + ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice; + ALuint samples, frame_size; + PaError err; + + if(!name) + name = pa_device; + else if(strcmp(name, pa_device) != 0) + return ALC_INVALID_VALUE; + + samples = device->UpdateSize * device->NumUpdates; + samples = maxu(samples, 100 * device->Frequency / 1000); + frame_size = FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder); + + self->ring = ll_ringbuffer_create(samples, frame_size, false); + if(self->ring == nullptr) return ALC_INVALID_VALUE; + + self->params.device = -1; + if(!ConfigValueInt(nullptr, "port", "capture", &self->params.device) || + self->params.device < 0) + self->params.device = Pa_GetDefaultInputDevice(); + self->params.suggestedLatency = 0.0f; + self->params.hostApiSpecificStreamInfo = nullptr; + + switch(device->FmtType) + { + case DevFmtByte: + self->params.sampleFormat = paInt8; + break; + case DevFmtUByte: + self->params.sampleFormat = paUInt8; + break; + case DevFmtShort: + self->params.sampleFormat = paInt16; + break; + case DevFmtInt: + self->params.sampleFormat = paInt32; + break; + case DevFmtFloat: + self->params.sampleFormat = paFloat32; + break; + case DevFmtUInt: + case DevFmtUShort: + ERR("%s samples not supported\n", DevFmtTypeString(device->FmtType)); + return ALC_INVALID_VALUE; + } + self->params.channelCount = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder); + + err = Pa_OpenStream(&self->stream, &self->params, nullptr, + device->Frequency, paFramesPerBufferUnspecified, paNoFlag, + ALCportCapture_ReadCallback, self + ); + if(err != paNoError) + { + ERR("Pa_OpenStream() returned an error: %s\n", Pa_GetErrorText(err)); + return ALC_INVALID_VALUE; + } + + alstr_copy_cstr(&device->DeviceName, name); + + return ALC_NO_ERROR; +} + + +static ALCboolean ALCportCapture_start(ALCportCapture *self) +{ + PaError err = Pa_StartStream(self->stream); + if(err != paNoError) + { + ERR("Error starting stream: %s\n", Pa_GetErrorText(err)); + return ALC_FALSE; + } + return ALC_TRUE; +} + +static void ALCportCapture_stop(ALCportCapture *self) +{ + PaError err = Pa_StopStream(self->stream); + if(err != paNoError) + ERR("Error stopping stream: %s\n", Pa_GetErrorText(err)); +} + + +static ALCuint ALCportCapture_availableSamples(ALCportCapture *self) +{ + return ll_ringbuffer_read_space(self->ring); +} + +static ALCenum ALCportCapture_captureSamples(ALCportCapture *self, ALCvoid *buffer, ALCuint samples) +{ + ll_ringbuffer_read(self->ring, static_cast<char*>(buffer), samples); + return ALC_NO_ERROR; +} + + +struct ALCportBackendFactory final : public ALCbackendFactory { + ALCportBackendFactory() noexcept; +}; + +static ALCboolean ALCportBackendFactory_init(ALCportBackendFactory *self); +static void ALCportBackendFactory_deinit(ALCportBackendFactory *self); +static ALCboolean ALCportBackendFactory_querySupport(ALCportBackendFactory *self, ALCbackend_Type type); +static void ALCportBackendFactory_probe(ALCportBackendFactory *self, enum DevProbe type, al_string *outnames); +static ALCbackend* ALCportBackendFactory_createBackend(ALCportBackendFactory *self, ALCdevice *device, ALCbackend_Type type); +DEFINE_ALCBACKENDFACTORY_VTABLE(ALCportBackendFactory); + + +ALCportBackendFactory::ALCportBackendFactory() noexcept + : ALCbackendFactory{GET_VTABLE2(ALCportBackendFactory, ALCbackendFactory)} +{ } + +static ALCboolean ALCportBackendFactory_init(ALCportBackendFactory* UNUSED(self)) +{ + if(!pa_load()) + return ALC_FALSE; + return ALC_TRUE; +} + +static void ALCportBackendFactory_deinit(ALCportBackendFactory* UNUSED(self)) +{ +#ifdef HAVE_DYNLOAD + if(pa_handle) + { + Pa_Terminate(); + CloseLib(pa_handle); + pa_handle = nullptr; + } +#else + Pa_Terminate(); +#endif +} + +static ALCboolean ALCportBackendFactory_querySupport(ALCportBackendFactory* UNUSED(self), ALCbackend_Type type) +{ + if(type == ALCbackend_Playback || type == ALCbackend_Capture) + return ALC_TRUE; + return ALC_FALSE; +} + +static void ALCportBackendFactory_probe(ALCportBackendFactory* UNUSED(self), enum DevProbe type, al_string *outnames) +{ + switch(type) + { + case ALL_DEVICE_PROBE: + case CAPTURE_DEVICE_PROBE: + alstr_append_range(outnames, pa_device, pa_device+sizeof(pa_device)); + break; + } +} + +static ALCbackend* ALCportBackendFactory_createBackend(ALCportBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type) +{ + if(type == ALCbackend_Playback) + { + ALCportPlayback *backend; + NEW_OBJ(backend, ALCportPlayback)(device); + if(!backend) return nullptr; + return STATIC_CAST(ALCbackend, backend); + } + if(type == ALCbackend_Capture) + { + ALCportCapture *backend; + NEW_OBJ(backend, ALCportCapture)(device); + if(!backend) return nullptr; + return STATIC_CAST(ALCbackend, backend); + } + + return nullptr; +} + +ALCbackendFactory *ALCportBackendFactory_getFactory(void) +{ + static ALCportBackendFactory factory{}; + return STATIC_CAST(ALCbackendFactory, &factory); +} |