Avoid raw lock/unlock calls
[openal-soft.git] / alc / backends / portaudio.cpp
blobba8e498424a34621b84eadf287ec01f8f20559eb
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"
36 #include <portaudio.h>
39 namespace {
41 constexpr ALCchar pa_device[] = "PortAudio Default";
44 #ifdef HAVE_DYNLOAD
45 void *pa_handle;
46 #define MAKE_FUNC(x) decltype(x) * p##x
47 MAKE_FUNC(Pa_Initialize);
48 MAKE_FUNC(Pa_Terminate);
49 MAKE_FUNC(Pa_GetErrorText);
50 MAKE_FUNC(Pa_StartStream);
51 MAKE_FUNC(Pa_StopStream);
52 MAKE_FUNC(Pa_OpenStream);
53 MAKE_FUNC(Pa_CloseStream);
54 MAKE_FUNC(Pa_GetDefaultOutputDevice);
55 MAKE_FUNC(Pa_GetDefaultInputDevice);
56 MAKE_FUNC(Pa_GetStreamInfo);
57 #undef MAKE_FUNC
59 #ifndef IN_IDE_PARSER
60 #define Pa_Initialize pPa_Initialize
61 #define Pa_Terminate pPa_Terminate
62 #define Pa_GetErrorText pPa_GetErrorText
63 #define Pa_StartStream pPa_StartStream
64 #define Pa_StopStream pPa_StopStream
65 #define Pa_OpenStream pPa_OpenStream
66 #define Pa_CloseStream pPa_CloseStream
67 #define Pa_GetDefaultOutputDevice pPa_GetDefaultOutputDevice
68 #define Pa_GetDefaultInputDevice pPa_GetDefaultInputDevice
69 #define Pa_GetStreamInfo pPa_GetStreamInfo
70 #endif
71 #endif
74 struct PortPlayback final : public BackendBase {
75 PortPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
76 ~PortPlayback() override;
78 static int writeCallbackC(const void *inputBuffer, void *outputBuffer,
79 unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
80 const PaStreamCallbackFlags statusFlags, void *userData)
82 return static_cast<PortPlayback*>(userData)->writeCallback(inputBuffer, outputBuffer,
83 framesPerBuffer, timeInfo, statusFlags);
85 int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
86 const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags);
88 void open(const ALCchar *name) override;
89 bool reset() override;
90 bool start() override;
91 void stop() override;
93 PaStream *mStream{nullptr};
94 PaStreamParameters mParams{};
95 ALuint mUpdateSize{0u};
97 DEF_NEWDEL(PortPlayback)
100 PortPlayback::~PortPlayback()
102 PaError err{mStream ? Pa_CloseStream(mStream) : paNoError};
103 if(err != paNoError)
104 ERR("Error closing stream: %s\n", Pa_GetErrorText(err));
105 mStream = nullptr;
109 int PortPlayback::writeCallback(const void*, void *outputBuffer,
110 unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo*,
111 const PaStreamCallbackFlags)
113 std::lock_guard<PortPlayback> _{*this};
114 aluMixData(mDevice, outputBuffer, static_cast<ALuint>(framesPerBuffer));
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 ERR("Pa_OpenStream() returned an error: %s\n", Pa_GetErrorText(err));
170 throw al::backend_exception{ALC_INVALID_VALUE, "Failed to open stream: %s",
171 Pa_GetErrorText(err)};
174 mDevice->DeviceName = name;
177 bool PortPlayback::reset()
179 const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)};
180 mDevice->Frequency = static_cast<ALuint>(streamInfo->sampleRate);
181 mDevice->UpdateSize = mUpdateSize;
183 if(mParams.sampleFormat == paInt8)
184 mDevice->FmtType = DevFmtByte;
185 else if(mParams.sampleFormat == paUInt8)
186 mDevice->FmtType = DevFmtUByte;
187 else if(mParams.sampleFormat == paInt16)
188 mDevice->FmtType = DevFmtShort;
189 else if(mParams.sampleFormat == paInt32)
190 mDevice->FmtType = DevFmtInt;
191 else if(mParams.sampleFormat == paFloat32)
192 mDevice->FmtType = DevFmtFloat;
193 else
195 ERR("Unexpected sample format: 0x%lx\n", mParams.sampleFormat);
196 return false;
199 if(mParams.channelCount == 2)
200 mDevice->FmtChans = DevFmtStereo;
201 else if(mParams.channelCount == 1)
202 mDevice->FmtChans = DevFmtMono;
203 else
205 ERR("Unexpected channel count: %u\n", mParams.channelCount);
206 return false;
208 SetDefaultChannelOrder(mDevice);
210 return true;
213 bool PortPlayback::start()
215 PaError err{Pa_StartStream(mStream)};
216 if(err != paNoError)
218 ERR("Pa_StartStream() returned an error: %s\n", Pa_GetErrorText(err));
219 return false;
221 return true;
224 void PortPlayback::stop()
226 PaError err{Pa_StopStream(mStream)};
227 if(err != paNoError)
228 ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
232 struct PortCapture final : public BackendBase {
233 PortCapture(ALCdevice *device) noexcept : BackendBase{device} { }
234 ~PortCapture() override;
236 static int readCallbackC(const void *inputBuffer, void *outputBuffer,
237 unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
238 const PaStreamCallbackFlags statusFlags, void *userData)
240 return static_cast<PortCapture*>(userData)->readCallback(inputBuffer, outputBuffer,
241 framesPerBuffer, timeInfo, statusFlags);
243 int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
244 const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags);
246 void open(const ALCchar *name) override;
247 bool start() override;
248 void stop() override;
249 ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
250 ALCuint availableSamples() override;
252 PaStream *mStream{nullptr};
253 PaStreamParameters mParams;
255 RingBufferPtr mRing{nullptr};
257 DEF_NEWDEL(PortCapture)
260 PortCapture::~PortCapture()
262 PaError err{mStream ? Pa_CloseStream(mStream) : paNoError};
263 if(err != paNoError)
264 ERR("Error closing stream: %s\n", Pa_GetErrorText(err));
265 mStream = nullptr;
269 int PortCapture::readCallback(const void *inputBuffer, void*,
270 unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo*,
271 const PaStreamCallbackFlags)
273 mRing->write(inputBuffer, framesPerBuffer);
274 return 0;
278 void PortCapture::open(const ALCchar *name)
280 if(!name)
281 name = pa_device;
282 else if(strcmp(name, pa_device) != 0)
283 throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
285 ALuint samples{mDevice->BufferSize};
286 samples = maxu(samples, 100 * mDevice->Frequency / 1000);
287 ALuint frame_size{mDevice->frameSizeFromFmt()};
289 mRing = CreateRingBuffer(samples, frame_size, false);
290 if(!mRing) throw al::backend_exception{ALC_INVALID_VALUE, "Failed to create ring buffer"};
292 auto devidopt = ConfigValueInt(nullptr, "port", "capture");
293 if(devidopt && *devidopt >= 0) mParams.device = *devidopt;
294 else mParams.device = Pa_GetDefaultOutputDevice();
295 mParams.suggestedLatency = 0.0f;
296 mParams.hostApiSpecificStreamInfo = nullptr;
298 switch(mDevice->FmtType)
300 case DevFmtByte:
301 mParams.sampleFormat = paInt8;
302 break;
303 case DevFmtUByte:
304 mParams.sampleFormat = paUInt8;
305 break;
306 case DevFmtShort:
307 mParams.sampleFormat = paInt16;
308 break;
309 case DevFmtInt:
310 mParams.sampleFormat = paInt32;
311 break;
312 case DevFmtFloat:
313 mParams.sampleFormat = paFloat32;
314 break;
315 case DevFmtUInt:
316 case DevFmtUShort:
317 ERR("%s samples not supported\n", DevFmtTypeString(mDevice->FmtType));
318 throw al::backend_exception{ALC_INVALID_VALUE, "%s samples not supported",
319 DevFmtTypeString(mDevice->FmtType)};
321 mParams.channelCount = static_cast<int>(mDevice->channelsFromFmt());
323 PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->Frequency,
324 paFramesPerBufferUnspecified, paNoFlag, &PortCapture::readCallbackC, this)};
325 if(err != paNoError)
327 ERR("Pa_OpenStream() returned an error: %s\n", Pa_GetErrorText(err));
328 throw al::backend_exception{ALC_INVALID_VALUE, "Failed to open stream: %s",
329 Pa_GetErrorText(err)};
332 mDevice->DeviceName = name;
336 bool PortCapture::start()
338 PaError err{Pa_StartStream(mStream)};
339 if(err != paNoError)
341 ERR("Error starting stream: %s\n", Pa_GetErrorText(err));
342 return false;
344 return true;
347 void PortCapture::stop()
349 PaError err{Pa_StopStream(mStream)};
350 if(err != paNoError)
351 ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
355 ALCuint PortCapture::availableSamples()
356 { return static_cast<ALCuint>(mRing->readSpace()); }
358 ALCenum PortCapture::captureSamples(al::byte *buffer, ALCuint samples)
360 mRing->read(buffer, samples);
361 return ALC_NO_ERROR;
364 } // namespace
367 bool PortBackendFactory::init()
369 PaError err;
371 #ifdef HAVE_DYNLOAD
372 if(!pa_handle)
374 #ifdef _WIN32
375 # define PALIB "portaudio.dll"
376 #elif defined(__APPLE__) && defined(__MACH__)
377 # define PALIB "libportaudio.2.dylib"
378 #elif defined(__OpenBSD__)
379 # define PALIB "libportaudio.so"
380 #else
381 # define PALIB "libportaudio.so.2"
382 #endif
384 pa_handle = LoadLib(PALIB);
385 if(!pa_handle)
386 return false;
388 #define LOAD_FUNC(f) do { \
389 p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(pa_handle, #f)); \
390 if(p##f == nullptr) \
392 CloseLib(pa_handle); \
393 pa_handle = nullptr; \
394 return false; \
396 } while(0)
397 LOAD_FUNC(Pa_Initialize);
398 LOAD_FUNC(Pa_Terminate);
399 LOAD_FUNC(Pa_GetErrorText);
400 LOAD_FUNC(Pa_StartStream);
401 LOAD_FUNC(Pa_StopStream);
402 LOAD_FUNC(Pa_OpenStream);
403 LOAD_FUNC(Pa_CloseStream);
404 LOAD_FUNC(Pa_GetDefaultOutputDevice);
405 LOAD_FUNC(Pa_GetDefaultInputDevice);
406 LOAD_FUNC(Pa_GetStreamInfo);
407 #undef LOAD_FUNC
409 if((err=Pa_Initialize()) != paNoError)
411 ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
412 CloseLib(pa_handle);
413 pa_handle = nullptr;
414 return false;
417 #else
418 if((err=Pa_Initialize()) != paNoError)
420 ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
421 return false;
423 #endif
424 return true;
427 bool PortBackendFactory::querySupport(BackendType type)
428 { return (type == BackendType::Playback || type == BackendType::Capture); }
430 void PortBackendFactory::probe(DevProbe type, std::string *outnames)
432 switch(type)
434 case DevProbe::Playback:
435 case DevProbe::Capture:
436 /* Includes null char. */
437 outnames->append(pa_device, sizeof(pa_device));
438 break;
442 BackendPtr PortBackendFactory::createBackend(ALCdevice *device, BackendType type)
444 if(type == BackendType::Playback)
445 return BackendPtr{new PortPlayback{device}};
446 if(type == BackendType::Capture)
447 return BackendPtr{new PortCapture{device}};
448 return nullptr;
451 BackendFactory &PortBackendFactory::getFactory()
453 static PortBackendFactory factory{};
454 return factory;