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
23 #include "portaudio.h"
30 #include "alc/alconfig.h"
32 #include "core/device.h"
33 #include "core/logging.h"
35 #include "ringbuffer.h"
37 #include <portaudio.h> /* NOLINT(*-duplicate-include) Not the same header. */
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
);
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
77 uint mPlaybackChannels
{};
78 uint mCaptureChannels
{};
80 std::vector
<DeviceEntry
> DeviceNames
;
82 void EnumerateDevices()
84 const auto devcount
= Pa_GetDeviceCount();
87 ERR("Error getting device count: %s\n", Pa_GetErrorText(devcount
));
91 std::vector
<DeviceEntry
>(static_cast<uint
>(devcount
)).swap(DeviceNames
);
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
);
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
};
132 ERR("Error closing stream: %s\n", Pa_GetErrorText(err
));
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
));
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, ¶ms
, 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;
189 throw al::backend_exception
{al::backend_error::NoDevice
, "Failed to open stream: %s",
190 Pa_GetErrorText(err
)};
196 void PortPlayback::open(std::string_view name
)
198 if(DeviceNames
.empty())
201 PaDeviceIndex deviceid
{-1};
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
;
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
;
227 bool PortPlayback::reset()
231 auto err
= Pa_CloseStream(mStream
);
233 ERR("Error closing stream: %s\n", Pa_GetErrorText(err
));
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;
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
,
270 mDevice
->BufferSize
= static_cast<uint
>(std::clamp(sampleLatency
,
271 double(mDevice
->BufferSize
), double{std::numeric_limits
<int>::max()}));
274 setDefaultChannelOrder();
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
};
316 ERR("Error closing stream: %s\n", Pa_GetErrorText(err
));
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
);
329 void PortCapture::open(std::string_view name
)
331 if(DeviceNames
.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
;
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
)
366 mParams
.sampleFormat
= paInt8
;
369 mParams
.sampleFormat
= paUInt8
;
372 mParams
.sampleFormat
= paInt16
;
375 mParams
.sampleFormat
= paInt32
;
378 mParams
.sampleFormat
= paFloat32
;
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)};
397 throw al::backend_exception
{al::backend_error::NoDevice
, "Failed to open stream: %s",
398 Pa_GetErrorText(err
)};
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
); }
427 bool PortBackendFactory::init()
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"
439 # define PALIB "libportaudio.so.2"
442 pa_handle
= LoadLib(PALIB
);
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; \
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
);
469 const PaError err
{Pa_Initialize()};
472 ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err
));
479 const PaError err
{Pa_Initialize()};
482 ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err
));
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
;
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
);
513 devices
.emplace_back(DeviceNames
[i
].mName
);
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
);
531 devices
.emplace_back(DeviceNames
[i
].mName
);
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
}};
549 BackendFactory
&PortBackendFactory::getFactory()
551 static PortBackendFactory factory
{};