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
)
98 entry
.mName
= "OpenAL Soft on "+std::string
{info
->name
};
100 entry
.mName
= info
->name
;
102 entry
.mPlaybackChannels
= static_cast<uint
>(std::max(info
->maxOutputChannels
, 0));
103 entry
.mCaptureChannels
= static_cast<uint
>(std::max(info
->maxInputChannels
, 0));
104 TRACE("Device %d \"%s\": %d playback, %d capture channels\n", idx
, entry
.mName
.c_str(),
105 info
->maxOutputChannels
, info
->maxInputChannels
);
111 struct StreamParamsExt
: public PaStreamParameters
{ uint updateSize
; };
113 struct PortPlayback final
: public BackendBase
{
114 PortPlayback(DeviceBase
*device
) noexcept
: BackendBase
{device
} { }
115 ~PortPlayback() override
;
117 int writeCallback(const void *inputBuffer
, void *outputBuffer
, unsigned long framesPerBuffer
,
118 const PaStreamCallbackTimeInfo
*timeInfo
, const PaStreamCallbackFlags statusFlags
) noexcept
;
120 void createStream(PaDeviceIndex deviceid
);
122 void open(std::string_view name
) override
;
123 bool reset() override
;
124 void start() override
;
125 void stop() override
;
127 PaStream
*mStream
{nullptr};
128 StreamParamsExt mParams
{};
129 PaDeviceIndex mDeviceIdx
{-1};
132 PortPlayback::~PortPlayback()
134 PaError err
{mStream
? Pa_CloseStream(mStream
) : paNoError
};
136 ERR("Error closing stream: %s\n", Pa_GetErrorText(err
));
141 int PortPlayback::writeCallback(const void*, void *outputBuffer
, unsigned long framesPerBuffer
,
142 const PaStreamCallbackTimeInfo
*, const PaStreamCallbackFlags
) noexcept
144 mDevice
->renderSamples(outputBuffer
, static_cast<uint
>(framesPerBuffer
),
145 static_cast<uint
>(mParams
.channelCount
));
150 void PortPlayback::createStream(PaDeviceIndex deviceid
)
152 auto &devinfo
= DeviceNames
.at(static_cast<uint
>(deviceid
));
154 auto params
= StreamParamsExt
{};
155 params
.device
= deviceid
;
156 params
.suggestedLatency
= mDevice
->BufferSize
/ static_cast<double>(mDevice
->Frequency
);
157 params
.hostApiSpecificStreamInfo
= nullptr;
158 params
.channelCount
= static_cast<int>(std::min(devinfo
.mPlaybackChannels
,
159 mDevice
->channelsFromFmt()));
160 switch(mDevice
->FmtType
)
162 case DevFmtByte
: params
.sampleFormat
= paInt8
; break;
163 case DevFmtUByte
: params
.sampleFormat
= paUInt8
; break;
164 case DevFmtUShort
: [[fallthrough
]];
165 case DevFmtShort
: params
.sampleFormat
= paInt16
; break;
166 case DevFmtUInt
: [[fallthrough
]];
167 case DevFmtInt
: params
.sampleFormat
= paInt32
; break;
168 case DevFmtFloat
: params
.sampleFormat
= paFloat32
; break;
170 params
.updateSize
= mDevice
->UpdateSize
;
172 auto srate
= uint
{mDevice
->Frequency
};
174 static constexpr auto writeCallback
= [](const void *inputBuffer
, void *outputBuffer
,
175 unsigned long framesPerBuffer
, const PaStreamCallbackTimeInfo
*timeInfo
,
176 const PaStreamCallbackFlags statusFlags
, void *userData
) noexcept
178 return static_cast<PortPlayback
*>(userData
)->writeCallback(inputBuffer
, outputBuffer
,
179 framesPerBuffer
, timeInfo
, statusFlags
);
181 while(PaError err
{Pa_OpenStream(&mStream
, nullptr, ¶ms
, srate
, params
.updateSize
, paNoFlag
,
182 writeCallback
, this)})
184 if(params
.updateSize
!= DefaultUpdateSize
)
185 params
.updateSize
= DefaultUpdateSize
;
186 else if(srate
!= 48000u)
187 srate
= (srate
!= 44100u) ? 44100u : 48000u;
188 else if(params
.sampleFormat
!= paInt16
)
189 params
.sampleFormat
= paInt16
;
190 else if(params
.channelCount
!= 2)
191 params
.channelCount
= 2;
193 throw al::backend_exception
{al::backend_error::NoDevice
, "Failed to open stream: %s",
194 Pa_GetErrorText(err
)};
200 void PortPlayback::open(std::string_view name
)
202 if(DeviceNames
.empty())
205 PaDeviceIndex deviceid
{-1};
208 if(auto devidopt
= ConfigValueInt({}, "port", "device"))
209 deviceid
= *devidopt
;
210 if(deviceid
< 0 || static_cast<uint
>(deviceid
) >= DeviceNames
.size())
211 deviceid
= Pa_GetDefaultOutputDevice();
212 name
= DeviceNames
.at(static_cast<uint
>(deviceid
)).mName
;
216 auto iter
= std::find_if(DeviceNames
.cbegin(), DeviceNames
.cend(),
217 [name
](const DeviceEntry
&entry
)
218 { return entry
.mPlaybackChannels
> 0 && name
== entry
.mName
; });
219 if(iter
== DeviceNames
.cend())
220 throw al::backend_exception
{al::backend_error::NoDevice
,
221 "Device name \"%.*s\" not found", al::sizei(name
), name
.data()};
222 deviceid
= static_cast<int>(std::distance(DeviceNames
.cbegin(), iter
));
225 createStream(deviceid
);
226 mDeviceIdx
= deviceid
;
228 mDevice
->DeviceName
= name
;
231 bool PortPlayback::reset()
235 auto err
= Pa_CloseStream(mStream
);
237 ERR("Error closing stream: %s\n", Pa_GetErrorText(err
));
241 createStream(mDeviceIdx
);
243 switch(mParams
.sampleFormat
)
245 case paFloat32
: mDevice
->FmtType
= DevFmtFloat
; break;
246 case paInt32
: mDevice
->FmtType
= DevFmtInt
; break;
247 case paInt16
: mDevice
->FmtType
= DevFmtShort
; break;
248 case paInt8
: mDevice
->FmtType
= DevFmtByte
; break;
249 case paUInt8
: mDevice
->FmtType
= DevFmtUByte
; break;
251 ERR("Unexpected PortAudio sample format: %lu\n", mParams
.sampleFormat
);
252 throw al::backend_exception
{al::backend_error::NoDevice
, "Invalid sample format: %lu",
253 mParams
.sampleFormat
};
256 if(mParams
.channelCount
!= static_cast<int>(mDevice
->channelsFromFmt()))
258 if(mParams
.channelCount
>= 2)
259 mDevice
->FmtChans
= DevFmtStereo
;
260 else if(mParams
.channelCount
== 1)
261 mDevice
->FmtChans
= DevFmtMono
;
262 mDevice
->mAmbiOrder
= 0;
265 const PaStreamInfo
*streamInfo
{Pa_GetStreamInfo(mStream
)};
266 mDevice
->Frequency
= static_cast<uint
>(std::lround(streamInfo
->sampleRate
));
267 mDevice
->UpdateSize
= mParams
.updateSize
;
268 mDevice
->BufferSize
= mDevice
->UpdateSize
* 2;
269 if(streamInfo
->outputLatency
> 0.0f
)
271 const double sampleLatency
{streamInfo
->outputLatency
* streamInfo
->sampleRate
};
272 TRACE("Reported stream latency: %f sec (%f samples)\n", streamInfo
->outputLatency
,
274 mDevice
->BufferSize
= static_cast<uint
>(std::clamp(sampleLatency
,
275 double(mDevice
->BufferSize
), double{std::numeric_limits
<int>::max()}));
278 setDefaultChannelOrder();
283 void PortPlayback::start()
285 if(const PaError err
{Pa_StartStream(mStream
)}; err
!= paNoError
)
286 throw al::backend_exception
{al::backend_error::DeviceError
, "Failed to start playback: %s",
287 Pa_GetErrorText(err
)};
290 void PortPlayback::stop()
292 if(PaError err
{Pa_StopStream(mStream
)}; err
!= paNoError
)
293 ERR("Error stopping stream: %s\n", Pa_GetErrorText(err
));
297 struct PortCapture final
: public BackendBase
{
298 PortCapture(DeviceBase
*device
) noexcept
: BackendBase
{device
} { }
299 ~PortCapture() override
;
301 int readCallback(const void *inputBuffer
, void *outputBuffer
, unsigned long framesPerBuffer
,
302 const PaStreamCallbackTimeInfo
*timeInfo
, const PaStreamCallbackFlags statusFlags
) const noexcept
;
304 void open(std::string_view name
) override
;
305 void start() override
;
306 void stop() override
;
307 void captureSamples(std::byte
*buffer
, uint samples
) override
;
308 uint
availableSamples() override
;
310 PaStream
*mStream
{nullptr};
311 PaStreamParameters mParams
{};
313 RingBufferPtr mRing
{nullptr};
316 PortCapture::~PortCapture()
318 PaError err
{mStream
? Pa_CloseStream(mStream
) : paNoError
};
320 ERR("Error closing stream: %s\n", Pa_GetErrorText(err
));
325 int PortCapture::readCallback(const void *inputBuffer
, void*, unsigned long framesPerBuffer
,
326 const PaStreamCallbackTimeInfo
*, const PaStreamCallbackFlags
) const noexcept
328 std::ignore
= mRing
->write(inputBuffer
, framesPerBuffer
);
333 void PortCapture::open(std::string_view name
)
335 if(DeviceNames
.empty())
341 if(auto devidopt
= ConfigValueInt({}, "port", "capture"))
342 deviceid
= *devidopt
;
343 if(deviceid
< 0 || static_cast<uint
>(deviceid
) >= DeviceNames
.size())
344 deviceid
= Pa_GetDefaultInputDevice();
345 name
= DeviceNames
.at(static_cast<uint
>(deviceid
)).mName
;
349 auto iter
= std::find_if(DeviceNames
.cbegin(), DeviceNames
.cend(),
350 [name
](const DeviceEntry
&entry
)
351 { return entry
.mCaptureChannels
> 0 && name
== entry
.mName
; });
352 if(iter
== DeviceNames
.cend())
353 throw al::backend_exception
{al::backend_error::NoDevice
,
354 "Device name \"%.*s\" not found", al::sizei(name
), name
.data()};
355 deviceid
= static_cast<int>(std::distance(DeviceNames
.cbegin(), iter
));
358 const uint samples
{std::max(mDevice
->BufferSize
, mDevice
->Frequency
/10u)};
359 const uint frame_size
{mDevice
->frameSizeFromFmt()};
361 mRing
= RingBuffer::Create(samples
, frame_size
, false);
363 mParams
.device
= deviceid
;
364 mParams
.suggestedLatency
= 0.0f
;
365 mParams
.hostApiSpecificStreamInfo
= nullptr;
367 switch(mDevice
->FmtType
)
370 mParams
.sampleFormat
= paInt8
;
373 mParams
.sampleFormat
= paUInt8
;
376 mParams
.sampleFormat
= paInt16
;
379 mParams
.sampleFormat
= paInt32
;
382 mParams
.sampleFormat
= paFloat32
;
386 throw al::backend_exception
{al::backend_error::DeviceError
, "%s samples not supported",
387 DevFmtTypeString(mDevice
->FmtType
)};
389 mParams
.channelCount
= static_cast<int>(mDevice
->channelsFromFmt());
391 static constexpr auto readCallback
= [](const void *inputBuffer
, void *outputBuffer
,
392 unsigned long framesPerBuffer
, const PaStreamCallbackTimeInfo
*timeInfo
,
393 const PaStreamCallbackFlags statusFlags
, void *userData
) noexcept
395 return static_cast<PortCapture
*>(userData
)->readCallback(inputBuffer
, outputBuffer
,
396 framesPerBuffer
, timeInfo
, statusFlags
);
398 PaError err
{Pa_OpenStream(&mStream
, &mParams
, nullptr, mDevice
->Frequency
,
399 paFramesPerBufferUnspecified
, paNoFlag
, readCallback
, this)};
401 throw al::backend_exception
{al::backend_error::NoDevice
, "Failed to open stream: %s",
402 Pa_GetErrorText(err
)};
404 mDevice
->DeviceName
= name
;
408 void PortCapture::start()
410 if(const PaError err
{Pa_StartStream(mStream
)}; err
!= paNoError
)
411 throw al::backend_exception
{al::backend_error::DeviceError
,
412 "Failed to start recording: %s", Pa_GetErrorText(err
)};
415 void PortCapture::stop()
417 if(PaError err
{Pa_StopStream(mStream
)}; err
!= paNoError
)
418 ERR("Error stopping stream: %s\n", Pa_GetErrorText(err
));
422 uint
PortCapture::availableSamples()
423 { return static_cast<uint
>(mRing
->readSpace()); }
425 void PortCapture::captureSamples(std::byte
*buffer
, uint samples
)
426 { std::ignore
= mRing
->read(buffer
, samples
); }
431 bool PortBackendFactory::init()
437 # define PALIB "portaudio.dll"
438 #elif defined(__APPLE__) && defined(__MACH__)
439 # define PALIB "libportaudio.2.dylib"
440 #elif defined(__OpenBSD__)
441 # define PALIB "libportaudio.so"
443 # define PALIB "libportaudio.so.2"
446 pa_handle
= LoadLib(PALIB
);
450 #define LOAD_FUNC(f) do { \
451 p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(pa_handle, #f)); \
452 if(p##f == nullptr) \
454 CloseLib(pa_handle); \
455 pa_handle = nullptr; \
459 LOAD_FUNC(Pa_Initialize
);
460 LOAD_FUNC(Pa_Terminate
);
461 LOAD_FUNC(Pa_GetErrorText
);
462 LOAD_FUNC(Pa_StartStream
);
463 LOAD_FUNC(Pa_StopStream
);
464 LOAD_FUNC(Pa_OpenStream
);
465 LOAD_FUNC(Pa_CloseStream
);
466 LOAD_FUNC(Pa_GetDeviceCount
);
467 LOAD_FUNC(Pa_GetDeviceInfo
);
468 LOAD_FUNC(Pa_GetDefaultOutputDevice
);
469 LOAD_FUNC(Pa_GetDefaultInputDevice
);
470 LOAD_FUNC(Pa_GetStreamInfo
);
473 const PaError err
{Pa_Initialize()};
476 ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err
));
483 const PaError err
{Pa_Initialize()};
486 ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err
));
493 bool PortBackendFactory::querySupport(BackendType type
)
494 { return (type
== BackendType::Playback
|| type
== BackendType::Capture
); }
496 auto PortBackendFactory::enumerate(BackendType type
) -> std::vector
<std::string
>
498 std::vector
<std::string
> devices
;
504 case BackendType::Playback
:
505 defaultid
= Pa_GetDefaultOutputDevice();
506 if(auto devidopt
= ConfigValueInt({}, "port", "device"); devidopt
&& *devidopt
>= 0
507 && static_cast<uint
>(*devidopt
) < DeviceNames
.size())
508 defaultid
= *devidopt
;
510 for(size_t i
{0};i
< DeviceNames
.size();++i
)
512 if(DeviceNames
[i
].mPlaybackChannels
> 0)
514 if(defaultid
>= 0 && static_cast<uint
>(defaultid
) == i
)
515 devices
.emplace(devices
.cbegin(), DeviceNames
[i
].mName
);
517 devices
.emplace_back(DeviceNames
[i
].mName
);
522 case BackendType::Capture
:
523 defaultid
= Pa_GetDefaultInputDevice();
524 if(auto devidopt
= ConfigValueInt({}, "port", "capture"); devidopt
&& *devidopt
>= 0
525 && static_cast<uint
>(*devidopt
) < DeviceNames
.size())
526 defaultid
= *devidopt
;
528 for(size_t i
{0};i
< DeviceNames
.size();++i
)
530 if(DeviceNames
[i
].mCaptureChannels
> 0)
532 if(defaultid
>= 0 && static_cast<uint
>(defaultid
) == i
)
533 devices
.emplace(devices
.cbegin(), DeviceNames
[i
].mName
);
535 devices
.emplace_back(DeviceNames
[i
].mName
);
544 BackendPtr
PortBackendFactory::createBackend(DeviceBase
*device
, BackendType type
)
546 if(type
== BackendType::Playback
)
547 return BackendPtr
{new PortPlayback
{device
}};
548 if(type
== BackendType::Capture
)
549 return BackendPtr
{new PortCapture
{device
}};
553 BackendFactory
&PortBackendFactory::getFactory()
555 static PortBackendFactory factory
{};