Clean up the debug example a little
[openal-soft.git] / alc / backends / portaudio.cpp
blob67641fee215879bea4236125d77dfb8ad2a1e75b
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 "portaudio.h"
25 #include <cmath>
26 #include <cstdio>
27 #include <cstdlib>
28 #include <cstring>
30 #include "alc/alconfig.h"
31 #include "alstring.h"
32 #include "core/device.h"
33 #include "core/logging.h"
34 #include "dynload.h"
35 #include "ringbuffer.h"
37 #include <portaudio.h> /* NOLINT(*-duplicate-include) Not the same header. */
40 namespace {
42 #ifdef HAVE_DYNLOAD
43 void *pa_handle;
44 #define MAKE_FUNC(x) decltype(x) * p##x
45 MAKE_FUNC(Pa_Initialize);
46 MAKE_FUNC(Pa_Terminate);
47 MAKE_FUNC(Pa_GetErrorText);
48 MAKE_FUNC(Pa_StartStream);
49 MAKE_FUNC(Pa_StopStream);
50 MAKE_FUNC(Pa_OpenStream);
51 MAKE_FUNC(Pa_CloseStream);
52 MAKE_FUNC(Pa_GetDeviceCount);
53 MAKE_FUNC(Pa_GetDeviceInfo);
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_GetDeviceCount pPa_GetDeviceCount
68 #define Pa_GetDeviceInfo pPa_GetDeviceInfo
69 #define Pa_GetDefaultOutputDevice pPa_GetDefaultOutputDevice
70 #define Pa_GetDefaultInputDevice pPa_GetDefaultInputDevice
71 #define Pa_GetStreamInfo pPa_GetStreamInfo
72 #endif
73 #endif
75 struct DeviceEntry {
76 std::string mName;
77 uint mPlaybackChannels{};
78 uint mCaptureChannels{};
80 std::vector<DeviceEntry> DeviceNames;
82 void EnumerateDevices()
84 const auto devcount = Pa_GetDeviceCount();
85 if(devcount < 0)
87 ERR("Error getting device count: %s\n", Pa_GetErrorText(devcount));
88 return;
91 std::vector<DeviceEntry>(static_cast<uint>(devcount)).swap(DeviceNames);
92 PaDeviceIndex idx{0};
93 for(auto &entry : DeviceNames)
95 if(auto info = Pa_GetDeviceInfo(idx); info && info->name)
97 entry.mName = info->name;
98 entry.mPlaybackChannels = static_cast<uint>(std::max(info->maxOutputChannels, 0));
99 entry.mCaptureChannels = static_cast<uint>(std::max(info->maxInputChannels, 0));
100 TRACE("Device %d \"%s\": %d playback, %d capture channels\n", idx, entry.mName.c_str(),
101 info->maxOutputChannels, info->maxInputChannels);
103 ++idx;
107 struct StreamParamsExt : public PaStreamParameters { uint updateSize; };
109 struct PortPlayback final : public BackendBase {
110 PortPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
111 ~PortPlayback() override;
113 int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
114 const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept;
116 void createStream(PaDeviceIndex deviceid);
118 void open(std::string_view name) override;
119 bool reset() override;
120 void start() override;
121 void stop() override;
123 PaStream *mStream{nullptr};
124 StreamParamsExt mParams{};
125 PaDeviceIndex mDeviceIdx{-1};
128 PortPlayback::~PortPlayback()
130 PaError err{mStream ? Pa_CloseStream(mStream) : paNoError};
131 if(err != paNoError)
132 ERR("Error closing stream: %s\n", Pa_GetErrorText(err));
133 mStream = nullptr;
137 int PortPlayback::writeCallback(const void*, void *outputBuffer, unsigned long framesPerBuffer,
138 const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept
140 mDevice->renderSamples(outputBuffer, static_cast<uint>(framesPerBuffer),
141 static_cast<uint>(mParams.channelCount));
142 return 0;
146 void PortPlayback::createStream(PaDeviceIndex deviceid)
148 auto &devinfo = DeviceNames.at(static_cast<uint>(deviceid));
150 auto params = StreamParamsExt{};
151 params.device = deviceid;
152 params.suggestedLatency = mDevice->BufferSize / static_cast<double>(mDevice->Frequency);
153 params.hostApiSpecificStreamInfo = nullptr;
154 params.channelCount = static_cast<int>(std::min(devinfo.mPlaybackChannels,
155 mDevice->channelsFromFmt()));
156 switch(mDevice->FmtType)
158 case DevFmtByte: params.sampleFormat = paInt8; break;
159 case DevFmtUByte: params.sampleFormat = paUInt8; break;
160 case DevFmtUShort: [[fallthrough]];
161 case DevFmtShort: params.sampleFormat = paInt16; break;
162 case DevFmtUInt: [[fallthrough]];
163 case DevFmtInt: params.sampleFormat = paInt32; break;
164 case DevFmtFloat: params.sampleFormat = paFloat32; break;
166 params.updateSize = mDevice->UpdateSize;
168 auto srate = uint{mDevice->Frequency};
170 static constexpr auto writeCallback = [](const void *inputBuffer, void *outputBuffer,
171 unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
172 const PaStreamCallbackFlags statusFlags, void *userData) noexcept
174 return static_cast<PortPlayback*>(userData)->writeCallback(inputBuffer, outputBuffer,
175 framesPerBuffer, timeInfo, statusFlags);
177 while(PaError err{Pa_OpenStream(&mStream, nullptr, &params, srate, params.updateSize, paNoFlag,
178 writeCallback, this)})
180 if(params.updateSize != DefaultUpdateSize)
181 params.updateSize = DefaultUpdateSize;
182 else if(srate != 48000u)
183 srate = (srate != 44100u) ? 44100u : 48000u;
184 else if(params.sampleFormat != paInt16)
185 params.sampleFormat = paInt16;
186 else if(params.channelCount != 2)
187 params.channelCount = 2;
188 else
189 throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
190 Pa_GetErrorText(err)};
193 mParams = params;
196 void PortPlayback::open(std::string_view name)
198 if(DeviceNames.empty())
199 EnumerateDevices();
201 PaDeviceIndex deviceid{-1};
202 if(name.empty())
204 if(auto devidopt = ConfigValueInt({}, "port", "device"))
205 deviceid = *devidopt;
206 if(deviceid < 0 || static_cast<uint>(deviceid) >= DeviceNames.size())
207 deviceid = Pa_GetDefaultOutputDevice();
208 name = DeviceNames.at(static_cast<uint>(deviceid)).mName;
210 else
212 auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(),
213 [name](const DeviceEntry &entry)
214 { return entry.mPlaybackChannels > 0 && name == entry.mName; });
215 if(iter == DeviceNames.cend())
216 throw al::backend_exception{al::backend_error::NoDevice,
217 "Device name \"%.*s\" not found", al::sizei(name), name.data()};
218 deviceid = static_cast<int>(std::distance(DeviceNames.cbegin(), iter));
221 createStream(deviceid);
222 mDeviceIdx = deviceid;
224 mDeviceName = name;
227 bool PortPlayback::reset()
229 if(mStream)
231 auto err = Pa_CloseStream(mStream);
232 if(err != paNoError)
233 ERR("Error closing stream: %s\n", Pa_GetErrorText(err));
234 mStream = nullptr;
237 createStream(mDeviceIdx);
239 switch(mParams.sampleFormat)
241 case paFloat32: mDevice->FmtType = DevFmtFloat; break;
242 case paInt32: mDevice->FmtType = DevFmtInt; break;
243 case paInt16: mDevice->FmtType = DevFmtShort; break;
244 case paInt8: mDevice->FmtType = DevFmtByte; break;
245 case paUInt8: mDevice->FmtType = DevFmtUByte; break;
246 default:
247 ERR("Unexpected PortAudio sample format: %lu\n", mParams.sampleFormat);
248 throw al::backend_exception{al::backend_error::NoDevice, "Invalid sample format: %lu",
249 mParams.sampleFormat};
252 if(mParams.channelCount != static_cast<int>(mDevice->channelsFromFmt()))
254 if(mParams.channelCount >= 2)
255 mDevice->FmtChans = DevFmtStereo;
256 else if(mParams.channelCount == 1)
257 mDevice->FmtChans = DevFmtMono;
258 mDevice->mAmbiOrder = 0;
261 const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)};
262 mDevice->Frequency = static_cast<uint>(std::lround(streamInfo->sampleRate));
263 mDevice->UpdateSize = mParams.updateSize;
264 mDevice->BufferSize = mDevice->UpdateSize * 2;
265 if(streamInfo->outputLatency > 0.0f)
267 const double sampleLatency{streamInfo->outputLatency * streamInfo->sampleRate};
268 TRACE("Reported stream latency: %f sec (%f samples)\n", streamInfo->outputLatency,
269 sampleLatency);
270 mDevice->BufferSize = static_cast<uint>(std::clamp(sampleLatency,
271 double(mDevice->BufferSize), double{std::numeric_limits<int>::max()}));
274 setDefaultChannelOrder();
276 return true;
279 void PortPlayback::start()
281 if(const PaError err{Pa_StartStream(mStream)}; err != paNoError)
282 throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: %s",
283 Pa_GetErrorText(err)};
286 void PortPlayback::stop()
288 if(PaError err{Pa_StopStream(mStream)}; err != paNoError)
289 ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
293 struct PortCapture final : public BackendBase {
294 PortCapture(DeviceBase *device) noexcept : BackendBase{device} { }
295 ~PortCapture() override;
297 int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
298 const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) const noexcept;
300 void open(std::string_view name) override;
301 void start() override;
302 void stop() override;
303 void captureSamples(std::byte *buffer, uint samples) override;
304 uint availableSamples() override;
306 PaStream *mStream{nullptr};
307 PaStreamParameters mParams{};
309 RingBufferPtr mRing{nullptr};
312 PortCapture::~PortCapture()
314 PaError err{mStream ? Pa_CloseStream(mStream) : paNoError};
315 if(err != paNoError)
316 ERR("Error closing stream: %s\n", Pa_GetErrorText(err));
317 mStream = nullptr;
321 int PortCapture::readCallback(const void *inputBuffer, void*, unsigned long framesPerBuffer,
322 const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) const noexcept
324 std::ignore = mRing->write(inputBuffer, framesPerBuffer);
325 return 0;
329 void PortCapture::open(std::string_view name)
331 if(DeviceNames.empty())
332 EnumerateDevices();
334 int deviceid{};
335 if(name.empty())
337 if(auto devidopt = ConfigValueInt({}, "port", "capture"))
338 deviceid = *devidopt;
339 if(deviceid < 0 || static_cast<uint>(deviceid) >= DeviceNames.size())
340 deviceid = Pa_GetDefaultInputDevice();
341 name = DeviceNames.at(static_cast<uint>(deviceid)).mName;
343 else
345 auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(),
346 [name](const DeviceEntry &entry)
347 { return entry.mCaptureChannels > 0 && name == entry.mName; });
348 if(iter == DeviceNames.cend())
349 throw al::backend_exception{al::backend_error::NoDevice,
350 "Device name \"%.*s\" not found", al::sizei(name), name.data()};
351 deviceid = static_cast<int>(std::distance(DeviceNames.cbegin(), iter));
354 const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)};
355 const uint frame_size{mDevice->frameSizeFromFmt()};
357 mRing = RingBuffer::Create(samples, frame_size, false);
359 mParams.device = deviceid;
360 mParams.suggestedLatency = 0.0f;
361 mParams.hostApiSpecificStreamInfo = nullptr;
363 switch(mDevice->FmtType)
365 case DevFmtByte:
366 mParams.sampleFormat = paInt8;
367 break;
368 case DevFmtUByte:
369 mParams.sampleFormat = paUInt8;
370 break;
371 case DevFmtShort:
372 mParams.sampleFormat = paInt16;
373 break;
374 case DevFmtInt:
375 mParams.sampleFormat = paInt32;
376 break;
377 case DevFmtFloat:
378 mParams.sampleFormat = paFloat32;
379 break;
380 case DevFmtUInt:
381 case DevFmtUShort:
382 throw al::backend_exception{al::backend_error::DeviceError, "%s samples not supported",
383 DevFmtTypeString(mDevice->FmtType)};
385 mParams.channelCount = static_cast<int>(mDevice->channelsFromFmt());
387 static constexpr auto readCallback = [](const void *inputBuffer, void *outputBuffer,
388 unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
389 const PaStreamCallbackFlags statusFlags, void *userData) noexcept
391 return static_cast<PortCapture*>(userData)->readCallback(inputBuffer, outputBuffer,
392 framesPerBuffer, timeInfo, statusFlags);
394 PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->Frequency,
395 paFramesPerBufferUnspecified, paNoFlag, readCallback, this)};
396 if(err != paNoError)
397 throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
398 Pa_GetErrorText(err)};
400 mDeviceName = name;
404 void PortCapture::start()
406 if(const PaError err{Pa_StartStream(mStream)}; err != paNoError)
407 throw al::backend_exception{al::backend_error::DeviceError,
408 "Failed to start recording: %s", Pa_GetErrorText(err)};
411 void PortCapture::stop()
413 if(PaError err{Pa_StopStream(mStream)}; err != paNoError)
414 ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
418 uint PortCapture::availableSamples()
419 { return static_cast<uint>(mRing->readSpace()); }
421 void PortCapture::captureSamples(std::byte *buffer, uint samples)
422 { std::ignore = mRing->read(buffer, samples); }
424 } // namespace
427 bool PortBackendFactory::init()
429 #ifdef HAVE_DYNLOAD
430 if(!pa_handle)
432 #ifdef _WIN32
433 # define PALIB "portaudio.dll"
434 #elif defined(__APPLE__) && defined(__MACH__)
435 # define PALIB "libportaudio.2.dylib"
436 #elif defined(__OpenBSD__)
437 # define PALIB "libportaudio.so"
438 #else
439 # define PALIB "libportaudio.so.2"
440 #endif
442 pa_handle = LoadLib(PALIB);
443 if(!pa_handle)
444 return false;
446 #define LOAD_FUNC(f) do { \
447 p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(pa_handle, #f)); \
448 if(p##f == nullptr) \
450 CloseLib(pa_handle); \
451 pa_handle = nullptr; \
452 return false; \
454 } while(0)
455 LOAD_FUNC(Pa_Initialize);
456 LOAD_FUNC(Pa_Terminate);
457 LOAD_FUNC(Pa_GetErrorText);
458 LOAD_FUNC(Pa_StartStream);
459 LOAD_FUNC(Pa_StopStream);
460 LOAD_FUNC(Pa_OpenStream);
461 LOAD_FUNC(Pa_CloseStream);
462 LOAD_FUNC(Pa_GetDeviceCount);
463 LOAD_FUNC(Pa_GetDeviceInfo);
464 LOAD_FUNC(Pa_GetDefaultOutputDevice);
465 LOAD_FUNC(Pa_GetDefaultInputDevice);
466 LOAD_FUNC(Pa_GetStreamInfo);
467 #undef LOAD_FUNC
469 const PaError err{Pa_Initialize()};
470 if(err != paNoError)
472 ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
473 CloseLib(pa_handle);
474 pa_handle = nullptr;
475 return false;
478 #else
479 const PaError err{Pa_Initialize()};
480 if(err != paNoError)
482 ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
483 return false;
485 #endif
486 return true;
489 bool PortBackendFactory::querySupport(BackendType type)
490 { return (type == BackendType::Playback || type == BackendType::Capture); }
492 auto PortBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
494 std::vector<std::string> devices;
496 EnumerateDevices();
497 int defaultid{-1};
498 switch(type)
500 case BackendType::Playback:
501 defaultid = Pa_GetDefaultOutputDevice();
502 if(auto devidopt = ConfigValueInt({}, "port", "device"); devidopt && *devidopt >= 0
503 && static_cast<uint>(*devidopt) < DeviceNames.size())
504 defaultid = *devidopt;
506 for(size_t i{0};i < DeviceNames.size();++i)
508 if(DeviceNames[i].mPlaybackChannels > 0)
510 if(defaultid >= 0 && static_cast<uint>(defaultid) == i)
511 devices.emplace(devices.cbegin(), DeviceNames[i].mName);
512 else
513 devices.emplace_back(DeviceNames[i].mName);
516 break;
518 case BackendType::Capture:
519 defaultid = Pa_GetDefaultInputDevice();
520 if(auto devidopt = ConfigValueInt({}, "port", "capture"); devidopt && *devidopt >= 0
521 && static_cast<uint>(*devidopt) < DeviceNames.size())
522 defaultid = *devidopt;
524 for(size_t i{0};i < DeviceNames.size();++i)
526 if(DeviceNames[i].mCaptureChannels > 0)
528 if(defaultid >= 0 && static_cast<uint>(defaultid) == i)
529 devices.emplace(devices.cbegin(), DeviceNames[i].mName);
530 else
531 devices.emplace_back(DeviceNames[i].mName);
534 break;
537 return devices;
540 BackendPtr PortBackendFactory::createBackend(DeviceBase *device, BackendType type)
542 if(type == BackendType::Playback)
543 return BackendPtr{new PortPlayback{device}};
544 if(type == BackendType::Capture)
545 return BackendPtr{new PortCapture{device}};
546 return nullptr;
549 BackendFactory &PortBackendFactory::getFactory()
551 static PortBackendFactory factory{};
552 return factory;