Limit convolution processing to the output ambisonic order
[openal-soft.git] / alc / backends / portaudio.cpp
blob42dbb4c916e40005cd5a372f9f7de1b1ad483bce
1 /**
2 * OpenAL cross platform audio library
3 * Copyright (C) 1999-2007 by authors.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 * Or go to http://www.gnu.org/copyleft/lgpl.html
21 #include "config.h"
23 #include "backends/portaudio.h"
25 #include <cstdio>
26 #include <cstdlib>
27 #include <cstring>
29 #include "alcmain.h"
30 #include "alexcpt.h"
31 #include "alu.h"
32 #include "alconfig.h"
33 #include "dynload.h"
34 #include "ringbuffer.h"
35 #include "logging.h"
37 #include <portaudio.h>
40 namespace {
42 constexpr ALCchar pa_device[] = "PortAudio Default";
45 #ifdef HAVE_DYNLOAD
46 void *pa_handle;
47 #define MAKE_FUNC(x) decltype(x) * p##x
48 MAKE_FUNC(Pa_Initialize);
49 MAKE_FUNC(Pa_Terminate);
50 MAKE_FUNC(Pa_GetErrorText);
51 MAKE_FUNC(Pa_StartStream);
52 MAKE_FUNC(Pa_StopStream);
53 MAKE_FUNC(Pa_OpenStream);
54 MAKE_FUNC(Pa_CloseStream);
55 MAKE_FUNC(Pa_GetDefaultOutputDevice);
56 MAKE_FUNC(Pa_GetDefaultInputDevice);
57 MAKE_FUNC(Pa_GetStreamInfo);
58 #undef MAKE_FUNC
60 #ifndef IN_IDE_PARSER
61 #define Pa_Initialize pPa_Initialize
62 #define Pa_Terminate pPa_Terminate
63 #define Pa_GetErrorText pPa_GetErrorText
64 #define Pa_StartStream pPa_StartStream
65 #define Pa_StopStream pPa_StopStream
66 #define Pa_OpenStream pPa_OpenStream
67 #define Pa_CloseStream pPa_CloseStream
68 #define Pa_GetDefaultOutputDevice pPa_GetDefaultOutputDevice
69 #define Pa_GetDefaultInputDevice pPa_GetDefaultInputDevice
70 #define Pa_GetStreamInfo pPa_GetStreamInfo
71 #endif
72 #endif
75 struct PortPlayback final : public BackendBase {
76 PortPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
77 ~PortPlayback() override;
79 int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
80 const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept;
81 static int writeCallbackC(const void *inputBuffer, void *outputBuffer,
82 unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
83 const PaStreamCallbackFlags statusFlags, void *userData) noexcept
85 return static_cast<PortPlayback*>(userData)->writeCallback(inputBuffer, outputBuffer,
86 framesPerBuffer, timeInfo, statusFlags);
89 void open(const ALCchar *name) override;
90 bool reset() override;
91 void start() override;
92 void stop() override;
94 PaStream *mStream{nullptr};
95 PaStreamParameters mParams{};
96 ALuint mUpdateSize{0u};
98 DEF_NEWDEL(PortPlayback)
101 PortPlayback::~PortPlayback()
103 PaError err{mStream ? Pa_CloseStream(mStream) : paNoError};
104 if(err != paNoError)
105 ERR("Error closing stream: %s\n", Pa_GetErrorText(err));
106 mStream = nullptr;
110 int PortPlayback::writeCallback(const void*, void *outputBuffer, unsigned long framesPerBuffer,
111 const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept
113 mDevice->renderSamples(outputBuffer, static_cast<ALuint>(framesPerBuffer),
114 static_cast<ALuint>(mParams.channelCount));
115 return 0;
119 void PortPlayback::open(const ALCchar *name)
121 if(!name)
122 name = pa_device;
123 else if(strcmp(name, pa_device) != 0)
124 throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
126 mUpdateSize = mDevice->UpdateSize;
128 auto devidopt = ConfigValueInt(nullptr, "port", "device");
129 if(devidopt && *devidopt >= 0) mParams.device = *devidopt;
130 else mParams.device = Pa_GetDefaultOutputDevice();
131 mParams.suggestedLatency = mDevice->BufferSize / static_cast<double>(mDevice->Frequency);
132 mParams.hostApiSpecificStreamInfo = nullptr;
134 mParams.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
136 switch(mDevice->FmtType)
138 case DevFmtByte:
139 mParams.sampleFormat = paInt8;
140 break;
141 case DevFmtUByte:
142 mParams.sampleFormat = paUInt8;
143 break;
144 case DevFmtUShort:
145 /* fall-through */
146 case DevFmtShort:
147 mParams.sampleFormat = paInt16;
148 break;
149 case DevFmtUInt:
150 /* fall-through */
151 case DevFmtInt:
152 mParams.sampleFormat = paInt32;
153 break;
154 case DevFmtFloat:
155 mParams.sampleFormat = paFloat32;
156 break;
159 retry_open:
160 PaError err{Pa_OpenStream(&mStream, nullptr, &mParams, mDevice->Frequency, mDevice->UpdateSize,
161 paNoFlag, &PortPlayback::writeCallbackC, this)};
162 if(err != paNoError)
164 if(mParams.sampleFormat == paFloat32)
166 mParams.sampleFormat = paInt16;
167 goto retry_open;
169 throw al::backend_exception{ALC_INVALID_VALUE, "Failed to open stream: %s",
170 Pa_GetErrorText(err)};
173 mDevice->DeviceName = name;
176 bool PortPlayback::reset()
178 const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)};
179 mDevice->Frequency = static_cast<ALuint>(streamInfo->sampleRate);
180 mDevice->UpdateSize = mUpdateSize;
182 if(mParams.sampleFormat == paInt8)
183 mDevice->FmtType = DevFmtByte;
184 else if(mParams.sampleFormat == paUInt8)
185 mDevice->FmtType = DevFmtUByte;
186 else if(mParams.sampleFormat == paInt16)
187 mDevice->FmtType = DevFmtShort;
188 else if(mParams.sampleFormat == paInt32)
189 mDevice->FmtType = DevFmtInt;
190 else if(mParams.sampleFormat == paFloat32)
191 mDevice->FmtType = DevFmtFloat;
192 else
194 ERR("Unexpected sample format: 0x%lx\n", mParams.sampleFormat);
195 return false;
198 if(mParams.channelCount == 2)
199 mDevice->FmtChans = DevFmtStereo;
200 else if(mParams.channelCount == 1)
201 mDevice->FmtChans = DevFmtMono;
202 else
204 ERR("Unexpected channel count: %u\n", mParams.channelCount);
205 return false;
207 setDefaultChannelOrder();
209 return true;
212 void PortPlayback::start()
214 const PaError err{Pa_StartStream(mStream)};
215 if(err == paNoError)
216 throw al::backend_exception{ALC_INVALID_DEVICE, "Failed to start playback: %s",
217 Pa_GetErrorText(err)};
220 void PortPlayback::stop()
222 PaError err{Pa_StopStream(mStream)};
223 if(err != paNoError)
224 ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
228 struct PortCapture final : public BackendBase {
229 PortCapture(ALCdevice *device) noexcept : BackendBase{device} { }
230 ~PortCapture() override;
232 int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
233 const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept;
234 static int readCallbackC(const void *inputBuffer, void *outputBuffer,
235 unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
236 const PaStreamCallbackFlags statusFlags, void *userData) noexcept
238 return static_cast<PortCapture*>(userData)->readCallback(inputBuffer, outputBuffer,
239 framesPerBuffer, timeInfo, statusFlags);
242 void open(const ALCchar *name) override;
243 void start() override;
244 void stop() override;
245 ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
246 ALCuint availableSamples() override;
248 PaStream *mStream{nullptr};
249 PaStreamParameters mParams;
251 RingBufferPtr mRing{nullptr};
253 DEF_NEWDEL(PortCapture)
256 PortCapture::~PortCapture()
258 PaError err{mStream ? Pa_CloseStream(mStream) : paNoError};
259 if(err != paNoError)
260 ERR("Error closing stream: %s\n", Pa_GetErrorText(err));
261 mStream = nullptr;
265 int PortCapture::readCallback(const void *inputBuffer, void*, unsigned long framesPerBuffer,
266 const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept
268 mRing->write(inputBuffer, framesPerBuffer);
269 return 0;
273 void PortCapture::open(const ALCchar *name)
275 if(!name)
276 name = pa_device;
277 else if(strcmp(name, pa_device) != 0)
278 throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
280 ALuint samples{mDevice->BufferSize};
281 samples = maxu(samples, 100 * mDevice->Frequency / 1000);
282 ALuint frame_size{mDevice->frameSizeFromFmt()};
284 mRing = RingBuffer::Create(samples, frame_size, false);
286 auto devidopt = ConfigValueInt(nullptr, "port", "capture");
287 if(devidopt && *devidopt >= 0) mParams.device = *devidopt;
288 else mParams.device = Pa_GetDefaultOutputDevice();
289 mParams.suggestedLatency = 0.0f;
290 mParams.hostApiSpecificStreamInfo = nullptr;
292 switch(mDevice->FmtType)
294 case DevFmtByte:
295 mParams.sampleFormat = paInt8;
296 break;
297 case DevFmtUByte:
298 mParams.sampleFormat = paUInt8;
299 break;
300 case DevFmtShort:
301 mParams.sampleFormat = paInt16;
302 break;
303 case DevFmtInt:
304 mParams.sampleFormat = paInt32;
305 break;
306 case DevFmtFloat:
307 mParams.sampleFormat = paFloat32;
308 break;
309 case DevFmtUInt:
310 case DevFmtUShort:
311 throw al::backend_exception{ALC_INVALID_VALUE, "%s samples not supported",
312 DevFmtTypeString(mDevice->FmtType)};
314 mParams.channelCount = static_cast<int>(mDevice->channelsFromFmt());
316 PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->Frequency,
317 paFramesPerBufferUnspecified, paNoFlag, &PortCapture::readCallbackC, this)};
318 if(err != paNoError)
319 throw al::backend_exception{ALC_INVALID_VALUE, "Failed to open stream: %s",
320 Pa_GetErrorText(err)};
322 mDevice->DeviceName = name;
326 void PortCapture::start()
328 const PaError err{Pa_StartStream(mStream)};
329 if(err != paNoError)
330 throw al::backend_exception{ALC_INVALID_DEVICE, "Failed to start recording: %s",
331 Pa_GetErrorText(err)};
334 void PortCapture::stop()
336 PaError err{Pa_StopStream(mStream)};
337 if(err != paNoError)
338 ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
342 ALCuint PortCapture::availableSamples()
343 { return static_cast<ALCuint>(mRing->readSpace()); }
345 ALCenum PortCapture::captureSamples(al::byte *buffer, ALCuint samples)
347 mRing->read(buffer, samples);
348 return ALC_NO_ERROR;
351 } // namespace
354 bool PortBackendFactory::init()
356 PaError err;
358 #ifdef HAVE_DYNLOAD
359 if(!pa_handle)
361 #ifdef _WIN32
362 # define PALIB "portaudio.dll"
363 #elif defined(__APPLE__) && defined(__MACH__)
364 # define PALIB "libportaudio.2.dylib"
365 #elif defined(__OpenBSD__)
366 # define PALIB "libportaudio.so"
367 #else
368 # define PALIB "libportaudio.so.2"
369 #endif
371 pa_handle = LoadLib(PALIB);
372 if(!pa_handle)
373 return false;
375 #define LOAD_FUNC(f) do { \
376 p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(pa_handle, #f)); \
377 if(p##f == nullptr) \
379 CloseLib(pa_handle); \
380 pa_handle = nullptr; \
381 return false; \
383 } while(0)
384 LOAD_FUNC(Pa_Initialize);
385 LOAD_FUNC(Pa_Terminate);
386 LOAD_FUNC(Pa_GetErrorText);
387 LOAD_FUNC(Pa_StartStream);
388 LOAD_FUNC(Pa_StopStream);
389 LOAD_FUNC(Pa_OpenStream);
390 LOAD_FUNC(Pa_CloseStream);
391 LOAD_FUNC(Pa_GetDefaultOutputDevice);
392 LOAD_FUNC(Pa_GetDefaultInputDevice);
393 LOAD_FUNC(Pa_GetStreamInfo);
394 #undef LOAD_FUNC
396 if((err=Pa_Initialize()) != paNoError)
398 ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
399 CloseLib(pa_handle);
400 pa_handle = nullptr;
401 return false;
404 #else
405 if((err=Pa_Initialize()) != paNoError)
407 ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
408 return false;
410 #endif
411 return true;
414 bool PortBackendFactory::querySupport(BackendType type)
415 { return (type == BackendType::Playback || type == BackendType::Capture); }
417 std::string PortBackendFactory::probe(BackendType type)
419 std::string outnames;
420 switch(type)
422 case BackendType::Playback:
423 case BackendType::Capture:
424 /* Includes null char. */
425 outnames.append(pa_device, sizeof(pa_device));
426 break;
428 return outnames;
431 BackendPtr PortBackendFactory::createBackend(ALCdevice *device, BackendType type)
433 if(type == BackendType::Playback)
434 return BackendPtr{new PortPlayback{device}};
435 if(type == BackendType::Capture)
436 return BackendPtr{new PortCapture{device}};
437 return nullptr;
440 BackendFactory &PortBackendFactory::getFactory()
442 static PortBackendFactory factory{};
443 return factory;