aboutsummaryrefslogtreecommitdiffstats
path: root/Alc/backends/portaudio.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Alc/backends/portaudio.cpp')
-rw-r--r--Alc/backends/portaudio.cpp559
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);
+}