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"
37 #include <portaudio.h>
42 constexpr ALCchar pa_device
[] = "PortAudio Default";
47 #define MAKE_FUNC(x) decltype(x) * p##x
48 MAKE_FUNC(Pa_Initialize
);
49 MAKE_FUNC(Pa_Terminate
);
50 MAKE_FUNC(Pa_GetErrorText
);
51 MAKE_FUNC(Pa_StartStream
);
52 MAKE_FUNC(Pa_StopStream
);
53 MAKE_FUNC(Pa_OpenStream
);
54 MAKE_FUNC(Pa_CloseStream
);
55 MAKE_FUNC(Pa_GetDefaultOutputDevice
);
56 MAKE_FUNC(Pa_GetDefaultInputDevice
);
57 MAKE_FUNC(Pa_GetStreamInfo
);
61 #define Pa_Initialize pPa_Initialize
62 #define Pa_Terminate pPa_Terminate
63 #define Pa_GetErrorText pPa_GetErrorText
64 #define Pa_StartStream pPa_StartStream
65 #define Pa_StopStream pPa_StopStream
66 #define Pa_OpenStream pPa_OpenStream
67 #define Pa_CloseStream pPa_CloseStream
68 #define Pa_GetDefaultOutputDevice pPa_GetDefaultOutputDevice
69 #define Pa_GetDefaultInputDevice pPa_GetDefaultInputDevice
70 #define Pa_GetStreamInfo pPa_GetStreamInfo
75 struct PortPlayback final
: public BackendBase
{
76 PortPlayback(ALCdevice
*device
) noexcept
: BackendBase
{device
} { }
77 ~PortPlayback() override
;
79 int writeCallback(const void *inputBuffer
, void *outputBuffer
, unsigned long framesPerBuffer
,
80 const PaStreamCallbackTimeInfo
*timeInfo
, const PaStreamCallbackFlags statusFlags
) noexcept
;
81 static int writeCallbackC(const void *inputBuffer
, void *outputBuffer
,
82 unsigned long framesPerBuffer
, const PaStreamCallbackTimeInfo
*timeInfo
,
83 const PaStreamCallbackFlags statusFlags
, void *userData
) noexcept
85 return static_cast<PortPlayback
*>(userData
)->writeCallback(inputBuffer
, outputBuffer
,
86 framesPerBuffer
, timeInfo
, statusFlags
);
89 void open(const ALCchar
*name
) override
;
90 bool reset() override
;
91 void start() override
;
94 PaStream
*mStream
{nullptr};
95 PaStreamParameters mParams
{};
96 ALuint mUpdateSize
{0u};
98 DEF_NEWDEL(PortPlayback
)
101 PortPlayback::~PortPlayback()
103 PaError err
{mStream
? Pa_CloseStream(mStream
) : paNoError
};
105 ERR("Error closing stream: %s\n", Pa_GetErrorText(err
));
110 int PortPlayback::writeCallback(const void*, void *outputBuffer
, unsigned long framesPerBuffer
,
111 const PaStreamCallbackTimeInfo
*, const PaStreamCallbackFlags
) noexcept
113 mDevice
->renderSamples(outputBuffer
, static_cast<ALuint
>(framesPerBuffer
),
114 static_cast<ALuint
>(mParams
.channelCount
));
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 throw al::backend_exception
{ALC_INVALID_VALUE
, "Failed to open stream: %s",
170 Pa_GetErrorText(err
)};
173 mDevice
->DeviceName
= name
;
176 bool PortPlayback::reset()
178 const PaStreamInfo
*streamInfo
{Pa_GetStreamInfo(mStream
)};
179 mDevice
->Frequency
= static_cast<ALuint
>(streamInfo
->sampleRate
);
180 mDevice
->UpdateSize
= mUpdateSize
;
182 if(mParams
.sampleFormat
== paInt8
)
183 mDevice
->FmtType
= DevFmtByte
;
184 else if(mParams
.sampleFormat
== paUInt8
)
185 mDevice
->FmtType
= DevFmtUByte
;
186 else if(mParams
.sampleFormat
== paInt16
)
187 mDevice
->FmtType
= DevFmtShort
;
188 else if(mParams
.sampleFormat
== paInt32
)
189 mDevice
->FmtType
= DevFmtInt
;
190 else if(mParams
.sampleFormat
== paFloat32
)
191 mDevice
->FmtType
= DevFmtFloat
;
194 ERR("Unexpected sample format: 0x%lx\n", mParams
.sampleFormat
);
198 if(mParams
.channelCount
== 2)
199 mDevice
->FmtChans
= DevFmtStereo
;
200 else if(mParams
.channelCount
== 1)
201 mDevice
->FmtChans
= DevFmtMono
;
204 ERR("Unexpected channel count: %u\n", mParams
.channelCount
);
207 setDefaultChannelOrder();
212 void PortPlayback::start()
214 const PaError err
{Pa_StartStream(mStream
)};
216 throw al::backend_exception
{ALC_INVALID_DEVICE
, "Failed to start playback: %s",
217 Pa_GetErrorText(err
)};
220 void PortPlayback::stop()
222 PaError err
{Pa_StopStream(mStream
)};
224 ERR("Error stopping stream: %s\n", Pa_GetErrorText(err
));
228 struct PortCapture final
: public BackendBase
{
229 PortCapture(ALCdevice
*device
) noexcept
: BackendBase
{device
} { }
230 ~PortCapture() override
;
232 int readCallback(const void *inputBuffer
, void *outputBuffer
, unsigned long framesPerBuffer
,
233 const PaStreamCallbackTimeInfo
*timeInfo
, const PaStreamCallbackFlags statusFlags
) noexcept
;
234 static int readCallbackC(const void *inputBuffer
, void *outputBuffer
,
235 unsigned long framesPerBuffer
, const PaStreamCallbackTimeInfo
*timeInfo
,
236 const PaStreamCallbackFlags statusFlags
, void *userData
) noexcept
238 return static_cast<PortCapture
*>(userData
)->readCallback(inputBuffer
, outputBuffer
,
239 framesPerBuffer
, timeInfo
, statusFlags
);
242 void open(const ALCchar
*name
) override
;
243 void start() override
;
244 void stop() override
;
245 ALCenum
captureSamples(al::byte
*buffer
, ALCuint samples
) override
;
246 ALCuint
availableSamples() override
;
248 PaStream
*mStream
{nullptr};
249 PaStreamParameters mParams
;
251 RingBufferPtr mRing
{nullptr};
253 DEF_NEWDEL(PortCapture
)
256 PortCapture::~PortCapture()
258 PaError err
{mStream
? Pa_CloseStream(mStream
) : paNoError
};
260 ERR("Error closing stream: %s\n", Pa_GetErrorText(err
));
265 int PortCapture::readCallback(const void *inputBuffer
, void*, unsigned long framesPerBuffer
,
266 const PaStreamCallbackTimeInfo
*, const PaStreamCallbackFlags
) noexcept
268 mRing
->write(inputBuffer
, framesPerBuffer
);
273 void PortCapture::open(const ALCchar
*name
)
277 else if(strcmp(name
, pa_device
) != 0)
278 throw al::backend_exception
{ALC_INVALID_VALUE
, "Device name \"%s\" not found", name
};
280 ALuint samples
{mDevice
->BufferSize
};
281 samples
= maxu(samples
, 100 * mDevice
->Frequency
/ 1000);
282 ALuint frame_size
{mDevice
->frameSizeFromFmt()};
284 mRing
= RingBuffer::Create(samples
, frame_size
, false);
286 auto devidopt
= ConfigValueInt(nullptr, "port", "capture");
287 if(devidopt
&& *devidopt
>= 0) mParams
.device
= *devidopt
;
288 else mParams
.device
= Pa_GetDefaultOutputDevice();
289 mParams
.suggestedLatency
= 0.0f
;
290 mParams
.hostApiSpecificStreamInfo
= nullptr;
292 switch(mDevice
->FmtType
)
295 mParams
.sampleFormat
= paInt8
;
298 mParams
.sampleFormat
= paUInt8
;
301 mParams
.sampleFormat
= paInt16
;
304 mParams
.sampleFormat
= paInt32
;
307 mParams
.sampleFormat
= paFloat32
;
311 throw al::backend_exception
{ALC_INVALID_VALUE
, "%s samples not supported",
312 DevFmtTypeString(mDevice
->FmtType
)};
314 mParams
.channelCount
= static_cast<int>(mDevice
->channelsFromFmt());
316 PaError err
{Pa_OpenStream(&mStream
, &mParams
, nullptr, mDevice
->Frequency
,
317 paFramesPerBufferUnspecified
, paNoFlag
, &PortCapture::readCallbackC
, this)};
319 throw al::backend_exception
{ALC_INVALID_VALUE
, "Failed to open stream: %s",
320 Pa_GetErrorText(err
)};
322 mDevice
->DeviceName
= name
;
326 void PortCapture::start()
328 const PaError err
{Pa_StartStream(mStream
)};
330 throw al::backend_exception
{ALC_INVALID_DEVICE
, "Failed to start recording: %s",
331 Pa_GetErrorText(err
)};
334 void PortCapture::stop()
336 PaError err
{Pa_StopStream(mStream
)};
338 ERR("Error stopping stream: %s\n", Pa_GetErrorText(err
));
342 ALCuint
PortCapture::availableSamples()
343 { return static_cast<ALCuint
>(mRing
->readSpace()); }
345 ALCenum
PortCapture::captureSamples(al::byte
*buffer
, ALCuint samples
)
347 mRing
->read(buffer
, samples
);
354 bool PortBackendFactory::init()
362 # define PALIB "portaudio.dll"
363 #elif defined(__APPLE__) && defined(__MACH__)
364 # define PALIB "libportaudio.2.dylib"
365 #elif defined(__OpenBSD__)
366 # define PALIB "libportaudio.so"
368 # define PALIB "libportaudio.so.2"
371 pa_handle
= LoadLib(PALIB
);
375 #define LOAD_FUNC(f) do { \
376 p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(pa_handle, #f)); \
377 if(p##f == nullptr) \
379 CloseLib(pa_handle); \
380 pa_handle = nullptr; \
384 LOAD_FUNC(Pa_Initialize
);
385 LOAD_FUNC(Pa_Terminate
);
386 LOAD_FUNC(Pa_GetErrorText
);
387 LOAD_FUNC(Pa_StartStream
);
388 LOAD_FUNC(Pa_StopStream
);
389 LOAD_FUNC(Pa_OpenStream
);
390 LOAD_FUNC(Pa_CloseStream
);
391 LOAD_FUNC(Pa_GetDefaultOutputDevice
);
392 LOAD_FUNC(Pa_GetDefaultInputDevice
);
393 LOAD_FUNC(Pa_GetStreamInfo
);
396 if((err
=Pa_Initialize()) != paNoError
)
398 ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err
));
405 if((err
=Pa_Initialize()) != paNoError
)
407 ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err
));
414 bool PortBackendFactory::querySupport(BackendType type
)
415 { return (type
== BackendType::Playback
|| type
== BackendType::Capture
); }
417 std::string
PortBackendFactory::probe(BackendType type
)
419 std::string outnames
;
422 case BackendType::Playback
:
423 case BackendType::Capture
:
424 /* Includes null char. */
425 outnames
.append(pa_device
, sizeof(pa_device
));
431 BackendPtr
PortBackendFactory::createBackend(ALCdevice
*device
, BackendType type
)
433 if(type
== BackendType::Playback
)
434 return BackendPtr
{new PortPlayback
{device
}};
435 if(type
== BackendType::Capture
)
436 return BackendPtr
{new PortCapture
{device
}};
440 BackendFactory
&PortBackendFactory::getFactory()
442 static PortBackendFactory factory
{};