From d9a47ab63ceb6947a2ac31ae0c73fbfb7ab845cc Mon Sep 17 00:00:00 2001
From: Chris Robinson <chris.kcat@gmail.com>
Date: Mon, 12 Nov 2018 23:32:11 -0800
Subject: Convert the PortAudio backend to C++

---
 Alc/backends/portaudio.cpp | 559 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 559 insertions(+)
 create mode 100644 Alc/backends/portaudio.cpp

(limited to 'Alc/backends/portaudio.cpp')

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);
+}
-- 
cgit v1.2.3