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 "backends/portaudio.h"
34 #include "ringbuffer.h"
36 #include <portaudio.h>
41 constexpr ALCchar pa_device
[] = "PortAudio Default";
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
);
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
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
;
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
};
104 ERR("Error closing stream: %s\n", Pa_GetErrorText(err
));
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
));
119 void PortPlayback::open(const ALCchar
*name
)
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
)
139 mParams
.sampleFormat
= paInt8
;
142 mParams
.sampleFormat
= paUInt8
;
147 mParams
.sampleFormat
= paInt16
;
152 mParams
.sampleFormat
= paInt32
;
155 mParams
.sampleFormat
= paFloat32
;
160 PaError err
{Pa_OpenStream(&mStream
, nullptr, &mParams
, mDevice
->Frequency
, mDevice
->UpdateSize
,
161 paNoFlag
, &PortPlayback::writeCallbackC
, this)};
164 if(mParams
.sampleFormat
== paFloat32
)
166 mParams
.sampleFormat
= paInt16
;
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
;
195 ERR("Unexpected sample format: 0x%lx\n", mParams
.sampleFormat
);
199 if(mParams
.channelCount
== 2)
200 mDevice
->FmtChans
= DevFmtStereo
;
201 else if(mParams
.channelCount
== 1)
202 mDevice
->FmtChans
= DevFmtMono
;
205 ERR("Unexpected channel count: %u\n", mParams
.channelCount
);
208 SetDefaultChannelOrder(mDevice
);
213 bool PortPlayback::start()
215 PaError err
{Pa_StartStream(mStream
)};
218 ERR("Pa_StartStream() returned an error: %s\n", Pa_GetErrorText(err
));
224 void PortPlayback::stop()
226 PaError err
{Pa_StopStream(mStream
)};
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
};
264 ERR("Error closing stream: %s\n", Pa_GetErrorText(err
));
269 int PortCapture::readCallback(const void *inputBuffer
, void*,
270 unsigned long framesPerBuffer
, const PaStreamCallbackTimeInfo
*,
271 const PaStreamCallbackFlags
)
273 mRing
->write(inputBuffer
, framesPerBuffer
);
278 void PortCapture::open(const ALCchar
*name
)
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
)
301 mParams
.sampleFormat
= paInt8
;
304 mParams
.sampleFormat
= paUInt8
;
307 mParams
.sampleFormat
= paInt16
;
310 mParams
.sampleFormat
= paInt32
;
313 mParams
.sampleFormat
= paFloat32
;
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)};
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
)};
341 ERR("Error starting stream: %s\n", Pa_GetErrorText(err
));
347 void PortCapture::stop()
349 PaError err
{Pa_StopStream(mStream
)};
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
);
367 bool PortBackendFactory::init()
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"
381 # define PALIB "libportaudio.so.2"
384 pa_handle
= LoadLib(PALIB
);
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; \
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
);
409 if((err
=Pa_Initialize()) != paNoError
)
411 ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err
));
418 if((err
=Pa_Initialize()) != paNoError
)
420 ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err
));
427 bool PortBackendFactory::querySupport(BackendType type
)
428 { return (type
== BackendType::Playback
|| type
== BackendType::Capture
); }
430 void PortBackendFactory::probe(DevProbe type
, std::string
*outnames
)
434 case DevProbe::Playback
:
435 case DevProbe::Capture
:
436 /* Includes null char. */
437 outnames
->append(pa_device
, sizeof(pa_device
));
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
}};
451 BackendFactory
&PortBackendFactory::getFactory()
453 static PortBackendFactory factory
{};