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"
29 #include "alc/alconfig.h"
30 #include "alnumeric.h"
31 #include "core/device.h"
32 #include "core/logging.h"
34 #include "ringbuffer.h"
36 #include <portaudio.h>
41 constexpr char 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(DeviceBase
*device
) noexcept
: BackendBase
{device
} { }
76 ~PortPlayback() override
;
78 int writeCallback(const void *inputBuffer
, void *outputBuffer
, unsigned long framesPerBuffer
,
79 const PaStreamCallbackTimeInfo
*timeInfo
, const PaStreamCallbackFlags statusFlags
) noexcept
;
80 static int writeCallbackC(const void *inputBuffer
, void *outputBuffer
,
81 unsigned long framesPerBuffer
, const PaStreamCallbackTimeInfo
*timeInfo
,
82 const PaStreamCallbackFlags statusFlags
, void *userData
) noexcept
84 return static_cast<PortPlayback
*>(userData
)->writeCallback(inputBuffer
, outputBuffer
,
85 framesPerBuffer
, timeInfo
, statusFlags
);
88 void open(const char *name
) override
;
89 bool reset() override
;
90 void start() override
;
93 PaStream
*mStream
{nullptr};
94 PaStreamParameters mParams
{};
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
, unsigned long framesPerBuffer
,
110 const PaStreamCallbackTimeInfo
*, const PaStreamCallbackFlags
) noexcept
112 mDevice
->renderSamples(outputBuffer
, static_cast<uint
>(framesPerBuffer
),
113 static_cast<uint
>(mParams
.channelCount
));
118 void PortPlayback::open(const char *name
)
122 else if(strcmp(name
, pa_device
) != 0)
123 throw al::backend_exception
{al::backend_error::NoDevice
, "Device name \"%s\" not found",
126 PaStreamParameters params
{};
127 auto devidopt
= ConfigValueInt(nullptr, "port", "device");
128 if(devidopt
&& *devidopt
>= 0) params
.device
= *devidopt
;
129 else params
.device
= Pa_GetDefaultOutputDevice();
130 params
.suggestedLatency
= mDevice
->BufferSize
/ static_cast<double>(mDevice
->Frequency
);
131 params
.hostApiSpecificStreamInfo
= nullptr;
133 params
.channelCount
= ((mDevice
->FmtChans
== DevFmtMono
) ? 1 : 2);
135 switch(mDevice
->FmtType
)
138 params
.sampleFormat
= paInt8
;
141 params
.sampleFormat
= paUInt8
;
146 params
.sampleFormat
= paInt16
;
151 params
.sampleFormat
= paInt32
;
154 params
.sampleFormat
= paFloat32
;
160 PaError err
{Pa_OpenStream(&stream
, nullptr, ¶ms
, mDevice
->Frequency
, mDevice
->UpdateSize
,
161 paNoFlag
, &PortPlayback::writeCallbackC
, this)};
164 if(params
.sampleFormat
== paFloat32
)
166 params
.sampleFormat
= paInt16
;
169 throw al::backend_exception
{al::backend_error::NoDevice
, "Failed to open stream: %s",
170 Pa_GetErrorText(err
)};
173 Pa_CloseStream(mStream
);
176 mUpdateSize
= mDevice
->UpdateSize
;
178 mDevice
->DeviceName
= name
;
181 bool PortPlayback::reset()
183 const PaStreamInfo
*streamInfo
{Pa_GetStreamInfo(mStream
)};
184 mDevice
->Frequency
= static_cast<uint
>(streamInfo
->sampleRate
);
185 mDevice
->UpdateSize
= mUpdateSize
;
187 if(mParams
.sampleFormat
== paInt8
)
188 mDevice
->FmtType
= DevFmtByte
;
189 else if(mParams
.sampleFormat
== paUInt8
)
190 mDevice
->FmtType
= DevFmtUByte
;
191 else if(mParams
.sampleFormat
== paInt16
)
192 mDevice
->FmtType
= DevFmtShort
;
193 else if(mParams
.sampleFormat
== paInt32
)
194 mDevice
->FmtType
= DevFmtInt
;
195 else if(mParams
.sampleFormat
== paFloat32
)
196 mDevice
->FmtType
= DevFmtFloat
;
199 ERR("Unexpected sample format: 0x%lx\n", mParams
.sampleFormat
);
203 if(mParams
.channelCount
>= 2)
204 mDevice
->FmtChans
= DevFmtStereo
;
205 else if(mParams
.channelCount
== 1)
206 mDevice
->FmtChans
= DevFmtMono
;
209 ERR("Unexpected channel count: %u\n", mParams
.channelCount
);
212 setDefaultChannelOrder();
217 void PortPlayback::start()
219 const PaError err
{Pa_StartStream(mStream
)};
221 throw al::backend_exception
{al::backend_error::DeviceError
, "Failed to start playback: %s",
222 Pa_GetErrorText(err
)};
225 void PortPlayback::stop()
227 PaError err
{Pa_StopStream(mStream
)};
229 ERR("Error stopping stream: %s\n", Pa_GetErrorText(err
));
233 struct PortCapture final
: public BackendBase
{
234 PortCapture(DeviceBase
*device
) noexcept
: BackendBase
{device
} { }
235 ~PortCapture() override
;
237 int readCallback(const void *inputBuffer
, void *outputBuffer
, unsigned long framesPerBuffer
,
238 const PaStreamCallbackTimeInfo
*timeInfo
, const PaStreamCallbackFlags statusFlags
) noexcept
;
239 static int readCallbackC(const void *inputBuffer
, void *outputBuffer
,
240 unsigned long framesPerBuffer
, const PaStreamCallbackTimeInfo
*timeInfo
,
241 const PaStreamCallbackFlags statusFlags
, void *userData
) noexcept
243 return static_cast<PortCapture
*>(userData
)->readCallback(inputBuffer
, outputBuffer
,
244 framesPerBuffer
, timeInfo
, statusFlags
);
247 void open(const char *name
) override
;
248 void start() override
;
249 void stop() override
;
250 void captureSamples(al::byte
*buffer
, uint samples
) override
;
251 uint
availableSamples() override
;
253 PaStream
*mStream
{nullptr};
254 PaStreamParameters mParams
;
256 RingBufferPtr mRing
{nullptr};
258 DEF_NEWDEL(PortCapture
)
261 PortCapture::~PortCapture()
263 PaError err
{mStream
? Pa_CloseStream(mStream
) : paNoError
};
265 ERR("Error closing stream: %s\n", Pa_GetErrorText(err
));
270 int PortCapture::readCallback(const void *inputBuffer
, void*, unsigned long framesPerBuffer
,
271 const PaStreamCallbackTimeInfo
*, const PaStreamCallbackFlags
) noexcept
273 mRing
->write(inputBuffer
, framesPerBuffer
);
278 void PortCapture::open(const char *name
)
282 else if(strcmp(name
, pa_device
) != 0)
283 throw al::backend_exception
{al::backend_error::NoDevice
, "Device name \"%s\" not found",
286 uint samples
{mDevice
->BufferSize
};
287 samples
= maxu(samples
, 100 * mDevice
->Frequency
/ 1000);
288 uint frame_size
{mDevice
->frameSizeFromFmt()};
290 mRing
= RingBuffer::Create(samples
, frame_size
, false);
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 throw al::backend_exception
{al::backend_error::DeviceError
, "%s samples not supported",
318 DevFmtTypeString(mDevice
->FmtType
)};
320 mParams
.channelCount
= static_cast<int>(mDevice
->channelsFromFmt());
322 PaError err
{Pa_OpenStream(&mStream
, &mParams
, nullptr, mDevice
->Frequency
,
323 paFramesPerBufferUnspecified
, paNoFlag
, &PortCapture::readCallbackC
, this)};
325 throw al::backend_exception
{al::backend_error::NoDevice
, "Failed to open stream: %s",
326 Pa_GetErrorText(err
)};
328 mDevice
->DeviceName
= name
;
332 void PortCapture::start()
334 const PaError err
{Pa_StartStream(mStream
)};
336 throw al::backend_exception
{al::backend_error::DeviceError
,
337 "Failed to start recording: %s", Pa_GetErrorText(err
)};
340 void PortCapture::stop()
342 PaError err
{Pa_StopStream(mStream
)};
344 ERR("Error stopping stream: %s\n", Pa_GetErrorText(err
));
348 uint
PortCapture::availableSamples()
349 { return static_cast<uint
>(mRing
->readSpace()); }
351 void PortCapture::captureSamples(al::byte
*buffer
, uint samples
)
352 { mRing
->read(buffer
, samples
); }
357 bool PortBackendFactory::init()
365 # define PALIB "portaudio.dll"
366 #elif defined(__APPLE__) && defined(__MACH__)
367 # define PALIB "libportaudio.2.dylib"
368 #elif defined(__OpenBSD__)
369 # define PALIB "libportaudio.so"
371 # define PALIB "libportaudio.so.2"
374 pa_handle
= LoadLib(PALIB
);
378 #define LOAD_FUNC(f) do { \
379 p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(pa_handle, #f)); \
380 if(p##f == nullptr) \
382 CloseLib(pa_handle); \
383 pa_handle = nullptr; \
387 LOAD_FUNC(Pa_Initialize
);
388 LOAD_FUNC(Pa_Terminate
);
389 LOAD_FUNC(Pa_GetErrorText
);
390 LOAD_FUNC(Pa_StartStream
);
391 LOAD_FUNC(Pa_StopStream
);
392 LOAD_FUNC(Pa_OpenStream
);
393 LOAD_FUNC(Pa_CloseStream
);
394 LOAD_FUNC(Pa_GetDefaultOutputDevice
);
395 LOAD_FUNC(Pa_GetDefaultInputDevice
);
396 LOAD_FUNC(Pa_GetStreamInfo
);
399 if((err
=Pa_Initialize()) != paNoError
)
401 ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err
));
408 if((err
=Pa_Initialize()) != paNoError
)
410 ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err
));
417 bool PortBackendFactory::querySupport(BackendType type
)
418 { return (type
== BackendType::Playback
|| type
== BackendType::Capture
); }
420 std::string
PortBackendFactory::probe(BackendType type
)
422 std::string outnames
;
425 case BackendType::Playback
:
426 case BackendType::Capture
:
427 /* Includes null char. */
428 outnames
.append(pa_device
, sizeof(pa_device
));
434 BackendPtr
PortBackendFactory::createBackend(DeviceBase
*device
, BackendType type
)
436 if(type
== BackendType::Playback
)
437 return BackendPtr
{new PortPlayback
{device
}};
438 if(type
== BackendType::Capture
)
439 return BackendPtr
{new PortCapture
{device
}};
443 BackendFactory
&PortBackendFactory::getFactory()
445 static PortBackendFactory factory
{};