aboutsummaryrefslogtreecommitdiffstats
path: root/alc/backends/portaudio.cpp
diff options
context:
space:
mode:
authorChris Robinson <[email protected]>2019-07-28 18:56:04 -0700
committerChris Robinson <[email protected]>2019-07-28 18:56:04 -0700
commitcb3e96e75640730b9391f0d2d922eecd9ee2ce79 (patch)
tree23520551bddb2a80354e44da47f54201fdc084f0 /alc/backends/portaudio.cpp
parent93e60919c8f387c36c267ca9faa1ac653254aea6 (diff)
Rename Alc to alc
Diffstat (limited to 'alc/backends/portaudio.cpp')
-rw-r--r--alc/backends/portaudio.cpp463
1 files changed, 463 insertions, 0 deletions
diff --git a/alc/backends/portaudio.cpp b/alc/backends/portaudio.cpp
new file mode 100644
index 00000000..73e972c5
--- /dev/null
+++ b/alc/backends/portaudio.cpp
@@ -0,0 +1,463 @@
+/**
+ * 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 "backends/portaudio.h"
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+#include "alcmain.h"
+#include "alu.h"
+#include "alconfig.h"
+#include "ringbuffer.h"
+#include "compat.h"
+
+#include <portaudio.h>
+
+
+namespace {
+
+constexpr ALCchar pa_device[] = "PortAudio Default";
+
+
+#ifdef HAVE_DYNLOAD
+void *pa_handle;
+#define MAKE_FUNC(x) decltype(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
+
+
+struct PortPlayback final : public BackendBase {
+ PortPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+ ~PortPlayback() override;
+
+ static int writeCallbackC(const void *inputBuffer, void *outputBuffer,
+ unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
+ const PaStreamCallbackFlags statusFlags, void *userData);
+ int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
+ const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags);
+
+ ALCenum open(const ALCchar *name) override;
+ ALCboolean reset() override;
+ ALCboolean start() override;
+ void stop() override;
+
+ PaStream *mStream{nullptr};
+ PaStreamParameters mParams{};
+ ALuint mUpdateSize{0u};
+
+ DEF_NEWDEL(PortPlayback)
+};
+
+PortPlayback::~PortPlayback()
+{
+ PaError err{mStream ? Pa_CloseStream(mStream) : paNoError};
+ if(err != paNoError)
+ ERR("Error closing stream: %s\n", Pa_GetErrorText(err));
+ mStream = nullptr;
+}
+
+
+int PortPlayback::writeCallbackC(const void *inputBuffer, void *outputBuffer,
+ unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
+ const PaStreamCallbackFlags statusFlags, void *userData)
+{
+ return static_cast<PortPlayback*>(userData)->writeCallback(inputBuffer, outputBuffer,
+ framesPerBuffer, timeInfo, statusFlags);
+}
+
+int PortPlayback::writeCallback(const void*, void *outputBuffer,
+ unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo*,
+ const PaStreamCallbackFlags)
+{
+ lock();
+ aluMixData(mDevice, outputBuffer, framesPerBuffer);
+ unlock();
+ return 0;
+}
+
+
+ALCenum PortPlayback::open(const ALCchar *name)
+{
+ if(!name)
+ name = pa_device;
+ else if(strcmp(name, pa_device) != 0)
+ return ALC_INVALID_VALUE;
+
+ mUpdateSize = mDevice->UpdateSize;
+
+ auto devidopt = ConfigValueInt(nullptr, "port", "device");
+ if(devidopt && *devidopt >= 0) mParams.device = *devidopt;
+ else mParams.device = Pa_GetDefaultOutputDevice();
+ mParams.suggestedLatency = mDevice->BufferSize / static_cast<double>(mDevice->Frequency);
+ mParams.hostApiSpecificStreamInfo = nullptr;
+
+ mParams.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
+
+ switch(mDevice->FmtType)
+ {
+ case DevFmtByte:
+ mParams.sampleFormat = paInt8;
+ break;
+ case DevFmtUByte:
+ mParams.sampleFormat = paUInt8;
+ break;
+ case DevFmtUShort:
+ /* fall-through */
+ case DevFmtShort:
+ mParams.sampleFormat = paInt16;
+ break;
+ case DevFmtUInt:
+ /* fall-through */
+ case DevFmtInt:
+ mParams.sampleFormat = paInt32;
+ break;
+ case DevFmtFloat:
+ mParams.sampleFormat = paFloat32;
+ break;
+ }
+
+retry_open:
+ PaError err{Pa_OpenStream(&mStream, nullptr, &mParams, mDevice->Frequency, mDevice->UpdateSize,
+ paNoFlag, &PortPlayback::writeCallbackC, this)};
+ if(err != paNoError)
+ {
+ if(mParams.sampleFormat == paFloat32)
+ {
+ mParams.sampleFormat = paInt16;
+ goto retry_open;
+ }
+ ERR("Pa_OpenStream() returned an error: %s\n", Pa_GetErrorText(err));
+ return ALC_INVALID_VALUE;
+ }
+
+ mDevice->DeviceName = name;
+ return ALC_NO_ERROR;
+
+}
+
+ALCboolean PortPlayback::reset()
+{
+ const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)};
+ mDevice->Frequency = streamInfo->sampleRate;
+ mDevice->UpdateSize = mUpdateSize;
+
+ if(mParams.sampleFormat == paInt8)
+ mDevice->FmtType = DevFmtByte;
+ else if(mParams.sampleFormat == paUInt8)
+ mDevice->FmtType = DevFmtUByte;
+ else if(mParams.sampleFormat == paInt16)
+ mDevice->FmtType = DevFmtShort;
+ else if(mParams.sampleFormat == paInt32)
+ mDevice->FmtType = DevFmtInt;
+ else if(mParams.sampleFormat == paFloat32)
+ mDevice->FmtType = DevFmtFloat;
+ else
+ {
+ ERR("Unexpected sample format: 0x%lx\n", mParams.sampleFormat);
+ return ALC_FALSE;
+ }
+
+ if(mParams.channelCount == 2)
+ mDevice->FmtChans = DevFmtStereo;
+ else if(mParams.channelCount == 1)
+ mDevice->FmtChans = DevFmtMono;
+ else
+ {
+ ERR("Unexpected channel count: %u\n", mParams.channelCount);
+ return ALC_FALSE;
+ }
+ SetDefaultChannelOrder(mDevice);
+
+ return ALC_TRUE;
+}
+
+ALCboolean PortPlayback::start()
+{
+ PaError err{Pa_StartStream(mStream)};
+ if(err != paNoError)
+ {
+ ERR("Pa_StartStream() returned an error: %s\n", Pa_GetErrorText(err));
+ return ALC_FALSE;
+ }
+ return ALC_TRUE;
+}
+
+void PortPlayback::stop()
+{
+ PaError err{Pa_StopStream(mStream)};
+ if(err != paNoError)
+ ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
+}
+
+
+struct PortCapture final : public BackendBase {
+ PortCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+ ~PortCapture() override;
+
+ static int readCallbackC(const void *inputBuffer, void *outputBuffer,
+ unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
+ const PaStreamCallbackFlags statusFlags, void *userData);
+ int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
+ const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags);
+
+ ALCenum open(const ALCchar *name) override;
+ ALCboolean start() override;
+ void stop() override;
+ ALCenum captureSamples(ALCvoid *buffer, ALCuint samples) override;
+ ALCuint availableSamples() override;
+
+ PaStream *mStream{nullptr};
+ PaStreamParameters mParams;
+
+ RingBufferPtr mRing{nullptr};
+
+ DEF_NEWDEL(PortCapture)
+};
+
+PortCapture::~PortCapture()
+{
+ PaError err{mStream ? Pa_CloseStream(mStream) : paNoError};
+ if(err != paNoError)
+ ERR("Error closing stream: %s\n", Pa_GetErrorText(err));
+ mStream = nullptr;
+}
+
+
+int PortCapture::readCallbackC(const void *inputBuffer, void *outputBuffer,
+ unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
+ const PaStreamCallbackFlags statusFlags, void* userData)
+{
+ return static_cast<PortCapture*>(userData)->readCallback(inputBuffer, outputBuffer,
+ framesPerBuffer, timeInfo, statusFlags);
+}
+
+int PortCapture::readCallback(const void *inputBuffer, void*,
+ unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo*,
+ const PaStreamCallbackFlags)
+{
+ mRing->write(inputBuffer, framesPerBuffer);
+ return 0;
+}
+
+
+ALCenum PortCapture::open(const ALCchar *name)
+{
+ if(!name)
+ name = pa_device;
+ else if(strcmp(name, pa_device) != 0)
+ return ALC_INVALID_VALUE;
+
+ ALuint samples{mDevice->BufferSize};
+ samples = maxu(samples, 100 * mDevice->Frequency / 1000);
+ ALsizei frame_size{mDevice->frameSizeFromFmt()};
+
+ mRing = CreateRingBuffer(samples, frame_size, false);
+ if(!mRing) return ALC_INVALID_VALUE;
+
+ auto devidopt = ConfigValueInt(nullptr, "port", "capture");
+ if(devidopt && *devidopt >= 0) mParams.device = *devidopt;
+ else mParams.device = Pa_GetDefaultOutputDevice();
+ mParams.suggestedLatency = 0.0f;
+ mParams.hostApiSpecificStreamInfo = nullptr;
+
+ switch(mDevice->FmtType)
+ {
+ case DevFmtByte:
+ mParams.sampleFormat = paInt8;
+ break;
+ case DevFmtUByte:
+ mParams.sampleFormat = paUInt8;
+ break;
+ case DevFmtShort:
+ mParams.sampleFormat = paInt16;
+ break;
+ case DevFmtInt:
+ mParams.sampleFormat = paInt32;
+ break;
+ case DevFmtFloat:
+ mParams.sampleFormat = paFloat32;
+ break;
+ case DevFmtUInt:
+ case DevFmtUShort:
+ ERR("%s samples not supported\n", DevFmtTypeString(mDevice->FmtType));
+ return ALC_INVALID_VALUE;
+ }
+ mParams.channelCount = mDevice->channelsFromFmt();
+
+ PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->Frequency,
+ paFramesPerBufferUnspecified, paNoFlag, &PortCapture::readCallbackC, this)};
+ if(err != paNoError)
+ {
+ ERR("Pa_OpenStream() returned an error: %s\n", Pa_GetErrorText(err));
+ return ALC_INVALID_VALUE;
+ }
+
+ mDevice->DeviceName = name;
+ return ALC_NO_ERROR;
+}
+
+
+ALCboolean PortCapture::start()
+{
+ PaError err{Pa_StartStream(mStream)};
+ if(err != paNoError)
+ {
+ ERR("Error starting stream: %s\n", Pa_GetErrorText(err));
+ return ALC_FALSE;
+ }
+ return ALC_TRUE;
+}
+
+void PortCapture::stop()
+{
+ PaError err{Pa_StopStream(mStream)};
+ if(err != paNoError)
+ ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
+}
+
+
+ALCuint PortCapture::availableSamples()
+{ return mRing->readSpace(); }
+
+ALCenum PortCapture::captureSamples(ALCvoid *buffer, ALCuint samples)
+{
+ mRing->read(buffer, samples);
+ return ALC_NO_ERROR;
+}
+
+} // namespace
+
+
+bool PortBackendFactory::init()
+{
+ 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 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 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 false;
+ }
+ }
+#else
+ if((err=Pa_Initialize()) != paNoError)
+ {
+ ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
+ return false;
+ }
+#endif
+ return true;
+}
+
+bool PortBackendFactory::querySupport(BackendType type)
+{ return (type == BackendType::Playback || type == BackendType::Capture); }
+
+void PortBackendFactory::probe(DevProbe type, std::string *outnames)
+{
+ switch(type)
+ {
+ case DevProbe::Playback:
+ case DevProbe::Capture:
+ /* Includes null char. */
+ outnames->append(pa_device, sizeof(pa_device));
+ break;
+ }
+}
+
+BackendPtr PortBackendFactory::createBackend(ALCdevice *device, BackendType type)
+{
+ if(type == BackendType::Playback)
+ return BackendPtr{new PortPlayback{device}};
+ if(type == BackendType::Capture)
+ return BackendPtr{new PortCapture{device}};
+ return nullptr;
+}
+
+BackendFactory &PortBackendFactory::getFactory()
+{
+ static PortBackendFactory factory{};
+ return factory;
+}